Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
Synopsis
- newtype ProjectConfigPath = ProjectConfigPath (NonEmpty FilePath)
- projectConfigPathRoot :: ProjectConfigPath -> FilePath
- nullProjectConfigPath :: ProjectConfigPath
- consProjectConfigPath :: FilePath -> ProjectConfigPath -> ProjectConfigPath
- docProjectConfigPath :: ProjectConfigPath -> Doc
- docProjectConfigPaths :: [ProjectConfigPath] -> Doc
- cyclicalImportMsg :: ProjectConfigPath -> Doc
- docProjectConfigPathFailReason :: VR -> ProjectConfigPath -> Doc
- isCyclicConfigPath :: ProjectConfigPath -> Bool
- isTopLevelConfigPath :: ProjectConfigPath -> Bool
- canonicalizeConfigPath :: FilePath -> ProjectConfigPath -> IO ProjectConfigPath
Project Config Path Manipulation
newtype ProjectConfigPath Source #
Path to a configuration file, either a singleton project root, or a longer
list representing a path to an import. The path is a non-empty list that we
build up by prepending relative imports with consProjectConfigPath
.
An import can be a URI, such as a stackage cabal.config, but we do not support URIs in the middle of the path, URIs that import other URIs, or URIs that import local files.
List elements are relative to each other but once canonicalized, elements are relative to the directory of the project root.
Instances
projectConfigPathRoot :: ProjectConfigPath -> FilePath Source #
The root of the path, the project itself.
nullProjectConfigPath :: ProjectConfigPath Source #
Used by some tests as a dummy "unused" project root.
consProjectConfigPath :: FilePath -> ProjectConfigPath -> ProjectConfigPath Source #
Prepends the path of the importee to the importer path.
Messages
docProjectConfigPath :: ProjectConfigPath -> Doc Source #
Renders the path like this;
D.config
imported by: C.config
imported by: B.config
imported by: A.project
>>> render . docProjectConfigPath $ ProjectConfigPath $ "D.config" :| ["C.config", "B.config", "A.project"]
"D.confign imported by: C.confign imported by: B.confign imported by: A.project"
docProjectConfigPaths :: [ProjectConfigPath] -> Doc Source #
Renders the paths as a list without showing which path imports another,
like this;
- cabal.project
- project-cabal/constraints.config
- project-cabal/ghc-latest.config
- project-cabal/ghc-options.config
- project-cabal/pkgs.config
- project-cabalpkgsbenchmarks.config
- project-cabalpkgsbuildinfo.config
- project-cabalpkgscabal.config
- project-cabalpkgsinstall.config
- project-cabalpkgsintegration-tests.config
- project-cabalpkgstests.config
>>>
:{
do let ps = [ ProjectConfigPath ("cabal.project" :| []) , ProjectConfigPath ("project-cabal/constraints.config" :| ["cabal.project"]) , ProjectConfigPath ("project-cabal/ghc-latest.config" :| ["cabal.project"]) , ProjectConfigPath ("project-cabal/ghc-options.config" :| ["cabal.project"]) , ProjectConfigPath ("project-cabal/pkgs.config" :| ["cabal.project"]) , ProjectConfigPath ("project-cabal/pkgs/benchmarks.config" :| ["project-cabal/pkgs.config","cabal.project"]) , ProjectConfigPath ("project-cabal/pkgs/buildinfo.config" :| ["project-cabal/pkgs.config","cabal.project"]) , ProjectConfigPath ("project-cabal/pkgs/cabal.config" :| ["project-cabal/pkgs.config","cabal.project"]) , ProjectConfigPath ("project-cabal/pkgs/install.config" :| ["project-cabal/pkgs.config","cabal.project"]) , ProjectConfigPath ("project-cabal/pkgs/integration-tests.config" :| ["project-cabal/pkgs.config","cabal.project"]) , ProjectConfigPath ("project-cabal/pkgs/tests.config" :| ["project-cabal/pkgs.config","cabal.project"]) ] return . render $ docProjectConfigPaths ps :} "- cabal.project\n- project-cabal/constraints.config\n- project-cabal/ghc-latest.config\n- project-cabal/ghc-options.config\n- project-cabal/pkgs.config\n- project-cabal/pkgs/benchmarks.config\n- project-cabal/pkgs/buildinfo.config\n- project-cabal/pkgs/cabal.config\n- project-cabal/pkgs/install.config\n- project-cabal/pkgs/integration-tests.config\n- project-cabal/pkgs/tests.config"
cyclicalImportMsg :: ProjectConfigPath -> Doc Source #
A message for a cyclical import, assuming the head of the path is the duplicate.
Checks and Normalization
isCyclicConfigPath :: ProjectConfigPath -> Bool Source #
Check if the path has duplicates. A cycle of imports is not allowed. This
check should only be done after the path has been canonicalized with
canonicalizeConfigPath
. This is because the import path may contain paths
that are the same in relation to their importers but different in relation to
the project root directory.
isTopLevelConfigPath :: ProjectConfigPath -> Bool Source #
Check if the project config path is top-level, meaning it was not included by some other project config.
canonicalizeConfigPath :: FilePath -> ProjectConfigPath -> IO ProjectConfigPath Source #
Normalizes and canonicalizes a path removing .
and '..' indirections.
Makes the path relative to the given directory (typically the project root)
instead of relative to the file it was imported from.
It converts paths like this:
└─ hops-0.project
└─ hops/hops-1.config
└─ ../hops-2.config
└─ hops/hops-3.config
└─ ../hops-4.config
└─ hops/hops-5.config
└─ ../hops-6.config
└─ hops/hops-7.config
└─ ../hops-8.config
└─ hops/hops-9.config
Into paths like this:
└─ hops-0.project
└─ hops/hops-1.config
└─ hops-2.config
└─ hops/hops-3.config
└─ hops-4.config
└─ hops/hops-5.config
└─ hops-6.config
└─ hops/hops-7.config
└─ hops-8.config
└─ hops/hops-9.config
That way we have hops-8.config
instead of
.hops..hops..hops..hops../hops-8.config
.
Let's see how canonicalizePath
works that is used in the implementation
then we'll see how canonicalizeConfigPath
works.
>>>
let d = testDir
>>>
makeRelative d <$> canonicalizePath (d </> "hops/../hops/../hops/../hops/../hops-8.config")
"hops-8.config"
>>>
let d = testDir
>>>
p <- canonicalizeConfigPath d (ProjectConfigPath $ (d </> "hops/../hops/../hops/../hops/../hops-8.config") :| [])
>>>
render $ docProjectConfigPath p
"hops-8.config"
>>>
:{
do let expected = unlines [ "hops/hops-9.config" , " imported by: hops-8.config" , " imported by: hops/hops-7.config" , " imported by: hops-6.config" , " imported by: hops/hops-5.config" , " imported by: hops-4.config" , " imported by: hops/hops-3.config" , " imported by: hops-2.config" , " imported by: hops/hops-1.config" , " imported by: hops-0.project" ] let d = testDir let configPath = ProjectConfigPath ("hops/hops-9.config" :| [ "../hops-8.config" , "hops/hops-7.config" , "../hops-6.config" , "hops/hops-5.config" , "../hops-4.config" , "hops/hops-3.config" , "../hops-2.config" , "hops/hops-1.config" , d </> "hops-0.project"]) p <- canonicalizeConfigPath d configPath return $ expected == render (docProjectConfigPath p) ++ "\n" :} True