{-# LANGUAGE OverloadedStrings
, ScopedTypeVariables
, StandaloneDeriving #-}
module System.Posix.ARX.CLI.CLTokens where
import Prelude hiding (takeWhile)
import Control.Applicative
import Data.ByteString (ByteString)
import Data.ByteString.Char8 ()
import Data.Either
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Attoparsec.ByteString.Char8 ( char8, choice, decimal, endOfInput,
inClass, isDigit, parseOnly, Parser,
satisfy, string, takeWhile,
takeWhile1, try )
data Class = EnvBinding
| QualifiedPath
| DashDash
| LongOption
| Dash
| ShortOption
| URL
| HexNum
| DecimalNum
| Size
deriving instance Eq Class
deriving instance Ord Class
deriving instance Show Class
match :: Class -> ByteString -> Bool
match = (e2b .) . parseOnly . recognizer
where
e2b (Left _) = False
e2b (Right _) = True
recognize :: ByteString -> Maybe Class
recognize = e2m . parseOnly (choice recognizers)
where
e2m (Left _) = Nothing
e2m (Right x) = Just x
recognizeIt x = x <$ recognizer x
recognizers = recognizeIt <$> [ EnvBinding,
QualifiedPath,
DashDash,
LongOption,
Dash,
ShortOption,
URL,
HexNum,
DecimalNum ]
exemplar :: Class -> ByteString
exemplar cls = case cls of
EnvBinding -> "VAR=value"
QualifiedPath -> "./qualified/path"
DashDash -> "--"
LongOption -> "--long-option"
Dash -> "-"
ShortOption -> "-shortopt"
URL -> "scheme://url-to-resource"
HexNum -> "0xA12FE"
DecimalNum -> "0123456789"
Size -> "4MiB"
recognizer :: Class -> Parser ()
recognizer cls = case cls of
EnvBinding -> () <$ do satisfy varFirst
takeWhile varBody
char8 '='
QualifiedPath -> () <$ do string "/" <|> string "./"
<|> string "../"
DashDash -> string "--" *> endOfInput
LongOption -> () <$ (string "--" >> satisfy (/= '-'))
Dash -> char8 '-' *> endOfInput
ShortOption -> () <$ (char8 '-' >> satisfy (/= '-'))
URL -> () <$ do takeWhile1 isURLSchemeChar
many $ do char8 '+' <|> char8 '/'
takeWhile1 isURLSchemeChar
string "://"
HexNum -> string "0x" >> takeWhile1 isHexDigit
*> endOfInput
DecimalNum -> takeWhile1 isDigit *> endOfInput
Size -> () <$ size
schemeSeparator = char8 '+' <|> char8 '/'
varFirst = inClass "a-zA-Z_"
varBody = inClass "a-zA-Z_0-9"
isLongOptionChar = inClass "a-zA-Z0-9-"
isShortOptionChar = inClass "a-zA-Z0-9!?"
isSchemeChar = inClass "a-z0-9"
isHexDigit = inClass "0-9a-fA-F"
isURLSchemeChar = inClass "a-z0-9"
sizes :: Map ByteString Integer
sizes = Map.fromList
[ ("B", 1),
("K", 2^10), ("KiB", 2^10), ("kB", 10^03),
("M", 2^20), ("MiB", 2^20), ("MB", 10^06),
("G", 2^30), ("GiB", 2^30), ("GB", 10^09),
("T", 2^40), ("TiB", 2^40), ("TB", 10^12),
("P", 2^50), ("PiB", 2^50), ("PB", 10^15),
("E", 2^60), ("EiB", 2^60), ("EB", 10^18),
("Z", 2^70), ("ZiB", 2^70), ("ZB", 10^21),
("Y", 2^80), ("YiB", 2^80), ("YB", 10^24) ]
size :: Parser Integer
size = (*) <$> decimal <*> suffix
where
asSuffix (k, v) = v <$ try (string k <* endOfInput)
suffix = choice (asSuffix <$> Map.toList sizes)
sizeBounded :: forall b . (Bounded b, Integral b) => Parser b
sizeBounded = fromInteger . min (toInteger (maxBound :: b)) <$> size