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 +3/−3
- Sloane/Config.hs +1/−6
- Sloane/DB.hs +5/−8
- Sloane/Entry.hs +51/−36
- Sloane/OEIS.hs +48/−102
- Sloane/Options.hs +5/−40
- sloane.1 +66/−128
- sloane.cabal +1/−2
- sloane.hs +54/−107
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 ()