packages feed

epub-metadata (empty) → 1.0.2

raw patch · 16 files changed

+1033/−0 lines, 16 filesdep +HSHdep +basedep +hxtsetup-changed

Dependencies added: HSH, base, hxt, mtl

Files

+ LICENSE view
@@ -0,0 +1,31 @@+Copyright Dino Morelli 2010++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Dino Morelli nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ README view
@@ -0,0 +1,107 @@+-----+Building:++   Easy with cabal-install, of course:++      $ cabal install epub-metadata++   Or the conventional way:++      $ runhaskell Setup.hs configure+      $ runhaskell Setup.hs build+      $ runhaskell Setup.hs test+      $ runhaskell Setup.hs haddock+      $ runhaskell Setup.hs install+++-----+Why was this done?++   The motivation for this project grew out of my desire to take charge+   of missing or incorrect ePub metadata in books I have purchased. I+   started out using the Calibre open source tools for examining this+   info. Limitations and incomplete implementation of those tools led+   me here to build a more complete implementation in the programming+   language that I love beyond all others.+++-----+Why didn't I just use existing solutions?++   - Calibre ebook-meta utility++      I experienced various problems using this software, such as:++      Incomplete and in some cases incorrect handling of tags that can+      exist more than once, particularly when they are differentiated+      using attributes according to the spec.++      Unable to display many fields in the OPF Package Document metadata+      specification. Unable to manipulate data that is represented as+      attributes of tags in the OPF spec.++      Astonishingly slow performance. The command-line tool in this+      new Haskell project is more than 45 times faster at parsing+      and displaying ePub metadata. I'm going to blame Python here for+      Calibre's performance. This has had a big impact on projects where+      I've been processing hundreds of ePubs in batch operations.++      To be fair, an effort is being made in Calibre to work with both+      ePub and Sony LRF book documents. That is going to naturally require+      a lowest-common-denominator approach. My focus here was to work+      with ePub only, and thoroughly support the OPF specification.+++   - epub on Hackage, EPUB E-Book construction support library++      The focus of this project seems to be with building new documents,+      not parsing existing files. And there is a specific attempt to+      do more than the metadata, to gather up the content and other+      metafiles that make up an ePub for creation.++      Examining Codec.Ebook.OPF.Types, most of the metadata fields+      from the OPF Package Document spec are missing or aren't modeled+      thoroughly. I felt to contribute to this project, I would have+      had to significantly rip up the types and redesign them.++      At this time I felt it was a better solution for me to start fresh+      with modelling these types and code to manipulate them. That said,+      I would be very interested in combining the epub and epub-metadata+      projects at some point in some way that makes sense.+++   - zip-archive on Hackage++      I started out using zip-archive for this work but soon found that+      it has problems with some poorly-made ePub files. Nearly 300 or 40%+      of the books I have are unreadable at this time with zip-archive.++      As ugly as it sounds, I found the most reliable solution right+      now is to use the unzip shell utility to extract the relevant XML+      documents from ePub books. This is why the dependency on HSH. I'm+      not thrilled with this situation and would like to find time to+      dig into the zip file spec and submit patches to zip-archive.++      But also note that epub-meta is obscenely fast even with invoking+      a shell for unzipping. Haskell FTW!+++-----+A word about the version numbering scheme:++   4-part: major.minor.status.build+   3-part: major.status.build++   status:+      0 alpha+      1 beta+      2 release candidate+      3 release++   examples:+      1.3.0.2         v1.3 alpha build 2+      1.2.1.0         v1.2 beta build 0+      4.2.24          v4 release candidate build 24+      2.10.3.5        v2.10 release build 5 (say they were bug fixes)+      1.5.2.20090818  Can even use a date for build+                      v1.5 release candidate 2009-08-18 build
+ Setup.hs view
@@ -0,0 +1,39 @@+#! /usr/bin/env runhaskell++-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++import Control.Monad ( unless )+import Distribution.Simple+import System.Cmd ( system )+import System.Directory ( createDirectoryIfMissing )+import System.FilePath+import System.Posix.Files ( createSymbolicLink, fileExist )+++main = defaultMainWithHooks (simpleUserHooks +   { postBuild = customPostBuild+   , runTests = testRunner+   } )++   where+      -- Create symlink to the binary after build for developer +      -- convenience+      customPostBuild _ _ _ _ = do+         let binDir = "bin"+         let binName = "epub-meta"+         let destPath = binDir </> binName++         createDirectoryIfMissing True binDir++         linkExists <- fileExist destPath+         unless linkExists $ do+            createSymbolicLink+               (".." </> "dist" </> "build" </> binName </> binName)+               destPath++      -- Target for running all unit tests+      testRunner _ _ _ _ = do+         system $ "runhaskell -isrc testsuite/runtests.hs"+         return ()
+ TODO view
@@ -0,0 +1,1 @@+- Better errors on parse failure, this will emerge along with more unit testing
+ epub-metadata.cabal view
@@ -0,0 +1,36 @@+name:                epub-metadata+version:             1.0.2+cabal-version:       >= 1.2+build-type:          Simple+license:             BSD3+license-file:        LICENSE+copyright:           2010 Dino Morelli+author:              Dino Morelli+maintainer:          Dino Morelli <dino@ui3.info>+stability:           experimental+homepage:            http://ui3.info/d/proj/epub-metadata.html+synopsis:            Library and utility for parsing and manipulating +                     ePub metadata+description:         Library for parsing and manipulating ePub OPF +                     metadata. An attempt has been made here to very +                     thoroughly implement the metadata portion of the OPF +                     Package Document specification. Also included is a +                     command-line utility to dump ePub metadata to stdout +                     in a human-readable form.+category:            Codec, Text+tested-with:         GHC>=6.12.1++library+   exposed-modules:  Codec.Epub.IO,+                     Codec.Epub.Opf.Metadata,+                     Codec.Epub.Opf.Metadata.Format,+                     Codec.Epub.Opf.Metadata.Parse+   hs-source-dirs:   src+   build-depends:    base >= 3 && < 5, HSH, hxt, mtl+   ghc-options:      -Wall++executable           epub-meta+   main-is:          Codec/Epub/Opf/epub-meta.hs+   hs-source-dirs:   src+   build-depends:    base >= 3 && < 5+   ghc-options:      -Wall
+ src/Codec/Epub/IO.hs view
@@ -0,0 +1,57 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++{-# LANGUAGE FlexibleContexts #-}++{- | Functions for doing some disk IO with ePub documents++   Note that these functions do their work by using the external +   unzip utility.+-}+module Codec.Epub.IO+   ( extractFileFromZip, opfPath )+   where++import Control.Monad.Error+import HSH.Command+import Text.Printf+import Text.XML.HXT.Arrow+++{- | Extract a file from a zipfile.+   This is here because ePub files are really just zip files.+-}+extractFileFromZip :: (MonadIO m, MonadError [Char] m)+   => FilePath    -- ^ path to zip file+   -> FilePath    -- ^ path within zip file to extract+   -> m String    -- ^ contents of expected file+extractFileFromZip zipPath filePath = do+   let dearchiver = "unzip"+   result <- liftIO $ tryEC $ run+      ((printf "%s -p %s %s" dearchiver zipPath filePath) :: String)+   case result of+      Left ps -> throwError $+         printf "[ERROR %s  zip file: %s  path in zip: %s  status: %s]"+            dearchiver zipPath filePath (show ps)+      Right output -> return output+++-- | Get the path within an ePub file to the OPF Package Document+opfPath :: (MonadError String m, MonadIO m)+   => FilePath    -- ^ path to ePub zip file+   -> m String    -- ^ path within ePub to the OPF Package Document+opfPath zipPath = do+   containerContents <- extractFileFromZip zipPath+      "META-INF/container.xml"++   result <- liftIO $ runX (+      readString [(a_validate, v_0)] containerContents+      >>> deep (isElem >>> hasName "rootfile")+      >>> getAttrValue "full-path"+      )++   case result of+      (p : []) -> return p+      _        -> throwError+         "ERROR: rootfile full-path missing from META-INF/container.xml"
+ src/Codec/Epub/Opf/Metadata.hs view
@@ -0,0 +1,92 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++{- | Data types for working with the metadata of ePub documents++   These data types were constructed by studying the IDPF OPF +   specification for ePub documents found here:++   <http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html>+-}+module Codec.Epub.Opf.Metadata+   ( OPFPackage (..)+   , EMTitle (..)+   , EMCreator (..)+   , EMDate (..)+   , EMId (..)+   , EpubMeta (..)+   , emptyEpubMeta+   )+   where+++{- | opf:package tag++   Note that we are not yet storing the data that comes after+   \/package\/metadata in an OPF Package Document. But that may+   be added at a later time.+-}+data OPFPackage = OPFPackage+   { opVersion :: String  -- ^ version attr+   , opUniqueId :: String  -- ^ unique-identifier attr+   , opMeta :: EpubMeta  -- ^ metadata child element contents+   }+   deriving (Eq, Show)+++-- | dc:title tag, xml:lang attr, content+data EMTitle = EMTitle (Maybe String) String+   deriving (Eq, Show)++-- | dc:creator tag, opf:role attr, opf:file-as attr, content+data EMCreator = EMCreator (Maybe String) (Maybe String) String+   deriving (Eq, Show)++-- | dc:date tag, opf:event attr, content+data EMDate = EMDate (Maybe String) String+   deriving (Eq, Show)++-- | dc:identifier tag, id attr, opf:scheme attr, content+data EMId = EMId String (Maybe String) String+   deriving (Eq, Show)++-- | opf:metadata tag+data EpubMeta = EpubMeta+   { emTitles :: [EMTitle]   -- ^ at least one required+   , emCreators :: [EMCreator]+   , emContributors :: [EMCreator]+   , emSubjects :: [String]  -- ^ dc:subject tags+   , emDescription :: Maybe String  -- ^ dc:description tags+   , emPublisher :: Maybe String  -- ^ dc:publisher tag+   , emDates :: [EMDate]+   , emType :: Maybe String  -- ^ dc:type tag+   , emFormat :: Maybe String  -- ^ dc:format tag+   , emIds :: [EMId]          -- ^ at least one required+   , emSource :: Maybe String  -- ^ dc:source tag+   , emLangs :: [String]    -- ^ dc:language tags, at least one required+   , emRelation :: Maybe String  -- ^ dc:relation tag+   , emCoverage :: Maybe String  -- ^ dc:coverage tag+   , emRights :: Maybe String  -- ^ dc:rights tag+   }+   deriving (Eq, Show)++-- | Note: This isn't valid as-is, some required values are empty lists!+emptyEpubMeta :: EpubMeta+emptyEpubMeta = EpubMeta+   { emTitles = []   -- one required+   , emCreators = []+   , emContributors = []+   , emSubjects = []+   , emDescription = Nothing+   , emPublisher = Nothing+   , emDates = []+   , emType = Nothing+   , emFormat = Nothing+   , emIds = []       -- one required+   , emSource = Nothing+   , emLangs = []     -- one required+   , emRelation = Nothing+   , emCoverage = Nothing+   , emRights = Nothing+   }
+ src/Codec/Epub/Opf/Metadata/Format.hs view
@@ -0,0 +1,131 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++-- | Module for pretty-printing ePub metadata info+module Codec.Epub.Opf.Metadata.Format+   ( opfToString+   )+   where++import Text.Printf++import Codec.Epub.Opf.Metadata+++formatSubline :: String -> Maybe String -> String+formatSubline _   Nothing = ""+formatSubline key (Just value) = printf "   %s: %s\n" key value+++packageToString :: (String, String) -> String+packageToString (version, uniqueId) =+   "package\n" +++   (formatSubline "version" (Just version)) +++   (formatSubline "unique-identifier" (Just uniqueId))+++titleToString :: EMTitle -> String+titleToString (EMTitle Nothing title) = printf "title: %s\n" title+titleToString (EMTitle lang title) =+   "title\n" +++   (formatSubline "lang" lang) +++   (formatSubline "title" (Just title))+++creatorToString :: EMCreator -> String+creatorToString (EMCreator Nothing Nothing creator) =+   printf "creator: %s\n" creator+creatorToString (EMCreator role fileAs creator) =+   "creator\n" +++   (formatSubline "role" role) +++   (formatSubline "file-as" fileAs) +++   (formatSubline "creator" (Just creator))+++contributorToString :: EMCreator -> String+contributorToString (EMCreator Nothing Nothing contributor) =+   printf "contributor: %s\n" contributor+contributorToString (EMCreator role fileAs contributor) =+   "contributor\n" +++   (formatSubline "role" role) +++   (formatSubline "file-as" fileAs) +++   (formatSubline "creator" (Just contributor))+++subjectToString :: String -> String+subjectToString = printf "subject: %s\n"+++descriptionToString :: Maybe String -> String+descriptionToString = maybe "" (printf "description: %s\n")+++publisherToString :: Maybe String -> String+publisherToString = maybe "" (printf "publisher: %s\n")+++dateToString :: EMDate -> String+dateToString (EMDate Nothing date) =+   printf "date: %s\n" date+dateToString (EMDate event date) =+   "date\n" +++   (formatSubline "event" event) +++   (formatSubline "date" (Just date))+++typeToString :: Maybe String -> String+typeToString = maybe "" (printf "type: %s\n")+++formatToString :: Maybe String -> String+formatToString = maybe "" (printf "format: %s\n")+++idToString :: EMId -> String+idToString (EMId idVal scheme content) =+   "identifier\n" +++   (formatSubline "id" (Just idVal)) +++   (formatSubline "scheme" scheme) +++   (formatSubline "identifier" (Just content))+++sourceToString :: Maybe String -> String+sourceToString = maybe "" (printf "source: %s\n")+++langToString :: String -> String+langToString = printf "language: %s\n"+++relationToString :: Maybe String -> String+relationToString = maybe "" (printf "relation: %s\n")+++coverageToString :: Maybe String -> String+coverageToString = maybe "" (printf "coverage: %s\n")+++rightsToString :: Maybe String -> String+rightsToString = maybe "" (printf "rights: %s\n")+++-- | Format an ePub metadata into a String+opfToString :: OPFPackage -> String+opfToString (OPFPackage v u em) = concat $+   [packageToString (v, u)] +++   (map titleToString $ emTitles em) +++   (map creatorToString $ emCreators em) +++   (map contributorToString $ emContributors em) +++   (map dateToString $ emDates em) +++   [typeToString . emType $ em] +++   [formatToString . emFormat $ em] +++   (map idToString $ emIds em) +++   [sourceToString . emSource $ em] +++   (map subjectToString $ emSubjects em) +++   [descriptionToString . emDescription $ em] +++   [publisherToString . emPublisher $ em] +++   (map langToString $ emLangs em) +++   [relationToString . emRelation $ em] +++   [coverageToString . emCoverage $ em] +++   [rightsToString . emRights $ em]
+ src/Codec/Epub/Opf/Metadata/Parse.hs view
@@ -0,0 +1,210 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++{-# LANGUAGE Arrows #-}+{-# LANGUAGE FlexibleContexts #-}++-- | Module for extracting the metadata from an ePub file+module Codec.Epub.Opf.Metadata.Parse+   ( parseXmlToOpf+   , parseEpubOpf+   )+   where++import Control.Applicative+import Control.Monad.Error+import Data.Tree.NTree.TypeDefs ( NTree )+import Prelude hiding ( cos )+import Text.XML.HXT.Arrow++import Codec.Epub.IO+import Codec.Epub.Opf.Metadata+++-- HXT helpers++atTag :: (ArrowXml a) => String -> a (NTree XNode) XmlTree+atTag tag = deep (isElem >>> hasName tag)++atQTag :: (ArrowXml a) => QName -> a (NTree XNode) XmlTree+atQTag tag = deep (isElem >>> hasQName tag)++text :: (ArrowXml a) => a (NTree XNode) String+text = getChildren >>> getText++notNullA :: (ArrowList a) => a [b] [b]+notNullA = isA $ not . null+++mbQTagText :: (ArrowXml a) => QName -> a (NTree XNode) (Maybe String)+mbQTagText tag =+   ( atQTag tag >>>+     text >>> notNullA >>> arr Just )+   `orElse`+   (constA Nothing)+++mbGetAttrValue :: (ArrowXml a) =>+   String -> a XmlTree (Maybe String)+mbGetAttrValue n =+   (getAttrValue n >>> notNullA >>> arr Just)+   `orElse` (constA Nothing)++mbGetQAttrValue :: (ArrowXml a) =>+   QName -> a XmlTree (Maybe String)+mbGetQAttrValue qn =+   (getQAttrValue qn >>> notNullA >>> arr Just)+   `orElse` (constA Nothing)+++{- ePub parsing helpers++   Note that these URIs could conceivably change in the future+   Is it ok that they're hardcoded like this?++   Well, ok, the xml namespace URI will probably never change.+-}++dcName, opfName, xmlName :: String -> QName+dcName local = mkQName "dc" local "http://purl.org/dc/elements/1.1/"+opfName local = mkQName "opf" local "http://www.idpf.org/2007/opf"+xmlName local = mkQName "xml" local "http://www.w3.org/XML/1998/namespace"+++getPackage :: (ArrowXml a) => a (NTree XNode) (String, String)+getPackage = atTag "package" >>>+   proc x -> do+      v <- getAttrValue "version" -< x+      u <- getAttrValue "unique-identifier" -< x+      returnA -< (v, u)+++getTitle :: (ArrowXml a) => a (NTree XNode) EMTitle+getTitle = atQTag (dcName "title") >>>+   proc x -> do+      l <- mbGetQAttrValue (xmlName "lang") -< x+      c <- text -< x+      returnA -< EMTitle l c+++{- Since creators and contributors have the same exact XML structure,+   this arrow is used to get either of them+-}+getCreator :: (ArrowXml a) => String -> a (NTree XNode) EMCreator+getCreator tag = atQTag (dcName tag) >>> ( unwrapArrow $ EMCreator+   <$> (WrapArrow $ mbGetQAttrValue (opfName "role"))+   <*> (WrapArrow $ mbGetQAttrValue (opfName "file-as"))+   <*> (WrapArrow $ text)+   )+++getSubject :: (ArrowXml a) => a (NTree XNode) String+getSubject = atQTag (dcName "subject") >>> text+++getDescription :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getDescription = mbQTagText $ dcName "description"+++getPublisher :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getPublisher = mbQTagText $ dcName "publisher"+++getDate :: (ArrowXml a) => a (NTree XNode) EMDate+getDate = atQTag (dcName "date") >>>+   proc x -> do+      e <- mbGetQAttrValue (opfName "event") -< x+      c <- text -< x+      returnA -< EMDate e c+++getType :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getType = mbQTagText $ dcName "type"+++getFormat :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getFormat = mbQTagText $ dcName "format"+++getId :: (ArrowXml a) => a (NTree XNode) EMId+getId = atQTag (dcName "identifier") >>>+   proc x -> do+      mbi <- mbGetAttrValue "id" -< x+      s <- mbGetQAttrValue (opfName "scheme") -< x+      c <- text -< x+      let i = maybe "[WARNING: missing required id attribute]" id mbi+      returnA -< EMId i s c+++getSource :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getSource = mbQTagText $ dcName "source"+++getLang :: (ArrowXml a) => a (NTree XNode) String+getLang = atQTag (dcName "language") >>> text+++getRelation :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getRelation = mbQTagText $ dcName "relation"+++getCoverage :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getCoverage = mbQTagText $ dcName "coverage"+++getRights :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getRights = mbQTagText $ dcName "rights"+++getMeta :: (ArrowXml a) => a (NTree XNode) EpubMeta+getMeta = atTag "metadata" >>> ( unwrapArrow $ EpubMeta+   <$> (WrapArrow $ listA getTitle)+   <*> (WrapArrow $ listA $ getCreator "creator")+   <*> (WrapArrow $ listA $ getCreator "contributor")+   <*> (WrapArrow $ listA getSubject)+   <*> (WrapArrow $ getDescription)+   <*> (WrapArrow $ getPublisher)+   <*> (WrapArrow $ listA getDate)+   <*> (WrapArrow $ getType)+   <*> (WrapArrow $ getFormat)+   <*> (WrapArrow $ listA getId)+   <*> (WrapArrow $ getSource)+   <*> (WrapArrow $ listA getLang)+   <*> (WrapArrow $ getRelation)+   <*> (WrapArrow $ getCoverage)+   <*> (WrapArrow $ getRights)+   )+++getBookData :: (ArrowXml a) => a (NTree XNode) OPFPackage+getBookData = +   proc x -> do+      (v, u) <- getPackage -< x+      m <- getMeta -< x+      returnA -< OPFPackage v u m+++{- | Extract the ePub metadata contained in the OPF Package Document +   contained in the supplied string+-}+parseXmlToOpf :: (MonadIO m) => String -> m [OPFPackage]+parseXmlToOpf opfContents =+   liftIO $ runX (+      readString [(a_validate, v_0)] opfContents+      >>> propagateNamespaces+      >>> getBookData+      )+++-- | Given the path to an ePub file, extract the metadata+parseEpubOpf :: (MonadIO m, MonadError String m) =>+   FilePath -> m OPFPackage+parseEpubOpf zipPath = do+   opfContents <- extractFileFromZip zipPath =<< opfPath zipPath+   result <- parseXmlToOpf opfContents++   case result of+      (em : []) -> return em+      _         -> throwError+         "ERROR: we didn't come up with a single EpubMeta"
+ src/Codec/Epub/Opf/epub-meta.hs view
@@ -0,0 +1,24 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++import Control.Monad.Error+import System.Environment ( getArgs )+import System.Exit++import Codec.Epub.Opf.Metadata.Format+import Codec.Epub.Opf.Metadata.Parse+++main :: IO ()+main = do+   as <- getArgs++   when (length as /= 1) $ do+      putStrLn "usage: epub-meta EPUBFILE"+      exitWith $ ExitFailure 1++   let zipPath = head as+   result <- runErrorT $ parseEpubOpf zipPath++   putStr $ either (++ "\n") opfToString result
+ testsuite/runtests.hs view
@@ -0,0 +1,118 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++import System.FilePath+import Test.HUnit ( Counts, Test (..), assertEqual, runTestTT )+import Test.HUnit.Base ( Assertion )++import Codec.Epub.Opf.Metadata+import Codec.Epub.Opf.Metadata.Parse+++main = runTestTT tests >> return ()+++tests :: Test+tests = TestList+   [ testFull+   , testMinimal+   , testMissingAll+   ]+++{- A fairly comprehensive test containing all possible things+   Not complete at this time because the library can't parse it all yet!+-}+testFull :: Test+testFull = TestCase $ do+   xmlString <- readFile $ "testsuite" </> "testFull.opf"+   actual <- parseXmlToOpf xmlString+   let expected = [ OPFPackage "2.0" "isbn" ( EpubMeta+         { emTitles =+            [ EMTitle Nothing "Title Of This Book"+            , EMTitle (Just "fr") "Titre De Ce Livre"+            ]+         , emCreators =+            [ EMCreator+               (Just "aut")+               (Just "Wiggins, Josephine B.")+               "Josephine B. Wiggins"+            , EMCreator+               (Just "aut")+               Nothing+               "Horatio Cromwell"+            , EMCreator+               Nothing+               Nothing+               "Natalia Jenkins"+            ]+         , emContributors =+            [ EMCreator+               (Just "ill")+               (Just "Knickerbocker, Reginald Q.")+               "Reginald Q. Knickerbocker"+            , EMCreator+               (Just "edt")+               Nothing+               "Beverly Abercrombie"+            ]+         , emSubjects = ["Fiction", "Science Fiction"]+         , emDescription = Just "This document is a stub used for unit testing. It is missing the rest of the tags that normally occur after metadata."+         , emPublisher = Just "Fictional Books Ltd."+         , emDates =+            [ EMDate (Just "published") "2010"+            , EMDate (Just "created") "2010-05-07"+            , EMDate (Just "modified") "2010-05-08T10:20:57"+            , EMDate Nothing "2009-08-03T16:22:20"+            ]+         , emType = Just "test OPF Package Document"+         , emFormat = Just "ePub publication"+         , emIds =+            [ EMId "isbn" (Just "ISBN") "1-82057-821-9"+            , EMId "other" Nothing "1386506873266"]+         , emSource = Just "document source"+         , emLangs = ["en-us"]+         , emRelation = Just "document relation"+         , emCoverage = Just "coverage information"+         , emRights = Just "Copyright: 2010 Dino Morelli, License: BSD3"+         } ) ]+   assertEqual "very full" expected actual+++{- Test the absolute minimum set of fields allowed while remaining +   compliant with the spec+-}+testMinimal :: Test+testMinimal = TestCase $ do+   xmlString <- readFile $ "testsuite" </> "testMinimal.opf"+   actual <- parseXmlToOpf xmlString+   let expected = [ OPFPackage "2.0" "isbn" ( EpubMeta+         { emTitles = [EMTitle Nothing "Title Of This Book"]+         , emCreators = []+         , emContributors = []+         , emSubjects = []+         , emDescription = Nothing+         , emPublisher = Nothing+         , emDates = []+         , emType = Nothing+         , emFormat = Nothing+         , emIds = [EMId "isbn" (Just "ISBN") "1-82057-821-9"]+         , emSource = Nothing+         , emLangs = ["en-us"]+         , emRelation = Nothing+         , emCoverage = Nothing+         , emRights = Nothing+         } ) ]+   assertEqual "minimal" expected actual+++{- Test data missing everything important: package version and +   unique-identifier attributes, title, identifier and language tags+-}+testMissingAll :: Test+testMissingAll = TestCase $ do+   xmlString <- readFile $ "testsuite" </> "testMissingAll.opf"+   actual <- parseXmlToOpf xmlString+   let expected = [ OPFPackage "" "" emptyEpubMeta ]+   assertEqual "missing all" expected actual
+ testsuite/testFull.opf view
@@ -0,0 +1,39 @@+<?xml version="1.0" encoding="UTF-8"?>+<package+   xmlns="http://www.idpf.org/2007/opf"+   version="2.0"+   unique-identifier="isbn">+   <metadata+      xmlns:dc="http://purl.org/dc/elements/1.1/"+      xmlns:opf="http://www.idpf.org/2007/opf"+      xmlns:xml="http://www.w3.org/XML/1998/namespace">+      <dc:title>Title Of This Book</dc:title>+      <dc:title xml:lang="fr">Titre De Ce Livre</dc:title>+      <dc:creator opf:role="aut" opf:file-as="Wiggins, Josephine B.">Josephine B. Wiggins</dc:creator>+      <dc:creator opf:role="aut">Horatio Cromwell</dc:creator>+      <dc:creator>Natalia Jenkins</dc:creator>+      <dc:contributor opf:role="ill" opf:file-as="Knickerbocker, Reginald Q.">Reginald Q. Knickerbocker</dc:contributor>+      <dc:contributor opf:role="edt">Beverly Abercrombie</dc:contributor>+      <dc:description>This document is a stub used for unit testing. It is missing the rest of the tags that normally occur after metadata.</dc:description>+      <dc:identifier id="isbn" opf:scheme="ISBN">1-82057-821-9</dc:identifier>+      <dc:date opf:event="published">2010</dc:date>+      <dc:date opf:event="created">2010-05-07</dc:date>+      <dc:date opf:event="modified">2010-05-08T10:20:57</dc:date>+      <dc:date>2009-08-03T16:22:20</dc:date>+      <dc:language>en-us</dc:language>+      <dc:publisher>Fictional Books Ltd.</dc:publisher>+      <dc:identifier id="other">1386506873266</dc:identifier>+      <dc:subject>Fiction</dc:subject>+      <dc:subject>Science Fiction</dc:subject>+      <dc:type>test OPF Package Document</dc:type>+      <dc:format>ePub publication</dc:format>+      <dc:source>document source</dc:source>+      <dc:relation>document relation</dc:relation>+      <dc:coverage>coverage information</dc:coverage>+      <dc:rights>Copyright: 2010 Dino Morelli, License: BSD3</dc:rights>+      <meta name="series_index" content="1"/>+   </metadata>++   <!-- This document is a stub used for unit testing. It is missing +        the rest of the tags that normally occur after metadata -->+</package>
+ testsuite/testMinimal.opf view
@@ -0,0 +1,17 @@+<?xml version="1.0" encoding="UTF-8"?>+<package+   xmlns="http://www.idpf.org/2007/opf"+   version="2.0"+   unique-identifier="isbn">+   <metadata+      xmlns:dc="http://purl.org/dc/elements/1.1/"+      xmlns:opf="http://www.idpf.org/2007/opf"+      xmlns:xml="http://www.w3.org/XML/1998/namespace">+      <dc:title>Title Of This Book</dc:title>+      <dc:identifier id="isbn" opf:scheme="ISBN">1-82057-821-9</dc:identifier>+      <dc:language>en-us</dc:language>+   </metadata>++   <!-- This document is a stub used for unit testing. It is missing +        the rest of the tags that normally occur after metadata -->+</package>
+ testsuite/testMissingAll.opf view
@@ -0,0 +1,11 @@+<?xml version="1.0" encoding="UTF-8"?>+<package xmlns="http://www.idpf.org/2007/opf">+   <metadata+      xmlns:dc="http://purl.org/dc/elements/1.1/"+      xmlns:opf="http://www.idpf.org/2007/opf"+      xmlns:xml="http://www.w3.org/XML/1998/namespace">+   </metadata>++   <!-- This document is a stub used for unit testing. It is missing +        the rest of the tags that normally occur after metadata -->+</package>
+ util/gentags.sh view
@@ -0,0 +1,3 @@+#! /bin/sh++find src -regex '.*\..?hs' | xargs hasktags -c
+ util/prefs/boring view
@@ -0,0 +1,117 @@+# Boring file regexps:++### compiler and interpreter intermediate files+# haskell (ghc) interfaces+\.hi$+\.hi-boot$+\.o-boot$+# object files+\.o$+\.o\.cmd$+# profiling haskell+\.p_hi$+\.p_o$+# haskell program coverage resp. profiling info+\.tix$+\.prof$+# fortran module files+\.mod$+# linux kernel+\.ko\.cmd$+\.mod\.c$+(^|/)\.tmp_versions($|/)+# *.ko files aren't boring by default because they might+# be Korean translations rather than kernel modules+# \.ko$+# python, emacs, java byte code+\.py[co]$+\.elc$+\.class$+# objects and libraries; lo and la are libtool things+\.(obj|a|exe|so|lo|la)$+# compiled zsh configuration files+\.zwc$+# Common LISP output files for CLISP and CMUCL+\.(fas|fasl|sparcf|x86f)$++### build and packaging systems+# cabal intermediates+\.installed-pkg-config+\.setup-config+# standard cabal build dir, might not be boring for everybody+# ^dist(/|$)+# autotools+(^|/)autom4te\.cache($|/)+(^|/)config\.(log|status)$+# microsoft web expression, visual studio metadata directories+\_vti_cnf$+\_vti_pvt$+# gentoo tools+\.revdep-rebuild.*+# generated dependencies+^\.depend$++### version control systems+# cvs+(^|/)CVS($|/)+\.cvsignore$+# cvs, emacs locks+^\.#+# rcs+(^|/)RCS($|/)+,v$+# subversion+(^|/)\.svn($|/)+# mercurial+(^|/)\.hg($|/)+# git+(^|/)\.git($|/)+# bzr+\.bzr$+# sccs+(^|/)SCCS($|/)+# darcs+(^|/)_darcs($|/)+(^|/)\.darcsrepo($|/)+^\.darcs-temp-mail$+-darcs-backup[[:digit:]]+$+# gnu arch+(^|/)(\+|,)+(^|/)vssver\.scc$+\.swp$+(^|/)MT($|/)+(^|/)\{arch\}($|/)+(^|/).arch-ids($|/)+# bitkeeper+(^|/)BitKeeper($|/)+(^|/)ChangeSet($|/)++### miscellaneous+# backup files+~$+\.bak$+\.BAK$+# patch originals and rejects+\.orig$+\.rej$+# X server+\..serverauth.*+# image spam+\#+(^|/)Thumbs\.db$+# vi, emacs tags+(^|/)(tags|TAGS)$+#(^|/)\.[^/]+# core dumps+(^|/|\.)core$+# partial broken files (KIO copy operations)+\.part$+# waf files, see http://code.google.com/p/waf/+(^|/)\.waf-[[:digit:].]+-[[:digit:]]+($|/)+(^|/)\.lock-wscript$+# mac os finder+(^|/)\.DS_Store$+# cabal+(^|/)dist($|/)++(^|/)bin($|/)