packages feed

sloane-0.1: sloane.hs

{-# LANGUAGE DeriveDataTypeable #-}

import Prelude hiding (all)
import System.Console.CmdArgs
import Data.Maybe (fromJust, isJust)
import Data.Function (on)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Control.Monad (when)
import System.IO.Unsafe (unsafePerformIO)
import Network.HTTP (simpleHTTP, getRequest, getResponseBody)
import Network.URL (importURL, exportURL, add_param)
import Data.List (groupBy, sortBy, intersperse)
import Data.Ord (comparing)

newtype OEISEntry = OEISEntry [(Char, String)]

oeisURL = fromJust $ importURL "http://oeis.org/search?fmt=text"
oeisKeys = "ISTUVWXNDHFYAOEeptoKC"

data Sloane = Sloane { keys  :: String
                     , all   :: Bool
                     , limit :: Int
                     , terms :: String
                     }
              deriving (Data, Typeable)

sloane = cmdArgsMode $ Sloane 
         { keys = "SN" &= typ "KEYS" 
           &= help "Keys of fields to print, http://oeis.org/eishelp1.html (default: SN)"
         , all = False 
           &= help "Print all fields"
         , limit = 5 &= name "n"
           &= help "Limit the number of entries retrieved (default: 5)"
         , terms = def &= argPos 0 &= typ "SEARCH-TERMS"
         } 
         &= versionArg [summary "sloane 0.1"]
         &= summary "Search Sloane's On-Line Encyclopedia of Integer Sequences"

-- The maximum number of results returned when searching the OEIS.
maxResults :: IORef Int
maxResults = unsafePerformIO $ newIORef 5

-- Get the maximum number of results returned when searching OEIS
getMaxResults :: IO Int
getMaxResults = readIORef maxResults

-- Set the maximum number of results returned when searching OEIS
setMaxResults :: Int -> IO ()
setMaxResults = writeIORef maxResults

nonempty = not . null

parseOEISEntry :: String -> OEISEntry
parseOEISEntry = OEISEntry
                 . map merge
                 . groupBy ((==) `on` fst)
                 . sortBy (comparing fst)
                 . map (split . tail) . filter nonempty . lines
    where
      split xs@(x:_) = (x, xs)
      merge kvs = let (k:_, vs) = unzip kvs in (k, init $ unlines vs)

parseOEISEntries :: String -> [OEISEntry]
parseOEISEntries = map parseOEISEntry
                   . filter nonempty
                   . map unlines
                   . groupBy (\_ x -> nonempty x)
                   . reverse . drop 2 . reverse . drop 5 . lines

-- Returns the given fields of an 'OEISEntry'. For instance, @fields "NFA" e@
-- would return the name, formula, and auhtor fields of @e@.
fields :: String -> OEISEntry -> [String]
fields ks (OEISEntry e) = [ fromJust d | k<-ks, let d = lookup k e, isJust d ]

-- Returns a list of matching entries in OEIS
lookupOEIS :: String -> IO [OEISEntry]
lookupOEIS s = do
  n <- getMaxResults
  parseOEISEntries `fmap` (simpleHTTP (getRequest (url n)) >>= getResponseBody)
    where
      url n = exportURL $ oeisURL `add_param` ("n", show n) `add_param` ("q", s)

main = do
  args <- cmdArgsRun sloane
  setMaxResults $ limit args
  es <- lookupOEIS . filter (`notElem` "[]") $ terms args
  when (nonempty es) $ putStrLn ""
  let ks = if all args then oeisKeys else keys args
  mapM_ putStrLn [ unlines $ fields ks e | e <- es ]