packages feed

os-release 0.2.2 → 1.0.0

raw patch · 35 files changed

+1067/−314 lines, 35 filesdep +aesondep +bytestringdep +filepathdep −containersdep −hlintdep −parsecdep ~basedep ~hspecnew-uploader

Dependencies added: aeson, bytestring, filepath, hspec-megaparsec, megaparsec, pretty-simple, safe-exceptions, tasty, tasty-golden, tasty-hspec, text, unordered-containers

Dependencies removed: containers, hlint, parsec, process, regex-compat, temporary, transformers

Dependency ranges changed: base, hspec

Files

ChangeLog.rst view
@@ -1,3 +1,8 @@+1.0.0+=====++- Redo of the entire API+ 0.2.2 ===== 
library/System/OsRelease.hs view
@@ -1,114 +1,192 @@-{-# LANGUAGE FlexibleContexts, TypeSynonymInstances, FlexibleInstances, GeneralizedNewtypeDeriving, CPP #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeOperators #-} +-- | A module to retrieve os-release information according to the+-- freedesktop standard:+-- https://www.freedesktop.org/software/systemd/man/os-release.html+--+-- Usage example:+--+-- @+-- do+--   Just (OsRelease {..}) <- fmap osRelease <$\> parseOsRelease+--   putStrLn name+-- @ module System.OsRelease-    ( parseOs-    , readOs-    , readOs'-    , OsReleaseValue (..)-    , OsReleaseKey (..)-    , OsReleaseLine-    , OsRelease-    , OsReleaseError-    )+  (+  -- * data types+    OsReleaseResult(..)+  , OsRelease(..)++  -- * read/parse os-release+  , parseOsRelease+  , readOsRelease++  -- * defaults+  , defaultOsRelease+  , defaultAssignments++  -- * low-level+  , parseAssignments+  , parseAssignment+  , getAllAssignments+  , getOsRelease+  , parseOsRelease'+  ) where -import Text.ParserCombinators.Parsec-import Text.Parsec.Prim hiding (try)-import Data.Map.Lazy hiding (foldl)-import Data.Functor.Identity-import Data.String-import Data.Monoid-import Data.Either+import           System.OsRelease.Megaparsec -#if MIN_VERSION_base(4,8,0)--- note: using hscpp it should be possible to use `#if not COND`-#else-import Control.Applicative ((<$>))-#endif+import           Control.Applicative+import           Control.Monad+import           Control.Exception.Safe+import           Data.Aeson+import           Data.Aeson.TH+import           Data.Char+import           Data.Either+import           Data.List+import           Data.Maybe+import           Data.Void+import           GHC.Generics+import           Prelude                 hiding ( id+                                                ) -import Control.Monad-import qualified Control.Exception as E+import qualified Data.HashMap.Strict           as HM+import qualified Data.Text                     as T+import qualified Text.Megaparsec               as MP -type OsReleaseLine  = (OsReleaseKey, OsReleaseValue)-newtype OsReleaseKey   = OsReleaseKey String-    deriving (Ord, Eq, IsString, Show) -newtype OsReleaseValue = OsReleaseValue String-    deriving (IsString, Eq, Show)+data OsReleaseResult = OsReleaseResult {+    osRelease :: !OsRelease+  , unknown_fields :: [(String, String)]+  , parse_errors :: [MP.ParseError String Void]+} deriving (Show) -type OsRelease      = Map OsReleaseKey OsReleaseValue -class Parsable a where-    parser :: ParsecT String () Identity a+-- | All the explicitly documented fields of @os-release@.+data OsRelease = OsRelease {+    name :: !(String)+  , version :: !(Maybe String)+  , id :: !(String)+  , id_like :: !(Maybe String)+  , version_codename :: !(Maybe String)+  , version_id :: !(Maybe String)+  , pretty_name :: !(String)+  , ansi_color :: !(Maybe String)+  , cpe_name :: !(Maybe String)+  , home_url :: !(Maybe String)+  , documentation_url :: !(Maybe String)+  , support_url :: !(Maybe String)+  , bug_report_url :: !(Maybe String)+  , privacy_policy_url :: !(Maybe String)+  , build_id :: !(Maybe String)+  , variant :: !(Maybe String)+  , variant_id :: !(Maybe String)+  , logo :: !(Maybe String)+} deriving (Generic, Show) -instance Parsable OsReleaseKey where-    parser = OsReleaseKey <$> many1 (alphaNum <|> char '_') -instance Parsable OsReleaseValue where-    parser = OsReleaseValue <$> (qVal <|> nqVal)-      where-        qVal :: Parser String-        qVal  = do-            quote <- oneOf "'\""-            value <- manyTill (qValInside quote) (char quote)-            noJunk-            return (foldl mappend "" value)+class GetRecords a where+  getRecords :: a -> [String] -        noJunk = try . lookAhead $ eof <|> (void newline)+instance {-# OVERLAPPABLE #-} GetRecords (f p) => GetRecords (M1 i c f p) where+  getRecords (M1 x) = getRecords x -        qValInside :: Char -> ParsecT String () Identity String-        qValInside quote = (qSpecial quote) <|> (:[]) <$> (noneOf $ specials quote)+instance {-# OVERLAPPING #-} Selector c => GetRecords (M1 S c f p) where+  getRecords x = [selName x] -        qSpecial :: Char -> ParsecT String () Identity String-        qSpecial quote = (\x -> [x!!1]) <$>-            foldl1 (<|>) [try (string $ "\\" <> [x]) | x <- specials quote]+instance (GetRecords (a p), GetRecords (b p)) => GetRecords ((a :*: b) p) where+  getRecords (a :*: b) = getRecords a ++ getRecords b -        specials :: Char -> [Char]-        specials quote =-            [ quote-            , '\\'-            , '$'-            , '`'-            ] -        nqVal :: Parser String-        nqVal = do-            x <- many alphaNum-            noJunk-            return x -instance Parsable OsReleaseLine where-    parser = do-        var <- parser :: ParsecT String () Identity OsReleaseKey-        _ <- char '='-        val <- parser :: ParsecT String () Identity OsReleaseValue-        return (var, val)+-- | The defaults as per the spec:+--+-- @+-- NAME=Linux+-- ID=linux+-- PRETTY_NAME=Linux+-- @+defaultOsRelease :: OsRelease+defaultOsRelease = OsRelease { name               = "Linux"+                             , version            = Nothing+                             , id                 = "linux"+                             , id_like            = Nothing+                             , version_codename   = Nothing+                             , version_id         = Nothing+                             , pretty_name        = "Linux"+                             , ansi_color         = Nothing+                             , cpe_name           = Nothing+                             , home_url           = Nothing+                             , documentation_url  = Nothing+                             , support_url        = Nothing+                             , bug_report_url     = Nothing+                             , privacy_policy_url = Nothing+                             , build_id           = Nothing+                             , variant            = Nothing+                             , variant_id         = Nothing+                             , logo               = Nothing+                             } -instance Parsable OsRelease where-    parser = fromList <$> (sepEndBy (parser :: ParsecT String () Identity OsReleaseLine) newline)+-- | Like `defaultOsRelease`, except as key-value pair.+defaultAssignments :: [(String, String)]+defaultAssignments =+  [("NAME", "Linux"), ("ID", "linux"), ("PRETTY_NAME", "Linux")]  -readOs :: IO (Either OsReleaseError OsRelease)-readOs = do-    xs <- readOs' ["/etc/os-release", "/usr/lib/os-release"]-    case xs of-        Left  e -> return $ Left e-        Right x -> return . h $ parseOs x-  where-    h (Left  e) = Left $ OsReleaseParseError e-    h (Right x) = Right x+-- | Get all allAssignments as @(key, val)@ from the @os-release@+-- file contents.+getAllAssignments :: String  -- ^ file contents of os-release+                  -> [Either (MP.ParseError String Void) (String, String)]+getAllAssignments = fromRight [] . MP.parse parseAssignments "os-release" -data OsReleaseError = OsReleaseError String-                    | OsReleaseParseError ParseError-    deriving (Show) -readOs' :: [FilePath] -> IO (Either OsReleaseError String)-readOs' fs = do-    xs <- mapM (E.try . readFile) fs :: IO [Either E.IOException String]-    return . h $ rights xs-  where-    h [] = Left . OsReleaseError $ "Neither of " <> unwords fs <> " could be read"-    h (x:_) = Right x+-- | Parse the assignments into `OsRelease`. This is merged with the+-- defaults as per the spec. In case of no assignments, also returns+-- the defaults.+getOsRelease :: [(String, String)]  -- ^ assignments+             -> OsRelease+getOsRelease =+  (\case+      Error   _ -> defaultOsRelease+      Success v -> v+    )+    . fromJSON+    . Object+    . (\x -> HM.union x (HM.fromList . aesonify $ defaultAssignments))+    . HM.fromList+    . aesonify+ where+  aesonify = fmap (\(k, v) -> (T.toLower . T.pack $ k, String . T.pack $ v)) -parseOs :: String -> Either ParseError OsRelease-parseOs xs = parse (parser :: ParsecT String () Identity OsRelease) "os-release" xs++-- | Tries to read @\"\/etc\/os-release\"@ and @\"\/usr\/lib\/os_release\"@ in order.+--+-- Throws @IOError@ if both files could not be read.+readOsRelease :: IO String+readOsRelease = readFile "/etc/os-release" <|> readFile "/usr/lib/os-release"+++-- | Tries to read @\"\/etc\/os-release\"@ and @\"\/usr\/lib\/os_release\"@ in order+-- and parses into `OsReleaseResult`. Returns @Nothing@ if both files could+-- not be read.+parseOsRelease :: IO (Maybe OsReleaseResult)+parseOsRelease =+  handleIO (\_ -> pure Nothing) . fmap (Just . parseOsRelease') $ readOsRelease+++-- | Like `parseOsRelease`, except taking the input String explicitly.+-- Primarily for tests.+parseOsRelease' :: String -> OsReleaseResult+parseOsRelease' s =+  let (errs, ass) = partitionEithers . getAllAssignments $ s+      osr         = getOsRelease ass+      unknown_fields' =+          HM.toList+            . foldr (\x y -> HM.delete (fmap toUpper x) y) (HM.fromList ass)+            $ (init . getRecords . from $ defaultOsRelease)+  in  OsReleaseResult osr unknown_fields' errs+++deriveJSON defaultOptions ''OsRelease
+ library/System/OsRelease/Megaparsec.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE CPP #-}++module System.OsRelease.Megaparsec where++import           Control.Applicative+import           Control.Monad+#if !MIN_VERSION_base(4,13,0)+import           Control.Monad.Fail             ( MonadFail )+#endif+import           Data.Char+import           Data.Functor+import           Data.Void++import qualified Text.Megaparsec               as MP+import qualified Text.Megaparsec.Char          as MP+++-- | Parse the entire file, handling newlines and comments gracefully.+--+-- This parser generally shouldn't fail, but instead report a failed+-- parsed line as @Left@ value.+parseAssignments :: MP.Parsec+                      Void+                      String+                      [Either (MP.ParseError String Void) (String, String)]+parseAssignments =+  (\xs x -> join xs ++ x) <$> many (line MP.eol) <*> line MP.eof+ where+  line eol = choice'+    [ comment $> []+    , blank $> []+    , fmap+      (: [])+      ( MP.withRecovery (\e -> parseUntil eol $> Left e)+      . fmap Right+      $ (parseAssignment <* eol)+      )+    ]+   where+    comment = pWs *> MP.char '#' *> parseUntil eol *> eol+    blank   = pWs *> eol+++-- | Parse a single line assignment and extract the right hand side.+-- This is only a subset of a shell parser, refer to the spec for+-- details.+parseAssignment :: MP.Parsec Void String (String, String)+parseAssignment =+  (,) <$> (pWs *> key) <*> (MP.char '=' *> (MP.try qval <|> mempty) <* pWs)+ where+  dropSpace :: String -> String+  dropSpace = reverse . dropWhile (\x -> x == ' ' || x == '\t') . reverse++  key :: MP.Parsec Void String String+  key = some (MP.try MP.alphaNumChar <|> MP.char '_')++  qval :: MP.Parsec Void String String+  qval = do+    c <- MP.lookAhead MP.printChar+    case c of+      ' '  -> pure ""+      '"'  -> MP.char c *> val c <* MP.char c+      '\'' -> MP.char c *> val c <* MP.char c+      -- no quote, have to drop trailing spaces+      _    -> fmap+        dropSpace+        (some $ MP.satisfy (\x -> isAlphaNum x || (x `elem` ['_', '-', '.']))) -- this is more lax than the spec+  val :: Char -> MP.Parsec Void String String+  val !q = many (qspecial q <|> MP.noneOf (specials q)) -- noneOf may be too lax++  qspecial :: Char -> MP.Parsec Void String Char+  qspecial !q =+    fmap (!! 1)+      . (\xs -> choice' xs)+      . fmap (\s -> MP.try . MP.chunk $ ['\\', s])+      $ (specials q)++  specials :: Char -> [Char]+  specials !q = [q, '\\', '$', '`']+++parseUntil :: MP.Parsec Void String a -> MP.Parsec Void String String+parseUntil !p = do+  (MP.try (MP.lookAhead p) $> [])+    <|> (do+          c  <- MP.anySingle+          c2 <- parseUntil p+          pure ([c] `mappend` c2)+        )+++-- | Parse one or more white spaces or tabs.+pWs :: MP.Parsec Void String ()+pWs = many (MP.satisfy (\x -> x == ' ' || x == '\t')) $> ()+++-- | Try all parses in order, failing if all failed. Also fails+-- on empty list.+choice' :: (MonadFail f, MP.MonadParsec e s f) => [f a] -> f a+choice' = \case+  [] -> fail "Empty list"+  xs -> foldr1 (\x y -> MP.try x <|> MP.try y) xs
os-release.cabal view
@@ -1,94 +1,131 @@-author:         Jan Matějka-category:       System-license-file:   LICENSE-build-type:     Simple-cabal-version:  >= 1.16-copyright:      2014 Jan Matějka <yac@blesmrt.net>-license:        BSD3-maintainer:     <yac@blesmrt.net>-homepage:       https://github.com/yaccz/os-release-name:           os-release-synopsis:       /etc/os-release helpers-version:        0.2.2-description:    /etc/os-release helpers-extra-doc-files: ChangeLog.rst README.rst+cabal-version:      3.0+author:             Jan Matějka+category:           System+license-file:       LICENSE+build-type:         Simple+copyright:          2014 Jan Matějka <yac@blesmrt.net>+license:            BSD-3-Clause+maintainer:         Julian Ospald <hasufell@posteo.de>+homepage:           https://github.com/yaccz/os-release+name:               os-release+synopsis:           /etc/os-release helpers+version:            1.0.0+description:+  \/etc\/os-release helpers as per the freedesktop spec: https://www.freedesktop.org/software/systemd/man/os-release.html -Flag devel-  Description:  Enables -Werror-  Default:      False-  Manual:       True+extra-doc-files:+  ChangeLog.rst+  README.rst +extra-source-files: tests/Golden/data/*.golden tests/Golden/data/*.in++flag devel+  description: Enables -Werror+  default:     False+  manual:      True+ source-repository head   type:     git   location: https://github.com/yaccz/os-release.git -library-    build-depends:-        base < 5-      , parsec-      , containers-      , parsec-      , containers-      , transformers-    default-language:   Haskell2010-    exposed-modules:    System.OsRelease+common base+  build-depends: base >=4.11 && <5++common bytestring+  build-depends: bytestring++common aeson+  build-depends: aeson >=1.4++common filepath+  build-depends: filepath >=1.4.2.1++common hspec+  build-depends: hspec >=2.7.1++common hspec-megaparsec+  build-depends: hspec-megaparsec >=2.1.0++common megaparsec+  build-depends: megaparsec >=8.0.0++common pretty-simple+  build-depends: pretty-simple >=1.0.0.0++common safe-exceptions+  build-depends: safe-exceptions >=0.1.7.0++common tasty+  build-depends: tasty >=1.3++common tasty-golden+  build-depends: tasty-golden >=2.3.4++common tasty-hspec+  build-depends: tasty-hspec >=1.1.5.1++common text+  build-depends: text >=1.2++common unordered-containers+  build-depends: unordered-containers >=0.2.10.0++common config+  default-language:   Haskell2010++  if flag(devel)     ghc-options:-        -Wall-    ghc-prof-options:-        -auto-all-        -prof-    hs-source-dirs:     library+      -Werror -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns+      -fwarn-incomplete-record-updates -test-suite tests-    build-depends:-        base < 5-      , os-release-      , hspec-      , parsec-      , containers-      , transformers-      , temporary-    default-language:-        Haskell2010+  else+    ghc-options:+      -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns+      -fwarn-incomplete-record-updates -    if flag(devel)-       ghc-options: -Wall -Werror-    else-       ghc-options: -Wall-    hs-source-dirs:-        library-        tests/unit-    main-is:-        Spec.hs-    other-modules:-        System.OsRelease-        OsReleaseSpec-    type:-        exitcode-stdio-1.0+  default-extensions:+    BangPatterns+    LambdaCase+    OverloadedStrings+    QuasiQuotes+    TemplateHaskell+    TupleSections+    TypeFamilies -test-suite documentation-    build-depends:-        base < 5-      , process == 1.*-      , regex-compat == 0.*-    default-language:-        Haskell2010-    hs-source-dirs:-        tests-    main-is:-        Haddock.hs-    type:-        exitcode-stdio-1.0+library+  import:+    config+    , base+    , aeson+    , megaparsec+    , safe-exceptions+    , text+    , unordered-containers -test-suite style-    build-depends:-        base < 5-      , hlint == 1.*-    default-language:-        Haskell2010-    hs-source-dirs:-        tests-    main-is:-        HLint.hs-    type:-        exitcode-stdio-1.0+  exposed-modules: System.OsRelease+  other-modules:   System.OsRelease.Megaparsec+  hs-source-dirs:  library++test-suite tests+  import:+    config+    , base+    , bytestring+    , filepath+    , hspec+    , hspec-megaparsec+    , megaparsec+    , pretty-simple+    , tasty+    , tasty-golden+    , tasty-hspec+    , text++  build-depends:  os-release+  hs-source-dirs: tests+  main-is:        Main.hs+  other-modules:+    Golden.Real+    Specs.Megaparsec++  type:           exitcode-stdio-1.0
+ tests/Golden/Real.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE CPP #-}++module Golden.Real where++import System.OsRelease++import System.FilePath+import Text.Pretty.Simple+import Test.Tasty+import Test.Tasty.Golden++import qualified Data.ByteString.Lazy          as B+import qualified Data.Text.Lazy                as L+import qualified Data.Text.Encoding            as E+++goldenTests :: IO TestTree+goldenTests = do+  files <- findByExtension [".in"] (takeDirectory (__FILE__) </> "data")+  return $ testGroup+    "Parse os-release into OsRelease"+    (flip fmap files $ \file ->+      let out = replaceExtension file ".golden"+      in  goldenVsString (takeBaseName file) out (parse file)+    )+ where+  parse f = do+    c <- readFile f+    pure+      . B.fromStrict+      . E.encodeUtf8+      . L.toStrict+      . pShowNoColor+      . parseOsRelease'+      $ c
+ tests/Golden/data/alpine.golden view
@@ -0,0 +1,24 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Alpine Linux"+        , version = Nothing+        , id = "alpine"+        , id_like = Nothing+        , version_codename = Nothing+        , version_id = Just "3.11.3"+        , pretty_name = "Alpine Linux v3.11"+        , ansi_color = Nothing+        , cpe_name = Nothing+        , home_url = Just "https://alpinelinux.org/"+        , documentation_url = Nothing+        , support_url = Nothing+        , bug_report_url = Just "https://bugs.alpinelinux.org/"+        , privacy_policy_url = Nothing+        , build_id = Nothing+        , variant = Nothing+        , variant_id = Nothing+        , logo = Nothing+        }+    , unknown_fields = []+    , parse_errors = []+    }
+ tests/Golden/data/alpine.in view
@@ -0,0 +1,7 @@+NAME="Alpine Linux"+ID=alpine+VERSION_ID=3.11.3+PRETTY_NAME="Alpine Linux v3.11"+HOME_URL="https://alpinelinux.org/"+BUG_REPORT_URL="https://bugs.alpinelinux.org/"+
+ tests/Golden/data/arch.golden view
@@ -0,0 +1,29 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Arch Linux"+        , version = Nothing+        , id = "arch"+        , id_like = Nothing+        , version_codename = Nothing+        , version_id = Nothing+        , pretty_name = "Arch Linux"+        , ansi_color = Just "38;2;23;147;209"+        , cpe_name = Nothing+        , home_url = Just "https://www.archlinux.org/"+        , documentation_url = Just "https://wiki.archlinux.org/"+        , support_url = Just "https://bbs.archlinux.org/"+        , bug_report_url = Just "https://bugs.archlinux.org/"+        , privacy_policy_url = Nothing+        , build_id = Just "rolling"+        , variant = Nothing+        , variant_id = Nothing+        , logo = Just "archlinux"+        }+    , unknown_fields =+        [+            ( "LOGO"+            , "archlinux"+            )+        ]+    , parse_errors = []+    }
+ tests/Golden/data/arch.in view
@@ -0,0 +1,11 @@+NAME="Arch Linux"+PRETTY_NAME="Arch Linux"+ID=arch+BUILD_ID=rolling+ANSI_COLOR="38;2;23;147;209"+HOME_URL="https://www.archlinux.org/"+DOCUMENTATION_URL="https://wiki.archlinux.org/"+SUPPORT_URL="https://bbs.archlinux.org/"+BUG_REPORT_URL="https://bugs.archlinux.org/"+LOGO=archlinux+
+ tests/Golden/data/centos.golden view
@@ -0,0 +1,45 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "CentOS Linux"+        , version = Just "8 (Core)"+        , id = "centos"+        , id_like = Just "rhel fedora"+        , version_codename = Nothing+        , version_id = Just "8"+        , pretty_name = "CentOS Linux 8 (Core)"+        , ansi_color = Just "0;31"+        , cpe_name = Just "cpe:/o:centos:centos:8"+        , home_url = Just "https://www.centos.org/"+        , documentation_url = Nothing+        , support_url = Nothing+        , bug_report_url = Just "https://bugs.centos.org/"+        , privacy_policy_url = Nothing+        , build_id = Nothing+        , variant = Nothing+        , variant_id = Nothing+        , logo = Nothing+        }+    , unknown_fields =+        [+            ( "REDHAT_SUPPORT_PRODUCT_VERSION"+            , "8"+            )+        ,+            ( "CENTOS_MANTISBT_PROJECT"+            , "CentOS-8"+            )+        ,+            ( "REDHAT_SUPPORT_PRODUCT"+            , "centos"+            )+        ,+            ( "PLATFORM_ID"+            , "platform:el8"+            )+        ,+            ( "CENTOS_MANTISBT_PROJECT_VERSION"+            , "8"+            )+        ]+    , parse_errors = []+    }
+ tests/Golden/data/centos.in view
@@ -0,0 +1,17 @@+NAME="CentOS Linux"+VERSION="8 (Core)"+ID="centos"+ID_LIKE="rhel fedora"+VERSION_ID="8"+PLATFORM_ID="platform:el8"+PRETTY_NAME="CentOS Linux 8 (Core)"+ANSI_COLOR="0;31"+CPE_NAME="cpe:/o:centos:centos:8"+HOME_URL="https://www.centos.org/"+BUG_REPORT_URL="https://bugs.centos.org/"++CENTOS_MANTISBT_PROJECT="CentOS-8"+CENTOS_MANTISBT_PROJECT_VERSION="8"+REDHAT_SUPPORT_PRODUCT="centos"+REDHAT_SUPPORT_PRODUCT_VERSION="8"+
+ tests/Golden/data/debian.golden view
@@ -0,0 +1,24 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Debian GNU/Linux"+        , version = Just "8 (jessie)"+        , id = "debian"+        , id_like = Nothing+        , version_codename = Nothing+        , version_id = Just "8"+        , pretty_name = "Debian GNU/Linux 8 (jessie)"+        , ansi_color = Nothing+        , cpe_name = Nothing+        , home_url = Just "http://www.debian.org/"+        , documentation_url = Nothing+        , support_url = Just "http://www.debian.org/support/"+        , bug_report_url = Just "https://bugs.debian.org/"+        , privacy_policy_url = Nothing+        , build_id = Nothing+        , variant = Nothing+        , variant_id = Nothing+        , logo = Nothing+        }+    , unknown_fields = []+    , parse_errors = []+    }
+ tests/Golden/data/debian.in view
@@ -0,0 +1,8 @@+PRETTY_NAME="Debian GNU/Linux 8 (jessie)"+NAME="Debian GNU/Linux"+VERSION_ID="8"+VERSION="8 (jessie)"+ID=debian+HOME_URL="http://www.debian.org/"+SUPPORT_URL="http://www.debian.org/support/"+BUG_REPORT_URL="https://bugs.debian.org/"
+ tests/Golden/data/empty.golden view
@@ -0,0 +1,24 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Linux"+        , version = Nothing+        , id = "linux"+        , id_like = Nothing+        , version_codename = Nothing+        , version_id = Nothing+        , pretty_name = "Linux"+        , ansi_color = Nothing+        , cpe_name = Nothing+        , home_url = Nothing+        , documentation_url = Nothing+        , support_url = Nothing+        , bug_report_url = Nothing+        , privacy_policy_url = Nothing+        , build_id = Nothing+        , variant = Nothing+        , variant_id = Nothing+        , logo = Nothing+        }+    , unknown_fields = []+    , parse_errors = []+    }
+ tests/Golden/data/empty.in view
@@ -0,0 +1,1 @@+
+ tests/Golden/data/exherbo.golden view
@@ -0,0 +1,24 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Exherbo"+        , version = Nothing+        , id = "exherbo"+        , id_like = Nothing+        , version_codename = Nothing+        , version_id = Nothing+        , pretty_name = "Exherbo Linux"+        , ansi_color = Just "0;32"+        , cpe_name = Nothing+        , home_url = Just "https://www.exherbo.org/"+        , documentation_url = Nothing+        , support_url = Just "irc://irc.freenode.net/#exherbo"+        , bug_report_url = Just "https://bugs.exherbo.org/"+        , privacy_policy_url = Nothing+        , build_id = Nothing+        , variant = Nothing+        , variant_id = Nothing+        , logo = Nothing+        }+    , unknown_fields = []+    , parse_errors = []+    }
+ tests/Golden/data/exherbo.in view
@@ -0,0 +1,9 @@+NAME="Exherbo"+PRETTY_NAME="Exherbo Linux"+ID="exherbo"+# comment+ANSI_COLOR="0;32"+HOME_URL="https://www.exherbo.org/"+SUPPORT_URL="irc://irc.freenode.net/#exherbo"+BUG_REPORT_URL="https://bugs.exherbo.org/"+
+ tests/Golden/data/fedora.golden view
@@ -0,0 +1,49 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Fedora"+        , version = Just "31 (Container Image)"+        , id = "fedora"+        , id_like = Nothing+        , version_codename = Just ""+        , version_id = Just "31"+        , pretty_name = "Fedora 31 (Container Image)"+        , ansi_color = Just "0;34"+        , cpe_name = Just "cpe:/o:fedoraproject:fedora:31"+        , home_url = Just "https://fedoraproject.org/"+        , documentation_url = Just "https://docs.fedoraproject.org/en-US/fedora/f31/system-administrators-guide/"+        , support_url = Just "https://fedoraproject.org/wiki/Communicating_and_getting_help"+        , bug_report_url = Just "https://bugzilla.redhat.com/"+        , privacy_policy_url = Just "https://fedoraproject.org/wiki/Legal:PrivacyPolicy"+        , build_id = Nothing+        , variant = Just "Container Image"+        , variant_id = Just "container"+        , logo = Just "fedora-logo-icon"+        }+    , unknown_fields =+        [+            ( "REDHAT_SUPPORT_PRODUCT_VERSION"+            , "31"+            )+        ,+            ( "REDHAT_BUGZILLA_PRODUCT"+            , "Fedora"+            )+        ,+            ( "REDHAT_SUPPORT_PRODUCT"+            , "Fedora"+            )+        ,+            ( "PLATFORM_ID"+            , "platform:f31"+            )+        ,+            ( "REDHAT_BUGZILLA_PRODUCT_VERSION"+            , "31"+            )+        ,+            ( "LOGO"+            , "fedora-logo-icon"+            )+        ]+    , parse_errors = []+    }
+ tests/Golden/data/fedora.in view
@@ -0,0 +1,21 @@+NAME=Fedora+VERSION="31 (Container Image)"+ID=fedora+VERSION_ID=31+VERSION_CODENAME=""+PLATFORM_ID="platform:f31"+PRETTY_NAME="Fedora 31 (Container Image)"+ANSI_COLOR="0;34"+LOGO=fedora-logo-icon+CPE_NAME="cpe:/o:fedoraproject:fedora:31"+HOME_URL="https://fedoraproject.org/"+DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f31/system-administrators-guide/"+SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help"+BUG_REPORT_URL="https://bugzilla.redhat.com/"+REDHAT_BUGZILLA_PRODUCT="Fedora"+REDHAT_BUGZILLA_PRODUCT_VERSION=31+REDHAT_SUPPORT_PRODUCT="Fedora"+REDHAT_SUPPORT_PRODUCT_VERSION=31+PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"+VARIANT="Container Image"+VARIANT_ID=container
+ tests/Golden/data/gentoo.golden view
@@ -0,0 +1,24 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Gentoo"+        , version = Nothing+        , id = "gentoo"+        , id_like = Nothing+        , version_codename = Nothing+        , version_id = Nothing+        , pretty_name = "Gentoo/Linux"+        , ansi_color = Just "1;32"+        , cpe_name = Nothing+        , home_url = Just "https://www.gentoo.org/"+        , documentation_url = Nothing+        , support_url = Just "https://www.gentoo.org/support/"+        , bug_report_url = Just "https://bugs.gentoo.org/"+        , privacy_policy_url = Nothing+        , build_id = Nothing+        , variant = Nothing+        , variant_id = Nothing+        , logo = Nothing+        }+    , unknown_fields = []+    , parse_errors = []+    }
+ tests/Golden/data/gentoo.in view
@@ -0,0 +1,8 @@+NAME=Gentoo+ID=gentoo+PRETTY_NAME="Gentoo/Linux"+ANSI_COLOR="1;32"+HOME_URL="https://www.gentoo.org/"+SUPPORT_URL="https://www.gentoo.org/support/"+BUG_REPORT_URL="https://bugs.gentoo.org/"+
+ tests/Golden/data/mint.golden view
@@ -0,0 +1,29 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Linux Mint"+        , version = Just "18.2 (Sonya)"+        , id = "linuxmint"+        , id_like = Just "ubuntu"+        , version_codename = Just "sonya"+        , version_id = Just "18.2"+        , pretty_name = "Linux Mint 18.2"+        , ansi_color = Nothing+        , cpe_name = Nothing+        , home_url = Just "http://www.linuxmint.com/"+        , documentation_url = Nothing+        , support_url = Just "http://forums.linuxmint.com/"+        , bug_report_url = Just "http://bugs.launchpad.net/linuxmint/"+        , privacy_policy_url = Nothing+        , build_id = Nothing+        , variant = Nothing+        , variant_id = Nothing+        , logo = Nothing+        }+    , unknown_fields =+        [+            ( "UBUNTU_CODENAME"+            , "xenial"+            )+        ]+    , parse_errors = []+    }
+ tests/Golden/data/mint.in view
@@ -0,0 +1,12 @@+NAME="Linux Mint"+VERSION="18.2 (Sonya)"+ID=linuxmint+ID_LIKE=ubuntu+PRETTY_NAME="Linux Mint 18.2"+VERSION_ID="18.2"+HOME_URL="http://www.linuxmint.com/"+SUPPORT_URL="http://forums.linuxmint.com/"+BUG_REPORT_URL="http://bugs.launchpad.net/linuxmint/"+VERSION_CODENAME=sonya+UBUNTU_CODENAME=xenial+
+ tests/Golden/data/red-hat.golden view
@@ -0,0 +1,41 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Red Hat Enterprise Linux Server"+        , version = Just "7.6 (Maipo)"+        , id = "rhel"+        , id_like = Just "fedora"+        , version_codename = Nothing+        , version_id = Just "7.6"+        , pretty_name = "Red Hat Enterprise Linux"+        , ansi_color = Just "0;31"+        , cpe_name = Just "cpe:/o:redhat:enterprise_linux:7.6:GA:server"+        , home_url = Just "https://www.redhat.com/"+        , documentation_url = Nothing+        , support_url = Nothing+        , bug_report_url = Just "https://bugzilla.redhat.com/"+        , privacy_policy_url = Nothing+        , build_id = Nothing+        , variant = Just "Server"+        , variant_id = Just "server"+        , logo = Nothing+        }+    , unknown_fields =+        [+            ( "REDHAT_SUPPORT_PRODUCT_VERSION"+            , "7.6"+            )+        ,+            ( "REDHAT_BUGZILLA_PRODUCT"+            , "Red Hat Enterprise Linux 7"+            )+        ,+            ( "REDHAT_SUPPORT_PRODUCT"+            , "Red Hat Enterprise Linux"+            )+        ,+            ( "REDHAT_BUGZILLA_PRODUCT_VERSION"+            , "7.6"+            )+        ]+    , parse_errors = []+    }
+ tests/Golden/data/red-hat.in view
@@ -0,0 +1,17 @@+NAME="Red Hat Enterprise Linux Server"+VERSION="7.6 (Maipo)"+ID="rhel"+ID_LIKE="fedora"+VARIANT="Server"+VARIANT_ID="server"+VERSION_ID="7.6"+PRETTY_NAME="Red Hat Enterprise Linux"+ANSI_COLOR="0;31"+CPE_NAME="cpe:/o:redhat:enterprise_linux:7.6:GA:server"+HOME_URL="https://www.redhat.com/"+BUG_REPORT_URL="https://bugzilla.redhat.com/"+ +REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7"+REDHAT_BUGZILLA_PRODUCT_VERSION=7.6+REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"+REDHAT_SUPPORT_PRODUCT_VERSION="7.6"
+ tests/Golden/data/red-hat_parseErrors.golden view
@@ -0,0 +1,49 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Red Hat Enterprise Linux Server"+        , version = Just "7.6 (Maipo)"+        , id = "rhel"+        , id_like = Just "fedora"+        , version_codename = Nothing+        , version_id = Just "7.6"+        , pretty_name = "Red Hat Enterprise Linux"+        , ansi_color = Just "0;31"+        , cpe_name = Just "cpe:/o:redhat:enterprise_linux:7.6:GA:server"+        , home_url = Just "https://www.redhat.com/"+        , documentation_url = Nothing+        , support_url = Nothing+        , bug_report_url = Just "https://bugzilla.redhat.com/"+        , privacy_policy_url = Nothing+        , build_id = Nothing+        , variant = Just "Server"+        , variant_id = Just "server"+        , logo = Nothing+        }+    , unknown_fields =+        [+            ( "REDHAT_SUPPORT_PRODUCT_VERSION"+            , "7.6"+            )+        ,+            ( "REDHAT_BUGZILLA_PRODUCT"+            , "Red Hat Enterprise Linux 7"+            )+        ,+            ( "REDHAT_SUPPORT_PRODUCT"+            , "Red Hat Enterprise Linux"+            )+        ,+            ( "REDHAT_BUGZILLA_PRODUCT_VERSION"+            , "7.6"+            )+        ]+    , parse_errors =+        [ TrivialError 520+            ( Just+                ( Tokens ( '\'' :| "" ) )+            )+            ( fromList+                [ Label ( 'e' :| "nd of line" ) ]+            )+        ]+    }
+ tests/Golden/data/red-hat_parseErrors.in view
@@ -0,0 +1,18 @@+NAME="Red Hat Enterprise Linux Server"+VERSION="7.6 (Maipo)"+ID="rhel"+ID_LIKE="fedora"+VARIANT="Server"+VARIANT_ID="server"+VERSION_ID="7.6"+PRETTY_NAME="Red Hat Enterprise Linux"+ANSI_COLOR="0;31"+CPE_NAME="cpe:/o:redhat:enterprise_linux:7.6:GA:server"+HOME_URL="https://www.redhat.com/"+BUG_REPORT_URL="https://bugzilla.redhat.com/"+ +REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7"+REDHAT_BUGZILLA_PRODUCT_VERSION=7.6+REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"+REDHAT_SUPPORT_PRODUCT_VERSION="7.6"+foo=1.'
+ tests/Golden/data/ubuntu.golden view
@@ -0,0 +1,29 @@+OsReleaseResult+    { osRelease = OsRelease+        { name = "Ubuntu"+        , version = Just "20.04 LTS (Focal Fossa)"+        , id = "ubuntu"+        , id_like = Just "debian"+        , version_codename = Just "focal"+        , version_id = Just "20.04"+        , pretty_name = "Ubuntu 20.04 LTS"+        , ansi_color = Nothing+        , cpe_name = Nothing+        , home_url = Just "https://www.ubuntu.com/"+        , documentation_url = Nothing+        , support_url = Just "https://help.ubuntu.com/"+        , bug_report_url = Just "https://bugs.launchpad.net/ubuntu/"+        , privacy_policy_url = Just "https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"+        , build_id = Nothing+        , variant = Nothing+        , variant_id = Nothing+        , logo = Nothing+        }+    , unknown_fields =+        [+            ( "UBUNTU_CODENAME"+            , "focal"+            )+        ]+    , parse_errors = []+    }
+ tests/Golden/data/ubuntu.in view
@@ -0,0 +1,13 @@+NAME="Ubuntu"+VERSION="20.04 LTS (Focal Fossa)"+ID=ubuntu+ID_LIKE=debian+PRETTY_NAME="Ubuntu 20.04 LTS"+VERSION_ID="20.04"+HOME_URL="https://www.ubuntu.com/"+SUPPORT_URL="https://help.ubuntu.com/"+BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"+PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"+VERSION_CODENAME=focal+UBUNTU_CODENAME=focal+
− tests/HLint.hs
@@ -1,17 +0,0 @@-module Main (main) where--import           Language.Haskell.HLint (hlint)-import           System.Exit            (exitFailure, exitSuccess)--arguments :: [String]-arguments =-    [-      "library"-    , "--cpp-file=dist/build/autogen/cabal_macros.h"-    , "tests"-    ]--main :: IO ()-main = do-    hints <- hlint arguments-    if null hints then exitSuccess else exitFailure
− tests/Haddock.hs
@@ -1,30 +0,0 @@-module Main (main) where--import           Data.List      (genericLength)-import           Data.Maybe     (catMaybes)-import           System.Exit    (exitFailure, exitSuccess)-import           System.Process (readProcess)-import           Text.Regex     (matchRegex, mkRegex)--arguments :: [String]-arguments =-    [ "haddock"-    ]--average :: (Fractional a, Real b) => [b] -> a-average xs = realToFrac (sum xs) / genericLength xs--expected :: Fractional a => a-expected = 0--main :: IO ()-main = do-    output <- readProcess "cabal" arguments ""-    if average (match output) >= expected-        then exitSuccess-        else putStr output >> exitFailure--match :: String -> [Int]-match = fmap read . concat . catMaybes . fmap (matchRegex pattern) . lines-  where-    pattern = mkRegex "^ *([0-9]*)% "
+ tests/Main.hs view
@@ -0,0 +1,16 @@+import Golden.Real+import Specs.Megaparsec++import Test.Tasty+import Test.Tasty.Hspec+++main :: IO ()+main = do+  ms <- testSpec "megaparsec spec" megaparsecSpec+  gs <- goldenTests+  defaultMain (tests [ms, gs])++tests :: [TestTree] -> TestTree+tests ts = testGroup "Tests" (ts++[])+
+ tests/Specs/Megaparsec.hs view
@@ -0,0 +1,88 @@+module Specs.Megaparsec where++import           System.OsRelease++import           Data.Either+import           Data.Void+import           Test.Hspec+import           Test.Hspec.Megaparsec++import qualified Text.Megaparsec               as MP+++megaparsecSpec :: Spec+megaparsecSpec = do+  describe "parseAssignment" $ do+    it "parses simple value" $ shouldParse' "foo=bar" ("foo", "bar")+    it "parses single quoted value" $ shouldParse' "foo='bar'" ("foo", "bar")+    it "parses double quoted value" $ shouldParse' "foo=\"bar\"" ("foo", "bar")++    it "parses _ var" $ shouldParse' "f_x=''" ("f_x", "")++    -- this is not valid per spec, but many files do this+    it "parses ._- in unquoted assignment"+      $ shouldParse' "VERSION_ID=bar-1.9_rc2" ("VERSION_ID", "bar-1.9_rc2")++    it "parses quoted space" $ shouldParse' "f='a b'" ("f", "a b")+    it "parses quoted -" $ shouldParse' "f='a-b'" ("f", "a-b")++    it "parses special \\" $ shouldParse' "foo='ba\\\\r'" ("foo", "ba\\r")+    it "parses special `" $ shouldParse' "foo='ba\\`r'" ("foo", "ba`r")+    it "parses special '" $ shouldParse' "foo='ba\\'r'" ("foo", "ba'r")+    it "parses special $" $ shouldParse' "foo='ba\\$r'" ("foo", "ba$r")+    it "parses special \"" $ shouldParse' "foo=\"ba\\\"r\"" ("foo", "ba\"r")++    it "parses empty val noquotes" $ shouldParse' "foo=" ("foo", "")+    it "parses empty val quote \"" $ shouldParse' "foo=\"\"" ("foo", "")+    it "parses empty val quote '" $ shouldParse' "foo=''" ("foo", "")++    it "breaks on comments" $ shouldFail' "# foo=\"bar'"+    it "breaks on misquoting 1" $ shouldFail' "foo=\"bar'"+    it "breaks on misquoting 2" $ shouldFail' "foo='bar\""+    it "breaks on unquoted $" $ shouldFail' "foo='ba$r'"+    it "breaks on unquoted `" $ shouldFail' "foo='ba`r'"+    it "breaks on unquoted \"" $ shouldFail' "foo=\"ba\"r\""+    it "breaks on unquoted '" $ shouldFail' "foo='ba'r'"+    it "breaks on unquoted \\" $ shouldFail' "foo='ba\\r'"+    it "breaks on unquoted val with space" $ shouldFail' "foo=ba r"+    it "breaks on unquoted val with ;" $ shouldFail' "foo=ba;r"+    it "breaks on unquoted val with \\" $ shouldFail' "foo=ba\\r"+    it "breaks on trailing NL" $ shouldFail' "foo=bar\n"+++  describe "parseAssignments" $ do+    it "parses simple values" $ shouldParseMany "foo=bar" [("foo", "bar")]++    it "parses multiple values"+      $ shouldParseMany "foo=bar\nbar=baz" [("foo", "bar"), ("bar", "baz")]+    it "parses multiple values with comments" $ shouldParseMany+      "foo=bar\n# comment\nbar=baz"+      [("foo", "bar"), ("bar", "baz")]++    it "parses gracefully" $ shouldParseMany "foo=bar\nbar=baz\"" [("foo", "bar")]++    it "parses empty files" $ shouldParseMany "" []+    it "parses empty files with newlines" $ shouldParseMany "\n\n" []+    it "parses empty files with newlines and comments"+      $ shouldParseMany "\n\n#\n" []++    it "parses comments with leading spaces" $ shouldParseMany "  #" []++parse :: String -> Either (MP.ParseErrorBundle String Void) (String, String)+parse = MP.parse (parseAssignment <* MP.eof) ""++shouldParse' :: String -> (String, String) -> Expectation+shouldParse' s s' = parse s `shouldParse` s'++shouldFail' :: String -> Expectation+shouldFail' s = parse `shouldFailOn` s++parseMany :: String+          -> Either (MP.ParseErrorBundle String Void) [(String, String)]+parseMany = fmap rights . MP.parse parseAssignments ""++shouldParseMany :: String -> [(String, String)] -> Expectation+shouldParseMany s s' = parseMany s `shouldParse` s'++shouldFailMany :: String -> Expectation+shouldFailMany s = parseMany `shouldFailOn` s
− tests/unit/OsReleaseSpec.hs
@@ -1,93 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module OsReleaseSpec (spec) where--import           Test.Hspec-import           System.OsRelease-import           Data.Map.Lazy-import           Data.Monoid-import           OsReleaseSpec.Reals-import           System.IO.Temp-import           System.IO--spec :: Spec-spec = do-    describe "parseOs" $ do-        it "parses simple value"                   $ fooBar "foo=bar"-        it "parses simple value with trailing NL"  $ fooBar "foo=bar\n"-        it "parses single quoted value"            $ fooBar "foo='bar'"-        it "parses double quoted value"            $ fooBar "foo=\"bar\""--        it "parses _ var" $ parseCase "f_x=''" [("f_x", "")]--        it "parses quoted space" $ parseCase "f='a b'" [("f", "a b")]-        it "parses quoted -"     $ parseCase "f='a-b'" [("f", "a-b")]--        it "parses special \\" $ specialFooBar "foo='ba\\\\r'" "\\"-        it "parses special `"  $ specialFooBar "foo='ba\\`r'" "`"-        it "parses special '"  $ specialFooBar "foo='ba\\'r'" "'"-        it "parses special $"  $ specialFooBar "foo='ba\\$r'" "$"-        it "parses special \"" $ specialFooBar "foo=\"ba\\\"r\"" "\""--        it "parses multiple values" $ parseCase-            "foo=bar\nqux=quux"-            [("foo", "bar"), ("qux", "quux")]--        it "parses empty val noquotes" $ parseCase "foo=" [("foo", "")]-        it "parses empty val quote \"" $ parseCase "foo=\"\"" [("foo", "")]-        it "parses empty val quote '"  $ parseCase "foo=''" [("foo", "")]--        it "breaks on misquoting 1" $ errCase "foo=\"bar'"-        it "breaks on misquoting 2" $ errCase "foo='bar\""-        it "breaks on unquoted $"   $ errCase "foo='ba$r'"-        it "breaks on unquoted `"   $ errCase "foo='ba`r'"-        it "breaks on unquoted \""  $ errCase "foo=\"ba\"r\""-        it "breaks on unquoted '"   $ errCase "foo='ba'r'"-        it "breaks on unquoted \\"  $ errCase "foo='ba\\r'"-        it "breaks on unquoted val with space"  $ errCase "foo=ba r"-        it "breaks on unquoted val with ;"      $ errCase "foo=ba;r"-        it "breaks on unquoted val with \\"     $ errCase "foo=ba\\r"--        it "parses Gentoo os-release"           $ realCase Gentoo-        it "parses OpenSUSE Factory os-release" $ realCase OpenSUSEFactory-        it "parses Arch os-release"             $ realCase Arch--    describe "readOs'" $ do-        it "reads 1st" $ do-            withSystemTempFile "1st.txt" $ \f1 hf1 -> do-                withSystemTempFile "2nd.txt" $ \f2 hf2 -> do-                    hPutStr hf1 "1"-                    hPutStr hf2 "2"-                    mapM_ hClose [hf1, hf2]--                    xs <- readOs' [f1, f2]-                    case xs of-                        Right x -> x `shouldBe` "1"-                        Left e -> expectationFailure $ show e--        it "reads 2nd" $ do-            withSystemTempFile "a-file.txt" $ \f h -> do-                hPutStr h "1"-                hClose h--                xs <- readOs' ["/oh-well-let's-hope-no-one-makes-this-file", f]-                case xs of-                    Right x -> x `shouldBe` "1"-                    Left e -> expectationFailure $ show e--    where-        fooBar x = parseCase x [("foo", "bar")]--        specialFooBar :: String -> String -> Expectation-        specialFooBar x y = parseCase x [("foo", OsReleaseValue $ "ba" <> y <> "r")]--        realCase x = parseCase (input x) (output x)--        parseCase x y =-            case parseOs x of-                Left  e -> expectationFailure $ show e-                Right z -> z `shouldBe` (fromList y)--        errCase x =-            case parseOs x of-                Left  _ -> ("1"::String) `shouldBe` "1"-                Right z -> expectationFailure (show z)
− tests/unit/Spec.hs
@@ -1,1 +0,0 @@-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}