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 +11/−8
- sloane.1 +13/−9
- sloane.cabal +14/−5
- sloane.hs +73/−60
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"