Skip to content
heff

std.glob

Glob patterns, as a shell writes them: src/**/*.hf, read once (compile), then matched against a path (matches) or expanded from a directory into the paths there that match (glob). A pattern is cut at each / into segments, matched one by one against a path’s (std.path’s segments); a pattern starting with / matches an absolute path, any other a relative one. In a segment:

  • a rune stands for itself; \ before a rune, that rune (\*, \[, \\);
  • *: any run of runes, none too, in the segment (never past a /, as Go’s filepath.Match), a . first in a name too;
  • ?: one rune;
  • [abc], [a-z0-9]: one rune of the class; [^...] or [!...]: one not of it (a ] first in it is one of it, as a - first or last is; \ escapes in it too);
  • **, a segment of its own: any number of segments, none too.

Runes are compared as they are: case counts.

import co
import exception
import std.filesystem
import std.glob as Glob
import std.path
-- | the heff sources under a directory, at any depth
sources : {e : HFunctor}
-> {{disk : filesystem <: e}}
-> {{ex : exception FileError <: e}}
-> Path
-> Co e (List Path)
sources dir = case Glob.compile "**/*.hf" of
Left why -> pure Nil
Right g -> Glob.glob dir g

Glob

data Glob

A pattern read: whether it starts with /, and its segments.

compile

compile : String -> Either String Glob

A pattern read, or why it cannot be: a [ with no ], a \ at the end, a range backwards, a ** with more in its segment. An empty segment, or ., is none (a//./b is a/b).

>>> either (\why -> why) (\g -> "read") (compile "src/**/*.hf")
"read"
>>> either (\why -> why) (\g -> "read") (compile "[ab")
"a `[` with no `]`"
>>> either (\why -> why) (\g -> "read") (compile "a\\")
"a `\\` at the end"
>>> either (\why -> why) (\g -> "read") (compile "[z-a]")
"a range of a class backwards"
>>> either (\why -> why) (\g -> "read") (compile "src/a**")
"a `**` with more in its segment: `**` is a segment of its own"

matches

matches : Glob -> Path -> Bool

Whether the pattern matches a path, segment by segment: both absolute, or neither, and each segment of the pattern matching one of the path’s — a **, any number of them.

>>> either (\why -> False) (\g -> matches g "main.hf") (compile "*.hf")
True
>>> either (\why -> False) (\g -> matches g "src/main.hf") (compile "*.hf")
False
>>> either (\why -> False) (\g -> matches g ".profile") (compile "*")
True
>>> either (\why -> Nil) (\g -> List.map (\p -> matches g p) ["src/a.hf", "src/x/y/b.hf", "a.hf", "src/x/y"]) (compile "src/**/*.hf")
[True, True, False, False]
>>> either (\why -> Nil) (\g -> List.map (\p -> matches g p) ["a/b", "a", "b"]) (compile "**")
[True, True, True]
>>> either (\why -> Nil) (\g -> List.map (\p -> matches g p) ["a/x/b/y/c", "a/b/c", "a/c", "a/b/x"]) (compile "a/**/b/**/c")
[True, True, False, False]
>>> either (\why -> Nil) (\g -> List.map (\p -> matches g p) ["abc", "a\u{e9}c", "ac", "abbc"]) (compile "a?c")
[True, True, False, False]
>>> either (\why -> Nil) (\g -> List.map (\p -> matches g p) ["xb", "xd", "x-", "x]"]) (compile "x[]a-c-]")
[True, False, True, True]
>>> either (\why -> Nil) (\g -> List.map (\p -> matches g p) ["b", "d", "!"]) (compile "[!a-c]")
[False, True, True]
>>> either (\why -> Nil) (\g -> List.map (\p -> matches g p) ["a*b", "axb"]) (compile "a\\*b")
[True, False]
>>> either (\why -> Nil) (\g -> List.map (\p -> matches g p) ["aaaab", "aaaa"]) (compile "*a*a*b")
[True, False]
>>> either (\why -> Nil) (\g -> List.map (\p -> matches g p) ["/usr/bin", "usr/bin"]) (compile "/usr/*")
[True, False]

glob

glob : {e : HFunctor}
-> {{ev : filesystem <: e}}
-> {{ex : exception FileError <: e}}
-> Path
-> Glob
-> Co e (List Path)

The paths the pattern matches from root — a relative pattern from it, an absolute one from the root — each root and the segments after it, sorted, name by name, as walk visits them. Only the directories the pattern reaches are read: a segment with no *, ? nor class is gone on to, not looked for, and a ** walks below where it is, links not followed (elsewhere a link is followed, as Go’s filepath.Glob follows one). A directory not there, or not a directory, has no paths that match; what else cannot be read is thrown.