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 +6/−0
- Batch.hs +151/−0
- BibAttach.hs +50/−0
- BibDB.hs +41/−0
- BibFile.hs +39/−0
- Config.hs +38/−0
- Imbibed.hs +296/−0
- LICENSE +340/−0
- MaybeIO.hs +62/−0
- Setup.hs +4/−0
- SuffixTreeCluster.hs +301/−0
- TypedBibData.hs +155/−0
- icon.svg +117/−0
- imbib.cabal +61/−0
+ .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.*+ ++