packages feed

bibdb-0.4.1: src/Main.hs

-----------------------------------------------------------------------------
-- |
-- Module      : Main
-- Description : Main file for bibdb bibliography manager
-- Maintainer  : coskuacay@gmail.com
-- Stability   : experimental
-----------------------------------------------------------------------------
module Main (main) where

import Data.Function (on)
import Data.Maybe (fromMaybe)
import Control.Monad (when)
import System.Exit (exitFailure, exitSuccess)
import System.FilePath (replaceExtension)

import qualified Data.ByteString.Lazy as BS
import Data.List (groupBy, sort, sortOn)
import Text.PrettyPrint
import Text.PrettyPrint.HughesPJClass (Pretty (..))

import qualified Options.Applicative as Options
import qualified Args

import Parser.Location (Located (..), spanSLine)
import qualified Parser.Parser as Parser
import Database.Fetch (fetchAll)
import Bibliography (bibliography)

import Utility.Except


main :: IO ()
main = do
  job <- Options.execParser opts
  res <- runExceptT (runJob job)
  case res of
    Left err -> do putStrLn err; exitFailure
    Right () -> exitSuccess
  where
    opts :: Options.ParserInfo Args.Job
    opts = Options.info (Options.helper <*> Args.jobParser)
      ( Options.fullDesc
      Options.<> Options.progDesc "Gather references specified in FILE"
      Options.<> Options.header "Bibdb bibliography manager"
      )

runJob :: Args.Job -> Exception ()
runJob job = do
  bib <- liftIO $ BS.readFile (Args.jobSource job)

  when (Args.jobStopAt job == Args.Lexing) $ do
    tokens <- liftEither $ Parser.runParser bib $
                Parser.tokenParser (Args.jobSource job)
    save "lexing" tokens
    printAndExit tokens

  entries <- liftEither $ Parser.runParser bib $
               Parser.fileParser $ Args.jobSource job
  save "parsing" entries
  when (Args.jobStopAt job == Args.Parsing) $ printAndExit entries

  checkDuplicatesOn Parser.entryIdent entries

  (ids, database) <- fetchAll (Args.jobBibSize job) (map Parser.entrySource entries)
  save "gather" database
  when (Args.jobStopAt job == Args.Downloading) $ printAndExit database

  let renames = zip ids (map Parser.entryIdent entries)
  let final = bibliography renames database
  let msg = "Automatically generated file. DO NOT MODIFY!\n"
  liftIO $ writeFile outputFile (msg ++ "\n" ++ renderList final)

  where
    outputFile :: FilePath
    outputFile = fromMaybe def (Args.jobOutput job)
      where def = replaceExtension (Args.jobSource job) "bib"

    save :: Pretty a => String -> [a] -> ExceptT String IO ()
    save extension es | Args.jobKeepIntermediate job =
      let out = replaceExtension outputFile extension
      in liftIO $ writeFile out (renderList es)
    save _ _ = return ()

    printAndExit :: Pretty a => [a] -> ExceptT String IO ()
    printAndExit es = do
      liftIO $ putStrLn $ renderList es
      liftIO exitSuccess

    renderList :: Pretty a => [a] -> String
    renderList = render . vcat . map pPrint


-- | Check that all elements of a list are distinct. Uses the given
-- function to extract the key to compare on.
checkDuplicatesOn :: forall key e . (Ord key, Pretty key, Located e)
                  => (e -> key)     -- ^ Extract a key to compare on
                  -> [e]            -- ^ List of inputs
                  -> Exception ()
checkDuplicatesOn key es = case map duplicateError (duplicates es) of
  [] -> return ()
  errs -> throwError $ render $ vcat errs
  where
    -- Find duplicates
    duplicates :: [e] -> [[e]]
    duplicates = filter ((>= 2) . length) . groupBy ((==) `on` key) . sortOn key

    -- Generate an error message for a list of duplicates
    duplicateError :: [e] -> Doc
    duplicateError ds@(d : _) = text "Multiple definitions of" <+> pPrint (key d)
      <+> text "at lines" <+> summarizeLocations ds
    duplicateError [] = error "impossible"

    summarizeLocations :: [e] -> Doc
    summarizeLocations = hsep . punctuate comma . map int . sort . map line

    line :: Located a => a -> Int
    line = spanSLine . location