packages feed

sloane 1.8 → 1.8.2

raw patch · 4 files changed

+111/−82 lines, 4 filesdep +textdep −stringsearch

Dependencies added: text

Dependencies removed: stringsearch

Files

README.md view
@@ -1,6 +1,6 @@ ----title: SLOANE(1) Sloane User Manual | Version 1.8-date: May 27, 2014+title: SLOANE(1) Sloane User Manual | Version 1.8.2+date: July 3, 2014 ---  # NAME@@ -12,7 +12,7 @@  sloane [-a | --all | -k *keys* | --url] [-n *entries*] *search-terms* ...   sloane (--update | --version | --help)  -sloane+sloane [--invert]  # DESCRIPTION @@ -28,15 +28,15 @@  If no search terms are specified the standard input is read line-by-line. In this mode the search is done locally against a-downloaded list of known sequences. If the sequence is in the OEIS, then+downloaded list of known sequences. If the sequence is in OEIS, then it is returned to the standard output; if not, it is ignored. This way sloane can quickly filter out the sequences from the input that are in-the OEIS. Assuming that *FILE* contains one sequence per line,+OEIS. Assuming that *FILE* contains one sequence per line,      sloane <FILE -returns the subset of the sequences in *FILE* that are in the OEIS. To-also look-up the names of those sequences in the OEIS one could, for+returns the subset of the sequences in *FILE* that are in OEIS. To+also look-up the names of those sequences in OEIS one could, for instance, run      sloane <FILE | xargs -L1 --verbose sloane@@ -58,6 +58,9 @@ -n *entries* :   Fetch at most this many entries (default: 5) +--invert+:   When used as a filter, return sequences *not* in OEIS+ --update :   Update the local sequence cache @@ -95,7 +98,7 @@     firefox `sloane --url -n 3 "(2+2)-free posets"`  In the final example the local cache is used to filter out sequences-from the standard input that are in the OEIS:+from the standard input that are in OEIS:      sloane <<END     1,2,3,6,11,23,47,106,235           # Comma separated integers
sloane.1 view
@@ -1,4 +1,4 @@-.TH "SLOANE" "1" "May 27, 2014" "Sloane User Manual" "Version 1.8"+.TH "SLOANE" "1" "July 3, 2014" "Sloane User Manual" "Version 1.8.2" .SH NAME .PP sloane \- a command line interface to Sloane\[aq]s On\-Line Encyclopedia@@ -14,7 +14,7 @@ .PD 0 .P .PD-sloane+sloane [\-\-invert] .SH DESCRIPTION .PP The sloane command searches Sloane\[aq]s On\-Line Encyclopedia of@@ -37,10 +37,10 @@ line\-by\-line. In this mode the search is done locally against a downloaded list of known sequences.-If the sequence is in the OEIS, then it is returned to the standard-output; if not, it is ignored.+If the sequence is in OEIS, then it is returned to the standard output;+if not, it is ignored. This way sloane can quickly filter out the sequences from the input that-are in the OEIS.+are in OEIS. Assuming that \f[I]FILE\f[] contains one sequence per line, .IP .nf@@ -49,9 +49,8 @@ \f[] .fi .PP-returns the subset of the sequences in \f[I]FILE\f[] that are in the-OEIS.-To also look\-up the names of those sequences in the OEIS one could, for+returns the subset of the sequences in \f[I]FILE\f[] that are in OEIS.+To also look\-up the names of those sequences in OEIS one could, for instance, run .IP .nf@@ -84,6 +83,11 @@ .RS .RE .TP+.B \-\-invert+When used as a filter, return sequences \f[I]not\f[] in OEIS+.RS+.RE+.TP .B \-\-update Update the local sequence cache .RS@@ -148,7 +152,7 @@ .fi .PP In the final example the local cache is used to filter out sequences-from the standard input that are in the OEIS:+from the standard input that are in OEIS: .IP .nf \f[C]
sloane.cabal view
@@ -1,5 +1,5 @@ Name:                sloane-Version:             1.8+Version:             1.8.2 Synopsis:            A command line interface to Sloane's On-Line Encyclopedia of Integer Sequences Description:         A command line interface to Sloane's On-Line Encyclopedia of Integer Sequences.                      For usage see <http://github.com/akc/sloane>.@@ -20,7 +20,16 @@  Executable sloane   Main-is:             sloane.hs-  Build-depends:       base >=3 && <5, optparse-applicative >=0.9, HTTP >=4000.0.9, network >=2.4,-                       bytestring >=0.9, ansi-terminal >=0.6, terminal-size >=0.2,-                       filepath >=1.3, directory >=1.2, stringsearch >=0.3,-                       zlib >=0.5, time >=1.4+  ghc-options:         -Wall+  Build-depends:       base >=3 && <5,+                       optparse-applicative >=0.9,+                       HTTP >=4000.0.9,+                       network >=2.4,+                       bytestring >=0.9,+                       text >=1.1,+                       ansi-terminal >=0.6,+                       terminal-size >=0.2,+                       filepath >=1.3,+                       directory >=1.2,+                       zlib >=0.5,+                       time >=1.4
sloane.hs view
@@ -4,11 +4,14 @@ -- License     : BSD-3 -- import qualified Codec.Compression.GZip       as GZip-import           Control.Monad                (unless, when)-import qualified Data.ByteString.Char8        as B-import qualified Data.ByteString.Lazy.Char8   as BL-import qualified Data.ByteString.Lazy.Search  as Search-import           Data.Maybe                   (fromJust, maybe)+import           Control.Monad                (unless, when, liftM2)+import qualified Data.ByteString              as B+import qualified Data.ByteString.Lazy         as BL+import           Data.Text                    (Text)+import qualified Data.Text                    as T+import qualified Data.Text.IO                 as IO+import           Data.Text.Encoding           (decodeUtf8)+import           Data.Maybe                   (fromJust) import           Data.Time                    (diffUTCTime, getCurrentTime) import           Network.HTTP import           Network.URI                  (parseURI)@@ -17,7 +20,7 @@ import           System.Console.Terminal.Size (Window (..), size) import           System.Directory import           System.FilePath              ((</>))-import           System.IO                    (stderr, stdin)+import           System.IO                    (stderr)  type OEISEntries = [String] type ANumbers = [String]@@ -25,25 +28,28 @@ type Keys = String  data Args = Args-    { all    :: Bool-    , keys   :: String-    , limit  :: Int-    , update :: Bool-    , url    :: Bool-    , ver    :: Bool-    , terms  :: [String]-    }+    Bool     -- a: Print all fields?+    String   -- k: Keys of fields to print+    Int      -- n: Fetch at most this many entries+    Bool     -- invert: Return sequences NOT in OEIS+    Bool     -- update: Update the local sequence cache+    Bool     -- url: Print URLs of found entries+    Bool     -- version+    [String] -- search terms -version   = "sloane 1.8"+name, oeisHost, oeisURL, cacheDir, cacheFile, cacheURL :: String +name = "sloane 1.8.2"+ oeisHost  = "http://oeis.org/" oeisURL   = oeisHost ++ "search?fmt=text"-oeisKeys  = "ISTUVWXNDHFYAOEeptoKC"  cacheDir  = ".sloane" cacheFile = "stripped.gz" cacheURL  = oeisHost ++ cacheFile +msgDownloadingCache, msgCacheIsUpToDate, msgNoCache, msgOldCache :: String+ msgDownloadingCache = unlines     [ "Downloading " ++ cacheURL     , "This may take a minute or two ..."@@ -73,15 +79,6 @@   where     uri' = fromJust $ parseURI uri -updateCache :: FilePath -> IO ()-updateCache home = do-    let dir = home </> cacheDir-    createDirectoryIfMissing False dir-    put msgDownloadingCache-    cache <- get cacheURL-    B.writeFile (dir </> cacheFile) cache-    put msgCacheIsUpToDate- searchOEIS :: Int -> Query -> IO OEISEntries searchOEIS n s = trim `fmap` get uri   where@@ -101,14 +98,20 @@ getWidth :: IO Int getWidth = maybe maxBound width `fmap` size -put = B.putStr . B.pack-putErr = B.hPutStr stderr . B.pack-newline = B.putStrLn B.empty+put :: String -> IO ()+put = IO.putStr . T.pack +putErr :: String -> IO ()+putErr = IO.hPutStr stderr . T.pack++newline :: IO ()+newline = IO.putStrLn T.empty+ putEntries :: Int -> OEISEntries -> IO ()-putEntries width = mapM_ $ \line ->+putEntries ncols = mapM_ $ \line -> do     case words line of-        [] -> newline+        []  -> return ()+        [w] -> put w -- Should never be reached         (key:aNum:rest) -> do             setSGR [ SetColor Foreground Dull Green ]             put key@@ -116,42 +119,48 @@             put $ ' ' : aNum             setSGR []             let crop = if key == "S" then cropSeq else cropLine-            put $ ' ' : crop width (unwords rest) ++ "\n"--isInfixOf :: B.ByteString -> BL.ByteString -> Bool-isInfixOf q = not . null . Search.indices q+            put $ ' ' : crop ncols (unwords rest)+    newline -dropPreamble :: BL.ByteString -> BL.ByteString-dropPreamble = BL.unlines . drop 4 . BL.lines+updateCache :: FilePath -> IO ()+updateCache home = do+    createDirectoryIfMissing False dir+    put msgDownloadingCache+    get cacheURL >>= BL.writeFile (dir </> cacheFile)+    put msgCacheIsUpToDate+  where+    dir = home </> cacheDir -readCache :: FilePath -> IO BL.ByteString+readCache :: FilePath -> IO Text readCache home = do-    let name = home </> cacheDir </> cacheFile-    updated <- doesFileExist name+    updated <- doesFileExist fname     if updated         then do-            c <- getCurrentTime-            m <- getModificationTime name-            let day = 60*60*24-            let expired = c `diffUTCTime` m > 100*day-            when expired $ putErr msgOldCache-            (dropPreamble . GZip.decompress) `fmap` BL.readFile name+            age <- liftM2 diffUTCTime getCurrentTime (getModificationTime fname)+            when (age > 100*day) $ putErr msgOldCache+            (dropPreamble . decompress) `fmap` BL.readFile fname         else             error msgNoCache+  where+    day = 60*60*24+    fname = home </> cacheDir </> cacheFile+    decompress = decodeUtf8 . B.concat . BL.toChunks . GZip.decompress+    dropPreamble = T.unlines . drop 4 . T.lines -seqs :: B.ByteString -> [B.ByteString]-seqs = filter (not . B.null) . map mkSeq . B.lines+seqs :: Text -> [Text]+seqs = filter (not . T.null) . map mkSeq . T.lines   where     mkSeq = normalize . dropComment-    dropComment = B.takeWhile (/= '#')-    normalize   = B.intercalate (B.pack ",") . B.words . clean . B.map tr+    dropComment = T.takeWhile (/= '#')+    normalize   = T.intercalate (T.pack ",") . T.words . clean . T.map tr     tr c  = if c `elem` ";," then ' ' else c-    clean = B.filter (\c -> B.elem c (B.pack " 0123456789-"))+    clean = T.filter (`elem` " 0123456789-") -filterSeqs :: FilePath -> IO ()-filterSeqs home = do+filterSeqs :: Bool -> FilePath -> IO ()+filterSeqs invert home = do     cache <- readCache home-    B.getContents >>= mapM_ B.putStrLn . filter (`isInfixOf` cache) . seqs+    let f q = (if invert then not else id) (q `T.isInfixOf` cache)+    IO.getContents >>= mapM_ IO.putStrLn . filter f . seqs  args :: Parser Args args = Args@@ -166,19 +175,22 @@        <> metavar "N"        <> value 5        <> help "Fetch at most this many entries [default: 5]" )+    <*> switch+        ( long "invert"+       <> help "When used as a filter, return sequences NOT in OEIS" )     <*> switch (long "update" <> help "Update the local sequence cache")     <*> switch (long "url" <> help "Print URLs of found entries")     <*> switch (hidden <> long "version")     <*> many (argument str (metavar "TERMS..."))  sloane :: Args -> IO ()-sloane (Args all keys n update url True terms) = put version >> newline-sloane (Args all keys n True   url ver  terms) = getHomeDirectory >>= updateCache-sloane (Args all keys n update url ver  []   ) = getHomeDirectory >>= filterSeqs-sloane (Args all keys n update url ver  terms) = do+sloane (Args _ _    _ _   _    _   True _ ) = put name >> newline+sloane (Args _ _    _ _   True _   _    _ ) = getHomeDirectory >>= updateCache+sloane (Args _ _    _ inv _    _   _    []) = getHomeDirectory >>= filterSeqs inv+sloane (Args a keys n _   _    url _    ts) = do     ncols <- getWidth-    hits  <- searchOEIS n (unwords terms)-    let pick = if all then id else select keys+    hits  <- searchOEIS n (unwords ts)+    let pick = if a then id else select keys     unless (null hits) $ do         newline         if url@@ -186,6 +198,7 @@             else putEntries (ncols - 10) (pick hits)         newline -main = execParser (info (h <*> args) (fullDesc <> header version)) >>= sloane+main :: IO ()+main = execParser (info (h <*> args) (fullDesc <> header name)) >>= sloane   where     h = abortOption ShowHelpText $ hidden <> long "help"