diff --git a/Changelog.md b/Changelog.md index c37dac95da..a795f465be 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ ## Unreleased +- Resolve an issue parsing toml configuration files. ([#1459](https://github.com/fossas/fossa-cli/pull/1459)) - Gradle: ignore deprecated configurations ([#1457](https://github.com/fossas/fossa-cli/pull/1457)) ## 3.9.30 diff --git a/integration-test/Analysis/Python/PoetrySpec.hs b/integration-test/Analysis/Python/PoetrySpec.hs index 57519b87b9..40a3da1a02 100644 --- a/integration-test/Analysis/Python/PoetrySpec.hs +++ b/integration-test/Analysis/Python/PoetrySpec.hs @@ -24,4 +24,4 @@ poetry = spec :: Spec spec = do - testSuiteDepResultSummary poetry PoetryProjectType (DependencyResultsSummary 66 29 69 1 Complete) + testSuiteDepResultSummary poetry PoetryProjectType (DependencyResultsSummary 65 29 69 1 Complete) diff --git a/spectrometer.cabal b/spectrometer.cabal index edda480615..2a1ea978b0 100644 --- a/spectrometer.cabal +++ b/spectrometer.cabal @@ -152,7 +152,7 @@ common deps , th-lift-instances ^>=0.1.17 , time >=1.9 && <1.13 , tls ^>=2.0 - , tomland ^>=1.3.3.0 + , toml-parser ^>=2.0.1.0 , transformers , typed-process ^>=0.2.6 , unix-compat ^>=0.7 diff --git a/src/Effect/ReadFS.hs b/src/Effect/ReadFS.hs index 3f04897a43..11e1852d7e 100644 --- a/src/Effect/ReadFS.hs +++ b/src/Effect/ReadFS.hs @@ -122,6 +122,7 @@ import System.PosixCompat.Types (CDev (..), CIno (..)) import Text.Megaparsec (Parsec, runParser) import Text.Megaparsec.Error (errorBundlePretty) import Toml qualified +import Toml.Schema qualified -- | A unique file identifier for a directory. -- Uniqueness is guaranteed within a single OS. @@ -365,12 +366,12 @@ readContentsJson file = context ("Parsing JSON file '" <> toText (toString file) Left err -> errSupport (fileParseErrorSupportMsg file) $ fatal $ FileParseError (toString file) (toText err) Right a -> pure a -readContentsToml :: (Has ReadFS sig m, Has Diagnostics sig m) => Toml.TomlCodec a -> Path Abs File -> m a -readContentsToml codec file = context ("Parsing TOML file '" <> toText (toString file) <> "'") $ do +readContentsToml :: (Toml.Schema.FromValue a, Has ReadFS sig m, Has Diagnostics sig m) => Path Abs File -> m a +readContentsToml file = context ("Parsing TOML file '" <> toText (toString file) <> "'") $ do contents <- readContentsText file - case Toml.decode codec contents of - Left err -> errSupport (fileParseErrorSupportMsg file) $ fatal $ FileParseError (toString file) (Toml.prettyTomlDecodeErrors err) - Right a -> pure a + case Toml.decode contents of + Toml.Failure err -> errSupport (fileParseErrorSupportMsg file) $ fatal $ FileParseError (toString file) (toText $ show err) + Toml.Success _ a -> pure a -- | Read YAML from a file readContentsYaml :: (FromJSON a, Has ReadFS sig m, Has Diagnostics sig m) => Path Abs File -> m a diff --git a/src/Strategy/Cargo.hs b/src/Strategy/Cargo.hs index 86fca51224..801985a2c1 100644 --- a/src/Strategy/Cargo.hs +++ b/src/Strategy/Cargo.hs @@ -88,8 +88,7 @@ import Text.Megaparsec ( try, ) import Text.Megaparsec.Char (char, digitChar, space) -import Toml (TomlCodec, dioptional, diwrap, (.=)) -import Toml qualified +import Toml.Schema qualified import Types ( DepEnvironment (EnvDevelopment, EnvProduction), DepType (CargoType), @@ -244,11 +243,12 @@ data CargoPackage = CargoPackage } deriving (Eq, Show) -cargoPackageCodec :: TomlCodec CargoPackage -cargoPackageCodec = - CargoPackage - <$> dioptional (Toml.text "license") .= license - <*> dioptional (Toml.string "license-file") .= cargoLicenseFile +instance Toml.Schema.FromValue CargoPackage where + fromValue = + Toml.Schema.parseTableFromValue $ + CargoPackage + <$> Toml.Schema.optKey "license" + <*> Toml.Schema.optKey "license-file" -- | Representation of a Cargo.toml file. See -- [here](https://doc.rust-lang.org/cargo/reference/manifest.html) @@ -257,12 +257,11 @@ newtype CargoToml = CargoToml {cargoPackage :: CargoPackage} deriving (Eq, Show) -cargoTomlCodec :: TomlCodec CargoToml -cargoTomlCodec = diwrap (Toml.table cargoPackageCodec "package") --- ^ ^ The above is a bit obscure. It's generating a TomlCodec CargoPackage and --- then using 'diwrap'/Coercible to make a TomlCodec CargoToml. I can't use --- 'CargoToml <$>' because TomlCodec aliases (Codec a a) and only (Codec a) --- has a Functor instance, so I'd end up with a (Codec CargoPackage CargoToml). +instance Toml.Schema.FromValue CargoToml where + fromValue = + Toml.Schema.parseTableFromValue $ + CargoToml + <$> Toml.Schema.reqKey "package" instance LicenseAnalyzeProject CargoProject where licenseAnalyzeProject = analyzeLicenses . cargoToml @@ -271,7 +270,7 @@ instance LicenseAnalyzeProject CargoProject where -- (here)[https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields] analyzeLicenses :: (Has ReadFS sig m, Has Diagnostics sig m) => Path Abs File -> m [LicenseResult] analyzeLicenses tomlPath = do - pkg <- cargoPackage <$> readContentsToml cargoTomlCodec tomlPath + pkg <- cargoPackage <$> readContentsToml tomlPath licensePathText <- maybe (pure Nothing) mkLicensePath (cargoLicenseFile pkg) -- The license-file field in Cargo.toml is relative to the dir of the diff --git a/src/Strategy/Fortran/FpmToml.hs b/src/Strategy/Fortran/FpmToml.hs index a1d801c3b4..8ec1cd4448 100644 --- a/src/Strategy/Fortran/FpmToml.hs +++ b/src/Strategy/Fortran/FpmToml.hs @@ -6,11 +6,10 @@ module Strategy.Fortran.FpmToml ( FpmDependency (..), FpmPathDependency (..), FpmGitDependency (..), + FpmTomlExecutables (..), buildGraph, - fpmTomlCodec, ) where -import Control.Applicative (Alternative ((<|>))) import Control.Effect.Diagnostics (Diagnostics, context) import Data.Foldable (asum) import Data.Map (Map, elems) @@ -25,28 +24,58 @@ import DepTypes ( import Effect.ReadFS (Has, ReadFS, readContentsToml) import Graphing (Graphing, directs, induceJust) import Path -import Toml (TomlCodec, (.=)) import Toml qualified +import Toml.Schema qualified -- | Represents the content of the fpm manifest. --- Reference: https://github.com/fortran-lang/fpm/blob/main/manifest-reference.md +-- Reference: https://fpm.fortran-lang.org/spec/manifest.html data FpmToml = FpmToml { fpmDependencies :: Map Text FpmDependency , fpmDevDependencies :: Map Text FpmDependency - , fpmExecutables :: [Map Text FpmDependency] + , fpmExecutables :: [FpmTomlExecutables] } deriving (Eq, Ord, Show) -fpmTomlCodec :: TomlCodec FpmToml -fpmTomlCodec = - FpmToml - <$> Toml.tableMap Toml._KeyText fpmDependenciesCodec "dependencies" .= fpmDependencies - <*> Toml.tableMap Toml._KeyText fpmDependenciesCodec "dev-dependencies" .= fpmDevDependencies - <*> Toml.list fpmExecutableDependenciesCodec "executable" .= fpmExecutables +instance Toml.Schema.FromValue FpmToml where + fromValue = + Toml.Schema.parseTableFromValue $ + FpmToml + <$> Toml.Schema.pickKey [Toml.Schema.Key "dependencies" Toml.Schema.fromValue, Toml.Schema.Else $ pure mempty] + <*> Toml.Schema.pickKey [Toml.Schema.Key "dev-dependencies" Toml.Schema.fromValue, Toml.Schema.Else $ pure mempty] + <*> Toml.Schema.pickKey [Toml.Schema.Key "executable" Toml.Schema.fromValue, Toml.Schema.Else $ pure []] + +newtype FpmTomlExecutables = FpmTomlExecutables + { fpmExecutableDependencies :: Map Text FpmDependency + } + deriving (Eq, Ord, Show) + +instance Toml.Schema.FromValue FpmTomlExecutables where + fromValue = + Toml.Schema.parseTableFromValue $ + FpmTomlExecutables + <$> Toml.Schema.pickKey [Toml.Schema.Key "dependencies" Toml.Schema.fromValue, Toml.Schema.Else $ pure mempty] data FpmDependency = FpmGitDep FpmGitDependency | FpmPathDep FpmPathDependency + | FpmMetaDep Text + deriving (Eq, Ord, Show) + +instance Toml.Schema.FromValue FpmDependency where + fromValue v@(Toml.Schema.Table' l t) = + Toml.Schema.parseTable + ( Toml.Schema.pickKey + [ Toml.Schema.Key "git" (const (FpmGitDep <$> Toml.Schema.fromValue v)) + , Toml.Schema.Key "path" (const (FpmPathDep <$> Toml.Schema.fromValue v)) + , Toml.Schema.Else (Toml.Schema.failAt (Toml.valueAnn v) "Expected either 'git' or 'path' key got: ") + ] + ) + l + t + fromValue (Toml.Schema.Text' _ t) = pure $ FpmMetaDep t + fromValue v = Toml.Schema.failAt (Toml.valueAnn v) "Invalid dependency value, expected a table or a string" + +newtype FpmMetaDependency = FpmMetaDependency Text deriving (Eq, Ord, Show) newtype FpmPathDependency = FpmPathDependency @@ -54,6 +83,9 @@ newtype FpmPathDependency = FpmPathDependency } deriving (Eq, Ord, Show) +instance Toml.Schema.FromValue FpmPathDependency where + fromValue = Toml.Schema.parseTableFromValue $ FpmPathDependency <$> Toml.Schema.reqKey "path" + data FpmGitDependency = FpmGitDependency { url :: Text , branch :: Maybe Text @@ -62,32 +94,14 @@ data FpmGitDependency = FpmGitDependency } deriving (Eq, Ord, Show) -fpmExecutableDependenciesCodec :: TomlCodec (Map Text FpmDependency) -fpmExecutableDependenciesCodec = Toml.tableMap Toml._KeyText fpmDependenciesCodec "dependencies" - -fpmDependenciesCodec :: Toml.Key -> TomlCodec FpmDependency -fpmDependenciesCodec key = - Toml.dimatch matchFpmPathDep FpmPathDep (Toml.table fpmPathDependencyCodec key) - <|> Toml.dimatch matchFpmGitDep FpmGitDep (Toml.table fpmGitDependencyCodec key) - where - matchFpmPathDep :: FpmDependency -> Maybe FpmPathDependency - matchFpmPathDep (FpmPathDep pathDep) = Just pathDep - matchFpmPathDep _ = Nothing - - matchFpmGitDep :: FpmDependency -> Maybe FpmGitDependency - matchFpmGitDep (FpmGitDep gitDep) = Just gitDep - matchFpmGitDep _ = Nothing - - fpmPathDependencyCodec :: TomlCodec FpmPathDependency - fpmPathDependencyCodec = FpmPathDependency <$> Toml.text "path" .= pathOf - - fpmGitDependencyCodec :: TomlCodec FpmGitDependency - fpmGitDependencyCodec = +instance Toml.Schema.FromValue FpmGitDependency where + fromValue = + Toml.Schema.parseTableFromValue $ FpmGitDependency - <$> Toml.text "git" .= url - <*> Toml.dioptional (Toml.text "branch") .= branch - <*> Toml.dioptional (Toml.text "tag") .= tag - <*> Toml.dioptional (Toml.text "rev") .= rev + <$> Toml.Schema.reqKey "git" + <*> Toml.Schema.optKey "branch" + <*> Toml.Schema.optKey "tag" + <*> Toml.Schema.optKey "rev" buildGraph :: FpmToml -> Graphing Dependency buildGraph fpmToml = induceJust $ foldMap directs [deps, execDeps, devDeps] @@ -96,7 +110,7 @@ buildGraph fpmToml = induceJust $ foldMap directs [deps, execDeps, devDeps] deps = map toProdDependency (elems $ fpmDependencies fpmToml) execDeps :: [Maybe Dependency] - execDeps = map toProdDependency (foldMap elems $ fpmExecutables fpmToml) + execDeps = map toProdDependency (foldMap (elems . fpmExecutableDependencies) (fpmExecutables fpmToml)) devDeps :: [Maybe Dependency] devDeps = map toDevDependency (elems $ fpmDevDependencies fpmToml) @@ -109,6 +123,7 @@ buildGraph fpmToml = induceJust $ foldMap directs [deps, execDeps, devDeps] toDependency :: Maybe DepEnvironment -> FpmDependency -> Maybe Dependency toDependency _ (FpmPathDep _) = Nothing + toDependency _ (FpmMetaDep _) = Nothing toDependency env (FpmGitDep dep) = Just $ Dependency @@ -128,5 +143,5 @@ analyzeFpmToml :: Path Abs File -> m (Graphing Dependency) analyzeFpmToml tomlFile = do - fpmTomlContent <- readContentsToml fpmTomlCodec tomlFile + fpmTomlContent <- readContentsToml tomlFile context "Building dependency graph from fpm.toml" $ pure $ buildGraph fpmTomlContent diff --git a/src/Strategy/Go/GopkgLock.hs b/src/Strategy/Go/GopkgLock.hs index adbd85a0dc..cdd7f55fa5 100644 --- a/src/Strategy/Go/GopkgLock.hs +++ b/src/Strategy/Go/GopkgLock.hs @@ -5,7 +5,6 @@ module Strategy.Go.GopkgLock ( GoLock (..), Project (..), buildGraph, - golockCodec, ) where import Control.Effect.Diagnostics @@ -24,26 +23,19 @@ import Graphing (Graphing) import Path import Strategy.Go.Transitive (fillInTransitive) import Strategy.Go.Types -import Toml (TomlCodec, (.=)) -import Toml qualified - -golockCodec :: TomlCodec GoLock -golockCodec = - GoLock - <$> Toml.list projectCodec "projects" .= lockProjects - -projectCodec :: TomlCodec Project -projectCodec = - Project - <$> Toml.text "name" .= projectName - <*> Toml.dioptional (Toml.text "source") .= projectSource - <*> Toml.text "revision" .= projectRevision +import Toml.Schema qualified newtype GoLock = GoLock { lockProjects :: [Project] } deriving (Eq, Ord, Show) +instance Toml.Schema.FromValue GoLock where + fromValue = + Toml.Schema.parseTableFromValue $ + GoLock + <$> Toml.Schema.reqKey "projects" + data Project = Project { projectName :: Text , projectSource :: Maybe Text @@ -51,6 +43,14 @@ data Project = Project } deriving (Eq, Ord, Show) +instance Toml.Schema.FromValue Project where + fromValue = + Toml.Schema.parseTableFromValue $ + Project + <$> Toml.Schema.reqKey "name" + <*> Toml.Schema.optKey "source" + <*> Toml.Schema.reqKey "revision" + analyze' :: ( Has ReadFS sig m , Has Exec sig m @@ -59,7 +59,7 @@ analyze' :: Path Abs File -> m (Graphing Dependency) analyze' file = graphingGolang $ do - golock <- readContentsToml golockCodec file + golock <- readContentsToml file context "Building dependency graph" $ buildGraph (lockProjects golock) void . recover diff --git a/src/Strategy/Go/GopkgToml.hs b/src/Strategy/Go/GopkgToml.hs index f5659b2e09..5f5fc2ebcd 100644 --- a/src/Strategy/Go/GopkgToml.hs +++ b/src/Strategy/Go/GopkgToml.hs @@ -5,7 +5,6 @@ module Strategy.Go.GopkgToml ( PkgConstraint (..), analyze', buildGraph, - gopkgCodec, ) where import Control.Applicative ((<|>)) @@ -27,23 +26,7 @@ import Graphing (Graphing) import Path import Strategy.Go.Transitive (fillInTransitive) import Strategy.Go.Types -import Toml (TomlCodec, (.=)) -import Toml qualified - -gopkgCodec :: TomlCodec Gopkg -gopkgCodec = - Gopkg - <$> Toml.list constraintCodec "constraint" .= pkgConstraints - <*> Toml.list constraintCodec "override" .= pkgOverrides - -constraintCodec :: TomlCodec PkgConstraint -constraintCodec = - PkgConstraint - <$> Toml.text "name" .= constraintName - <*> Toml.dioptional (Toml.text "source") .= constraintSource - <*> Toml.dioptional (Toml.text "version") .= constraintVersion - <*> Toml.dioptional (Toml.text "branch") .= constraintBranch - <*> Toml.dioptional (Toml.text "revision") .= constraintRevision +import Toml.Schema qualified data Gopkg = Gopkg { pkgConstraints :: [PkgConstraint] @@ -51,6 +34,13 @@ data Gopkg = Gopkg } deriving (Eq, Ord, Show) +instance Toml.Schema.FromValue Gopkg where + fromValue = + Toml.Schema.parseTableFromValue $ + Gopkg + <$> Toml.Schema.reqKey "constraint" + <*> Toml.Schema.reqKey "override" + data PkgConstraint = PkgConstraint { constraintName :: Text , constraintSource :: Maybe Text @@ -60,6 +50,16 @@ data PkgConstraint = PkgConstraint } deriving (Eq, Ord, Show) +instance Toml.Schema.FromValue PkgConstraint where + fromValue = + Toml.Schema.parseTableFromValue $ + PkgConstraint + <$> Toml.Schema.reqKey "name" + <*> Toml.Schema.optKey "source" + <*> Toml.Schema.optKey "version" + <*> Toml.Schema.optKey "branch" + <*> Toml.Schema.optKey "revision" + analyze' :: ( Has ReadFS sig m , Has Exec sig m @@ -68,7 +68,7 @@ analyze' :: Path Abs File -> m (Graphing Dependency) analyze' file = graphingGolang $ do - gopkg <- readContentsToml gopkgCodec file + gopkg <- readContentsToml file context "Building dependency graph" $ buildGraph gopkg void . recover diff --git a/src/Strategy/Python/PDM/Pdm.hs b/src/Strategy/Python/PDM/Pdm.hs index 56935f205f..2f86912921 100644 --- a/src/Strategy/Python/PDM/Pdm.hs +++ b/src/Strategy/Python/PDM/Pdm.hs @@ -16,8 +16,8 @@ import DepTypes ( import Effect.ReadFS (Has, ReadFS, readContentsToml) import Graphing (Graphing, directs) import Path (Abs, Dir, File, Path) -import Strategy.Python.PDM.PdmLock (buildGraph, pdmLockCodec) -import Strategy.Python.Poetry.PyProject (PyProject (..), PyProjectMetadata (..), pyProjectCodec) +import Strategy.Python.PDM.PdmLock (buildGraph) +import Strategy.Python.Poetry.PyProject (PyProject (..), PyProjectMetadata (..), PyProjectPdm (..), PyProjectTool (..)) import Strategy.Python.Util (Req (..), toConstraint) import Text.URI qualified as URI @@ -93,7 +93,7 @@ analyze :: Maybe (Path Abs File) -> m (Graphing Dependency) analyze pyProjectToml pdmLockFile = do - pyproject <- readContentsToml pyProjectCodec pyProjectToml + pyproject <- readContentsToml pyProjectToml -- According to PDM, optional dependencies are not -- prod dependencies, and they are not installed when, @@ -112,7 +112,7 @@ analyze pyProjectToml pdmLockFile = do (toDependency EnvProduction <$> prodReqs) ++ (toDependency EnvDevelopment <$> devReqs) Just pdmLockFile' -> do - pdmLock <- readContentsToml pdmLockCodec pdmLockFile' + pdmLock <- readContentsToml pdmLockFile' pure $ buildGraph prodReqs devReqs pdmLock toDependency :: DepEnvironment -> Req -> Dependency @@ -146,9 +146,13 @@ reqDepVersion (NameReq _ _ Nothing _) = Nothing reqDepVersion (NameReq _ _ (Just ver) _) = Just . toConstraint $ ver reqsFromPdmMetadata :: PyProject -> [Req] -reqsFromPdmMetadata pr = case pyprojectPdmDevDependencies pr of +reqsFromPdmMetadata pr = case pyprojectTool pr of Nothing -> mempty - Just reqs -> concat . Map.elems $ reqs + Just (PyProjectTool{pyprojectPdm}) -> case pyprojectPdm of + Nothing -> mempty + Just (PyProjectPdm{pdmDevDependencies}) -> case pdmDevDependencies of + Nothing -> mempty + Just reqs -> concat . Map.elems $ reqs reqsFromPyProject :: PyProject -> ([Req], [Req]) reqsFromPyProject pr = case pyprojectProject pr of diff --git a/src/Strategy/Python/PDM/PdmLock.hs b/src/Strategy/Python/PDM/PdmLock.hs index 8ff165869d..a34c15a651 100644 --- a/src/Strategy/Python/PDM/PdmLock.hs +++ b/src/Strategy/Python/PDM/PdmLock.hs @@ -1,7 +1,6 @@ module Strategy.Python.PDM.PdmLock ( PdmLock (..), PdmLockPackage (..), - pdmLockCodec, buildGraph, toDependency, ) where @@ -14,9 +13,8 @@ import Data.Text (Text) import DepTypes (DepEnvironment (EnvDevelopment, EnvProduction), DepType (GitType, PipType, URLType, UnresolvedPathType), Dependency (..), VerConstraint (..), hydrateDepEnvs) import Effect.Grapher (deep, direct, edge, evalGrapher, run) import Graphing (Graphing, gmap) -import Strategy.Python.Util (Req (..), reqCodec) -import Toml (TomlCodec, (.=)) -import Toml qualified +import Strategy.Python.Util (Req (..)) +import Toml.Schema qualified -- | Represents pdm lock file. -- @@ -34,6 +32,15 @@ import Toml qualified newtype PdmLock = PdmLock {pdmLockPackages :: [PdmLockPackage]} deriving (Eq, Ord, Show) +instance Toml.Schema.FromValue PdmLock where + fromValue = + Toml.Schema.parseTableFromValue $ + PdmLock + <$> Toml.Schema.pickKey + [ Toml.Schema.Key "package" Toml.Schema.fromValue + , Toml.Schema.Else (pure []) + ] + -- | Represents pdm lock package. -- -- In pdm.lock file it looks like following: @@ -57,19 +64,17 @@ data PdmLockPackage = PdmLockPackage } deriving (Eq, Ord, Show) -pdmLockCodec :: TomlCodec PdmLock -pdmLockCodec = PdmLock <$> Toml.list pdmLockPackageCodec "package" .= pdmLockPackages - -pdmLockPackageCodec :: TomlCodec PdmLockPackage -pdmLockPackageCodec = - PdmLockPackage - <$> Toml.diwrap (Toml.text "name") .= name - <*> Toml.text "version" .= version - <*> Toml.dioptional (Toml.text "git") .= gitUrl - <*> Toml.dioptional (Toml.text "revision") .= gitRevision - <*> Toml.dioptional (Toml.text "path") .= fsPath - <*> Toml.dioptional (Toml.text "url") .= url - <*> Toml.dioptional (Toml.arrayOf reqCodec "dependencies") .= pdmDependencies +instance Toml.Schema.FromValue PdmLockPackage where + fromValue = + Toml.Schema.parseTableFromValue $ + PdmLockPackage + <$> Toml.Schema.reqKey "name" + <*> Toml.Schema.reqKey "version" + <*> Toml.Schema.optKey "git" + <*> Toml.Schema.optKey "revision" + <*> Toml.Schema.optKey "path" + <*> Toml.Schema.optKey "url" + <*> Toml.Schema.optKey "dependencies" toDependency :: [Req] -> [Req] -> PdmLockPackage -> Dependency toDependency prodReqs devReqs pkg = diff --git a/src/Strategy/Python/Poetry.hs b/src/Strategy/Python/Poetry.hs index f27e72d849..a9194f6728 100644 --- a/src/Strategy/Python/Poetry.hs +++ b/src/Strategy/Python/Poetry.hs @@ -47,8 +47,8 @@ import Strategy.Python.Errors ( commitPoetryLockToVCS, ) import Strategy.Python.Poetry.Common (getPoetryBuildBackend, logIgnoredDeps, makePackageToLockDependencyMap, pyProjectDeps, toCanonicalName) -import Strategy.Python.Poetry.PoetryLock (PackageName (..), PoetryLock (..), PoetryLockPackage (..), PoetryMetadata (poetryMetadataLockVersion), poetryLockCodec) -import Strategy.Python.Poetry.PyProject (PyProject (..), allPoetryProductionDeps, pyProjectCodec) +import Strategy.Python.Poetry.PoetryLock (PackageName (..), PoetryLock (..), PoetryLockPackage (..), PoetryMetadata (poetryMetadataLockVersion)) +import Strategy.Python.Poetry.PyProject (PyProject (..), PyProjectTool (..), allPoetryProductionDeps) import Types (DependencyResults (..), DiscoveredProject (..), DiscoveredProjectType (PoetryProjectType), GraphBreadth (..)) newtype PyProjectTomlFile = PyProjectTomlFile {pyProjectTomlPath :: Path Abs File} deriving (Eq, Ord, Show, Generic) @@ -117,7 +117,7 @@ findProjects = walkWithFilters' $ \dir _ files -> do case (poetryLockFile, pyprojectFile) of (poetry, Just pyproject) -> do - poetryProject <- readContentsToml pyProjectCodec pyproject + poetryProject <- readContentsToml pyproject let project = PoetryProject (ProjectDir dir) (PyProjectTomlFile pyproject) (PoetryLockFile <$> poetry) let pyprojectBuildBackend = getPoetryBuildBackend poetryProject @@ -155,10 +155,10 @@ analyze :: PoetryProject -> m DependencyResults analyze PoetryProject{pyProjectToml, poetryLock} = do - pyproject <- readContentsToml pyProjectCodec (pyProjectTomlPath pyProjectToml) + pyproject <- readContentsToml (pyProjectTomlPath pyProjectToml) case poetryLock of Just lockPath -> do - poetryLockProject <- readContentsToml poetryLockCodec (poetryLockPath lockPath) + poetryLockProject <- readContentsToml (poetryLockPath lockPath) _ <- logIgnoredDeps pyproject (Just poetryLockProject) graph <- context "Building dependency graph from pyproject.toml and poetry.lock" . pure $ graphFromPyProjectAndLockFile pyproject poetryLockProject pure $ @@ -221,9 +221,11 @@ graphFromPyProjectAndLockFile pyProject poetryLock = graph directDeps = pyProjectDeps pyProject isDirect :: Dependency -> Bool - isDirect dep = case pyprojectPoetry pyProject of + isDirect dep = case pyprojectTool pyProject of Nothing -> False - Just _ -> any (\n -> toCanonicalName (dependencyName n) == toCanonicalName (dependencyName dep)) directDeps + Just (PyProjectTool{pyprojectPoetry}) -> case pyprojectPoetry of + Nothing -> False + Just _ -> any (\n -> toCanonicalName (dependencyName n) == toCanonicalName (dependencyName dep)) directDeps pkgs :: [PoetryLockPackage] pkgs = poetryLockPackages poetryLock diff --git a/src/Strategy/Python/Poetry/Common.hs b/src/Strategy/Python/Poetry/Common.hs index fe726a9f60..7aa23ad55d 100644 --- a/src/Strategy/Python/Poetry/Common.hs +++ b/src/Strategy/Python/Poetry/Common.hs @@ -33,15 +33,18 @@ import Strategy.Python.Poetry.PyProject ( PyProjectPoetry (..), PyProjectPoetryDetailedVersionDependency (..), PyProjectPoetryGitDependency (..), + PyProjectPoetryGroup (..), + PyProjectPoetryGroupDependencies (..), PyProjectPoetryPathDependency (..), PyProjectPoetryUrlDependency (..), + PyProjectTool (..), allPoetryNonProductionDeps, toDependencyVersion, ) -- | Gets build backend of pyproject. getPoetryBuildBackend :: PyProject -> Maybe Text -getPoetryBuildBackend project = buildBackend <$> pyprojectBuildSystem project +getPoetryBuildBackend project = buildBackend =<< pyprojectBuildSystem project -- | Supported pyproject dependencies. supportedPyProjectDep :: PoetryDependency -> Bool @@ -70,7 +73,10 @@ logIgnoredDeps pyproject poetryLock = for_ notSupportedDepsMsgs (logDebug . pret notSupportedPyProjectDeps = Map.keys $ Map.filter (not . supportedPyProjectDep) $ - maybe Map.empty dependencies (pyprojectPoetry pyproject) + case pyprojectTool pyproject of + Nothing -> mempty + Just (PyProjectTool{pyprojectPoetry}) -> + maybe Map.empty dependencies (pyprojectPoetry) -- | Not supported poetry lock package. supportedPoetryLockDep :: PoetryLockPackage -> Bool @@ -89,8 +95,8 @@ pyProjectDeps project = filter notNamedPython $ map snd allDeps -- These are dependencies coming from dev-dependencies table -- which is pre 1.2.x style, understood by Poetry 1.0–1.2 olderPoetryDevDeps :: Map Text PoetryDependency - olderPoetryDevDeps = case pyprojectPoetry project of - Just (PyProjectPoetry{devDependencies}) -> devDependencies + olderPoetryDevDeps = case pyprojectTool project of + Just (PyProjectTool{pyprojectPoetry}) -> maybe mempty devDependencies pyprojectPoetry _ -> mempty -- These are 'group' dependencies. All group dependencies are optional. @@ -103,12 +109,22 @@ pyProjectDeps project = filter notNamedPython $ map snd allDeps -- \* https://github.com/kowainik/tomland/issues/336 -- \* https://python-poetry.org/docs/managing-dependencies#dependency-groups groupDeps :: Map Text PoetryDependency - groupDeps = case pyprojectPoetry project of - Just (PyProjectPoetry{groupDevDependencies, groupTestDependencies}) -> Map.unions [groupDevDependencies, groupTestDependencies] + groupDeps = case pyprojectTool project of + Just (PyProjectTool{pyprojectPoetry}) -> case pyprojectPoetry of + Just (PyProjectPoetry{pyprojectPoetryGroup}) -> case pyprojectPoetryGroup of + Just (PyProjectPoetryGroup{groupDev, groupTest}) -> case (groupDev, groupTest) of + (Just devDeps, Just testDeps) -> Map.unions [groupDependencies devDeps, groupDependencies testDeps] + (Just devDeps, Nothing) -> groupDependencies devDeps + (Nothing, Just testDeps) -> groupDependencies testDeps + _ -> mempty + _ -> mempty + _ -> mempty _ -> mempty supportedProdDeps :: Map Text PoetryDependency - supportedProdDeps = Map.filter supportedPyProjectDep $ maybe Map.empty dependencies (pyprojectPoetry project) + supportedProdDeps = Map.filter supportedPyProjectDep $ case pyprojectTool project of + Just (PyProjectTool{pyprojectPoetry}) -> maybe Map.empty dependencies pyprojectPoetry + _ -> mempty toDependency :: [DepEnvironment] -> Map Text PoetryDependency -> Map Text Dependency toDependency depEnvs = Map.mapWithKey $ poetrytoDependency depEnvs diff --git a/src/Strategy/Python/Poetry/PoetryLock.hs b/src/Strategy/Python/Poetry/PoetryLock.hs index df598a9bed..1522ad2ae6 100644 --- a/src/Strategy/Python/Poetry/PoetryLock.hs +++ b/src/Strategy/Python/Poetry/PoetryLock.hs @@ -4,18 +4,16 @@ module Strategy.Python.Poetry.PoetryLock ( PackageName (..), PoetryLockPackageSource (..), PoetryLockDependencySpec (..), - poetryLockCodec, -- * for testing only ObjectVersion (..), PoetryMetadata (..), ) where -import Control.Applicative (Alternative ((<|>))) import Data.Map (Map) import Data.Text (Text) -import Toml (TomlCodec, (.=)) import Toml qualified +import Toml.Schema qualified -- | Represents the content of the poetry lock file. data PoetryLock = PoetryLock @@ -24,15 +22,14 @@ data PoetryLock = PoetryLock } deriving (Eq, Ord, Show) -newtype PackageName = PackageName {unPackageName :: Text} deriving (Eq, Ord, Show) +instance Toml.Schema.FromValue PoetryLock where + fromValue = + Toml.Schema.parseTableFromValue $ + PoetryLock + <$> Toml.Schema.reqKey "package" + <*> Toml.Schema.reqKey "metadata" -poetryLockCodec :: TomlCodec PoetryLock -poetryLockCodec = - PoetryLock - <$> Toml.list poetryLockPackageCodec "package" - .= poetryLockPackages - <*> Toml.table poetryMetadataCodec "metadata" - .= poetryLockMetadata +newtype PackageName = PackageName {unPackageName :: Text} deriving (Eq, Ord, Show) -- | Metadata of poetry lock file. data PoetryMetadata = PoetryMetadata @@ -42,15 +39,13 @@ data PoetryMetadata = PoetryMetadata } deriving (Eq, Ord, Show) -poetryMetadataCodec :: TomlCodec PoetryMetadata -poetryMetadataCodec = - PoetryMetadata - <$> Toml.text "lock-version" - .= poetryMetadataLockVersion - <*> Toml.text "content-hash" - .= poetryMetadataContentHash - <*> Toml.text "python-versions" - .= poetryMetadataPythonVersions +instance Toml.Schema.FromValue PoetryMetadata where + fromValue = + Toml.Schema.parseTableFromValue $ + PoetryMetadata + <$> Toml.Schema.reqKey "lock-version" + <*> Toml.Schema.reqKey "content-hash" + <*> Toml.Schema.reqKey "python-versions" -- | A PoetryLockPackageSource represents [package.source] field in poetry.lock. -- Source indicates from where the package was retrieved. @@ -62,6 +57,15 @@ data PoetryLockPackageSource = PoetryLockPackageSource } deriving (Eq, Ord, Show) +instance Toml.Schema.FromValue PoetryLockPackageSource where + fromValue = + Toml.Schema.parseTableFromValue $ + PoetryLockPackageSource + <$> Toml.Schema.reqKey "type" + <*> Toml.Schema.reqKey "url" + <*> Toml.Schema.optKey "reference" + <*> Toml.Schema.optKey "resolved_reference" + -- | Poetry package entry found in poetry lock file. data PoetryLockPackage = PoetryLockPackage { poetryLockPackageName :: PackageName @@ -74,35 +78,16 @@ data PoetryLockPackage = PoetryLockPackage } deriving (Eq, Ord, Show) -poetryLockPackageCodec :: TomlCodec PoetryLockPackage -poetryLockPackageCodec = - PoetryLockPackage - <$> Toml.diwrap (Toml.text "name") - .= poetryLockPackageName - <*> Toml.text "version" - .= poetryLockPackageVersion - <*> Toml.dioptional (Toml.text "category") - .= poetryLockPackageCategory - <*> Toml.bool "optional" - .= poetryLockPackageOptional - <*> Toml.text "python-versions" - .= poetryLockPackagePythonVersions - <*> Toml.tableMap Toml._KeyText poetryLockPackagePoetryLockDependencySpecCodec "dependencies" - .= poetryLockPackageDependencies - <*> Toml.dioptional (Toml.table poetryLockPackageSourceCodec "source") - .= poetryLockPackageSource - -poetryLockPackageSourceCodec :: TomlCodec PoetryLockPackageSource -poetryLockPackageSourceCodec = - PoetryLockPackageSource - <$> Toml.text "type" - .= poetryLockPackageSourceType - <*> Toml.text "url" - .= poetryLockPackageSourceUrl - <*> Toml.dioptional (Toml.text "reference") - .= poetryLockPackageSourceReference - <*> Toml.dioptional (Toml.text "resolved_reference") - .= poetryLockPackageSourceResolvedReference +instance Toml.Schema.FromValue PoetryLockPackage where + fromValue = + Toml.Schema.parseTableFromValue $ + (PoetryLockPackage . PackageName <$> Toml.Schema.reqKey "name") + <*> Toml.Schema.reqKey "version" + <*> Toml.Schema.optKey "category" + <*> Toml.Schema.reqKey "optional" + <*> Toml.Schema.reqKey "python-versions" + <*> Toml.Schema.pickKey [Toml.Schema.Key "dependencies" Toml.Schema.fromValue, Toml.Schema.Else $ pure mempty] + <*> Toml.Schema.optKey "source" data PoetryLockDependencySpec = TextVersion Text @@ -110,31 +95,19 @@ data PoetryLockDependencySpec | MultipleObjectVersionSpec [ObjectVersion] deriving (Eq, Ord, Show) +instance Toml.Schema.FromValue PoetryLockDependencySpec where + fromValue (Toml.Schema.Text' _ t) = pure $ TextVersion t + fromValue v@(Toml.Schema.Table' _ _) = ObjectVersionSpec <$> Toml.Schema.fromValue v + fromValue v@Toml.Schema.List'{} = MultipleObjectVersionSpec <$> Toml.Schema.fromValue v + fromValue v = Toml.Schema.failAt (Toml.valueAnn v) $ "invalid poetry dependency spec " <> Toml.valueType v + newtype ObjectVersion = ObjectVersion { unObjectVersion :: Text } deriving (Eq, Ord, Show) -objectVersionCodec :: TomlCodec ObjectVersion -objectVersionCodec = - ObjectVersion - <$> Toml.text "version" - .= unObjectVersion - -matchTextVersion :: PoetryLockDependencySpec -> Maybe Text -matchTextVersion (TextVersion version) = Just version -matchTextVersion _ = Nothing - -matchObjectVersionSpec :: PoetryLockDependencySpec -> Maybe ObjectVersion -matchObjectVersionSpec (ObjectVersionSpec version) = Just version -matchObjectVersionSpec _ = Nothing - -matchMultipleObjectVersionSpec :: PoetryLockDependencySpec -> Maybe [ObjectVersion] -matchMultipleObjectVersionSpec (MultipleObjectVersionSpec version) = Just version -matchMultipleObjectVersionSpec _ = Nothing - -poetryLockPackagePoetryLockDependencySpecCodec :: Toml.Key -> TomlCodec PoetryLockDependencySpec -poetryLockPackagePoetryLockDependencySpecCodec key = - Toml.dimatch matchTextVersion TextVersion (Toml.text key) - <|> Toml.dimatch matchObjectVersionSpec ObjectVersionSpec (Toml.table objectVersionCodec key) - <|> Toml.dimatch matchMultipleObjectVersionSpec MultipleObjectVersionSpec (Toml.list objectVersionCodec key) +instance Toml.Schema.FromValue ObjectVersion where + fromValue = + Toml.Schema.parseTableFromValue $ + ObjectVersion + <$> Toml.Schema.reqKey "version" diff --git a/src/Strategy/Python/Poetry/PyProject.hs b/src/Strategy/Python/Poetry/PyProject.hs index 3c5152fe1a..29bb2e5aff 100644 --- a/src/Strategy/Python/Poetry/PyProject.hs +++ b/src/Strategy/Python/Poetry/PyProject.hs @@ -1,10 +1,13 @@ module Strategy.Python.Poetry.PyProject ( PyProject (..), PyProjectMetadata (..), + PyProjectTool (..), + PyProjectPdm (..), + PyProjectPoetryGroup (..), + PyProjectPoetryGroupDependencies (..), PyProjectPoetry (..), PyProjectBuildSystem (..), PoetryDependency (..), - pyProjectCodec, PyProjectPoetryPathDependency (..), PyProjectPoetryGitDependency (..), PyProjectPoetryUrlDependency (..), @@ -38,7 +41,7 @@ import DepTypes ( COr ), ) -import Strategy.Python.Util (Req, reqCodec) +import Strategy.Python.Util (Req) import Text.Megaparsec ( Parsec, empty, @@ -48,12 +51,11 @@ import Text.Megaparsec ( parse, some, takeWhileP, - (<|>), ) import Text.Megaparsec.Char (char) import Text.Megaparsec.Char.Lexer qualified as Lexer -import Toml (TomlCodec, (.=)) import Toml qualified +import Toml.Schema qualified type Parser = Parsec Void Text @@ -65,19 +67,48 @@ newtype PackageName = PackageName {unPackageName :: Text} deriving (Eq, Ord, Sho -- when pyproject does not use poetry for build or has no dependencies. data PyProject = PyProject { pyprojectBuildSystem :: Maybe PyProjectBuildSystem - , pyprojectPoetry :: Maybe PyProjectPoetry , pyprojectProject :: Maybe PyProjectMetadata - , pyprojectPdmDevDependencies :: Maybe (Map Text [Req]) + , pyprojectTool :: Maybe PyProjectTool } deriving (Show, Eq, Ord) -pyProjectCodec :: TomlCodec PyProject -pyProjectCodec = - PyProject - <$> Toml.dioptional (Toml.table pyProjectBuildSystemCodec "build-system") .= pyprojectBuildSystem - <*> Toml.dioptional (Toml.table pyProjectPoetryCodec "tool.poetry") .= pyprojectPoetry - <*> Toml.dioptional (Toml.table pyPyProjectMetadataCodec "project") .= pyprojectProject - <*> Toml.dioptional (Toml.tableMap Toml._KeyText (Toml.arrayOf reqCodec) "tool.pdm.dev-dependencies") .= pyprojectPdmDevDependencies +instance Toml.Schema.FromValue PyProject where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProject + <$> Toml.Schema.optKey "build-system" + <*> Toml.Schema.optKey "project" + <*> Toml.Schema.optKey "tool" + +data PyProjectTool = PyProjectTool + { pyprojectPoetry :: Maybe PyProjectPoetry + , pyprojectPdm :: Maybe PyProjectPdm + } + deriving (Show, Eq, Ord) + +instance Toml.Schema.FromValue PyProjectTool where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectTool + <$> Toml.Schema.optKey "poetry" + <*> Toml.Schema.optKey "pdm" + +newtype PyProjectPdm = PyProjectPdm + {pdmDevDependencies :: Maybe (Map Text [Req])} + deriving (Show, Eq, Ord) + +instance Toml.Schema.FromValue PyProjectPdm where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectPdm + <$> Toml.Schema.optKey "dev-dependencies" + +instance Toml.Schema.FromValue PyProjectMetadata where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectMetadata + <$> Toml.Schema.optKey "dependencies" + <*> Toml.Schema.optKey "optional-dependencies" -- | Represents [project] block -- > [project] @@ -94,21 +125,16 @@ data PyProjectMetadata = PyProjectMetadata } deriving (Show, Eq, Ord) -pyPyProjectMetadataCodec :: TomlCodec PyProjectMetadata -pyPyProjectMetadataCodec = - PyProjectMetadata - <$> Toml.dioptional (Toml.arrayOf reqCodec "dependencies") .= pyprojectDependencies - <*> Toml.dioptional (Toml.tableMap Toml._KeyText (Toml.arrayOf reqCodec) "optional-dependencies") .= pyprojectOptionalDependencies - newtype PyProjectBuildSystem = PyProjectBuildSystem - { buildBackend :: Text + { buildBackend :: Maybe Text } deriving (Show, Eq, Ord) -pyProjectBuildSystemCodec :: TomlCodec PyProjectBuildSystem -pyProjectBuildSystemCodec = - PyProjectBuildSystem - <$> Toml.diwrap (Toml.text "build-backend") .= buildBackend +instance Toml.Schema.FromValue PyProjectBuildSystem where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectBuildSystem + <$> Toml.Schema.optKey "build-backend" data PyProjectPoetry = PyProjectPoetry { name :: Maybe Text @@ -124,27 +150,72 @@ data PyProjectPoetry = PyProjectPoetry -- Due to current toml-parsing limitations, we explicitly specify dev, and -- test group only. Note that any dependencies from these groups are excluded -- by default. refer to: https://github.com/kowainik/tomland/issues/336 - groupDevDependencies :: Map Text PoetryDependency - , groupTestDependencies :: Map Text PoetryDependency + pyprojectPoetryGroup :: Maybe PyProjectPoetryGroup } deriving (Show, Eq, Ord) +instance Toml.Schema.FromValue PyProjectPoetry where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectPoetry + <$> Toml.Schema.optKey "name" + <*> Toml.Schema.optKey "version" + <*> Toml.Schema.optKey "description" + <*> Toml.Schema.pickKey [Toml.Schema.Key "dependencies" Toml.Schema.fromValue, Toml.Schema.Else (pure mempty)] + <*> Toml.Schema.pickKey [Toml.Schema.Key "dev-dependencies" Toml.Schema.fromValue, Toml.Schema.Else (pure mempty)] + <*> Toml.Schema.optKey "group" + +data PyProjectPoetryGroup = PyProjectPoetryGroup + { groupDev :: Maybe PyProjectPoetryGroupDependencies + , groupTest :: Maybe PyProjectPoetryGroupDependencies + } + deriving (Show, Eq, Ord) + +instance Toml.Schema.FromValue PyProjectPoetryGroup where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectPoetryGroup + <$> Toml.Schema.optKey "dev" + <*> Toml.Schema.optKey "test" + +newtype PyProjectPoetryGroupDependencies = PyProjectPoetryGroupDependencies + {groupDependencies :: Map Text PoetryDependency} + deriving (Show, Eq, Ord) + +instance Toml.Schema.FromValue PyProjectPoetryGroupDependencies where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectPoetryGroupDependencies + <$> Toml.Schema.pickKey [Toml.Schema.Key "dependencies" Toml.Schema.fromValue, Toml.Schema.Else (pure mempty)] + allPoetryProductionDeps :: PyProject -> Map Text PoetryDependency -allPoetryProductionDeps project = case pyprojectPoetry project of - Just (PyProjectPoetry{dependencies}) -> dependencies +allPoetryProductionDeps project = case pyprojectTool project of + Just (PyProjectTool{pyprojectPoetry}) -> case pyprojectPoetry of + Just (PyProjectPoetry{dependencies}) -> dependencies + _ -> mempty _ -> mempty allPoetryNonProductionDeps :: PyProject -> Map Text PoetryDependency allPoetryNonProductionDeps project = unions [olderPoetryDevDeps, optionalDeps] where optionalDeps :: Map Text PoetryDependency - optionalDeps = case pyprojectPoetry project of - Just (PyProjectPoetry{groupDevDependencies, groupTestDependencies}) -> unions [groupDevDependencies, groupTestDependencies] + optionalDeps = case pyprojectTool project of + Just (PyProjectTool{pyprojectPoetry}) -> case pyprojectPoetry of + Just (PyProjectPoetry{pyprojectPoetryGroup}) -> case pyprojectPoetryGroup of + Just (PyProjectPoetryGroup{groupDev, groupTest}) -> case (groupDev, groupTest) of + (Just devDeps, Just testDeps) -> unions [groupDependencies devDeps, groupDependencies testDeps] + (Just devDeps, Nothing) -> groupDependencies devDeps + (Nothing, Just testDeps) -> groupDependencies testDeps + _ -> mempty + _ -> mempty + _ -> mempty _ -> mempty olderPoetryDevDeps :: Map Text PoetryDependency - olderPoetryDevDeps = case pyprojectPoetry project of - Just (PyProjectPoetry{devDependencies}) -> devDependencies + olderPoetryDevDeps = case pyprojectTool project of + Just (PyProjectTool{pyprojectPoetry}) -> case pyprojectPoetry of + Just (PyProjectPoetry{devDependencies}) -> devDependencies + _ -> mempty _ -> mempty data PoetryDependency @@ -155,54 +226,32 @@ data PoetryDependency | PyProjectPoetryUrlDependencySpec PyProjectPoetryUrlDependency deriving (Show, Eq, Ord) -pyProjectPoetryCodec :: TomlCodec PyProjectPoetry -pyProjectPoetryCodec = - PyProjectPoetry - <$> Toml.dioptional (Toml.text "name") .= name - <*> Toml.dioptional (Toml.text "version") .= version - <*> Toml.dioptional (Toml.text "description") .= description - <*> Toml.tableMap Toml._KeyText pyProjectPoetryDependencyCodec "dependencies" .= dependencies - <*> Toml.tableMap Toml._KeyText pyProjectPoetryDependencyCodec "dev-dependencies" .= devDependencies - <*> Toml.tableMap Toml._KeyText pyProjectPoetryDependencyCodec "group.dev.dependencies" .= groupDevDependencies - <*> Toml.tableMap Toml._KeyText pyProjectPoetryDependencyCodec "group.test.dependencies" .= groupTestDependencies - -pyProjectPoetryDependencyCodec :: Toml.Key -> TomlCodec PoetryDependency -pyProjectPoetryDependencyCodec key = - Toml.dimatch matchPyProjectPoetryTextVersionDependecySpec PoetryTextVersion (Toml.text key) - <|> Toml.dimatch matchPyProjectPoetryDetailedVersionDependencySpec PyProjectPoetryDetailedVersionDependencySpec (Toml.table pyProjectPoetryDetailedVersionDependencyCodec key) - <|> Toml.dimatch matchPyProjectPoetryGitDependencySpec PyProjectPoetryGitDependencySpec (Toml.table pyProjectPoetryGitDependencyCodec key) - <|> Toml.dimatch matchPyProjectPoetryPathDependencySpec PyProjectPoetryPathDependencySpec (Toml.table pyProjectPoetryPathDependencyCodec key) - <|> Toml.dimatch matchPyProjectPoetryUrlDependencySpec PyProjectPoetryUrlDependencySpec (Toml.table pyProjectPoetryUrlDependencyCodec key) - -matchPyProjectPoetryTextVersionDependecySpec :: PoetryDependency -> Maybe Text -matchPyProjectPoetryTextVersionDependecySpec (PoetryTextVersion version) = Just version -matchPyProjectPoetryTextVersionDependecySpec _ = Nothing - -matchPyProjectPoetryDetailedVersionDependencySpec :: PoetryDependency -> Maybe PyProjectPoetryDetailedVersionDependency -matchPyProjectPoetryDetailedVersionDependencySpec (PyProjectPoetryDetailedVersionDependencySpec spec) = Just spec -matchPyProjectPoetryDetailedVersionDependencySpec _ = Nothing - -matchPyProjectPoetryGitDependencySpec :: PoetryDependency -> Maybe PyProjectPoetryGitDependency -matchPyProjectPoetryGitDependencySpec (PyProjectPoetryGitDependencySpec spec) = Just spec -matchPyProjectPoetryGitDependencySpec _ = Nothing - -matchPyProjectPoetryPathDependencySpec :: PoetryDependency -> Maybe PyProjectPoetryPathDependency -matchPyProjectPoetryPathDependencySpec (PyProjectPoetryPathDependencySpec spec) = Just spec -matchPyProjectPoetryPathDependencySpec _ = Nothing - -matchPyProjectPoetryUrlDependencySpec :: PoetryDependency -> Maybe PyProjectPoetryUrlDependency -matchPyProjectPoetryUrlDependencySpec (PyProjectPoetryUrlDependencySpec spec) = Just spec -matchPyProjectPoetryUrlDependencySpec _ = Nothing +instance Toml.Schema.FromValue PoetryDependency where + fromValue (Toml.Text' _ t) = pure $ PoetryTextVersion t + fromValue v@(Toml.Table' l t) = + Toml.Schema.parseTable + ( Toml.Schema.pickKey + [ Toml.Schema.Key "version" (const (PyProjectPoetryDetailedVersionDependencySpec <$> Toml.Schema.fromValue v)) + , Toml.Schema.Key "git" (const (PyProjectPoetryGitDependencySpec <$> Toml.Schema.fromValue v)) + , Toml.Schema.Key "path" (const (PyProjectPoetryPathDependencySpec <$> Toml.Schema.fromValue v)) + , Toml.Schema.Key "url" (const (PyProjectPoetryUrlDependencySpec <$> Toml.Schema.fromValue v)) + , Toml.Schema.Else (Toml.Schema.failAt (Toml.valueAnn v) "invalid spec") + ] + ) + l + t + fromValue v = Toml.Schema.failAt (Toml.valueAnn v) $ "invalid poetry dependency" <> Toml.valueType v newtype PyProjectPoetryDetailedVersionDependency = PyProjectPoetryDetailedVersionDependency { poetryDependencyVersion :: Text } deriving (Show, Eq, Ord) -pyProjectPoetryDetailedVersionDependencyCodec :: TomlCodec PyProjectPoetryDetailedVersionDependency -pyProjectPoetryDetailedVersionDependencyCodec = - PyProjectPoetryDetailedVersionDependency - <$> Toml.text "version" .= poetryDependencyVersion +instance Toml.Schema.FromValue PyProjectPoetryDetailedVersionDependency where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectPoetryDetailedVersionDependency + <$> Toml.Schema.reqKey "version" data PyProjectPoetryGitDependency = PyProjectPoetryGitDependency { gitUrl :: Text @@ -212,33 +261,36 @@ data PyProjectPoetryGitDependency = PyProjectPoetryGitDependency } deriving (Show, Eq, Ord) -pyProjectPoetryGitDependencyCodec :: TomlCodec PyProjectPoetryGitDependency -pyProjectPoetryGitDependencyCodec = - PyProjectPoetryGitDependency - <$> Toml.text "git" .= gitUrl - <*> Toml.dioptional (Toml.text "branch") .= gitBranch - <*> Toml.dioptional (Toml.text "rev") .= gitRev - <*> Toml.dioptional (Toml.text "tag") .= gitTag +instance Toml.Schema.FromValue PyProjectPoetryGitDependency where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectPoetryGitDependency + <$> Toml.Schema.reqKey "git" + <*> Toml.Schema.optKey "branch" + <*> Toml.Schema.optKey "rev" + <*> Toml.Schema.optKey "tag" newtype PyProjectPoetryPathDependency = PyProjectPoetryPathDependency { sourcePath :: Text } deriving (Show, Eq, Ord) -pyProjectPoetryPathDependencyCodec :: TomlCodec PyProjectPoetryPathDependency -pyProjectPoetryPathDependencyCodec = - PyProjectPoetryPathDependency - <$> Toml.text "path" .= sourcePath +instance Toml.Schema.FromValue PyProjectPoetryPathDependency where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectPoetryPathDependency + <$> Toml.Schema.reqKey "path" newtype PyProjectPoetryUrlDependency = PyProjectPoetryUrlDependency { sourceUrl :: Text } deriving (Show, Eq, Ord) -pyProjectPoetryUrlDependencyCodec :: TomlCodec PyProjectPoetryUrlDependency -pyProjectPoetryUrlDependencyCodec = - PyProjectPoetryUrlDependency - <$> Toml.text "url" .= sourceUrl +instance Toml.Schema.FromValue PyProjectPoetryUrlDependency where + fromValue = + Toml.Schema.parseTableFromValue $ + PyProjectPoetryUrlDependency + <$> Toml.Schema.reqKey "url" toDependencyVersion :: Text -> Maybe VerConstraint toDependencyVersion dt = case parse parseConstraintExpr "" dt of diff --git a/src/Strategy/Python/Util.hs b/src/Strategy/Python/Util.hs index ede340b693..bf9b7fabe7 100644 --- a/src/Strategy/Python/Util.hs +++ b/src/Strategy/Python/Util.hs @@ -8,7 +8,6 @@ module Strategy.Python.Util ( Req (..), requirementParser, reqToDependency, - reqCodec, toConstraint, ) where @@ -30,6 +29,7 @@ import Text.Megaparsec import Text.Megaparsec.Char import Text.URI qualified as URI import Toml qualified +import Toml.Schema qualified pkgToReq :: PythonPackage -> Req pkgToReq p = @@ -173,8 +173,12 @@ data Req | UrlReq Text (Maybe [Text]) URI.URI (Maybe Marker) -- name, extras, ... deriving (Eq, Ord, Show) -reqCodec :: Toml.TomlBiMap Req Toml.AnyValue -reqCodec = Toml._TextBy (toText . show) parseReq +instance Toml.Schema.FromValue Req where + fromValue v = do + value <- Toml.Schema.fromValue v + case parseReq value of + Left _ -> Toml.Schema.failAt (Toml.valueAnn v) "invalid req" + Right r -> pure r parseReq :: Text -> Either Text Req parseReq candidate = case runParser requirementParser "" candidate of diff --git a/test/Fortran/FpmTomlSpec.hs b/test/Fortran/FpmTomlSpec.hs index 33b32e81e7..a4bfcec351 100644 --- a/test/Fortran/FpmTomlSpec.hs +++ b/test/Fortran/FpmTomlSpec.hs @@ -18,8 +18,8 @@ import Strategy.Fortran.FpmToml ( FpmGitDependency (..), FpmPathDependency (..), FpmToml (..), + FpmTomlExecutables (..), buildGraph, - fpmTomlCodec, ) import Test.Hspec ( Spec, @@ -51,14 +51,23 @@ expectedFpmToml = ] ) (fromList [("dep-dev", FpmGitDep mkGitFpmDep{url = "git-url-dev-dep", rev = Just "2f5eaba"})]) - [fromList [("dep-exec", FpmGitDep mkGitFpmDep{url = "git-url-exec"})]] + ([FpmTomlExecutables $ fromList [("dep-exec", FpmGitDep mkGitFpmDep{url = "git-url-exec"})]]) spec :: Spec spec = do content <- runIO (TIO.readFile "test/Fortran/testdata/fpm.toml") describe "fpmTomlCodec" $ it "should parse fpm.toml file" $ - Toml.decode fpmTomlCodec content `shouldBe` Right expectedFpmToml + Toml.decode content + `shouldBe` Toml.Success + [ "11:33: unexpected key: branch in dependencies.dep-branch" + , "13:30: unexpected key: rev in dependencies.dep-rev" + , "12:30: unexpected key: tag in dependencies.dep-tag" + , "16:38: unexpected key: rev in dev-dependencies.dep-dev" + , "4:1: unexpected key: name in executable[0]" + , "1:1: unexpected key: name in " + ] + expectedFpmToml describe "buildGraph" $ do let graph = buildGraph expectedFpmToml diff --git a/test/Go/GopkgLockSpec.hs b/test/Go/GopkgLockSpec.hs index 8afa23463f..f14c794eeb 100644 --- a/test/Go/GopkgLockSpec.hs +++ b/test/Go/GopkgLockSpec.hs @@ -68,11 +68,25 @@ spec = do describe "analyze" $ it "should produce expected output" $ do - case Toml.decode golockCodec contents of - Left err -> expectationFailure ("decode failed: " <> show err) - Right golock -> do + case Toml.decode contents of + Toml.Failure err -> expectationFailure ("decode failed: " <> show err) + Toml.Success warnings golock -> do let result = buildGraph (lockProjects golock) & graphingGolang & run result `shouldBe` expected + warnings + `shouldBe` [ "5:3: unexpected key: digest in projects[0]" + , "7:3: unexpected key: packages in projects[0]" + , "8:3: unexpected key: pruneopts in projects[0]" + , "10:3: unexpected key: version in projects[0]" + , "13:3: unexpected key: digest in projects[1]" + , "15:3: unexpected key: packages in projects[1]" + , "23:3: unexpected key: pruneopts in projects[1]" + , "25:3: unexpected key: version in projects[1]" + , "29:3: unexpected key: branch in projects[2]" + , "30:3: unexpected key: digest in projects[2]" + , "32:3: unexpected key: packages in projects[2]" + , "33:3: unexpected key: pruneopts in projects[2]" + ] describe "buildGraph" $ it "should produce expected output" $ do diff --git a/test/Go/GopkgTomlSpec.hs b/test/Go/GopkgTomlSpec.hs index 5393a3a999..7e6735bb2f 100644 --- a/test/Go/GopkgTomlSpec.hs +++ b/test/Go/GopkgTomlSpec.hs @@ -102,11 +102,12 @@ spec = do describe "analyze" $ it "should produce expected output" $ do - case Toml.decode gopkgCodec contents of - Left err -> expectationFailure ("decode failed: " <> show err) - Right pkg -> do + case Toml.decode contents of + Toml.Failure err -> expectationFailure ("decode failed: " <> show err) + Toml.Success warnings pkg -> do let result = buildGraph pkg & graphingGolang & run result `shouldBe` expected + warnings `shouldBe` [] describe "buildGraph" $ it "should produce expected output" $ do diff --git a/test/Pdm/PdmLockSpec.hs b/test/Pdm/PdmLockSpec.hs index f654a2df56..db6477b375 100644 --- a/test/Pdm/PdmLockSpec.hs +++ b/test/Pdm/PdmLockSpec.hs @@ -6,7 +6,7 @@ module Pdm.PdmLockSpec ( import Data.Text (Text) import DepTypes (DepType (..), Dependency (..), VerConstraint (..)) -import Strategy.Python.PDM.PdmLock (PdmLock (..), PdmLockPackage (..), pdmLockCodec, toDependency) +import Strategy.Python.PDM.PdmLock (PdmLock (..), PdmLockPackage (..), toDependency) import Strategy.Python.Util (Req (..)) import Test.Hspec ( Spec, @@ -25,11 +25,11 @@ spec = do describe "lockfile" $ do it "should parse empty lock file" $ do let expected = PdmLock mempty - Toml.decode pdmLockCodec lockNoContent `shouldBe` Right expected + Toml.decode lockNoContent `shouldBe` (Toml.Success [] expected) it "should parse lock file with one entry" $ do let expected = PdmLock [PdmLockPackage "blinker" "1.6.2" Nothing Nothing Nothing Nothing Nothing] - Toml.decode pdmLockCodec lockOneEntry `shouldBe` Right expected + Toml.decode lockOneEntry `shouldBe` (Toml.Success [] expected) it "should parse lock file with multiple entry" $ do let expected = @@ -37,7 +37,7 @@ spec = do [ PdmLockPackage "blinker" "1.6.2" Nothing Nothing Nothing Nothing (Just [mkReq "b"]) , PdmLockPackage "b" "1.0.0" Nothing Nothing Nothing Nothing Nothing ] - Toml.decode pdmLockCodec lockMultipleEntry `shouldBe` Right expected + Toml.decode lockMultipleEntry `shouldBe` (Toml.Success [] expected) it "should parse lock file, with git deps" $ do let expected = @@ -51,14 +51,14 @@ spec = do Nothing Nothing ] - Toml.decode pdmLockCodec lockWithGitEntry `shouldBe` Right expected + Toml.decode lockWithGitEntry `shouldBe` (Toml.Success ["7:1: unexpected key: ref in package[0]", "5:1: unexpected key: requires_python in package[0]", "9:1: unexpected key: summary in package[0]"] expected) it "should parse lock file, with filepath deps" $ do let expected = PdmLock [ lockWithFilePathEntryPackage ] - Toml.decode pdmLockCodec lockWithFilePathEntry `shouldBe` Right expected + Toml.decode lockWithFilePathEntry `shouldBe` (Toml.Success ["5:1: unexpected key: requires_python in package[0]", "7:1: unexpected key: summary in package[0]"] expected) it "should parse lock file, with url deps" $ do let expected = @@ -72,7 +72,7 @@ spec = do (Just "https://github.com/explosion/spacy-models/releases/download/en_core_web_trf-3.5.0/en_core_web_trf-3.5.0-py3-none-any.whl") Nothing ] - Toml.decode pdmLockCodec lockWithFileUrlEntry `shouldBe` Right expected + Toml.decode lockWithFileUrlEntry `shouldBe` (Toml.Success [] expected) describe "toDependency" $ it "should handle pdm's local dependency correctly" $ do diff --git a/test/Python/Poetry/CommonSpec.hs b/test/Python/Poetry/CommonSpec.hs index b126736d84..01d80a330a 100644 --- a/test/Python/Poetry/CommonSpec.hs +++ b/test/Python/Poetry/CommonSpec.hs @@ -24,7 +24,7 @@ import Strategy.Python.Poetry.PyProject ( PyProjectPoetryGitDependency (..), PyProjectPoetryPathDependency (..), PyProjectPoetryUrlDependency (..), - pyProjectCodec, + PyProjectTool (..), ) import Test.Hspec ( Spec, @@ -35,73 +35,77 @@ import Test.Hspec ( shouldMatchList, ) import Toml qualified +import Toml.Schema qualified expectedPyProject :: PyProject expectedPyProject = PyProject - { pyprojectBuildSystem = Just $ PyProjectBuildSystem{buildBackend = "poetry.core.masonry.api"} + { pyprojectBuildSystem = Just $ PyProjectBuildSystem{buildBackend = Just "poetry.core.masonry.api"} , pyprojectProject = Nothing - , pyprojectPdmDevDependencies = Nothing - , pyprojectPoetry = + , pyprojectTool = Just $ - PyProjectPoetry - { name = Just "test_name" - , version = Just "test_version" - , description = Just "test_description" - , dependencies = - Map.fromList - [ ("flake8", PoetryTextVersion "^1.1") - , ("python", PoetryTextVersion "^3.9") - , - ( "flask" - , PyProjectPoetryGitDependencySpec - PyProjectPoetryGitDependency - { gitUrl = "https://github.com/pallets/flask.git" - , gitRev = Just "38eb5d3b" - , gitTag = Nothing - , gitBranch = Nothing - } - ) - , - ( "networkx" - , PyProjectPoetryGitDependencySpec - PyProjectPoetryGitDependency - { gitUrl = "https://github.com/networkx/networkx.git" - , gitRev = Nothing - , gitTag = Nothing - , gitBranch = Nothing - } - ) - , - ( "numpy" - , PyProjectPoetryGitDependencySpec - PyProjectPoetryGitDependency - { gitUrl = "https://github.com/numpy/numpy.git" - , gitRev = Nothing - , gitTag = Just "v0.13.2" - , gitBranch = Nothing - } - ) - , - ( "requests" - , PyProjectPoetryGitDependencySpec - PyProjectPoetryGitDependency - { gitUrl = "https://github.com/kennethreitz/requests.git" - , gitRev = Nothing - , gitTag = Nothing - , gitBranch = Just "next" - } - ) - , ("my-packageUrl", PyProjectPoetryUrlDependencySpec $ PyProjectPoetryUrlDependency "https://example.com/my-package-0.1.0.tar.gz") - , ("my-packageFile", PyProjectPoetryPathDependencySpec $ PyProjectPoetryPathDependency "../my-package/dist/my-package-0.1.0.tar.gz") - , ("my-packageDir", PyProjectPoetryPathDependencySpec $ PyProjectPoetryPathDependency "../my-package/") - , ("black", PyProjectPoetryDetailedVersionDependencySpec $ PyProjectPoetryDetailedVersionDependency "19.10b0") - ] - , devDependencies = - Map.fromList - [("pytest", PoetryTextVersion "*")] - , groupDevDependencies = Map.empty - , groupTestDependencies = Map.empty + PyProjectTool + { pyprojectPdm = Nothing + , pyprojectPoetry = + Just $ + PyProjectPoetry + { name = Just "test_name" + , version = Just "test_version" + , description = Just "test_description" + , dependencies = + Map.fromList + [ ("flake8", PoetryTextVersion "^1.1") + , ("python", PoetryTextVersion "^3.9") + , + ( "flask" + , PyProjectPoetryGitDependencySpec + PyProjectPoetryGitDependency + { gitUrl = "https://github.com/pallets/flask.git" + , gitRev = Just "38eb5d3b" + , gitTag = Nothing + , gitBranch = Nothing + } + ) + , + ( "networkx" + , PyProjectPoetryGitDependencySpec + PyProjectPoetryGitDependency + { gitUrl = "https://github.com/networkx/networkx.git" + , gitRev = Nothing + , gitTag = Nothing + , gitBranch = Nothing + } + ) + , + ( "numpy" + , PyProjectPoetryGitDependencySpec + PyProjectPoetryGitDependency + { gitUrl = "https://github.com/numpy/numpy.git" + , gitRev = Nothing + , gitTag = Just "v0.13.2" + , gitBranch = Nothing + } + ) + , + ( "requests" + , PyProjectPoetryGitDependencySpec + PyProjectPoetryGitDependency + { gitUrl = "https://github.com/kennethreitz/requests.git" + , gitRev = Nothing + , gitTag = Nothing + , gitBranch = Just "next" + } + ) + , ("my-packageUrl", PyProjectPoetryUrlDependencySpec $ PyProjectPoetryUrlDependency "https://example.com/my-package-0.1.0.tar.gz") + , ("my-packageFile", PyProjectPoetryPathDependencySpec $ PyProjectPoetryPathDependency "../my-package/dist/my-package-0.1.0.tar.gz") + , ("my-packageDir", PyProjectPoetryPathDependencySpec $ PyProjectPoetryPathDependency "../my-package/") + , ("black", PyProjectPoetryDetailedVersionDependencySpec $ PyProjectPoetryDetailedVersionDependency "19.10b0") + ] + , devDependencies = + Map.fromList + [("pytest", PoetryTextVersion "*")] + , pyprojectPoetryGroup = Nothing + } } } @@ -146,6 +150,11 @@ expectedDeps = devEnvs :: DepEnvironment devEnvs = EnvDevelopment +decodeEither :: (Toml.Schema.FromValue a) => Text -> Either [String] a +decodeEither f = case Toml.decode f of + Toml.Success _ r -> Right r + Toml.Failure e -> Left e + spec :: Spec spec = do nominalContents <- runIO (TIO.readFile "test/Python/Poetry/testdata/pyproject1.toml") @@ -172,12 +181,12 @@ spec = do describe "getPoetryBuildBackend" $ do describe "when provided with poetry build backend" $ it "should return true" $ - getPoetryBuildBackend <$> (Toml.decode pyProjectCodec nominalContents) + getPoetryBuildBackend <$> (decodeEither nominalContents) `shouldBe` Right (Just "poetry.core.masonry.api") describe "when not provided with any build system" $ it "should return nothing" $ - getPoetryBuildBackend <$> Toml.decode pyProjectCodec emptyContents + getPoetryBuildBackend <$> decodeEither emptyContents `shouldBe` Right Nothing describe "makePackageToLockDependencyMap" $ do diff --git a/test/Python/Poetry/PoetryLockSpec.hs b/test/Python/Poetry/PoetryLockSpec.hs index a1e0e8c9fe..bce187646a 100644 --- a/test/Python/Poetry/PoetryLockSpec.hs +++ b/test/Python/Poetry/PoetryLockSpec.hs @@ -12,7 +12,6 @@ import Strategy.Python.Poetry.PoetryLock ( PoetryLockPackage (..), PoetryLockPackageSource (..), PoetryMetadata (..), - poetryLockCodec, ) import Test.Hspec ( Spec, @@ -149,6 +148,20 @@ expectedPoetryLock = spec :: Spec spec = do contents <- runIO (TIO.readFile "test/Python/Poetry/testdata/poetry.lock") - describe "poetryLockCodec" $ + describe "decoding toml file" $ it "should produce expected output" $ - Toml.decode poetryLockCodec contents `shouldBe` Right expectedPoetryLock + Toml.decode contents + `shouldBe` Toml.Success + [ "5:1: unexpected key: description in package[0]" + , "21:1: unexpected key: description in package[1]" + , "34:1: unexpected key: description in package[2]" + , "54:28: unexpected key: markers in package[3].dependencies.pkgThreeChildofOne[0]" + , "55:28: unexpected key: markers in package[3].dependencies.pkgThreeChildofOne[1]" + , "57:38: unexpected key: markers in package[3].dependencies.pkgTwoChildofOne" + , "45:1: unexpected key: description in package[3]" + , "62:1: unexpected key: description in package[4]" + , "69:1: unexpected key: description in package[5]" + , "76:1: unexpected key: description in package[6]" + , "83:1: unexpected key: description in package[7]" + ] + expectedPoetryLock diff --git a/test/Python/Poetry/PyProjectSpec.hs b/test/Python/Poetry/PyProjectSpec.hs index c52b529a36..bc929bfcf8 100644 --- a/test/Python/Poetry/PyProjectSpec.hs +++ b/test/Python/Poetry/PyProjectSpec.hs @@ -1,7 +1,6 @@ module Python.Poetry.PyProjectSpec ( spec, -) -where +) where import Data.Map qualified as Map import Data.Text (Text) @@ -20,7 +19,20 @@ import DepTypes ( COr ), ) -import Strategy.Python.Poetry.PyProject (PoetryDependency (..), PyProject (..), PyProjectBuildSystem (..), PyProjectPoetry (..), PyProjectPoetryDetailedVersionDependency (..), PyProjectPoetryGitDependency (..), PyProjectPoetryPathDependency (..), PyProjectPoetryUrlDependency (..), parseConstraintExpr, pyProjectCodec) +import Strategy.Python.Poetry.PyProject ( + PoetryDependency (..), + PyProject (..), + PyProjectBuildSystem (..), + PyProjectPoetry (..), + PyProjectPoetryDetailedVersionDependency (..), + PyProjectPoetryGitDependency (..), + PyProjectPoetryGroup (..), + PyProjectPoetryGroupDependencies (..), + PyProjectPoetryPathDependency (..), + PyProjectPoetryUrlDependency (..), + PyProjectTool (..), + parseConstraintExpr, + ) import Test.Hspec ( Expectation, Spec, @@ -42,63 +54,78 @@ shouldParseInto = parseMatch parseConstraintExpr expectedPyProject :: PyProject expectedPyProject = PyProject - { pyprojectBuildSystem = Just $ PyProjectBuildSystem{buildBackend = "poetry.core.masonry.api"} + { pyprojectBuildSystem = Just $ PyProjectBuildSystem{buildBackend = Just "poetry.core.masonry.api"} , pyprojectProject = Nothing - , pyprojectPdmDevDependencies = Just mempty - , pyprojectPoetry = + , pyprojectTool = Just $ - PyProjectPoetry - { name = Just "test_name" - , version = Just "test_version" - , description = Just "test_description" - , dependencies = - Map.fromList - [ ("flake8", PoetryTextVersion "^1.1") - , ("python", PoetryTextVersion "^3.9") - , ("flask", PyProjectPoetryGitDependencySpec $ PyProjectPoetryGitDependency{gitUrl = "https://github.com/pallets/flask.git", gitRev = Just "38eb5d3b", gitTag = Nothing, gitBranch = Nothing}) - , ("networkx", PyProjectPoetryGitDependencySpec $ PyProjectPoetryGitDependency{gitUrl = "https://github.com/networkx/networkx.git", gitRev = Nothing, gitTag = Nothing, gitBranch = Nothing}) - , ("numpy", PyProjectPoetryGitDependencySpec $ PyProjectPoetryGitDependency{gitUrl = "https://github.com/numpy/numpy.git", gitRev = Nothing, gitTag = Just "v0.13.2", gitBranch = Nothing}) - , ("requests", PyProjectPoetryGitDependencySpec $ PyProjectPoetryGitDependency{gitUrl = "https://github.com/kennethreitz/requests.git", gitRev = Nothing, gitTag = Nothing, gitBranch = Just "next"}) - , ("my-packageUrl", PyProjectPoetryUrlDependencySpec $ PyProjectPoetryUrlDependency{sourceUrl = "https://example.com/my-package-0.1.0.tar.gz"}) - , ("my-packageFile", PyProjectPoetryPathDependencySpec $ PyProjectPoetryPathDependency{sourcePath = "../my-package/dist/my-package-0.1.0.tar.gz"}) - , ("my-packageDir", PyProjectPoetryPathDependencySpec $ PyProjectPoetryPathDependency{sourcePath = "../my-package/"}) - , ("black", PyProjectPoetryDetailedVersionDependencySpec $ PyProjectPoetryDetailedVersionDependency{poetryDependencyVersion = "19.10b0"}) - ] - , devDependencies = - Map.fromList - [("pytest", PoetryTextVersion "*")] - , groupDevDependencies = Map.empty - , groupTestDependencies = Map.empty + PyProjectTool + { pyprojectPoetry = + Just $ + PyProjectPoetry + { name = Just "test_name" + , version = Just "test_version" + , description = Just "test_description" + , dependencies = + Map.fromList + [ ("flake8", PoetryTextVersion "^1.1") + , ("python", PoetryTextVersion "^3.9") + , ("flask", PyProjectPoetryGitDependencySpec $ PyProjectPoetryGitDependency{gitUrl = "https://github.com/pallets/flask.git", gitRev = Just "38eb5d3b", gitTag = Nothing, gitBranch = Nothing}) + , ("networkx", PyProjectPoetryGitDependencySpec $ PyProjectPoetryGitDependency{gitUrl = "https://github.com/networkx/networkx.git", gitRev = Nothing, gitTag = Nothing, gitBranch = Nothing}) + , ("numpy", PyProjectPoetryGitDependencySpec $ PyProjectPoetryGitDependency{gitUrl = "https://github.com/numpy/numpy.git", gitRev = Nothing, gitTag = Just "v0.13.2", gitBranch = Nothing}) + , ("requests", PyProjectPoetryGitDependencySpec $ PyProjectPoetryGitDependency{gitUrl = "https://github.com/kennethreitz/requests.git", gitRev = Nothing, gitTag = Nothing, gitBranch = Just "next"}) + , ("my-packageUrl", PyProjectPoetryUrlDependencySpec $ PyProjectPoetryUrlDependency{sourceUrl = "https://example.com/my-package-0.1.0.tar.gz"}) + , ("my-packageFile", PyProjectPoetryPathDependencySpec $ PyProjectPoetryPathDependency{sourcePath = "../my-package/dist/my-package-0.1.0.tar.gz"}) + , ("my-packageDir", PyProjectPoetryPathDependencySpec $ PyProjectPoetryPathDependency{sourcePath = "../my-package/"}) + , ("black", PyProjectPoetryDetailedVersionDependencySpec $ PyProjectPoetryDetailedVersionDependency{poetryDependencyVersion = "19.10b0"}) + ] + , devDependencies = + Map.fromList + [("pytest", PoetryTextVersion "*")] + , pyprojectPoetryGroup = Nothing + } + , pyprojectPdm = Nothing } } expectedPyProject3 :: PyProject expectedPyProject3 = PyProject - { pyprojectBuildSystem = Just $ PyProjectBuildSystem{buildBackend = "poetry.core.masonry.api"} + { pyprojectBuildSystem = Just $ PyProjectBuildSystem{buildBackend = Just "poetry.core.masonry.api"} , pyprojectProject = Nothing - , pyprojectPdmDevDependencies = Just mempty - , pyprojectPoetry = + , pyprojectTool = Just $ - PyProjectPoetry - { name = Just "test_name" - , version = Just "test_version" - , description = Just "test_description" - , dependencies = - Map.fromList - [ ("python", PoetryTextVersion "^3.12") - , ("rich", PoetryTextVersion "*") - ] - , devDependencies = Map.empty - , groupDevDependencies = - Map.fromList - [ ("click", PoetryTextVersion "*") - ] - , groupTestDependencies = - Map.fromList - [ ("pytest", PoetryTextVersion "^6.0.0") - , ("pytest-mock", PoetryTextVersion "*") - ] + PyProjectTool + { pyprojectPoetry = + Just $ + PyProjectPoetry + { name = Just "test_name" + , version = Just "test_version" + , description = Just "test_description" + , dependencies = + Map.fromList + [ ("python", PoetryTextVersion "^3.12") + , ("rich", PoetryTextVersion "*") + ] + , devDependencies = Map.empty + , pyprojectPoetryGroup = + Just $ + PyProjectPoetryGroup + { groupDev = + Just $ + PyProjectPoetryGroupDependencies $ + Map.fromList + [ ("click", PoetryTextVersion "*") + ] + , groupTest = + Just $ + PyProjectPoetryGroupDependencies $ + Map.fromList + [ ("pytest", PoetryTextVersion "^6.0.0") + , ("pytest-mock", PoetryTextVersion "*") + ] + } + } + , pyprojectPdm = Nothing } } @@ -110,8 +137,33 @@ spec = do describe "pyProjectCodec" $ describe "when provided with all possible types of dependency sources" $ it "should parse pyrproject file with all source types" $ do - Toml.decode pyProjectCodec nominalContents `shouldBe` Right expectedPyProject - Toml.decode pyProjectCodec groupDevContents `shouldBe` Right expectedPyProject3 + Toml.decode nominalContents + `shouldBe` Toml.Success + [ "40:1: unexpected key: requires in build-system" + , "27:31: unexpected key: allow-prereleases in tool.poetry.dependencies.black.version" + , "27:74: unexpected key: markers in tool.poetry.dependencies.black.version" + , "27:57: unexpected key: python in tool.poetry.dependencies.black.version" + , "27:31: unexpected key: allow-prereleases in tool.poetry.dependencies.black" + , "27:74: unexpected key: markers in tool.poetry.dependencies.black" + , "27:57: unexpected key: python in tool.poetry.dependencies.black" + , "12:56: unexpected key: rev in tool.poetry.dependencies.flask" + , "24:43: unexpected key: develop in tool.poetry.dependencies.my-packageDir.path" + , "24:43: unexpected key: develop in tool.poetry.dependencies.my-packageDir" + , "14:54: unexpected key: tag in tool.poetry.dependencies.numpy" + , "15:67: unexpected key: branch in tool.poetry.dependencies.requests" + , "6:14: unexpected key: scripts in tool.poetry" + , "42:15: unexpected key: source in tool.poetry" + ] + expectedPyProject + + Toml.decode groupDevContents + `shouldBe` Toml.Success + [ "26:1: unexpected key: requires in build-system" + , "19:20: unexpected key: docs in tool.poetry.group" + , "5:1: unexpected key: authors in tool.poetry" + , "6:1: unexpected key: readme in tool.poetry" + ] + expectedPyProject3 describe "parseConstraintExpr" $ do it "should parse equality constraint" $ do diff --git a/test/Python/Poetry/testdata/pyproject1.toml b/test/Python/Poetry/testdata/pyproject1.toml index d810bd8e03..b16f7bbd5b 100644 --- a/test/Python/Poetry/testdata/pyproject1.toml +++ b/test/Python/Poetry/testdata/pyproject1.toml @@ -3,6 +3,9 @@ description = "test_description" name = "test_name" version = "test_version" +[tool.poetry.scripts] +test-script = "test_script" + [tool.poetry.dependencies] # git diff --git a/test/Python/PoetrySpec.hs b/test/Python/PoetrySpec.hs index cf697a6626..78ea41a853 100644 --- a/test/Python/PoetrySpec.hs +++ b/test/Python/PoetrySpec.hs @@ -2,8 +2,7 @@ module Python.PoetrySpec ( spec, -) -where +) where import Data.Map qualified as Map import Data.Set qualified as Set @@ -29,7 +28,7 @@ import Strategy.Python.Poetry.PoetryLock ( PoetryLockPackage (..), PoetryMetadata (..), ) -import Strategy.Python.Poetry.PyProject (PoetryDependency (..), PyProject (..), PyProjectBuildSystem (..), PyProjectPoetry (..)) +import Strategy.Python.Poetry.PyProject (PoetryDependency (..), PyProject (..), PyProjectBuildSystem (..), PyProjectPoetry (..), PyProjectTool (..)) import Test.Effect (it') import Test.Hspec (Spec, describe, it, runIO, shouldBe) import Types (DependencyResults (dependencyGraph)) @@ -40,10 +39,15 @@ newPoetryLock pkgs = PoetryLock pkgs $ PoetryMetadata "some-version" "some-hash" candidatePyProject :: PyProject candidatePyProject = PyProject - (Just $ PyProjectBuildSystem "poetry.core.masonry.api") - (Just $ PyProjectPoetry Nothing Nothing Nothing (Map.fromList ([("flow_pipes", PoetryTextVersion "^1.21")])) mempty mempty mempty) - Nothing - Nothing + { pyprojectBuildSystem = Just $ PyProjectBuildSystem{buildBackend = Just "poetry.core.masonry.api"} + , pyprojectProject = Nothing + , pyprojectTool = + Just $ + PyProjectTool + { pyprojectPdm = Nothing + , pyprojectPoetry = Just $ PyProjectPoetry Nothing Nothing Nothing (Map.fromList ([("flow_pipes", PoetryTextVersion "^1.21")])) mempty Nothing + } + } candidatePoetryLock :: PoetryLock candidatePoetryLock =