packages feed

imbib (empty) → 1.0.0

raw patch · 14 files changed

+1661/−0 lines, 14 filesdep +ConfigFiledep +basedep +bibtexsetup-changed

Dependencies added: ConfigFile, base, bibtex, bytestring, containers, curl, directory, download-curl, filepath, glib, gnomevfs, gtk, mtl, parsec, process, split, utf8-string

Files

+ .imbib view
@@ -0,0 +1,6 @@+watched = %(home)s/Downloads+archive = %(home)s/Papers+library = %(home)s/library.bib++viewer = /usr/bin/evince+editor = /usr/bin/emacs
+ Batch.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE RecordWildCards, TupleSections  #-}++import Control.Applicative hiding ((<|>),many)+import Control.Monad+import Control.Monad.Trans+import qualified Data.ByteString.Lazy as BS+import Data.Char+import Data.List+import Data.Maybe+import Data.Tree+import Data.Traversable+import System.FilePath+import System.Process+import Text.BibTeX.Entry as Entry+import Text.BibTeX.Parse+import Data.Function+import System.Environment+import qualified Data.Map as M+import Config++import TypedBibData+import BibDB+import BibAttach+import qualified SuffixTreeCluster as SC+-- import Text.Groom+import MaybeIO++-------------------------------------------------------------------------+-- CheckDuplicates, method 1+++pairs (x:xs) = map (x,) xs ++ pairs xs+pairs _ = []++checkDup bib = do+  -- mapM_ putString $  [ groom $ (map findTitle es, shared) | (es,shared) <- common ]+  putString "Possible duplicates:"+  mapM_ putString $ map (show . map findTitle) dups+  saveBib $ uniqDups ++ (bib \\ uniqDups) -- clump together the duplicates+  where+    uniqDups = nub $ concat $ dups+    trueDups es =  filter (\e -> any (not . (areRelated e)) es) es+    dups = [ es | (es0,shared) <- common, +             let es = trueDups es0, +             not (null es),+             sum (map length shared) >= threshold (minimum (map (length . project . findTitle) es))]+    threshold x = (9 * x) `div` 10+    -- SC.printTree $ SC.select ("", clusterTree)+    common = M.toList $ SC.commonStrings info+    clusterTree = SC.construct info+    info = [(project $ findTitle e,[e]) | e <- bib]+ +-------------------------------------------------------------------------+-- Auto-renaming of attachments++rename :: (String -> String) -> (String,String) -> MaybeIO (String,String) +rename new (oldfname,typ) +    | oldfname == newfname = bail+    | otherwise = do+  oex <- doesFileExist oldfname+  nex <- doesFileExist newfname+  case (oex,nex) of+    (True,False) -> do renameFile oldfname newfname+                       return (newfname,typ)+    (False,_) -> putString ("old does not exist: " ++ oldfname) >> bail+    (_,True) -> putString  ("new already exists: " ++ newfname) >> bail+    -- (_,True) -> do removeFile oldfname >> return (newfname,typ)+                        +  where newfname = new typ+        bail = return (oldfname,typ)++renamer :: Entry -> MaybeIO Entry+renamer t@Entry{..} = do +  files <- traverse (rename (findAttachName t)) files+  return $ Entry{..}++renameAttachments bib = do +  bib' <- traverse renamer bib+  saveBib bib'++------------------------------------------------------------------------+-- Check for orphans++check :: (String,String) -> MaybeIO () +check (fname,typ) = do+  ex <- doesFileExist fname+  if (not ex) then putString $ "missing: " ++ fname else return ()++checker Entry{..} = mapM_ check files++getDirectoryContents' d = map (d </>) <$> getDirectoryContents d++checkAttachments bib = do+  fnames <- getDirectoryContents' "/home/bernardy/Papers"+  let attachments = [ f | e <- bib, (f,t) <- files e] +      +  mapM_ putString (fnames \\ attachments)++----------------------------------------------------------------------+-- Merge another bibtex file++mergeIn bib fname = do+  bib2 <- uncheckedHarmless $ rightOrDie <$> loadBibliographyFrom fname+  checkDup $ bib2 ++ bib+  return ()+++{-+mergeBibs bib1 bib2 = +    where bibM1 = M.fromList [(project $ findTitle e, e) | e <- bib1]+-}          +++-----------------------------------------------------------------------+-- Harvest Downloads++harvest bib = do+  contents <- getDirectoryContents' downloadsDirectory+  let oldFiles = concatMap (map snd . files) bib+      newFiles = contents \\ oldFiles +      papers = [Entry {kind = "download", +                       seeAlso = [],+                       authors = [],+                       files = [(fname,guessType fname BS.empty)],+                       otherFields = [("title",fname),("date","2010")]+                      } | fname <- newFiles, takeExtension fname `elem` [".pdf",".ps"]]+  saveBib $ papers ++ bib++------------------------------------------------------------------------+-- Driver++saveBib b = safely "Saving bibfile" $ saveBibliography b+++go (command: ~(arg1:_)) = do +  bib <- uncheckedHarmless $ (rightOrDie <$> loadBibliography)+  case command of+    "check" -> checkAttachments bib+    "rename" -> renameAttachments bib+    "merge" -> mergeIn bib arg1+    "harvest" -> harvest bib+    "dup" -> checkDup bib++dryRun = run True+trueRun = run False++main = do +  args <- getArgs+  case args of+    ("dry":args) -> dryRun $ go args+    _ -> trueRun $ go args
+ BibAttach.hs view
@@ -0,0 +1,50 @@+module BibAttach where++import Control.Applicative hiding ((<|>),many)+import Control.Monad+import Control.Monad.Trans+import qualified Data.ByteString.Lazy as BS+import Data.Char+import Data.List+import Data.List.Split+import Data.Maybe+import Data.Tree+import System.Directory+import System.FilePath+import System.Process+import Data.Function+import System.FilePath++import TypedBibData+import Config++--------------------------------------+-- Attachment manipulation++sanitize = filter (\c -> c`notElem` ";:{}/\\" && ord c < 128) +-- there seems to be a problem with big chars in filenames at the moment.+-- see http://hackage.haskell.org/trac/ghc/ticket/3307++-- save a (binary) file, safely+saveFile name contents = do+  putStrLn $ "Creating directory for " ++ name+  createDirectoryIfMissing True (dropFileName name)+  putStrLn $ "Writing..."+  BS.writeFile name contents+  return name++findAttachName entry ext = attachmentsRoot </> (sanitize $ findTitle entry ++ "-" ++ findYear entry ++ "." ++ ext)+++guessType :: FilePath -> BS.ByteString -> String+guessType fname contents +    | BS.head contents == ord' '@' = "bib"+    | magic == pdfMagic = "pdf"+    | magic == psMagic = "ps"+    | otherwise = drop 1 $ takeExtension fname+   where magic = BS.take 4 contents+         +         pdfMagic = BS.pack $ map ord' "%PDF"+         psMagic  = BS.pack $ map ord' "%!PS"+         ord' = fromIntegral . ord +        
+ BibDB.hs view
@@ -0,0 +1,41 @@+module BibDB where+++import Control.Applicative hiding ((<|>),many)+import Control.Monad+import Control.Monad.Trans+import qualified Data.ByteString +import Data.Char+import Data.List+import Data.List.Split+import Data.Maybe+import Data.Tree+import System.Directory+import System.FilePath+import Data.Function++import Text.ParserCombinators.Parsec (parseFromFile)+import TypedBibData+import Config++------------+-- DB+++loadBibliography = loadBibliographyFrom bibfile++loadBibliographyFrom fileName = do+  mBib <- fmap bibToForest <$> parseFromFile parseBib fileName +  case mBib of+    Left err -> return (Left err)+    Right bib -> do putStrLn $ show (length $ bib) ++ " entries loaded." -- force it right here+                    return (Right bib)++formatBib = concatMap formatEntry . map treeToEntry++saveBibliography :: [TypedBibData.Entry] -> IO ()+saveBibliography bib = do+  writeFile bibfile (formatBib bib)+  putStrLn $ show (length bib) ++ " entries saved to " ++ bibfile++
+ BibFile.hs view
@@ -0,0 +1,39 @@+module BibFile where++import Control.Applicative hiding ((<|>),many)+import Control.Monad+import Control.Monad.Trans+import qualified Data.ByteString +import Data.Char+import Data.List+import Data.List.Split+import Data.Maybe+import Data.Tree+import System.Directory+import System.FilePath+import Data.Function++import Text.ParserCombinators.Parsec (parseFromFile)+import BibData+++--------------------------------------+-- Bib file manipulation++bibfile = "/home/bernardy/project/gitroot/bibtex/jp.bib"++loadBibliography = loadBibliographyFrom bibfile++loadBibliographyFrom fname = do+  mBib <- fmap bibToForest <$> parseFromFile parseBib bibfile +  case mBib of+    Left err -> do print "Failed to load library"+                   print err+                   return []+    Right bib -> do putStrLn $ show (length $ bib) ++ " entries loaded." -- force it right here+                    -- print $ last bib -- really force closing the file... ?+                    return bib++saveBibliography bib = do+  writeFile bibfile (concatMap formatEntry $ map treeToEntry bib)+  putStrLn $ show (length bib) ++ " entries saved to " ++ bibfile
+ Config.hs view
@@ -0,0 +1,38 @@+module Config where+    +import Paths_imbib+import System.IO.Unsafe+import System.FilePath+import System.Process+import System.Directory+import Data.ConfigFile+import Control.Applicative++rightOrDie (Left err) = error (show err)+rightOrDie (Right x) = x++startConfig = rightOrDie $ +  set (emptyCP {accessfunc = interpolatingAccess 10}) "DEFAULT" "home" homeDirectory++configFileName = ".imbib"++configuration = unsafePerformIO $ do+  c0f <- getDataFileName configFileName                  +  c0 <- rightOrDie <$> readfile startConfig c0f+  let user = homeDirectory </> configFileName+  ex <- doesFileExist user+  if ex then  rightOrDie <$> readfile c0 user else return c0+  +getOption = rightOrDie . get configuration "DEFAULT"++downloadsDirectory = getOption "watched"+attachmentsRoot = getOption "archive"+bibfile = getOption "library"+runEditor lineNumber file = runProcess' (getOption "editor") ['+':show lineNumber,file]+runViewer file = runProcess' (getOption "viewer") [file] ++-- The following should probably not change+homeDirectory = unsafePerformIO $ getHomeDirectory+iconFile = unsafePerformIO $ getDataFileName "icon.svg"++runProcess' = \bin args -> runProcess bin args Nothing Nothing Nothing Nothing Nothing
+ Imbibed.hs view
@@ -0,0 +1,296 @@+{-# LANGUAGE RecordWildCards, TupleSections  #-}++{--+TODO:+if URI is bib, use as such+Search google on title+merge other bibtex(s); duplicate detection+-}++import Control.Applicative hiding ((<|>),many)+import Control.Monad+import Control.Monad.Trans+import qualified Data.ByteString +import qualified Data.ByteString.Lazy.UTF8 as UTF8 (toString)+import Data.Char+import Data.List+import Data.Maybe+import Graphics.UI.Gtk hiding (on, Entry)+import qualified Graphics.UI.Gtk as Gtk +import Graphics.UI.Gtk.ModelView as New+import Graphics.UI.Gtk.ModelView.ListStore+import Network.Curl.Download.Lazy+import Network.Curl.Opts+import System.Directory+import System.FilePath+import Text.BibTeX.Entry as Entry+import Text.BibTeX.Parse+import Data.Function+import System.Gnome.VFS.Monitor+import qualified System.Gnome.VFS.Init as VFS+import System.Glib.GError+import Text.ParserCombinators.Parsec (parse, parseFromFile)++import TypedBibData+import BibDB+import BibAttach+import Config+++{-+import Text.ParserCombinators.Parsec as Parsec hiding ...++instance Alternative (GenParser Char ()) where+    (<|>) = (Parsec.<|>)+    ...+-}++----------------------+-- GUI++main = do+  bib <- rightOrDie <$> loadBibliography+ +  VFS.init+  initGUI++  win <- windowNew++  model <- listStoreNewDND bib +             (Just (DragSourceIface draggable dataGet dataDelete)) +             (Just (DragDestIface possible recieved))++  onDestroy win $ do+         saveStore model +         VFS.shutdown+         mainQuit++  monitorAdd ("file://" ++ bibfile) (toEnum 0) $ \handle monitored changed evType -> do+         -- ugh: toEnum 0 == MonitorFile+         putStrLn $ "Bibfile changed; reloading " ++ show evType+         mBib <- loadBibliography+         case mBib of+           Left err -> putStr $ show err+           Right bib -> do oldBib <- listStoreToList model+                           when (bib /= oldBib) $ do+                             listStoreClear model+                             forM_ bib (listStoreAppend model)++  searchBox <- entryNew++  filt <- treeModelFilterNew model []+  treeModelFilterSetVisibleFunc filt $ Just $ \iter -> do+    needle <- get searchBox entryText+    let idx = listStoreIterToIndex iter+    e <- listStoreGetValue model idx+    return $ e `matchSearch` needle++ +  onEditableChanged searchBox $ treeModelFilterRefilter filt++  view <- New.treeViewNewWithModel filt++  New.treeViewSetHeadersVisible view True++  -- columns+  [col0,col1,col2] <- forM ["⎙","Cit","Title"] $ \h -> do +     c <- New.treeViewColumnNew+     New.treeViewColumnSetTitle c h+     return c+       +  treeViewColumnSetResizable col1 True+  treeViewColumnSetResizable col2 True++  [renderer0,renderer1,renderer2] <- forM [0..2] $ \_ -> New.cellRendererTextNew++  New.cellLayoutPackStart col0 renderer0 True+  New.cellLayoutPackStart col1 renderer1 True+  New.cellLayoutPackStart col2 renderer2 True++  Gtk.set col1 [treeViewColumnMinWidth := 180]++  forM [renderer1,renderer2] $ \r -> set r [ cellTextEllipsizeSet := True , cellTextEllipsize := EllipsizeEnd ]++  New.cellLayoutSetAttributes col0 renderer0 model $ \row -> [ New.cellText := if null $ findFullText row then "" else "✓"]+  New.cellLayoutSetAttributes col1 renderer1 model $ \row -> [ New.cellText := findCite row ]+  New.cellLayoutSetAttributes col2 renderer2 model $ \row -> [ New.cellText := renderTex $ findTitle row  ]++  {-+  Gtk.on renderer2 edited $ \path@(i:_) newText -> do+         (k,v) <- listStoreGetValue model path+         listStoreSetValue model path (k,newText)+  -}++  New.treeViewAppendColumn view col0+  New.treeViewAppendColumn view col1+  New.treeViewAppendColumn view col2 +  +  -- enable interactive search+  treeViewSetEnableSearch view True+  treeViewSetSearchEqualFunc view $ Just $ \str iter -> do+    p0 <- treeModelGetPath filt iter    +    [i] <- treeModelFilterConvertPathToChildPath filt p0+    entry <- listStoreGetValue model i+    return $ entry `matchSearch` str++  -- make the widget a drag source+  tl <- targetListNew+  targetListAddTextTargets tl 0+  treeViewEnableModelDragSource view [Button1] tl [ActionCopy]++  -- make the widget a drag destination+  tl <- targetListNew+  targetListAddUriTargets tl 0+  targetListAddTextTargets tl 1+  treeViewEnableModelDragDest view tl [ActionCopy]++  -- See the discussion on the gtk2hs-users list: +  -- Drag and drop support in filtered TreeView+  -- (July 2010)+  Gtk.on view dragDrop $ \ context point time -> do+    putStrLn $ "dragDrop"+    signalStopEmission view "drag_drop"+    Just target <- dragDestFindTarget view context (Just tl)+    -- print target+    dragStatus context Nothing time +    -- not so important as we do not need the data for motion: we never check this status.+    dragGetData view context target time+    return True++  Gtk.on view dragDataReceived $ \ context pt infoId time -> do+    liftIO $ putStrLn $ "dragData"+    liftIO $ signalStopEmission view "drag_data_received"+    pt' <- liftIO $ treeViewConvertWidgetToBinWindowCoords view pt+    mPath <- liftIO $ treeViewGetPathAtPos view pt'+    done <- case mPath of+      Nothing -> return False+      Just (p0,_,_) -> do+         p1 <- liftIO $ treeModelFilterConvertPathToChildPath filt p0+         recieved model p1 -- normally we'd call "possible" upon motion; but it's always possible.+    liftIO $ dragFinish context done False time++  let entryFromPath p0 = do+        [i] <- treeModelFilterConvertPathToChildPath filt p0+        listStoreGetValue model i+++  -- Handle "return"+  afterRowActivated view $ \path col -> do+    openFullText =<< entryFromPath path+    +  -- Handle clicks+  Gtk.on view buttonPressEvent $ do +    b <- eventButton+    c <- eventClick+    (x,y) <- eventCoordinates     +    let pt = (round x, round y)+    mPath <- liftIO $ treeViewGetPathAtPos view pt+    case mPath of+      Nothing -> return False+      Just (p0,_,_) -> liftIO $ do+         case (b,c) of+           (LeftButton,DoubleClick) -> do+              openFullText =<< entryFromPath p0+              return True+           (RightButton,DoubleClick) -> do+              -- Open emacs at the adequate line to edit.+              [i] <- treeModelFilterConvertPathToChildPath filt p0+              saveStore model+              before <- take i <$> listStoreToList model+              let lineNumber = length $ lines $ formatBib before+              runEditor lineNumber bibfile +              return True+           _ -> return False++  layout <- vBoxNew False 0+  scr <- scrolledWindowNew Nothing Nothing         +  containerAdd scr view+  boxPackStart layout searchBox PackNatural 0 +  boxPackStart layout scr PackGrow 0+  containerAdd win layout++  catchGError (windowSetIconFromFile win iconFile) $ \(GError dom code msg) -> do+      putStrLn $ "Could not load icon file: " ++ msg+  windowSetDefaultSize win 640 360+  widgetShowAll win+  mainGUI ++openFullText :: Entry -> IO ()+openFullText t = do+  let file = findFullText t+  case file of +    [] -> putStrLn "no pdf found!"+    (f:_) -> do putStrLn $ "Opening " ++ f+                runViewer f +                return ()+++draggable store [_] = return True+draggable store _ = return False++dataGet store [p] = do+  t <- liftIO $ listStoreGetValue store p+  let k = findNiceKey t+  selectionDataSetText $ "\\cite{"  ++ k ++ "}"++dataDelete _ _ = return True++receivedBib store text = do+  putStrLn $ "Got bib: "+  forM_ (lines text) putStrLn+  let mBib = parse parseBib "dropped text" text+  case mBib of+    Left err -> print err >> return False+    Right bib -> do print bib+                    mapM_ (listStorePrepend store) (bibToForest bib) +                    -- saveStore store+                    return True++recievedFile i store uri = do+  putStrLn $ "Downloading " ++ uri+  mFile <- openLazyURIWithOpts [CurlFollowLocation True] uri+  case mFile of+    Left err -> putStrLn err >> return False+    Right file -> do +      putStrLn "Success!"+      let fileType = guessType uri file+      if fileType == "bib" then receivedBib store (UTF8.toString file) else do+        entry <- listStoreGetValue store i+        putStrLn "Saving..."+        fname <- saveFile (findAttachName entry fileType) file+        putStrLn "Updating store"+        storeModify store i (addFile (fname, fileType))+        -- saveStore store+        return True++recieved store path@(i:_) = do+      liftIO $ putStrLn "recieved!"+      mURI <- selectionDataGetURIs+      case mURI :: Maybe [String] of+        -- Accept URI as download to link to the library+        -- Normally this stuff should be done in the background.+        -- I don't care.+        Just [uri] -> liftIO $ recievedFile i store uri+        _ -> do+          -- Accept drop of text as bibtex+          mText <- selectionDataGetText+          case mText :: Maybe String of+            Just text -> liftIO $ receivedBib store text+            _ -> return False++possible store path = do +  liftIO $ putStrLn "Possible?"+  return True -- accept all text & URI, a priori.++--------------------------------------+-- Store++storeModify store i f = do+  a <- listStoreGetValue store i+  listStoreSetValue store i (f a)++saveStore store = do+  putStrLn "Syncing on disk"+  bib <- listStoreToList store+  saveBibliography bib+
+ LICENSE view
@@ -0,0 +1,340 @@+		    GNU GENERAL PUBLIC LICENSE+		       Version 2, June 1991++ Copyright (C) 1989, 1991 Free Software Foundation, Inc.+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++			    Preamble++  The licenses for most software are designed to take away your+freedom to share and change it.  By contrast, the GNU General Public+License is intended to guarantee your freedom to share and change free+software--to make sure the software is free for all its users.  This+General Public License applies to most of the Free Software+Foundation's software and to any other program whose authors commit to+using it.  (Some other Free Software Foundation software is covered by+the GNU Library General Public License instead.)  You can apply it to+your programs, too.++  When we speak of free software, we are referring to freedom, not+price.  Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+this service if you wish), that you receive source code or can get it+if you want it, that you can change the software or use pieces of it+in new free programs; and that you know you can do these things.++  To protect your rights, we need to make restrictions that forbid+anyone to deny you these rights or to ask you to surrender the rights.+These restrictions translate to certain responsibilities for you if you+distribute copies of the software, or if you modify it.++  For example, if you distribute copies of such a program, whether+gratis or for a fee, you must give the recipients all the rights that+you have.  You must make sure that they, too, receive or can get the+source code.  And you must show them these terms so they know their+rights.++  We protect your rights with two steps: (1) copyright the software, and+(2) offer you this license which gives you legal permission to copy,+distribute and/or modify the software.++  Also, for each author's protection and ours, we want to make certain+that everyone understands that there is no warranty for this free+software.  If the software is modified by someone else and passed on, we+want its recipients to know that what they have is not the original, so+that any problems introduced by others will not reflect on the original+authors' reputations.++  Finally, any free program is threatened constantly by software+patents.  We wish to avoid the danger that redistributors of a free+program will individually obtain patent licenses, in effect making the+program proprietary.  To prevent this, we have made it clear that any+patent must be licensed for everyone's free use or not licensed at all.++  The precise terms and conditions for copying, distribution and+modification follow.++		    GNU GENERAL PUBLIC LICENSE+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION++  0. This License applies to any program or other work which contains+a notice placed by the copyright holder saying it may be distributed+under the terms of this General Public License.  The "Program", below,+refers to any such program or work, and a "work based on the Program"+means either the Program or any derivative work under copyright law:+that is to say, a work containing the Program or a portion of it,+either verbatim or with modifications and/or translated into another+language.  (Hereinafter, translation is included without limitation in+the term "modification".)  Each licensee is addressed as "you".++Activities other than copying, distribution and modification are not+covered by this License; they are outside its scope.  The act of+running the Program is not restricted, and the output from the Program+is covered only if its contents constitute a work based on the+Program (independent of having been made by running the Program).+Whether that is true depends on what the Program does.++  1. You may copy and distribute verbatim copies of the Program's+source code as you receive it, in any medium, provided that you+conspicuously and appropriately publish on each copy an appropriate+copyright notice and disclaimer of warranty; keep intact all the+notices that refer to this License and to the absence of any warranty;+and give any other recipients of the Program a copy of this License+along with the Program.++You may charge a fee for the physical act of transferring a copy, and+you may at your option offer warranty protection in exchange for a fee.++  2. You may modify your copy or copies of the Program or any portion+of it, thus forming a work based on the Program, and copy and+distribute such modifications or work under the terms of Section 1+above, provided that you also meet all of these conditions:++    a) You must cause the modified files to carry prominent notices+    stating that you changed the files and the date of any change.++    b) You must cause any work that you distribute or publish, that in+    whole or in part contains or is derived from the Program or any+    part thereof, to be licensed as a whole at no charge to all third+    parties under the terms of this License.++    c) If the modified program normally reads commands interactively+    when run, you must cause it, when started running for such+    interactive use in the most ordinary way, to print or display an+    announcement including an appropriate copyright notice and a+    notice that there is no warranty (or else, saying that you provide+    a warranty) and that users may redistribute the program under+    these conditions, and telling the user how to view a copy of this+    License.  (Exception: if the Program itself is interactive but+    does not normally print such an announcement, your work based on+    the Program is not required to print an announcement.)++These requirements apply to the modified work as a whole.  If+identifiable sections of that work are not derived from the Program,+and can be reasonably considered independent and separate works in+themselves, then this License, and its terms, do not apply to those+sections when you distribute them as separate works.  But when you+distribute the same sections as part of a whole which is a work based+on the Program, the distribution of the whole must be on the terms of+this License, whose permissions for other licensees extend to the+entire whole, and thus to each and every part regardless of who wrote it.++Thus, it is not the intent of this section to claim rights or contest+your rights to work written entirely by you; rather, the intent is to+exercise the right to control the distribution of derivative or+collective works based on the Program.++In addition, mere aggregation of another work not based on the Program+with the Program (or with a work based on the Program) on a volume of+a storage or distribution medium does not bring the other work under+the scope of this License.++  3. You may copy and distribute the Program (or a work based on it,+under Section 2) in object code or executable form under the terms of+Sections 1 and 2 above provided that you also do one of the following:++    a) Accompany it with the complete corresponding machine-readable+    source code, which must be distributed under the terms of Sections+    1 and 2 above on a medium customarily used for software interchange; or,++    b) Accompany it with a written offer, valid for at least three+    years, to give any third party, for a charge no more than your+    cost of physically performing source distribution, a complete+    machine-readable copy of the corresponding source code, to be+    distributed under the terms of Sections 1 and 2 above on a medium+    customarily used for software interchange; or,++    c) Accompany it with the information you received as to the offer+    to distribute corresponding source code.  (This alternative is+    allowed only for noncommercial distribution and only if you+    received the program in object code or executable form with such+    an offer, in accord with Subsection b above.)++The source code for a work means the preferred form of the work for+making modifications to it.  For an executable work, complete source+code means all the source code for all modules it contains, plus any+associated interface definition files, plus the scripts used to+control compilation and installation of the executable.  However, as a+special exception, the source code distributed need not include+anything that is normally distributed (in either source or binary+form) with the major components (compiler, kernel, and so on) of the+operating system on which the executable runs, unless that component+itself accompanies the executable.++If distribution of executable or object code is made by offering+access to copy from a designated place, then offering equivalent+access to copy the source code from the same place counts as+distribution of the source code, even though third parties are not+compelled to copy the source along with the object code.++  4. You may not copy, modify, sublicense, or distribute the Program+except as expressly provided under this License.  Any attempt+otherwise to copy, modify, sublicense or distribute the Program is+void, and will automatically terminate your rights under this License.+However, parties who have received copies, or rights, from you under+this License will not have their licenses terminated so long as such+parties remain in full compliance.++  5. You are not required to accept this License, since you have not+signed it.  However, nothing else grants you permission to modify or+distribute the Program or its derivative works.  These actions are+prohibited by law if you do not accept this License.  Therefore, by+modifying or distributing the Program (or any work based on the+Program), you indicate your acceptance of this License to do so, and+all its terms and conditions for copying, distributing or modifying+the Program or works based on it.++  6. Each time you redistribute the Program (or any work based on the+Program), the recipient automatically receives a license from the+original licensor to copy, distribute or modify the Program subject to+these terms and conditions.  You may not impose any further+restrictions on the recipients' exercise of the rights granted herein.+You are not responsible for enforcing compliance by third parties to+this License.++  7. If, as a consequence of a court judgment or allegation of patent+infringement or for any other reason (not limited to patent issues),+conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License.  If you cannot+distribute so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you+may not distribute the Program at all.  For example, if a patent+license would not permit royalty-free redistribution of the Program by+all those who receive copies directly or indirectly through you, then+the only way you could satisfy both it and this License would be to+refrain entirely from distribution of the Program.++If any portion of this section is held invalid or unenforceable under+any particular circumstance, the balance of the section is intended to+apply and the section as a whole is intended to apply in other+circumstances.++It is not the purpose of this section to induce you to infringe any+patents or other property right claims or to contest validity of any+such claims; this section has the sole purpose of protecting the+integrity of the free software distribution system, which is+implemented by public license practices.  Many people have made+generous contributions to the wide range of software distributed+through that system in reliance on consistent application of that+system; it is up to the author/donor to decide if he or she is willing+to distribute software through any other system and a licensee cannot+impose that choice.++This section is intended to make thoroughly clear what is believed to+be a consequence of the rest of this License.++  8. If the distribution and/or use of the Program is restricted in+certain countries either by patents or by copyrighted interfaces, the+original copyright holder who places the Program under this License+may add an explicit geographical distribution limitation excluding+those countries, so that distribution is permitted only in or among+countries not thus excluded.  In such case, this License incorporates+the limitation as if written in the body of this License.++  9. The Free Software Foundation may publish revised and/or new versions+of the General Public License from time to time.  Such new versions will+be similar in spirit to the present version, but may differ in detail to+address new problems or concerns.++Each version is given a distinguishing version number.  If the Program+specifies a version number of this License which applies to it and "any+later version", you have the option of following the terms and conditions+either of that version or of any later version published by the Free+Software Foundation.  If the Program does not specify a version number of+this License, you may choose any version ever published by the Free Software+Foundation.++  10. If you wish to incorporate parts of the Program into other free+programs whose distribution conditions are different, write to the author+to ask for permission.  For software which is copyrighted by the Free+Software Foundation, write to the Free Software Foundation; we sometimes+make exceptions for this.  Our decision will be guided by the two goals+of preserving the free status of all derivatives of our free software and+of promoting the sharing and reuse of software generally.++			    NO WARRANTY++  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,+REPAIR OR CORRECTION.++  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE+POSSIBILITY OF SUCH DAMAGES.++		     END OF TERMS AND CONDITIONS++	    How to Apply These Terms to Your New Programs++  If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these terms.++  To do so, attach the following notices to the program.  It is safest+to attach them to the start of each source file to most effectively+convey the exclusion of warranty; and each file should have at least+the "copyright" line and a pointer to where the full notice is found.++    <one line to give the program's name and a brief idea of what it does.>+    Copyright (C) <year>  <name of author>++    This program is free software; you can redistribute it and/or modify+    it under the terms of the GNU General Public License as published by+    the Free Software Foundation; either version 2 of the License, or+    (at your option) any later version.++    This program is distributed in the hope that it will be useful,+    but WITHOUT ANY WARRANTY; without even the implied warranty of+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+    GNU General Public License for more details.++    You should have received a copy of the GNU General Public License+    along with this program; if not, write to the Free Software+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+++Also add information on how to contact you by electronic and paper mail.++If the program is interactive, make it output a short notice like this+when it starts in an interactive mode:++    Gnomovision version 69, Copyright (C) year  name of author+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.+    This is free software, and you are welcome to redistribute it+    under certain conditions; type `show c' for details.++The hypothetical commands `show w' and `show c' should show the appropriate+parts of the General Public License.  Of course, the commands you use may+be called something other than `show w' and `show c'; they could even be+mouse-clicks or menu items--whatever suits your program.++You should also get your employer (if you work as a programmer) or your+school, if any, to sign a "copyright disclaimer" for the program, if+necessary.  Here is a sample; alter the names:++  Yoyodyne, Inc., hereby disclaims all copyright interest in the program+  `Gnomovision' (which makes passes at compilers) written by James Hacker.++  <signature of Ty Coon>, 1 April 1989+  Ty Coon, President of Vice++This General Public License does not permit incorporating your program into+proprietary programs.  If your program is a subroutine library, you may+consider it more useful to permit linking proprietary applications with the+library.  If this is what you want to do, use the GNU Library General+Public License instead of this License.
+ MaybeIO.hs view
@@ -0,0 +1,62 @@+module MaybeIO (+                MaybeIO,+                doesFileExist,+                renameFile, removeFile,+                putString,+                getDirectoryContents,+                uncheckedHarmless, safely,+                run,+               ) where++import qualified System.Directory as R+import Prelude hiding (putStrLn)+import qualified Prelude as R+import Control.Monad (ap,liftM)+import Control.Applicative++data MaybeIO a where+    Return :: a -> MaybeIO a+    (:>>=) :: MaybeIO a -> (a -> MaybeIO b) -> MaybeIO b+    Safe :: IO a -> MaybeIO a+    Harmful :: String -> IO () -> MaybeIO ()++instance Functor MaybeIO where+    fmap = liftM++instance Applicative MaybeIO where+    pure = Return+    (<*>) = ap++run :: Bool -> MaybeIO a -> IO a+run dry mio = run' mio+ where+  run' :: forall a. MaybeIO a -> IO a+  run' (Return a) = return a+  run' (a :>>= b) = do x <- run' a+                       run' (b x)+  run' (Safe i) = i+  run' (Harmful msg i) = if dry then R.putStrLn ('|':msg) else i++instance Monad MaybeIO where+    (>>=) = (:>>=)+    return = Return                     ++uncheckedHarmless = Safe+safely = Harmful+++doesFileExist f = uncheckedHarmless $ R.doesFileExist f++putString = uncheckedHarmless . R.putStrLn++getDirectoryContents = uncheckedHarmless . R.getDirectoryContents++renameFile :: String -> String -> MaybeIO ()+renameFile old new = do  +  safely ("RENAME: " ++ old ++ " TO " ++ new)+         (R.renameFile old new)++ +removeFile :: String -> MaybeIO ()+removeFile f = do+  safely ("DELETE: " ++ f) (R.removeFile f)
+ Setup.hs view
@@ -0,0 +1,4 @@+#!/usr/bin/env runhaskell+import Distribution.Simple+main :: IO ()+main = defaultMain
+ SuffixTreeCluster.hs view
@@ -0,0 +1,301 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  Data.SuffixTreeCluster+-- Copyright   :  (c) Bryan O'Sullivan 2007, JP Bernardy 2010+-- License     :  BSD-style+-- Maintainer  :  nobody+-- Stability   :  experimental+-- Portability :  portable+--+-- A suffix tree cluster implementation.+--+-- Since many function names (but not the type name) clash with+-- "Prelude" names, this module is usually imported @qualified@, e.g.+--+-- >  import Data.SuffixTreeCluster (STree)+-- >  import qualified Data.SuffixTreeCluster as T+--+-- This implementation constructs the suffix tree lazily, so subtrees+-- are not created until they are traversed.  +--+-- Estimates are given for performance.  The value /k/ is a constant;+-- /n/ is the length of a query string; and /t/ is the number of+-- elements (nodes and leaves) in a suffix tree.++module SuffixTreeCluster+    (+    -- * Types+      Alphabet+    , Edge+    , Prefix+    , STree(..)++    -- * Construction+    , construct++    -- * Querying+    , elem+    , findEdge+    , findTree+    , findPath++    -- * Other useful functions+    , mkPrefix+    , prefix+    , suffixes+    , select+    , commonStrings++    -- * Debug+    , printTree+    ) where+                   +import Prelude hiding (elem, foldl, foldr)+import qualified Data.Map as M+import Control.Arrow (second)+import qualified Data.ByteString as SB+import qualified Data.ByteString.Lazy as LB+import qualified Data.List as L+import Data.Maybe (listToMaybe, mapMaybe, catMaybes)+import Data.Monoid hiding (Sum)+-- import Text.Groom+import Data.Function (on)+import qualified Data.Tree as T++-- | The length of a prefix list.  This type is formulated to do cheap+-- work eagerly (to avoid constructing a pile of deferred thunks),+-- while deferring potentially expensive work (computing the length of+-- a list).+data Length a = Exactly {-# UNPACK #-} !Int+              | Sum {-# UNPACK #-} !Int [a]+              deriving (Show)++-- | The list of symbols that 'constructWith' can possibly see in its+-- input.+type Alphabet a = [a]++-- | The prefix string associated with an 'Edge'.  Use 'mkPrefix' to+-- create a value of this type, and 'prefix' to deconstruct one.+newtype Prefix a = Prefix ([a], Length a)++instance (Eq a) => Eq (Prefix a) where+    a == b = prefix a == prefix b++instance (Ord a) => Ord (Prefix a) where+    compare a b = compare (prefix a) (prefix b)++instance (Show a) => Show (Prefix a) where+    show a = "mkPrefix " ++ show (prefix a)++-- | An edge in the suffix tree.+type Edge a b = (Prefix a, STree a b)++-- | /O(1)/. Construct a 'Prefix' value.+mkPrefix :: [a] -> Prefix a+mkPrefix xs = Prefix (xs, Sum 0 xs)++pmap :: (a -> b) -> Prefix a -> Prefix b+pmap f = mkPrefix . map f . prefix++instance Functor Prefix where+    fmap = pmap++-- | The suffix tree type.  The implementation is exposed to ease the+-- development of custom traversal functions.  Note that @('Prefix' a,+-- 'STree' a b)@ pairs are not stored in any order.+data STree a b = Node b [Edge a b]+               deriving (Show)++smap :: (a -> b) -> STree a c -> STree b c+smap f (Node b es) = Node b (map (\(p, t) -> (fmap f p, smap f t)) es)++lmap :: (a -> b) -> STree c a -> STree c b+lmap f (Node b es) = Node (f b) (map (second (lmap f)) es)++instance Functor (STree a) where+    fmap = lmap++-- | /O(n)/. Obtain the list stored in a 'Prefix'.+prefix :: Prefix a -> [a]+prefix (Prefix (ys, Exactly n)) = take n ys+prefix (Prefix (ys, Sum n xs)) = tk n ys+    where tk 0 ys = zipWith (const id) xs ys+          tk n (y:ys) = y : tk (n-1) ys++++-- | Increments the length of a prefix.+inc :: Length a -> Length a+inc (Exactly n) = Exactly (n+1)+inc (Sum n xs)  = Sum (n+1) xs++-- | /O(n)/. Returns all non-empty suffixes of the argument, longest+-- first.  Behaves as follows:+--+-- >suffixes xs == init (tails xs)+suffixes :: [a] -> [[a]]+suffixes (xs@(_:xs')) = (xs) : suffixes (xs')+suffixes _ = []++constr :: (Eq b, Monoid b, Ord a) => [(b,[[a]])] -> STree a b+constr is = Node label (suf xs)+    where xs = filter (not . null . snd) $ map (second (filter (not . null))) is+          label = (mconcat $ map fst is)++construct :: (Eq b, Monoid b, Ord a) => [([a],b)] -> STree a b+construct is = constr [(b,L.tails a) | (a,b) <- is]++suf :: (Eq b, Monoid b, Ord a) => [(b,[[a]])] -> [Edge a b]+suf ss = [(Prefix (a:sa, inc cpl), constr ssr)+              | (a, n@((_,sa:_):_)) <- suffixMap ss,+              let (cpl,ssr) = cst n]++regroup :: (Eq b) => [(b, a)] -> [(b, [a])]+regroup xs = [(b,map snd as) | as@((b,_):_) <- L.groupBy ((==) `on` fst) xs]++-- ++suffixMap :: (Eq b, Ord a) => [(b,[[a]])] -> [(a, [(b, [[a]])])]+suffixMap sss = map (second (regroup . reverse)) .+                M.toList . L.foldl' step M.empty $ [(b,s) | (b,ss) <- sss, s <- ss]+    where step m (b,x:xs) = M.alter (f (b,xs)) x m+          step m _ = m+          f x Nothing = Just [x]+          f x (Just xs) = Just (x:xs)++cst :: (Monoid b, Eq b, Eq a) => [(b, [[a]])] -> (Length a, [(b, [[a]])])++cst bss | or [null s | (_,ss) <- bss, s <- ss] = (Exactly 0, bss)+cst ss0@((_,[s]):ss) | all ((== [s]) . snd) ss = (Sum 0 s, [(b, []) | (b,_) <- ss0])+cst awss@((_,(a:w):_):ss)+    | null [c | (_,x) <- ss, (c:_) <- x, a /= c] +        = let cpl' = inc cpl+          in seq cpl' (cpl', rss)+    | otherwise = (Exactly 0, awss)+    where (cpl, rss) = cst $ map (second (map tail')) awss+          tail' (_:t) = t -- so we see the pattern fail here instead of a generic tail.+++suffix :: (Eq a) => [a] -> [a] -> Maybe [a]+suffix (p:ps) (x:xs) | p == x = suffix ps xs+                     | otherwise = Nothing+suffix _ xs = Just xs++{-# SPECIALISE elem :: [Char] -> STree Char b -> Bool #-}+{-# SPECIALISE elem :: [[Char]] -> STree [Char] b -> Bool #-}+{-# SPECIALISE elem :: [SB.ByteString] -> STree SB.ByteString b -> Bool #-}+{-# SPECIALISE elem :: [LB.ByteString] -> STree LB.ByteString b -> Bool #-}+{-# SPECIALISE elem :: (Eq a) => [[a]] -> STree [a] b -> Bool #-}++-- | /O(n)/.  Indicates whether the suffix tree contains the given+-- sublist.  Performance is linear in the length /n/ of the+-- sublist.+elem :: (Eq a) => [a] -> STree a b -> Bool+elem [] _ = True+elem xs (Node mb es) = any pfx es+    where pfx (e, t) = maybe False (`elem` t) (suffix (prefix e) xs)++lkup :: (Monoid b, Eq a) => [a] -> STree a b -> b+lkup [] (Node b es) = b+lkup xs (Node _ es) = mconcat $ map pfx es+    where pfx (e, t) = maybe mempty (`lkup` t) (suffix (prefix e) xs)++{-# SPECIALISE findEdge :: [Char] -> STree Char b+                        -> Maybe (Edge Char b, Int) #-}+{-# SPECIALISE findEdge :: [String] -> STree String b+                        -> Maybe (Edge String b, Int) #-}+{-# SPECIALISE findEdge :: [SB.ByteString] -> STree SB.ByteString b+                        -> Maybe (Edge SB.ByteString b, Int) #-}+{-# SPECIALISE findEdge :: [ LB.ByteString] -> STree LB.ByteString b+                        -> Maybe (Edge LB.ByteString b, Int) #-}+{-# SPECIALISE findEdge :: (Eq a) => [[a]] -> STree [a] b+                        -> Maybe (Edge [a] b, Int) #-}++-- | /O(n)/.  Finds the given subsequence in the suffix tree.  On+-- failure, returns 'Nothing'.  On success, returns the 'Edge' in the+-- suffix tree at which the subsequence ends, along with the number of+-- elements to drop from the prefix of the 'Edge' to get the \"real\"+-- remaining prefix.+--+-- Here is an example:+--+-- >> find "ssip" (construct "mississippi")+-- >Just ((mkPrefix "ppi",Leaf),1)+--+-- This indicates that the edge @('mkPrefix' \"ppi\",'Leaf')@ matches,+-- and that we must strip 1 character from the string @\"ppi\"@ to get+-- the remaining prefix string @\"pi\"@.+--+-- Performance is linear in the length /n/ of the query list.+findEdge :: (Eq a) => [a] -> STree a b -> Maybe (Edge a b, Int)+findEdge xs (Node mb es) = listToMaybe (mapMaybe pfx es)+    where pfx e@(p, t) = let p' = prefix p+                         in suffix p' xs >>= \suf ->+            case suf of+              [] -> return (e, length (zipWith const xs p'))+              s -> findEdge s t++-- | /O(n)/. Finds the subtree rooted at the end of the given query+-- sequence.  On failure, returns 'Nothing'.+--+-- Performance is linear in the length /n/ of the query list.+findTree :: (Eq a) => [a] -> STree a b -> Maybe (STree a b)+findTree s t = (snd . fst) `fmap` findEdge s t++-- | /O(n)/. Returns the path from the 'Edge' in the suffix tree at+-- which the given subsequence ends, up to the root of the tree.  If+-- the subsequence is not found, returns the empty list.+--+-- Performance is linear in the length of the query list.+findPath :: (Eq a) => [a] -> STree a b -> [Edge a b]+findPath = go []+    where go me xs (Node mb es) = pfx me es+              where pfx _ [] = []+                    pfx me (e@(p, t):es) =+                        case suffix (prefix p) xs of+                          Nothing -> pfx me es+                          Just [] -> e:me+                          Just s -> go (e:me) s t+++-- select :: SC.STree a b -> Tree Int+selectPairs (p0,Node entries sub) = T.Node (p0,entries) $+              [select (p0 ++ prefix p,n) | (p,n) <- sub, isRelevantNode n]++selectLen (T.Node (shared,entries) sub) = T.Node here (map selectLen sub)+   where here | length shared < 7 = ([],mempty) +              | otherwise = (shared,entries)+++selectLongest (T.Node (shared,entries) sub) = T.Node here (map selectLongest sub)+   where here | subsumed = ([],[]) +              | otherwise = (shared,entries)+         subsumed = all (`L.elem` (concatMap (snd . T.rootLabel) sub)) entries +    ++select :: Eq b => ([a],STree a [b]) -> T.Tree ([a],[b])+select = selectLongest . selectLen . selectPairs++buildMap :: (Eq a, Ord b) => T.Tree ([a],[b]) -> M.Map [b] [[a]]+buildMap t = L.foldl' step M.empty $ filter (not . null . snd) $ T.flatten t+    where step m (x,bs) = M.alter (lub x) bs m++commonStrings :: (Eq b, Ord a, Ord b) => [([a],[b])] -> M.Map [b] [[a]]+commonStrings xs = buildMap $ select ([],construct xs)++lub y Nothing = Just [y]+lub y (Just xs)+    | (y `L.isSuffixOf`) `any` xs = Just xs+    | otherwise = Just (y : filter (not . (`L.isSuffixOf` y)) xs)++printTree :: Show a => T.Tree a -> IO ()+printTree = putStrLn . T.drawTree . fmap show++isRelevant [] = False+isRelevant [_] = False+isRelevant _ = True++isRelevantNode (Node e _) = isRelevant e++emptyPrefix :: Prefix a+emptyPrefix = mkPrefix []
+ TypedBibData.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE RecordWildCards, TupleSections  #-}++module TypedBibData where++import Control.Applicative hiding ((<|>),many)+import Control.Monad+import Control.Monad.Trans+import Data.Char+import Data.List+import Data.List.Split+import Data.Maybe+import Data.Tree+import Text.BibTeX.Entry as Entry+import Text.BibTeX.Parse+import Data.Function+import Text.ParserCombinators.Parsec as Parsec+++-------------------------------------+-- Bib Data manipulation++data Entry = Entry {+      kind :: String,+      authors :: [(String,String)],+      files :: [(String,String)],      -- ^ name, type+      seeAlso :: [(String,String)],+      otherFields :: [(String,String)]+    } deriving (Show, Eq, Ord)+++renderTex = filter (`notElem` "{}")++sanitizeIdent = filter (\c -> (c >= 'a' && c <= 'z') || isDigit c || c `elem` ".-_?+") . +                map ((\c -> if isSpace c then '_' else c) . toLower)+++parseBib = skippingSpace $ skippingLeadingSpace Text.BibTeX.Parse.file++pAuth :: Parser (String, String)+pAuth = do+  many Parsec.space+  p1 <- pAuthNamePart `Parsec.sepBy` (many Parsec.space)+  do Parsec.char ','+     many Parsec.space+     first <- pAuthName+     return (first, intercalate " " p1)+    <|> let (first,[last]) = splitAt (length p1 - 1) p1 in+            return (intercalate " " first, last)+      +pAuthName = concat <$> many (pAuthBlock True)+pAuthNamePart = concat <$> many1 (pAuthBlock False)++pAuthBlock :: Bool -> Parser String+pAuthBlock spaceOk =+   liftM3 (\open body close -> open : body ++ close : [])+      (Parsec.char '{') pAuthName (Parsec.char '}') <|>+   sequence+      [Parsec.char '\\',+       Parsec.oneOf "{}'`^&%\".,~# " <|> Parsec.letter] <|>+   fmap (:[]) (Parsec.noneOf $ [' ' | not spaceOk] ++ "},")+++-- When searching ignore special characters+project = map toLower . filter isAlphaNum++-- | Does a node contain a string (for search)+contains :: Entry -> String -> Bool+contains t needle = or [needle `isInfixOf` project txt | txt <- findTitle t : map snd (authors t)]++matchSearch entry pattern =  all (contains entry) (map project $ words pattern)  ++findCiteAuth :: Entry -> String+findCiteAuth Entry {..} = renderTex $ case map snd authors of+      [] -> "????"+      [a] -> a+      [a,b] -> a ++ " and " ++ b+      [a,b,c] -> a ++ ", " ++ b ++ " and " ++ c+      (a:_) -> a ++ " et al."++findYear :: Entry -> String+findYear = findField "year" ++findTitle :: Entry -> String+findTitle = findField "title"++findField :: String -> Entry -> String+findField f t = findField' f t ?? "????"++findField' f  Entry {..} = map snd (filter ((== f) . fst) otherFields)++findFirstAuthor :: Entry -> String+findFirstAuthor Entry{..} = map snd authors ?? "????"++findCite t = findCiteAuth t ++ " " ++ findYear t+findNiceKey t = findField' "forcedkey" t ?? +                (intercalate "_" $ map sanitizeIdent $ [findFirstAuthor t, title, findYear t])+    where title = ((filter ((> 1) . length) . map sanitizeIdent . words . findTitle $ t) +                   \\ ["for","le","an","to","be","on","make","the","how","why","its","from","towards"])+                  ?? "????"++findFullText Entry {..} = map fst . filter ((`elem` ["pdf","ps"]) . snd) $ files++addFile f (Entry {..})= Entry {files = f:files,..}++partitions :: [a -> Bool] -> [a] -> [[a]]+partitions [] l = [l]+partitions (x:xs) l = yes : partitions xs no+    where (yes,no) = partition x l++entryToTree :: Entry.T -> Entry+entryToTree Entry.Cons{..} = Entry {..}+  where+    [auths,fils,seeAlsos,otherFields] = partitions (map (\k -> (k ==) . fst) ["author","file","see"]) fields+    kind = entryType+    authors = [authorToTree a | (_,as) <- auths, a <- splitOn " and " as]+    ident = identifier+    files = [fileToTree f | (_,fs) <- fils, f <- splitOn ";" fs]+    seeAlso = [seeAlsoToTree r | (_,rs) <- seeAlsos, not $ null rs, r <- splitOn ";" rs ]++treeToEntry :: Entry -> Entry.T+treeToEntry t@Entry {..} = Entry.Cons{..}+   where fields = ("author", intercalate " and " [first ++ " " ++ last | (first,last) <- authors]) :+                  [("file",intercalate ";" [":" ++ f ++ ":" ++ t | (f,t) <- files]) | not $ null files] +++                  otherFields +++                  [("see",intercalate ";" [how ++ ":" ++ what | (how,what) <- seeAlso]) | not $ null seeAlso]+         entryType = kind+         identifier = findNiceKey t ++fileToTree (':':fs) = (f, t) +    where (f,':':t) = break (== ':') fs++authorToTree :: String -> (String,String)+authorToTree s = case parse pAuth ("in " ++ s) s of+                            Left err -> error $ show err+                            Right r -> r++seeAlsoToTree r = (how,what)+    where (how,':':what) = break (== ':') r++l ?? b = head $ l ++ [b]++bibToForest = map entryToTree ++formatEntry :: Entry.T -> String+formatEntry (Entry.Cons entryType bibId items) =+   let formatItem (name, value) =+         "\t"++name++" = {"++value++"}"+   in  "@" ++ entryType ++ "{" ++ bibId ++ ",\n" +++       intercalate ",\n" (map formatItem items) +++       "\n},\n\n"+++e1 `isSeeAlso` e2 = findNiceKey e1 `elem` (map snd (seeAlso e2))++areRelated e1 e2 = e1 == e2 || e1 `isSeeAlso` e2 || e2 `isSeeAlso` e1
+ icon.svg view
@@ -0,0 +1,117 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!-- Created with Inkscape (http://www.inkscape.org/) -->++<svg+   xmlns:dc="http://purl.org/dc/elements/1.1/"+   xmlns:cc="http://creativecommons.org/ns#"+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+   xmlns:svg="http://www.w3.org/2000/svg"+   xmlns="http://www.w3.org/2000/svg"+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+   width="128"+   height="128"+   id="svg2"+   version="1.1"+   inkscape:version="0.47 r22583"+   sodipodi:docname="icon.svg">+  <defs+     id="defs4">+    <marker+       inkscape:stockid="Arrow2Lstart"+       orient="auto"+       refY="0.0"+       refX="0.0"+       id="Arrow2Lstart"+       style="overflow:visible">+      <path+         id="path3617"+         style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round"+         d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "+         transform="scale(1.1) translate(1,0)" />+    </marker>+    <inkscape:perspective+       sodipodi:type="inkscape:persp3d"+       inkscape:vp_x="0 : 526.18109 : 1"+       inkscape:vp_y="0 : 1000 : 0"+       inkscape:vp_z="744.09448 : 526.18109 : 1"+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"+       id="perspective10" />+    <inkscape:perspective+       id="perspective4236"+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"+       inkscape:vp_z="1 : 0.5 : 1"+       inkscape:vp_y="0 : 1000 : 0"+       inkscape:vp_x="0 : 0.5 : 1"+       sodipodi:type="inkscape:persp3d" />+    <inkscape:perspective+       id="perspective4302"+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"+       inkscape:vp_z="1 : 0.5 : 1"+       inkscape:vp_y="0 : 1000 : 0"+       inkscape:vp_x="0 : 0.5 : 1"+       sodipodi:type="inkscape:persp3d" />+  </defs>+  <sodipodi:namedview+     id="base"+     pagecolor="#ffffff"+     bordercolor="#666666"+     borderopacity="1.0"+     inkscape:pageopacity="0.0"+     inkscape:pageshadow="2"+     inkscape:zoom="2.8"+     inkscape:cx="67.772195"+     inkscape:cy="78.158112"+     inkscape:document-units="px"+     inkscape:current-layer="layer1"+     showgrid="false"+     inkscape:window-width="875"+     inkscape:window-height="894"+     inkscape:window-x="302"+     inkscape:window-y="24"+     inkscape:window-maximized="0" />+  <metadata+     id="metadata7">+    <rdf:RDF>+      <cc:Work+         rdf:about="">+        <dc:format>image/svg+xml</dc:format>+        <dc:type+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+        <dc:title />+      </cc:Work>+    </rdf:RDF>+  </metadata>+  <g+     inkscape:label="Layer 1"+     inkscape:groupmode="layer"+     id="layer1"+     transform="translate(0,-924.36218)">+    <rect+       style="opacity:0.85470085;fill:#ffffff;fill-opacity:1;stroke-width:6;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none"+       id="rect3615"+       width="176.42857"+       height="151.42857"+       x="-21.785715"+       y="-14.857142"+       transform="translate(0,924.36218)" />+    <path+       style="color:#000000;fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"+       d="M 4.6428571,1000.2193 C 46.556965,956.20726 58.658789,945.8826 70,939.14789 c 9.150772,-5.71379 43.04351,-12.93641 49.28571,-8.57142 2.89087,6.13888 -0.45116,39.83088 -5.71428,46.07142 -5.69338,6.98443 -22.163439,29.48731 -69.642859,68.92861"+       id="path3594"+       sodipodi:nodetypes="ccccc" />+    <rect+       style="opacity:0.91025636;fill:#f1f1d7;fill-opacity:1;stroke-width:3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none"+       id="rect4250"+       width="84.394226"+       height="157.64793"+       x="30.029348"+       y="904.68732"+       transform="matrix(0.98813171,-0.15360902,0.02892918,0.99958146,0,0)" />+    <path+       style="color:#000000;fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"+       d="M 43.411265,1045.6866 C 35.523964,1037.051 10.382737,1014.6929 9.8398358,1006.0438 22.760511,995.91675 31.429608,981.75489 51.982693,966.40096 c 28.190874,-21.05966 33.459501,-17.12176 40.714287,-10 3.844479,3.77398 7.60489,22.06338 -3.571429,35.35714 -16.021392,19.0567 -29.334821,28.9776 -45,44.6428"+       id="path3594-3"+       sodipodi:nodetypes="ccsssc" />+  </g>+</svg>
+ imbib.cabal view
@@ -0,0 +1,61 @@+name:           imbib+version:        1.0.0+category:       Text+synopsis:       Minimalistic reference manager.+description:    +                The tool to facilitates the workflow: webbrowser -> bibtex file -> directory of .pdfs. The design is minimalistic (eg. no editor is included; emacs can be fired up by double right-clicking an entry).+                +license:        GPL+license-file:   LICENSE+author:         Jean-Philippe Bernardy+maintainer:     jeanphilippe.bernardy@gmail.com+Cabal-Version:  >= 1.8+tested-with:    GHC==6.12.2+build-type:     Simple++data-files:+  icon.svg+  .imbib++executable imbib+  main-is: Imbibed.hs+  extensions: TypeSynonymInstances, RecordWildCards, FlexibleInstances+  build-depends: base==4.*+  build-depends: mtl==1.1.*+  build-depends: parsec==2.1.*+  build-depends: process==1.0.*+  build-depends: filepath==1.1.*+  build-depends: directory==1.0.*+  build-depends: containers==0.*+  build-depends: bibtex==0.*+  build-depends: split==0.*+  build-depends: gtk==0.11.*+  build-depends: glib==0.11.*+  build-depends: download-curl==0.*+  build-depends: curl==1.3.*+  build-depends: bytestring==0.*+  build-depends: gnomevfs==0.11.*+  build-depends: utf8-string==0.3.*+  build-depends: ConfigFile==1.0.*+  +  other-modules:  +     BibDB Config MaybeIO TypedBibData BibAttach  BibFile  SuffixTreeCluster+++executable imbibatch+  main-is: Batch.hs+  extensions: TypeSynonymInstances, RecordWildCards, FlexibleInstances, RankNTypes, GADTs+  build-depends: base==4.*+  build-depends: mtl==1.1.*+  build-depends: parsec==2.1.*+  build-depends: process==1.0.*+  build-depends: filepath==1.1.*+  build-depends: directory==1.0.*+  build-depends: containers==0.*+  build-depends: bibtex==0.*+  build-depends: split==0.*+  build-depends: bytestring==0.*+  build-depends: ConfigFile==1.0.*+  ++