packages feed

sloane 4.2.0 → 5.0.0

raw patch · 9 files changed

+234/−432 lines, 9 filesdep −terminal-size

Dependencies removed: terminal-size

Files

Sloane/Bloom.hs view
@@ -20,7 +20,7 @@                 , ByteString, ByteString, ByteString                 , ByteString, ByteString, ByteString ) -type BlF = Bloom T9+type BF = Bloom T9  instance Hashable T9 where     hashIO32 (T9 (a,b,c,d,e,f,g,h,i)) salt =@@ -38,7 +38,7 @@  -- | Make a Bloom filter of all nine integer segments of all sequences -- in the given data base.-mkBloomFilter :: DB Sequences -> BlF+mkBloomFilter :: DB Seqs -> BF mkBloomFilter (DB db) = F.fromList (cheapHashes numHashes) numBits ts   where     ts = ninegrams (parseTermsOfRecords db)@@ -48,5 +48,5 @@ -- | Are all the nine element factors of the given (packed) sequence -- members of the Bloom filter. May give a false positive answer, but -- never a false negative answer.-isFactorOf :: PackedSeq -> BlF -> Bool+isFactorOf :: PackedSeq -> BF -> Bool isFactorOf (PSeq s) bf = all (`F.elem` bf) $ ninegrams (parseTermsErr s)
Sloane/Config.hs view
@@ -1,5 +1,5 @@ -- |--- Copyright   : Anders Claesson 2014-2015+-- Copyright   : Anders Claesson 2014-2016 -- Maintainer  : Anders Claesson <anders.claesson@gmail.com> -- License     : BSD-3 --@@ -10,7 +10,6 @@     , getConfig     ) where -import System.Console.Terminal.Size (width, size) import System.FilePath ((</>)) import System.Directory @@ -25,19 +24,15 @@     , seqDBPath   :: FilePath     -- | Path to 'names' file.     , namesDBPath :: FilePath-    -- | The width of the terminal.-    , termWidth   :: Int     }  -- | Get configuration. getConfig :: IO Config getConfig = do-    w <- maybe maxBound width `fmap` size     h <- getHomeDirectory     let c = Config { home        = h                    , sloaneDir   = h </> ".oeis-data"                    , seqDBPath   = sloaneDir c </> "stripped"                    , namesDBPath = sloaneDir c </> "names"-                   , termWidth   = w                    }     return c
Sloane/DB.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE PolyKinds #-}@@ -10,16 +9,14 @@ --  module Sloane.DB-    ( DB (..), Sequences, Names+    ( DB (..), Seqs, Names     , readSeqDB     , readNamesDB     , grepN     , grep     ) where -#if __GLASGOW_HASKELL__ < 710 import Control.Applicative-#endif import Data.Maybe import Data.List import Data.ByteString (ByteString)@@ -30,7 +27,7 @@ import Sloane.Config  -- | An empty data declaration used with the phantom `DB` data type.-data Sequences+data Seqs  -- | An empty data declaration used with the phantom `DB` data type. data Names@@ -45,7 +42,7 @@          else error "No local database; run 'sloane --update' first."  -- | Read the sequence DB (derived from \"stripped.gz\").-readSeqDB :: Config -> IO (DB Sequences)+readSeqDB :: Config -> IO (DB Seqs) readSeqDB = readDB . seqDBPath  -- | Read the names DB (derived from \"names.gz\").@@ -54,7 +51,7 @@  -- | Return all A-numbers whose associated sequence contains a given -- sequence as a factor.-grep :: PackedSeq -> DB Sequences -> [ANum]+grep :: PackedSeq -> DB Seqs -> [ANum] grep (PSeq p) (DB bs) = mapMaybe locateANum (S.indices q bs)   where     q = B.snoc (B.cons ',' p) ','@@ -66,5 +63,5 @@         ]  -- | Similar to `grep` but return at most 'n' unique hits.-grepN :: Int -> PackedSeq -> DB Sequences -> [ANum]+grepN :: Int -> PackedSeq -> DB Seqs -> [ANum] grepN n q db = Prelude.take n $ map head $ group (grep q db)
Sloane/Entry.hs view
@@ -1,54 +1,69 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE DeriveGeneric #-}  -- |--- Copyright   : Anders Claesson 2015+-- Copyright   : Anders Claesson 2015, 2016 -- Maintainer  : Anders Claesson <anders.claesson@gmail.com> -- License     : BSD-3 --  module Sloane.Entry-    ( PackedPrg (..)-    , PackedEntry (..)-    , parsePackedEntry-    , parsePackedEntryErr+    ( Prg (..)+    , Name (..)+    , Trail+    , Entry (..)     ) where -import GHC.Generics (Generic)+import Data.Aeson import Data.Maybe import Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Char8 as B-import qualified Data.Attoparsec.ByteString.Char8 as Ch-import Data.Attoparsec.ByteString.Char8+import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Control.Monad-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative-#endif-import Sloane.Utils-import Sloane.OEIS --- | A compact `ByteString` representation of a `Prg`.-newtype PackedPrg = PPrg ByteString deriving (Eq, Show, Generic)+newtype Prg = Prg ByteString deriving (Show, Eq) --- | Similary, a packed entry consists of a packed program together with--- a packed sequence.-data PackedEntry = PackedEntry-    { getPackedPrg :: PackedPrg-    , getPackedSeq :: PackedSeq-    } deriving (Eq, Show, Generic)+type Trail = [Prg] -packedEntry :: Parser PackedEntry-packedEntry =-    let f w = if B.last w == '=' then return (PPrg (B.init w)) else mzero-    in PackedEntry <$> (Ch.takeWhile1 (/='>') >>= f)-                   <*> (char '>' *> packedSeq)+newtype Name = Name ByteString deriving (Show, Eq) --- | A parser for packed entries.-parsePackedEntry :: ByteString -> Maybe PackedEntry-parsePackedEntry = parse_ packedEntry . B.filter (/=' ')+instance ToJSON Prg where+    toJSON (Prg bs) = String (decodeUtf8 bs) --- | Like `parsePackedEntry` but throws an error rather than returning--- `Nothing` in case the parse fails.-parsePackedEntryErr :: ByteString -> PackedEntry-parsePackedEntryErr = fromMaybe (error "cannot parse input") . parsePackedEntry+instance FromJSON Prg where+    parseJSON (String s) = return $ Prg (encodeUtf8 s)+    parseJSON _ = mzero++instance ToJSON Name where+    toJSON (Name bs) = String (decodeUtf8 bs)++instance FromJSON Name where+    parseJSON (String s) = return $ Name (encodeUtf8 s)+    parseJSON _ = mzero++-- | An entry consists of a program together with a list of rational+-- numbers.+data Entry = Entry+    { getPrg   :: Prg+    , getSeq   :: [Integer]+    , getDens  :: Maybe [Integer]+    , getName  :: Maybe Name+    , getTrail :: [Prg]+    } deriving (Eq, Show)++instance ToJSON Entry where+    toJSON (Entry prg s dens name trail) =+        object ([ "hops"         .= toJSON prg+                , "seq"          .= toJSON s ] +++                [ "denominators" .= toJSON dens  | isJust dens ] +++                [ "name"         .= toJSON name  | isJust name ] +++                [ "trail"        .= toJSON trail | not (null trail) ]+               )++instance FromJSON Entry where+    parseJSON (Object v) = do+        prg   <- v .:  "hops"+        ns    <- v .:  "seq"+        dens  <- v .:? "denominators"+        name  <- v .:? "name"+        trail <- v .:? "trail"+        return $ Entry prg ns dens name (fromMaybe [] trail)+    parseJSON _ = mzero
Sloane/OEIS.hs view
@@ -1,9 +1,8 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveGeneric #-}  -- |--- Copyright   : Anders Claesson 2015+-- Copyright   : Anders Claesson 2015, 2016 -- Maintainer  : Anders Claesson <anders.claesson@gmail.com> -- License     : BSD-3 --@@ -12,40 +11,28 @@     (     -- * Types       URL-    , Key-    , Name     , ANum (..)     , PackedSeq (..)-    , Table (..)-    , Reply (..)+    , OEISEntry (..)     -- * Parse names.gz and stripped.gz     , parseNames     , parseStripped     , parseTermsErr     , parseTermsOfRecords     -- * Parse replies from oeis.org-    , oeisKeys-    , parseReplies+    , parseOEISEntries     -- * Parse sequences     , shave-    , parseSeqErr     , parseIntegerSeq-    , packedSeq     , packSeq     -- * Parse A-numbers and B-numbers-    , aNumInt     , parseANum-    , packANum-    , tag     ) where  import GHC.Generics (Generic) import Data.Maybe-#if __GLASGOW_HASKELL__ < 710 import Data.Monoid-#endif import Data.String-import Data.Ratio import Data.Map (Map) import qualified Data.Map as M import Data.ByteString.Char8 (ByteString)@@ -54,29 +41,32 @@ import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Data.Aeson import qualified Data.Attoparsec.ByteString as A-import qualified Data.Attoparsec.ByteString.Char8 as Ch import Data.Attoparsec.ByteString.Char8 import Control.Monad import Control.Applicative import Sloane.Utils+import Sloane.Entry  -- | An OEIS key is `Char`.-type Key  = Char---- | The name of an OEIS entry is a short description of the--- sequence. Here represented as a `ByteString`.-type Name = ByteString+type Key = Char -type Row  = (Key, ANum, ByteString)+type Row = (Key, ANum, ByteString)  -- | A URL is currently just a synonym for `String`.-type URL  = String+type URL = String  -- | An A-number is the character \'A\' followed by a six digit -- number. Here we represent that by a wrapped (7 character) -- `ByteString`. newtype ANum = ANum {unANum :: ByteString} deriving (Eq, Ord, Show, Generic) +instance ToJSON ANum where+    toJSON (ANum bs) = String (decodeUtf8 bs)++instance FromJSON ANum where+    parseJSON (String s) = pure $ ANum (encodeUtf8 s)+    parseJSON _ = mzero+ -- | A `PackedSeq` is a wrapped `ByteString`. newtype PackedSeq = PSeq {unPSeq :: ByteString} deriving (Eq, Show, Generic) @@ -87,21 +77,6 @@ instance IsString PackedSeq where     fromString = PSeq . fromString --- | A `Table` represents an OEIS entry. It is a `Map` from OEIS keys to--- lists of `ByteString`s.-newtype Table = Table (Map Key [ByteString]) deriving Show---- | A `Reply` is an A-number together with an associated `Table` (OEIS--- entry).-data Reply = Reply ANum Table deriving Show--instance ToJSON ANum where-    toJSON (ANum bs) = String (decodeUtf8 bs)--instance FromJSON ANum where-    parseJSON (String s) = pure $ ANum (encodeUtf8 s)-    parseJSON _ = mzero- instance ToJSON PackedSeq where     toJSON (PSeq bs) = String (decodeUtf8 bs) @@ -109,26 +84,19 @@     parseJSON (String s) = pure $ PSeq (encodeUtf8 s)     parseJSON _ = mzero -instance ToJSON Table where-    toJSON (Table tbl) =-        object [ T.singleton key .= toJSON (map decodeUtf8 ls)-               | (key, ls) <- M.toList tbl-               ]+data OEISEntry = OEISEntry ANum (Map Key [ByteString]) deriving Show -instance ToJSON Reply where-    toJSON (Reply anum table) =-        object [ "A-number" .= toJSON anum-               , "table" .= toJSON table-               ]+instance ToJSON OEISEntry where+    toJSON (OEISEntry anum tbl) =+        object ("A-number" .= toJSON anum :+                [ T.singleton key .= toJSON (map decodeUtf8 ls)+                | (key, ls) <- M.toList tbl+                ]) -instance FromJSON Table where+instance FromJSON OEISEntry where     parseJSON (Object v) =         let f k = (,) <$> pure k <*> (map encodeUtf8 <$> v .: T.singleton k)-        in Table . M.fromList <$> mapM f oeisKeys-    parseJSON _ = mzero--instance FromJSON Reply where-    parseJSON (Object v) = Reply <$> v .: "A-number" <*> v .: "table"+        in OEISEntry <$> (v .: "A-number") <*> (M.fromList <$> mapM f oeisKeys)     parseJSON _ = mzero  spc :: Parser Char@@ -155,8 +123,8 @@ -- -- > A000108 Catalan numbers: C(n) = binomial(2n,n)/(n+1) = (2n)!/(n!(n+1)!). ---parseNames :: ByteString -> [(ANum, ByteString)]-parseNames = parseRecords+parseNames :: ByteString -> [(ANum, Name)]+parseNames bs = [ (a, Name n) | (a, n) <- parseRecords bs ]  -- | Parse a list of A-number-sequence pairs. It's purpose is to parse -- lines of the @stripped@ file. A typical line of that file looks like@@ -164,8 +132,8 @@ -- -- > A000108 ,1,1,2,5,14,42,132,429,1430,4862,16796,58786,208012,742900, ---parseStripped :: ByteString -> [(ANum, PackedSeq)]-parseStripped bs = [ (anum, PSeq (shave s)) | (anum, s) <- parseRecords bs ]+parseStripped :: ByteString -> [(ANum, [Integer])]+parseStripped bs = [ (anum, parseIntegerSeqErr (shave s)) | (anum, s) <- parseRecords bs ]  ------------------------------------------------------------------------------- -- Building the Bloom filter@@ -203,61 +171,43 @@   where     rest = A.takeTill isEndOfLine <* endOfLine -rows :: Parser Reply-rows = mkMap1 <$> many1 row-  where-    mkMap2 = Table . M.fromListWith (flip (++))-    mkMap1 rs@((_,a,_):_) = Reply a $ mkMap2 (map (\(key, _, r) -> (key, [r])) rs)-    mkMap1 [] = error "internal error"- noise :: Parser () noise = skipMany (notChar '%') -replies :: Parser [Reply]-replies = concat <$> (noise *> (many1 rows `sepBy` many1 endOfLine) <* noise)+oeisEntry :: Parser OEISEntry+oeisEntry = mkMap1 <$> many1 row+  where+    mkMap2 = M.fromListWith (flip (++))+    mkMap1 rs@((_,a,_):_) = OEISEntry a $ mkMap2 (map (\(key, _, r) -> (key, [r])) rs)+    mkMap1 [] = error "internal error" --- | Parse OEIS replies as recieved from @oeis.org/search?fmt=text@.-parseReplies :: ByteString -> [Reply]-parseReplies = fromMaybe [] . parse_ replies+oeisEntries :: Parser [OEISEntry]+oeisEntries = concat <$> (noise *> (many1 oeisEntry `sepBy` many1 endOfLine) <* noise) +parseOEISEntries :: ByteString -> [OEISEntry]+parseOEISEntries = fromMaybe [] . parse_ oeisEntries+ ------------------------------------------------------------------------------- -- Parse sequences ------------------------------------------------------------------------------- -rat :: Parser Rational-rat = (%) <$> signed decimal <*> ((char '/' *> decimal) <|> return 1)--ratSeq :: Parser [Rational]-ratSeq = rat `sepBy` char ','- integerSeq :: Parser [Integer] integerSeq = signed decimal `sepBy` char ',' -parseSeq :: ByteString -> Maybe [Rational]-parseSeq = parse_ (ratSeq <* endOfInput) . B.filter (/=' ')---- | Parse a sequence of `Rational`s.-parseSeqErr :: ByteString -> [Rational]-parseSeqErr = fromMaybe (error "error parsing sequence") . parseSeq- -- | Parse a sequence of `Integer`s. parseIntegerSeq :: ByteString -> Maybe [Integer] parseIntegerSeq = parse_ (integerSeq <* endOfInput) . B.filter (/=' ') --- | Parser for `PackedSeq`.-packedSeq :: Parser PackedSeq-packedSeq = PSeq <$> (char '{' *> Ch.takeWhile (/='}') <* char '}')+-- | Parse a sequence of `Integer`s or throw an error.+parseIntegerSeqErr :: ByteString -> [Integer]+parseIntegerSeqErr = fromMaybe (error "error parsing sequence") . parseIntegerSeq --- | Pack a sequence of `Rational`s into a `PackedSeq`. E.g.+-- | Pack a sequence of `Integers`s into a `PackedSeq`. E.g. ----- > packSeq [1,1/2,1/3] = PSeq {unPSeq = "1,1/2,1/3"}+-- > packSeq [1,-1,3] = PSeq {unPSeq = "1,-1,3"} ---packSeq :: [Rational] -> PackedSeq-packSeq = PSeq . B.intercalate (B.pack ",") . map (B.pack . f)-  where-    f r = case (numerator r, denominator r) of-            (n, 1) -> show n-            (n, d) -> show n ++ '/':show d+packSeq :: [Integer] -> PackedSeq+packSeq = PSeq . B.intercalate (B.pack ",") . map (B.pack . show)  ------------------------------------------------------------------------------- -- Utility functions@@ -274,10 +224,6 @@ aNumInt :: Parser Int aNumInt = char 'A' >> decimal --- | Run the `aNumInt` parser.-parseANum :: ByteString -> Maybe ANum-parseANum = parse_ (packANum <$> aNumInt)- -- | Pack an A-number given as an `Int` into a wrapped `ByteString` -- consistsing of an \'A\' followed by six digits. E.g. --@@ -286,6 +232,6 @@ packANum :: Int -> ANum packANum anum = ANum $ B.cons 'A' (pad 6 anum) --- | A parser for tags (B-numbers) as `Int`s.-tag :: Parser Int-tag = string "TAG" >> decimal+-- | Run the `aNumInt` parser.+parseANum :: ByteString -> Maybe ANum+parseANum = parse_ (packANum <$> aNumInt)
Sloane/Options.hs view
@@ -1,46 +1,27 @@ -- |--- Copyright   : Anders Claesson 2015+-- Copyright   : Anders Claesson 2015, 2016 -- Maintainer  : Anders Claesson <anders.claesson@gmail.com> -- License     : BSD-3 -- -- Command line options for sloane.  module Sloane.Options-    ( Palette-    , monochrome-    , Options (..)+    ( Options (..)     , getOptions     ) where  import Options.Applicative-import Sloane.OEIS --- | A palette is either colorful or monochrome.-data Palette = Colorful | Monochrome deriving (Eq, Enum, Show, Read)---- | Is the palette monochrome?-monochrome :: Palette -> Bool-monochrome Monochrome = True-monochrome Colorful   = False- -- | Command line options: data Options = Options     {     -- | Search oeis.org.       oeis           :: Bool-    -- | Print all fields?-    , longFormat     :: Bool-    -- | Keys of fields to print.-    , keys           :: String     -- | Fetch at most this many entries.     , limit          :: Int     -- | Fetch all entries (that match).     , limitless      :: Bool-    -- | Should we colorize the output?-    , palette        :: Palette     -- | Filter out sequences in local DB.-    , tojson         :: Bool-    -- | Return sequences NOT in DB     , filtr          :: Bool     -- | Return sequences NOT in DB     , invert         :: Bool@@ -48,7 +29,7 @@     , update         :: Bool     -- | Show version info     , version        :: Bool-    -- Search terms or programs+    -- | Search terms or hops entries     , terms          :: [String]     } @@ -59,14 +40,6 @@     <$> switch         ( long "oeis"        <> help "Search oeis.org" )-    <*> switch-        ( long "long"-       <> help "Long format; print all fields" )-    <*> strOption-        ( short 'k'-       <> metavar "KEYS"-       <> value "SN"-       <> help "Keys of fields to print [default: SN]" )     <*> option auto         ( short 'n'        <> metavar "N"@@ -75,13 +48,7 @@     <*> switch         ( long "all"        <> help "Fetch all matching entries (equivalent to -n 999999)" )-    <*> (toEnum . fromEnum <$> switch-        ( long "monochrome"-       <> help "Do not colorize the output" ) )     <*> switch-        ( long "json"-       <> help "Return results is JSON format" )-    <*> switch         ( long "filter"        <> help ("Read sequences from stdin and return"             ++ " those that are in the local database") )@@ -98,8 +65,6 @@  -- | Run the command line options parser (above). getOptions :: IO Options-getOptions = updateOpts <$> execParser (info optionsParser fullDesc)+getOptions = updateLimit <$> execParser (info optionsParser fullDesc)   where-    updateOpts = updateLimit . updateKeys-    updateKeys  opts = if longFormat opts then opts {keys = oeisKeys} else opts-    updateLimit opts = if limitless  opts then opts {limit = 999999}  else opts+    updateLimit opts = if limitless opts then opts {limit = 999999} else opts
sloane.1 view
@@ -1,24 +1,35 @@-.\" Automatically generated by Pandoc 1.15.1+.\" Automatically generated by Pandoc 1.16.0.2 .\"+.TH "SLOANE" "1" "22 March 2016" "User Manual" "Version 5.0.0" .hy-.TH "SLOANE" "1" "4 Sep 2015" "User Manual" "Version 4.2.0" .SH NAME .PP sloane \- lookup integer sequences, OEIS A\-numbers, etc. .SH SYNOPSIS .PP-\f[C]sloane\ [\-\-long]\ [\-k\ KEYS]\ [\-n\ N]\ [\-\-all]\ [\-\-monochrome]\ [\-\-json]\ [\-\-oeis]\ TERMS...\f[]+\f[C]sloane\ [\-n\ N]\ [\-\-all]\ [\-\-oeis]\ TERMS...\f[] .PD 0 .P .PD \f[C]sloane\ [\-\-invert]\ \-\-filter\f[]+.PD 0+.P+.PD+\f[C]sloane\ (\-\-update|\-\-version|\-\-help)\f[] .SH DESCRIPTION .PP \f[C]sloane\f[] provides a command line interface to Sloane\[aq]s OEIS (The On\-Line Encyclopedia of Integer Sequences). It can be used offline (the default) as well as online (with the \f[C]\-\-oeis\f[] option).-Local searches are faster but a bit less flexible.+The first time \f[C]sloane\f[] is used in offline mode the user will be+asked to run \f[C]sloane\ \-\-update\f[].+This will download \f[C]https://oeis.org/{names.gz,stripped.gz}\f[] and+unpack them into \f[C]\&.oeis\-data/{names,stripped}\f[] in the home+directory.+Alternatively, one can do this by hand using \f[C]wget\f[] and+\f[C]gunzip\f[], say, if prefered.+.PP A common way to use \f[C]sloane\f[] is to search for entries matching a sequence of consecutive terms: .IP@@ -32,48 +43,24 @@ .IP .nf \f[C]-S\ A006531\ 1,1,3,19,183,2371,38703,763099,17648823,468603091,14050842303,-N\ A006531\ Semiorders\ on\ n\ elements.-\f[]-.fi-.PP-As shown here the default is to return the sequence (S) and the name (N)-fields.-These are the only fields that are available in offline mode.-In online mode all fields are available.-To select among the fields use the \f[C]\-k\f[] option, or, to select-all fields, use to \f[C]\-\-long\f[] option.-The following search would show the sequence, name, comments, and-formula fields:-.IP-.nf-\f[C]-sloane\ \-k\ SNCF\ \-\-oeis\ 1,3,19,183,2371,38703+{+\ \ "trail":\ ["{1,3,19,183,2371,38703}"],+\ \ "hops":\ "A006531",+\ \ "name":\ "Semiorders\ on\ n\ elements.",+\ \ "seq":\ [1,1,3,19,183,2371,38703,763099,17648823,468603091,...]+} \f[] .fi .PP-One can also lookup A\-numbers or do a free text search; see the-\f[B]EXAMPLES\f[] section.-.PP The \f[C]\-\-filter\f[] option can be very useful when checking a large number of sequences.-In this mode \f[C]sloane\f[] reads standard input line\-by\-line, if the-sequence read is in the local database, then it is returned to standard-output; if not, it is ignored.+In this mode \f[C]sloane\f[] expects input in the form returned by+\f[B]hops\f[](1).+It reads standard input line\-by\-line, if the sequence in the right+hand side of the entry read is in the local database, then the entry is+returned to standard output; if not, it is ignored. This way one can quickly filter out the sequences from the input that are in the local database.-To be concrete, assume that \f[I]FILE\f[] contains one sequence per-line.-Then-.IP-.nf-\f[C]-sloane\ \-\-filter\ <FILE-\f[]-.fi-.PP-returns the subset of the sequences in \f[I]FILE\f[] that are in the-local database. To also lookup the names of those sequences one could run .IP .nf@@ -82,8 +69,10 @@ \f[] .fi .PP-The way this works is that if \f[C]sloane\f[] is called without search-terms then it reads from standard input.+If the sequences one wishes to filter are not already in form returned+by \f[C]hops\f[] then one may mold them into that form using+\f[C]hops\ \-\-tag\f[].+See the \f[B]EXAMPLES\f[] section for more usage examples. .SH OPTIONS .TP .B \-\-oeis@@ -92,16 +81,6 @@ .RS .RE .TP-.B \-k \f[I]KEYS\f[]-Keys of fields to print (default: SN).-.RS-.RE-.TP-.B \-\-long-Long format; print all fields.-.RS-.RE-.TP .B \-n \f[I]N\f[] Fetch at most this many entries (default: 5). .RS@@ -112,17 +91,6 @@ .RS .RE .TP-.B \-\-monochrome-Do not colorize the output.-Useful when piping the output to another program.-.RS-.RE-.TP-.B \-\-json-Output results is JSON format.-.RS-.RE-.TP .B \-\-invert Return sequences \f[I]not\f[] in the database (used with \f[C]\-\-filter\f[]).@@ -150,12 +118,16 @@ .nf \f[C] $\ sloane\ A000111\ A000112--S\ A000111\ 1,1,1,2,5,16,61,272,1385,7936,50521,353792,2702765,-N\ A000111\ Euler\ or\ up/down\ numbers:\ e.g.f.\ sec(x)\ +\ tan(x)..--S\ A000112\ 1,1,2,5,16,63,318,2045,16999,183231,2567284,46749427,-N\ A000112\ Number\ of\ partially\ ordered\ sets\ ("posets")\ with\ n..+{+\ \ "hops":\ "A000111",+\ \ "name":\ "Euler\ or\ up/down\ numbers:\ e.g.f.\ sec(x)\ +\ tan(x)...",+\ \ "seq":\ [1,1,1,2,5,16,61,272,1385,7936,50521,353792,2702765,22368256,...]+}+{+\ \ "hops":\ "A000112",+\ \ "name":\ "Number\ of\ partially\ ordered\ sets\ (\\"posets\\")\ with\ n\ unlabeled\ elements.",+\ \ "seq":\ [1,1,2,5,16,63,318,2045,16999,183231,2567284,46749427,...]+} \f[] .fi .PP@@ -163,13 +135,19 @@ .IP .nf \f[C]-$\ sloane\ \-n2\ 1,1,2,5,15,52,203,877,4140,21147,115975,678570,--S\ A000110\ 1,1,2,5,15,52,203,877,4140,21147,115975,678570,4213597,-N\ A000110\ Bell\ or\ exponential\ numbers:\ number\ of\ ways\ to\ partition..--S\ A192128\ 1,1,2,5,15,52,203,877,4140,21147,115975,678570,4213597,-N\ A192128\ Number\ of\ set\ partitions\ of\ {1,\ ...,\ n}\ that\ avoid..+$\ sloane\ \-n2\ 1,1,2,5,15,52,203,877,4140,21147,115975,678570+{+\ \ "trail":\ ["{1,1,2,5,15,52,203,877,4140,21147,115975,678570}"],+\ \ "hops":\ "A000110",+\ \ "name":\ "Bell\ or\ exponential\ numbers:\ number\ of\ ways\ to\ partition\ a\ set\ of\ n...",+\ \ "seq":\ [1,1,2,5,15,52,203,877,4140,21147,115975,678570,4213597,27644437,...]+}+{+\ \ "trail":\ ["{1,1,2,5,15,52,203,877,4140,21147,115975,678570}"],+\ \ "hops":\ "A192127",+\ \ "name":\ "Number\ of\ set\ partitions\ of\ {1,\ ...,\ n}\ that\ avoid\ 6\-nestings.",+\ \ "seq":\ [1,1,2,5,15,52,203,877,4140,21147,115975,678570,4213596,27644383,...]+} \f[] .fi .PP@@ -178,21 +156,25 @@ .nf \f[C] $\ hops\ \[aq]y=1+integral(2*y^2\-y);laplace(y)\[aq]\ |\ sloane--S\ A000670\ 1,1,3,13,75,541,4683,47293,545835,7087261,102247563,-N\ A000670\ Fubini\ numbers:\ number\ of\ preferential\ arrangements\ of..--S\ A034172\ 1,1,3,13,75,541,4683,47293,545835,7087261,102247563,-N\ A034172\ Nearest\ integer\ to\ n!/(2*log(2)^(n+1)).+{+\ \ "trail":["y=1+integral(2*y^2\-y);laplace(y)"],+\ \ "hops":"A000670",+\ \ "name":"Fubini\ numbers:\ number\ of\ preferential\ arrangements\ of\ n\ labeled\ ...",+\ \ "seq":[1,1,3,13,75,541,4683,47293,545835,7087261,102247563,1622632573,...]+}+{+\ \ "trail":["y=1+integral(2*y^2\-y);laplace(y)"],+\ \ "hops":"A034172","name":"Nearest\ integer\ to\ n!/(2*log(2)^(n+1)).",+\ \ "seq":[1,1,3,13,75,541,4683,47293,545835,7087261,102247563,1622632573,...]+} \f[] .fi .PP-Show the sequence, name, comments, and formula fields of the sequence-whose A\-number is A006531:+Fetch the OEIS entry whose A\-number is A006531: .IP .nf \f[C]-sloane\ \-k\ SNCF\ \-\-oeis\ id:A006531+sloane\ \-\-oeis\ id:A006531 \f[] .fi .PP@@ -201,50 +183,6 @@ .nf \f[C] sloane\ \-n\ 3\ \-\-oeis\ "(2+2)\-free\ posets"-\f[]-.fi-.PP-\f[C]sloane\f[] normally crops long lines to fit the widths of the-terminal.-If this is unwanted, pipe the output through cat or less:-.IP-.nf-\f[C]-sloane\ \-\-long\ \-\-oeis\ id:A000110\ |\ less\ \-R-\f[]-.fi-.SH KEYS-.PP-These are the keys used by the OEIS (http://oeis.org/eishelp2.html).-.IP-.nf-\f[C]-I\ \ ID\ number--S\ \ 1st\ line\ of\ unsigned\ sequence-T\ \ 2nd\ line\ of\ unsigned\ sequence-U\ \ 3rd\ line\ of\ unsigned\ sequence--V\ \ 1st\ line\ of\ signed\ sequence-W\ \ 2nd\ line\ of\ signed\ sequence-X\ \ 3rd\ line\ of\ signed\ sequence--N\ \ Name-C\ \ Comments-D\ \ References-H\ \ Links-F\ \ Formula-e\ \ Examples--p\ \ Maple\ program-t\ \ Mathematica\ program-o\ \ Program\ in\ other\ language--Y\ \ Cross\-references-K\ \ Keywords-O\ \ Offset-A\ \ Author-E\ \ Extensions\ and\ errors \f[] .fi .SH NOTES
sloane.cabal view
@@ -1,5 +1,5 @@ Name:                sloane-Version:             4.2.0+Version:             5.0.0 Synopsis:            A command line interface to Sloane's OEIS.  Description:         A command line interface to Sloane's On-Line Encyclopedia of@@ -49,7 +49,6 @@                        http-types >=0.8,                        optparse-applicative >=0.10,                        stringsearch >=0.3,-                       terminal-size >=0.2,                        text >=0.11,                        transformers >=0.3,                        resourcet >=1.1
sloane.hs view
@@ -4,14 +4,13 @@ {-# LANGUAGE PolyKinds #-}  -- |--- Copyright   : Anders Claesson 2012-2015+-- Copyright   : Anders Claesson 2012-2016 -- Maintainer  : Anders Claesson <anders.claesson@gmail.com> -- License     : BSD-3 --  module Main (main) where -import Data.List import Data.Aeson import Data.Bits (xor) import Data.Maybe@@ -20,13 +19,8 @@ import qualified Data.Map as M import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as BL-import Data.Text.Encoding (decodeUtf8)-import qualified Data.Text as T-import qualified Data.Text.IO as IO import Control.Applicative-import Control.Monad import System.Directory-import System.Console.ANSI import System.IO import Sloane.OEIS import Sloane.Entry@@ -36,37 +30,35 @@ import Sloane.Bloom import Sloane.DB -nameVer  = "sloane 4.2.0"                 :: String-oeisURL  = "https://oeis.org/search"      :: URL-strpdURL = "https://oeis.org/stripped.gz" :: URL-namesURL = "https://oeis.org/names.gz"    :: URL+versionString :: String+versionString = "5.0.0" -type Query  = B.ByteString-type Width  = Int-type Limit  = Int-type View   = (Width, Palette, [Key])+oeisURL :: URL+oeisURL = "https://oeis.org/search" -data QA = QA Query [Reply]+seqsURL :: URL+seqsURL = "https://oeis.org/stripped.gz" -instance ToJSON QA where-    toJSON (QA q rs) =-        object [ "query" .= String (decodeUtf8 q)-               , "reply" .= toJSON rs-               ]+namesURL :: URL+namesURL = "https://oeis.org/names.gz" +type Limit = Int+ data Input-    = SearchLocalDB (DB Sequences) (DB Names) Bool Limit View [Either ANum PackedSeq]-    | SearchOEIS Bool Limit View String-    | FilterSeqs (DB Sequences) Bool [PackedEntry]+    = SearchLocalDB (DB Seqs) (DB Names) Limit [(Trail, Either ANum PackedSeq)]+    | SearchOEIS Limit String+    | FilterSeqs (DB Seqs) Bool [Entry]     | UpdateDBs FilePath FilePath FilePath     | Empty  data Output-    = OEISReplies View [QA]-    | OEISRepliesJSON [QA]-    | Entries [PackedEntry]+    = OEISReplies [OEISEntry]+    | Entries [Entry]     | NOP +decodeErr :: B.ByteString -> Entry+decodeErr = fromMaybe (error "error decoding JSON") . decodeStrict+ nonEmptyLines :: BL.ByteString -> [B.ByteString] nonEmptyLines = map BL.toStrict . filter (not . BL.null) . BL.lines @@ -82,124 +74,79 @@      | filtr opts = do         db <- readSeqDB cfg-        FilterSeqs db (invert opts) . map parsePackedEntryErr <$> readStdin+        FilterSeqs db (invert opts) . map decodeErr <$> readStdin      | oeis opts =-        return $ SearchOEIS (tojson opts) (limit opts) view (unwords (terms opts))+        return $ SearchOEIS (limit opts) (unwords (terms opts))      | otherwise = do         sdb <- readSeqDB cfg         ndb <- readNamesDB cfg-        let parseInp t = fromMaybe (error "cannot parse input")-                       $  (Right . getPackedSeq <$> parsePackedEntry t)-                      <|> (Left <$> parseANum t)-                      <|> (Right . packSeq . map fromIntegral <$> parseIntegerSeq t)-        SearchLocalDB sdb ndb (tojson opts) (limit opts) view . map parseInp-            <$> case map B.pack (terms opts) of-                  [] -> readStdin-                  ts -> return ts-  where-    view = (termWidth cfg, palette opts, keys opts)--few :: [a] -> Bool-few (_:_:_) = False-few _       = True--allANum :: [QA] -> Bool-allANum= all (\(QA q _) -> B.head q == 'A')--printReply :: View -> Reply -> IO ()-printReply (width, pal, ks) (Reply (ANum anum) (Table table)) =-    forM_ (ks `intersect` M.keys table) $ \key -> do-        let entry = table ! key-        let crop' = crop key (width - 10)-        let mono  = monochrome pal-        forM_ entry $ \line -> do-            unless mono $ setSGR [ SetColor Foreground Dull Green ]-            putStr [key]-            unless mono $ setSGR [ SetColor Foreground Dull Yellow ]-            B.putStr $ indent anum-            unless mono $ setSGR []-            IO.putStrLn $ indent (crop' (decodeUtf8 line))-  where-    indent s = " " <> s-    dropLastField = T.reverse . T.dropWhile (/= ',') . T.reverse-    cropText f cap s = if cap < T.length s then f cap s else s-    crop key = cropText $ \cap s ->-        if key `elem` ['S'..'X']-            then dropLastField (T.take cap s)-            else T.take (cap-2) s <> ".."+        inp <- case terms opts of+                 [] -> readStdin+                 ts -> return (map B.pack ts)+        let parseInp t =+              case decodeStrict t of+                Just e  -> (getPrg e : getTrail e, Right (packSeq (getSeq e)))+                Nothing -> case parseIntegerSeq t of+                             Just s  -> let s' = packSeq s+                                        in ([Prg ("{" <> unPSeq s' <> "}")], Right s')+                             Nothing -> case parseANum t of+                                          Just a  -> ([], Left a)+                                          Nothing -> error "cannot parse input"+        return $ SearchLocalDB sdb ndb (limit opts) (map parseInp inp)  printOutput :: Output -> IO () printOutput NOP = return ()-printOutput (Entries es) =-    forM_ es $ \(PackedEntry (PPrg p) (PSeq s)) ->-        B.putStrLn $ p <> " => {" <> s <> "}"--printOutput (OEISRepliesJSON rss) = mapM_ (BL.putStrLn . encode) rss--printOutput (OEISReplies view rss) = do-    let hideQuery = few rss || allANum rss-    let noReplies = null $ concat [ rs | QA _ rs <- rss ]-    forM_ (zip [1::Int ..] rss) $ \(i, QA q replies) -> do-        unless hideQuery $ do-            when (i > 1) $ putStrLn ""-            B.putStrLn ("query: " <> q)-        forM_ replies $ \r -> do-            putStrLn ""-            printReply view r-    unless noReplies $ putStrLn ""+printOutput (Entries es) = mapM_ (BL.putStrLn . encode) es+printOutput (OEISReplies rs) = mapM_ (BL.putStrLn . encode) rs  -- Construct a list of replies associated with a list of A-numbers.-mkReplies :: Map ANum PackedSeq -> Map ANum Name -> [ANum] -> [Reply]-mkReplies s n anums =-    [ Reply k (Table $ M.fromList [('S', [unPSeq (s!k)]), ('N', [n!k])])-    | k <- anums-    , M.member k s && M.member k n+mkReplies :: Map ANum [Integer] -> Map ANum Name -> [(Trail, ANum)] -> [Entry]+mkReplies s n tas =+    [ Entry (Prg b) (s!a) Nothing (Just (n!a)) trail+    | (trail, a@(ANum b)) <- tas+    , M.member a s && M.member a n     ]  sloane :: Input -> IO Output sloane inp =     case inp of -      SearchLocalDB sdb ndb jsonflag maxReplies view ts -> do+      SearchLocalDB sdb ndb maxReplies ts -> do           let sm = M.fromList $ parseStripped (unDB sdb)           let nm = M.fromList $ parseNames (unDB ndb)-          let qas = [ QA q (mkReplies sm nm ks)-                    | (q, ks) <--                        [ case t of-                            Right s@(PSeq r) -> (r, grepN maxReplies s sdb)-                            Left (ANum anum) -> (anum, [ANum anum])-                        | t <- ts-                        ]-                    ]-          return $ if jsonflag then OEISRepliesJSON qas else OEISReplies view qas+          let anums (trail, Left  a) = [ (trail, a) ]+              anums (trail, Right s) = [ (trail, a) | a <- grepN maxReplies s sdb ]+          return $ Entries (mkReplies sm nm . anums =<< ts) -      SearchOEIS jsonflag lim view q -> do+      SearchOEIS lim q -> do           let kvs = [("n", B.pack (show lim)), ("q", B.pack q), ("fmt", "text")]           replies <- requestPage oeisURL kvs-          let qas = [QA (B.pack q) (parseReplies replies)]-          return $ if jsonflag then OEISRepliesJSON qas else OEISReplies view qas+          return $ OEISReplies (parseOEISEntries replies)        FilterSeqs db invFlag es -> do           let bloom = mkBloomFilter db           return $ Entries-              [ e | e@(PackedEntry _ s) <- es+              [ e | e <- es+              , let s = packSeq (getSeq e)               , not (B.null (unPSeq s))               , invFlag `xor` (s `isFactorOf` bloom && not (null (grep s db)))               ]        UpdateDBs sloanedir sdbPath ndbPath -> do           createDirectoryIfMissing False sloanedir-          let msg1 = "Downloading " ++ strpdURL ++ ": "+          let msg1 = "Downloading " ++ seqsURL ++ ": "           let msg2 = "Downloading " ++ namesURL ++ ": "           putStr msg1 >> hFlush stdout-          download (length msg1) strpdURL sdbPath >> putStrLn ""+          download (length msg1) seqsURL sdbPath >> putStrLn ""           putStr msg2 >> hFlush stdout           download (length msg2) namesURL ndbPath >> putStrLn ""           return NOP -      Empty -> putStrLn nameVer >> return NOP+      Empty -> do+          putStrLn $ "sloane " ++ versionString+          return NOP  -- | Main function and entry point for sloane. main :: IO ()