packages feed

cabal-info (empty) → 0.1.0.0

raw patch · 8 files changed

+766/−0 lines, 8 filesdep +Cabaldep +basedep +cabal-infosetup-changed

Dependencies added: Cabal, base, cabal-info, directory, filepath, optparse-applicative

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2016 Michael Walker++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cabal-info.cabal view
@@ -0,0 +1,59 @@+-- Initial cabal-info.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                cabal-info+version:             0.1.0.0+synopsis:            Read information from cabal files+description:+  Have you ever needed to get information from a cabal file in a shell+  script? Now you can! cabal-info exposes a simple command-line+  interface to the cabal file format.+  .+  There is also a library interface, to solve tasks based on searching+  for a .cabal file and then doing something with it.+  .+  See the <https://github.com/barrucadu/cabal-info README> for more+  details.++homepage:            https://github.com/barrucadu/cabal-info+license:             MIT+license-file:        LICENSE+author:              Michael Walker+maintainer:          mike@barrucadu.co.uk+-- copyright:           +category:            Development+build-type:          Simple+-- extra-source-files:  +cabal-version:       >=1.10++source-repository head+  type:     git+  location: https://github.com/barrucadu/cabal-info.git++source-repository this+  type:     git+  location: https://github.com/barrucadu/cabal-info.git+  tag:      0.1.0.0++library+  exposed-modules:  Cabal.Info+  build-depends:    base >=4.8 && <4.9+                  , Cabal+                  , directory+                  , filepath+                  , optparse-applicative+  hs-source-dirs:   library+  default-language: Haskell2010++executable cabal-info+  main-is:          Main.hs+  other-modules:    Args+                  , Describe+                  , Fields+  build-depends:    base >=4.8 && <4.9+                  , cabal-info+                  , Cabal+                  , filepath+                  , optparse-applicative+  hs-source-dirs:   cabal-info+  default-language: Haskell2010
+ cabal-info/Args.hs view
@@ -0,0 +1,92 @@+-- | Command-line argument handling.+module Args where++import Data.Char (toLower)+import Options.Applicative+import Distribution.PackageDescription (FlagAssignment, FlagName(..))+import Distribution.System (Arch(..), OS(..))+import System.FilePath (FilePath)++import Fields++data Args = Args+  { cabalFile :: Maybe FilePath+  , flags     :: FlagAssignment+  , theArch   :: Maybe Arch+  , theOS     :: Maybe OS+  , field     :: Maybe FieldName+  }+  deriving Show++-- | Parse the command-line arguments.+getArgs :: IO Args+getArgs = execParser opts where+  opts = info (helper <*> argsParser)+    (fullDesc <> progDesc "Print fields from a cabal file.")++-------------------------------------------------------------------------------+-- Parsers++argsParser :: Parser Args+argsParser = Args+  <$> optional (strOption+      $  long "cabal-file"+      <> metavar "FILE"+      <> help "The cabal file to use. If unspecified, the first one found in this directory is used instead.")++  <*> flagAssignmentParser++  <*> optional archParser++  <*> optional osParser++  <*> optional fieldNameParser++flagAssignmentParser :: Parser FlagAssignment+flagAssignmentParser = map go . words <$> strOption (long "flags" <> short 'f' <> metavar "FLAGS" <> help "Force values for the given flags in Cabal conditionals in the .cabal file. E.g. --flags=\"debug -usebytestrings\" forces the flag \"debug\" to true and the flag \"usebytestrings\" to false." <> value "") where++  go ('-':flag) = (FlagName flag, False)+  go flag = (FlagName flag, True)++osParser :: Parser OS+osParser = go . map toLower <$> strOption (long "os" <> short 'o' <> metavar "OS" <> help "The operating system to use when expanding conditionals. Allowed values are (case insensitive): linux, windows, osx, freebsd, openbsd, netbsd, dragonfly, solaris, aix, hpux, iric, halvm, ios, ghcjs.") where+  go "linux" = Linux+  go "windows" = Windows+  go "osx" = OSX+  go "freebsd" = FreeBSD+  go "netbsd" = NetBSD+  go "dragonfly" = DragonFly+  go "solaris" = Solaris+  go "aix" = AIX+  go "hpux" = HPUX+  go "irix" = IRIX+  go "halvm" = HaLVM+  go "ios" = IOS+  go "ghcjs" = Ghcjs+  go os = OtherOS os++archParser :: Parser Arch+archParser = go . map toLower <$> strOption (long "arch" <> short 'a' <> metavar "ARCH" <> help "The architecture to use when expanding conditionals. Allowed values are (case insensitive): i386, x86_64, ppc, ppc64, sparc, arm, mips, sh, ia64, s390, alpha, hppa, rs6000, m68k, vax, javascript.") where+  go "i386" = I386+  go "x86_64" = X86_64+  go "ppc" = PPC+  go "ppc64" = PPC64+  go "sparc" = Sparc+  go "arm" = Arm+  go "mips" = Mips+  go "sh" = SH+  go "ia64" = IA64+  go "s390" = S390+  go "alpha" = Alpha+  go "hppa" = Hppa+  go "rs6000" = Rs6000+  go "m68k" = M68k+  go "vax" = Vax+  go "javascript" = JavaScript+  go arch = OtherArch arch++fieldNameParser :: Parser FieldName+fieldNameParser = go <$> argument str (metavar "FIELD" <> help "This is in the format [section:]field, where the section can be the name of a source repository, executable, test suite, or benchmark. If no field is given, then the file is pretty-printed, with any flags applied.") where+  go fname = case break (==':') fname of+    (name, ':':field) -> FieldName (Just $ map toLower name) (map toLower field)+    (field, []) -> FieldName Nothing (map toLower field)
+ cabal-info/Describe.hs view
@@ -0,0 +1,107 @@+-- | Pretty-printing packages.+module Describe where++import Data.List (elemIndex, delete, intercalate, isInfixOf, isPrefixOf)+import Data.Maybe (fromJust, maybeToList)+import Distribution.PackageDescription (PackageDescription(..), Library, Executable, TestSuite, Benchmark, SourceRepo)++import Fields++-- | A description of a part of a package.+--+-- For source repos, executables, testsuites, and benchmarks, the name+-- is not included in the list of fields.+data Description =+    Package [(String, String)] [Description]+  | SourceRepo String [(String, String)]+  | Library [(String, String)]+  | Executable String [(String, String)]+  | TestSuite String [(String, String)]+  | Benchmark String [(String, String)]+  deriving Show++-- | Pretty-print a package description.+describe :: PackageDescription -> String+describe = prettyPrint . describePackage where+  prettyPrint (Package fields rest) = intercalate "\n" $ prettyPrintFields fields : map prettyPrint rest+  prettyPrint (SourceRepo name fields) = "source-repository " ++ name ++ "\n" ++ indent 2 (prettyPrintFields fields)+  prettyPrint (Library fields) = "library\n" ++ indent 2 (prettyPrintFields fields)+  prettyPrint (Executable name fields) = "executable " ++ name ++ "\n" ++ indent 2 (prettyPrintFields fields)+  prettyPrint (TestSuite  name fields) = "test-suite " ++ name ++ "\n" ++ indent 2 (prettyPrintFields fields)+  prettyPrint (Benchmark  name fields) = "benchmark "  ++ name ++ "\n" ++ indent 2 (prettyPrintFields fields)++  prettyPrintFields fs = foldl pp "" $ map (\(f,v) -> (align (maxlen + 2) (f ++ ":"), v)) fs where+    maxlen = maximum $ map (length . fst) fs++    -- Pretty-print a field and value.+    pp sofar (f, v) = sofar ++ f ++ ppv f v ++ "\n"++    -- Pretty-print a value. Has some special cases.+    ppv f v+      -- Special case for description: just want to indent further+      -- lines by 2 spaces, and replace blank lines with an indented+      -- '.'.+      | "description" `isPrefixOf` f = indentS 2 . unlines' . map (\l -> if null l then "." else l) . lines $ v++      -- Special case for non-description multi-line fields: want to+      -- indent non-first-lines to the ":" in the field name, and add+      -- commas.+      | "\n" `isInfixOf` v = indentS (length f - 2) $ mapLines (", " ++) v++      -- Normal case is just the value.+      | otherwise = v++    -- Apply padding at the end of the line to the desired length.+    align n s = s ++ replicate (n - length s) ' '++  -- Indent every line.+  indent n = unlines . map (replicate n ' '++) . lines++  -- Indent every line after the first.+  indentS n = mapLines (replicate n ' '++)++  -- Apply a function to all but the first line.+  mapLines f str = case lines str of+    (x:xs) -> unlines' $ x : map f xs+    [] -> ""++-- | Produce all defined fields in a package.+describePackage :: PackageDescription -> Description+describePackage pkg = Package fields sections where+  fields   = getFields packageDescriptionFields getPackageDescriptionField pkg+  sections = repos ++ maybeToList lib ++ exes ++ tests ++ benchs+  repos  = describeSourceRepo <$> sourceRepos pkg+  lib    = describeLibrary    <$> library pkg+  exes   = describeExecutable <$> executables pkg+  tests  = describeTestSuite  <$> testSuites pkg+  benchs = describeBenchmark  <$> benchmarks pkg++-- | Produce all defined fields in a source repository.+describeSourceRepo :: SourceRepo -> Description+describeSourceRepo = section SourceRepo sourceRepoFields getSourceRepoField++-- | Produce all defined fields in a library.+describeLibrary :: Library -> Description+describeLibrary = Library . getFields (libraryFields ++ buildInfoFields) getLibraryField++-- | Produce all defined fields in an executable.+describeExecutable :: Executable -> Description+describeExecutable = section Executable (executableFields ++ buildInfoFields) getExecutableField++-- | Produce all defined fields in a test suite.+describeTestSuite :: TestSuite -> Description+describeTestSuite = section TestSuite (testSuiteFields ++ buildInfoFields) getTestSuiteField++-- | Produce all defined fields in a benchmark.+describeBenchmark :: Benchmark -> Description+describeBenchmark = section Benchmark (benchmarkFields ++ buildInfoFields) getBenchmarkField++-- | Describe a named section.+section :: (String -> [(String, String)] -> Description) -> [String] -> (String -> a -> String) -> a -> Description+section constr fieldNames get a = constr name fields where+  name   = get "name" a+  fields = getFields (delete "name" fieldNames) get a++-- | Get all non-empty fields.+getFields :: [String] -> (String -> a -> String) -> a -> [(String, String)]+getFields fields get a = [(f, v) | f <- fields, let v = get f a, not (null v)]
+ cabal-info/Fields.hs view
@@ -0,0 +1,253 @@+-- | Accessing fields from packages.+module Fields where++import Control.Applicative ((<|>))+import Data.Char (toLower)+import Data.Maybe (fromMaybe, listToMaybe, maybeToList)++import Distribution.Compiler (CompilerFlavor(GHC))+import Distribution.Package+import Distribution.PackageDescription+import Distribution.Text (display)+import Distribution.Version++-- | A field name is a string, optionally qualified with a specific+-- executable/test-suite/benchmark.+data FieldName = FieldName (Maybe String) String+  deriving Show++-- | Get a field from a package description, returning a list of+-- values. The empty list indicates that either the field was present,+-- but contained nothing, or the field was not present.+--+-- There are a number of parts of the package description not yet+-- exposed. Doing this nicely may require more than a single-word+-- field name eg, to specify which executable or test suite is being+-- referred to.+--+-- The following don't show up in the Cabal User Guide as of+-- 2016-02-01, and so are intentionally omitted for now:+--+-- - library requiredSignatures+-- - library exposedSignatures+getField :: FieldName -> (GenericPackageDescription, PackageDescription) -> String+-- Special case pseudo-fields+---- First:+getField (FieldName Nothing "flag")       = maybe "" (getFlagField "name") . listToMaybe . genPackageFlags . fst+getField (FieldName Nothing "executable") = maybe "" (getExecutableField "name") . listToMaybe . executables . snd+getField (FieldName Nothing "testsuite")  = maybe "" (getTestSuiteField "name") . listToMaybe . testSuites . snd+getField (FieldName Nothing "benchmark")  = maybe "" (getBenchmarkField "name") . listToMaybe . benchmarks . snd+getField (FieldName Nothing "repository") = maybe "" (getSourceRepoField "name") . listToMaybe . sourceRepos . snd+---- Collection:+getField (FieldName Nothing "flags")        = unlines' . map (getFlagField "name")  . genPackageFlags . fst+getField (FieldName Nothing "executables")  = unlines' . map (getExecutableField "name") . executables . snd+getField (FieldName Nothing "testsuites")   = unlines' . map (getTestSuiteField  "name") . testSuites . snd+getField (FieldName Nothing "benchmarks")   = unlines' . map (getBenchmarkField  "name") . benchmarks . snd+getField (FieldName Nothing "repositories") = unlines' . map (getSourceRepoField "name") . sourceRepos . snd+---- Other+getField (FieldName Nothing "main-is") = maybe "" (getExecutableField "main-is") . listToMaybe . executables . snd+getField (FieldName Nothing "upstream") = maybe "" (getSourceRepoField "location") . listToMaybe . filter ((RepoHead==) . repoKind) . sourceRepos . snd+-- Qualified Fields+getField (FieldName (Just name) field) = \(gpkg, pkg) ->+  let flag  = listToMaybe $ filter (\f -> map toLower (flagName' f) == name) (genPackageFlags gpkg)+      exe   = listToMaybe $ filter (\e -> map toLower (exeName  e) == name) (executables pkg)+      test  = listToMaybe $ filter (\t -> map toLower (testName t) == name) (testSuites  pkg)+      bench = listToMaybe $ filter (\b -> map toLower (benchmarkName b) == name) (benchmarks pkg)+      repo  = listToMaybe $ filter (\r -> display (repoKind r) == name || (map toLower <$> repoTag r) == Just name) (sourceRepos pkg)+  in fromMaybe "" $+       (getFlagField       field <$> flag)  <|>+       (getExecutableField field <$> exe)   <|>+       (getTestSuiteField  field <$> test)  <|>+       (getBenchmarkField  field <$> bench) <|>+       (getSourceRepoField field <$> repo)+-- Catch-all+getField (FieldName Nothing field)+  | field `elem` packageDescriptionFields = getPackageDescriptionField field . snd++  | field `elem` libraryFields = maybe "" (getLibraryField field) . library . snd++  | field `elem` buildInfoFields = \(_, pkg) ->+    let lib = libBuildInfo <$> library pkg+        exe = buildInfo <$> listToMaybe (executables pkg)+    in maybe "" (getBuildInfoField field) (lib <|> exe)++  | otherwise = const ""++-- * 'PackageDescription'++-- | Get a field from a 'PackageDescription'.+getPackageDescriptionField :: String -> PackageDescription -> String+getPackageDescriptionField "extra-source-files" = unlines' . extraSrcFiles+getPackageDescriptionField "extra-doc-files" = unlines' . extraDocFiles+getPackageDescriptionField "extra-tmp-files" = unlines' . extraTmpFiles+getPackageDescriptionField "license-files" = unlines' . licenseFiles+getPackageDescriptionField "build-depends" = unlines' . map display . buildDepends+getPackageDescriptionField "license-file" = unlines' . licenseFiles+getPackageDescriptionField "package-url" = pkgUrl+getPackageDescriptionField "bug-reports" = bugReports+getPackageDescriptionField "description" = description+getPackageDescriptionField "tested-with" = unlines' . map (\(c, v) -> display c ++ " " ++ display v) . testedWith+getPackageDescriptionField "data-files" = unlines' . dataFiles+getPackageDescriptionField "maintainer" = maintainer+getPackageDescriptionField "build-type" = unlines' . map display . maybeToList . buildType+getPackageDescriptionField "copyright" = copyright+getPackageDescriptionField "stability" = stability+getPackageDescriptionField "data-dir" = dataDir+getPackageDescriptionField "homepage" = homepage+getPackageDescriptionField "synopsis" = synopsis+getPackageDescriptionField "category" = category+getPackageDescriptionField "version" = display . pkgVersion . package+getPackageDescriptionField "license" = display . license+getPackageDescriptionField "author" = author+getPackageDescriptionField "name" = unPackageName . pkgName . package+getPackageDescriptionField _ = const ""++-- | All the fields in a 'PackageDescription'.+packageDescriptionFields :: [String]+packageDescriptionFields = ["name", "version", "build-type", "build-depends", "license", "license-files", "copyright", "maintainer", "author", "stability", "homepage", "package-url", "bug-reports", "synopsis", "description", "category", "tested-with", "data-files", "data-dir", "extra-source-files", "extra-doc-files", "extra-tmp-files"]++-- * 'Flag'++-- | Get a field from a 'Flag'.+getFlagField :: String -> Flag -> String+getFlagField "description" = flagDescription+getFlagField "default" = display . flagDefault+getFlagField "manual" = display . flagManual+getFlagField "name" = flagName'+getFlagField _ = const ""++-- | All the fields in a 'Flag'.+flagFields :: [String]+flagFields = ["name", "description", "default", "manual"]++-- * 'SourceRepo'++-- | Get a field from a 'SourceRepo'.+getSourceRepoField :: String -> SourceRepo -> String+getSourceRepoField "name"     = display . repoKind+getSourceRepoField "type"     = maybe "" display . repoType+getSourceRepoField "location" = fromMaybe "" . repoLocation+getSourceRepoField "module"   = fromMaybe "" . repoModule+getSourceRepoField "branch"   = fromMaybe "" . repoBranch+getSourceRepoField "tag"      = fromMaybe "" . repoTag+getSourceRepoField "subdir"   = fromMaybe "" . repoSubdir+getSourceRepoField _ = const ""++-- | All the fields in a 'SourceRepo'.+sourceRepoFields :: [String]+sourceRepoFields = ["name", "type", "location", "module", "branch", "tag", "subdir"]++-- * 'Library'++-- | Get a field from a 'Library'.+getLibraryField :: String -> Library -> String+getLibraryField "exposed" = display . libExposed+getLibraryField "exposed-modules" = unlines' . map display . exposedModules+getLibraryField "reexported-modules" = unlines' . map display . reexportedModules+getLibraryField field = getBuildInfoField field . libBuildInfo++-- | All the fields from a 'Library'.+libraryFields :: [String]+libraryFields = ["exposed", "exposed-modules", "reexported-modules"]++-- * @Executable'++-- | Get a field from an 'Executable'.+getExecutableField :: String -> Executable -> String+getExecutableField "name"    = exeName+getExecutableField "main-is" = modulePath+getExecutableField field = getBuildInfoField field . buildInfo++-- | All the fields in an 'Executable'.+executableFields :: [String]+executableFields = ["name", "main-is"]++-- * 'TestSuite'++-- | Get a field from a 'TestSuite'.+getTestSuiteField :: String -> TestSuite -> String+getTestSuiteField "name" = testName+getTestSuiteField "type" = get . testInterface where+  get (TestSuiteExeV10 _ _) = "exitcode-stdio-1.0"+  get (TestSuiteLibV09 _ _) = "detailed-0.9"+  get (TestSuiteUnsupported (TestTypeExe v)) = "exitcode-stdio-" ++ display v+  get (TestSuiteUnsupported (TestTypeLib v)) = "detailed-" ++ display v+  get (TestSuiteUnsupported (TestTypeUnknown s v)) = s ++ "-" ++ display v+getTestSuiteField "main-is" = get . testInterface where+  get (TestSuiteExeV10 _ f) = f+  get _ = ""+getTestSuiteField "test-module" = get . testInterface where+  get (TestSuiteLibV09 _ m) = display m+  get _ = ""+getTestSuiteField "enabled" = display . testEnabled+getTestSuiteField field = getBuildInfoField field . testBuildInfo++-- | All the fields in a 'TestSuite'.+testSuiteFields :: [String]+testSuiteFields = ["name", "type", "main-is", "test-module", "enabled"]++-- * 'Benchmark'++-- | Get a field from a 'Benchmark'.+getBenchmarkField :: String -> Benchmark -> String+getBenchmarkField "name" = benchmarkName+getBenchmarkField "type" = get . benchmarkInterface where+  get (BenchmarkExeV10 _ _) = "exitcode-stdio-1.0"+  get (BenchmarkUnsupported (BenchmarkTypeExe v)) = "exitcode-stdio-" ++ display v+  get (BenchmarkUnsupported (BenchmarkTypeUnknown s v)) = s ++ "-" ++ display v+getBenchmarkField "main-is" = get . benchmarkInterface where+  get (BenchmarkExeV10 _ f) = f+  get _ = ""+getBenchmarkField "enabled" = display . benchmarkEnabled+getBenchmarkField field = getBuildInfoField field . benchmarkBuildInfo++-- | All the fields in a 'Benchmark'.+benchmarkFields :: [String]+benchmarkFields = ["name", "type", "main-is", "enabled"]++-- * 'BuildInfo'++-- | Get a field from some 'BuildInfo'.+getBuildInfoField :: String -> BuildInfo -> String+getBuildInfoField field = unlines' . get field where+  get "extra-libraries"      = extraLibs+  get "extra-ghci-libraries" = extraGHCiLibs+  get "extra-lib-dirs"       = extraLibDirs+  get "extensions"         = map display . oldExtensions+  get "default-extensions" = map display . defaultExtensions+  get "other-extensions"   = map display . otherExtensions+  get "ghc-options"        = concatMap snd . filter ((==GHC) . fst) . options+  get "ghc-prof-options"   = concatMap snd . filter ((==GHC) . fst) . profOptions+  get "ghc-shared-options" = concatMap snd . filter ((==GHC) . fst) . sharedOptions+  get "pkgconfig-depends"  = map display . pkgconfigDepends+  get "install-includes"   = installIncludes+  get "hs-source-dirs" = hsSourceDirs+  get "build-depends"  = map display . targetBuildDepends+  get "other-modules"  = map display . otherModules+  get "include-dirs"   = includeDirs+  get "build-tools" = map display . buildTools+  get "cc-options"  = ccOptions+  get "cpp-options" = cppOptions+  get "ld-options"  = ldOptions+  get "c-sources"  = cSources+  get "js-sources" = jsSources+  get "frameworks" = frameworks+  get "buildable"  = (:[]) . display . buildable+  get "includes" = includes+  get _ = const []++-- | All the fields in a 'BuildInfo'+buildInfoFields :: [String]+buildInfoFields = ["build-depends", "other-modules", "hs-source-dirs", "extensions", "default-extensions", "other-extensions", "build-tools", "buildable", "ghc-options", "ghc-prof-options", "ghc-shared-options", "includes", "install-includes", "include-dirs", "c-sources", "js-sources", "extra-libraries", "extra-ghci-libraries", "extra-lib-dirs", "cc-options", "ld-options", "pkgconfig-depends", "frameworks"]++-- * Utilities++-- | Like 'unlines', but don't include the trailing newline.+unlines' :: [String] -> String+unlines' = init' . unlines where+  init' [] = []+  init' xs = init xs++-- | Get the name of a flag.+flagName' :: Flag -> String+flagName' = (\(FlagName name) -> name) . flagName
+ cabal-info/Main.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE TupleSections #-}++module Main where++import Control.Exception (SomeException, catch)+import Control.Monad (unless)++import Data.Maybe (fromMaybe)++import Distribution.PackageDescription (GenericPackageDescription, PackageDescription)+import Distribution.System (buildArch, buildOS)++import System.Exit (exitFailure)+import System.IO (hPutStrLn, stderr)++import Args+import Describe+import Fields++import Cabal.Info++main :: IO ()+main = do+  args  <- getArgs+  gpkgd <- getGenericPackageDescription args+  let pkgd = do+        (g, fp) <- gpkgd+        p <- applyConfiguration args fp g+        pure (g, p)++  case (pkgd, field args) of+    (Right pkg, Just fld) ->+      let fldval = getField fld pkg+      in unless (null fldval) $ putStrLn fldval+    (Right (_, pkg), Nothing) -> putStrLn $ describe pkg+    (Left err, _) -> dieWith $ prettyPrintErr err++-- | Attempt to fetch and parse the package description.+getGenericPackageDescription :: Args -> IO (Either CabalError (GenericPackageDescription, FilePath))+getGenericPackageDescription args = case cabalFile args of+  Just fp -> fmap (,fp) <$> openGenericPackageDescription fp+  Nothing -> findGenericPackageDescription++-- | Evaluate the conditionals in the package description.+applyConfiguration :: Args -> FilePath -> GenericPackageDescription -> Either CabalError PackageDescription+applyConfiguration args = evaluateConditions (flags args) os arch . Just where+  os   = Just . fromMaybe buildOS   $ theOS   args+  arch = Just . fromMaybe buildArch $ theArch args++-- | Print a message to stderr and exit with failure.+dieWith :: String -> IO ()+dieWith err = hPutStrLn stderr err >> exitFailure
+ library/Cabal/Info.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TupleSections #-}++-- | Get information from cabal files.+--+-- Motivating example, test discovery based on a library's exposed+-- modules:+--+-- > import Test.Doctest (doctest)+-- > import Cabal.Info (getLibraryModules)+-- >+-- > main :: IO ()+-- > main = getLibraryModules >>= doctest . either (const []) id+--+-- Simple!+module Cabal.Info+  ( -- * Reading .cabal files+    findCabalFile+  , findPackageDescription+  , findPackageDescription'+  , findGenericPackageDescription+  , openPackageDescription+  , openPackageDescription'+  , openGenericPackageDescription++  -- * Errors+  , CabalError(..)+  , prettyPrintErr++  -- * Conditionals+  , evaluateConditions++  -- * Libraries+  , getLibrary+  , getLibraryModules++  -- * Modules+  , moduleFilePath+  ) where++import Control.Exception (SomeException, catch)+import Control.Monad (unless)++import Data.Maybe (fromMaybe, listToMaybe)++import Distribution.Compiler+import Distribution.InstalledPackageInfo (PError(..))+import Distribution.ModuleName+import Distribution.PackageDescription+import Distribution.PackageDescription.Configuration+import Distribution.PackageDescription.Parse+import Distribution.System++import System.FilePath+import System.Directory (getCurrentDirectory, getDirectoryContents)++-- * Errors++-- | Automatically finding and dealing with a .cabal file failed for+-- some reason.+data CabalError =+    NoCabalFile+  -- ^ A .cabal file could not be found in the current directory or+  -- any of its parents.+  | ParseError FilePath PError+  -- ^ A file with the extension .cabal was found, but could not be+  -- parsed.+  | NoFlagAssignment (Maybe FilePath)+  -- ^ A consistent flag assignment could not be found.+  | NoLibrary FilePath+  -- ^ There is no library section.+  deriving (Eq, Show)++-- | Pretty-print an error.+prettyPrintErr :: CabalError -> String+prettyPrintErr NoCabalFile = "Could not find .cabal file."+prettyPrintErr (ParseError fp err) = "Parse error in " ++ fp ++ ": " ++ show' err ++ "." where+  show' (AmbiguousParse _ l) = "ambiguous parse on line " ++ show l+  show' (NoParse _ l) = "no parse on line " ++ show l+  show' (TabsError l) = "tabbing error on line " ++ show l+  show' (FromString _ (Just l)) = "no parse on line " ++ show l+  show' (FromString _ Nothing) = "no parse"+prettyPrintErr (NoFlagAssignment (Just fp)) = "Could not find flag assignment for " ++ fp ++ "."+prettyPrintErr (NoFlagAssignment Nothing) = "Could not find flag assignment."+prettyPrintErr (NoLibrary fp) = "Missing library section in " ++ fp ++ "."++-- * Reading .cabal files++-- | Find the .cabal file.+--+-- If there are .cabal files in the current directory, the first is+-- read. Otherwise, the parent directory is checked. This continues+-- until the filesystem root is reached without finding a .cabal file.+findCabalFile :: IO (Maybe FilePath)+findCabalFile = do+  cwd <- getCurrentDirectory+  listToMaybe <$> findFile ((==".cabal") . takeExtension) (dirs cwd)++  where+    dirs dir = takeWhile (\d -> takeDirectory d /= d) (iterate takeDirectory dir) ++ [takeDrive dir]++    findFile p (d:ds) = (++) <$> (filter p . map (d</>) <$> getDirectoryContents d) <*> findFile p ds+    findFile _ [] = pure []++-- | Find and read the .cabal file, applying the default flags.+findPackageDescription :: IO (Either CabalError (PackageDescription, FilePath))+findPackageDescription = findPackageDescription' [] Nothing Nothing++-- | Find and read the .cabal file, applying the given flags,+-- operating system, and architecture.+findPackageDescription' :: FlagAssignment -> Maybe OS -> Maybe Arch -> IO (Either CabalError (PackageDescription, FilePath))+findPackageDescription' flags os arch = findCabalFile >>=+   maybe (pure $ Left NoCabalFile) (\fp -> fmap (,fp) <$> openPackageDescription' flags os arch fp)++-- | Find and read the .cabal file.+findGenericPackageDescription :: IO (Either CabalError (GenericPackageDescription, FilePath))+findGenericPackageDescription = findCabalFile >>=+  maybe (pure $ Left NoCabalFile) (\fp -> fmap (,fp) <$> openGenericPackageDescription fp)++-- | Open and parse a .cabal file, applying the default flags.+openPackageDescription :: FilePath -> IO (Either CabalError PackageDescription)+openPackageDescription = openPackageDescription' [] Nothing Nothing++-- | Open and parse a .cabal file, and apply the given flags,+-- operating system, and architecture.+openPackageDescription' :: FlagAssignment -> Maybe OS -> Maybe Arch -> FilePath -> IO (Either CabalError PackageDescription)+openPackageDescription' flags os arch fp = openGenericPackageDescription fp <$$> \case+  Right gpkg -> evaluateConditions flags os arch (Just fp) gpkg+  Left err -> Left err++-- | Open and parse a .cabal file.+openGenericPackageDescription :: FilePath -> IO (Either CabalError GenericPackageDescription)+openGenericPackageDescription fp = do+  cabalFile <- readFile fp+  pure $ case parsePackageDescription cabalFile of+    ParseOk _ pkg -> Right pkg+    ParseFailed err -> Left $ ParseError fp err++-- * Conditionals++-- | Apply the given flags, operating system, and architecture.+evaluateConditions :: FlagAssignment -> Maybe OS -> Maybe Arch -> Maybe FilePath -> GenericPackageDescription -> Either CabalError PackageDescription+evaluateConditions flags os arch fp gpkg = either (const . Left $ NoFlagAssignment fp) (Right . fst) $+  finalizePackageDescription flags (const True) platform compiler [] gpkg++  where+    platform = Platform (fromMaybe buildArch arch) (fromMaybe buildOS os)+    compiler = unknownCompilerInfo buildCompilerId NoAbiTag++-- * Libraries++-- | Search for the .cabal file and return its library section.+getLibrary :: IO (Either CabalError Library)+getLibrary = findPackageDescription <$$> \case+  Right (pkgd, fp) -> maybe (Left $ NoLibrary fp) Right $ library pkgd+  Left err -> Left err++-- | Search for the .cabal file and return its exposed library+-- modules, as absolute paths.+getLibraryModules :: IO (Either CabalError [FilePath])+getLibraryModules = findPackageDescription <$$> \case+  Right (pkgd, fp) -> maybe (Left $ NoLibrary fp) (\l -> Right . map (moddir fp l) $ exposedModules l) $ library pkgd+  Left err -> Left err++  where+    moddir fp l m = dropFileName fp </> moduleFilePath (libBuildInfo l) m++-- * Modules++-- | Turn a module name + some build info to a file path taking the+-- hs-source-dirs field into account.+--+-- This path will be relative to the .cabal file.+moduleFilePath :: BuildInfo -> ModuleName -> FilePath+moduleFilePath b m = joinPath ((fromMaybe "" . listToMaybe $ hsSourceDirs b) : components m) <.> "hs"++-- * Utils++-- | Flipped fmap+(<$$>) :: Functor f => f a -> (a -> b) -> f b+(<$$>) = flip fmap