packages feed

smh 0.1.3.1 → 0.2.0

raw patch · 7 files changed

+384/−146 lines, 7 filesdep −scientificPVP ok

version bump matches the API change (PVP)

Dependencies removed: scientific

API changes (from Hackage documentation)

- Common: safeDiv :: Scientific -> Scientific -> Scientific
- Common: scient :: Parser Scientific
- Common: showScientific :: Scientific -> Text
- Focusers: readMaybeScientific :: Text -> Maybe Scientific
+ Common: fromIndexes :: Int -> Text -> [(Int, Int)] -> ([Text], [Text])
+ Common: rational :: Parser Rational
+ Common: readMaybeDouble :: Text -> Maybe Double
+ Common: readMaybeInteger :: Text -> Maybe Integer
+ Common: readMaybeRational :: Text -> Maybe Rational
+ Common: showRational :: Rational -> Text
+ Focusers: Default :: KeyType
+ Focusers: InDoubleQuotes :: Text -> KeyType
+ Focusers: InQuotes :: Text -> KeyType
+ Focusers: data KeyType
+ Focusers: escaping :: Char -> Char -> Int -> Parser ()
+ Focusers: escapingColonCurlyBrace :: ParsecT Void Text Identity ()
+ Focusers: escapingCommaCurlyBrace :: ParsecT Void Text Identity ()
+ Focusers: escapingCommaSquareBrace :: ParsecT Void Text Identity ()
+ Focusers: focusAtIdx :: Int -> Focuser
+ Focusers: focusAtKey :: Text -> Focuser
+ Focusers: focusEl :: Focuser
+ Focusers: focusKV :: Focuser
+ Focusers: focusKey :: Focuser
+ Focusers: focusVal :: Focuser
+ Focusers: inCurlyBraces :: ParsecT Void Text Identity ()
+ Focusers: inDoubleQuotes :: ParsecT Void Text Identity ()
+ Focusers: inParens :: ParsecT Void Text Identity ()
+ Focusers: inQuotes :: ParsecT Void Text Identity ()
+ Focusers: inSquareBraces :: ParsecT Void Text Identity ()
+ Focusers: parseElemIdxs :: Parser (Int, Int)
+ Focusers: parseKVIdxs :: Parser ((Int, Int), (Int, Int))
+ Focusers: parseKeyIdxs :: Parser (Int, Int)
+ Focusers: parseListElemIdxs :: Parser [(Int, Int)]
+ Focusers: parseObjKVIdxs :: Parser [((Int, Int), (Int, Int))]
+ Focusers: parseValIdxs :: Parser (Int, Int)
+ Focusers: setKey :: Text -> Text -> Focus
+ Focusers: skipKey :: Parser ()
+ Focusers: skipListElem :: Parser ()
+ Focusers: skipVal :: Parser ()
+ Focusers: stripKey :: Text -> KeyType
+ Parsers: parseFocusAtIdx :: Parser Focuser
+ Parsers: parseFocusAtKey :: Parser Focuser
- Common: ENumber :: !Scientific -> Evaluatable
+ Common: ENumber :: !Rational -> Evaluatable
- Focusers: average :: Scientific -> [Scientific] -> Scientific
+ Focusers: average :: Rational -> [Rational] -> Rational
- Focusers: focusAverage :: Scientific -> Focuser
+ Focusers: focusAverage :: Rational -> Focuser
- Focusers: getAverage :: Scientific -> Focus -> Focus
+ Focusers: getAverage :: Rational -> Focus -> Focus
- Mappings: mappingAdd :: Scientific -> Mapping
+ Mappings: mappingAdd :: Rational -> Mapping
- Mappings: mappingDiv :: Scientific -> Mapping
+ Mappings: mappingDiv :: Rational -> Mapping
- Mappings: mappingMath :: (Scientific -> Scientific) -> Mapping
+ Mappings: mappingMath :: (Rational -> Rational) -> Mapping
- Mappings: mappingMult :: Scientific -> Mapping
+ Mappings: mappingMult :: Rational -> Mapping
- Mappings: mappingSub :: Scientific -> Mapping
+ Mappings: mappingSub :: Rational -> Mapping

Files

