packages feed

epub-tools (empty) → 1.0.0.0

raw patch · 47 files changed

+2714/−0 lines, 47 filesdep +basedep +bytestringdep +directorysetup-changed

Dependencies added: base, bytestring, directory, epub-metadata, filepath, mtl, process, regex-compat, zip-archive

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2008-2011 Dino Morelli+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.
+ Setup.hs view
@@ -0,0 +1,19 @@+#! /usr/bin/env runhaskell++-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++import Distribution.Simple+import System.Cmd ( system )+++main = defaultMainWithHooks (simpleUserHooks +   { runTests = testRunner+   } )++   where+      -- Target for running all unit tests+      testRunner _ _ _ _ = do+         system $ "runhaskell -isrc testsuite/runtests.hs"+         return ()
+ TODO view
@@ -0,0 +1,11 @@+-----+general++- Some notes in README about installing epubcheck if possible+++-----+epubmeta++- Maybe more options for modifying metadata from the command-line+
+ doc/INSTALL view
@@ -0,0 +1,24 @@+Installation information for the Windows binary distribution+++First, understand that this is command-line software. You don't run+it with the mouse. There is no icon. It's not going in your Start+menu. It's to be run from within a command shell.+++These epub tools will run from the Windows cmd.exe shell.++Even better, what I would do is install the cygwin tools and run+them from a bash or similar shell. Having cygwin in place, I would+place the binaries in /usr/local/bin, which should be on your PATH+in cygwin shells. And you're good to go.+++For help, try this in your shell:++   > epubmeta.exe --help+   > epubname.exe --help+   > epubzip.exe --help+++And if you find bugs or problems, email: dino@ui3.info
+ doc/dev/notes view
@@ -0,0 +1,12 @@+This project may benefit from the use of some other tools out here+such as epubcheck, zip and unzip++- Tools unzipping can try to fall back on unzip upon failure++- epubmeta needs write capability++- epub creation, think about this++- always release Windows binaries++- OSX too?
+ epub-tools.cabal view
@@ -0,0 +1,44 @@+name:                epub-tools+cabal-version:       >= 1.2+version:             1.0.0.0+build-type:          Simple+license:             BSD3+license-file:        LICENSE+copyright:           2008-2011 Dino Morelli +author:              Dino Morelli +maintainer:          Dino Morelli <dino@ui3.info>+stability:           stable+homepage:            http://ui3.info/d/proj/epub-tools.html+synopsis:            Command line utilities for working with epub files+description:         A suite of command-line utilities for creating and manipulating epub book files. Included are: epubmeta, epubname, epubzip+category:            Application, Console+tested-with:         GHC >= 6.12.3++executable           epubmeta+   main-is:          EpubTools/epubmeta.hs+   build-depends:    base >= 3 && < 5, bytestring, epub-metadata >= 2.2, +                     process, zip-archive+   hs-source-dirs:   src++   -- To strip debug symbols from the binary, for Windows, not portable+   --ghc-options:      -Wall -optl-s+   ghc-options:      -Wall++executable           epubname+   main-is:          EpubTools/epubname.hs+   build-depends:    base >= 3 && < 5, directory, epub-metadata >= 2.2, +                     mtl, regex-compat+   hs-source-dirs:   src++   -- To strip debug symbols from the binary, for Windows, not portable+   --ghc-options:      -Wall -optl-s+   ghc-options:      -Wall++executable           epubzip+   main-is:          EpubTools/epubzip.hs+   build-depends:    base >= 3 && < 5, epub-metadata >= 2.2, filepath+   hs-source-dirs:   src++   -- To strip debug symbols from the binary, for Windows, not portable+   --ghc-options:      -Wall -optl-s+   ghc-options:      -Wall
+ src/EpubTools/EpubMeta/Display.hs view
@@ -0,0 +1,18 @@+-- Copyright: 2010, 2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubMeta.Display+   where++import Codec.Epub.Opf.Format.Package+import Codec.Epub.Opf.Parse++import EpubTools.EpubMeta.Opts+import EpubTools.EpubMeta.Util+++display :: Options -> FilePath -> EM ()+display opts zipPath = do+   meta <- parseEpubOpf zipPath+   liftIO $ putStr $ formatPackage (optVerbose opts) meta
+ src/EpubTools/EpubMeta/Edit.hs view
@@ -0,0 +1,82 @@+-- Copyright: 2010, 2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubMeta.Edit+   ( edit )+   where++import Control.Monad+import System.Cmd+import System.Directory+import System.FilePath+import System.Environment+import System.Exit+import Text.Printf++import EpubTools.EpubMeta.Export+import EpubTools.EpubMeta.Import+import EpubTools.EpubMeta.Opts+import EpubTools.EpubMeta.Util+++{- Turn an (IO a) action into a (Maybe a) with a value of Nothing+   if an IO exception is raised+-}+ioMaybe :: IO a -> IO (Maybe a)+ioMaybe action =+   (fmap Just $ action)+   `catch`+   (const $ return Nothing)+++{- Look through the user's environment looking for their preferred +   editor. Use /usr/bin/vi as a default if none found+-}+findEditor :: EM FilePath+findEditor = do+   mbEditor <- liftIO $ foldr (liftM2 mplus) (findExecutable "vi")+      [ ioMaybe $ getEnv "EDITOR"+      , ioMaybe $ getEnv "VISUAL"+      ]++   maybe (throwError "epubmeta: ERROR: Could not find a suitable editor in your EDITOR or VISUAL environment variables, and could not find the vi binary. Fix this situation or use epubmeta export/import.") return mbEditor+++edit :: Options -> FilePath -> EM ()+edit opts = edit' $ optEdit opts+++edit' :: Edit -> FilePath -> EM ()++edit' NoBackup zipPath = do+   -- Make a temp path for the OPF document+   tempDir <- liftIO $ getTemporaryDirectory+   let opfPath = tempDir </> "epubmeta-temp-content.opf"++   -- Dump the OPF document to this path+   exportOpf (defaultOptions { optExport = ToPath opfPath }) zipPath++   -- Open this file in the user's editor and wait for it+   editor <- findEditor+   ec <- liftIO $ system $ printf "%s %s" editor opfPath++   case ec of+      ExitSuccess -> do+         -- Modify the book with the now-edited OPF data+         importOpf (defaultOptions { optImport = Just opfPath }) zipPath++         -- Delete the temp file+         liftIO $ removeFile opfPath++      ExitFailure _ -> do+         -- Delete the temp file+         liftIO $ removeFile opfPath++         throwError "epubmeta: ERROR: Something went wrong during editing, your file has not been changed."++edit' (Backup suffix) zipPath = do+   liftIO $ copyFile zipPath (zipPath ++ suffix)+   edit' NoBackup zipPath++edit' NotEditing _ = undefined
+ src/EpubTools/EpubMeta/Export.hs view
@@ -0,0 +1,36 @@+-- Copyright: 2010, 2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubMeta.Export+   ( exportOpf )+   where++import Codec.Epub.IO+import System.FilePath++import EpubTools.EpubMeta.Opts+import EpubTools.EpubMeta.Util+++write :: FilePath -> String -> EM ()+write path contents = liftIO $ do+   writeFile path contents+   putStrLn $ "OPF XML saved as: " ++ path+++exportOpf :: Options -> FilePath -> EM ()+exportOpf opts = export' (optExport opts)+++export' :: Export -> FilePath -> EM ()++export' Existing zipPath = do+   (fullPath, contents) <- opfContentsFromZip zipPath+   write (takeFileName fullPath) contents++export' (ToPath path) zipPath = do+   (_, contents) <- opfContentsFromZip zipPath+   write path contents++export' NoExport _ = undefined
+ src/EpubTools/EpubMeta/Import.hs view
@@ -0,0 +1,47 @@+-- Copyright: 2010, 2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubMeta.Import+   ( importOpf )+   where++import Codec.Archive.Zip ( Entry (..), addEntryToArchive, readEntry, toArchive )+import Codec.Epub.Archive ( writeArchive )+import Codec.Epub.IO ( opfContentsFromBS )+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as B+import Data.Maybe ( fromJust )+import System.Directory ( removeFile )++import EpubTools.EpubMeta.Opts+import EpubTools.EpubMeta.Util+++importOpf :: Options -> FilePath -> EM ()+importOpf opts zipPath = do+   -- The file we want to insert into the book+   let pathToNewOpf = fromJust . optImport $ opts++   -- Make new file into a Zip Entry+   tempEntry <- liftIO $ readEntry [] pathToNewOpf++   -- The path in the book where new file must go+   strictBytes <- liftIO $ S.readFile zipPath+   let bytes = B.fromChunks [strictBytes]+   (pathToOldFile, _) <- opfContentsFromBS bytes++   -- Adjust the entry's path+   let newEntry = tempEntry { eRelativePath = pathToOldFile }++   -- Bytes of existing archive already loaded, make an Archive from that+   let oldArchive = toArchive bytes++   -- Add new entry to it (replacing old one)+   let newArchive = addEntryToArchive newEntry oldArchive++   -- We're ready to write the new file, delete the old one+   liftIO $ removeFile zipPath++   -- Write the new archive out+   liftIO $ writeArchive zipPath newArchive
+ src/EpubTools/EpubMeta/Opts.hs view
@@ -0,0 +1,107 @@+-- Copyright: 2010, 2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubMeta.Opts+   ( Edit (..)+   , Export (..)+   , Options (..), defaultOptions+   , parseOpts, usageText+   )+   where++import System.Console.GetOpt++import EpubTools.EpubMeta.Util+++-- There are three states for this, representing with a new type+data Edit+   = Backup String+   | NoBackup+   | NotEditing+   deriving Eq+++maybeToEdit :: Maybe String -> Edit+maybeToEdit (Just suffix) = Backup suffix+maybeToEdit Nothing       = NoBackup+++-- There are three states for this, representing with a new type+data Export+   = ToPath String   -- User specified a file to export to+   | Existing        -- User wants export to use existing name+   | NoExport        -- This arg wasn't used at all+   deriving Eq+++maybeToExport :: Maybe String -> Export+maybeToExport (Just file) = ToPath file+maybeToExport Nothing     = Existing+++data Options = Options+   { optHelp :: Bool+   , optEdit :: Edit+   , optImport :: Maybe String+   , optExport :: Export+   , optVerbose :: Bool+   }+++defaultOptions :: Options+defaultOptions = Options+   { optHelp = False+   , optEdit = NotEditing+   , optImport = Nothing+   , optExport = NoExport+   , optVerbose = False+   }+++options :: [OptDescr (Options -> Options)]+options =+   [ Option ['h'] ["help"] +      (NoArg (\opts -> opts { optHelp = True } ))+      "This help text"+   , Option ['e'] ["edit-opf"]+      (OptArg (\f opts -> opts { optEdit = maybeToEdit f }) "SUF")+      "Edit a book's OPF XML data in a text editor. If an optional suffix is supplied, a backup of the book will be created with that suffix"+   , Option ['x'] ["export"]+      (OptArg (\f opts -> opts { optExport = maybeToExport f }) "FILE")+      "Export the book's OPF XML metadata to a file. If no file name is given, the name of the file in the book will be used"+   , Option ['i'] ["import"]+      (ReqArg (\f opts -> opts { optImport = Just f }) "FILE")+      "Import OPF metadata from the supplied XML file"+   , Option ['v'] ["verbose"]+      (NoArg (\opts -> opts { optVerbose = True } )) +      "Display all OPF package info, including manifest, spine and guide"+   ]+++parseOpts :: [String] -> EM (Options, [String])+parseOpts argv = +   case getOpt Permute options argv of+      (o,n,[]  ) -> return (foldl (flip id) defaultOptions o, n)+      (_,_,errs) -> throwError $ concat errs ++ usageText+++usageText :: String+usageText = (usageInfo header options) ++ "\n" ++ footer+   where+      header = init $ unlines+         [ "Usage: epubmeta [OPTIONS] EPUBFILE"+         , "View or edit EPUB OPF package data"+         , ""+         , "Options:"+         ]+      footer = init $ unlines+         [ "When -v or no options are given, epubmeta will display the OPF package data in a human-readable form."+         , ""+         , "The -e feature will look for an editor in this order: the EDITOR environment variable, the VISUAL environment variable, vi"+         , ""+         , "For more information please see the IDPF OPF specification found here: http://idpf.org/epub/20/spec/OPF_2.0.1_draft.htm"+         , ""+         , "Version 1.0.0.0  Dino Morelli <dino@ui3.info>"+         ]
+ src/EpubTools/EpubMeta/Util.hs view
@@ -0,0 +1,18 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubMeta.Util+   ( EM , runEM+   , liftIO+   , throwError+   )+   where++import Control.Monad.Error+++type EM a = (ErrorT String IO) a++runEM :: EM a -> IO (Either String a)+runEM = runErrorT
+ src/EpubTools/EpubName/Format/Anonymous.hs view
@@ -0,0 +1,23 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++{-# LANGUAGE FlexibleContexts #-}++module EpubTools.EpubName.Format.Anonymous+   ( fmtAnonymous )+   where++import Control.Monad.Error++import EpubTools.EpubName.Format.Util+   ( format+   , authorSingle , titleSimple+   )+import EpubTools.EpubName.Util ( Fields )+++fmtAnonymous :: (MonadError String m) => Fields -> m (String, String)+fmtAnonymous = format "Anonymous"+   "(.*)(Anonymous)" authorSingle+   "(.*)" titleSimple
+ src/EpubTools/EpubName/Format/AuthorBasic.hs view
@@ -0,0 +1,34 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.AuthorBasic+   ( fmtAuthorBasic )+   where++import Codec.Epub.Opf.Package.Metadata++import EpubTools.EpubName.Format.Util+   ( format+   , author, titleSimple+   )+import EpubTools.EpubName.Util+++fmtAuthorBasic :: Metadata -> EN (String, String)+fmtAuthorBasic = format "AuthorBasic"+   ".*" author+   "(.*)" titleSimple+++{-+authorPatterns :: [(String, [String] -> String)]+authorPatterns =+   [ ( ".* ([^ ]+) and .* ([^ ]+)", authorDouble )+   , ( "(.*)(Anonymous)", authorSingle )+   , ( "(.*) ([^ ]+ III)$", authorSingle )+   , ( "(.*) ([^ ]+ Jr\\.)$", authorSingle )+   , ( "(.*) (St\\. [^ ]+)$", authorSingle )+   , ( "(.*) ([^ ]+)$", authorSingle )+   ]+-}
+ src/EpubTools/EpubName/Format/AuthorDouble.hs view
@@ -0,0 +1,34 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.AuthorDouble+   ( fmtAuthorDouble )+   where++import Codec.Epub.Opf.Package.Metadata+import Data.Maybe ( fromJust )+import Prelude hiding ( last )+import Text.Printf+import Text.Regex++import EpubTools.EpubName.Format.Util ( filterCommon, format, justAuthors, +   titleSimple )+import EpubTools.EpubName.Util+++fmtAuthorDouble :: Metadata -> EN (String, String)+fmtAuthorDouble = format "AuthorDouble"+   ".* and .*" authorDouble+   "(.*)" titleSimple+++authorDouble :: Metadata -> String+authorDouble = fmtAuthors . extractParts . head . justAuthors+   where+      extractParts (MetaCreator _ _ di) = fromJust . +         matchRegex (mkRegex ".* ([^ ]+) and .* ([^ ]+)") $ di++      fmtAuthors (last1:last2:_) = +         printf "%s_%s-" (filterCommon last1) (filterCommon last2)+      fmtAuthors _ = undefined
+ src/EpubTools/EpubName/Format/AuthorSt.hs view
@@ -0,0 +1,23 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++{-# LANGUAGE FlexibleContexts #-}++module EpubTools.EpubName.Format.AuthorSt+   ( fmtAuthorSt )+   where++import Control.Monad.Error++import EpubTools.EpubName.Format.Util+   ( format+   , authorSingle, titleSimple+   )+import EpubTools.EpubName.Util ( Fields )+++fmtAuthorSt :: (MonadError String m) => Fields -> m (String, String)+fmtAuthorSt = format "AuthorSt"+   "(.*) (St\\. [^ ]+)$" authorSingle+   "(.*)" titleSimple
+ src/EpubTools/EpubName/Format/AuthorThird.hs view
@@ -0,0 +1,23 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++{-# LANGUAGE FlexibleContexts #-}++module EpubTools.EpubName.Format.AuthorThird+   ( fmtAuthorThird )+   where++import Control.Monad.Error++import EpubTools.EpubName.Format.Util+   ( format+   , authorPostfix, titleSimple+   )+import EpubTools.EpubName.Util ( Fields )+++fmtAuthorThird :: (MonadError String m) => Fields -> m (String, String)+fmtAuthorThird = format "AuthorThird"+   "(.*) ([^ ]+) (III)$" authorPostfix+   "(.*)" titleSimple
+ src/EpubTools/EpubName/Format/MagAeon.hs view
@@ -0,0 +1,45 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagAeon+   ( fmtMagAeon )+   where++import Codec.Epub.Opf.Package.Metadata++import EpubTools.EpubName.Format.Util ( format )+import EpubTools.EpubName.Util+++fmtMagAeon :: Metadata -> EN (String, String)+fmtMagAeon = format "MagAeon"+   ".* Authors" (const "")+   "^A[eE]on ([^ ]+)$" titleMagAeon+++titleMagAeon :: String -> [String] -> String+titleMagAeon _ (numWord:_) = "AeonMagazine" ++ (num numWord)+   where+      num "One"       = "01"+      num "Two"       = "02"+      num "Three"     = "03"+      num "Four"      = "04"+      num "Five"      = "05"+      num "Six"       = "06"+      num "Seven"     = "07"+      num "Eight"     = "08"+      num "Nine"      = "09"+      num "Ten"       = "10"+      num "Eleven"    = "11"+      num "Twelve"    = "12"+      num "Thirteen"  = "13"+      num "Fourteen"  = "14"+      num "Fifteen"   = "15"+      num "Sixteen"   = "16"+      num "Seventeen" = "17"+      num "Eighteen"  = "18"+      num "Nineteen"  = "19"+      num "Twenty"    = "20"+      num x           = "[ERROR titleMagAeon " ++ x ++ "]"+titleMagAeon _ _      = undefined
+ src/EpubTools/EpubName/Format/MagAnalog.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagAnalog+   ( fmtMagAnalog )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format, monthNum )+import EpubTools.EpubName.Util+++fmtMagAnalog :: Metadata -> EN (String, String)+fmtMagAnalog = format "MagAnalog"+   "Dell Magazine.*" (const "")+   "([^ ]*).*, ([^ ]+) ([0-9]{4})$" title+++title :: String -> [String] -> String+title _ (prefix:month:year:_) =+   printf "%sSF%s-%s" (filterCommon prefix) year (monthNum month)+title _ _ = undefined
+ src/EpubTools/EpubName/Format/MagApex.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagApex+   ( fmtMagApex )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( format )+import EpubTools.EpubName.Util+++fmtMagApex :: Metadata -> EN (String, String)+fmtMagApex = format "MagApex"+   ".*" (const "")+   "^Apex[^0-9]*([0-9]+)$" title+++title :: String -> [String] -> String+title _ (issue:_) =+   printf "ApexMagazine%03d" (read issue :: Int)+title _ _         = undefined
+ src/EpubTools/EpubName/Format/MagBcs.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagBcs+   ( fmtMagBcs )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format )+import EpubTools.EpubName.Util+++fmtMagBcs :: Metadata -> EN (String, String)+fmtMagBcs = format "MagBcs"+   ".*" (const "")+   "(Beneath Ceaseless.*) #([0-9]+).*" title+++title :: String -> [String] -> String+title _ (name:issue:_) =+   printf "%s_Issue%03d" (filterCommon name) (read issue :: Int)+title _ _              = undefined
+ src/EpubTools/EpubName/Format/MagChallengingDestiny.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagChallengingDestiny+   ( fmtMagChallengingDestiny )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format )+import EpubTools.EpubName.Util+++fmtMagChallengingDestiny :: Metadata -> EN (String, String)+fmtMagChallengingDestiny = format "MagChallengingDestiny"+   "Crystalline Sphere .*" (const "")+   "(Challenging Destiny) #([0-9]+).*" title+++title :: String -> [String] -> String+title _ (name:issue:_) =+   printf "%sMagazine%03d" (filterCommon name) (read issue :: Int)+title _ _              = undefined
+ src/EpubTools/EpubName/Format/MagClarkesworld.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagClarkesworld+   ( fmtMagClarkesworld )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( format )+import EpubTools.EpubName.Util+++fmtMagClarkesworld :: Metadata -> EN (String, String)+fmtMagClarkesworld = format "MagClarkesworld"+   ".*" (const "")+   "^(Clarkesworld)[^0-9]*([0-9]+)$" title+++title :: String -> [String] -> String+title _ (name:issue:_) =+   printf "%s%03d" name (read issue :: Int)+title _ _              = undefined
+ src/EpubTools/EpubName/Format/MagEclipse.hs view
@@ -0,0 +1,45 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagEclipse+   ( fmtMagEclipse )+   where++import Codec.Epub.Opf.Package.Metadata++import EpubTools.EpubName.Format.Util ( format )+import EpubTools.EpubName.Util+++fmtMagEclipse :: Metadata -> EN (String, String)+fmtMagEclipse = format "MagEclipse"+   ".*" (const "")+   "^(Eclipse) ([^ ]+)$" titleMagEclipse+++titleMagEclipse :: String -> [String] -> String+titleMagEclipse _ (magName:numWord:_) = magName ++ (num numWord)+   where+      num "One"       = "01"+      num "Two"       = "02"+      num "Three"     = "03"+      num "Four"      = "04"+      num "Five"      = "05"+      num "Six"       = "06"+      num "Seven"     = "07"+      num "Eight"     = "08"+      num "Nine"      = "09"+      num "Ten"       = "10"+      num "Eleven"    = "11"+      num "Twelve"    = "12"+      num "Thirteen"  = "13"+      num "Fourteen"  = "14"+      num "Fifteen"   = "15"+      num "Sixteen"   = "16"+      num "Seventeen" = "17"+      num "Eighteen"  = "18"+      num "Nineteen"  = "19"+      num "Twenty"    = "20"+      num x           = "[ERROR titleMagEclipse " ++ x ++ "]"+titleMagEclipse _ _      = undefined
+ src/EpubTools/EpubName/Format/MagFsf.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagFsf+   ( fmtMagFsf )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( format, monthNum )+import EpubTools.EpubName.Util+++fmtMagFsf :: Metadata -> EN (String, String)+fmtMagFsf = format "MagFsf"+   "Spilogale.*" (const "")+   ".* ([^ ]+) ([0-9]{4})$" title+++title :: String -> [String] -> String+title _ (month:year:_) =+   printf "FantasyScienceFiction%s-%s" year (monthNum month)+title _ _ = undefined
+ src/EpubTools/EpubName/Format/MagFutureOrbits.hs view
@@ -0,0 +1,26 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagFutureOrbits+   ( fmtMagFutureOrbits )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format, monthNum )+import EpubTools.EpubName.Util+++fmtMagFutureOrbits :: Metadata -> EN (String, String)+fmtMagFutureOrbits = format "MagFutureOrbits"+   ".*" (const "")+   "(Future Orbits) Issue ([0-9]+), ([^ ]+) ([0-9]{4})$" title+++title :: String -> [String] -> String+title _ (name:issue:month:year:_) =+   printf "%sMagazine%02d_%s-%s"+      (filterCommon name) (read issue :: Int) year (monthNum month)+title _ _ = undefined
+ src/EpubTools/EpubName/Format/MagGud.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagGud+   ( fmtMagGud )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( format )+import EpubTools.EpubName.Util+++fmtMagGud :: Metadata -> EN (String, String)+fmtMagGud = format "MagGud"+   "GUD Magazine Authors" (const "")+   ".* Magazine Issue ([0-9]+) ::.*" title+++title :: String -> [String] -> String+title _ (issue:_) =+   printf "GUDMagazine%02d" (read issue :: Int)+title _ _              = undefined
+ src/EpubTools/EpubName/Format/MagInterzone.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagInterzone+   ( fmtMagInterzone )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( format )+import EpubTools.EpubName.Util+++fmtMagInterzone :: Metadata -> EN (String, String)+fmtMagInterzone = format "MagInterzone"+   ".* Authors" (const "")+   "^(Interzone)[^0-9]*([0-9]+)$" title+++title :: String -> [String] -> String+title _ (name:issue:_) =+   printf "%sSFF%d" name (read issue :: Int)+title _ _              = undefined
+ src/EpubTools/EpubName/Format/MagLightspeed.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagLightspeed+   ( fmtMagLightspeed )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( format, monthNum )+import EpubTools.EpubName.Util+++fmtMagLightspeed :: Metadata -> EN (String, String)+fmtMagLightspeed = format "MagInterzone"+   ".*" (const "")+   "(Lightspeed) Magazine, (.*) (.*)" title+++title :: String -> [String] -> String+title _ (name:month:year:_) =+   printf "%s%s-%s" name year (monthNum month)+title _ _              = undefined
+ src/EpubTools/EpubName/Format/MagNameIssue.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagNameIssue+   ( fmtMagNameIssue )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format )+import EpubTools.EpubName.Util+++fmtMagNameIssue :: Metadata -> EN (String, String)+fmtMagNameIssue = format "MagNameIssue"+   ".* Authors" (const "")+   "(.*) #([0-9]+).*" title+++title :: String -> [String] -> String+title _ (name:issue:_) =+   printf "%s%02d" (filterCommon name) (read issue :: Int)+title _ _              = undefined
+ src/EpubTools/EpubName/Format/MagNemesis.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagNemesis+   ( fmtMagNemesis )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format )+import EpubTools.EpubName.Util+++fmtMagNemesis :: Metadata -> EN (String, String)+fmtMagNemesis = format "MagNemesis"+   "Stephen Adams" (const "")+   "(Nemesis Mag)azine #([0-9]+).*" title+++title :: String -> [String] -> String+title _ (name:issue:_) =+   printf "%s%03d" (filterCommon name) (read issue :: Int)+title _ _              = undefined
+ src/EpubTools/EpubName/Format/MagRageMachine.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagRageMachine+   ( fmtMagRageMachine )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format, monthNum )+import EpubTools.EpubName.Util+++fmtMagRageMachine :: Metadata -> EN (String, String)+fmtMagRageMachine = format "MagRageMachine"+   ".*" (const "")+   "(Rage Machine.*)--([^ ]+) ([0-9]{4})$" title+++title :: String -> [String] -> String+title _ (name:month:year:_) =+   printf "%s_%s-%s" (filterCommon name) year (monthNum month)+title _ _ = undefined
+ src/EpubTools/EpubName/Format/MagSomethingWicked.hs view
@@ -0,0 +1,25 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagSomethingWicked+   ( fmtMagSomethingWicked )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format )+import EpubTools.EpubName.Util+++fmtMagSomethingWicked :: Metadata -> EN (String, String)+fmtMagSomethingWicked = format "MagSomethingWicked"+   ".* Authors" (const "")+   "(Something Wicked).* #([0-9]+).*" title+++title :: String -> [String] -> String+title _ (name:issue:_) =+   printf "%sMagazine%02d" (filterCommon name) (read issue :: Int)+title _ _              = undefined
+ src/EpubTools/EpubName/Format/MagUniverse.hs view
@@ -0,0 +1,26 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.MagUniverse+   ( fmtMagUniverse )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format )+import EpubTools.EpubName.Util+++fmtMagUniverse :: Metadata -> EN (String, String)+fmtMagUniverse = format "MagUniverse"+   ".*" (const "")+   "^(Jim Baen's Universe)-Vol ([^ ]+) Num ([^ ]+)" title+++title :: String -> [String] -> String+title _ (name:vol:num:_) =+   printf "%sVol%02dNum%02d" (filterCommon name) (read vol :: Int) +      (read num :: Int)+title _ _         = undefined
+ src/EpubTools/EpubName/Format/SFBestOf.hs view
@@ -0,0 +1,24 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.SFBestOf+   ( fmtSFBestOf )+   where++import Codec.Epub.Opf.Package.Metadata+import Text.Printf++import EpubTools.EpubName.Format.Util ( filterCommon, format )+import EpubTools.EpubName.Util+++fmtSFBestOf :: Metadata -> EN (String, String)+fmtSFBestOf = format "SFBestOf"+   "Rich Horton.*" (const "")+   "(.*)" title+++title :: String -> [String] -> String+title year (name:_) = printf "%s%s" (filterCommon name) year+title _ _ = undefined
+ src/EpubTools/EpubName/Format/Util.hs view
@@ -0,0 +1,290 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Format.Util+   ( filterCommon, format+   , author+   , authorSingle, authorPostfix+   , justAuthors+   , titleSimple+   , monthNum )+   where++import Codec.Epub.Opf.Package+import Control.Monad.Error+import Data.Char+import Data.List ( foldl', intercalate, isPrefixOf )+import Data.Maybe ( fromJust )+--import Debug.Trace+import Prelude hiding ( last )+import Text.Printf+import Text.Regex++import EpubTools.EpubName.Opts+import EpubTools.EpubName.Util++++{- Convenience function to make a regex replacer for a given pattern +   and replacement string. Helps by doing the 'flip' of str and rpl+   so you can partial eval.+-}+repl :: String -> String -> String -> String+repl re rpl str = subRegex (mkRegex re) str rpl+++{- Transforms a string like this:+      "the quick brown McCheeseburger" -> "TheQuickBrownMccheeseburger"+-}+capFirstAndDeSpace :: String -> String+capFirstAndDeSpace s = concat $ map capFirst $ words s+   where+      capFirst (first:rest) = (toUpper first) : (map toLower rest)+      capFirst _ = undefined+++{- A set of common string filters that apply to any and all parts+   of every single string we process in this project.+-}+commonFilters :: [(String -> String)]+commonFilters =+   [ repl "[',\\?();#’]"  ""+   , repl "\\."           " "+   , repl ":"             "_"+   , filter (/= '"')+   , repl "]"             ""+   , repl "\\*"           ""+   , repl "!"             ""+   , repl "-"             " "+   , repl "\\["           "_ "+   -- Decided that I like the article included in titles+   --, repl "^The "         ""+   , repl "&"             " And "+   , capFirstAndDeSpace+   ]+++{- Utility function to apply the above commonFilters to a string,+   giving you back the transformed string+-}+filterCommon :: String -> String+filterCommon s = foldl' (flip id) s commonFilters+++{- Convert an English month name (with creative ranges and variations)+   into number form+-}+monthNum :: String -> String+monthNum x+   | isPrefixOf x "January"   = "01"+monthNum "January-February"   = "01_02"+monthNum "January/February"   = "01_02"+monthNum "Jan-Feb"            = "01_02"+monthNum "Jan/Feb"            = "01_02"+monthNum x+   | isPrefixOf x "February"  = "02"+monthNum x+   | isPrefixOf x "March"     = "03"+monthNum "March-April"        = "03_04"+monthNum "March/April"        = "03_04"+monthNum "Mar-Apr"            = "03_04"+monthNum "Mar/Apr"            = "03_04"+monthNum x+   | isPrefixOf x "April"     = "04"+monthNum "April-May"          = "04_05"+monthNum "April/May"          = "04_05"+monthNum "Apr-May"            = "04_05"+monthNum "Apr/May"            = "04_05"+monthNum "May"                = "05"+monthNum "May-June"           = "05_06"+monthNum "May/June"           = "05_06"+monthNum "May-Jun"            = "05_06"+monthNum "May/Jun"            = "05_06"+monthNum x+   | isPrefixOf x "June"      = "06"+monthNum "June/July"          = "06_07"+monthNum "June-July"          = "06_07"+monthNum "Jun-Jul"            = "06_07"+monthNum "Jun/Jul"            = "06_07"+monthNum x+   | isPrefixOf x "July"      = "07"+monthNum "July-August"        = "07_08"+monthNum "July/August"        = "07_08"+monthNum "Jul-Aug"            = "07_08"+monthNum "Jul/Aug"            = "07_08"+monthNum x+   | isPrefixOf x "August"    = "08"+monthNum "August-September"   = "08_09"+monthNum "August/September"   = "08_09"+monthNum "Aug-Sep"            = "08_09"+monthNum "Aug/Sep"            = "08_09"+monthNum x+   | isPrefixOf x "September" = "09"+monthNum "September-October"  = "09_10"+monthNum x+   | isPrefixOf x "October"   = "10"+monthNum "October-November"   = "10_11"+monthNum "October/November"   = "10_11"+monthNum "Oct-Nov"            = "10_11"+monthNum "Oct/Nov"            = "10_11"+monthNum "November-December"  = "11_12"+monthNum x+   | isPrefixOf x "November"  = "11"+monthNum x+   | isPrefixOf x "December"  = "12"+monthNum x                    = "[ERROR monthNum " ++ x ++ "]"+++formatAuthor+   :: String+   -> (Metadata -> String)+   -> Metadata+   -> EN String+formatAuthor re f md =+   maybe (throwError "formatAuthor failed")+      (const (return . f $ md)) (tryPats $ justAuthors md)++   where+      justAuthorStrings = map (\(MetaCreator _ _ n) -> n)++      match = matchRegex (mkRegex re)++      tryPats [] = Just []  -- Special case of no authors at all+      tryPats as = foldr mplus Nothing+         ((map match) . justAuthorStrings $ as)+++formatTitle +   :: String+   -> (String -> [String] -> String)+   -> String+   -> String+   -> EN String+formatTitle re f year s = case matchRegex (mkRegex re) s of+   Just xs -> return $ f year xs+   Nothing -> throwError "formatTitle failed"+++{- Look for a date tag with event="original-publication" in the+   metadata+-}+extractYear :: Metadata -> EN String+extractYear md = do+   inclY <- asks optYear+   return . maybe "" ('_' :) $+      (foldr mplus Nothing (map (maybeYear inclY) $ metaDates md))++   where+      maybeYear True (MetaDate (Just "original-publication") d) = Just d+      maybeYear _    _                                          = Nothing+++extractPublisher :: Metadata -> Bool -> String+extractPublisher _  False = ""+extractPublisher md True  = maybe "" ('_' :)+   (foldr mplus Nothing (map maybePub $ metaContributors md))++   where+      maybePub (MetaCreator (Just "bkp") (Just fa) _ ) = Just fa+      maybePub _                                       = Nothing+++{- This is the main work performing function that's called by every +   formatter. It expects to see a regexp pattern and format function +   for both author and title parts of the book info. And then the map +   of fields from a book.+   If the pattern matches, the resulting list of match results is sent +   to the supplied format function. If any of this fails, the entire +   action throws.+-}+format+   :: String+   -> String+   -> (Metadata -> String)+   -> String+   -> (String -> [String] -> String)+   -> Metadata+   -> EN (String, String)+format label authorPat authorFmt titlePat titleFmt md = do+   newAuthor <- formatAuthor authorPat authorFmt md+   --trace newAuthor (return ())++   (MetaTitle _ oldTitle) <- case metaTitles md of+      [] -> throwError "format failed, no title present"+      ts -> return . head $ ts+   year <- extractYear md+   newTitle <- formatTitle titlePat titleFmt year oldTitle++   publisher <- fmap (extractPublisher md) $ asks optPublisher++   return+      ( label+      , printf "%s%s%s.epub" newAuthor newTitle publisher+      )+++formatSingleAuthor :: MetaCreator -> String++formatSingleAuthor (MetaCreator _ (Just fa) di) = +   if ((fa == di) && all (/= ',') fa)+      then formatSingleAuthor $ MetaCreator Nothing Nothing di+      else authorSingle [fa]++formatSingleAuthor (MetaCreator _ _         di) = +   authorSingle . reverse $ parts+   where+      parts = fromJust .+         (matchRegex (mkRegex "(.*) ([^ ]+)$")) $ di+++lastName :: MetaCreator -> String+lastName (MetaCreator _ (Just fa) _ ) = head . fromJust .+   (matchRegex (mkRegex "(.*),.*")) $ fa+lastName (MetaCreator _ _         di) = head . fromJust .+   (matchRegex (mkRegex ".* (.*)")) $ di+++formatMultiAuthors :: [MetaCreator] -> String+formatMultiAuthors = (intercalate "_") . (map lastName)+++justAuthors :: Metadata -> [MetaCreator]+justAuthors = (filter isAut) . metaCreators+   where+      isAut (MetaCreator (Just "aut") _ _) = True+      isAut (MetaCreator Nothing      _ _) = True+      isAut _                              = False+++author :: Metadata -> String+author = fmtAuthor . justAuthors+   where+      fmtAuthor [c] = formatSingleAuthor c+      fmtAuthor cs = (formatMultiAuthors cs) ++ "-"+++{- A common simple formatter for many book authors+-}+authorSingle :: [String] -> String+authorSingle (last:rest:_) =+   printf "%s%s-" (filterCommon last) (filterCommon rest)+authorSingle [n]           = printf "%s-" $ filterCommon n+--authorSingle x             = show x+authorSingle _             = undefined+++{- Author names with a postfix like II, III, Jr. or Sr.+-}+authorPostfix :: [String] -> String+authorPostfix (rest:last:postfix:_) =+   printf "%s%s%s-" (filterCommon last) (filterCommon rest)+      (filterCommon postfix)+authorPostfix _             = undefined+++{- A common simple formatter for many book titles. Handles year too.+-}+titleSimple :: String -> [String] -> String+titleSimple year (old:_) = printf "%s%s" (filterCommon old) year+titleSimple _ _          = undefined
+ src/EpubTools/EpubName/Formatters.hs view
@@ -0,0 +1,74 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Formatters+   ( tryFormatting )+   where++import Codec.Epub.Opf.Package.Metadata+import Control.Monad+import Text.Printf++--import EpubTools.EpubName.Format.Anonymous+import EpubTools.EpubName.Format.AuthorBasic+import EpubTools.EpubName.Format.AuthorDouble+--import EpubTools.EpubName.Format.AuthorSt+--import EpubTools.EpubName.Format.AuthorThird+import EpubTools.EpubName.Format.MagAeon+import EpubTools.EpubName.Format.MagAnalog+import EpubTools.EpubName.Format.MagApex+import EpubTools.EpubName.Format.MagBcs+import EpubTools.EpubName.Format.MagChallengingDestiny+import EpubTools.EpubName.Format.MagClarkesworld+import EpubTools.EpubName.Format.MagEclipse+import EpubTools.EpubName.Format.MagFsf+import EpubTools.EpubName.Format.MagFutureOrbits+import EpubTools.EpubName.Format.MagGud+import EpubTools.EpubName.Format.MagInterzone+import EpubTools.EpubName.Format.MagLightspeed+import EpubTools.EpubName.Format.MagNameIssue+import EpubTools.EpubName.Format.MagNemesis+import EpubTools.EpubName.Format.MagRageMachine+import EpubTools.EpubName.Format.MagSomethingWicked+import EpubTools.EpubName.Format.MagUniverse+import EpubTools.EpubName.Format.SFBestOf+import EpubTools.EpubName.Util+++formatters :: [Metadata -> EN (String, String)]+formatters =+{-+   , fmtAnonymous+   , fmtAuthorThird+   , fmtAuthorSt+-}+   [ fmtMagAnalog+   , fmtMagNemesis+   , fmtMagAeon+   , fmtMagEclipse+   , fmtMagChallengingDestiny+   , fmtMagClarkesworld+   , fmtMagGud+   , fmtMagInterzone+   , fmtMagLightspeed+   , fmtMagSomethingWicked+   , fmtMagRageMachine+   , fmtMagFsf+   , fmtMagFutureOrbits+   , fmtMagBcs+   , fmtMagApex+   , fmtMagNameIssue+   , fmtMagUniverse+   , fmtSFBestOf+   , fmtAuthorDouble++   , fmtAuthorBasic+   ]+++tryFormatting :: (FilePath, Metadata) -> EN (String, String)+tryFormatting (oldPath, md) = do+   foldr mplus +      (throwError $ printf "%s [ERROR No formatter found]" oldPath) $+      map (\f -> f md) formatters
+ src/EpubTools/EpubName/Opts.hs view
@@ -0,0 +1,114 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>+++module EpubTools.EpubName.Opts+   ( Options (..)+   , defaultOptions+   , parseOpts, usageText+   )+   where++import Data.Maybe+import System.Console.GetOpt+import System.Exit+++data Options = Options+   { optHelp :: Bool+   , optNoAction :: Bool+   , optOverwrite :: Bool+   , optPublisher :: Bool+   , optVerbose :: Maybe Int+   , optYear :: Bool+   }+++defaultOptions :: Options+defaultOptions = Options+   { optHelp = False+   , optNoAction = False+   , optOverwrite = False+   , optPublisher = False+   , optVerbose = Nothing+   , optYear = True+   }+++options :: [OptDescr (Options -> Options)]+options =+   [ Option ['h'] ["help"] +      (NoArg (\opts -> opts { optHelp = True } ))+      "This help text"+   , Option ['n'] ["no-action"]+      (NoArg (\opts -> opts { optNoAction = True } )) +      "Display what would be done, but do nothing"+   , Option ['o'] ["overwrite"]+      (NoArg (\opts -> opts { optOverwrite = True } )) +      "Overwrite existing file with new name"+   , Option ['p'] ["publisher"]+      (NoArg (\opts -> opts { optPublisher = True } )) +      "Include book publisher if present. See below"+   , Option ['v'] ["verbose"]+      ( OptArg+         ((\n opts -> opts { optVerbose = Just (read n)}) . fromMaybe "1")+         "LEVEL")+      "Verbosity level: 1, 2"+   , Option ['y'] ["year"]+      (NoArg (\opts -> opts { optPublisher = True } )) +      "Suppress inclusion of original publication year, if present"+   ]+++parseOpts :: [String] -> IO (Either ExitCode (Options, [String]))+parseOpts argv = +   case getOpt Permute options argv of+      (o,n,[]  ) -> return . Right $ (foldl (flip id) defaultOptions o, n)+      (_,_,errs) -> do+         putStrLn $ concat errs ++ usageText+         return . Left $ ExitFailure 1+++usageText :: String+usageText = (usageInfo header options) ++ "\n" ++ footer+   where+      header = init $ unlines+         [ "Usage: epubname [OPTIONS] FILES"+         , "Rename EPUB book files with clear names based on their metadata"+         , ""+         , "Options:"+         ]+      footer = init $ unlines+         [ "Verbosity levels:"+         , "   1      - Include which formatter processed the file"+         , "   2      - Include the OPF Package and Metadata info"+         , ""+         , "Exit codes:"+         , "   0 - success"+         , "   1 - bad options"+         , "   2 - failed to process one or more of the files given"+         , ""+         , "Book names are constructed by examining parts of the OPF Package metadata such as the title, creators, contributors and dates."+         , ""+         , "For books with a single author:"+         , "   LastFirst[Middle]-TitleText[_year][_publisher].epub"+         , ""+         , "For books with multiple authors:"+         , "   Last1_Last2[_Last3...]-TitleText[_year][_publisher].epub"+         , ""+         , "For books that have no clear authors, such as compilations:"+         , "   TitleText[_year][_publisher].epub"+         , ""+         , "Only creator tags with either a role attribute of 'aut' or no role at all are considered authors. If a file-as attribute is present, this will be the preferred string. If not, the program tries to do some intelligent parsing of the name."+         , ""+         , "The date is taken from the first date tag with an event attribute of 'original-publication', if present. The OPF spec is a little thin on this. I noticed frequent use of this attribute in books, and so went with that."+         , ""+         , "Publisher: I wanted to provide a way to have multiple copies of the same book produced by different publishers and name them sort-of unambiguously. I came up with the idea of expecting a contributor tag with role attribute of 'bkp' (so far, this is fairly normal). And then use a file-as attribute on that tag to contain a small string to be used in the filename. The idea here is short and sweet for the file-as."+         , ""+         , "Magazines are kind of a sticky problem in that it's often desireable to have edition and/or date info in the filename. There's a lot of chaos out there with titling the epub editions of magazines. The solution in this software is to do some pattern matching on multiple fields in the magazine's metadata combined with custom naming code for specific magazines. This means that support for future magazines will likely have to be hand-coded into future versions of this utility. Modifying this just isn't very non-programmer friendly."+         , ""+         , "For more information please see the IDPF OPF specification found here: http://idpf.org/epub/20/spec/OPF_2.0.1_draft.htm"+         , ""+         , "Version 1.0.0.0  Dino Morelli <dino@ui3.info>"+         ]
+ src/EpubTools/EpubName/Util.hs view
@@ -0,0 +1,21 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubName.Util+   ( EN , runEN+   , throwError+   , asks+   )+   where++import Control.Monad.Error+import Control.Monad.Reader++import EpubTools.EpubName.Opts+++type EN a = ReaderT Options (ErrorT String IO) a++runEN :: Options -> EN a -> IO (Either String a)+runEN env ev = runErrorT $ runReaderT ev env
+ src/EpubTools/EpubZip/Opts.hs view
@@ -0,0 +1,72 @@+-- Copyright: 2010, 2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module EpubTools.EpubZip.Opts+   ( Options (..)+   , parseOpts, usageText+   )+   where++import System.Console.GetOpt+++data Options = Options+   { optHelp :: Bool+   , optOverwrite :: Bool+   }+++defaultOptions :: Options+defaultOptions = Options+   { optHelp = False+   , optOverwrite = False+   }+++options :: [OptDescr (Options -> Options)]+options =+   [ Option ['h'] ["help"] +      (NoArg (\opts -> opts { optHelp = True } ))+      "This help text"+   , Option ['o'] ["overwrite"] +      (NoArg (\opts -> opts { optOverwrite = True } ))+      "Force overwrite if dest file exists"+   ]+++parseOpts :: [String] -> IO (Options, [String])+parseOpts argv = +   case getOpt Permute options argv of+      (o,n,[]  ) -> return (foldl (flip id) defaultOptions o, n)+      (_,_,errs) -> ioError $ userError (concat errs ++ usageText)+++usageText :: String+usageText = (usageInfo header options) ++ "\n" ++ footer+   where+      header = init $ unlines+         [ "Usage: epubzip [OPTIONS] DESTDIR"+         , "       epubzip [OPTIONS] DESTDIR/EPUBFILE"+         , "Construct an epub zip archive from files in the current directory"+         , ""+         , "Options:"+         ]+      footer = init $ unlines+         [ "If run with DESTDIR alone, epubzip will try to construct a name from the OPF package data for this book (see epubname). If run with DESTDIR/EPUBFILE, epubzip will use that name for the destination file."+         , ""+         , "You may have noticed that there is no epubunzip utility. Truth is, epubs are just zip files and you barely need epubzip either if you have the normal zip/unzip utilities installed. While not as fancy with file naming and leaving out dotfiles, this works for zipping:"+         , ""+         , "   $ cd DIR"+         , "   $ zip -Xr9D ../EPUBFILE mimetype *"+         , ""+         , "And for unzipping, it's really just as easy:"+         , ""+         , "   $ mkdir TEMPDIR"+         , "   $ cd TEMPDIR"+         , "   $ unzip EPUBFILE"+         , ""+         , "For more information please see the IDPF OPF specification found here: http://idpf.org/epub/20/spec/OPF_2.0.1_draft.htm"+         , ""+         , "Version 1.0.0.0  Dino Morelli <dino@ui3.info>"+         ]
+ src/EpubTools/epubmeta.hs view
@@ -0,0 +1,34 @@+-- Copyright: 2010, 2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++import Data.Maybe+import System.Environment ( getArgs )+import System.Exit++import EpubTools.EpubMeta.Display+import EpubTools.EpubMeta.Edit+import EpubTools.EpubMeta.Export+import EpubTools.EpubMeta.Import+import EpubTools.EpubMeta.Opts+import EpubTools.EpubMeta.Util+++dispatch :: Options -> [FilePath] -> EM ()+dispatch opts (f:[]) | optEdit opts /= NotEditing = edit opts f+dispatch opts (f:[]) | isJust . optImport $ opts  = importOpf opts f+dispatch opts (f:[]) | optExport opts /= NoExport = exportOpf opts f+dispatch opts (f:[])                              = display opts f+dispatch _    _                                   = throwError usageText+++main :: IO ()+main = do+   result <- runEM $+      liftIO getArgs >>= parseOpts >>= uncurry dispatch+   either exitFail (const (exitWith ExitSuccess)) result++   where+      exitFail msg = do+         putStrLn msg+         exitWith $ ExitFailure 1
+ src/EpubTools/epubname.hs view
@@ -0,0 +1,88 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++import Codec.Epub.Opf.Format.Package+import Codec.Epub.Opf.Package+import Codec.Epub.Opf.Parse+import Control.Monad+import Control.Monad.Trans+import System.Directory ( doesFileExist, renameFile )+import System.Environment ( getArgs )+import System.Exit+import System.IO ( BufferMode ( NoBuffering )+                 , hSetBuffering, stdout, stderr +                 )+import Text.Printf++import EpubTools.EpubName.Formatters ( tryFormatting )+import EpubTools.EpubName.Opts ( Options (..), parseOpts, usageText )+import EpubTools.EpubName.Util+++{- Construct additional verbose output+-}+formatF :: (String, Package) -> String+formatF (fmtUsed, _) = printf "\n   formatter: %s" fmtUsed++formatFM :: (String, Package) -> String+formatFM (fmtUsed, pkg) =+   printf "\n   formatter: %s\n%s" fmtUsed+      (formatPackage False pkg)+++{- Format output for a book that was processed+-}+makeOutput :: Options -> (FilePath, FilePath, String, Package) -> String+makeOutput opts (oldPath, newPath, fmtUsed, pkg) =+   printf "%s -> %s%s" oldPath newPath+      (additional (optVerbose opts) (fmtUsed, pkg))+   where+      additional Nothing  = const ""+      additional (Just 1) = formatF+      additional _        = formatFM+++{- Process an individual epub book file+-}+processBook :: Options -> FilePath -> IO Bool+processBook opts oldPath = do+   result <- runEN opts $ do+      pkg <- parseEpubOpf oldPath+      let md = opMeta pkg+      (fmtUsed, newPath) <- tryFormatting (oldPath, md)++      when (not $ optOverwrite opts) $ do+         fileExists <- liftIO $ doesFileExist newPath+         when fileExists $ throwError $ +            printf "ERROR: File %s already exists!" newPath++      unless (optNoAction opts) $ liftIO $ renameFile oldPath newPath++      return (oldPath, newPath, fmtUsed, pkg)++   let (success, report) = either ((,) False)+         (\r -> (True, makeOutput opts r)) result+   putStrLn report+   return success+++main :: IO ()+main = do+   -- No buffering, it messes with the order of output+   mapM_ (flip hSetBuffering NoBuffering) [ stdout, stderr ]++   (opts, paths) <- getArgs >>= parseOpts >>= either exitWith return++   ec <- if ((optHelp opts) || (null paths))+      then do+         putStrLn usageText+         return ExitSuccess+      else do+         when (optNoAction opts) (putStrLn "No-action specified")+         codes <- mapM (processBook opts) paths+         case all id codes of+            True  -> return ExitSuccess+            False -> return . ExitFailure $ 2++   exitWith ec
+ src/EpubTools/epubzip.hs view
@@ -0,0 +1,58 @@+-- Copyright: 2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++import Codec.Epub.Archive+import Codec.Epub.IO+import Codec.Epub.Opf.Package+import Codec.Epub.Opf.Parse+import Control.Monad+import System.Directory+import System.Environment+import System.Exit+import System.FilePath+import Text.Printf++import EpubTools.EpubName.Formatters ( tryFormatting )+import qualified EpubTools.EpubName.Opts as EN+import EpubTools.EpubName.Util ( runEN )+import EpubTools.EpubZip.Opts+++exitFail :: String -> IO ()+exitFail msg = do+      putStrLn msg+      exitWith $ ExitFailure 1+++main :: IO ()+main = do+   (opts, paths) <- getArgs >>= parseOpts++   when ((optHelp opts) || (null paths)) $ exitFail usageText++   let inputPath = head paths+   isDir <- doesDirectoryExist inputPath+   en <- if isDir+         then runEN EN.defaultOptions $ do+            package <- do+               (_, contents) <- opfContentsFromDir "."+               parseXmlToOpf contents+            (_, newPath) <- tryFormatting+               ("CURRENT DIRECTORY", opMeta package)+            return $ inputPath </> newPath+         else return . Right $ inputPath++   case en of+      Right zipPath -> do+         exists <- doesFileExist zipPath+         when ((not . optOverwrite $ opts) && exists) $ do+            exitFail $+               printf "File %s exists, use --overwrite to force" zipPath++         archive <- mkEpubArchive "."+         writeArchive zipPath archive++         printf "Book created: %s\n" zipPath++      Left errorMsg -> exitFail errorMsg
+ testsuite/runtests.hs view
@@ -0,0 +1,786 @@+-- Copyright: 2008-2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module Main+   where++import Codec.Epub.Opf.Package.Metadata+import Control.Monad.Error+import Test.HUnit ( Counts, Test (..), assertEqual, runTestTT )+import Test.HUnit.Base ( Assertion )++import EpubTools.EpubName.Formatters ( tryFormatting )+import EpubTools.EpubName.Opts+import EpubTools.EpubName.Util+++assertNewNameOpts :: Options -> String -> Metadata +   -> (String, String) -> Assertion+assertNewNameOpts opts desc meta expected = do+   result <- runEN opts $ tryFormatting ("", meta)+   let actual = either (\em -> ("NO FORMATTER", em)) id result+   assertEqual desc expected actual+++assertNewName :: String -> Metadata -> (String, String) -> Assertion+assertNewName = assertNewNameOpts defaultOptions+++main :: IO ()+main = do+   runTestTT tests+   return ()+++tests :: Test+tests = TestList+   [ testAuthorMinimal+   , testAuthorRole+   , testAuthorFileas+   , testAuthorFull+   , testAuthorDoubleAnd+   , testNoAuthor+   , testNoAuthorPubDate+   , testCapsTitle+   , testColon+   , testBracketTitle+   , testNoTitle+   , testAllPunctuation+   , testPubYear+   , testPubYearUnwanted+   , testMagAeon+   , testMagAEon+   , testMagApexLong+   , testMagApexShort+   , testChallengingDestinyShort+   , testChallengingDestinyLong+   , testChallengingDestinyPub+   , testAnalog+   , testAsimovs+   , testFsfShort+   , testFsfLong+   , testMagFutureOrbits+   , testGudShort+   , testGudLong+   , testInterzoneShort+   , testInterzoneLong+   , testNemesisShort+   , testNemesisLong+   , testMagSomethingWicked+   , testSFBestOf+   , testMagBlackStatic+   , testRageMachineMag+   , testEclipseMag+   , testBcs+   , testBkpFileAs+   , testBkpText+   , testBkpMissing+   , testMagUniverse+   , testMagClarkesworld+   ]+++testAuthorMinimal :: Test+testAuthorMinimal = TestCase $+   assertNewName "minimal author" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing "Herman Melville"]+         , metaTitles = [MetaTitle Nothing "Moby Dick"]+         }+      expected =+         ( "AuthorBasic"+         , "MelvilleHerman-MobyDick.epub"+         )+++testAuthorRole :: Test+testAuthorRole = TestCase $+   assertNewName "author with role" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut") Nothing+            "Herman Melville"]+         , metaTitles = [MetaTitle Nothing "Moby Dick"]+         }+      expected =+         ( "AuthorBasic"+         , "MelvilleHerman-MobyDick.epub"+         )+++testAuthorFileas :: Test+testAuthorFileas = TestCase $+   assertNewName "author with file-as" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing+            (Just "Melville, Herman")+            "Herman Melville"]+         , metaTitles = [MetaTitle Nothing "Moby Dick"]+         }+      expected =+         ( "AuthorBasic"+         , "MelvilleHerman-MobyDick.epub"+         )+++testAuthorFull :: Test+testAuthorFull = TestCase $+   assertNewName "author fully filled out" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut")+            (Just "Melville, Herman")+            "Herman Melville"]+         , metaTitles = [MetaTitle Nothing "Moby Dick"]+         }+      expected =+         ( "AuthorBasic"+         , "MelvilleHerman-MobyDick.epub"+         )+++testNoAuthor :: Test+testNoAuthor = TestCase $+   assertNewName "no author(s) at all" meta expected+   where+      meta = emptyMetadata+         { metaCreators =+            [ MetaCreator (Just "edt") Nothing+               "Graham Spindlewest"+            , MetaCreator (Just "ill") Nothing+               "Eva Tunglewacker"+            ]+         , metaTitles = [MetaTitle Nothing+            "Some Collection of Fine Stories, Volume 1"]+         }+      expected =+         ( "SFBestOf"+         , "SomeCollectionOfFineStoriesVolume1.epub"+         )+++testNoAuthorPubDate :: Test+testNoAuthorPubDate = TestCase $+   assertNewName "no author(s) at all, with publication date" +      meta expected+   where+      meta = emptyMetadata+         { metaCreators =+            [ MetaCreator (Just "edt") Nothing+               "Graham Spindlewest"+            , MetaCreator (Just "ill") Nothing+               "Eva Tunglewacker"+            ]+         , metaTitles = [MetaTitle Nothing+            "Some Collection of Fine Stories, Volume 1"]+         , metaDates = [MetaDate (Just "original-publication")+            "2008"]+         }+      expected =+         ( "SFBestOf"+         , "SomeCollectionOfFineStoriesVolume1_2008.epub"+         )+++testAuthorDoubleAnd :: Test+testAuthorDoubleAnd = TestCase $+   assertNewName "two authors separated by and" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing+            "Kevin J. Anderson and Rebecca Moesta"]+         , metaTitles = [MetaTitle Nothing "Rough Draft"]+         }+      expected =+         ( "AuthorDouble"+         , "Anderson_Moesta-RoughDraft.epub"+         )+++testCapsTitle :: Test+testCapsTitle = TestCase $+   assertNewName "title all caps" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing "Greg Bear"]+         , metaTitles = [MetaTitle Nothing "EON"]+         }+      expected =+         ( "AuthorBasic"+         , "BearGreg-Eon.epub"+         )+++testColon :: Test+testColon = TestCase $+   assertNewName "colon becomes underscore" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing "Ed Howdershelt"]+         , metaTitles = [MetaTitle Nothing +            "Book 1: 3rd World Products, Inc."]+         }+      expected =+         ( "AuthorBasic"+         , "HowdersheltEd-Book1_3rdWorldProductsInc.epub"+         )+++testBracketTitle :: Test+testBracketTitle = TestCase $+   assertNewName "title with brackets" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing "Mercedes Lackey"]+         , metaTitles = [MetaTitle Nothing "SKitty [Shipscat series #1]"]+         }+      expected =+         ( "AuthorBasic"+         , "LackeyMercedes-Skitty_ShipscatSeries1.epub"+         )+++testNoTitle :: Test+testNoTitle = TestCase $+   assertNewName "missing title" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing+            "Nobody McCrankypants"]+         }+      expected =+         ( "NO FORMATTER"+         , " [ERROR No formatter found]"+         )+++testAllPunctuation :: Test+testAllPunctuation = TestCase $+   assertNewName "big test of all punctuation" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing+            "Dino Morelli"]+         , metaTitles = [MetaTitle Nothing+            "The *crazy*: Sand-box. Of Smedley's discontent, fear & Malnourishment? (Maybe not!); [Part #2]"]+         }+      expected =+         ( "AuthorBasic"+         , "MorelliDino-TheCrazy_SandBoxOfSmedleysDiscontentFearAndMalnourishmentMaybeNot_Part2.epub"+         )+++testPubYear :: Test+testPubYear = TestCase $+   assertNewName "book with a publication year" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing "Jim Jones"]+         , metaTitles = [MetaTitle Nothing "A Timeless Story"]+         , metaDates = [MetaDate (Just "original-publication") "2003"]+         }+      expected =+         ( "AuthorBasic"+         , "JonesJim-ATimelessStory_2003.epub"+         )+++testPubYearUnwanted :: Test+testPubYearUnwanted = TestCase $+   assertNewNameOpts opts+      "book with a publication year but we don't want"+      meta expected+   where+      opts = defaultOptions { optYear = False }+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing "Jim Jones"]+         , metaTitles = [MetaTitle Nothing "A Timeless Story"]+         , metaDates = [MetaDate (Just "original-publication") "2003"]+         }+      expected =+         ( "AuthorBasic"+         , "JonesJim-ATimelessStory.epub"+         )+++testMagAeon :: Test+testMagAeon = TestCase $+   assertNewName "Aeon magazine" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing "Aeon Authors"]+         , metaTitles = [MetaTitle Nothing "Aeon Eight"]+         }+      expected =+         ( "MagAeon"+         , "AeonMagazine08.epub"+         )+++testMagAEon :: Test+testMagAEon = TestCase $+   assertNewName "AEon magazine" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing "AEon Authors"]+         , metaTitles = [MetaTitle Nothing "Aeon Thirteen"]+         }+      expected =+         ( "MagAeon"+         , "AeonMagazine13.epub"+         )+++testMagApexLong :: Test+testMagApexLong = TestCase $+   assertNewName "Apex Magazine, older, long title" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut") Nothing+            "Apex Authors"]+         , metaTitles = [MetaTitle Nothing+            "Apex Science Fiction and Horror Digest #9"]+         }+      expected =+         ( "MagApex"+         , "ApexMagazine009.epub"+         )+++testMagApexShort :: Test+testMagApexShort = TestCase $+   assertNewName "Apex Magazine, newer, short title" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut") Nothing+            "Apex Authors"]+         , metaTitles = [MetaTitle Nothing+            "Apex Magazine Issue 17"]+         }+      expected =+         ( "MagApex"+         , "ApexMagazine017.epub"+         )+++testChallengingDestinyShort :: Test+testChallengingDestinyShort = TestCase $+   assertNewName "Challenging Destiny Magazine, short" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing+            "Crystalline Sphere Authors"]+         , metaTitles = [MetaTitle Nothing+            "Challenging Destiny #23"]+         }+      expected =+         ( "MagChallengingDestiny"+         , "ChallengingDestinyMagazine023.epub"+         )+++testChallengingDestinyLong :: Test+testChallengingDestinyLong = TestCase $+   assertNewName "Challenging Destiny Magazine, long" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "Crystalline Sphere Authors"]+         , metaTitles = [MetaTitle Nothing +            "Challenging Destiny #24: August 2007"]+         }+      expected =+         ( "MagChallengingDestiny"+         , "ChallengingDestinyMagazine024.epub"+         )+++testChallengingDestinyPub :: Test+testChallengingDestinyPub = TestCase $+   assertNewName "Challenging Destiny Magazine, Publishing in author"+      meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "Crystalline Sphere Publishing"]+         , metaTitles = [MetaTitle Nothing +            "Challenging Destiny #18"]+         }+      expected =+         ( "MagChallengingDestiny"+         , "ChallengingDestinyMagazine018.epub"+         )+++testAnalog :: Test+testAnalog = TestCase $+   assertNewName "Analog" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing+            "Dell Magazine Authors"]+         , metaTitles = [MetaTitle Nothing+            "Analog SFF, July-August 2003"]+         }+      expected =+         ( "MagAnalog"+         , "AnalogSF2003-07_08.epub"+         )+++testAsimovs :: Test+testAsimovs = TestCase $+   assertNewName "Asimovs" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing+            "Dell Magazine Authors"]+         , metaTitles = [MetaTitle Nothing+            "Asimov's SF, August 2003"]+         }+      expected =+         ( "MagAnalog"+         , "AsimovsSF2003-08.epub"+         )+++testFsfShort :: Test+testFsfShort = TestCase $+   assertNewName "FSF Magazine, short" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing+            "Spilogale Authors"]+         , metaTitles = [MetaTitle Nothing "FSF, April 2008"]+         }+      expected =+         ( "MagFsf"+         , "FantasyScienceFiction2008-04.epub"+         )+++testFsfLong :: Test+testFsfLong = TestCase $+   assertNewName "FSF Magazine, long" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "Spilogale Authors"]+         , metaTitles = [MetaTitle Nothing +            "FSF Magazine, April 2006"]+         }+      expected =+         ( "MagFsf"+         , "FantasyScienceFiction2006-04.epub"+         )+++testMagFutureOrbits :: Test+testMagFutureOrbits = TestCase $+   assertNewName "testMagFutureOrbits" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "Vander Neut Publications, L.L.C."]+         , metaTitles = [MetaTitle Nothing +            "Future Orbits Issue 5, June/July 2002"]+         }+      expected =+         ( "MagFutureOrbits"+         , "FutureOrbitsMagazine05_2002-06_07.epub"+         )+++testGudShort :: Test+testGudShort = TestCase $+   assertNewName "Gud Magazine, short" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "GUD Magazine Authors"]+         , metaTitles = [MetaTitle Nothing +            "GUD Magazine Issue 0 :: Spring 2007"]+         }+      expected =+         ( "MagGud"+         , "GUDMagazine00.epub"+         )+++testGudLong :: Test+testGudLong = TestCase $+   assertNewName "Gud Magazine, long" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "GUD Magazine Authors, Jeff Somers, Jeremy Shipp"]+         , metaTitles = [MetaTitle Nothing +            "GUD Magazine Issue 2 :: Spring 2008"]+         }+      expected =+         ( "MagGud"+         , "GUDMagazine02.epub"+         )+++testInterzoneShort :: Test+testInterzoneShort = TestCase $+   assertNewName "Interzone Magazine, short" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "TTA Press Authors"]+         , metaTitles = [MetaTitle Nothing +            "Interzone SFF #212"]+         }+      expected =+         ( "MagInterzone"+         , "InterzoneSFF212.epub"+         )+++testInterzoneLong :: Test+testInterzoneLong = TestCase $+   assertNewName "Interzone Magazine, long" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "TTA Press Authors"]+         , metaTitles = [MetaTitle Nothing +            "Interzone Science Fiction and Fantasy Magazine #216"]+         }+      expected =+         ( "MagInterzone"+         , "InterzoneSFF216.epub"+         )+++testNemesisShort :: Test+testNemesisShort = TestCase $+   assertNewName "Nemesis Magazine, short" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "Stephen Adams"]+         , metaTitles = [MetaTitle Nothing +            "Nemesis Magazine #2"]+         }+      expected =+         ( "MagNemesis"+         , "NemesisMag002.epub"+         )+++testNemesisLong :: Test+testNemesisLong = TestCase $+   assertNewName "Nemesis Magazine, long" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "Stephen Adams"]+         , metaTitles = [MetaTitle Nothing +            "Nemesis Magazine #7: Featuring Victory Rose in Death Stalks the Ruins"]+         }+      expected =+         ( "MagNemesis"+         , "NemesisMag007.epub"+         )+++testMagSomethingWicked :: Test+testMagSomethingWicked = TestCase $+   assertNewName "Something Wicked Magazine" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "Something Wicked Authors"]+         , metaTitles = [MetaTitle Nothing +            "Something Wicked SF and Horror Magazine #5"]+         }+      expected =+         ( "MagSomethingWicked"+         , "SomethingWickedMagazine05.epub"+         )+++testSFBestOf :: Test+testSFBestOf = TestCase $+   assertNewName "Science Fiction: The Best of the Year" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "Rich Horton"]+         , metaTitles = [MetaTitle Nothing +            "Science Fiction: The Best of the Year, 2007 Edition"]+         }+      expected =+         ( "SFBestOf"+         , "ScienceFiction_TheBestOfTheYear2007Edition.epub"+         )+++testMagBlackStatic :: Test+testMagBlackStatic = TestCase $+   assertNewName "Black Static Magazine" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator Nothing Nothing +            "TTA Press Authors"]+         , metaTitles = [MetaTitle Nothing +            "Black Static Horror Magazine #5"]+         }+      expected =+         ( "MagNameIssue"+         , "BlackStaticHorrorMagazine05.epub"+         )+++testRageMachineMag :: Test+testRageMachineMag = TestCase $+   assertNewName "Rage Machine Magazine" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "edt") Nothing +            "G. W. Thomas"]+         , metaTitles = [MetaTitle Nothing +            "Rage Machine Magazine #1--December 2005"]+         }+      expected =+         ( "MagRageMachine"+         , "RageMachineMagazine1_2005-12.epub"+         )+++testEclipseMag :: Test+testEclipseMag = TestCase $+   assertNewName "Eclipse Magazine" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut")+            (Just "Strahan, Jonathan")+            "Jonathan Strahan"]+         , metaTitles = [MetaTitle Nothing +            "Eclipse One"]+         }+      expected =+         ( "MagEclipse"+         , "Eclipse01.epub"+         )+++testBcs :: Test+testBcs = TestCase $+   assertNewName "Beneath Ceaseless Skies Magazine" meta expected+   where+      meta = emptyMetadata+         { metaCreators = +            [ MetaCreator (Just "aut")+               (Just "Tidwell, Erin A.")+               "Hoover, Kenneth Mark"+            , MetaCreator (Just "aut")+               (Just "Tidwell, Erin A.")+               "Tidwell, Erin A."+            ]+         , metaTitles = [MetaTitle Nothing +            "Beneath Ceaseless Skies #32"]+         }+      expected =+         ( "MagBcs"+         , "BeneathCeaselessSkies_Issue032.epub"+         )+++testBkpFileAs :: Test+testBkpFileAs = TestCase $+   assertNewNameOpts opts +      "book publisher suffix requested and present in file-as"+      meta expected+   where+      opts = defaultOptions { optPublisher = True }+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut") Nothing+            "Herman Melville"]+         , metaContributors = [MetaCreator (Just "bkp") (Just "acme") +            "Acme Publishing, Inc."]+         , metaTitles = [MetaTitle Nothing "Moby Dick"]+         }+      expected =+         ( "AuthorBasic"+         , "MelvilleHerman-MobyDick_acme.epub"+         )+++testBkpText :: Test+testBkpText = TestCase $+   assertNewNameOpts opts +      "book publisher suffix requested and present in text"+      meta expected+   where+      opts = defaultOptions { optPublisher = True }+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut") Nothing+            "Herman Melville"]+         , metaContributors = [MetaCreator (Just "bkp") Nothing+            "Acme Publishing, Inc."]+         , metaTitles = [MetaTitle Nothing "Moby Dick"]+         }+      expected =+         ( "AuthorBasic"+         , "MelvilleHerman-MobyDick.epub"+         )+++testBkpMissing :: Test+testBkpMissing = TestCase $+   assertNewNameOpts opts +      "book publisher suffix requested and not present"+      meta expected+   where+      opts = defaultOptions { optPublisher = True }+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut") Nothing+            "Herman Melville"]+         , metaTitles = [MetaTitle Nothing "Moby Dick"]+         }+      expected =+         ( "AuthorBasic"+         , "MelvilleHerman-MobyDick.epub"+         )+++testMagUniverse :: Test+testMagUniverse = TestCase $+   assertNewNameOpts opts +      "Jim Baen's Universe Magazine"+      meta expected+   where+      opts = defaultOptions { optPublisher = True }+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut") Nothing+            "Jim Baen's Universe"]+         , metaTitles = [MetaTitle Nothing "Jim Baen's Universe-Vol 4 Num 6"]+         }+      expected =+         ( "MagUniverse"+         , "JimBaensUniverseVol04Num06.epub"+         )+++testMagClarkesworld :: Test+testMagClarkesworld = TestCase $+   assertNewName "Clarkesworld Magazine" meta expected+   where+      meta = emptyMetadata+         { metaCreators = [MetaCreator (Just "aut")+            (Just "Kowal, Mary Robinette")+            "Mary Robinette Kowal"]+         , metaTitles = [MetaTitle Nothing+            "Clarkesworld Magazine - Issue 21"]+         }+      expected =+         ( "MagClarkesworld"+         , "Clarkesworld021.epub"+         )
+ util/all-books.sh view
@@ -0,0 +1,17 @@+#! /bin/bash++# Copyright: 2008-2011 Dino Morelli+# License: BSD3 (see LICENSE)+# Author: Dino Morelli <dino@ui3.info>+++cd /var/local/archive/doc/books/fiction++tempDir="/home/dino/temp"+allFile="epubname-all"++find . -name '*.epub' | xargs -n 30 epubname -n -v1 > $tempDir/$allFile++cd $tempDir++grep -v 'No-action' $allFile | grep -v 'formatter:' | sed -e 's/.*\///' | perl -ne '($o, $n) = /(.*) -> (.*)/; if ($o ne $n) { print $_ }' | sort > epubname-changed
+ util/prefs/boring view
@@ -0,0 +1,69 @@+# Boring file regexps:+\.hi$+\.hi-boot$+\.o-boot$+\.o$+\.o\.cmd$+\.p_hi$+\.p_o$+\.installed-pkg-config+\.setup-config+\.setup-config^dist(/|$)+# *.ko files aren't boring by default because they might+# be Korean translations rather than kernel modules.+# \.ko$+\.ko\.cmd$+\.mod\.c$+(^|/)\.tmp_versions($|/)+(^|/)CVS($|/)+\.cvsignore$+^\.#+(^|/)RCS($|/)+,v$+(^|/)\.svn($|/)+(^|/)\.hg($|/)+(^|/)\.git($|/)+\.bzr$+(^|/)SCCS($|/)+~$+(^|/)_darcs($|/)+(^|/)\.darcsrepo($|/)+\.bak$+\.BAK$+\.orig$+\.rej$+(^|/)vssver\.scc$+\.swp$+(^|/)MT($|/)+(^|/)\{arch\}($|/)+(^|/).arch-ids($|/)+(^|/),+\.prof$+(^|/)\.DS_Store$+(^|/)BitKeeper($|/)+(^|/)ChangeSet($|/)+\.py[co]$+\.elc$+\.class$+\.zwc$+\.revdep-rebuild.*+\..serverauth.*+\#+(^|/)Thumbs\.db$+(^|/)autom4te\.cache($|/)+(^|/)config\.(log|status)$+^\.depend$+(^|/)(tags|TAGS)$+#(^|/)\.[^/]+(^|/|\.)core$+\.(obj|a|exe|so|lo|la)$+^\.darcs-temp-mail$+-darcs-backup[[:digit:]]+$+\.(fas|fasl|sparcf|x86f)$+\.part$+(^|/)\.waf-[[:digit:].]+-[[:digit:]]+($|/)+(^|/)\.lock-wscript$+^\.darcs-temp-mail$+\_vti_cnf$+\_vti_pvt$+(^|/)dist($|/)
+ util/win-dist.sh view
@@ -0,0 +1,15 @@+#! /bin/bash++if [ -z "$1" ]+then+   echo "ERROR: Please give basename for zip file"+   echo "example: ./util/win-dist.sh epub-tools-1.0.0.0-win"+   exit 1+fi++zipFile="$1.zip"++rm $zipFile++buildDir="dist/build"+zip -j $zipFile doc/INSTALL $buildDir/epubmeta/epubmeta.exe $buildDir/epubname/epubname.exe $buildDir/epubzip/epubzip.exe