lens-filesystem-0.1.0.1: Lens interface for your filesystem; still a bit experimental
Copyright(c) Chris Penner 2019
LicenseBSD3
Safe HaskellNone
LanguageHaskell2010

Control.Lens.FileSystem

Description

Note that this package is experimental, test things carefully before performing destructive operations. I'm not responsible if things go wrong.

This package is meant to be used alongside combinators from 'lens-action'; for example ^!, ^!! and act.

Synopsis

File System Helpers

ls :: Monoid r => Acting IO r FilePath [FilePath] Source #

List the files at a given directory If the focused path isn't a directory this fold will return 0 results

>>> "./test/data" ^! ls
["./test/data/flat","./test/data/symlinked","./test/data/.dotfile","./test/data/permissions","./test/data/nested"]

ls'ed :: Monoid r => Acting IO r FilePath FilePath Source #

Fold over all files in the given directory. If the focused path isn't a directory this fold will return 0 results This is an alias for @ls . traversed@

>>> "./test/data" ^!! ls'ed
["./test/data/flat","./test/data/symlinked","./test/data/.dotfile","./test/data/permissions","./test/data/nested"]

path :: FilePath -> Getter FilePath FilePath Source #

Append a path the end of the current path. This uses </> for cross platform compatibility so you don't need leading/trailing slashes here

>>> "./src" ^! path "Control"
"./src/Control"

pathL :: [FilePath] -> Getter FilePath FilePath Source #

Create a filepath from a list of path segments, then append it to the focused path.

>>> "." ^! pathL ["a", "b", "c"]
"./a/b/c"

branching :: [FilePath] -> Fold FilePath FilePath Source #

Branch a fold into many sub-paths. E.g. if we want to crawl into BOTH of src and test directories we can do:

>>> "." ^!! branching ["src", "test"] . ls
[["./src/Control"],["./test/Spec.hs","./test/data"]]

dirs :: Monoid r => Acting IO r FilePath FilePath Source #

Filter for only paths which point to a valid directory

>>> "./test" ^!! ls'ed
["./test/Spec.hs","./test/data"]
>>> "./test" ^!! ls'ed . dirs
["./test/data"]

files :: Monoid r => Acting IO r FilePath FilePath Source #

Filter for only paths which point to a valid file

>>> "./test" ^!! ls'ed
["./test/Spec.hs","./test/data"]
>>> "./test" ^!! ls'ed . files
["./test/Spec.hs"]

contents :: (Indexable FilePath p, Effective IO r f, Monoid r) => Over' p f FilePath String Source #

Get the contents of a file This fold will return 0 results if the path does not exist, if it isn't a file, or if reading the file causes any exceptions.

This fold lifts the path of the current file into the index of the fold in case you need it downstream.

>>> "./test/data/flat/file.md" ^! contents
"markdown\n"
>>> "./test/data/flat/file.md" ^! contents . withIndex
("./test/data/flat/file.md","markdown\n")

exts :: [String] -> Traversal' FilePath FilePath Source #

Filter the fold for only files which have ANY of the given file extensions. E.g. to find all Haskell or Markdown files in the current directory:

>>> "./test/" ^!! crawled . exts ["hs", "md"]
["./test/Spec.hs","./test/data/flat/file.md","./test/data/symlinked/file.md"]

crawled :: Monoid r => Acting IO r FilePath FilePath Source #

Crawl over every file AND directory in the given path.

>>> "./test/data/nested/top" ^!! crawled
["./test/data/nested/top","./test/data/nested/top/mid","./test/data/nested/top/mid/bottom","./test/data/nested/top/mid/bottom/floor.txt"]

crawling :: Monoid r => Acting IO r FilePath FilePath -> Acting IO r FilePath FilePath Source #

Continually run the given fold until all branches hit dead ends, yielding over all elements encountered the way.

>>> "./test/data" ^!! crawling (ls'ed . filtered ((== "flat") . view filename))
["./test/data","./test/data/flat"]

absolute :: MonadicFold IO FilePath FilePath Source #

Make filepaths absolute in reference to the current working directory

>>> "./test/data" ^! absolute
"/Users/chris/dev/lens-filesystem/test/data"

withPerms :: Monoid r => [Permissions -> Bool] -> Acting IO r FilePath FilePath Source #

Filter for only paths which have ALL of the given file-permissions See readable, writable, executable

>>> "./test/data" ^!! crawled . withPerms [readable, executable]
["./test/data/permissions/exe"]

symLinksFollowed :: Monoid r => Acting IO r FilePath FilePath Source #

If the path is a symlink, rewrite the path to its destination and keep folding If it's not a symlink; pass the path onwards as is.

>>> "./test/data/symlinked" ^! symLinksFollowed
"flat"

Combinators

filteredM :: (Monad m, Monoid r) => (a -> m Bool) -> Acting m r a a Source #

Filter a fold using a monadic action

merging :: (Applicative f, Contravariant f) => LensLike' f s a -> LensLike' f s a -> LensLike' f s a Source #

Merge two folds

including :: (Applicative f, Contravariant f) => LensLike' f a a -> LensLike' f a a Source #

Include the results of an additional fold alongside the original values

Exception Handling

recovering :: (Monad m, Alternative m, Monoid r, Effective m r f) => Over' p f s a -> Over' p f s a Source #

If a given fold fails (e.g. with an exception), recover and simply return 0 elements rather than crashing.

tryOrContinue :: (Monad m, Alternative m) => Acting m r a a -> Acting m r a a Source #

Try the given fold, if it throws an exception then return the input as the output instead

tryCatch :: (Monad m, Alternative m) => Acting m r s b -> (s -> m b) -> Acting m r s b Source #

Try the given fold, if it throws an exception then use the given handler to compute a replacement value and continue with that.

Re-exports

(</>) :: FilePath -> FilePath -> FilePath infixr 5 #

Combine two paths with a path separator. If the second path starts with a path separator or a drive letter, then it returns the second. The intention is that readFile (dir </> file) will access the same file as setCurrentDirectory dir; readFile file.

Posix:   "/directory" </> "file.ext" == "/directory/file.ext"
Windows: "/directory" </> "file.ext" == "/directory\\file.ext"
         "directory" </> "/file.ext" == "/file.ext"
Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x

Combined:

Posix:   "/" </> "test" == "/test"
Posix:   "home" </> "bob" == "home/bob"
Posix:   "x:" </> "foo" == "x:/foo"
Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar"
Windows: "home" </> "bob" == "home\\bob"

Not combined:

Posix:   "home" </> "/bob" == "/bob"
Windows: "home" </> "C:\\bob" == "C:\\bob"

Not combined (tricky):

On Windows, if a filepath starts with a single slash, it is relative to the root of the current drive. In [1], this is (confusingly) referred to as an absolute path. The current behavior of </> is to never combine these forms.

Windows: "home" </> "/bob" == "/bob"
Windows: "home" </> "\\bob" == "\\bob"
Windows: "C:\\home" </> "\\bob" == "\\bob"

On Windows, from [1]: "If a file name begins with only a disk designator but not the backslash after the colon, it is interpreted as a relative path to the current directory on the drive with the specified letter." The current behavior of </> is to never combine these forms.

Windows: "D:\\foo" </> "C:bar" == "C:bar"
Windows: "C:\\foo" </> "C:bar" == "C:bar"