shellwords 0.1.2.2 → 0.1.3.0
raw patch · 6 files changed
+180/−83 lines, 6 filesdep ~hspecdep ~megaparsecdep ~text
Dependency ranges changed: hspec, megaparsec, text
Files
- CHANGELOG.md +4/−3
- shellwords.cabal +68/−49
- src/ShellWords.hs +46/−21
- src/Text/Megaparsec/Compat.hs +17/−0
- test/ShellWordsSpec.hs +44/−9
- test/Spec.hs +1/−1
CHANGELOG.md view
@@ -1,10 +1,11 @@-## [*Unreleased*](https://github.com/pbrisbin/hs-shellwords/compare/v0.1.2.2...master)+## [*Unreleased*](https://github.com/pbrisbin/hs-shellwords/compare/v0.1.3.0...main) None -## [v0.1.2.2](https://github.com/pbrisbin/hs-shellwords/compare/v0.1.2.1...v0.1.2.2)+## [v0.1.3.0](https://github.com/pbrisbin/hs-shellwords/compare/v0.1.2.1...v0.1.3.0) -- Require megaparsec-7.0.0 (LTS-13.6/GHC-8.6)+- Define reserved characters, to enable delimited parsing `$(<words)`+- Export `Parser`-related functions, to enable incorporating in a larger parser ## [v0.1.2.1](https://github.com/pbrisbin/hs-shellwords/compare/v0.1.2.0...v0.1.2.1)
shellwords.cabal view
@@ -1,57 +1,76 @@-cabal-version: 1.12---- This file has been generated from package.yaml by hpack version 0.31.1.------ see: https://github.com/sol/hpack------ hash: f97a5c28708a550d522890024daed5930cd886bb2b5638a0d815a370602e190c--name: shellwords-version: 0.1.2.2-synopsis: Parse strings into words, like a shell would-description: See https://github.com/pbrisbin/hs-shellwords#readme-category: Text-homepage: https://github.com/pbrisbin/hs-shellwords#readme-bug-reports: https://github.com/pbrisbin/hs-shellwords/issues-author: Patrick Brisbin-maintainer: pbrisbin@gmail.com-copyright: 2018 Patrick Brisbin-license: MIT-license-file: LICENSE-build-type: Simple-extra-source-files:+cabal-version: 1.18+name: shellwords+version: 0.1.3.0+license: MIT+license-file: LICENSE+copyright: 2018 Patrick Brisbin+maintainer: pbrisbin@gmail.com+author: Patrick Brisbin+homepage: https://github.com/pbrisbin/hs-shellwords#readme+bug-reports: https://github.com/pbrisbin/hs-shellwords/issues+synopsis: Parse strings into words, like a shell would+description: See https://github.com/pbrisbin/hs-shellwords#readme+category: Text+build-type: Simple+extra-doc-files: README.md CHANGELOG.md source-repository head- type: git- location: https://github.com/pbrisbin/hs-shellwords+ type: git+ location: https://github.com/pbrisbin/hs-shellwords library- hs-source-dirs:- src- ghc-options: -Wall- build-depends:- base >=4.7 && <5- , megaparsec >=7.0.0- , text- exposed-modules:- ShellWords- other-modules:- Paths_shellwords- default-language: Haskell2010+ exposed-modules:+ ShellWords+ Text.Megaparsec.Compat + hs-source-dirs: src+ other-modules: Paths_shellwords+ default-language: Haskell2010+ default-extensions: NoImplicitPrelude+ ghc-options:+ -Weverything -Wno-missing-exported-signatures+ -Wno-missing-import-lists -Wno-missing-local-signatures+ -Wno-monomorphism-restriction -Wno-unsafe -Wno-safe++ build-depends:+ base >=4.7 && <5,+ megaparsec >=6.5.0,+ text >=1.2.3.1++ if impl(ghc >=9.2)+ ghc-options: -Wno-missing-kind-signatures++ if impl(ghc >=8.10)+ ghc-options:+ -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module+ test-suite hspec- type: exitcode-stdio-1.0- main-is: Spec.hs- hs-source-dirs:- test- ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N- build-depends:- base >=4.7 && <5- , hspec- , shellwords- other-modules:- ShellWordsSpec- Paths_shellwords- default-language: Haskell2010+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ hs-source-dirs: test+ other-modules:+ ShellWordsSpec+ Paths_shellwords++ default-language: Haskell2010+ default-extensions: NoImplicitPrelude+ ghc-options:+ -Weverything -Wno-missing-exported-signatures+ -Wno-missing-import-lists -Wno-missing-local-signatures+ -Wno-monomorphism-restriction -Wno-unsafe -Wno-safe -threaded+ -rtsopts -with-rtsopts=-N++ build-depends:+ base >=4.7 && <5,+ hspec >=2.5.5,+ megaparsec >=6.5.0,+ shellwords -any++ if impl(ghc >=9.2)+ ghc-options: -Wno-missing-kind-signatures++ if impl(ghc >=8.10)+ ghc-options:+ -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module
src/ShellWords.hs view
@@ -3,34 +3,48 @@ module ShellWords ( parse , parseText++ -- * Low-level parser+ , Parser+ , runParser+ , parser ) where +import Prelude+ import Data.Bifunctor (first) import Data.Char+import Data.List (dropWhileEnd) import Data.Maybe (fromMaybe)-import Data.Semigroup ((<>))-import Data.Text (Text)-import qualified Data.Text as T+import Data.Text (Text, pack, unpack) import Data.Void (Void)-import Text.Megaparsec hiding (parse) import qualified Text.Megaparsec as Megaparsec import Text.Megaparsec.Char+import Text.Megaparsec.Compat hiding (parse, runParser) type Parser = Parsec Void String parse :: String -> Either String [String]-parse = first errorBundlePretty . Megaparsec.parse parser "<input>" . strip- where strip = let f = reverse . dropWhile isSpace in f . f+parse = runParser parser +runParser :: Parser a -> String -> Either String a+runParser p = first errorBundlePretty . Megaparsec.parse p "<input>"+ -- | Parse and return @'Text'@ values parseText :: Text -> Either String [Text]-parseText = fmap (map T.pack) . parse . T.unpack+parseText = fmap (map pack) . parse . unpack parser :: Parser [String]-parser = shellword `sepBy` space1+parser = fmap (dropWhileEnd null) $ space *> shellword `sepEndBy1` space1 shellword :: Parser String-shellword = choice [quoted, shelloption, value]+shellword =+ choice+ [ quoted <?> "quoted string"+ , shelloption <?> "shell option"+ , value <?> "bare value"+ ]+ <?> "shell word" -- | A balanced, single- or double-quoted string quoted :: Parser String@@ -49,7 +63,7 @@ flag = (<>) <$> (string "--" <|> string "-")- <*> (quoted <|> many (nonSpaceOr '='))+ <*> (quoted <|> value) -- | The argument to a flag like @=foo@, or @=\"baz bat\"@ argument :: Parser String@@ -57,21 +71,32 @@ -- | A plain value, here till an (unescaped) space value :: Parser String-value = many nonSpace+value = many nonSpaceNonReserved escaped :: Char -> Parser Char-escaped c = c <$ string ("\\" <> [c])+escaped c = c <$ (escapedSatisfy (== c) <?> "escaped" <> show c) +escapedSpace :: Parser Char+escapedSpace = escapedSatisfy isSpace <?> "escaped white space"++escapedAnyOf :: [Char] -> Parser Char+escapedAnyOf cs = escapedSatisfy (`elem` cs) <?> "escaped one of " <> cs++escapedSatisfy :: (Char -> Bool) -> Parser Char+escapedSatisfy p = try $ string "\\" *> satisfy p+ anyToken :: Parser Char anyToken = satisfy $ const True -nonSpace :: Parser Char-nonSpace = escaped ' ' <|> satisfy (not . isSpace)+nonSpaceNonReserved :: Parser Char+nonSpaceNonReserved =+ escapedSpace+ <|> escapedAnyOf reserved+ <|> satisfy (\c -> not $ isSpace c || isReserved c)+ <?> "non white space / non reserved character" -nonSpaceOr :: Char -> Parser Char-nonSpaceOr c = escaped ' ' <|> escaped c <|> satisfy (not . isSpaceOrChar)- where- isSpaceOrChar c'- | isSpace c' = True- | c' == c = True- | otherwise = False+isReserved :: Char -> Bool+isReserved = (`elem` reserved)++reserved :: [Char]+reserved = "();="
+ src/Text/Megaparsec/Compat.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE CPP #-}++module Text.Megaparsec.Compat+ ( module X+#if !MIN_VERSION_megaparsec(7,0,0)+ , errorBundlePretty+#endif+ ) where++import Text.Megaparsec as X++#if !MIN_VERSION_megaparsec(7,0,0)+import Prelude++errorBundlePretty :: Show a => a -> String+errorBundlePretty = show+#endif
test/ShellWordsSpec.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -- |@@ -10,10 +11,13 @@ ( spec ) where +import Prelude+ import Data.Foldable (for_)-import Data.Semigroup ((<>)) import ShellWords import Test.Hspec+import Text.Megaparsec.Char (string)+import Text.Megaparsec.Compat (between) testCases :: [(String, [String])] testCases =@@ -72,27 +76,58 @@ ] spec :: Spec-spec = describe "parse" $ do+spec = describe "parser" $ do for_ testCases $ \(input, expected) -> do it ("parses |" <> input <> "| correctly") $ do- parse input `shouldBe` Right expected+ runParser parser input `shouldBeParsed` expected for_ errorCases $ \input -> do it ("errors on |" <> input <> "|") $ do- parse input `shouldSatisfy` isLeft+ expectParseError $ runParser parser input it "fixes #3" $ do- let input = "-LC:/Users/Vitor\\ Coimbra/AppData/Local/Programs/stack/x86_64-windows/msys2-20150512/mingw64/lib -ltag\n"+ let input+ = "-LC:/Users/Vitor\\ Coimbra/AppData/Local/Programs/stack/x86_64-windows/msys2-20150512/mingw64/lib -ltag\n" expected = [ "-LC:/Users/Vitor Coimbra/AppData/Local/Programs/stack/x86_64-windows/msys2-20150512/mingw64/lib" , "-ltag" ] - parse input `shouldBe` Right expected+ runParser parser input `shouldBeParsed` expected -isLeft :: Either a b -> Bool-isLeft (Left _) = True-isLeft _ = False+ context "delimited" $ do+ let+ parseDelimited input =+ runParser (between (string "FOO=$(") (string ")") parser)+ $ "FOO=$("+ <> input+ <> ")"++ it "parses within delimiters" $ do+ parseDelimited "echo \"hi\"" `shouldBeParsed` ["echo", "hi"]++ it "handles quoted delimiter" $ do+ parseDelimited "echo \"hi (quietly)\")"+ `shouldBeParsed` ["echo", "hi (quietly)"]++ it "works with white space" $ do+ parseDelimited " echo \n\"hi\" " `shouldBeParsed` ["echo", "hi"]++ it "works with newlines" $ do+ parseDelimited "echo \n\"hi\"" `shouldBeParsed` ["echo", "hi"]++ it "works with final newline" $ do+ parseDelimited "echo \n\"hi\"\n" `shouldBeParsed` ["echo", "hi"]++expectParseError :: Show a => Either String a -> Expectation+expectParseError = \case+ Left{} -> pure ()+ Right a' -> expectationFailure $ "Expected parse error, got:" <> show a'++shouldBeParsed :: (Show a, Eq a) => Either String a -> a -> Expectation+a `shouldBeParsed` b = case a of+ Left e -> expectationFailure e+ Right a' -> a' `shouldBe` b {- Features I'm not sure I'll be porting, certainly not yet.
test/Spec.hs view
@@ -1,1 +1,1 @@-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}+{-# OPTIONS_GHC -F -pgmF hspec-discover -Wno-missing-export-lists #-}