packages feed

sloane-1.3: sloane.hs

{-# LANGUAGE DeriveDataTypeable #-}

-- |
-- Copyright   : Anders Claesson 2012, 2013, 2014
-- Maintainer  : Anders Claesson <anders.claesson@gmail.com>
-- License     : BSD-3
--
import Prelude hiding (all, putStrLn, putStr)
import Data.ByteString.Char8 (putStrLn, putStr, pack, empty)
import System.Console.ANSI
import System.Console.CmdArgs
import System.Console.Terminal.Size (Window(..), size)
import Data.Maybe (fromJust)
import Control.Monad (unless, guard)
import Network.HTTP (simpleHTTP, getRequest, getResponseBody)
import Network.URL (importURL, exportURL, add_param)

type OEISEntries = [String]
type ANumbers = [String]
type Query = String
type Keys = String

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

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

sloane = cmdArgsMode $ Sloane
  { keys = "SN"  &= typ "KEYS" &= help "Keys of fields to print (default: SN)"
  , all  = False &= name "a"   &= help "Print all fields"
  , url  = False &= name "u"   &= help "Print urls of found entries"
  , limit = 5 &= name "n" &= help "Retrieve at most this many entries (default: 5)"
  , terms = def &= argPos 0 &= typ "SEARCH-TERMS"
  }
  &= versionArg [summary "sloane 1.3"]
  &= summary "Search Sloane's On-Line Encyclopedia of Integer Sequences"

select :: Keys -> OEISEntries -> OEISEntries
select ks = filter (\line -> null line || head line `elem` ks)

aNumbers :: OEISEntries -> ANumbers
aNumbers es = [ words ids !! 1 | ids@(_:_) <- select "I" es ]

urls :: OEISEntries -> String
urls = unlines . map ((oeisHost ++ "/") ++ ) . aNumbers

searchOEIS :: Int -> Query -> IO OEISEntries
searchOEIS n s =
    trim `fmap` (simpleHTTP (getRequest url) >>= getResponseBody)
  where
    trim = map (drop 1) . reverse . drop 2 . reverse . drop 5 . lines
    url = exportURL $ oeisURL `add_param` ("n", show n) `add_param` ("q", s)

cropSeq :: Int -> String -> String
cropSeq maxLen = reverse . dropWhile (/= ',') . reverse . take maxLen

cropLine :: Int -> String -> String
cropLine maxLen s
    | maxLen >= length s = s
    | otherwise          = take (maxLen-2) s ++ ".."

getWidth :: IO Int
getWidth = do
    win <- size
    case win of
        Nothing  -> error "Can't get width of terminal"
        Just win -> return $ width win

put = putStr . pack
putLn = putStrLn . pack
newline = putStrLn empty

putEntries :: Int -> OEISEntries -> IO ()
putEntries width = mapM_ $ \line ->
    case words line of
        [] -> newline
        (key:aNum:rest) -> do
            setSGR [ SetColor Foreground Dull Green ]
            put key
            setSGR [ SetColor Foreground Dull Yellow ]
            put $ ' ' : aNum
            setSGR []
            let crop = if key == "S" then cropSeq else cropLine
            put $ ' ' : crop width (unwords rest) ++ "\n"

main = do
    args  <- cmdArgsRun sloane
    ncols <- getWidth
    let pick = if all args then id else select (keys args)
    let query = filter (`notElem` "[{}]") $ terms args
    hits <- searchOEIS (limit args) query
    unless (null hits) $ do
        newline
        if url args
            then put (urls hits)
            else putEntries (ncols - 10) (pick hits)
        newline