packages feed

pandoc-citeproc-0.1.2.1: biblio2yaml.hs

module Main where
import Text.CSL.Input.Bibutils (readBiblioString, BibFormat(..))
import Text.CSL.Reference (Reference(refId))
import Data.List (group, sort)
import Data.Char (chr, toLower)
import Data.Monoid
import Data.Yaml
import Control.Applicative
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import Data.Attoparsec.ByteString.Char8 as Attoparsec
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import System.Console.GetOpt
import Control.Monad
import System.IO
import System.FilePath (takeExtension)
import System.Environment (getArgs)
import System.Exit
import Data.Version (showVersion)
import Paths_pandoc_citeproc (version)

main :: IO ()
main = do
  argv <- getArgs
  let (flags, args, errs) = getOpt Permute options argv
  let header = "Usage: biblio2yaml [OPTION..] [FILE]"
  unless (null errs && length args < 2) $ do
    hPutStrLn stderr $ usageInfo (unlines $ errs ++ [header]) options
    exitWith $ ExitFailure 1
  when (Version `elem` flags) $ do
    putStrLn $ "biblio2yaml " ++ showVersion version
    exitWith ExitSuccess
  when (Help `elem` flags) $ do
    putStrLn $ usageInfo header options
    exitWith ExitSuccess
  let mbformat = case [f | Format f <- flags] of
                      [x] -> readFormat x
                      _   -> Nothing
  bibstring <- case args of
                    (x:_) -> readFile x
                    []    -> getContents
  let bibformat = mbformat <|> msum (map formatFromExtension args)
  case bibformat of
       Nothing  -> do
         hPutStrLn stderr $ usageInfo ("Unknown format\n" ++ header) options
         exitWith $ ExitFailure 3
       Just f   -> readBiblioString f bibstring >>= warnDuplicateKeys >>=
                     outputYamlBlock . unescapeTags . encode

warnDuplicateKeys :: [Reference] -> IO [Reference]
warnDuplicateKeys refs = mapM_ warnDup dupKeys >> return refs
  where warnDup k = hPutStrLn stderr $ "biblio2yaml: duplicate key " ++ k
        allKeys   = map refId refs
        dupKeys   = [x | (x:_:_) <- group (sort allKeys)]

outputYamlBlock :: B.ByteString -> IO ()
outputYamlBlock contents = do
  putStrLn "---\nreferences:"
  B.putStr contents
  putStrLn "..."

formatFromExtension :: FilePath -> Maybe BibFormat
formatFromExtension = readFormat . dropWhile (=='.') . takeExtension

data Option =
    Help | Version | Format String
  deriving (Ord, Eq, Show)

readFormat :: String -> Maybe BibFormat
readFormat = go . map toLower
  where go "mods"     = Just Mods
        go "biblatex" = Just BibLatex
        go "bib"      = Just BibLatex
        go "bibtex"   = Just Bibtex
        go "ris"      = Just Ris
        go "endnote"  = Just Endnote
        go "enl"      = Just Endnote
        go "endnotexml" = Just EndnotXml
        go "xml"      = Just EndnotXml
        go "wos"      = Just Isi
        go "isi"      = Just Isi
        go "medline"  = Just Medline
        go "copac"    = Just Copac
        go "json"     = Just Json
        go _          = Nothing

options :: [OptDescr Option]
options =
  [ Option ['h'] ["help"] (NoArg Help) "show usage information"
  , Option ['V'] ["version"] (NoArg Version) "show program version"
  , Option ['f'] ["format"] (ReqArg Format "FORMAT") "bibliography format"
  ]

-- turn
-- id: ! "\u043F\u0443\u043D\u043A\u04423"
-- into
-- id: пункт3
unescapeTags :: B.ByteString -> B.ByteString
unescapeTags bs = case parseOnly (many $ tag <|> other) bs of
                       Left e  -> error e
                       Right r -> B.concat r

tag :: Attoparsec.Parser B.ByteString
tag = do
  _ <- string $ B8.pack ": ! \""
  cs <- manyTill (uchar <|> regchar) (char '"')
  return $ B8.pack ": \"" <> B.concat cs <> B8.pack "\""

other :: Attoparsec.Parser B.ByteString
other = Attoparsec.takeWhile1 (/=':') <|> Attoparsec.take 1

uchar :: Attoparsec.Parser B.ByteString
uchar = do
  _ <- string $ B8.pack "\\u"
  cs <- count 4 $ satisfy $ inClass "0-9a-fA-F"
  let n = read ('0':'x':cs)
  return $ encodeUtf8 $ T.pack [chr n]

regchar :: Attoparsec.Parser B.ByteString
regchar = (B8.pack . (:[])) <$> anyChar