0 | module Libraries.Utils.Path
10 | import Libraries.Data.String.Extra
11 | import Libraries.Text.Token
12 | import Libraries.Text.Lexer
13 | import Libraries.Text.Parser
20 | export infixl 5 </>
, />
22 | export infixr 7 <..>
28 | dirSeparator = if isWindows then '\\' else '/'
32 | pathSeparator : Char
33 | pathSeparator = if isWindows then ';' else ':'
71 | volume : Maybe Volume
81 | (==) (UNC l1 l2) (UNC r1 r2) = l1 == r1 && r1 == r2
82 | (==) (Disk l) (Disk r) = l == r
87 | (==) CurDir CurDir = True
88 | (==) ParentDir ParentDir = True
89 | (==) (Normal l) (Normal r) = l == r
94 | (==) (MkPath l1 l2 l3 _) (MkPath r1 r2 r3 _) =
95 | l1 == r1 && l2 == r2 && l3 == r3
100 | emptyPath = MkPath Nothing False [] False
109 | show ParentDir = ".."
110 | show (Normal normal) = normal
114 | show (UNC server share) = "\\\\" ++ server ++ "\\" ++ share
115 | show (Disk disk) = String.singleton disk ++ ":"
122 | sep = String.singleton dirSeparator
123 | showVol = maybe "" show path.volume
124 | showRoot = if path.hasRoot then sep else ""
125 | showBody = join sep $
map show path.body
126 | showTrail = if path.hasTrailSep then sep else ""
128 | showVol ++ showRoot ++ showBody ++ showTrail
134 | data PathTokenKind = PTText | PTPunct Char
136 | Eq PathTokenKind where
137 | (==) PTText PTText = True
138 | (==) (PTPunct c1) (PTPunct c2) = c1 == c2
142 | PathToken = Token PathTokenKind
144 | PathGrammar : Bool -> Type -> Type
145 | PathGrammar = Grammar () PathToken
147 | TokenKind PathTokenKind where
148 | TokType PTText = String
149 | TokType (PTPunct _) = ()
151 | tokValue PTText x = x
152 | tokValue (PTPunct _) _ = ()
154 | pathTokenMap : TokenMap PathToken
155 | pathTokenMap = toTokenMap $
156 | [ (is '/', PTPunct '/')
157 | , (is '\\', PTPunct '\\')
158 | , (is ':', PTPunct ':')
159 | , (is '?', PTPunct '?')
160 | , (some $
non $
oneOf "/\\:?", PTText)
163 | lexPath : String -> List (WithBounds PathToken)
164 | lexPath str = let (tokens, _, _, _) = lex pathTokenMap str in tokens
167 | bodySeparator : PathGrammar True ()
168 | bodySeparator = (match $
PTPunct '\\') <|> (match $
PTPunct '/')
175 | verbatim : PathGrammar True ()
178 | ignore $
count (exactly 2) $
match $
PTPunct '\\'
179 | match $
PTPunct '?'
180 | match $
PTPunct '\\'
184 | unc : PathGrammar True Volume
187 | ignore $
count (exactly 2) $
match $
PTPunct '\\'
188 | server <- match PTText
190 | share <- match PTText
191 | pure $
UNC server share
194 | verbatimUnc : PathGrammar True Volume
198 | server <- match PTText
200 | share <- match PTText
201 | pure $
UNC server share
204 | disk : PathGrammar True Volume
207 | text <- match PTText
208 | disk <- case unpack text of
209 | (disk :: xs) => pure disk
210 | [] => fail "Expects disk"
211 | match $
PTPunct ':'
212 | pure $
Disk (toUpper disk)
215 | verbatimDisk : PathGrammar True Volume
222 | parseVolume : PathGrammar True Volume
229 | parseBody : PathGrammar True Body
232 | text <- match PTText
233 | the (PathGrammar False _) $
235 | ".." => pure ParentDir
237 | normal => pure (Normal normal)
239 | parsePath : PathGrammar False Path
242 | vol <- optional parseVolume
243 | root <- optional (some bodySeparator)
244 | body <- sepBy (some bodySeparator) parseBody
245 | trailSep <- optional (some bodySeparator)
246 | let body = filter (\case Normal s => ltrim s /= "";
_ => True) body
247 | let body = case body of
249 | (x::xs) => x :: delete CurDir xs
250 | pure $
MkPath vol (isJust root) body (isJust trailSep)
275 | parse : String -> Path
277 | case parse parsePath (lexPath str) of
278 | Right (_, path, _) => path
285 | isAbsolute' : Path -> Bool
288 | case path.volume of
289 | Just (UNC {}) => True
290 | Just (Disk {}) => path.hasRoot
295 | append' : (left : Path) -> (right : Path) -> Path
296 | append' left right =
297 | if isAbsolute' right || isJust right.volume then
299 | else if hasRoot right then
300 | { volume := left.volume } right
302 | { body := left.body ++ right.body, hasTrailSep := right.hasTrailSep } left
304 | splitPath' : Path -> List Path
306 | case splitRoot path of
307 | (Just root, other) => root :: iterateBody (path.body) (path.hasTrailSep)
308 | (Nothing, other) => iterateBody (path.body) (path.hasTrailSep)
310 | splitRoot : Path -> (Maybe Path, Path)
311 | splitRoot path@(MkPath Nothing False _ _) = (Nothing, path)
312 | splitRoot (MkPath vol root xs trailSep) =
313 | (Just $
MkPath vol root [] False, MkPath Nothing False xs trailSep)
315 | iterateBody : List Body -> (trailSep : Bool) -> List Path
316 | iterateBody [] _ = []
317 | iterateBody [x] trailSep = [MkPath Nothing False [x] trailSep]
318 | iterateBody (x::y::xs) trailSep =
319 | (MkPath Nothing False [x] False) :: iterateBody (y::xs) trailSep
321 | splitParent' : Path -> Maybe (Path, Path)
322 | splitParent' path =
327 | parent = { body := init (x::xs), hasTrailSep := False } path
328 | child = MkPath Nothing False [last (x::xs)] path.hasTrailSep
330 | Just (parent, child)
332 | parent' : Path -> Maybe Path
333 | parent' = map fst . splitParent'
335 | fileName' : Path -> Maybe String
337 | findNormal $
reverse path.body
339 | findNormal : List Body -> Maybe String
340 | findNormal ((Normal normal)::xs) = Just normal
341 | findNormal (CurDir::xs) = findNormal xs
342 | findNormal _ = Nothing
344 | setFileName' : (name : String) -> Path -> Path
345 | setFileName' name path =
346 | if isJust (fileName' path) then
347 | append' (fromMaybe emptyPath $
parent' path) (parse name)
349 | append' path (parse name)
352 | splitFileName : String -> (String, String)
353 | splitFileName name =
354 | case break (== '.') $
reverse $
unpack name of
355 | (_, []) => (name, "")
356 | (_, ['.']) => (name, "")
357 | (revExt, (dot :: revStem)) =>
358 | ((pack $
reverse revStem), (pack $
reverse revExt))
368 | splitExtensions : String -> (String, List String)
369 | splitExtensions name = case map pack $
split (== '.') (unpack name) of
370 | ("" ::: base :: exts) => ("." ++ base, exts)
371 | (base ::: exts) => (base, exts)
386 | isAbsolute : String -> Bool
387 | isAbsolute = isAbsolute' . parse
391 | isRelative : String -> Bool
392 | isRelative = not . isAbsolute
408 | (/>) : (left : Path) -> (right : String) -> Path
409 | (/>) left right = append' left (parse right)
425 | (</>) : (left : String) -> (right : String) -> String
426 | (</>) left right = show $
parse left /> right
435 | joinPath : List String -> String
436 | joinPath xs = show $
foldl (/>) (parse "") xs
445 | splitPath : String -> List String
446 | splitPath = map show . splitPath' . parse
454 | splitParent : String -> Maybe (String, String)
457 | (parent, child) <- splitParent' (parse path)
458 | pure (show parent, show child)
464 | parent : String -> Maybe String
465 | parent = map show . parent' . parse
474 | parents : String -> List String
475 | parents = map show . List.iterate parent' . parse
485 | isBaseOf : (base : String) -> (target : String) -> Bool
486 | isBaseOf base target =
488 | MkPath baseVol baseRoot baseBody _ = parse base
489 | MkPath targetVol targetRoot targetBody _ = parse target
491 | baseVol == targetVol
492 | && baseRoot == targetRoot
493 | && (baseBody `isPrefixOf` targetBody)
499 | dropBase : (base : String) -> (target : String) -> Maybe String
500 | dropBase base target =
502 | let MkPath baseVol baseRoot baseBody _ = parse base
503 | let MkPath targetVol targetRoot targetBody targetTrialSep = parse target
504 | if baseVol == targetVol && baseRoot == targetRoot then Just () else Nothing
505 | body <- dropBody baseBody targetBody
506 | pure $
show $
MkPath Nothing False body targetTrialSep
508 | dropBody : (base : List Body) -> (target : List Body) -> Maybe (List Body)
509 | dropBody [] ys = Just ys
510 | dropBody xs [] = Nothing
511 | dropBody (x::xs) (y::ys) = if x == y then dropBody xs ys else Nothing
520 | fileName : String -> Maybe String
521 | fileName = fileName' . parse
532 | fileStem : String -> Maybe String
533 | fileStem path = pure $
fst $
splitFileName !(fileName path)
544 | extension : String -> Maybe String
545 | extension path = fileName path >>=
546 | filter (/= "") . Just . snd . splitFileName
556 | extensions : String -> Maybe (List String)
557 | extensions path = snd . splitExtensions <$> fileName path
564 | setFileName : (name : String) -> String -> String
565 | setFileName name path = show $
setFileName' name (parse path)
578 | (<.>) : String -> (extension : String) -> String
582 | ext = pack $
dropWhile (\char => char == '.' || isSpace char) (unpack ext)
583 | ext = if ext == "" then "" else "." ++ ext
585 | case fileName' path' of
587 | let (stem, _) = splitFileName name in
588 | show $
setFileName' (stem ++ ext) path'
602 | (<..>) : String -> (extension : String) -> String
606 | ext = pack $
dropWhile (\char => char == '.' || isSpace char) (unpack ext)
607 | ext = if ext == "" then "" else "." ++ ext
609 | case fileName' path' of
611 | let (stem, _) = splitExtensions name in
612 | show $
setFileName' (stem ++ ext) path'
618 | dropExtension : String -> String
619 | dropExtension path = path <.> ""
625 | dropExtensions : String -> String
626 | dropExtensions path = path <..> ""
630 | pathLookup : List String -> IO (Maybe String)
631 | pathLookup candidates
632 | = do path <- idrisGetEnv "PATH"
633 | let extensions = if isWindows then [".exe", ".cmd", ".bat", ""] else [""]
634 | let pathList = forget $
String.split (== pathSeparator) $
fromMaybe "/usr/bin:/usr/local/bin" path
635 | let candidates = [p ++ "/" ++ x ++ y | p <- pathList,
638 | firstExists candidates