CHANGELOG.md view
@@ -16,4 +16,14 @@  * made slicing more flexible (you can now change the length of the focused segment) -* made regex user perl style instead of POSIX style+* made regex use perl style instead of POSIX style++## 0.2.0 -- 2024-03-10++* added "el", "kv", "key", "val", "atKey", "atIdx" focusers++* switched from internally using scientific to Rational++* fixed "regex" (it was very broken, lol)++* added escaping support to string literals (only \")
smh.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               smh-version:            0.1.3.1+version:            0.2.0 synopsis:           String manipulation tool written in haskell description:        String manipulation CLI tool based on optics category:           CLI Tool@@ -24,9 +24,9 @@     main-is:          Main.hs     other-modules:    Paths_smh     autogen-modules:  Paths_smh-    build-depends:    base ^>=4.17.2.1,+    build-depends:    base >= 4.17 && < 4.20,                       megaparsec >= 9.6.1 && < 9.7,-                      text >= 2.0.2 && < 2.1,+                      text >= 2.0.2 && < 2.2,                       lib     hs-source-dirs:   app     default-language: Haskell2010@@ -37,15 +37,14 @@                       Focusers,                       Mappings,                       Parsers-    build-depends:    base ^>=4.17.2.1,+    build-depends:    base >= 4.17 && < 4.20,                       megaparsec >= 9.6.1 && < 9.7,                       lens >= 5.2.3 && < 5.3,-                      scientific >= 0.3.7 && < 0.4,-                      text >= 2.0.2 && < 2.1,+                      text >= 2.0.2 && < 2.2,                       array >= 0.5.4 && < 0.6,                       extra >= 1.7.14 && < 1.8,                       regex-pcre-builtin >= 0.95.2 && < 0.96,-                      loop >= 0.3.0 && < 0.4+                      loop >= 0.3.0 && < 0.4,     hs-source-dirs:   src     default-language: Haskell2010 @@ -54,13 +53,12 @@     main-is:          Main.hs     hs-source-dirs:   test     ghc-options:      -threaded -rtsopts -with-rtsopts=-N-    build-depends:    base ^>=4.17.2.1,+    build-depends:    base >= 4.17 && < 4.20,                       tasty,                       tasty-hunit,                       process,                       extra >= 1.7.14 && < 1.8,                       lib,-                      scientific >= 0.3.7 && < 0.4,-                      text >= 2.0.2 && < 2.1+                      text >= 2.0.2 && < 2.2     default-language: Haskell2010 
src/Common.hs view
@@ -1,9 +1,11 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE RankNTypes         #-}+{-# LANGUAGE ViewPatterns       #-}+{-# LANGUAGE KindSignatures #-}  module Common(module Common) where -import           Control.Applicative        (empty)+import           Control.Applicative        (empty, (<|>)) import           Control.Lens               (Lens', Traversal', lens, (^..)) import           Control.Loop               (numLoop) import           Control.Monad              (forM_)@@ -13,8 +15,7 @@ import           Data.Data                  (Data) import           Data.List                  (nub, sort) import           Data.List.Extra            (nubOrd)-import           Data.Scientific            (Scientific, floatingOrInteger,-                                             fromFloatDigits, toRealFloat)+import           Data.Ratio                 (denominator, numerator) import           Data.STRef                 () import           Data.Text                  (Text) import qualified Data.Text                  as T@@ -22,6 +23,7 @@ import           Text.Megaparsec            (Parsec, label) import           Text.Megaparsec.Char       (space1) import qualified Text.Megaparsec.Char.Lexer as L+import           Text.Read                  (readMaybe)  data Focus     = FText !Text@@ -65,13 +67,10 @@  type Parser = Parsec Void Text -showScientific :: Scientific -> Text-showScientific n = case floatingOrInteger n :: Either Double Int of-    Left d  -> T.pack $ show d-    Right i -> T.pack $ show i--safeDiv :: Scientific -> Scientific -> Scientific-safeDiv a b = fromFloatDigits (toRealFloat a / toRealFloat b)+showRational :: Rational -> Text+showRational n = if denominator n == 1+    then T.pack $ show (numerator n)+    else T.pack $ show (fromInteger (numerator n) / fromInteger (denominator n))  data Range     = RangeSingle !Int@@ -99,7 +98,7 @@  data Evaluatable     = EText !Text-    | ENumber !Scientific+    | ENumber !Rational     | EFocuser { evalFocuserUnsafe :: !Focuser }  data IfExpr@@ -135,8 +134,8 @@ integer :: Parser Int integer = label "integer" $ lexeme $ L.signed ws L.decimal -scient :: Parser Scientific-scient = label "number" $ lexeme $ L.signed ws L.scientific+rational :: Parser Rational+rational = toRational <$> label "number" (lexeme $ L.signed ws L.scientific)  mapText :: (Char -> a) -> Text -> [a] mapText f = T.foldr (\c cs -> f c : cs) []@@ -164,3 +163,19 @@ mappingTo (FTrav trav) focus = case (focus, focus ^.. trav) of     (FText _, [FText str]) -> FText str     _                      -> focus++fromIndexes :: Int -> Text -> [(Int, Int)] -> ([Text], [Text])+fromIndexes _ str [] = ([str], [])+fromIndexes offset str ((i, j) : is) =+    let (nonMatch, T.splitAt j -> (match, str')) = T.splitAt (i - offset) str+        (nonMatches, matches) = fromIndexes (i + j) str' is+    in  (nonMatch : nonMatches, match : matches)++readMaybeRational :: Text -> Maybe Rational+readMaybeRational s = (toRational <$> readMaybeInteger s) <|> (toRational <$> readMaybeDouble s)++readMaybeInteger :: Text -> Maybe Integer+readMaybeInteger = readMaybe . T.unpack++readMaybeDouble :: Text -> Maybe Double+readMaybeDouble = readMaybe . T.unpack
src/Focusers.hs view
@@ -3,19 +3,20 @@ {-# LANGUAGE LambdaCase        #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes        #-}-{-# LANGUAGE ViewPatterns      #-}  module Focusers where  import           Common               (Comparison (..), Evaluatable (..),                                        Focus (..), Focuser (..), IfExpr (..),-                                       Mapping, Oper (..), Quantor (..),+                                       Mapping, Oper (..), Parser, Quantor (..),                                        Range (RangeSingle), _toListUnsafe,-                                       composeFocusers, getIndexes,-                                       makeFilteredText, mapText, safeDiv,-                                       showScientific, toListUnsafe,-                                       toTextUnsafe, unsort)+                                       composeFocusers, fromIndexes, getIndexes,+                                       lexeme, makeFilteredText, mapText,+                                       readMaybeRational, showRational, symbol,+                                       toListUnsafe, toTextUnsafe, unsort, ws)+import           Control.Applicative  ((<|>)) import           Control.Lens         (lens, partsOf, (^..))+import           Control.Monad        (void, when, zipWithM) import           Data.Char            (isAlpha, isAlphaNum, isDigit, isLower,                                        isSpace, isUpper) import           Data.Data.Lens       (biplate)@@ -24,9 +25,13 @@ import           Data.List            (sortBy, transpose) import           Data.Maybe           (mapMaybe) import           Data.Ord             (comparing)-import           Data.Scientific      (Scientific)+import           Data.Ratio           (denominator) import           Data.Text            (Text) import qualified Data.Text            as T+import           Text.Megaparsec      (anySingle, anySingleBut, between, choice,+                                       empty, getOffset, many, optional,+                                       parseMaybe, satisfy, sepBy, some, try)+import           Text.Megaparsec.Char (char) import           Text.Read            (readMaybe) import           Text.Regex.PCRE      (AllMatches (getAllMatches), (=~)) import           Text.Regex.PCRE.Text ()@@ -271,38 +276,39 @@  getSum :: Focus -> Focus getSum focus = case focus of-    FList _ -> FText $ showScientific $ sum $-        mapMaybe readMaybeScientific $ focus ^.. biplate-    FText s -> FText $ showScientific $ sum $-        mapMaybe (readMaybeScientific . T.singleton) $ T.unpack s+    FList _ -> FText $ showRational $ sum $+        mapMaybe readMaybeRational $ focus ^.. biplate+    FText s -> FText $ showRational $ sum $+        mapMaybe (readMaybeRational . T.singleton) $ T.unpack s  focusProduct :: Focuser focusProduct = FTrav $ lens getProduct const  getProduct :: Focus -> Focus getProduct focus = case focus of-    FList _ -> FText $ showScientific $ product $-        mapMaybe readMaybeScientific $ focus ^.. biplate-    FText s -> FText $ showScientific $ product $-        mapMaybe (readMaybeScientific . T.singleton) $ T.unpack s+    FList _ -> FText $ showRational $ product $+        mapMaybe readMaybeRational $ focus ^.. biplate+    FText s -> FText $ showRational $ product $+        mapMaybe (readMaybeRational . T.singleton) $ T.unpack s -focusAverage :: Scientific -> Focuser+focusAverage :: Rational -> Focuser focusAverage n = FTrav $ lens (getAverage n) const -getAverage :: Scientific -> Focus -> Focus+getAverage :: Rational -> Focus -> Focus getAverage n focus = case focus of-    FList _ -> FText $ showScientific $ average n $-        mapMaybe readMaybeScientific $ focus ^.. biplate-    FText s -> FText $ showScientific $ average n $-        mapMaybe (readMaybeScientific . T.singleton) $ T.unpack s+    FList _ -> FText $ showRational $ average n $+        mapMaybe readMaybeRational $ focus ^.. biplate+    FText s -> FText $ showRational $ average n $+        mapMaybe (readMaybeRational . T.singleton) $ T.unpack s -average :: Scientific -> [Scientific] -> Scientific+average :: Rational -> [Rational] -> Rational average n [] = n average _ xs = sum xs / fromIntegral (length xs) -readMaybeScientific :: Text -> Maybe Scientific-readMaybeScientific = readMaybe . T.unpack +++ focusIf :: IfExpr -> Focuser focusIf ifexpr = FTrav $ \f focus -> if focus `passesIf` ifexpr     then f focus@@ -324,22 +330,22 @@             (QAny, QAll) -> any and results             (QAny, QAny) -> any or results -    evaluateEval :: Focus -> Evaluatable -> [Either Scientific Focus]+    evaluateEval :: Focus -> Evaluatable -> [Either Rational Focus]     evaluateEval focus eval = case eval of         EText s               -> [Right $ FText s]         ENumber n             -> [Left n]         EFocuser (FTrav trav) -> Right <$> focus ^.. trav -    applyOp :: Oper -> Either Scientific Focus -> Either Scientific Focus -> Bool+    applyOp :: Oper -> Either Rational Focus -> Either Rational Focus -> Bool     applyOp OpEq (Left n1) (Left n2) = n1 == n2     applyOp OpEq (Right f1) (Right f2) = f1 == f2-    applyOp OpEq (Left n1) (Right (FText s2)) = Just n1 == readMaybeScientific s2-    applyOp OpEq (Right (FText s1)) (Left n2) = readMaybeScientific s1 == Just n2+    applyOp OpEq (Left n1) (Right (FText s2)) = Just n1 == readMaybeRational s2+    applyOp OpEq (Right (FText s1)) (Left n2) = readMaybeRational s1 == Just n2     applyOp OpNe (Left n1) (Left n2) = n1 /= n2-    applyOp OpNe (Left n1) (Right (FText s2)) = case readMaybeScientific s2 of+    applyOp OpNe (Left n1) (Right (FText s2)) = case readMaybeRational s2 of         Just n2 -> n1 /= n2         Nothing -> False-    applyOp OpNe (Right (FText s1)) (Left n2) = case readMaybeScientific s1 of+    applyOp OpNe (Right (FText s1)) (Left n2) = case readMaybeRational s1 of         Just n1 -> n1 /= n2         Nothing -> False     applyOp OpNe (Right f1) (Right f2) = f1 /= f2@@ -352,18 +358,18 @@       where         applyOpOrd             :: (forall a. Ord a => a -> a -> Bool)-            -> Either Scientific Focus-            -> Either Scientific Focus+            -> Either Rational Focus+            -> Either Rational Focus             -> Bool         applyOpOrd f (Left n1) (Left n2) = f n1 n2-        applyOpOrd f (Left n1) (Right (FText s2)) = case readMaybeScientific s2 of+        applyOpOrd f (Left n1) (Right (FText s2)) = case readMaybeRational s2 of             Just n2 -> f n1 n2             Nothing -> False-        applyOpOrd f (Right (FText s1)) (Left n2) = case readMaybeScientific s1 of+        applyOpOrd f (Right (FText s1)) (Left n2) = case readMaybeRational s1 of             Just n1 -> f n1 n2             Nothing -> False         applyOpOrd f (Right (FText s1)) (Right (FText s2)) =-            case (readMaybeScientific s1, readMaybeScientific s2) of+            case (readMaybeRational s1, readMaybeRational s2) of                 (Just n1, Just n2) -> f n1 n2                 _                  -> f s1 s2         applyOpOrd _ _ _ = False@@ -413,15 +419,7 @@             newMatches = map toTextUnsafe <$> traverse (f . FText) matches         in  FText . T.concat . interleave nonMatches <$> newMatches     _ -> pure focus-  where-    fromIndexes :: Int -> Text -> [(Int, Int)] -> ([Text], [Text])-    fromIndexes _ str [] = ([str], [])-    fromIndexes offset str ((i, j) : is) =-        let (nonMatch, T.splitAt j -> (match, str')) = T.splitAt (i - offset) str-            (nonMatches, matches) = fromIndexes (offset + i + j) str' is-        in  (nonMatch : nonMatches, match : matches) - focusFilter :: IfExpr -> Focuser focusFilter pred = focusCollect $ focusEach `composeFocusers` focusIf pred @@ -451,5 +449,167 @@  focusLength :: Focuser focusLength = FTrav $ \f focus -> case focus of-    FText s          -> f . FText . T.pack . show . T.length $ s+    fs@(FText s)     -> fs <$ f (FText . T.pack . show . T.length $ s)     flst@(FList lst) -> flst <$ f (FText . T.pack . show . length $ lst)++parseListElemIdxs :: Parser [(Int, Int)]+parseListElemIdxs = do+    symbol "["+    idxs <- parseElemIdxs `sepBy` symbol ","+    symbol "]"+    pure idxs++parseElemIdxs :: Parser (Int, Int)+parseElemIdxs = lexeme $ do+    idx1 <- getOffset+    skipListElem+    idx2 <- getOffset+    pure (idx1, idx2 - idx1)++skipListElem :: Parser ()+skipListElem = choice+    [ inQuotes+    , inDoubleQuotes+    , inSquareBraces+    , inParens+    , inCurlyBraces+    , escapingCommaSquareBrace]+    >> void (optional (try $ ws >> skipListElem))++inQuotes = char '\'' >> escaping '\'' '\'' 1+inDoubleQuotes = char '"' >> escaping '"' '"' 1+inSquareBraces = char '[' >> escaping '[' ']' 1+inParens = char '(' >> escaping '(' ')' 1+inCurlyBraces = char '{' >> escaping '{' '}' 1+escapingCommaSquareBrace = void $ some $ satisfy (\c -> c /= ',' && c /= ']' && not (isSpace c))++escaping :: Char -> Char -> Int -> Parser ()+escaping start end depth = choice+    [ char end >> if depth == 1 then return () else void (optional $ escaping start end (depth - 1))+    , char start >> void (optional $ escaping start end (depth + 1))+    , char '\\' >> anySingle >> void (optional $ escaping start end depth)+    , anySingle >> void (optional $ escaping start end depth)+    ]++focusEl :: Focuser+focusEl = FTrav $ \f focus -> case focus of+    FText s -> case parseMaybe parseListElemIdxs s of+        Just idxs ->+            let (nonMatches, matches) = fromIndexes 0 s idxs+                newMatches = map toTextUnsafe <$> traverse (f . FText) matches+            in  FText . T.concat . interleave nonMatches <$> newMatches+        Nothing -> pure focus+    FList _ -> pure focus++parseObjKVIdxs :: Parser [((Int, Int), (Int, Int))]+parseObjKVIdxs = do+    symbol "{"+    idxs <- parseKVIdxs `sepBy` symbol ","+    symbol "}"+    pure idxs++parseKVIdxs :: Parser ((Int, Int), (Int, Int))+parseKVIdxs = do+    keyIdxs <- parseKeyIdxs+    symbol ":"+    valIdxs <- parseValIdxs+    pure (keyIdxs, valIdxs)++parseKeyIdxs :: Parser (Int, Int)+parseKeyIdxs = lexeme $ do+    idx1 <- getOffset+    skipKey+    idx2 <- getOffset+    pure (idx1, idx2 - idx1)++skipKey :: Parser ()+skipKey = choice+    [ inQuotes+    , inDoubleQuotes+    , inSquareBraces+    , inParens+    , inCurlyBraces+    , escapingColonCurlyBrace]+    >> void (optional (try $ ws >> skipKey))++escapingColonCurlyBrace = void $ some $ satisfy (\c -> c /= ':' && c /= '}' && not (isSpace c))++parseValIdxs :: Parser (Int, Int)+parseValIdxs = lexeme $ do+    idx1 <- getOffset+    skipVal+    idx2 <- getOffset+    pure (idx1, idx2 - idx1)++skipVal :: Parser ()+skipVal = choice+    [ inQuotes+    , inDoubleQuotes+    , inSquareBraces+    , inParens+    , inCurlyBraces+    , escapingCommaCurlyBrace]+    >> void (optional (try $ ws >> skipVal))++escapingCommaCurlyBrace = void $ some $ satisfy (\c -> c /= ',' && c /= '}' && not (isSpace c))++focusKV :: Focuser+focusKV = FTrav $ \f focus -> case focus of+    FText s -> case parseMaybe parseObjKVIdxs s of+        Just idxs ->+            let idxs_ = concatMap (\(a, b) -> [a, b]) idxs+                (nonMatches, matches) = fromIndexes 0 s idxs_+                matches_ = pairUp $ map FText matches+                newMatches_ = map toListUnsafe <$> traverse (f . FList) matches_+                newMatches = map toTextUnsafe . concat <$> newMatches_+            in  FText . T.concat . interleave nonMatches <$> newMatches+        Nothing -> pure focus+    FList _ -> pure focus+  where+    pairUp :: [a] -> [[a]]+    pairUp []             = []+    pairUp (a1 : a2 : as) = [a1, a2] : pairUp as+    pairUp _              = error "pairUp: list too short"++data KeyType+    = InQuotes Text+    | InDoubleQuotes Text+    | Default++focusKey :: Focuser+focusKey = FTrav $ \f focus -> case focus of+    FList [FText key, FText val] -> case stripKey key of+        InQuotes key_ -> setKey val . (\k -> "'" <> k <> "'") . toTextUnsafe <$> f (FText key_)+        InDoubleQuotes key_ -> setKey val . (\k -> "\"" <> k <> "\"") . toTextUnsafe <$> f (FText key_)+        Default -> setKey val . toTextUnsafe <$> f (FText key)+    FText _ ->+        let FTrav trav = focusKV `composeFocusers` focusKey+        in  trav f focus++stripKey :: Text -> KeyType+stripKey s+    | T.index s 0 == '"' && T.index s (T.length s - 1) == '"' =+        InDoubleQuotes $ T.drop 1 $ T.dropEnd 1 s+    | T.index s 0 == '\'' && T.index s (T.length s - 1) == '\''=+        InQuotes $ T.drop 1 $ T.dropEnd 1 s+    | otherwise = Default++setKey :: Text -> Text -> Focus+setKey val key = FList [FText key, FText val]++focusVal :: Focuser+focusVal = FTrav $ \f focus -> case focus of+    FList _ ->+        let FTrav trav = focusIndex 1+        in  trav f focus+    FText _ ->+        let FTrav trav = focusKV `composeFocusers` focusVal+        in  trav f focus++focusAtKey :: Text -> Focuser+focusAtKey key = focusKV+    `composeFocusers` focusIf (IfSingle $ Comparison (QAll, EFocuser focusKey) OpEq (QAll, EText key))+    `composeFocusers` focusVal++focusAtIdx :: Int -> Focuser+focusAtIdx i = focusCollect focusEl `composeFocusers` focusIndex i
src/Mappings.hs view
@@ -5,13 +5,12 @@  import           Common          (Evaluatable (..), Focus (FList, FText),                                   Focuser (..), Mapping, Range, getIndexes,-                                  makeFilteredText, mapText, safeDiv,-                                  showScientific, toTextUnsafe)+                                  makeFilteredText, mapText,+                                  showRational, toTextUnsafe, readMaybeRational) import           Control.Lens    ((^..)) import           Data.Char       (toLower, toUpper) import           Data.Function   (on) import           Data.List       (sortBy)-import           Data.Scientific (Scientific) import           Data.Text       (Text) import qualified Data.Text       as T import           Text.Read       (readMaybe)@@ -31,7 +30,7 @@  mappingAppend :: Evaluatable -> Mapping mappingAppend (EText str') (FText str) = FText $ T.append str str'-mappingAppend (ENumber n) (FText str) = FText $ T.append str (showScientific n)+mappingAppend (ENumber n) (FText str) = FText $ T.append str (showRational n) mappingAppend (EFocuser (FTrav trav)) fstr@(FText str) = case fstr ^.. trav of     [FText s] -> FText $ T.append str s     _         -> fstr@@ -39,7 +38,7 @@  mappingPrepend :: Evaluatable -> Mapping mappingPrepend (EText str') (FText str) = FText $ T.append str' str-mappingPrepend (ENumber n) (FText str) = FText $ T.append (showScientific n) str+mappingPrepend (ENumber n) (FText str) = FText $ T.append (showRational n) str mappingPrepend (EFocuser (FTrav trav)) fstr@(FText str) = case fstr ^.. trav of     [FText s] -> FText $ T.append s str     _         -> fstr@@ -53,23 +52,23 @@ mappingLower (FText str) = FText $ T.toLower str mappingLower flist       = flist -mappingMath :: (Scientific -> Scientific) -> Mapping-mappingMath f (FText str) = case readMaybe $ T.unpack str of+mappingMath :: (Rational -> Rational) -> Mapping+mappingMath f (FText str) = case readMaybeRational str of     Nothing -> FText str-    Just n  -> FText $ showScientific $ f n+    Just n  -> FText $ showRational $ f n mappingMath _ flist         = flist -mappingAdd :: Scientific -> Mapping+mappingAdd :: Rational -> Mapping mappingAdd = mappingMath . (+) -mappingSub :: Scientific -> Mapping+mappingSub :: Rational -> Mapping mappingSub = mappingMath . flip (-) -mappingMult :: Scientific -> Mapping+mappingMult :: Rational -> Mapping mappingMult = mappingMath . (*) -mappingDiv :: Scientific -> Mapping-mappingDiv = mappingMath . flip safeDiv+mappingDiv :: Rational -> Mapping+mappingDiv = mappingMath . flip (/)  mappingPow :: Int -> Mapping mappingPow = mappingMath . flip (^^)
src/Parsers.hs view
@@ -7,23 +7,25 @@                                        Oper (..), Parser, Quantor (..),                                        Range (..), composeFocusers, focusTo,                                        foldFocusers, foldMappings, integer,-                                       lexeme, scient, symbol, mappingTo)+                                       lexeme, mappingTo, rational, symbol) import           Data.Char            (isAlphaNum) import           Data.Functor         (($>)) import           Data.Maybe           (fromMaybe) import           Data.Text            (Text) import qualified Data.Text            as T-import           Focusers             (focusAverage, focusCollect, focusCols,-                                       focusContains, focusEach, focusEndsWith,-                                       focusFilter, focusId, focusIf,-                                       focusIndex, focusIsAlpha,+import           Focusers             (escaping, focusAtIdx, focusAtKey,+                                       focusAverage, focusCollect, focusCols,+                                       focusContains, focusEach, focusEl,+                                       focusEndsWith, focusFilter, focusId,+                                       focusIf, focusIndex, focusIsAlpha,                                        focusIsAlphaNum, focusIsDigit,                                        focusIsLower, focusIsSpace, focusIsUpper,-                                       focusLength, focusLines, focusMaxBy,-                                       focusMaxLexBy, focusMinBy, focusMinLexBy,-                                       focusProduct, focusRegex, focusSlice,-                                       focusSortedBy, focusSortedLexBy,-                                       focusSpace, focusStartsWith, focusSum,+                                       focusKV, focusKey, focusLength,+                                       focusLines, focusMaxBy, focusMaxLexBy,+                                       focusMinBy, focusMinLexBy, focusProduct,+                                       focusRegex, focusSlice, focusSortedBy,+                                       focusSortedLexBy, focusSpace,+                                       focusStartsWith, focusSum, focusVal,                                        focusWords) import           Mappings             (mappingAbs, mappingAdd, mappingAppend,                                        mappingDiv, mappingId, mappingLength,@@ -33,10 +35,11 @@                                        mappingSlice, mappingSortBy,                                        mappingSortLexBy, mappingSub,                                        mappingUpper)-import           Text.Megaparsec      (MonadParsec (try), anySingle, between,-                                       choice, empty, label, many, noneOf,-                                       notFollowedBy, optional, satisfy, sepBy,-                                       sepBy1, takeWhile1P, (<|>))+import           Text.Megaparsec      (MonadParsec (try), anySingle,+                                       anySingleBut, between, choice, empty,+                                       label, many, noneOf, notFollowedBy,+                                       optional, satisfy, sepBy, sepBy1,+                                       takeWhile1P, (<|>)) import           Text.Megaparsec.Char (char, string)  -- Focuser parsers@@ -89,6 +92,12 @@     , parseFocusContains     , parseFocusStartsWith     , parseFocusEndsWith+    , symbol "el" $> focusEl+    , symbol "kv" $> focusKV+    , symbol "key" $> focusKey+    , symbol "val" $> focusVal+    , parseFocusAtKey+    , parseFocusAtIdx     ]  parseFocusers :: Parser [Focuser]@@ -115,9 +124,9 @@  rangeRange :: Parser Range rangeRange = label "range" $ do-    mstart <- lexeme $ optional integer+    mstart <- optional integer     symbol ":"-    mend <- lexeme $ optional integer+    mend <- optional integer     return $ RangeRange mstart mend  parseFocusSortedBy :: Parser Focuser@@ -166,22 +175,22 @@ parseFocusAdd :: Parser Focuser parseFocusAdd = do     symbol "add "-    focusTo . mappingAdd <$> scient+    focusTo . mappingAdd <$> rational  parseFocusSub :: Parser Focuser parseFocusSub = do     symbol "sub "-    focusTo . mappingSub <$> scient+    focusTo . mappingSub <$> rational  parseFocusMult :: Parser Focuser parseFocusMult = do     symbol "mult "-    focusTo . mappingMult <$> scient+    focusTo . mappingMult <$> rational  parseFocusDiv :: Parser Focuser parseFocusDiv = do     symbol "div "-    focusTo . mappingDiv <$> scient+    focusTo . mappingDiv <$> rational  parseFocusPow :: Parser Focuser parseFocusPow = do@@ -191,8 +200,7 @@ parseFocusIf :: Parser Focuser parseFocusIf = do     lexeme $ string "if" >> notFollowedBy (satisfy isAlphaNum)-    ifExpr <- parseIfExpr-    return $ focusIf ifExpr+    focusIf <$> parseIfExpr  parseIfExpr :: Parser IfExpr parseIfExpr = label "one or more blocks separated by '||'" $ do@@ -269,9 +277,19 @@ parseFocusAverage :: Parser Focuser parseFocusAverage = do     symbol "average"-    def <- fromMaybe 0 <$> optional scient+    def <- fromMaybe 0 <$> optional rational     return $ focusAverage def +parseFocusAtKey :: Parser Focuser+parseFocusAtKey = do+    symbol "atKey"+    focusAtKey <$> stringLiteral++parseFocusAtIdx :: Parser Focuser+parseFocusAtIdx = do+    symbol "atIdx "+    focusAtIdx <$> integer+ -- mapping parsers  parseMapping :: Parser Mapping@@ -311,25 +329,21 @@ parseEvaluatable :: Parser Evaluatable parseEvaluatable =     EText <$> stringLiteral <|>-    ENumber <$> scient <|>+    ENumber <$> rational <|>     EFocuser <$> parseFocuser  parseEvaluatableLong :: Parser Evaluatable parseEvaluatableLong =     EText <$> stringLiteral <|>-    ENumber <$> scient <|>+    ENumber <$> rational <|>     EFocuser . foldFocusers <$> parseFocusers  stringLiteral :: Parser Text-stringLiteral = label "string literal" $ lexeme $ do-    char '"'-    inner <- T.concat <$> many (choice-        [ takeWhile1P Nothing (\c -> c /= '/' && c /= '"')-        , try (string "\\\"" $> "\"")-        , string "\\"+stringLiteral = T.pack <$> label "string literal" (do+    between (char '"') (char '"') $ many $ choice+        [ char '\\' >> anySingle+        , anySingleBut '"'         ])-    char '"'-    return inner  parseMappingAppend :: Parser Mapping parseMappingAppend = do@@ -344,22 +358,22 @@ parseMappingAdd :: Parser Mapping parseMappingAdd = do     symbol "add "-    mappingAdd <$> scient+    mappingAdd <$> rational  parseMappingSub :: Parser Mapping parseMappingSub = do     symbol "sub "-    mappingSub <$> scient+    mappingSub <$> rational  parseMappingMult :: Parser Mapping parseMappingMult = do     symbol "mult "-    mappingMult <$> scient+    mappingMult <$> rational  parseMappingDiv :: Parser Mapping parseMappingDiv = do     symbol "div "-    mappingDiv <$> scient+    mappingDiv <$> rational  parseMappingPow :: Parser Mapping parseMappingPow = do
test/Main.hs view
@@ -1,14 +1,13 @@ {-# LANGUAGE GADTs #-} module Main where -import           Common           (safeDiv, showScientific)+import           Common           (readMaybeRational, showRational) import           Data.Char        (isAlpha, isAlphaNum, isDigit, isLower,                                    isSpace, isUpper, toLower, toUpper) import           Data.Function    (on) import           Data.List        (groupBy, transpose) import           Data.List.Extra  (dropEnd, groupBy, sort, takeEnd, transpose)-import           Data.Maybe       (fromMaybe, mapMaybe)-import           Data.Scientific  (Scientific)+import           Data.Maybe       (fromMaybe, mapMaybe, fromJust) import qualified Data.Text        as T import           Focusers         (interleave, myWords) import           System.IO.Unsafe (unsafePerformIO)@@ -16,7 +15,6 @@ import           Test.Tasty       (TestTree, defaultMain, testGroup) import           Test.Tasty.HUnit (testCase, (@?=)) import           Text.Read        (readMaybe)-import Focusers (readMaybeScientific)  main :: IO () main = defaultMain $ testGroup "All" [focuserTests, mappingTests]@@ -74,7 +72,7 @@         ]     , testGroup "sortedBy"         [ "<words.if isDigit>.sortedBy id.each|get-tree" $=-            show (map showScientific $ sort $ map (read :: String -> Scientific) $ filter (all isDigit) $ words input)+            show (map showRational $ sort $ map (fromJust . readMaybeRational . T.pack) $ filter (all isDigit) $ words input)         , "<words.if isAlpha>.sortedBy id.each|over id" $=$ "id|over id"         ]     , testGroup "sorted"@@ -107,40 +105,40 @@         , "(words.len)|get-tree" $=$ "words.len|get-tree"         ]     , testGroup "sum"-        [ "<words>.sum|get-tree" $= show [showScientific (sum $ inputNums input)]+        [ "<words>.sum|get-tree" $= show [showRational (sum $ inputNums input)]         , "words.sum|get-tree" $=-            show (map (showScientific . sum . mapMaybe (readMaybeScientific . T.pack . (:[]))) $ words input)+            show (map (showRational . sum . mapMaybe (readMaybeRational . T.pack . (:[]))) $ words input)         ]     , testGroup "product"-        [ "<words>.product|get-tree" $= show [showScientific (product $ inputNums input)]+        [ "<words>.product|get-tree" $= show [showRational (product $ inputNums input)]         , "words.product|get-tree" $=-            show (map (showScientific . product . mapMaybe (readMaybeScientific . T.pack . (:[]))) $ words input)+            show (map (showRational . product . mapMaybe (readMaybeRational . T.pack . (:[]))) $ words input)         ]     , testGroup "average"-        [ "<words>.average|get-tree" $= show [showScientific (average $ inputNums input)]+        [ "<words>.average|get-tree" $= show [showRational (average $ inputNums input)]         , "words.average|get-tree" $=-            show (map (showScientific . average . mapMaybe (readMaybeScientific . T.pack . (:[]))) $ words input)+            show (map (showRational . average . mapMaybe (readMaybeRational . T.pack . (:[]))) $ words input)         ]     , testGroup "add"-        [ "<words.add 1>.sum|get-tree" $= show [showScientific (sum $ map (+1) $ inputNums input)]+        [ "<words.add 1>.sum|get-tree" $= show [showRational (sum $ map (+1) $ inputNums input)]         ]     , testGroup "sub"-        [ "<words.sub 1>.sum|get-tree" $= show [showScientific (sum $ map (subtract 1) $ inputNums input)]+        [ "<words.sub 1>.sum|get-tree" $= show [showRational (sum $ map (subtract 1) $ inputNums input)]         ]     , testGroup "mult"-        [ "<words.mult 2>.sum|get-tree" $= show [showScientific (sum $ map (*2) $ inputNums input)]+        [ "<words.mult 2>.sum|get-tree" $= show [showRational (sum $ map (*2) $ inputNums input)]         ]     , testGroup "div"-        [ "<words.div 2>.sum|get-tree" $= show [showScientific (sum $ map (`safeDiv` 2) $ inputNums input)]+        [ "<words.div 2>.sum|get-tree" $= show [showRational (sum $ map (/ 2) $ inputNums input)]         ]     , testGroup "pow"-        [ "<words.pow 2>.sum|get-tree" $= show [showScientific (sum $ map (^^ 2) $ inputNums input)]+        [ "<words.pow 2>.sum|get-tree" $= show [showRational (sum $ map (^^ 2) $ inputNums input)]         ]     , testGroup "abs"-        [ "<words.abs>.sum|get-tree" $= show [showScientific (sum $ map abs $ inputNums input)]+        [ "<words.abs>.sum|get-tree" $= show [showRational (sum $ map abs $ inputNums input)]         ]     , testGroup "sign"-        [ "<words.sign>.sum|get-tree" $= show [showScientific (sum $ map signum $ inputNums input)]+        [ "<words.sign>.sum|get-tree" $= show [showRational (sum $ map signum $ inputNums input)]         ]     , testGroup "if"         [ "words.if 1=1|get-tree" $= show (words input)@@ -198,6 +196,32 @@         ]     , testGroup "filter"         [ "<words>.filter len<3.each|get-tree" $=$ "words.if len<3|get-tree" ]+    , testGroup "regex"+        [ "regex \"([a-z]|[A-Z]|[0-9]|_)+\"|get" $=$ "words|get"+        , "regex \"([a-z]|[A-Z]|[0-9]|_)+\"|over id" $=$ "id|over id"+        ]+    , testGroup "json"+        [ testGroup "kv"+            [ "kv.[0]|get" $$= "\"quiz\"\n"+            , "kv.[1].kv.[0]|get" $$= "\"sport\"\n\"maths\"\n"+            ]+        , testGroup "key"+            [ "key|get" $$=$ "kv.key|get"+            , "kv.key|get" $$=$ "kv.[0].{1:-1}|get"+            ]+        , testGroup "val"+            [ "val|get" $$=$ "kv.val|get"+            , "kv.val|get" $$=$ "kv.[1]|get"+            ]+        , testGroup "atKey"+            [ "atKey \"quiz\"|get" $$=$ "kv.if key=\"quiz\".val|get"+            ]+        , testGroup "atIdx"+            [ "atKey \"quiz\".atKey \"sport\".val.atKey \"options\".atIdx 3|get" $$= "\"Huston Rocket\"\n"+            ]+        , testGroup "el"+            [ "atKey \"quiz\".atKey \"sport\".val.atKey \"options\".<el>.[3]|get" $$= "\"Huston Rocket\"\n" ]+        ]     ]  mappingTests :: TestTree@@ -232,7 +256,7 @@     , testGroup "add/sub/div/pow/abs/sign"         [ "id|over add 1" $= mapNums (+ 1) input         , "id|over sub 1" $= mapNums (subtract 1) input-        , "id|over div 2" $= mapNums (`safeDiv` 2) input+        , "id|over div 2" $= mapNums (/ 2) input         , "id|over pow 2" $= mapNums (^^ 2) input         , "id|over abs" $= mapNums abs input         , "id|over sign" $= mapNums signum input@@ -275,23 +299,23 @@     ]  allEqual :: (Eq a) => [a] -> Bool-allEqual [] = True+allEqual []     = True allEqual (x:xs) = all (== x) xs  anyEqual :: (Eq a) => [a] -> Bool-anyEqual [] = False+anyEqual []     = False anyEqual (x:xs) = x `elem` xs || anyEqual xs -mapNums :: (Scientific -> Scientific) -> String -> String+mapNums :: (Rational -> Rational) -> String -> String mapNums f str =     let (ws, words) = myWords $ T.pack str         new_words = map (mapNum f . T.unpack) words     in  T.unpack $ T.concat $ interleave ws words   where-    mapNum :: (Scientific -> Scientific) -> String -> String+    mapNum :: (Rational -> Rational) -> String -> String     mapNum f str = case readMaybe str of         Nothing -> str-        Just n  -> T.unpack $ showScientific $ f n+        Just n  -> T.unpack $ showRational $ f n  getWS :: String -> [String] getWS str =@@ -301,17 +325,21 @@ getCols :: String -> [[String]] getCols = transpose . map words . lines -inputNums :: String -> [Scientific]-inputNums str = mapMaybe readMaybe $ words str+inputNums :: String -> [Rational]+inputNums str = mapMaybe (readMaybeRational . T.pack) $ words str -average :: [Scientific] -> Scientific+average :: [Rational] -> Rational average [] = 0-average ns = sum ns `safeDiv` fromIntegral (length ns)+average ns = sum ns / fromIntegral (length ns)  {-# NOINLINE input #-} input :: String input = unsafePerformIO $ readFile "test/input.txt" +{-# NOINLINE json #-}+json :: String+json = unsafePerformIO $ readFile "test/input.json"+ infixl 1 $= ($=) :: String -> String -> TestTree ($=) command desiredOutput = testCase command $ do@@ -323,5 +351,19 @@ ($=$) cmd1 cmd2 = testCase (cmd1 ++ " == " ++ cmd2) $ do     out1 <- readProcess "./smh" [cmd1] input     out2 <- readProcess "./smh" [cmd2] input+    out1 @?= out2++-- This is stupid, but whatever+infixl 1 $$=+($$=) :: String -> String -> TestTree+($$=) command desiredOutput = testCase command $ do+    output <- readProcess "./smh" [command] json+    output @?= desiredOutput++infixl 1 $$=$+($$=$) :: String -> String -> TestTree+($$=$) cmd1 cmd2 = testCase (cmd1 ++ " == " ++ cmd2) $ do+    out1 <- readProcess "./smh" [cmd1] json+    out2 <- readProcess "./smh" [cmd2] json     out1 @?= out2