packages feed

mathgenealogy 1.2.0 → 1.3.0

raw patch · 6 files changed

+326/−137 lines, 6 filesdep +binarydep +filepathdep ~directory

Dependencies added: binary, filepath

Dependency ranges changed: directory

Files

+ CmdParams.hs view
@@ -0,0 +1,84 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  CmdParams+-- Copyright   :  (C) Peter Robinson 2010-2013+-- License     :  GPL-2+--+-- Maintainer  :  Peter Robinson <thaldyron@gmail.com>+-- Stability   :  experimental+-- Portability :  portable +-- +-- Contains the command line parameter definitions.+--+-----------------------------------------------------------------------------+module CmdParams+where+import System.Console.CmdArgs++data OutputFile = FileSVG | FilePDF | FilePNG +  deriving(Eq,Data,Typeable)++instance Show OutputFile where+  show FileSVG = ".svg"+  show FilePDF = ".pdf"+  show FilePNG = ".png"++outputFileToGraphviz :: OutputFile -> String+outputFileToGraphviz FileSVG = " -Tsvg "+outputFileToGraphviz FilePDF = " -Tpdf "+outputFileToGraphviz FilePNG = " -Tpng "+++instance Default OutputFile where+  def = FilePDF++-- | Command line arguments:+data MathGenealogy = MathGenealogy +  { prefix              :: String +  , keepDotFile         :: Bool+  , graphvizArgs        :: String+--  , onlyDotFile         :: Bool+  , verbose             :: Bool+  , includeTheses       :: Bool+  , startURL            :: String+  , outputFile          :: OutputFile+  , font                :: String+  , fontHeading         :: String+  , fontSize            :: Double+  , fontSizeHeading     :: Double+  , fontSizeFirst       :: Double+  , fontSizeFirstHeading:: Double+  , nodeBackgroundColor :: String+  , edgeColor           :: String+  , oldTextLabels       :: Bool+  }+  deriving(Eq,Data,Typeable,Show)+++mathGenealogy :: MathGenealogy+mathGenealogy = MathGenealogy+  { startURL     = def &= args &= typ "URL" +  , prefix       = "output" &= typ "PREFIX" +  , keepDotFile  = False +  , graphvizArgs = " -Gcharset=utf8 " &= typ "<graphviz parameters>" -- &= opt " -Gcharset=utf8 "+--  , onlyDotFile  = False &= help "Only create a GraphViz '.dot' file."+  , verbose      = False &= help "Print data to terminal."+  , includeTheses= False &= help "Include PhD thesis in output"+  , outputFile   = enum [ FileSVG &= help "create SVG file (default - best supported by GraphViz; use this if you run into issues.)"+                        , FilePDF &= help "create PDF file"+                        , FilePNG &= help "create PNG file"+                        ]+  , font         = "Helvetica" &= help "Fonts included with GraphViz (see http://www.graphviz.org/doc/fontfaq.txt): AvantGarde-Book AvantGarde-BookOblique AvantGarde-Demi AvantGarde-DemiOblique Bookman-Demi Bookman-DemiItalic Bookman-Light Bookman-LightItalic Courier Courier-Bold Courier-BoldOblique Courier-Oblique Helvetica Helvetica-Bold Helvetica-BoldOblique Helvetica-Narrow Helvetica-Narrow-Bold Helvetica-Narrow-BoldOblique Helvetica-Narrow-Oblique Helvetica-Oblique NewCenturySchlbk-Bold NewCenturySchlbk-BoldItalic NewCenturySchlbk-Italic NewCenturySchlbk-Roman Palatino-Bold Palatino-BoldItalic Palatino-Italic Palatino-Roman Symbol Times-Bold Times-BoldItalic Times-Italic Times-Roman ZapfChancery-MediumItalic ZapfDingbats  "+  , fontSize             = 12 &= help "Font size of the additional information of all entries except the first one."+  , fontHeading          = "Helvetica" &= help "Font used for names"+  , fontSizeHeading      = 16 &= help "Font size used for names"+  , fontSizeFirstHeading = 20 &= help "Font size of first entry."+  , fontSizeFirst        = 16 &= help "Font size of first entry."+  , nodeBackgroundColor  = "LightBlue1" &= help "Node background color. See http://hackage.haskell.org/packages/archive/graphviz/2999.15.0.1/doc/html/Data-GraphViz-Attributes-Colors-X11.html"+  , edgeColor            = "Gray" &= help "Edge color. See http://hackage.haskell.org/packages/archive/graphviz/2999.15.0.1/doc/html/Data-GraphViz-Attributes-Colors-X11.html"+  , oldTextLabels        = False &= help "Fall back to non-HTML text labels. Use this if you are having issues with the rendering of the HTML-like labels. Note that all font parameters except 'font', 'fontsize', and 'fontsizefirst' are ignored with this setting."+  } &= summary "Mathematics Genealogy Visualizer (C) 2010-2013 Peter Robinson thaldyron@gmail.com" +    &= details[ "Run the program with a start-URL, for example:"+              ,"# mathgenealogy http://genealogy.math.ndsu.nodak.edu/id.php?id=18231"+              ,"Disclaimer: 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 (version 2) for full details."+              ]
Entry.hs view
@@ -15,16 +15,18 @@ import Data.List(intercalate) import Data.Text.Lazy(Text) import qualified Data.Text.Lazy as T+import qualified Data.GraphViz.Attributes.HTML as GA+import Data.Text.Lazy.Encoding +import Data.Binary+import Control.Monad(liftM)  data Entry = Entry    { entryName :: Text    , entryGraduationInfo :: [GraduationInfo]   , entryAdvisors :: [(Text,Text)]   }-  deriving(Ord,Eq)+  deriving(Eq,Ord) -urlAdvisors :: Entry -> [Text]-urlAdvisors = map (T.pack . (++) "http://genealogy.math.ndsu.nodak.edu/" . T.unpack . snd) . entryAdvisors  data GraduationInfo = GraduationInfo   { gradDegree :: Maybe Text@@ -32,22 +34,42 @@   , gradYear :: Maybe Text   , gradThesis :: Maybe Text   }-  deriving(Ord,Eq)+  deriving(Eq,Ord) -entryToText :: Entry -> Text-entryToText e =-    entryName e `T.append` (T.concat $ map graduationInfoToText (entryGraduationInfo e))-                `T.append` (T.concat $ map fst (entryAdvisors e))+instance Binary Text where+    put = put . encodeUtf8+    get = liftM decodeUtf8 get +instance Binary Entry where+        put (Entry x1 x2 x3) = put x1 >> put x2 >> put x3+        get = do +            x1 <- get+            x2 <- get+            x3 <- get+            return $ Entry x1 x2 x3+ +instance Binary GraduationInfo where+        put (GraduationInfo x1 x2 x3 x4) = put x1 >> put x2 >> put x3 >> put x4+        get = do +            x1 <- get+            x2 <- get+            x3 <- get+            x4 <- get+            return $ GraduationInfo x1 x2 x3 x4++++urlAdvisors :: Entry -> [Text]+urlAdvisors = map (T.pack . (++) "http://genealogy.math.ndsu.nodak.edu/" . T.unpack . snd) . entryAdvisors++ instance Show Entry where   show e =      let ginfos = concatMap show (entryGraduationInfo e) in     T.unpack (entryName e)-      ++ if ginfos /= [] then "\n" ++ ginfos else ""-    where -      maybeShow (Just g) = "\\n" ++ show g-      maybeShow Nothing  = ""+      ++ if length ginfos > 2 then "\\n" ++ ginfos else "" + instance Show GraduationInfo where   show g =      let ls  = map T.unpack $ catMaybes [gradDegree g, gradUniversity g, gradYear g] in@@ -56,19 +78,54 @@       maybeShow (Just gi) = T.unpack gi        maybeShow Nothing   = ""  ++-- | Transform an `Entry` to plaintext. +entryToText :: Entry -> Text+entryToText e =+  let ginfos = T.intercalate (T.pack "\\n") $ map graduationInfoToText (entryGraduationInfo e) in+  entryName e `T.append` (if T.length ginfos > 2 then T.pack "\\n" `T.append` ginfos else T.pack "")++ graduationInfoToText :: GraduationInfo -> Text graduationInfoToText g = -    let ls  = catMaybes [gradDegree g, gradUniversity g, gradYear g] in-    T.intercalate (T.pack ", ") ls  `T.append` (T.pack "\\n") `T.append` (maybeText (gradThesis g))-    where-      maybeText (Just gi) = gi -      maybeText Nothing   = T.pack "" +  let ls  = catMaybes [gradDegree g, gradUniversity g, gradYear g] in+  T.intercalate (T.pack ", ") ls  `T.append` (maybeText (gradThesis g))+  where+    maybeText (Just gi) = T.pack "\\n" `T.append` gi +    maybeText Nothing   = T.pack ""   +-- | Transform an `Entry` to an HTML-like label.+entryToHtml :: GA.Attributes  -- ^ table attributes+            -> GA.Attributes  -- ^ font options for header+            -> GA.Attributes  -- ^ font options for body text+            -> Entry+            -> GA.Label+entryToHtml tableAtts headingAtts fontAtts e = +  let ginfos = intercalate [GA.Newline []] $ map graduationInfoToHtml (entryGraduationInfo e) in+  GA.Table $ GA.HTable Nothing tableAtts $+      (GA.Cells [GA.LabelCell [GA.Align GA.HCenter] $ +          GA.Text [GA.Font headingAtts [GA.Str (entryName e)]]]) :+              [GA.Cells [GA.LabelCell [] $ GA.Text [GA.Font fontAtts ginfos]]+              | not $ null ginfos ]+++graduationInfoToHtml :: GraduationInfo -> [GA.TextItem]+graduationInfoToHtml g = +  let ls     = catMaybes [gradDegree g, gradUniversity g, gradYear g] +      thesis = maybeText (gradThesis g) in+  case (null ls,isNothing $ gradThesis g) of +     (True,True)   -> []+     (True,False)  -> [thesis] +     (False,True)  -> [GA.Str (T.intercalate (T.pack ", ") ls)]+     (False,False) -> GA.Str (T.intercalate (T.pack ", ") ls) : (GA.Newline [] : [thesis])+      +  where+    maybeText (Just gi) = GA.Str gi+    maybeText Nothing   = GA.Str $ T.pack ""++ removeThesis :: Entry -> Entry removeThesis e = -    e{ entryGraduationInfo = map (\g -> g{ gradThesis = Nothing }) $ entryGraduationInfo e }--- e{ entryGraduationInfo = case entryGraduationInfo e of---                       Nothing -> Nothing---                       Just g  -> Just g{gradThesis = Nothing}}+  e{ entryGraduationInfo = map (\g -> g{ gradThesis = Nothing }) $ entryGraduationInfo e } 
Extract.hs view
@@ -15,12 +15,10 @@ where import Network.HTTP import Text.HTML.TagSoup-import Control.Monad-import Control.Exception import Data.Char(isSpace)-import Data.Maybe import Data.Text.Lazy(Text) import Data.Text.Lazy.Encoding +import Control.Exception import qualified Data.Text.Lazy as T import qualified Data.ByteString.Lazy.Char8 as B import Control.Applicative@@ -29,6 +27,7 @@ import Entry  +openURL :: String -> IO Text openURL x = return . T.pack =<<  getResponseBody =<< simpleHTTP (getRequest x)  @@ -58,7 +57,7 @@     let suffixes = sections (~== "<span style=\"margin-right: 0.5em\">") tags in     map (graduationInfo tags) suffixes     where-        graduationInfo tags suffix = +        graduationInfo _ suffix =              let getTag i = notEmptyMaybeTagText $ removeClutter $ suffix !! i                  degree = getTag 1                 univ = getTag 3 @@ -71,6 +70,7 @@                     | length s >= 6  = GraduationInfo degree univ year Nothing                     | length s >= 4  = GraduationInfo degree univ Nothing Nothing                     | length s >= 2  = GraduationInfo degree Nothing Nothing Nothing+                    | otherwise      = throw $ AssertionFailed "Error - Could not parse downloaded data!"   advisors :: [Tag Text] -> [(Text,Text)] @@ -99,7 +99,7 @@     catMaybes' :: [(Maybe a,Maybe b)] -> [(a,b)]     catMaybes' [] = []     catMaybes' ((Just a,Just l):xs) = (a,l) : catMaybes' xs-    catMaybes' ((Just a,Nothing):xs)=  catMaybes' xs+    catMaybes' ((Just _,Nothing):xs)=  catMaybes' xs     catMaybes' ((Nothing,_):xs) = catMaybes' xs      maybeAdv adv@(Just t)
Graph.hs view
@@ -1,7 +1,7 @@ ----------------------------------------------------------------------------- -- | -- Module      :  Graph--- Copyright   :  (C) Peter Robinson 2010-2012+-- Copyright   :  (C) Peter Robinson 2010-2013 -- License     :  GPL-2 -- -- Maintainer  :  Peter Robinson <thaldyron@gmail.com>@@ -9,19 +9,18 @@ -- Portability :  portable  -- ------------------------------------------------------------------------------module Graph+module Graph(entryGraph) where import qualified Data.Map as M import Data.Graph.Inductive import Data.Text.Lazy(Text)-import qualified Data.Text.Lazy as T import Entry  -- | Maps scientist names to Entries type EntryMap = M.Map Text Entry  mkEntryMap :: [Entry] -> EntryMap -> EntryMap-mkEntryMap es theMap = foldl (\theMap e -> M.insert (entryName e) e theMap) theMap es+mkEntryMap es theMap = foldl (\m e -> M.insert (entryName e) e m) theMap es   entryGraph :: [Entry] -> Gr Entry ()
Main.hs view
@@ -15,155 +15,180 @@ import Control.Applicative import Control.Concurrent import Control.Exception+import Prelude hiding(catch)+import Data.Char(toUpper) import Data.GraphViz import Data.GraphViz.Attributes.Complete+import qualified Data.GraphViz.Attributes.HTML as GA import qualified Data.ByteString.Lazy.Char8 as B import qualified Data.List as L+import System.Console.CmdArgs hiding (args) import System.Cmd(system) import System.IO(hFlush,stdout) import System.Exit import System.Directory-import Data.Maybe import Data.Text.Lazy(Text)-import qualified Data.Text.Lazy.IO as TI import Data.Text.Lazy.Encoding  import qualified Data.Text.Lazy as T-import System.Console.CmdArgs-import Prelude hiding(catch)+import qualified Data.Text.Lazy.IO as TI+import Safe(readDef)+import Data.Binary(encodeFile,decodeFile)+import System.FilePath(pathSeparator) +import CmdParams(mathGenealogy,MathGenealogy(..),outputFileToGraphviz) import Extract import Entry import Graph -data OutputFile = FilePDF | FilePNG | FileSVG-  deriving(Eq,Data,Typeable)--instance Show OutputFile where-  show FilePDF = ".pdf"-  show FilePNG = ".png"-  show FileSVG = ".svg"--outputFileToGraphviz :: OutputFile -> String-outputFileToGraphviz FilePDF = " -Tpdf "-outputFileToGraphviz FilePNG = " -Tpng "-outputFileToGraphviz FileSVG = " -Tsvg "---instance Default OutputFile where-  def = FilePDF---- command line arguments:-data MathGenealogy = MathGenealogy -  { filePrefix   :: String -  , keepDotFile  :: Bool-  , graphvizArgs :: String-  , onlyDotFile  :: Bool---  , verbose      :: Bool-  , includeTheses:: Bool-  , startURL     :: String-  , outputFile   :: OutputFile-  }-  deriving(Eq,Data,Typeable,Show)--mathGenealogy = MathGenealogy-  { startURL     = def &= args &= typ "URL" -  , filePrefix   = "output" &= typ "PREFIX" -  , keepDotFile  = False ---  , graphvizArgs = " -Tpdf -Gcharset=utf8 " &= typ "<graphviz parameters>" &= opt " -Tpdf -Gcharset=utf8 "-  , graphvizArgs = " -Gcharset=utf8 " &= typ "<graphviz parameters>" -- &= opt " -Gcharset=utf8 "-  , onlyDotFile  = False &= help "Only create the GraphViz '.dot' file"---  , verbose      = False &= help "Print data to terminal."-  , includeTheses= False &= help "Include PhD thesis in output"-  , outputFile   = enum [ FilePDF &= help "create PDF file (default)"-                        , FilePNG &= help "create PNG file"-                        , FileSVG &= help "create SVG file"-                        ]-  } &= summary "Mathematics Genealogy Visualizer (C) 2010-2012 Peter Robinson thaldyron@gmail.com" -    &= details[ "Run the program with a start-URL, for example:"-              ,"# mathgenealogy http://genealogy.math.ndsu.nodak.edu/id.php?id=18231"-              ,"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 (version 2) for more details."-              ]---- | GraphViz style attributes for edges and nodes.--- See: http://hackage.haskell.org/packages/archive/graphviz/2999.12.0.4/doc/html/Data-GraphViz-Attributes.html--- TODO: figure out if GraphViz supports CSS files or similar. -edgeAtt = const [ Dir Forward-                , color LightBlue2-                ] --nodeAtt (num,e) = -  [ textLabel (T.pack (show e)) -  , Shape BoxShape-  , FontSize (if num == 1 then 18 else 14)-  , styles [rounded,filled,bold]-  , color LightYellow1-  , FontName (T.pack "ZapfChancery-MediumItalic") -- "Helvetica")-  ]--+main :: IO () main = do   args <- cmdArgs mathGenealogy-  let targs = args   when (null $ startURL args) $      throw (AssertionFailed "Missing start-URL.\nTry 'mathgenealogy --help' for more information.") -  gvExecutable <- (do m <- findExecutable "dot" -                      if m == Nothing -                        then throw $ AssertionFailed "Error - Couldn't find 'dot' program. Did you install graphviz?"-                        else return (fromJust m))+  gvExecutable <- findExecutable "dot"  >>=+                      maybe (throw $ AssertionFailed "Error - Couldn't find the 'dot' program. Did you install GraphViz?")+                            return    let traverseEntries :: [Text] -> IO [Entry]       traverseEntries = traverseEntries' 1 [] []         where+          traverseEntries' :: Int -> [Entry] -> [Text] -> [Text] -> IO [Entry]           traverseEntries' _ acc _        []         = return $ L.nub acc           traverseEntries' c acc prevUrls (url:urls) = do             e <- (if includeTheses args then id else removeThesis)                  `liftM` downloadEntry url             threadDelay 1000000---            if verbose args ---                then  TI.putStrLn $ entryToText e---                else do -            putStr $ show c ++ " "-            hFlush stdout+            if verbose args then  +                TI.putStrLn $ entryToText e+            else do +                putStr $ "..."  ++ show c+                hFlush stdout             let newUrls = urlAdvisors e             traverseEntries' (c+1) (e:acc) (url:prevUrls)                               ((newUrls L.\\ prevUrls) ++ urls) -  putStrLn "Downloading entries from http://genealogy.math.ndsu.nodak.edu..."-  putStrLn "(this might take a few minutes)"-  theGraph <- entryGraph <$> traverseEntries [T.pack $ startURL args]-  putStrLn "done. :)"-  let dotFileName = filePrefix args ++ ".dot"         -  putStr $ "Writing DOT-file " ++ dotFileName ++ "..."+  appDir   <- getAppUserDataDirectory "mathgenealogy"+  doesDirectoryExist appDir >>= \exAppDir -> +      unless exAppDir $ createDirectory appDir+  let entryFilename = tail $ L.dropWhile (/='=') $ startURL args+  mEntryFile <- findFile [appDir] entryFilename+  doDownload <- case mEntryFile of+                    Nothing -> return True+                    Just _  -> yesno False "Found locally stored data for this entry. Download new data and overwrite it?"++  let dotFileName = prefix args ++ ".dot"         +  theGraph <- if doDownload then do+                  putStrLn "Fetching entries from http://genealogy.math.ndsu.nodak.edu..."+                  putStrLn "(this might take a few minutes)"+                  putStr "Downloading entry number"+                  entries  <- traverseEntries [T.pack $ startURL args]+                  putStrLn ". Done. :)"+                  encodeFile (appDir ++ [pathSeparator] ++ entryFilename) entries+                  putStr $ "Building DOT-file '" ++ dotFileName ++ "' from downloaded data..."+                  return $ entryGraph entries+              else do+                  (entries::[Entry]) <- decodeFile (appDir ++ [pathSeparator] ++ entryFilename)+                  putStr $ "Building DOT-file '" ++ dotFileName ++ "' from locally stored data..."+                  return $ entryGraph entries++  +  let edgeAtt = const [ Dir Forward+                      , parseColor "Error - Could not parse edge color!" $ edgeColor args+                      ] +  let bgcolorX11 = parseColorX11 "Error - Could not parse node background color!" $ +                         nodeBackgroundColor args+  let nodeAtt (num,e) = +        if not $ oldTextLabels args then+            [ styles [rounded,filled]+            , Shape PlainText+            , parseColor "Error - Could not parse node backgroundcolor!" $ +                           nodeBackgroundColor args+            , toLabel $ +                let fshead = if num == 1 then fontSizeFirstHeading args +                                         else fontSizeHeading args +                    fs = if num == 1 then fontSizeFirst args +                                     else fontSize args in+                entryToHtml [ GA.BGColor $ X11Color bgcolorX11+                            , GA.Border 0+                            , GA.CellBorder 0+                            ]+                            [ GA.Face (T.pack $ fontHeading args)+                            , GA.PointSize fshead+                            ]+                            [ GA.Face (T.pack $ font args)+                            , GA.PointSize fs+                            ] e +            ]+        else+            [ styles [rounded,filled]+            , textLabel $ entryToText e+            , Shape BoxShape+            , FontSize (if num == 1 then fontSizeFirst args else fontSize args)+            , parseColor "Error - Could not parse node background color!" $ +                         nodeBackgroundColor args+            , FontName (T.pack $ font args) +            ]   let output = printDotGraph $                   graphToDot nonClusteredParams{ fmtNode = nodeAtt                                                , fmtEdge = edgeAtt                                               } theGraph   B.writeFile dotFileName (encodeUtf8 output)     `catch` (\(e::IOException) -> do { print e ; throw e })-  putStrLn "done. :)"+  putStrLn "success! :)" -  unless (onlyDotFile args) $ do-    putStr "Generating graphics file..."-    let command = gvExecutable ++ " " ++ outputFileToGraphviz (outputFile args) ++ graphvizArgs args-                               ++ " " ++ dotFileName ++ " > " -                               ++ filePrefix args ++ show (outputFile args)-    print command-    result <- system command-    when (isExitFailure result) $ do-      print $ "Error running the graphviz (dot) program. I tried: " ++ command-      throw $ AssertionFailed "Exiting."-    putStrLn "done. :)"+  putStr $ "Generating graphics file " ++ prefix args ++ show (outputFile args) ++ "..."+  let command = gvExecutable ++ " " ++ outputFileToGraphviz (outputFile args) ++ graphvizArgs args+                             ++ " " ++ dotFileName ++ " > " +                             ++ prefix args ++ show (outputFile args)+  when (verbose args) $+      print command+  result <- system command+  when (isExitFailure result) $ do+    print $ "Error running the graphviz (dot) program. I tried: " ++ command+    throw $ AssertionFailed "Exiting."+  putStrLn "done. :)"   unless (keepDotFile args) $ removeFile dotFileName   where     isExitFailure (ExitFailure _) = True     isExitFailure _               = False+  +    parseColor :: String -> String -> Attribute+    parseColor errMsg colStr = +        let colStr1 = headToUpper colStr in+        color (readDef (throw $ AssertionFailed errMsg) colStr1 :: X11Color) +    parseColorX11 :: String -> String -> X11Color+    parseColorX11 errMsg colStr = +        let colStr1 = headToUpper colStr in+        readDef (throw $ AssertionFailed errMsg) colStr1 :: X11Color++-- | Conver the first character of the string to upper-case.+headToUpper :: String -> String+headToUpper str = toUpper  (head str) : tail str+     +-- | Download and parse an entry. downloadEntry :: Text -> IO Entry downloadEntry t = do      res <- parseEntry <$> getTags t     case res of-        Nothing -> throw $ AssertionFailed "Error parsing fetched data. Did you provide a valid URL to an existing math-genealogy entry?"+        Nothing -> throw $ AssertionFailed "Error - Could not parse fetched data. Did you provide a valid URL to an existing math-genealogy entry?"         Just r -> return r ++yesno :: Bool    -- Set to 'True' iff "y" is the default value.+      -> String  -- Prompt string.+      -> IO Bool -- 'True' if the user answered affirmatively.+yesno defval prompt = do+          putStr $ prompt ++ if defval then " (Y/n) " else " (y/N) "+          hFlush stdout+          str <- getLine+          if null str then+              return defval+          else case headToUpper str of+                "Y" -> return True+                "N" -> return False+                _   -> do+                  putStrLn "Invalid input."+                  yesno defval prompt
mathgenealogy.cabal view
@@ -1,11 +1,12 @@ Name:                mathgenealogy -Version:             1.2.0+Version:             1.3.0  Synopsis:            Discover your (academic) ancestors! -Description:         A simple command line program for extracting data from the+Description:         A command line program for extracting data from the   Mathematics Genealogy Project (<http://genealogy.math.ndsu.nodak.edu/index.php>).+  Note that this database also contains many entries of computer scientists.   .   Lookup your entry at <http://genealogy.math.ndsu.nodak.edu/index.php> and   then use that URL as a command line argument.  @@ -15,15 +16,34 @@   > mathgenealogy http://genealogy.math.ndsu.nodak.edu/id.php?id=18231    .   which produces the directed acyclic graph-  <http://dl.dropbox.com/u/22490968/genealogy_of_gauss.png>. See +  <http://dl.dropbox.com/u/22490968/genealogy_of_gauss.svg>. See    .   > mathgenealogy --help   .-  for more options. Requires GraphViz (in particular the /dot/ program) to run.+  for the complete list of options. Requires the /dot/ program, which is+  part of the GraphViz package (/version 2.28.0 or later!/) to run.   .+  Feedback and bug reports are appreciated!+  .+  /Changes in 1.3.0:/+  .+  * Switched to HTML-like labels in GraphViz. This is required for the advanced+    font formatting options. ATTENTION: this needs a recent+    installation of GraphViz (Linux: 2.28.0; MacOS: 2.29) and will yield+    best results for SVG output files. If you have an older version of GraphViz+    or run into issues, try the '--oldtextlabels' option.+  .+  * Downloaded entries are stored locally. This avoids re-downloading the same+    data if you want to generate multiple graphs of the same entry with+    different formatting options.+  .+  * Added more options for fine-tuning the fonts and colors. (requested by users)+  .+  * Default output is now SVG, as this is the format best supported by GraphViz.+  .   /Changes in 1.2.0:/   .-  * Correct handling of entries with multiple PhDs (reported by Dima Pasechnik)+  * Fixed handling of entries that have multiple degrees. (reported by Dima Pasechnik)   .    /Changes in 1.1.1:/   .@@ -39,7 +59,7 @@   .   /Changes in 0.0.2:/   .-  * Fixed bug regarding trailing commas (reported by Alexander Koessler) +  * Fixed handling of trailing commas (reported by Alexander Koessler)   -- Homepage:            http://darcs.monoid.at/mathgenealogy @@ -69,7 +89,7 @@ Executable mathgenealogy       build-depends: base >= 4 && < 5                    , text >= 0.11 && <0.13-                   , directory >= 1.1 && <1.3+                   , directory >= 1.2 && <1.3 --                   , haskell98 >= 1.1 && <2.1                    , process >= 1.1 && < 1.2                    , bytestring >= 0.9 && <1.0@@ -80,11 +100,15 @@                    , tagsoup >= 0.12.6 && <0.13                    , HTTP >= 4000.1.2 && <5000                    , safe >= 0.3.3 && <0.5+                   , binary >= 0.6.4 && <0.7+                   , filepath >= 1.3.0 && <1.4       Main-Is: Main.hs       Other-modules: Graph                    , Extract                     , Entry+                   , CmdParams       extensions: ScopedTypeVariables                 , DeriveDataTypeable---      ghc-options:     -O+                , DoAndIfThenElse+      ghc-options:     -Wall --      ld-options: -static -pthread