From 05b5da6330235a8727fc3ecc6fbeae8bcdfc4bea Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sun, 27 Oct 2024 00:10:58 -0400 Subject: [PATCH] Dev docs --- CONTRIBUTING.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++ core/src/Path.purs | 26 ++++++++++++------------- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 46151bfcb..056533417 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,3 +111,50 @@ Learn by doing and get your hands dirty! [f-f]: https://github.com/f-f [discord]: https://purescript.org/chat [spago-issues]: https://github.com/purescript/spago/issues + +## Working with file paths + +File paths are very important in Spago. A very big chunk of Spago does is +shuffling files around and manipulating their paths. Representing them as plain +strings is not enough. + +Spago has three different kinds of paths, represented as distinct types: + +- `RootPath` can generally be the root of anything, but in practice it usually + points to the root directory of the current workspace. It is constructed in + `Main.purs` close to the entry point and is available in all `ReaderT` + environments as `rootPath :: RootPath` +- `LocalPath` is path of a particular file or directory within the workspace. It + doesn't have to be literally _within_ the workspace directory - e.g. a custom + dependency that lives somewhere on the local file system, - but it's still + _relative_ to the workspace. A `LocalPath` is explicitly broken into two + parts: a `RootPath` and the "local" part relative to the root. This is useful + for printing out workspace-relative paths in user-facing output, while still + retaining the full path for actual file operations. A `LocalPath` can be + constructed by appending to a `RootPath`. Once so constructed, the `LocalPath` + always retains the same root, no matter what subsequent manipulations are done + to it. Therefore, if you have a `LocalPath` value, its root is probably + pointing to the current workspace directory. +- `GlobalPath` is for things that are not related to the current workspace. + Examples include paths to executables, such as `node` and `purs`, and global + directories, such as `registryPath` and `globalCachePath`. + +Paths can be appended by using the `` operator. It is overloaded for all +three path types and allows to append string segments to them. When appending to +a `RootPath`, the result comes out as `LocalPath`. You cannot produce a new +`RootPath` by appending. + +Most code that deals with the workspace operates in `LocalPath` values. Most +code that deals with external and global things operates in `GlobalPath` values. +Lower-level primitives, such as in the `Spago.FS` module, are polymorphic and +can take all three path types as parameters. + +For example: + +```haskell +rootPath <- Path.mkRootPath =<< Paths.cwd +config <- readConfig (rootPath "spago.yaml") +let srcDir = rootPath "src" +compileResult <- callCompiler [ srcDir "Main.purs", srcDir "Lib.purs" ] +FS.writeFile (rootPath "result.json") (serialize compipleResult) +``` \ No newline at end of file diff --git a/core/src/Path.purs b/core/src/Path.purs index 0ed5f2abb..351ea6b4b 100644 --- a/core/src/Path.purs +++ b/core/src/Path.purs @@ -69,28 +69,28 @@ class (Show path, Eq path, Ord path) <= IsPath path where withForwardSlashes :: path -> path instance IsPath LocalPath where - toGlobal (LocalPath { root: RootPath root, local }) = + toGlobal (LocalPath { root: RootPath root, local }) = GlobalPath $ Path.concat [ root, local ] relativeTo path root | rootPart path == root = path | otherwise = toGlobal path `relativeTo` root quote (LocalPath path) - | path.local == "" = "\".\"" + | path.local == "" = "\".\"" | otherwise = "\"" <> path.local <> "\"" - replaceExtension p r (LocalPath path) = + replaceExtension p r (LocalPath path) = LocalPath <<< path { local = _ } <$> replaceExtension_ p r path.local - withForwardSlashes (LocalPath path) = + withForwardSlashes (LocalPath path) = LocalPath { root: withForwardSlashes path.root, local: withForwardSlashes' path.local } instance IsPath GlobalPath where toGlobal = identity - relativeTo (GlobalPath path) (RootPath root) = + relativeTo (GlobalPath path) (RootPath root) = LocalPath { root: RootPath root, local: Path.relative root path } - quote (GlobalPath path) = + quote (GlobalPath path) = "\"" <> path <> "\"" - replaceExtension p r (GlobalPath path) = + replaceExtension p r (GlobalPath path) = GlobalPath <$> replaceExtension_ p r path - withForwardSlashes (GlobalPath path) = + withForwardSlashes (GlobalPath path) = GlobalPath $ withForwardSlashes' path instance IsPath RootPath where @@ -100,17 +100,17 @@ instance IsPath RootPath where replaceExtension p r (RootPath path) = RootPath <$> replaceExtension_ p r path withForwardSlashes (RootPath path) = RootPath $ withForwardSlashes' path -class AppendPath base path result | base path -> result where - appendPath :: base -> path -> result -instance AppendPath RootPath AdHocFilePath LocalPath where +class AppendPath base result | base -> result where + appendPath :: base -> AdHocFilePath -> result +instance AppendPath RootPath LocalPath where appendPath root local | Path.isAbsolute local = global local `relativeTo` root | otherwise = LocalPath { root, local } -instance AppendPath LocalPath AdHocFilePath LocalPath where +instance AppendPath LocalPath LocalPath where appendPath (LocalPath { root, local }) path | Path.isAbsolute path = global path `relativeTo` root | otherwise = LocalPath { root, local: Path.concat [ local, path ] } -instance AppendPath GlobalPath AdHocFilePath GlobalPath where +instance AppendPath GlobalPath GlobalPath where appendPath (GlobalPath path) p | Path.isAbsolute p = GlobalPath p | otherwise = GlobalPath $ Path.concat [ path, p ]