diff --git a/geode.cabal b/geode.cabal index 721701067ad4482f6c963ece6ab35e260c118c4c..f88f1e36f1b2c088ad78e2271adf57a784cb5c72 100644 --- a/geode.cabal +++ b/geode.cabal @@ -22,11 +22,13 @@ extra-source-files: CHANGELOG.md library exposed-modules: GEODE.Metadata - , Options.GEODE + , GEODE.Options -- Modules included in this library but not exported. - other-modules: GEODE.Metadata.Article + other-modules: GEODE.Metadata.Entry + , GEODE.Metadata.PrimaryKey , GEODE.Metadata.Projector + , GEODE.Metadata.TSV , GEODE.Metadata.Types -- LANGUAGE extensions used by modules in this package. @@ -35,8 +37,24 @@ library , bytestring >= 0.11.3 && <0.12 , containers >= 0.6.5.1 && <0.7 , cassava >= 0.5.3 && <0.6 + , filepath >= 1.4.2.2 && <1.5 , optparse-applicative >= 0.13.2 && < 0.18 , text >= 1.2.5 && <1.3 + , unordered-containers >= 0.2.19.1 && <0.3 , vector >= 0.12.3.1 && <0.13 hs-source-dirs: lib default-language: Haskell2010 + +test-suite ghc-geode-test + default-language: Haskell2010 + type: exitcode-stdio-1.0 + hs-source-dirs: test + main-is: Main.hs + other-modules: GEODE.Metadata.TestPrimaryKey + , Test.HUnit.Extra + build-depends: base + , bytestring + , cassava + , geode + , HUnit >= 1.6.2.0 && <1.7 + , unordered-containers diff --git a/guix.scm b/guix.scm index 1986f26f6f2179e0a7ba4ec60f2ef947c7f02548..64f4d5791bacfbe2a106433cd14cb3959f80beff 100644 --- a/guix.scm +++ b/guix.scm @@ -1,5 +1,6 @@ (use-modules ((gnu packages haskell-xyz) #:select (ghc-cassava ghc-optparse-applicative)) + ((gnu packages haskell-check) #:select (ghc-hunit)) ((guix build-system haskell) #:select (haskell-build-system)) ((guix git-download) #:select (git-predicate)) ((guix gexp) #:select (local-file)) @@ -16,7 +17,7 @@ #:recursive? #t #:select? (git-predicate %source-dir))) (build-system haskell-build-system) - (inputs (list ghc-cassava ghc-optparse-applicative)) + (inputs (list ghc-cassava ghc-optparse-applicative ghc-hunit)) (home-page "https://gitlab.liris.cnrs.fr/geode/ghc-geode") (synopsis "Data structures and tooling used in project GEODE") (description diff --git a/lib/GEODE/Metadata.hs b/lib/GEODE/Metadata.hs index 04b64178a5c4a1b18f7f266be6f03f096f586036..9eaeab858926f8ae9c6f14fc2798a9e5256a209a 100644 --- a/lib/GEODE/Metadata.hs +++ b/lib/GEODE/Metadata.hs @@ -1,6 +1,6 @@ {-# LANGUAGE NamedFieldPuns, OverloadedStrings #-} module GEODE.Metadata - ( module ID + ( module PrimaryKey , module Entry , module TSV , module Projector @@ -14,7 +14,7 @@ import Data.Foldable as Foldable (toList) import Data.List (sortOn) import Data.Map.Strict as Map (Map, alter, empty, insert, toList) import Data.Text as Text (Text, intercalate, unpack) -import GEODE.Metadata.ID as ID +import GEODE.Metadata.PrimaryKey as PrimaryKey import GEODE.Metadata.Entry as Entry import GEODE.Metadata.Projector as Projector import GEODE.Metadata.TSV as TSV diff --git a/lib/GEODE/Metadata/Entry.hs b/lib/GEODE/Metadata/Entry.hs index 6e19a214d24ac12771d82a4200be5895be8e31c1..d34326eaaa433d931f9eab2fe568c8cbcb053eed 100644 --- a/lib/GEODE/Metadata/Entry.hs +++ b/lib/GEODE/Metadata/Entry.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DeriveGeneric #-} module GEODE.Metadata.Entry ( Entry(..) , headerSection ) where @@ -9,11 +10,11 @@ import GHC.Generics (Generic) data Entry = Entry { headWord :: Text - , rank :: Int + , name :: Text , page :: Int } deriving (Generic, Show) instance FromNamedRecord Entry instance ToNamedRecord Entry instance DefaultHeader Entry where - headerSection = Default [ "headWord", "rank", "page" ] + headerSection = Default [ "headWord", "name", "page" ] diff --git a/lib/GEODE/Metadata/ID.hs b/lib/GEODE/Metadata/ID.hs deleted file mode 100644 index 2d9f1f82ce5f440cee64f687df460c871c9959ea..0000000000000000000000000000000000000000 --- a/lib/GEODE/Metadata/ID.hs +++ /dev/null @@ -1,64 +0,0 @@ -module GEODE.Metadata.ID - ( HasID(..) - , ID - , book - , tome - , name - , headerSection - , relativePath - , uid ) where - -import Data.Csv (FromNamedRecord(..), ToNamedRecord(..)) -import Data.Text (Text, unpack) -import GEODE.Metadata.Types (Book) ---import GEODE.Metadata.Projector (InFile(..), Unique(..)) -import GEODE.Metadata.TSV (Default(..), DefaultHeader(..)) -import GHC.Generics (Generic) -import System.FilePath ((</>), (<.>)) -import Text.Printf (printf) - -data ID = ID - { book_ :: Book - , tome_ :: Int - , name_ :: Text } deriving (Eq, Ord, Generic, Show) - -class HasID t where - iD :: t -> ID - -instance HasID ID where - iD = id - -instance HasID (ID, a) where - iD = fst - -book :: HasID a => a -> Book -book = book_ . iD - -tome :: HasID a => a -> Int -tome = tome_ . iD - -name :: HasID a => a -> Text -name = name_ . iD - -instance FromNamedRecord ID -instance ToNamedRecord ID - -uid :: HasID a => a -> String -uid a = printf "%s_%d_%s" (show $ book a) (tome a) (unpack $ name a) - -relativePath :: HasID a => a -> String -> FilePath -relativePath a extension = - (show $ book a) </> (show $ tome a) </> (unpack $ name a) <.> extension - -{- -instance Unique ID where - uid (ID {book, tome, name}) = - printf "%s_%d_%s" (show book) tome (unpack name) - -instance InFile ID where - relativePath (ID {book, tome, name}) extension = - show book </> show tome </> unpack name <.> extension --} - -instance DefaultHeader ID where - headerSection = Default [ "book", "tome", "name" ] diff --git a/lib/GEODE/Metadata/PrimaryKey.hs b/lib/GEODE/Metadata/PrimaryKey.hs new file mode 100644 index 0000000000000000000000000000000000000000..dde111492f623886bc3db8fa867ce8170a59604d --- /dev/null +++ b/lib/GEODE/Metadata/PrimaryKey.hs @@ -0,0 +1,46 @@ +{-# LANGUAGE DeriveGeneric, FlexibleInstances, NamedFieldPuns #-} +module GEODE.Metadata.PrimaryKey + ( HasPK(..) + , PrimaryKey(..) + , headerSection + , relativePath + , uid ) where + +import Data.Csv (FromNamedRecord(..), ToNamedRecord(..)) +import Data.Text (Text, unpack) +import GEODE.Metadata.Types (Book) +import GEODE.Metadata.TSV (Default(..), DefaultHeader(..)) +import GHC.Generics (Generic) +import System.FilePath ((</>), (<.>)) +import Text.Printf (printf) + +data PrimaryKey = PrimaryKey + { book :: Book + , tome :: Int + , rank :: Int } deriving (Eq, Ord, Generic, Show) + +class HasPK t where + pKey :: t -> PrimaryKey + +instance HasPK PrimaryKey where + pKey = id + +instance HasPK (PrimaryKey, a) where + pKey = fst + +instance FromNamedRecord PrimaryKey +instance ToNamedRecord PrimaryKey + +uid :: HasPK a => a -> String +uid a = printf "%s_%d_%d" (show $ book) tome rank + where + PrimaryKey {book, tome, rank} = pKey a + +relativePath :: HasPK a => a -> String -> FilePath +relativePath a extension = + (show book) </> (show tome) </> (show rank) <.> extension + where + PrimaryKey {book, tome, rank} = pKey a + +instance DefaultHeader PrimaryKey where + headerSection = Default [ "book", "tome", "rank" ] diff --git a/lib/GEODE/Metadata/TSV.hs b/lib/GEODE/Metadata/TSV.hs index cd06047c5cc9c44168fccac6cfd79fd7b030d3c2..e8e84d2c7fd59407ca396b6e0a225483c7afa089 100644 --- a/lib/GEODE/Metadata/TSV.hs +++ b/lib/GEODE/Metadata/TSV.hs @@ -1,7 +1,9 @@ -{-# LANGUAGE ScopedTypeVariables, UndecidableInstances #-} +{-# LANGUAGE DeriveGeneric, FlexibleInstances, ScopedTypeVariables, UndecidableInstances #-} module GEODE.Metadata.TSV ( Default(..) , DefaultHeader(..) + , Glue(..) + , Sections(..) , readNamedTsv , readTsv , toTsv @@ -11,11 +13,11 @@ module GEODE.Metadata.TSV import Data.ByteString.Char8 as StrictByteString (pack) import Data.ByteString.Lazy as ByteString (putStr, readFile, writeFile) import Data.Csv - ( DecodeOptions(..), EncodeOptions(..), FromNamedRecord, FromRecord + ( DecodeOptions(..), EncodeOptions(..), FromNamedRecord(..), FromRecord , HasHeader(..), ToNamedRecord(..), ToRecord(..), decodeByNameWith, decodeWith , defaultEncodeOptions, encodeByNameWith, encodeWith, header ) import Data.Foldable (toList) -import Data.HashMap.Strict ((!)) +import Data.HashMap.Strict ((!), union) import Data.Vector (Vector, fromList) readNamedTsv :: FromNamedRecord a => FilePath -> IO (Either String (Vector a)) @@ -34,12 +36,30 @@ newtype Default a = Default { defaultHeader :: [String] } class DefaultHeader a where headerSection :: Default a -instance (DefaultHeader a, DefaultHeader b) => DefaultHeader (a, b) where +data Glue a b = Glue a b +newtype Sections a = Sections a + +instance (DefaultHeader a, DefaultHeader b) => DefaultHeader (Glue a b) where headerSection = Default (a ++ b) where Default a = (headerSection :: Default a) Default b = (headerSection :: Default b) +instance (DefaultHeader a, DefaultHeader b) => DefaultHeader (Sections (a, b)) where + headerSection = Default (a ++ b) + where + Default a = (headerSection :: Default a) + Default b = (headerSection :: Default b) + +instance (ToNamedRecord a, ToNamedRecord b) => ToNamedRecord (Glue a b) where + toNamedRecord (Glue a b) = union (toNamedRecord a) (toNamedRecord b) + +instance (ToNamedRecord a, ToNamedRecord b) => ToNamedRecord (Sections (a, b)) where + toNamedRecord (Sections (a, b)) = union (toNamedRecord a) (toNamedRecord b) + +instance (FromNamedRecord a, FromNamedRecord b) => FromNamedRecord (Glue a b) where + parseNamedRecord nr = Glue <$> parseNamedRecord nr <*> parseNamedRecord nr + instance (DefaultHeader a, ToNamedRecord a) => ToRecord a where toRecord = fromList . prepare . toNamedRecord where diff --git a/test/GEODE/Metadata/TestPrimaryKey.hs b/test/GEODE/Metadata/TestPrimaryKey.hs new file mode 100644 index 0000000000000000000000000000000000000000..aa32db01300e10a47253c708395f8911bfe2b0b3 --- /dev/null +++ b/test/GEODE/Metadata/TestPrimaryKey.hs @@ -0,0 +1,26 @@ +{-# LANGUAGE OverloadedStrings #-} +module GEODE.Metadata.TestPrimaryKey (testPrimaryKey) where + +import Data.ByteString.Char8 as BS (pack) +import Data.Csv (ToNamedRecord(..)) +import Data.HashMap.Strict ((!?)) +import GEODE.Metadata (Book(..), Default(..), DefaultHeader(..), PrimaryKey(..)) +import Test.HUnit (Test(..), (~?=)) +import Test.HUnit.Extra (isJust) + +testPrimaryKey :: Test +testPrimaryKey = TestLabel "Testing the PrimaryKey data type" $ + TestList [ testToNamedRecord ] + +testToNamedRecord :: Test +testToNamedRecord = TestLabel "Testing ToNamedRecord instance" $ + TestList [ has3Keys, validDefaultHeader ] + where + has3Keys = length aNamedRecord ~?= 3 + validDefaultHeader = + TestList ((isJust . (aNamedRecord !?) . BS.pack) <$> defaultHeader) + aNamedRecord = toNamedRecord aPrimaryKey + Default defaultHeader = (headerSection :: Default PrimaryKey) + +aPrimaryKey :: PrimaryKey +aPrimaryKey = PrimaryKey LGE 1 1212 -- ALCALA DE HÉNARÈS diff --git a/test/Main.hs b/test/Main.hs new file mode 100644 index 0000000000000000000000000000000000000000..dcfad4e5c7b1f3be464ecc7fd768a4c9bd05ecd5 --- /dev/null +++ b/test/Main.hs @@ -0,0 +1,7 @@ +module Main (main) where + +import Test.HUnit (runTestTT, showCounts) +import GEODE.Metadata.TestPrimaryKey (testPrimaryKey) + +main :: IO () +main = runTestTT testPrimaryKey >>= putStr . showCounts diff --git a/test/Test/HUnit/Extra.hs b/test/Test/HUnit/Extra.hs new file mode 100644 index 0000000000000000000000000000000000000000..046c3faf0b34ddb2b4d5ebd3d0b4d2ac1864b35a --- /dev/null +++ b/test/Test/HUnit/Extra.hs @@ -0,0 +1,12 @@ +module Test.HUnit.Extra + ( isJust + , isNothing ) where + +import Test.HUnit (Test(..), assertBool) +import qualified Data.Maybe as Maybe (isJust, isNothing) + +isJust :: Maybe a -> Test +isJust = TestCase . assertBool "Expected a Just" . Maybe.isJust + +isNothing :: Maybe a -> Test +isNothing = TestCase . assertBool "Expected a Nothing" . Maybe.isNothing