diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml index c0de7d4f..380cfb83 100644 --- a/.github/workflows/haskell-ci.yml +++ b/.github/workflows/haskell-ci.yml @@ -1,6 +1,6 @@ # This GitHub workflow config has been generated by a script via # -# haskell-ci 'github' 'optparse-applicative.cabal' +# haskell-ci 'github' '--project' '--installed=-process -filepath -Win32 -unix -directory -all -bytestring -text' 'cabal.project' # # To regenerate the script (for example after adjusting tested-with) run # @@ -8,9 +8,9 @@ # # For more information, see https://github.com/haskell-CI/haskell-ci # -# version: 0.19.20240708 +# version: 0.19.20250605 # -# REGENDATA ("0.19.20240708",["github","optparse-applicative.cabal"]) +# REGENDATA ("0.19.20250605",["github","--project","--installed=-process -filepath -Win32 -unix -directory -all -bytestring -text","cabal.project"]) # name: Haskell-CI on: @@ -19,7 +19,7 @@ on: jobs: linux: name: Haskell-CI - Linux - ${{ matrix.compiler }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 60 container: @@ -78,32 +78,31 @@ jobs: compilerVersion: 8.6.5 setup-method: ghcup allow-failure: false - - compiler: ghc-8.4.4 - compilerKind: ghc - compilerVersion: 8.4.4 - setup-method: ghcup - allow-failure: false - - compiler: ghc-8.2.2 - compilerKind: ghc - compilerVersion: 8.2.2 - setup-method: ghcup - allow-failure: false - - compiler: ghc-8.0.2 - compilerKind: ghc - compilerVersion: 8.0.2 - setup-method: ghcup - allow-failure: false fail-fast: false steps: - - name: apt + - name: apt-get install run: | apt-get update - apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 libnuma-dev + apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 + - name: Install GHCup + run: | mkdir -p "$HOME/.ghcup/bin" - curl -sL https://downloads.haskell.org/ghcup/0.1.30.0/x86_64-linux-ghcup-0.1.30.0 > "$HOME/.ghcup/bin/ghcup" + curl -sL https://downloads.haskell.org/ghcup/0.1.50.1/x86_64-linux-ghcup-0.1.50.1 > "$HOME/.ghcup/bin/ghcup" chmod a+x "$HOME/.ghcup/bin/ghcup" + - name: Install cabal-install + run: | + "$HOME/.ghcup/bin/ghcup" install cabal 3.14.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false) + echo "CABAL=$HOME/.ghcup/bin/cabal-3.14.2.0 -vnormal+nowrap" >> "$GITHUB_ENV" + - name: Install GHC (GHCup) + if: matrix.setup-method == 'ghcup' + run: | "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false) - "$HOME/.ghcup/bin/ghcup" install cabal 3.12.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false) + HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER") + HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#') + HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#') + echo "HC=$HC" >> "$GITHUB_ENV" + echo "HCPKG=$HCPKG" >> "$GITHUB_ENV" + echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV" env: HCKIND: ${{ matrix.compilerKind }} HCNAME: ${{ matrix.compiler }} @@ -114,21 +113,12 @@ jobs: echo "LANG=C.UTF-8" >> "$GITHUB_ENV" echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV" echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV" - HCDIR=/opt/$HCKIND/$HCVER - HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER") - HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#') - HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#') - echo "HC=$HC" >> "$GITHUB_ENV" - echo "HCPKG=$HCPKG" >> "$GITHUB_ENV" - echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV" - echo "CABAL=$HOME/.ghcup/bin/cabal-3.12.1.0 -vnormal+nowrap" >> "$GITHUB_ENV" HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV" echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV" echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV" echo "HEADHACKAGE=false" >> "$GITHUB_ENV" echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV" - echo "GHCJSARITH=0" >> "$GITHUB_ENV" env: HCKIND: ${{ matrix.compilerKind }} HCNAME: ${{ matrix.compiler }} @@ -202,11 +192,13 @@ jobs: touch cabal.project touch cabal.project.local echo "packages: ${PKGDIR_optparse_applicative}" >> cabal.project - if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo "package optparse-applicative" >> cabal.project ; fi - if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi + echo "package optparse-applicative" >> cabal.project + echo " ghc-options: -Werror=missing-methods" >> cabal.project cat >> cabal.project <> cabal.project.local cat cabal.project cat cabal.project.local - name: dump install plan @@ -244,8 +236,8 @@ jobs: rm -f cabal.project.local $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all - name: save cache - uses: actions/cache/save@v4 if: always() + uses: actions/cache/save@v4 with: key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} path: ~/.cabal/store diff --git a/cabal.project b/cabal.project new file mode 100644 index 00000000..8834d044 --- /dev/null +++ b/cabal.project @@ -0,0 +1,2 @@ +packages: + ./ diff --git a/hie.yaml b/hie.yaml index d392c6aa..04cd2439 100644 --- a/hie.yaml +++ b/hie.yaml @@ -1,4 +1,2 @@ cradle: cabal: - - path: "./" - component: "optparse-applicative:lib:optparse-applicative" diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 971b06fb..ef1d41bb 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -61,35 +61,20 @@ tested-with: GHC==8.10.7 GHC==8.8.4 GHC==8.6.5 - GHC==8.4.4 - GHC==8.2.2 - GHC==8.0.2 - GHC==7.10.3 - GHC==7.8.4 - GHC==7.6.3 - GHC==7.4.2 - GHC==7.2.2 - GHC==7.0.4 source-repository head type: git location: https://github.com/pcapriotti/optparse-applicative.git -flag process - description: - Depend on the process package for Bash autocompletion - default: True - library hs-source-dirs: src ghc-options: -Wall default-language: Haskell98 -- See https://ghc.haskell.org/trac/ghc/wiki/Migration/8.0#base-4.9.0.0 - if impl(ghc >= 8.0) - ghc-options: -Wno-redundant-constraints -Wcompat -Wnoncanonical-monad-instances - if impl(ghc < 8.8) - ghc-options: -Wnoncanonical-monadfail-instances + ghc-options: -Wno-redundant-constraints -Wcompat -Wnoncanonical-monad-instances + if impl(ghc < 8.8) + ghc-options: -Wnoncanonical-monadfail-instances exposed-modules: Options.Applicative , Options.Applicative.Arrows @@ -109,18 +94,16 @@ library , Options.Applicative.Types , Options.Applicative.Internal - build-depends: base >= 4.5 && < 5 + build-depends: base >= 4.12 && < 5 , text >= 1.2 , transformers >= 0.5 && < 0.7 , prettyprinter >= 1.7 && < 1.8 , prettyprinter-ansi-terminal >= 1.1.2 && < 1.2 - - if flag(process) - build-depends: process >= 1.0 && < 1.7 - - if !impl(ghc >= 8) - build-depends: semigroups >= 0.10 && < 0.21 - , fail == 4.9.* + -- 1.6.26.0 due to System.Process.Environment.OsString.getArgs + , process >= 1.6.26.0 && < 1.7 + , bytestring + , os-string >= 2.0.5 && < 3 + , filepath >= 1.5 test-suite tests type: exitcode-stdio-1.0 @@ -148,7 +131,9 @@ test-suite tests build-depends: base , optparse-applicative + , os-string + , file-io + , filepath + , text , QuickCheck >= 2.8 && < 2.16 - if !impl(ghc >= 8) - build-depends: semigroups diff --git a/src/Options/Applicative.hs b/src/Options/Applicative.hs index b714c131..7af49b36 100644 --- a/src/Options/Applicative.hs +++ b/src/Options/Applicative.hs @@ -63,9 +63,11 @@ module Options.Applicative ( switch, strOption, + osStrOption, option, strArgument, + osStrArgument, argument, subparser, @@ -134,6 +136,7 @@ module Options.Applicative ( auto, str, + osStr, maybeReader, eitherReader, disabled, diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index e4b6356c..37000e49 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -1,3 +1,6 @@ +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} -- | You don't need to import this module to enable bash completion. -- -- See @@ -14,7 +17,6 @@ module Options.Applicative.BashCompletion import Control.Applicative import Prelude import Data.Foldable ( asum ) -import Data.List ( isPrefixOf ) import Data.Maybe ( fromMaybe, listToMaybe ) import Options.Applicative.Builder @@ -23,6 +25,10 @@ import Options.Applicative.Internal import Options.Applicative.Types import Options.Applicative.Help.Pretty import Options.Applicative.Help.Chunk +import "os-string" System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString +import qualified Data.Text.Lazy as Lazy +import qualified Data.Text as Strict -- | Provide basic or rich command completions data Richness @@ -38,39 +44,42 @@ data Richness bashCompletionParser :: ParserInfo a -> ParserPrefs -> Parser CompletionResult bashCompletionParser pinfo pprefs = complParser where + returnCompletions :: (OsString -> IO [Strict.Text]) -> CompletionResult returnCompletions opts = CompletionResult $ - \progn -> unlines <$> opts progn + \progn -> Strict.unlines <$> opts progn + scriptRequest :: (OsString -> Strict.Text) -> CompletionResult scriptRequest = CompletionResult . fmap pure + complParser :: Parser CompletionResult complParser = asum [ returnCompletions <$> - ( bashCompletionQuery pinfo pprefs + ( let a = bashCompletionQuery pinfo pprefs in a -- To get rich completions, one just needs the first -- command. To customise the lengths, use either of -- the `desc-length` options. -- zsh commands can go on a single line, so they might -- want to be longer. - <$> ( flag' Enriched (long "bash-completion-enriched" `mappend` internal) - <*> option auto (long "bash-completion-option-desc-length" `mappend` internal `mappend` value 40) - <*> option auto (long "bash-completion-command-desc-length" `mappend` internal `mappend` value 40) + <$> ( flag' Enriched (long [osstr|bash-completion-enriched|] `mappend` internal) + <*> option auto (long [osstr|bash-completion-option-desc-length|] `mappend` internal `mappend` value 40) + <*> option auto (long [osstr|bash-completion-command-desc-length|] `mappend` internal `mappend` value 40) <|> pure Standard ) - <*> (many . strOption) (long "bash-completion-word" + <*> (many . osStrOption) (long [osstr|bash-completion-word|] `mappend` internal) - <*> option auto (long "bash-completion-index" `mappend` internal) ) + <*> option auto (long [osstr|bash-completion-index|] `mappend` internal) ) , scriptRequest . bashCompletionScript <$> - strOption (long "bash-completion-script" `mappend` internal) + osStrOption (long [osstr|bash-completion-script|] `mappend` internal) , scriptRequest . fishCompletionScript <$> - strOption (long "fish-completion-script" `mappend` internal) + osStrOption (long [osstr|fish-completion-script|] `mappend` internal) , scriptRequest . zshCompletionScript <$> - strOption (long "zsh-completion-script" `mappend` internal) + osStrOption (long [osstr|zsh-completion-script|] `mappend` internal) ] -bashCompletionQuery :: ParserInfo a -> ParserPrefs -> Richness -> [String] -> Int -> String -> IO [String] +bashCompletionQuery :: ParserInfo a -> ParserPrefs -> Richness -> [OsString] -> Int -> OsString -> IO [Strict.Text] bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl pprefs of Just (Left (SomeParser p, a)) -> list_options a p @@ -81,6 +90,7 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre where compl = runParserInfo pinfo (drop 1 ws') + list_options :: ArgPolicy -> Parser a -> IO [Strict.Text] list_options a = fmap concat . sequence @@ -97,6 +107,7 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre -- -- For options and flags, ensure that the user -- hasn't disabled them with `--`. + opt_completions :: ArgPolicy -> ArgumentReachability -> Option a -> IO [Strict.Text] opt_completions argPolicy reachability opt = case optMain opt of OptReader ns _ _ | argPolicy /= AllPositionals @@ -121,54 +132,56 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre -- When doing enriched completions, add any help specified -- to the completion variables (tab separated). - add_opt_help :: Functor f => Option a -> f String -> f String + add_opt_help :: Option a -> [OsString] -> [Strict.Text] add_opt_help opt = case richness of Standard -> - id + fmap osStringToStrictText Enriched len _ -> fmap $ \o -> let h = unChunk $ optHelp opt - in maybe o (\h' -> o ++ "\t" ++ render_line len h') h + o' = osStringToLazyText o + in maybe (osStringToStrictText o) (\h' -> Lazy.toStrict (o' <> "\t" <> render_line len h')) h -- When doing enriched completions, add the command description - -- to the completion variables (tab separated). - with_cmd_help :: Functor f => f (String, ParserInfo a) -> f String + -- to the completion variables (tab separated). + with_cmd_help :: [(OsString, ParserInfo a)] -> [Strict.Text] with_cmd_help = case richness of Standard -> - fmap fst + fmap (osStringToStrictText . fst) Enriched _ len -> fmap $ \(cmd, cmdInfo) -> let h = unChunk (infoProgDesc cmdInfo) - in maybe cmd (\h' -> cmd ++ "\t" ++ render_line len h') h + cmd' = osStringToLazyText cmd + in maybe (osStringToStrictText cmd) (\h' -> Lazy.toStrict ((cmd' `Lazy.snoc` '\t') <> render_line len h')) h - show_names :: [OptName] -> [String] + show_names :: [OptName] -> [OsString] show_names = filter is_completion . map showOption -- We only want to show a single line in the completion results description. -- If there was a line break, it would come across as a different completion -- possibility. - render_line :: Int -> Doc -> String - render_line len doc = case lines (prettyString 1 len doc) of - [] -> "" + render_line :: Int -> Doc -> Lazy.Text + render_line len doc = case Lazy.lines (prettyLazyText 1 len doc) of + [] -> Lazy.empty [x] -> x - x : _ -> x ++ "..." + x : _ -> x <> "..." - run_completer :: Completer -> IO [String] - run_completer c = runCompleter c (fromMaybe "" (listToMaybe ws'')) + run_completer :: Completer -> IO [Strict.Text] + run_completer c = runCompleter c (fromMaybe OsString.empty (listToMaybe ws'')) (ws', ws'') = splitAt i ws - is_completion :: String -> Bool + is_completion :: OsString -> Bool is_completion = case ws'' of - w:_ -> isPrefixOf w + w:_ -> OsString.isPrefixOf w _ -> const True -- | Generated bash shell completion script -bashCompletionScript :: String -> String -> String -bashCompletionScript prog progn = unlines - [ "_" ++ progn ++ "()" +bashCompletionScript :: OsString -> OsString -> Strict.Text +bashCompletionScript prog progn = Strict.unlines + [ "_" <> osStringToStrictText progn <> "()" , "{" , " local CMDLINE" , " local IFS=$'\\n'" @@ -178,10 +191,13 @@ bashCompletionScript prog progn = unlines , " CMDLINE=(${CMDLINE[@]} --bash-completion-word $arg)" , " done" , "" - , " COMPREPLY=( $(" ++ prog ++ " \"${CMDLINE[@]}\") )" + , " COMPREPLY=( $(" <> prog' <> " \"${CMDLINE[@]}\") )" , "}" , "" - , "complete -o filenames -F _" ++ progn ++ " " ++ progn ] + , "complete -o filenames -F _" <> progn' <> " " <> progn' ] + where + progn' = osStringToStrictText progn + prog' = osStringToStrictText prog {- /Note/: Fish Shell @@ -203,9 +219,9 @@ Tab characters separate items from descriptions. -} -- | Generated fish shell completion script -fishCompletionScript :: String -> String -> String -fishCompletionScript prog progn = unlines - [ " function _" ++ progn +fishCompletionScript :: OsString -> OsString -> Strict.Text +fishCompletionScript prog progn = Strict.unlines + [ " function _" <> osStringToStrictText progn , " set -l cl (commandline --tokenize --current-process)" , " # Hack around fish issue #3934" , " set -l cn (commandline --tokenize --cut-at-cursor --current-process)" @@ -214,7 +230,7 @@ fishCompletionScript prog progn = unlines , " for arg in $cl" , " set tmpline $tmpline --bash-completion-word $arg" , " end" - , " for opt in (" ++ prog ++ " $tmpline)" + , " for opt in (" <> osStringToStrictText prog <> " $tmpline)" , " if test -d $opt" , " echo -E \"$opt/\"" , " else" @@ -223,13 +239,13 @@ fishCompletionScript prog progn = unlines , " end" , "end" , "" - , "complete --no-files --command " ++ progn ++ " --arguments '(_" ++ progn ++ ")'" + , "complete --no-files --command " <> osStringToStrictText progn <> " --arguments '(_" <> osStringToStrictText progn <> ")'" ] -- | Generated zsh shell completion script -zshCompletionScript :: String -> String -> String -zshCompletionScript prog progn = unlines - [ "#compdef " ++ progn +zshCompletionScript :: OsString -> OsString -> Strict.Text +zshCompletionScript prog progn = Strict.unlines + [ "#compdef " <> osStringToStrictText progn , "" , "local request" , "local completions" @@ -241,7 +257,7 @@ zshCompletionScript prog progn = unlines , " request=(${request[@]} --bash-completion-word $arg)" , "done" , "" - , "IFS=$'\\n' completions=($( " ++ prog ++ " \"${request[@]}\" ))" + , "IFS=$'\\n' completions=($( " <> osStringToStrictText prog <> " \"${request[@]}\" ))" , "" , "for word in $completions; do" , " local -a parts" diff --git a/src/Options/Applicative/Builder.hs b/src/Options/Applicative/Builder.hs index b59c1cd7..36b97e62 100644 --- a/src/Options/Applicative/Builder.hs +++ b/src/Options/Applicative/Builder.hs @@ -1,4 +1,7 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE OverloadedStrings #-} module Options.Applicative.Builder ( -- * Parser builders -- @@ -19,6 +22,7 @@ module Options.Applicative.Builder ( -- creates a parser for an option called \"output\". subparser, strArgument, + osStrArgument, argument, flag, flag', @@ -26,6 +30,7 @@ module Options.Applicative.Builder ( abortOption, infoOption, strOption, + osStrOption, option, -- * Modifiers @@ -56,6 +61,7 @@ module Options.Applicative.Builder ( -- | A collection of basic 'Option' readers. auto, str, + osStr, maybeReader, eitherReader, disabled, @@ -105,14 +111,13 @@ module Options.Applicative.Builder ( HasName, HasCompleter, HasValue, - HasMetavar + HasMetavar, ) where import Control.Applicative -#if __GLASGOW_HASKELL__ < 804 -import Data.Semigroup hiding (Option, option) -#endif -import Data.String (fromString, IsString) + + + import Options.Applicative.Builder.Completer import Options.Applicative.Builder.Internal @@ -122,19 +127,34 @@ import Options.Applicative.Help.Pretty import Options.Applicative.Help.Chunk import Options.Applicative.Internal (mapParserOptions) +import qualified "os-string" System.OsString as OsString +import System.IO.Unsafe (unsafePerformIO) +import "os-string" System.OsString (OsString, osstr, OsChar) +import qualified Data.Text as Strict +import Data.String (IsString (fromString)) + -- Readers -- -- | 'Option' reader based on the 'Read' type class. auto :: Read a => ReadM a -auto = eitherReader $ \arg -> case reads arg of +auto = eitherReader $ \arg -> + let arg' = unsafePerformIO $ OsString.decodeLE arg + in case reads arg' of [(r, "")] -> return r - _ -> Left $ "cannot parse value `" ++ arg ++ "'" + _ -> Left $ "cannot parse value `" <> osStringToStrictText arg <> "'" -- | String 'Option' reader. -- --- Polymorphic over the `IsString` type class since 0.14. +-- Polymorphic over the `IsString` type class since 0.4. str :: IsString s => ReadM s -str = fromString <$> readerAsk +str = do + fromString . unsafePerformIO . OsString.decodeLE <$> readerAsk + +-- | OsString 'Option' reader. +-- +-- Synonim for 'readerAsk' +osStr :: ReadM OsString +osStr = readerAsk -- | Convert a function producing an 'Either' into a reader. -- @@ -145,14 +165,14 @@ str = fromString <$> readerAsk -- > import qualified Data.Text as T -- > attoparsecReader :: A.Parser a -> ReadM a -- > attoparsecReader p = eitherReader (A.parseOnly p . T.pack) -eitherReader :: (String -> Either String a) -> ReadM a +eitherReader :: (OsString -> Either Strict.Text a) -> ReadM a eitherReader f = readerAsk >>= either readerError return . f -- | Convert a function producing a 'Maybe' into a reader. -maybeReader :: (String -> Maybe a) -> ReadM a +maybeReader :: (OsString -> Maybe a) -> ReadM a maybeReader f = do arg <- readerAsk - maybe (readerError $ "cannot parse value `" ++ arg ++ "'") return . f $ arg + maybe (readerError $ "cannot parse value `" <> osStringToStrictText arg <> "'") return . f $ arg -- | Null 'Option' reader. All arguments will fail validation. disabled :: ReadM a @@ -161,11 +181,11 @@ disabled = readerError "disabled option" -- modifiers -- -- | Specify a short name for an option. -short :: HasName f => Char -> Mod f a +short :: HasName f => OsChar -> Mod f a short = fieldMod . name . OptShort -- | Specify a long name for an option. -long :: HasName f => String -> Mod f a +long :: HasName f => OsString -> Mod f a long = fieldMod . name . OptLong -- | Specify a default value for an option. @@ -181,15 +201,15 @@ value :: HasValue f => a -> Mod f a value x = Mod id (DefaultProp (Just x) Nothing) id -- | Specify a function to show the default value for an option. -showDefaultWith :: (a -> String) -> Mod f a +showDefaultWith :: (a -> Strict.Text) -> Mod f a showDefaultWith s = Mod id (DefaultProp Nothing (Just s)) id -- | Show the default value for this option using its 'Show' instance. showDefault :: Show a => Mod f a -showDefault = showDefaultWith show +showDefault = showDefaultWith (Strict.pack . show) -- | Specify the help text for an option. -help :: String -> Mod f a +help :: Strict.Text -> Mod f a help s = optionMod $ \p -> p { propHelp = paragraph s } -- | Specify the help text for an option as a 'Prettyprinter.Doc AnsiStyle' @@ -205,7 +225,7 @@ noArgError e = fieldMod $ \p -> p { optNoArgError = const e } -- -- Metavariables have no effect on the actual parser, and only serve to specify -- the symbolic name for an argument to be displayed in the help text. -metavar :: HasMetavar f => String -> Mod f a +metavar :: HasMetavar f => Strict.Text -> Mod f a metavar var = optionMod $ \p -> p { propMetaVar = var } -- | Hide this option from the brief description. @@ -240,7 +260,7 @@ style x = optionMod $ \p -> -- (info goodbye (progDesc "Say goodbye")) -- ) -- @ -command :: String -> ParserInfo a -> Mod CommandFields a +command :: OsString -> ParserInfo a -> Mod CommandFields a command cmd pinfo = fieldMod $ \p -> p { cmdCommands = (cmd, pinfo) : cmdCommands p } @@ -250,19 +270,19 @@ command cmd pinfo = fieldMod $ \p -> -- -- If using the same `metavar` for each group of commands, it may yield a more -- attractive usage text combined with `hidden` for some groups. -commandGroup :: String -> Mod CommandFields a +commandGroup :: OsString -> Mod CommandFields a commandGroup g = fieldMod $ \p -> p { cmdGroup = Just g } -- | Add a list of possible completion values. -completeWith :: HasCompleter f => [String] -> Mod f a +completeWith :: HasCompleter f => [Strict.Text] -> Mod f a completeWith = completer . listCompleter -- | Add a bash completion action. Common actions include @file@ and -- @directory@. See -- -- for a complete list. -action :: HasCompleter f => String -> Mod f a +action :: HasCompleter f => OsString -> Mod f a action = completer . bashCompleter -- | Add a completer to an argument. @@ -296,10 +316,14 @@ argument p m = mkParser d g (ArgReader rdr) ArgumentFields compl = f (ArgumentFields mempty) rdr = CReader compl p --- | Builder for a 'String' argument. +-- | Builder for an 'IsString' argument. strArgument :: IsString s => Mod ArgumentFields s -> Parser s strArgument = argument str +-- | Builder for a 'OsString' argument. +osStrArgument :: Mod ArgumentFields OsString -> Parser OsString +osStrArgument = argument osStr + -- | Builder for a flag parser. -- -- A flag that switches from a \"default value\" to an \"active value\" when @@ -357,16 +381,20 @@ abortOption :: ParseError -> Mod OptionFields (a -> a) -> Parser (a -> a) abortOption err m = option (readerAbort err) . (`mappend` m) $ mconcat [ noArgError err , value id - , metavar "" ] + , metavar Strict.empty ] -- | An option that always fails and displays a message. -infoOption :: String -> Mod OptionFields (a -> a) -> Parser (a -> a) +infoOption :: Strict.Text -> Mod OptionFields (a -> a) -> Parser (a -> a) infoOption = abortOption . InfoMsg --- | Builder for an option taking a 'String' argument. +-- | Builder for an option taking a 'IsString' argument. strOption :: IsString s => Mod OptionFields s -> Parser s strOption = option str +-- | Builder for an option taking a 'OsString' argument. +osStrOption :: Mod OptionFields OsString -> Parser OsString +osStrOption = option osStr + -- | Builder for an option using the given reader. -- -- This is a regular option, and should always have either a @long@ or @@ -395,13 +423,13 @@ option r m = mkParser d g rdr -- - Group Inner -- ... -- @ -optPropertiesGroup :: String -> OptProperties -> OptProperties +optPropertiesGroup :: OsString -> OptProperties -> OptProperties optPropertiesGroup g o = o { propGroup = OptGroup (g : oldGroup) } where OptGroup oldGroup = propGroup o -- | Prepends a group per 'optPropertiesGroup'. -optionGroup :: String -> Option a -> Option a +optionGroup :: OsString -> Option a -> Option a optionGroup grp o = o { optProps = props' } where props' = optPropertiesGroup grp (optProps o) @@ -428,8 +456,8 @@ optionGroup grp o = o { optProps = props' } -- > Group B -- > -- --- @since 0.19.0.0 -parserOptionGroup :: String -> Parser a -> Parser a +-- @since 0.9.0.0 +parserOptionGroup :: OsString -> Parser a -> Parser a parserOptionGroup g = mapParserOptions (optionGroup g) -- | Modifier for 'ParserInfo'. @@ -452,7 +480,7 @@ briefDesc :: InfoMod a briefDesc = InfoMod $ \i -> i { infoFullDesc = False } -- | Specify a header for this parser. -header :: String -> InfoMod a +header :: Strict.Text -> InfoMod a header s = InfoMod $ \i -> i { infoHeader = paragraph s } -- | Specify a header for this parser as a 'Prettyprinter.Doc AnsiStyle' @@ -461,7 +489,7 @@ headerDoc :: Maybe Doc -> InfoMod a headerDoc doc = InfoMod $ \i -> i { infoHeader = Chunk doc } -- | Specify a footer for this parser. -footer :: String -> InfoMod a +footer :: Strict.Text -> InfoMod a footer s = InfoMod $ \i -> i { infoFooter = paragraph s } -- | Specify a footer for this parser as a 'Prettyprinter.Doc AnsiStyle' @@ -470,7 +498,7 @@ footerDoc :: Maybe Doc -> InfoMod a footerDoc doc = InfoMod $ \i -> i { infoFooter = Chunk doc } -- | Specify a short program description. -progDesc :: String -> InfoMod a +progDesc :: Strict.Text -> InfoMod a progDesc s = InfoMod $ \i -> i { infoProgDesc = paragraph s } -- | Specify a short program description as a 'Prettyprinter.Doc AnsiStyle' @@ -532,7 +560,7 @@ instance Semigroup PrefsMod where -- | Include a suffix to attach to the metavar when multiple values -- can be entered. -multiSuffix :: String -> PrefsMod +multiSuffix :: Strict.Text -> PrefsMod multiSuffix s = PrefsMod $ \p -> p { prefMultiSuffix = s } -- | Turn on disambiguation. @@ -593,7 +621,7 @@ prefs :: PrefsMod -> ParserPrefs prefs m = applyPrefsMod m base where base = ParserPrefs - { prefMultiSuffix = "" + { prefMultiSuffix = Strict.empty , prefDisambiguate = False , prefShowHelpOnError = False , prefShowHelpOnEmpty = False @@ -613,3 +641,5 @@ idm = mempty -- | Default preferences. defaultPrefs :: ParserPrefs defaultPrefs = prefs idm + + diff --git a/src/Options/Applicative/Builder/Completer.hs b/src/Options/Applicative/Builder/Completer.hs index 5da556e7..34e03a59 100644 --- a/src/Options/Applicative/Builder/Completer.hs +++ b/src/Options/Applicative/Builder/Completer.hs @@ -1,4 +1,7 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE PatternGuards #-} module Options.Applicative.Builder.Completer ( Completer @@ -13,21 +16,22 @@ module Options.Applicative.Builder.Completer import Control.Applicative import Prelude import Control.Exception (IOException, try) -import Data.List (isPrefixOf) -#ifdef MIN_VERSION_process import System.Process (readProcess) -#endif import Options.Applicative.Types +import qualified "os-string" System.OsString as OsString +import "os-string" System.OsString (OsString, osstr) +import qualified Data.Text as Strict +import Options.Applicative.Help (osStringToStrictText) -- | Create a 'Completer' from an IO action -listIOCompleter :: IO [String] -> Completer +listIOCompleter :: IO [Strict.Text] -> Completer listIOCompleter ss = Completer $ \s -> - filter (isPrefixOf s) <$> ss + filter (Strict.isPrefixOf (osStringToStrictText s)) <$> ss -- | Create a 'Completer' from a constant -- list of strings. -listCompleter :: [String] -> Completer +listCompleter :: [Strict.Text] -> Completer listCompleter = listIOCompleter . pure -- | Run a compgen completion action. @@ -36,15 +40,11 @@ listCompleter = listIOCompleter . pure -- @directory@. See -- -- for a complete list. -bashCompleter :: String -> Completer -#ifdef MIN_VERSION_process +bashCompleter :: OsString -> Completer bashCompleter action = Completer $ \word -> do - let cmd = unwords ["compgen", "-A", action, "--", requote word] + cmd <- OsString.decodeUtf $ OsString.intercalate [osstr| |] [[osstr|compgen|], [osstr|-A|], action, [osstr|--|], requote word] result <- tryIO $ readProcess "bash" ["-c", cmd] "" - return . lines . either (const []) id $ result -#else -bashCompleter = const $ Completer $ const $ return [] -#endif + return . (Strict.lines) . either (const Strict.empty) (Strict.pack) $ result tryIO :: IO a -> IO (Either IOException a) tryIO = try @@ -54,79 +54,103 @@ tryIO = try -- We need to do this so bash doesn't expand out any ~ or other -- chars we want to complete on, or emit an end of line error -- when seeking the close to the quote. -requote :: String -> String +requote :: OsString -> OsString requote s = let -- Bash doesn't appear to allow "mixed" escaping -- in bash completions. So we don't have to really -- worry about people swapping between strong and -- weak quotes. + unescaped = - case s of + case OsString.uncons s of -- It's already strongly quoted, so we -- can use it mostly as is, but we must -- ensure it's closed off at the end and -- there's no single quotes in the -- middle which might confuse bash. - ('\'': rs) -> unescapeN rs - + Just (c, rs) + | c == OsString.unsafeFromChar '\'' -> unescapeN rs -- We're weakly quoted. - ('"': rs) -> unescapeD rs - + Just (c, rs) + | c == OsString.unsafeFromChar '"'-> unescapeD rs -- We're not quoted at all. -- We need to unescape some characters like -- spaces and quotation marks. - elsewise -> unescapeU elsewise + _ -> unescapeU s in - strong unescaped + strong unescaped where - strong ss = '\'' : foldr go "'" ss + strong ss = OsString.unsafeFromChar '\'' `OsString.cons` OsString.foldr go [osstr|'|] ss where -- If there's a single quote inside the -- command: exit from the strong quote and -- emit it the quote escaped, then resume. - go '\'' t = "'\\''" ++ t - go h t = h : t + -- go '\'' t = "'\\''" ++ t + -- go h t = h : t + go h t = + if h == OsString.unsafeFromChar '\'' + then [osstr|'\''|] <> t + else h `OsString.cons` t + -- Unescape a strongly quoted string -- We have two recursive functions, as we -- can enter and exit the strong escaping. - unescapeN = goX + unescapeN = OsString.pack . goX where - goX ('\'' : xs) = goN xs - goX (x : xs) = x : goX xs - goX [] = [] - - goN ('\\' : '\'' : xs) = '\'' : goN xs - goN ('\'' : xs) = goX xs - goN (x : xs) = x : goN xs - goN [] = [] + goX v = case OsString.uncons v of + Just (x, xs) + | x == OsString.unsafeFromChar '\'' + -> goN xs + Just (x, xs) -> x : goX xs + Nothing -> [] + + goN v = case OsString.uncons v of + Just (x, xs) + | x == OsString.unsafeFromChar '\\' + , Just (x', xs') <- OsString.uncons xs + , x' == OsString.unsafeFromChar '\'' + -> x' : goN xs' + Just (x, xs) + | x == OsString.unsafeFromChar '\'' + -> goX xs + Just (x, xs) -> x : goN xs + Nothing -> [] -- Unescape an unquoted string - unescapeU = goX + unescapeU = OsString.pack . goX where - goX [] = [] - goX ('\\' : x : xs) = x : goX xs - goX (x : xs) = x : goX xs + goX v = case OsString.uncons v of + Nothing -> [] + Just (c1, xs) + | c1 == OsString.unsafeFromChar '\\' + , Just (x, xs') <- OsString.uncons xs + -> x : goX xs' + Just (x, xs) -> x : goX xs -- Unescape a weakly quoted string - unescapeD = goX + unescapeD = OsString.pack . goX where - -- Reached an escape character - goX ('\\' : x : xs) - -- If it's true escapable, strip the - -- slashes, as we're going to strong - -- escape instead. - | x `elem` "$`\"\\\n" = x : goX xs - | otherwise = '\\' : x : goX xs - -- We've ended quoted section, so we - -- don't recurse on goX, it's done. - goX ('"' : xs) - = xs - -- Not done, but not a special character - -- just continue the fold. - goX (x : xs) - = x : goX xs - goX [] - = [] + goX v = case OsString.uncons v of + -- Reached an escape character + Just (x, xs) + | x == OsString.unsafeFromChar '\\' + , Just (x', xs') <- OsString.uncons xs -> + -- If it's true escapable, strip the + -- slashes, as we're going to strong + -- escape instead. + if x' + `OsString.elem` [osstr||$`"|] + then x' : goX xs' + else x : x' : goX xs' + -- We've ended quoted section, so we + -- don't recurse on goX, it's done. + Just (x, xs) + | x == OsString.unsafeFromChar '"' -> + OsString.unpack xs + -- Not done, but not a special character + -- just continue the fold. + Just (x, xs) -> x : goX xs + Nothing -> [] diff --git a/src/Options/Applicative/Builder/Internal.hs b/src/Options/Applicative/Builder/Internal.hs index 2110067b..9a71fa2e 100644 --- a/src/Options/Applicative/Builder/Internal.hs +++ b/src/Options/Applicative/Builder/Internal.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE PackageImports #-} module Options.Applicative.Builder.Internal ( -- * Internals Mod(..), @@ -31,19 +32,22 @@ import Prelude import Options.Applicative.Common import Options.Applicative.Types +import "os-string" System.OsString (OsString) +import qualified "os-string" System.OsString as OsString +import qualified Data.Text as Strict data OptionFields a = OptionFields { optNames :: [OptName] , optCompleter :: Completer - , optNoArgError :: String -> ParseError } + , optNoArgError :: OsString -> ParseError } data FlagFields a = FlagFields { flagNames :: [OptName] , flagActive :: a } data CommandFields a = CommandFields - { cmdCommands :: [(String, ParserInfo a)] - , cmdGroup :: Maybe String } + { cmdCommands :: [(OsString, ParserInfo a)] + , cmdGroup :: Maybe OsString } data ArgumentFields a = ArgumentFields { argCompleter :: Completer } @@ -86,8 +90,8 @@ instance HasMetavar CommandFields where -- mod -- data DefaultProp a = DefaultProp - (Maybe a) - (Maybe (a -> String)) + (Maybe a) -- ^ Default value + (Maybe (a -> Strict.Text)) -- ^ Function to show the default value instance Monoid (DefaultProp a) where mempty = DefaultProp Nothing Nothing @@ -145,7 +149,7 @@ instance Semigroup (Mod f a) where -- | Base default properties. baseProps :: OptProperties baseProps = OptProperties - { propMetaVar = "" + { propMetaVar = Strict.empty , propVisibility = Visible , propHelp = mempty , propShowDefault = Nothing @@ -154,7 +158,7 @@ baseProps = OptProperties , propGroup = OptGroup [] } -mkCommand :: Mod CommandFields a -> (Maybe String, [(String, ParserInfo a)]) +mkCommand :: Mod CommandFields a -> (Maybe OsString, [(OsString, ParserInfo a)]) mkCommand m = (group, cmds) where Mod f _ _ = m diff --git a/src/Options/Applicative/Common.hs b/src/Options/Applicative/Common.hs index e6d6cda0..1b646c35 100644 --- a/src/Options/Applicative/Common.hs +++ b/src/Options/Applicative/Common.hs @@ -1,4 +1,9 @@ {-# LANGUAGE Rank2Types #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PatternGuards #-} +{-# LANGUAGE OverloadedStrings #-} module Options.Applicative.Common ( -- * Option parsers -- @@ -23,7 +28,7 @@ module Options.Applicative.Common ( liftOpt, showOption, - -- * Program descriptions + -- * Program descriptions -- -- A 'ParserInfo' describes a command line program, used to generate a help -- screen. Two help modes are supported: brief and full. In brief mode, only @@ -33,6 +38,7 @@ module Options.Applicative.Common ( -- A basic 'ParserInfo' with default values for fields can be created using -- the 'info' function. -- + -- -- A 'ParserPrefs' contains general preferences for all command-line -- options, and can be built with the 'prefs' function. ParserInfo(..), @@ -55,16 +61,20 @@ import Control.Applicative import Control.Monad (guard, mzero, msum, when) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.State (StateT(..), get, put, runStateT) -import Data.List (isPrefixOf) import Data.Maybe (maybeToList, isJust, isNothing) import Prelude import Options.Applicative.Internal import Options.Applicative.Types -showOption :: OptName -> String -showOption (OptLong n) = "--" ++ n -showOption (OptShort n) = '-' : [n] +import qualified "os-string" System.OsString as OsString +import "os-string" System.OsString (osstr, OsString) +import qualified Data.Text as Strict +import Options.Applicative.Help.Pretty (osStringToStrictText) + +showOption :: OptName -> OsString +showOption (OptLong n) = [osstr|--|] <> n +showOption (OptShort n) = OsString.pack [OsString.unsafeFromChar '-',n] optionNames :: OptReader a -> [OptName] optionNames (OptReader names _ _) = names @@ -73,7 +83,7 @@ optionNames _ = [] isOptionPrefix :: OptName -> OptName -> Bool isOptionPrefix (OptShort x) (OptShort y) = x == y -isOptionPrefix (OptLong x) (OptLong y) = x `isPrefixOf` y +isOptionPrefix (OptLong x) (OptLong y) = x `OsString.isPrefixOf` y isOptionPrefix _ _ = False -- | Create a parser composed of a single option. @@ -101,7 +111,7 @@ optMatches disambiguate opt (OptWord arg1 val) = case opt of guard $ isShortName arg1 || isNothing val Just $ do args <- get - let val' = ('-' :) <$> val + let val' = (OsString.unsafeFromChar '-' `OsString.cons`) <$> val put $ maybeToList val' ++ args return x @@ -109,7 +119,8 @@ optMatches disambiguate opt (OptWord arg1 val) = case opt of Nothing where - errorFor name msg = "option " ++ showOption name ++ ": " ++ msg + errorFor :: OptName -> Strict.Text -> Strict.Text + errorFor name msg = "option " <> osStringToStrictText (showOption name) <> ": " <> msg has_name a | disambiguate = any (isOptionPrefix a) @@ -119,20 +130,23 @@ isArg :: OptReader a -> Bool isArg (ArgReader _) = True isArg _ = False -data OptWord = OptWord OptName (Maybe String) +data OptWord = OptWord OptName (Maybe OsString) -parseWord :: String -> Maybe OptWord -parseWord ('-' : '-' : w) = Just $ let - (opt, arg) = case span (/= '=') w of - (_, "") -> (w, Nothing) - (w', _ : rest) -> (w', Just rest) +parseWord :: OsString -> Maybe OptWord +parseWord s + | ([osstr|--|], w) <- OsString.splitAt 2 s = + Just $ let + (opt, arg) = case OsString.span (/= OsString.unsafeFromChar '=') w of + (_, t) | OsString.null t -> (w, Nothing) + (w', rest) -> (w', Just (OsString.drop 1 rest)) in OptWord (OptLong opt) arg -parseWord ('-' : w) = case w of - [] -> Nothing - (a : rest) -> Just $ let - arg = rest <$ guard (not (null rest)) - in OptWord (OptShort a) arg -parseWord _ = Nothing + | ([osstr|-|], w) <- OsString.splitAt 1 s = + case OsString.uncons w of + Nothing -> Nothing + Just (a, rest) -> Just $ let + arg = rest <$ guard (not (OsString.null rest)) + in OptWord (OptShort a) arg + | otherwise = Nothing searchParser :: Monad m => (forall r . Option r -> NondetT m (Parser r)) @@ -166,7 +180,7 @@ searchOpt pprefs w = searchParser $ \opt -> do Nothing -> mzero -searchArg :: MonadP m => ParserPrefs -> String -> Parser a +searchArg :: MonadP m => ParserPrefs -> OsString -> Parser a -> NondetT (StateT Args m) (Parser a) searchArg prefs arg = searchParser $ \opt -> do @@ -194,11 +208,12 @@ searchArg prefs arg = mzero where + cmdMatches :: [(OsString, ParserInfo r)] -> [ParserInfo r] cmdMatches cs - | prefDisambiguate prefs = snd <$> filter (isPrefixOf arg . fst) cs + | prefDisambiguate prefs = snd <$> filter (OsString.isPrefixOf arg . fst) cs | otherwise = maybeToList (lookup arg cs) -stepParser :: MonadP m => ParserPrefs -> ArgPolicy -> String +stepParser :: MonadP m => ParserPrefs -> ArgPolicy -> OsString -> Parser a -> NondetT (StateT Args m) (Parser a) stepParser pprefs AllPositionals arg p = searchArg pprefs arg p @@ -214,7 +229,7 @@ stepParser pprefs _ arg p = case parseWord arg of -- arguments. This function returns an error if any parsing error occurs, or -- if any options are missing and don't have a default value. runParser :: MonadP m => ArgPolicy -> Parser a -> Args -> m (a, Args) -runParser policy p ("--" : argt) | policy /= AllPositionals +runParser policy p ([osstr|--|] : argt) | policy /= AllPositionals = runParser AllPositionals p argt runParser policy p args = case args of [] -> exitP policy p result @@ -233,14 +248,14 @@ runParser policy p args = case args of NoIntersperse -> if isJust (parseWord a) then NoIntersperse else AllPositionals x -> x -runParserStep :: MonadP m => ArgPolicy -> Parser a -> String -> Args -> m (Maybe (Parser a), Args) +runParserStep :: MonadP m => ArgPolicy -> Parser a -> OsString -> Args -> m (Maybe (Parser a), Args) runParserStep policy p arg args = do prefs <- getPrefs flip runStateT args $ disamb (not (prefDisambiguate prefs)) $ stepParser prefs policy arg p -parseError :: MonadP m => String -> Parser x -> m a +parseError :: MonadP m => OsString -> Parser x -> m a parseError arg = errorP . UnexpectedError arg . SomeParser runParserInfo :: MonadP m => ParserInfo a -> Args -> m a diff --git a/src/Options/Applicative/Extra.hs b/src/Options/Applicative/Extra.hs index 97ed572d..1a60f04c 100644 --- a/src/Options/Applicative/Extra.hs +++ b/src/Options/Applicative/Extra.hs @@ -1,4 +1,7 @@ {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Options.Applicative.Extra ( -- * Extra parser utilities -- @@ -26,9 +29,9 @@ import Control.Monad (void) import Data.Monoid import Data.Foldable (traverse_) import Prelude -import System.Environment (getArgs, getProgName) +import System.Environment (getProgName) import System.Exit (exitSuccess, exitWith, ExitCode(..)) -import System.IO (hPutStrLn, stderr) +import System.IO (stderr) import Options.Applicative.BashCompletion import Options.Applicative.Builder @@ -38,6 +41,15 @@ import Options.Applicative.Help import Options.Applicative.Internal import Options.Applicative.Types +import qualified System.Process.Environment.OsString as EOS +import System.OsPath (OsPath) +import "os-string" System.OsString (osstr, OsString) +import qualified "os-string" System.OsString as OsString +import System.IO.Unsafe (unsafePerformIO) +import qualified Data.Text.Lazy as Lazy +import qualified Data.Text.Lazy.IO as Lazy +import qualified Data.Text.IO as Strict +import qualified Data.Text as Strict -- | A hidden \"helper\" option which always fails. -- @@ -50,8 +62,8 @@ import Options.Applicative.Types helper :: Parser (a -> a) helper = helperWith (mconcat [ - long "help", - short 'h', + long [osstr|help|], + short $ OsString.unsafeFromChar 'h', help "Show this help text" ]) @@ -70,7 +82,7 @@ helperWith modifiers = option helpReader $ mconcat [ value id, - metavar "", + metavar Strict.empty, noGlobal, noArgError (ShowHelpText Nothing), hidden, @@ -98,11 +110,11 @@ hsubparser m = mkParser d g rdr -- -- > opts :: ParserInfo Sample -- > opts = info (sample <**> simpleVersioner "v1.2.3") mempty -simpleVersioner :: String -- ^ Version string to be shown +simpleVersioner :: Strict.Text -- ^ Version string to be shown -> Parser (a -> a) simpleVersioner version = infoOption version $ mconcat - [ long "version" + [ long [osstr|version|] , help "Show version information" , hidden ] @@ -117,23 +129,25 @@ execParser = customExecParser defaultPrefs -- | Run a program description with custom preferences. customExecParser :: ParserPrefs -> ParserInfo a -> IO a customExecParser pprefs pinfo - = execParserPure pprefs pinfo <$> getArgs + = execParserPure pprefs pinfo <$> EOS.getArgs >>= handleParseResult -- | Handle `ParserResult`. handleParseResult :: ParserResult a -> IO a handleParseResult (Success a) = return a handleParseResult (Failure failure) = do - progn <- getProgName + -- TODO: OsString.getProgName (process/unix/Win32)? + progn <- OsString.unsafeEncodeUtf <$> getProgName let (msg, exit) = renderFailure failure progn case exit of - ExitSuccess -> putStrLn msg - _ -> hPutStrLn stderr msg + ExitSuccess -> Lazy.putStrLn msg + _ -> Lazy.hPutStrLn stderr msg exitWith exit handleParseResult (CompletionInvoked compl) = do - progn <- getProgName + -- TODO: OsString.getProgName (process/unix/Win32)? + progn <- OsString.unsafeEncodeUtf <$> getProgName msg <- execCompletion compl progn - putStr msg + Strict.putStr msg exitSuccess -- | Extract the actual result from a `ParserResult` value. @@ -150,7 +164,7 @@ getParseResult _ = Nothing -- | The most general way to run a program description in pure code. execParserPure :: ParserPrefs -- ^ Global preferences for this parser -> ParserInfo a -- ^ Description of the program to run - -> [String] -- ^ Program arguments + -> [OsPath] -- ^ Program arguments -> ParserResult a execParserPure pprefs pinfo args = case runP p pprefs of @@ -204,7 +218,7 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> with_context :: [Context] -> ParserInfo a - -> (forall b . [String] -> ParserInfo b -> c) + -> (forall b . [OsString] -> ParserInfo b -> c) -> c with_context [] i f = f [] i with_context c@(Context _ i:_) _ f = f (contextNames c) i @@ -229,7 +243,7 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> -> mempty _ -> mconcat [ - usageHelp (pure . parserUsage pprefs (infoParser i) . unwords $ progn : names) + usageHelp (pure . parserUsage pprefs (infoParser i) . (OsString.intercalate [osstr| |]) $ progn : names) , descriptionHelp (infoProgDesc i) ] @@ -251,17 +265,17 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> -> stringChunk "Missing:" <<+>> missingDesc pprefs x ExpectsArgError x - -> stringChunk $ "The option `" ++ x ++ "` expects an argument." + -> stringChunk $ "The option `" <> osStringToStrictText x <> "` expects an argument." UnexpectedError arg _ -> stringChunk msg' where - -- + arg' = osStringToStrictText arg -- This gives us the same error we have always -- reported - msg' = case arg of - ('-':_) -> "Invalid option `" ++ arg ++ "'" - _ -> "Invalid argument `" ++ arg ++ "'" + msg' = case OsString.uncons arg of + Just (char,_) | OsString.unsafeFromChar '-' == char -> "Invalid option `" <> arg' <> "'" + _ -> "Invalid argument `" <> arg' <> "'" UnknownError -> mempty @@ -283,7 +297,7 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> -- show "Did you mean" if there's nothing there -- to show suggestions = (.$.) <$> prose - <*> (indent 4 <$> (vcatChunks . fmap stringChunk $ good )) + <*> (indent 4 <$> (vcatChunks . fmap (stringChunk . osStringToStrictText) $ good )) -- -- We won't worry about the 0 case, it won't be @@ -299,8 +313,9 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> -- -- Bit of an arbitrary decision here. - -- Edit distances of 1 or 2 will give hints - isClose a = editDistance a arg < 3 + -- Edit distances of or 2 will give hints + isClose :: OsString -> Bool + isClose a = editDistance (unsafePerformIO . OsString.decodeLE $ a) (unsafePerformIO . OsString.decodeLE $ arg) < 3 -- -- Similar to how bash completion works. @@ -341,7 +356,7 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> InfoMsg _ -> False _ -> prefShowHelpOnError pprefs -renderFailure :: ParserFailure ParserHelp -> String -> (String, ExitCode) +renderFailure :: ParserFailure ParserHelp -> OsString -> (Lazy.Text, ExitCode) renderFailure failure progn = let (h, exit, cols) = execFailure failure progn in (renderHelp cols h, exit) diff --git a/src/Options/Applicative/Help/Chunk.hs b/src/Options/Applicative/Help/Chunk.hs index be5fd6e9..d286d7b3 100644 --- a/src/Options/Applicative/Help/Chunk.hs +++ b/src/Options/Applicative/Help/Chunk.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE PackageImports #-} module Options.Applicative.Help.Chunk ( Chunk(..) , chunked @@ -21,6 +22,7 @@ import Data.Semigroup import Prelude import Options.Applicative.Help.Pretty +import qualified Data.Text as Strict -- | The free monoid on a semigroup @a@. newtype Chunk a = Chunk @@ -114,9 +116,10 @@ isEmpty = isNothing . unChunk -- -- > isEmpty . stringChunk = null -- > extractChunk . stringChunk = string -stringChunk :: String -> Chunk Doc -stringChunk "" = mempty -stringChunk s = pure (pretty s) +stringChunk :: Strict.Text -> Chunk Doc +stringChunk s = if Strict.null s + then mempty + else pure (pretty s) -- | Convert a paragraph into a 'Chunk'. The resulting chunk is composed by the -- words of the original paragraph separated by softlines, so it will be @@ -125,9 +128,9 @@ stringChunk s = pure (pretty s) -- This satisfies: -- -- > isEmpty . paragraph = null . words -paragraph :: String -> Chunk Doc +paragraph :: Strict.Text -> Chunk Doc paragraph = foldr (chunked () . stringChunk) mempty - . words + . Strict.words -- | Display pairs of strings in a table. tabulate :: Int -> [(Doc, Doc)] -> Chunk Doc diff --git a/src/Options/Applicative/Help/Core.hs b/src/Options/Applicative/Help/Core.hs index f193f0eb..b3a53237 100644 --- a/src/Options/Applicative/Help/Core.hs +++ b/src/Options/Applicative/Help/Core.hs @@ -1,4 +1,7 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Options.Applicative.Help.Core ( cmdDesc, briefDesc, @@ -35,6 +38,7 @@ import Options.Applicative.Common import Options.Applicative.Types import Options.Applicative.Help.Pretty import Options.Applicative.Help.Chunk +import "os-string" System.OsString (OsString, osstr) -- | Style for rendering an option. data OptDescStyle @@ -56,7 +60,7 @@ optDesc pprefs style _reachability opt = stringChunk $ optMetaVar opt grp = propGroup $ optProps opt descs = - map (pretty . showOption) names + map (pretty . osStringToStrictText . showOption) names descriptions = listToChunk (List.intersperse (descSep style) descs) desc @@ -88,7 +92,7 @@ optDesc pprefs style _reachability opt = in (grp, modified, wrapping) -- | Generate descriptions for commands. -cmdDesc :: ParserPrefs -> Parser a -> [(Maybe String, Chunk Doc)] +cmdDesc :: ParserPrefs -> Parser a -> [(Maybe OsString, Chunk Doc)] cmdDesc pprefs = mapParser desc where desc _ opt = @@ -96,7 +100,7 @@ cmdDesc pprefs = mapParser desc CmdReader gn cmds -> (,) gn $ tabulate (prefTabulateFill pprefs) - [ (pretty nm, align (extractChunk (infoProgDesc cmd))) + [ (pretty . osStringToStrictText $ nm, align (extractChunk (infoProgDesc cmd))) | (nm, cmd) <- reverse cmds ] _ -> mempty @@ -263,11 +267,11 @@ optionsDesc global pprefs p = formatTitle' :: [(OptGroup, Chunk Doc)] -> [Chunk Doc] formatTitle' = reverse . snd . foldl' formatTitle ([], []) - formatTitle :: ([String], [Chunk Doc]) -> (OptGroup, Chunk Doc) -> ([String], [Chunk Doc]) + formatTitle :: ([OsString], [Chunk Doc]) -> (OptGroup, Chunk Doc) -> ([OsString], [Chunk Doc]) formatTitle (printedGroups, acc) o@(OptGroup groups, opts) = case parentGroups of -- No nested groups: No special logic. - [] -> (groupTitle : printedGroups, ((\d -> pretty groupTitle .$. d) <$> opts) : acc) + [] -> (groupTitle : printedGroups, ((\d -> pretty (osStringToStrictText groupTitle) .$. d) <$> opts) : acc) -- We have at least one parent group title P for current group G: P has -- already been printed iff it is attached to another (non-grouped) -- option. In other words, P has __not__ been printed if its only @@ -305,8 +309,8 @@ optionsDesc global pprefs p = defTitle = if global - then "Global options:" - else "Available options:" + then [osstr|Global options:|] + else [osstr|Available options:|] maxGroupLevel :: Int maxGroupLevel = findMaxGroupLevel docs @@ -330,7 +334,7 @@ optionsDesc global pprefs p = (grp, n, _) = optDesc pprefs style info opt h = optHelp opt hdef = Chunk . fmap show_def . optShowDefault $ opt - show_def s = parens (pretty "default:" <+> pretty s) + show_def s = parens (pretty ("default:" :: String) <+> pretty s) style = OptDescStyle { descSep = pretty ',', descHidden = True, @@ -340,17 +344,17 @@ optionsDesc global pprefs p = -- -- Prints all parent titles that have not already been printed -- (i.e. in printedGroups). - mkParentDocs :: [String] -> [(Int, String)] -> Doc + mkParentDocs :: [OsString] -> [(Int, OsString)] -> Doc mkParentDocs printedGroups = foldr g mempty where - g :: (Int, String) -> Doc -> Doc + g :: (Int, OsString) -> Doc -> Doc g (i, s) acc | s `List.elem` printedGroups = acc - | i == 0 = pretty s .$. acc + | i == 0 = pretty (osStringToStrictText s) .$. acc | otherwise = lvlIndentNSub1 i $ hyphenate s .$. acc - hyphenate s = pretty ("- " <> s) + hyphenate s = pretty $ osStringToStrictText ([osstr|- |] <> s) lvlIndentNSub1 :: Int -> Doc -> Doc lvlIndentNSub1 n = indent (lvlIndent * (n - 1)) @@ -389,7 +393,7 @@ parserHelp pprefs p = fullDesc pprefs p : (group_title <$> cs) where - def = "Available commands:" + def = [osstr|Available commands:|] cs = groupFstAll $ cmdDesc pprefs p group_title a@((n, _) : _) = @@ -397,8 +401,8 @@ parserHelp pprefs p = vcatChunks (snd <$> a) group_title _ = mempty - with_title :: String -> Chunk Doc -> Chunk Doc - with_title title = fmap (pretty title .$.) + with_title :: OsString -> Chunk Doc -> Chunk Doc + with_title title = fmap (pretty (osStringToStrictText title) .$.) parserGlobals :: ParserPrefs -> Parser a -> ParserHelp @@ -408,12 +412,12 @@ parserGlobals pprefs p = -- | Generate option summary. -parserUsage :: ParserPrefs -> Parser a -> String -> Doc +parserUsage :: ParserPrefs -> Parser a -> OsString -> Doc parserUsage pprefs p progn = group $ hsep - [ pretty "Usage:", - pretty progn, + [ pretty ("Usage:" :: String), + pretty (osStringToStrictText progn), hangAtIfOver 9 (prefBriefHangPoint pprefs) (extractChunk (briefDesc pprefs p)) ] @@ -484,3 +488,6 @@ groupFstAll = -- | From base-4.19.0.0. unsnoc :: [a] -> Maybe ([a], a) unsnoc = foldr (\x -> Just . maybe ([], x) (\(~(a, b)) -> (x : a, b))) Nothing + + + diff --git a/src/Options/Applicative/Help/Levenshtein.hs b/src/Options/Applicative/Help/Levenshtein.hs index 149b9d95..f167c3b5 100644 --- a/src/Options/Applicative/Help/Levenshtein.hs +++ b/src/Options/Applicative/Help/Levenshtein.hs @@ -17,7 +17,7 @@ module Options.Applicative.Help.Levenshtein ( -- -- Complexity -- O(|a|*(1 + editDistance a b)) -editDistance :: Eq a => [a] -> [a] -> Int +editDistance :: String -> String -> Int editDistance a b = let mainDiag = diff --git a/src/Options/Applicative/Help/Pretty.hs b/src/Options/Applicative/Help/Pretty.hs index 43d111a8..8dae938e 100644 --- a/src/Options/Applicative/Help/Pretty.hs +++ b/src/Options/Applicative/Help/Pretty.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE PackageImports #-} module Options.Applicative.Help.Pretty ( module Prettyprinter , module Prettyprinter.Render.Terminal @@ -12,19 +13,27 @@ module Options.Applicative.Help.Pretty , altSep , hangAtIfOver - , prettyString + , prettyLazyText + , osStringToStrictText + , osStringToLazyText ) where -#if !MIN_VERSION_base(4,11,0) -import Data.Semigroup ((<>), mempty) -#endif -import qualified Data.Text.Lazy as Lazy +import qualified Data.Text.Encoding as Strict + +import qualified Data.ByteString.Short as Short import Prettyprinter hiding (Doc) import qualified Prettyprinter as PP import Prettyprinter.Render.Terminal import Prelude +import "os-string" System.OsString (OsString) +import qualified "os-string" System.OsString.Internal.Types as OsString +import Data.Coerce (coerce) +import Data.ByteString (ByteString) +import Data.Text (Text) +import qualified Data.Text.Lazy as Lazy +import qualified Data.Text as Strict type Doc = PP.Doc AnsiStyle type SimpleDoc = SimpleDocStream AnsiStyle @@ -109,15 +118,25 @@ renderPretty ribbonFraction lineWidth = layoutPretty LayoutOptions { layoutPageWidth = AvailablePerLine lineWidth ribbonFraction } -prettyString :: Double -> Int -> Doc -> String -prettyString ribbonFraction lineWidth +prettyLazyText :: Double -> Int -> Doc -> Lazy.Text +prettyLazyText ribbonFraction lineWidth = streamToString . renderPretty ribbonFraction lineWidth -streamToString :: SimpleDocStream AnsiStyle -> String -streamToString sdoc = - let - rendered = - Prettyprinter.Render.Terminal.renderLazy sdoc - in - Lazy.unpack rendered +streamToString :: SimpleDocStream AnsiStyle -> Lazy.Text +streamToString = + Prettyprinter.Render.Terminal.renderLazy + +osStringToStrictText :: OsString -> Strict.Text +osStringToStrictText = decoder . Short.fromShort . coerce + where + decoder :: ByteString -> Text + decoder = +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) + Strict.decodeUtf16LEWith Strict.lenientDecode +#else + Strict.decodeUtf8Lenient +#endif + +osStringToLazyText :: OsString -> Lazy.Text +osStringToLazyText = Lazy.fromStrict . osStringToStrictText \ No newline at end of file diff --git a/src/Options/Applicative/Help/Types.hs b/src/Options/Applicative/Help/Types.hs index e9743ca2..755c8d86 100644 --- a/src/Options/Applicative/Help/Types.hs +++ b/src/Options/Applicative/Help/Types.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE PackageImports #-} module Options.Applicative.Help.Types ( ParserHelp (..) , renderHelp @@ -8,6 +9,7 @@ import Prelude import Options.Applicative.Help.Chunk import Options.Applicative.Help.Pretty +import qualified Data.Text.Lazy as Lazy data ParserHelp = ParserHelp { helpError :: Chunk Doc @@ -21,7 +23,7 @@ data ParserHelp = ParserHelp } instance Show ParserHelp where - showsPrec _ h = showString (renderHelp 80 h) + showsPrec _ h = shows (renderHelp 80 h) instance Monoid ParserHelp where mempty = ParserHelp mempty mempty mempty mempty mempty mempty mempty mempty @@ -39,8 +41,8 @@ helpText (ParserHelp e s h u d b g f) = extractChunk $ vsepChunks [e, s, h, u, fmap (indent 2) d, b, g, f] --- | Convert a help text to 'String'. -renderHelp :: Int -> ParserHelp -> String +-- | Convert a help text to 'Text'. +renderHelp :: Int -> ParserHelp -> Lazy.Text renderHelp cols - = prettyString 1.0 cols + = prettyLazyText 1.0 cols . helpText diff --git a/src/Options/Applicative/Internal.hs b/src/Options/Applicative/Internal.hs index 98ff4ff7..8f147840 100644 --- a/src/Options/Applicative/Internal.hs +++ b/src/Options/Applicative/Internal.hs @@ -42,9 +42,11 @@ import Control.Monad.Trans.State (StateT, get, put, modify, evalStateT, runState import Options.Applicative.Types +import System.OsPath (OsPath, OsString) +import qualified Data.Text as Strict class (Alternative m, MonadPlus m) => MonadP m where - enterContext :: String -> ParserInfo a -> m () + enterContext :: OsString -> ParserInfo a -> m () exitContext :: m () continueContext :: m () getPrefs :: m ParserPrefs @@ -74,7 +76,7 @@ instance MonadPlus P where mzero = P mzero mplus (P x) (P y) = P $ mplus x y -contextNames :: [Context] -> [String] +contextNames :: [Context] -> [OsString] contextNames ns = let go (Context n _) = n in reverse $ go <$> ns @@ -108,10 +110,10 @@ uncons :: [a] -> Maybe (a, [a]) uncons [] = Nothing uncons (x : xs) = Just (x, xs) -runReadM :: MonadP m => ReadM a -> String -> m a +runReadM :: MonadP m => ReadM a -> OsPath -> m a runReadM (ReadM r) s = hoistEither . runExcept $ runReaderT r s -withReadM :: (String -> String) -> ReadM a -> ReadM a +withReadM :: (Strict.Text -> Strict.Text) -> ReadM a -> ReadM a withReadM f = ReadM . mapReaderT (withExcept f') . unReadM where f' (ErrorMsg err) = ErrorMsg (f err) diff --git a/src/Options/Applicative/Types.hs b/src/Options/Applicative/Types.hs index 6639de4b..e86a9f8a 100644 --- a/src/Options/Applicative/Types.hs +++ b/src/Options/Applicative/Types.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE CPP, Rank2Types, ExistentialQuantification #-} +{-# LANGUAGE CPP, Rank2Types, ExistentialQuantification, QuasiQuotes, PackageImports #-} module Options.Applicative.Types ( ParseError(..), ParserInfo(..), @@ -64,16 +64,20 @@ import System.Exit (ExitCode(..)) import Options.Applicative.Help.Types import Options.Applicative.Help.Pretty import Options.Applicative.Help.Chunk +import System.OsPath (OsPath, OsString, OsChar) +import qualified "os-string" System.OsString as OsString +import "os-string" System.OsString (osstr) +import qualified Data.Text as Strict data ParseError - = ErrorMsg String - | InfoMsg String - | ShowHelpText (Maybe String) + = ErrorMsg Strict.Text -- ^ Erorr message to prettyprint + | InfoMsg Strict.Text -- ^ Erorr message to prettyprint + | ShowHelpText (Maybe OsString) | UnknownError | MissingError IsCmdStart SomeParser - | ExpectsArgError String - | UnexpectedError String SomeParser + | ExpectsArgError OsString -- ^ Expected argument, not received + | UnexpectedError OsString SomeParser data IsCmdStart = CmdStart | CmdCont deriving Show @@ -110,7 +114,7 @@ data Backtracking -- | Global preferences for a top-level 'Parser'. data ParserPrefs = ParserPrefs - { prefMultiSuffix :: String -- ^ metavar suffix for multiple options + { prefMultiSuffix :: Strict.Text -- ^ metavar suffix for multiple options , prefDisambiguate :: Bool -- ^ automatically disambiguate abbreviations -- (default: False) , prefShowHelpOnError :: Bool -- ^ always show help text on parse errors @@ -131,8 +135,8 @@ data ParserPrefs = ParserPrefs , prefBriefHangPoint :: Int -- ^ Width at which to hang the brief description } deriving (Eq, Show) -data OptName = OptShort !Char - | OptLong !String +data OptName = OptShort !OsChar + | OptLong !OsString deriving (Eq, Ord, Show) isShortName :: OptName -> Bool @@ -152,15 +156,15 @@ data OptVisibility -- | Groups for optionals. Can be multiple in the case of nested groups. -- -- @since 0.19.0.0 -newtype OptGroup = OptGroup [String] +newtype OptGroup = OptGroup [OsString] deriving (Eq, Ord, Show) -- | Specification for an individual parser option. data OptProperties = OptProperties { propVisibility :: OptVisibility -- ^ whether this flag is shown in the brief description , propHelp :: Chunk Doc -- ^ help text for this option - , propMetaVar :: String -- ^ metavariable for this option - , propShowDefault :: Maybe String -- ^ what to show in the help text as the default + , propMetaVar :: Strict.Text -- ^ metavariable for this option + , propShowDefault :: Maybe Strict.Text -- ^ what to show in the help text as the default , propShowGlobal :: Bool -- ^ whether the option is presented in global options text , propDescMod :: Maybe ( Doc -> Doc ) -- ^ a function to run over the brief description , propGroup :: OptGroup @@ -192,7 +196,7 @@ data SomeParser = forall a . SomeParser (Parser a) -- | Subparser context, containing the name of the subparser and its parser info. -- Used by 'Options.Applicative.Extra.parserFailure' to display relevant usage -- information when parsing inside a subparser fails. -data Context = forall a. Context String (ParserInfo a) +data Context = forall a. Context OsString (ParserInfo a) instance Show (Option a) where show opt = "Option {optProps = " ++ show (optProps opt) ++ "}" @@ -202,7 +206,7 @@ instance Functor Option where -- | A newtype over 'ReaderT String Except', used by option readers. newtype ReadM a = ReadM - { unReadM :: ReaderT String (Except ParseError) a } + { unReadM :: ReaderT OsString (Except ParseError) a } instance Functor ReadM where fmap f (ReadM r) = ReadM (fmap f r) @@ -224,14 +228,14 @@ instance Monad ReadM where #endif instance Fail.MonadFail ReadM where - fail = readerError + fail v = readerError $ Strict.pack v instance MonadPlus ReadM where mzero = ReadM mzero mplus (ReadM x) (ReadM y) = ReadM $ mplus x y -- | Return the value being read. -readerAsk :: ReadM String +readerAsk :: ReadM OsString readerAsk = ReadM ask -- | Abort option reader by exiting with a 'ParseError'. @@ -239,7 +243,7 @@ readerAbort :: ParseError -> ReadM a readerAbort = ReadM . lift . throwE -- | Abort option reader by exiting with an error message. -readerError :: String -> ReadM a +readerError :: Strict.Text -> ReadM a readerError = readerAbort . ErrorMsg data CReader a = CReader @@ -251,13 +255,13 @@ instance Functor CReader where -- | An 'OptReader' defines whether an option matches an command line argument. data OptReader a - = OptReader [OptName] (CReader a) (String -> ParseError) + = OptReader [OptName] (CReader a) (OsString -> ParseError) -- ^ option reader | FlagReader [OptName] !a -- ^ flag reader | ArgReader (CReader a) -- ^ argument reader - | CmdReader (Maybe String) [(String, ParserInfo a)] + | CmdReader (Maybe OsString) [(OsString, ParserInfo a)] -- ^ command reader instance Functor OptReader where @@ -323,10 +327,10 @@ instance Alternative Parser where -- | A shell complete function. newtype Completer = Completer - { runCompleter :: String -> IO [String] } + { runCompleter :: OsString -> IO [Strict.Text] } -- | Smart constructor for a 'Completer' -mkCompleter :: (String -> IO [String]) -> Completer +mkCompleter :: (OsString -> IO [Strict.Text]) -> Completer mkCompleter = Completer instance Semigroup Completer where @@ -338,20 +342,20 @@ instance Monoid Completer where mappend = (<>) newtype CompletionResult = CompletionResult - { execCompletion :: String -> IO String } + { execCompletion :: OsString -> IO Strict.Text } instance Show CompletionResult where showsPrec p _ = showParen (p > 10) $ showString "CompletionResult _" newtype ParserFailure h = ParserFailure - { execFailure :: String -> (h, ExitCode, Int) } + { execFailure :: OsString -> (h, ExitCode, Int) } instance Show h => Show (ParserFailure h) where showsPrec p (ParserFailure f) = showParen (p > 10) $ showString "ParserFailure" - . showsPrec 11 (f "") + . showsPrec 11 (f [osstr||]) instance Functor ParserFailure where fmap f (ParserFailure err) = ParserFailure $ \progn -> @@ -386,7 +390,7 @@ instance Monad ParserResult where Failure f >>= _ = Failure f CompletionInvoked c >>= _ = CompletionInvoked c -type Args = [String] +type Args = [OsPath] -- | Policy for how to handle options within the parse data ArgPolicy @@ -449,10 +453,10 @@ optVisibility = propVisibility . optProps optHelp :: Option a -> Chunk Doc optHelp = propHelp . optProps -optMetaVar :: Option a -> String +optMetaVar :: Option a -> Strict.Text optMetaVar = propMetaVar . optProps -optShowDefault :: Option a -> Maybe String +optShowDefault :: Option a -> Maybe Strict.Text optShowDefault = propShowDefault . optProps optDescMod :: Option a -> Maybe ( Doc -> Doc ) diff --git a/tests/Examples/Alternatives.hs b/tests/Examples/Alternatives.hs index 178ade6a..b596b6b1 100644 --- a/tests/Examples/Alternatives.hs +++ b/tests/Examples/Alternatives.hs @@ -1,7 +1,10 @@ +{-# LANGUAGE PackageImports #-} module Examples.Alternatives where import Options.Applicative +import qualified "os-string" System.OsString as OsString + data Value = A | B deriving (Eq, Show) @@ -9,10 +12,10 @@ values :: Parser [Value] values = many $ a <|> b a :: Parser Value -a = flag' A (short 'a') +a = flag' A (short (OsString.unsafeFromChar 'a')) b :: Parser Value -b = flag' B (short 'b') +b = flag' B (short (OsString.unsafeFromChar 'b')) opts :: ParserInfo [Value] opts = info values idm diff --git a/tests/Examples/Cabal.hs b/tests/Examples/Cabal.hs index 7100a545..f77a1b5d 100644 --- a/tests/Examples/Cabal.hs +++ b/tests/Examples/Cabal.hs @@ -1,4 +1,7 @@ {-# LANGUAGE Arrows, CPP #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Examples.Cabal where import Options.Applicative @@ -6,10 +9,9 @@ import Options.Applicative.Arrows import Data.Monoid -#if __GLASGOW_HASKELL__ <= 702 -(<>) :: Monoid a => a -> a -> a -(<>) = mappend -#endif +import System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString +import System.OsPath (OsPath) data Args = Args CommonOpts Command deriving Show @@ -32,11 +34,11 @@ data InstallOpts = InstallOpts data ConfigureOpts = ConfigureOpts { configTests :: Bool - , configFlags :: [String] } + , configFlags :: [OsString] } deriving Show data BuildOpts = BuildOpts - { buildDir :: FilePath } + { buildDir :: OsPath } deriving Show @@ -44,16 +46,16 @@ parser :: Parser Args parser = runA $ proc () -> do opts <- asA commonOpts -< () cmds <- (asA . hsubparser) - ( command "install" + ( command [osstr|install|] (info installParser (progDesc "Installs a list of packages")) - <> command "update" + <> command [osstr|update|] (info updateParser (progDesc "Updates list of known packages")) - <> command "configure" + <> command [osstr|configure|] (info configureParser (progDesc "Prepare to build the package")) - <> command "build" + <> command [osstr|build|] (info buildParser (progDesc "Make this package ready for installation")) ) -< () A (simpleVersioner "0.0.0") >>> A helper -< Args opts cmds @@ -61,8 +63,8 @@ parser = runA $ proc () -> do commonOpts :: Parser CommonOpts commonOpts = CommonOpts <$> option auto - ( short 'v' - <> long "verbose" + ( short (OsString.unsafeFromChar 'v') + <> long [osstr|verbose|] <> metavar "LEVEL" <> help "Set verbosity to LEVEL" <> value 0 ) @@ -75,8 +77,8 @@ installParser = runA $ proc () -> do installOpts :: Parser InstallOpts installOpts = runA $ proc () -> do - reinst <- asA (switch (long "reinstall")) -< () - force <- asA (switch (long "force-reinstall")) -< () + reinst <- asA (switch (long [osstr|reinstall|])) -< () + force <- asA (switch (long [osstr|force-reinstall|])) -< () returnA -< InstallOpts { instReinstall = reinst , instForce = force } @@ -92,11 +94,11 @@ configureParser = runA $ proc () -> do configureOpts :: Parser ConfigureOpts configureOpts = runA $ proc () -> do tests <- (asA . switch) - ( long "enable-tests" + ( long [osstr|enable-tests|] <> help "Enable compilation of test suites" ) -< () - flags <- (asA . many . strOption) - ( short 'f' - <> long "flags" + flags <- (asA . many . osStrOption) + ( short (OsString.unsafeFromChar 'f') + <> long [osstr|flags|] <> metavar "FLAGS" <> help "Enable the given flag" ) -< () returnA -< ConfigureOpts tests flags @@ -108,10 +110,10 @@ buildParser = runA $ proc () -> do buildOpts :: Parser BuildOpts buildOpts = runA $ proc () -> do - bdir <- (asA . strOption) - ( long "builddir" + bdir <- (asA . osStrOption) + ( long [osstr|builddir|] <> metavar "DIR" - <> value "dist" ) -< () + <> value [osstr|dist|] ) -< () returnA -< BuildOpts bdir pinfo :: ParserInfo Args diff --git a/tests/Examples/Commands.hs b/tests/Examples/Commands.hs index bb0be3a9..8a17e0e1 100644 --- a/tests/Examples/Commands.hs +++ b/tests/Examples/Commands.hs @@ -1,46 +1,50 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Examples.Commands where import Data.List import Data.Monoid import Options.Applicative -#if __GLASGOW_HASKELL__ <= 702 -(<>) :: Monoid a => a -> a -> a -(<>) = mappend -#endif +import System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString +import qualified Data.Text as Strict +import qualified Data.Text.IO as Strict.IO +import Options.Applicative.Help (osStringToStrictText) data Sample - = Hello [String] + = Hello [OsString] | Goodbye deriving (Eq, Show) hello :: Parser Sample -hello = Hello <$> many (argument str (metavar "TARGET...")) +hello = Hello <$> many (argument osStr (metavar "TARGET...")) sample :: Parser Sample sample = subparser - ( command "hello" + ( command [osstr|hello|] (info hello (progDesc "Print greeting")) - <> command "goodbye" + <> command [osstr|goodbye|] (info (pure Goodbye) (progDesc "Say goodbye")) ) <|> subparser - ( command "bonjour" + ( command [osstr|bonjour|] (info hello (progDesc "Print greeting")) - <> command "au-revoir" + <> command [osstr|au-revoir|] (info (pure Goodbye) (progDesc "Say goodbye")) - <> commandGroup "French commands:" + <> commandGroup [osstr|French commands:|] <> hidden ) run :: Sample -> IO () -run (Hello targets) = putStrLn $ "Hello, " ++ intercalate ", " targets ++ "!" -run Goodbye = putStrLn "Goodbye." +run (Hello targets) = Strict.IO.putStrLn $ "Hello, " <> Strict.intercalate ", " (osStringToStrictText <$> targets) <> "!" +run Goodbye = Strict.IO.putStrLn "Goodbye." opts :: ParserInfo Sample opts = info (sample <**> helper) idm diff --git a/tests/Examples/Formatting.hs b/tests/Examples/Formatting.hs index e064713a..0861d382 100644 --- a/tests/Examples/Formatting.hs +++ b/tests/Examples/Formatting.hs @@ -1,13 +1,19 @@ +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Examples.Formatting where import Data.Monoid import Options.Applicative import Prelude +import System.OsString (osstr) +import qualified "os-string" System.OsString as OsString + opts :: Parser Int opts = option auto $ mconcat - [ long "test" - , short 't' + [ long [osstr|test|] + , short (OsString.unsafeFromChar 't') , value 0 , metavar "FOO_BAR_BAZ_LONG_METAVARIABLE" , help "This is an options with a very very long description. Hopefully, this will be nicely formatted by the help text generator." ] diff --git a/tests/Examples/Hello.hs b/tests/Examples/Hello.hs index fcbb350e..c799f742 100644 --- a/tests/Examples/Hello.hs +++ b/tests/Examples/Hello.hs @@ -1,28 +1,36 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Examples.Hello where import Options.Applicative import Data.Semigroup ((<>)) import Control.Monad (replicateM_) +import System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString +import Options.Applicative.Help (osStringToStrictText) +import qualified Data.Text.IO as Strict.IO + data Sample = Sample - { hello :: String + { hello :: OsString , quiet :: Bool , repeat :: Int } deriving Show sample :: Parser Sample sample = Sample - <$> strOption - ( long "hello" + <$> osStrOption + ( long [osstr|hello|] <> metavar "TARGET" <> help "Target for the greeting" ) <*> switch - ( long "quiet" - <> short 'q' + ( long [osstr|quiet|] + <> short (OsString.unsafeFromChar 'q') <> help "Whether to be quiet" ) <*> option auto - ( long "repeat" + ( long [osstr|repeat|] <> help "Repeats for greeting" <> showDefault <> value 1 @@ -38,5 +46,5 @@ opts = info (sample <**> helper) <> header "hello - a test for optparse-applicative" ) greet :: Sample -> IO () -greet (Sample h False n) = replicateM_ n . putStrLn $ "Hello, " ++ h +greet (Sample h False n) = replicateM_ n . Strict.IO.putStrLn $ "Hello, " <> (osStringToStrictText h) greet _ = return () diff --git a/tests/Examples/LongSub.hs b/tests/Examples/LongSub.hs index 22ced292..e5350512 100644 --- a/tests/Examples/LongSub.hs +++ b/tests/Examples/LongSub.hs @@ -1,31 +1,30 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE OverloadedStrings #-} module Examples.LongSub where import Data.Monoid import Options.Applicative -#if __GLASGOW_HASKELL__ <= 702 -(<>) :: Monoid a => a -> a -> a -(<>) = mappend -#endif +import System.OsString (OsString, osstr) data Sample - = Hello [String] + = Hello [OsString] | Goodbye deriving (Eq, Show) hello :: Parser Sample hello = Hello - <$> many (argument str (metavar "TARGET...")) - <* switch (long "first-flag") - <* switch (long "second-flag") - <* switch (long "third-flag") - <* switch (long "fourth-flag") + <$> many (argument osStr (metavar "TARGET...")) + <* switch (long [osstr|first-flag|]) + <* switch (long [osstr|second-flag|]) + <* switch (long [osstr|third-flag|]) + <* switch (long [osstr|fourth-flag|]) sample :: Parser Sample sample = hsubparser - ( command "hello-very-long-sub" + ( command [osstr|hello-very-long-sub|] (info hello (progDesc "Print greeting")) ) diff --git a/tests/Examples/ParserGroup/AllGrouped.hs b/tests/Examples/ParserGroup/AllGrouped.hs index a3f718d1..a1dcb6dc 100644 --- a/tests/Examples/ParserGroup/AllGrouped.hs +++ b/tests/Examples/ParserGroup/AllGrouped.hs @@ -1,10 +1,14 @@ {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE OverloadedStrings #-} module Examples.ParserGroup.AllGrouped (opts, main) where import Data.Semigroup ((<>)) import Options.Applicative +import System.OsString (OsString, osstr) + -- Tests the help page when every option belongs to some group i.e. there are -- no top-level options. Notice we put the helper (<**> helper) __inside__ -- one of the groups, so that it is not a top-level option. @@ -14,7 +18,7 @@ import Options.Applicative -- and should not be rendered with the Options. data LogGroup = LogGroup - { logPath :: Maybe String, + { logPath :: Maybe OsString, logVerbosity :: Maybe Int } deriving (Show) @@ -28,7 +32,7 @@ data SystemGroup = SystemGroup data Sample = Sample { logGroup :: LogGroup, systemGroup :: SystemGroup, - cmd :: String + cmd :: OsString } deriving (Show) @@ -41,11 +45,11 @@ sample = where parseLogGroup = - parserOptionGroup "Logging" $ + parserOptionGroup [osstr|Logging|] $ LogGroup <$> optional - ( strOption - ( long "file-log-path" + ( osStrOption + ( long [osstr|file-log-path|] <> metavar "PATH" <> help "Log file path" ) @@ -53,7 +57,7 @@ sample = <*> optional ( option auto - ( long "file-log-verbosity" + ( long [osstr|file-log-verbosity|] <> metavar "INT" <> help "File log verbosity" ) @@ -61,20 +65,20 @@ sample = <**> helper parseSystemGroup = - parserOptionGroup "System Options" $ + parserOptionGroup [osstr|System Options|] $ SystemGroup <$> switch - ( long "poll" + ( long [osstr|poll|] <> help "Whether to poll" ) <*> option auto - ( long "timeout" + ( long [osstr|timeout|] <> metavar "INT" <> help "Whether to time out" ) - parseCmd = argument str (metavar "Command") + parseCmd = argument osStr (metavar "Command") opts :: ParserInfo Sample opts = diff --git a/tests/Examples/ParserGroup/Basic.hs b/tests/Examples/ParserGroup/Basic.hs index f68e78d8..32a2e756 100644 --- a/tests/Examples/ParserGroup/Basic.hs +++ b/tests/Examples/ParserGroup/Basic.hs @@ -1,12 +1,18 @@ {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Examples.ParserGroup.Basic (opts, main) where import Data.Semigroup ((<>)) import Options.Applicative +import System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString + data LogGroup = LogGroup - { logPath :: Maybe String, + { logPath :: Maybe OsString, logVerbosity :: Maybe Int } deriving (Show) @@ -18,12 +24,12 @@ data SystemGroup = SystemGroup deriving (Show) data Sample = Sample - { hello :: String, + { hello :: OsString, logGroup :: LogGroup, quiet :: Bool, systemGroup :: SystemGroup, verbosity :: Int, - cmd :: String + cmd :: OsString } deriving (Show) @@ -39,18 +45,18 @@ sample = where parseHello = - strOption - ( long "hello" + osStrOption + ( long [osstr|hello|] <> metavar "TARGET" <> help "Target for the greeting" ) parseLogGroup = - parserOptionGroup "Logging" $ + parserOptionGroup [osstr|Logging|] $ LogGroup <$> optional - ( strOption - ( long "file-log-path" + ( osStrOption + ( long [osstr|file-log-path|] <> metavar "PATH" <> help "Log file path" ) @@ -58,7 +64,7 @@ sample = <*> optional ( option auto - ( long "file-log-verbosity" + ( long [osstr|file-log-verbosity|] <> metavar "INT" <> help "File log verbosity" ) @@ -66,21 +72,21 @@ sample = parseQuiet = switch - ( long "quiet" - <> short 'q' + ( long [osstr|quiet|] + <> short (OsString.unsafeFromChar 'q') <> help "Whether to be quiet" ) parseSystemGroup = - parserOptionGroup "System Options" $ + parserOptionGroup [osstr|System Options|] $ SystemGroup <$> switch - ( long "poll" + ( long [osstr|poll|] <> help "Whether to poll" ) <*> ( option auto - ( long "timeout" + ( long [osstr|timeout|] <> metavar "INT" <> help "Whether to time out" ) @@ -89,12 +95,12 @@ sample = parseVerbosity = option auto - ( long "verbosity" - <> short 'v' + ( long [osstr|verbosity|] + <> short (OsString.unsafeFromChar 'v') <> help "Console verbosity" ) - parseCmd = argument str (metavar "Command") + parseCmd = argument osStr (metavar "Command") opts :: ParserInfo Sample opts = diff --git a/tests/Examples/ParserGroup/CommandGroups.hs b/tests/Examples/ParserGroup/CommandGroups.hs index 9f0679f9..f3d954ae 100644 --- a/tests/Examples/ParserGroup/CommandGroups.hs +++ b/tests/Examples/ParserGroup/CommandGroups.hs @@ -1,13 +1,18 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} module Examples.ParserGroup.CommandGroups (opts, main) where import Data.Semigroup ((<>)) import Options.Applicative +import System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString + data LogGroup = LogGroup - { logPath :: Maybe String, + { logPath :: Maybe OsString, logVerbosity :: Maybe Int } deriving (Show) @@ -26,7 +31,7 @@ data Command deriving (Show) data Sample = Sample - { hello :: String, + { hello :: OsString, logGroup :: LogGroup, quiet :: Bool, systemGroup :: SystemGroup, @@ -47,18 +52,18 @@ sample = where parseHello = - strOption - ( long "hello" + osStrOption + ( long [osstr|hello|] <> metavar "TARGET" <> help "Target for the greeting" ) parseLogGroup = - parserOptionGroup "Logging" $ + parserOptionGroup [osstr|Logging|] $ LogGroup <$> optional - ( strOption - ( long "file-log-path" + ( osStrOption + ( long [osstr|file-log-path|] <> metavar "PATH" <> help "Log file path" ) @@ -66,7 +71,7 @@ sample = <*> optional ( option auto - ( long "file-log-verbosity" + ( long [osstr|file-log-verbosity|] <> metavar "INT" <> help "File log verbosity" ) @@ -74,21 +79,21 @@ sample = parseQuiet = switch - ( long "quiet" - <> short 'q' + ( long [osstr|quiet|] + <> short (OsString.unsafeFromChar 'q') <> help "Whether to be quiet" ) parseSystemGroup = - parserOptionGroup "System Options" $ + parserOptionGroup [osstr|System Options|] $ SystemGroup <$> switch - ( long "poll" + ( long [osstr|poll|] <> help "Whether to poll" ) <*> ( option auto - ( long "timeout" + ( long [osstr|timeout|] <> metavar "INT" <> help "Whether to time out" ) @@ -97,26 +102,26 @@ sample = parseVerbosity = option auto - ( long "verbosity" - <> short 'v' + ( long [osstr|verbosity|] + <> short (OsString.unsafeFromChar 'v') <> help "Console verbosity" ) parseCommand = hsubparser - ( command "list 2" (info (pure List) $ progDesc "Lists elements") + ( command [osstr|list 2|] (info (pure List) $ progDesc "Lists elements") ) <|> hsubparser - ( command "list" (info (pure List) $ progDesc "Lists elements") - <> command "print" (info (pure Print) $ progDesc "Prints table") - <> commandGroup "Info commands" + ( command [osstr|list|] (info (pure List) $ progDesc "Lists elements") + <> command [osstr|print|] (info (pure Print) $ progDesc "Prints table") + <> commandGroup [osstr|Info commands|] ) <|> hsubparser - ( command "delete" (info (pure Delete) $ progDesc "Deletes elements") + ( command [osstr|delete|] (info (pure Delete) $ progDesc "Deletes elements") ) <|> hsubparser - ( command "query" (info (pure Query) $ progDesc "Runs a query") - <> commandGroup "Query commands" + ( command [osstr|query|] (info (pure Query) $ progDesc "Runs a query") + <> commandGroup [osstr|Query commands|] ) opts :: ParserInfo Sample diff --git a/tests/Examples/ParserGroup/DuplicateCommandGroups.hs b/tests/Examples/ParserGroup/DuplicateCommandGroups.hs index 56f4c1d0..bb4329b2 100644 --- a/tests/Examples/ParserGroup/DuplicateCommandGroups.hs +++ b/tests/Examples/ParserGroup/DuplicateCommandGroups.hs @@ -1,11 +1,16 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} module Examples.ParserGroup.DuplicateCommandGroups (opts, main) where import Data.Semigroup ((<>)) import Options.Applicative +import System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString + -- This test demonstrates that duplicate + consecutive groups are merged, -- while duplicate + non-consecutive groups are not merged. @@ -18,7 +23,7 @@ data Command deriving (Show) data Sample = Sample - { hello :: String, + { hello :: OsString, quiet :: Bool, verbosity :: Int, cmd :: Command @@ -35,46 +40,46 @@ sample = where parseHello = - strOption - ( long "hello" + osStrOption + ( long [osstr|hello|] <> metavar "TARGET" <> help "Target for the greeting" ) parseQuiet = switch - ( long "quiet" - <> short 'q' + ( long [osstr|quiet|] + <> short (OsString.unsafeFromChar 'q') <> help "Whether to be quiet" ) parseVerbosity = option auto - ( long "verbosity" - <> short 'v' + ( long [osstr|verbosity|] + <> short (OsString.unsafeFromChar 'v') <> help "Console verbosity" ) parseCommand = hsubparser - ( command "list" (info (pure List) $ progDesc "Lists elements") - <> commandGroup "Info commands" + ( command [osstr|list|] (info (pure List) $ progDesc "Lists elements") + <> commandGroup [osstr|Info commands|] ) <|> hsubparser - ( command "delete" (info (pure Delete) $ progDesc "Deletes elements") - <> commandGroup "Update commands" + ( command [osstr|delete|] (info (pure Delete) $ progDesc "Deletes elements") + <> commandGroup [osstr|Update commands|] ) <|> hsubparser - ( command "insert" (info (pure Insert) $ progDesc "Inserts elements") - <> commandGroup "Update commands" + ( command [osstr|insert|] (info (pure Insert) $ progDesc "Inserts elements") + <> commandGroup [osstr|Update commands|] ) <|> hsubparser - ( command "query" (info (pure Query) $ progDesc "Runs a query") + ( command [osstr|query|] (info (pure Query) $ progDesc "Runs a query") ) <|> hsubparser - ( command "print" (info (pure Print) $ progDesc "Prints table") - <> commandGroup "Info commands" + ( command [osstr|print|] (info (pure Print) $ progDesc "Prints table") + <> commandGroup [osstr|Info commands|] ) opts :: ParserInfo Sample diff --git a/tests/Examples/ParserGroup/Duplicates.hs b/tests/Examples/ParserGroup/Duplicates.hs index f1b34126..e954e7d7 100644 --- a/tests/Examples/ParserGroup/Duplicates.hs +++ b/tests/Examples/ParserGroup/Duplicates.hs @@ -1,10 +1,16 @@ {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Examples.ParserGroup.Duplicates (opts, main) where import Data.Semigroup ((<>)) import Options.Applicative +import System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString + -- NOTE: This is the same structure as ParserGroup.Basic __except__ -- we have two (non-consecutive) "Logging" groups and two (consecutive) -- System groups. This test demonstrates two things: @@ -16,13 +22,13 @@ import Options.Applicative -- This is like command groups. data LogGroup1 = LogGroup1 - { logPath :: Maybe String, + { logPath :: Maybe OsString, logVerbosity :: Maybe Int } deriving (Show) data LogGroup2 = LogGroup2 - { logNamespace :: String + { logNamespace :: OsString } deriving (Show) @@ -38,14 +44,14 @@ newtype SystemGroup2 = SystemGroup2 deriving (Show) data Sample = Sample - { hello :: String, + { hello :: OsString, logGroup1 :: LogGroup1, quiet :: Bool, systemGroup1 :: SystemGroup1, systemGroup2 :: SystemGroup2, logGroup2 :: LogGroup2, verbosity :: Int, - cmd :: String + cmd :: OsString } deriving (Show) @@ -63,18 +69,18 @@ sample = where parseHello = - strOption - ( long "hello" + osStrOption + ( long [osstr|hello|] <> metavar "TARGET" <> help "Target for the greeting" ) parseLogGroup1 = - parserOptionGroup "Logging" $ + parserOptionGroup [osstr|Logging|] $ LogGroup1 <$> optional - ( strOption - ( long "file-log-path" + ( osStrOption + ( long [osstr|file-log-path|] <> metavar "PATH" <> help "Log file path" ) @@ -82,7 +88,7 @@ sample = <*> optional ( option auto - ( long "file-log-verbosity" + ( long [osstr|file-log-verbosity|] <> metavar "INT" <> help "File log verbosity" ) @@ -90,39 +96,39 @@ sample = parseQuiet = switch - ( long "quiet" - <> short 'q' + ( long [osstr|quiet|] + <> short (OsString.unsafeFromChar 'q') <> help "Whether to be quiet" ) parseSystemGroup1 = - parserOptionGroup "System" $ + parserOptionGroup [osstr|System|] $ SystemGroup1 <$> switch - ( long "poll" + ( long [osstr|poll|] <> help "Whether to poll" ) <*> option auto - ( long "timeout" + ( long [osstr|timeout|] <> metavar "INT" <> help "Whether to time out" ) parseSystemGroup2 = - parserOptionGroup "System" $ + parserOptionGroup [osstr|System|] $ SystemGroup2 <$> switch - ( long "sysFlag" + ( long [osstr|sysFlag|] <> help "Some flag" ) parseLogGroup2 = - parserOptionGroup "Logging" $ + parserOptionGroup [osstr|Logging|] $ LogGroup2 <$> - strOption - ( long "log-namespace" + osStrOption + ( long [osstr|log-namespace|] <> metavar "STR" <> help "Log namespace" ) @@ -130,12 +136,12 @@ sample = parseVerbosity = option auto - ( long "verbosity" - <> short 'v' + ( long [osstr|verbosity|] + <> short (OsString.unsafeFromChar 'v') <> help "Console verbosity" ) - parseCmd = argument str (metavar "Command") + parseCmd = argument osStr (metavar "Command") opts :: ParserInfo Sample opts = diff --git a/tests/Examples/ParserGroup/Nested.hs b/tests/Examples/ParserGroup/Nested.hs index 05311494..023dd449 100644 --- a/tests/Examples/ParserGroup/Nested.hs +++ b/tests/Examples/ParserGroup/Nested.hs @@ -1,14 +1,20 @@ {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Examples.ParserGroup.Nested (opts, main) where import Data.Semigroup ((<>)) import Options.Applicative +import System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString + -- Nested groups. Demonstrates that group can nest. data LogGroup = LogGroup - { logPath :: Maybe String, + { logPath :: Maybe OsString, systemGroup :: SystemGroup, logVerbosity :: Maybe Int } @@ -22,23 +28,23 @@ data SystemGroup = SystemGroup deriving (Show) data Nested2 = Nested2 - { nested2Str :: String, + { nested2Str :: OsString, nested3 :: Nested3 } deriving (Show) newtype Nested3 = Nested3 - { nested3Str :: String + { nested3Str :: OsString } deriving (Show) data Sample = Sample - { hello :: String, + { hello :: OsString, logGroup :: LogGroup, quiet :: Bool, verbosity :: Int, group2 :: (Int, Int), - cmd :: String + cmd :: OsString } deriving (Show) @@ -54,16 +60,16 @@ sample = where parseHello = - strOption - ( long "hello" + osStrOption + ( long [osstr|hello|] <> metavar "TARGET" <> help "Target for the greeting" ) parseLogGroup = - parserOptionGroup "First group" $ - parserOptionGroup "Second group" $ - parserOptionGroup "Logging" $ + parserOptionGroup [osstr|First group|] $ + parserOptionGroup [osstr|Second group|] $ + parserOptionGroup [osstr|Logging|] $ LogGroup <$> parseLogPath <*> parseSystemGroup @@ -72,8 +78,8 @@ sample = where parseLogPath = optional - ( strOption - ( long "file-log-path" + ( osStrOption + ( long [osstr|file-log-path|] <> metavar "PATH" <> help "Log file path" ) @@ -82,7 +88,7 @@ sample = optional ( option auto - ( long "file-log-verbosity" + ( long [osstr|file-log-verbosity|] <> metavar "INT" <> help "File log verbosity" ) @@ -90,39 +96,39 @@ sample = parseQuiet = switch - ( long "quiet" - <> short 'q' + ( long [osstr|quiet|] + <> short (OsString.unsafeFromChar 'q') <> help "Whether to be quiet" ) parseSystemGroup = - parserOptionGroup "System Options" $ + parserOptionGroup [osstr|System Options|] $ SystemGroup - <$> switch (long "poll" <> help "Whether to poll") + <$> switch (long [osstr|poll|] <> help "Whether to poll") <*> parseNested2 - <*> option auto (long "timeout" <> metavar "INT" <> help "Whether to time out") + <*> option auto (long [osstr|timeout|] <> metavar "INT" <> help "Whether to time out") parseNested2 = - parserOptionGroup "Nested2" $ + parserOptionGroup [osstr|Nested2|] $ Nested2 - <$> option auto (long "double-nested" <> metavar "STR" <> help "Some nested option") + <$> option osStr (long [osstr|double-nested|] <> metavar "STR" <> help "Some nested option") <*> parseNested3 parseNested3 = - parserOptionGroup "Nested3" $ - Nested3 <$> option auto (long "triple-nested" <> metavar "STR" <> help "Another option") + parserOptionGroup [osstr|Nested3|] $ + Nested3 <$> option osStr (long [osstr|triple-nested|] <> metavar "STR" <> help "Another option") parseGroup2 :: Parser (Int, Int) - parseGroup2 = parserOptionGroup "Group 2" $ + parseGroup2 = parserOptionGroup [osstr|Group 2|] $ (,) - <$> parserOptionGroup "G 2.1" (option auto (long "one" <> help "Option 1")) - <*> parserOptionGroup "G 2.2" (option auto (long "two" <> help "Option 2")) + <$> parserOptionGroup [osstr|G 2.1|] (option auto (long [osstr|one|] <> help "Option 1")) + <*> parserOptionGroup [osstr|G 2.2|] (option auto (long [osstr|two|] <> help "Option 2")) parseVerbosity = - option auto (long "verbosity" <> short 'v' <> help "Console verbosity") + option auto (long [osstr|verbosity|] <> short (OsString.unsafeFromChar 'v') <> help "Console verbosity") parseCmd = - argument str (metavar "Command") + argument osStr (metavar "Command") opts :: ParserInfo Sample opts = diff --git a/tests/test.hs b/tests/test.hs index 5b01d335..0f2f8ad6 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -1,7 +1,9 @@ {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE OverloadedStrings #-} module Main where import qualified Examples.Hello as Hello @@ -17,13 +19,15 @@ import qualified Examples.ParserGroup.DuplicateCommandGroups as ParserGroup.Dupl import qualified Examples.ParserGroup.Duplicates as ParserGroup.Duplicates import qualified Examples.ParserGroup.Nested as ParserGroup.Nested +import System.OsString (OsString, osstr) +import qualified "os-string" System.OsString as OsString + import Control.Applicative import Control.Monad -import Data.Function (on) -import Data.List hiding (group) +import Data.List hiding (lines, group) import Data.List.NonEmpty (NonEmpty ((:|))) import Data.Semigroup hiding (option) -import Data.String +import Data.String hiding (lines) import System.Exit import Test.QuickCheck hiding (Success, Failure) @@ -40,9 +44,15 @@ import qualified Options.Applicative.Help.Pretty as Doc import Options.Applicative.Help.Chunk import Options.Applicative.Help.Levenshtein -import Prelude +import Prelude hiding (lines) +import qualified Data.Text.Lazy as Lazy +import qualified System.File.OsPath as OsPath +import qualified Data.Text.Lazy.Encoding as Lazy.Encoding +import qualified Data.Text as Strict +import Data.Coerce (coerce) +import Options.Applicative.Help.Pretty (osStringToStrictText) -run :: ParserInfo a -> [String] -> ParserResult a +run :: ParserInfo a -> [OsString] -> ParserResult a run = execParserPure defaultPrefs assertError :: Show a => ParserResult a @@ -56,65 +66,65 @@ assertResult :: ParserResult a -> (a -> Property) -> Property assertResult x f = case x of Success r -> f r Failure e -> do - let (msg, _) = renderFailure e "test" - counterexample ("unexpected parse error\n" ++ msg) failed + let (msg, _) = renderFailure e [osstr|test|] + counterexample ("unexpected parse error\n" ++ Lazy.unpack msg) failed CompletionInvoked _ -> counterexample "expected result, got completion" failed -assertHasLine :: String -> String -> Property -assertHasLine l s = counterexample ("expected line:\n\t" ++ l ++ "\nnot found") - $ l `elem` lines s +assertHasLine :: Lazy.Text -> Lazy.Text -> Property +assertHasLine l s = counterexample ("expected line:\n\t" ++ Lazy.unpack l ++ "\nnot found") + $ l `elem` Lazy.lines s -checkHelpTextWith :: Show a => ExitCode -> ParserPrefs -> String - -> ParserInfo a -> [String] -> Property +checkHelpTextWith :: Show a => ExitCode -> ParserPrefs -> OsString + -> ParserInfo a -> [OsString] -> Property checkHelpTextWith ecode pprefs name p args = ioProperty $ do let result = execParserPure pprefs p args - expected <- readFile $ "tests/" ++ name ++ ".err.txt" + expected <- OsPath.readFile $ mconcat [[osstr|tests/|] <> name <> [osstr|.err.txt|]] return $ assertError result $ \failure -> let (msg, code) = renderFailure failure name - in (expected === msg ++ "\n") .&&. (ecode === code) + in (expected === Lazy.Encoding.encodeUtf8 (msg <> "\n")) .&&. (ecode === code) -checkHelpText :: Show a => String -> ParserInfo a -> [String] -> Property +checkHelpText :: Show a => OsString -> ParserInfo a -> [OsString] -> Property checkHelpText = checkHelpTextWith ExitSuccess defaultPrefs prop_hello :: Property prop_hello = once $ - checkHelpText "hello" Hello.opts ["--help"] + checkHelpText [osstr|hello|] Hello.opts [[osstr|--help|]] prop_modes :: Property prop_modes = once $ - checkHelpText "commands" Commands.opts ["--help"] + checkHelpText [osstr|commands|] Commands.opts [[osstr|--help|]] prop_cmd_header :: Property prop_cmd_header = once $ let i = info (helper <*> Commands.sample) (header "foo") r1 = checkHelpTextWith (ExitFailure 1) defaultPrefs - "commands_header" i ["-zello"] + [osstr|commands_header|] i [[osstr|-zello|]] r2 = checkHelpTextWith (ExitFailure 1) (prefs showHelpOnError) - "commands_header_full" i ["-zello"] + [osstr|commands_header_full|] i [[osstr|-zello|]] in (r1 .&&. r2) prop_cabal_conf :: Property prop_cabal_conf = once $ - checkHelpTextWith ExitSuccess (prefs helpShowGlobals) "cabal" Cabal.pinfo ["configure", "--help"] + checkHelpTextWith ExitSuccess (prefs helpShowGlobals) [osstr|cabal|] Cabal.pinfo [[osstr|configure|], [osstr|--help|]] prop_args :: Property prop_args = once $ - let result = run Commands.opts ["hello", "foo", "bar"] - in assertResult result ((===) (Commands.Hello ["foo", "bar"])) + let result = run Commands.opts [[osstr|hello|], [osstr|foo|], [osstr|bar|]] + in assertResult result (Commands.Hello [[osstr|foo|], [osstr|bar|]] ===) prop_args_opts :: Property prop_args_opts = once $ - let result = run Commands.opts ["hello", "foo", "--bar"] + let result = run Commands.opts [[osstr|hello|], [osstr|foo|], [osstr|--bar|]] in assertError result (\_ -> property succeeded) prop_args_ddash :: Property prop_args_ddash = once $ - let result = run Commands.opts ["hello", "foo", "--", "--bar", "--", "baz"] - in assertResult result ((===) (Commands.Hello ["foo", "--bar", "--", "baz"])) + let result = run Commands.opts [[osstr|hello|], [osstr|foo|], [osstr|--|], [osstr|--bar|], [osstr|--|], [osstr|baz|]] + in assertResult result ((===) (Commands.Hello [[osstr|foo|], [osstr|--bar|], [osstr|--|], [osstr|baz|]])) prop_alts :: Property prop_alts = once $ - let result = run Alternatives.opts ["-b", "-a", "-b", "-a", "-a", "-b"] + let result = run Alternatives.opts [[osstr|-b|], [osstr|-a|], [osstr|-b|], [osstr|-a|], [osstr|-a|], [osstr|-b|]] in assertResult result $ \xs -> let a = Alternatives.A b = Alternatives.B @@ -123,14 +133,14 @@ prop_alts = once $ prop_show_default :: Property prop_show_default = once $ let p = option auto - ( short 'n' + ( short (OsString.unsafeFromChar 'n') <> help "set count" <> value (0 :: Int) <> showDefault ) i = info (p <**> helper) idm - result = run i ["--help"] + result = run i [[osstr|--help|]] in assertError result $ \failure -> - let (msg, _) = renderFailure failure "test" + let (msg, _) = renderFailure failure [osstr|test|] in assertHasLine " -n ARG set count (default: 0)" msg @@ -139,245 +149,245 @@ prop_alt_cont :: Property prop_alt_cont = once $ let p = Alternatives.a <|> Alternatives.b i = info p idm - result = run i ["-a", "-b"] + result = run i [[osstr|-a|], [osstr|-b|]] in assertError result (\_ -> property succeeded) prop_alt_help :: Property prop_alt_help = once $ - let p :: Parser (Maybe (Either String String)) + let p :: Parser (Maybe (Either OsString OsString)) p = p1 <|> p2 <|> p3 p1 = (Just . Left) - <$> strOption ( long "virtual-machine" + <$> osStrOption ( long [osstr|virtual-machine|] <> metavar "VM" <> help "Virtual machine name" ) p2 = (Just . Right) - <$> strOption ( long "cloud-service" + <$> osStrOption ( long [osstr|cloud-service|] <> metavar "CS" <> help "Cloud service name" ) - p3 = flag' Nothing ( long "dry-run" ) + p3 = flag' Nothing ( long [osstr|dry-run|] ) i = info (p <**> helper) idm - in checkHelpText "alt" i ["--help"] + in checkHelpText [osstr|alt|] i [[osstr|--help|]] prop_optional_help :: Property prop_optional_help = once $ - let p :: Parser (Maybe (String, String)) + let p :: Parser (Maybe (OsString, OsString)) p = optional ((,) - <$> strOption ( long "a" + <$> osStrOption ( long [osstr|a|] <> metavar "A" <> help "value a" ) - <*> strOption ( long "b" + <*> osStrOption ( long [osstr|b|] <> metavar "B" <> help "value b" ) ) i = info (p <**> helper) idm - in checkHelpText "optional" i ["--help"] + in checkHelpText [osstr|optional|] i [[osstr|--help|]] prop_optional_requiring_parens :: Property prop_optional_requiring_parens = once $ let p = optional $ (,) - <$> flag' () ( short 'a' <> long "a" ) - <*> flag' () ( short 'b' <> long "b" ) + <$> flag' () ( short (OsString.unsafeFromChar 'a') <> long [osstr|a|] ) + <*> flag' () ( short (OsString.unsafeFromChar 'b') <> long [osstr|b|] ) i = info (p <**> helper) briefDesc - result = run i ["--help"] + result = run i [[osstr|--help|]] in assertError result $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "Usage: test [(-a|--a) (-b|--b)]" === text prop_optional_alt_requiring_parens :: Property prop_optional_alt_requiring_parens = once $ let p = optional $ - flag' () ( short 'a' <> long "a" ) - <|> flag' () ( short 'b' <> long "b" ) + flag' () ( short (OsString.unsafeFromChar 'a') <> long [osstr|a|] ) + <|> flag' () ( short (OsString.unsafeFromChar 'b') <> long [osstr|b|] ) i = info (p <**> helper) briefDesc - result = run i ["--help"] + result = run i [[osstr|--help|]] in assertError result $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "Usage: test [(-a|--a) | (-b|--b)]" === text prop_nested_optional_help :: Property prop_nested_optional_help = once $ - let p :: Parser (String, Maybe (String, Maybe String)) + let p :: Parser (OsString, Maybe (OsString, Maybe OsString)) p = (,) <$> - (strOption ( short 'a' - <> long "a" + (osStrOption ( short (OsString.unsafeFromChar 'a') + <> long [osstr|a|] <> metavar "A" <> help "value a" ) ) <*> (optional ((,) <$> - (strOption ( long "b0" + (osStrOption ( long [osstr|b0|] <> metavar "B0" <> help "value b0" ) ) <*> - (optional (strOption ( long "b1" + (optional (osStrOption ( long [osstr|b1|] <> metavar "B1" <> help "value b1" ))))) i = info (p <**> helper) idm - in checkHelpText "nested_optional" i ["--help"] + in checkHelpText [osstr|nested_optional|] i [[osstr|--help|]] prop_long_equals :: Property prop_long_equals = once $ - let p :: Parser String - p = option auto ( long "intval" - <> short 'j' - <> long "intval2" - <> short 'i' + let p :: Parser OsString + p = option osStr ( long [osstr|intval|] + <> short (OsString.unsafeFromChar 'j') + <> long [osstr|intval2|] + <> short (OsString.unsafeFromChar 'i') <> help "integer value") i = info (p <**> helper) fullDesc - in checkHelpTextWith ExitSuccess (prefs helpLongEquals) "long_equals" i ["--help"] + in checkHelpTextWith ExitSuccess (prefs helpLongEquals) [osstr|long_equals|] i [[osstr|--help|]] prop_long_equals_doesnt_do_shorts :: Property prop_long_equals_doesnt_do_shorts = once $ - let p :: Parser String - p = option auto ( short 'i' + let p :: Parser OsString + p = option osStr ( short (OsString.unsafeFromChar 'i') <> help "integer value") i = info (p <**> helper) fullDesc - result = execParserPure (prefs helpLongEquals) i ["--help"] + result = execParserPure (prefs helpLongEquals) i [[osstr|--help|]] in assertError result $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "Usage: test -i ARG" === text prop_nested_fun :: Property prop_nested_fun = once $ - let p :: Parser (String, Maybe (String, Maybe String)) + let p :: Parser (OsString, Maybe (OsString, Maybe OsString)) p = (,) <$> - (strOption (short 'a' <> long "a" <> metavar "A")) <*> + (osStrOption (short (OsString.unsafeFromChar 'a') <> long [osstr|a|] <> metavar "A")) <*> (optional ((,) <$> - (strOption (short 'b' <> long "b" <> metavar "B")) <*> - (optional (strOption (short 'c' <> long "c" <> metavar "C"))))) + (osStrOption (short (OsString.unsafeFromChar 'b') <> long [osstr|b|] <> metavar "B")) <*> + (optional (osStrOption (short (OsString.unsafeFromChar 'c') <> long [osstr|c|] <> metavar "C"))))) i = info (p <**> helper) briefDesc - result = run i ["--help"] + result = run i [[osstr|--help|]] in assertError result $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "Usage: test (-a|--a A) [(-b|--b B) [-c|--c C]]" === text prop_nested_commands :: Property prop_nested_commands = once $ - let p3 :: Parser String - p3 = strOption (short 'a' <> metavar "A") - p2 = subparser (command "b" (info p3 idm)) - p1 = subparser (command "c" (info p2 idm)) + let p3 :: Parser OsString + p3 = osStrOption (short (OsString.unsafeFromChar 'a') <> metavar "A") + p2 = subparser (command [osstr|b|] (info p3 idm)) + p1 = subparser (command [osstr|c|] (info p2 idm)) i = info (p1 <**> helper) idm - in checkHelpTextWith (ExitFailure 1) defaultPrefs "nested" i ["c", "b"] + in checkHelpTextWith (ExitFailure 1) defaultPrefs [osstr|nested|] i [[osstr|c|], [osstr|b|]] prop_drops_back_contexts :: Property prop_drops_back_contexts = once $ - let p3 :: Parser String - p3 = strOption (short 'a' <> metavar "A") - p2 = subparser (command "b" (info p3 idm) <> metavar "B") - p1 = subparser (command "c" (info p3 idm) <> metavar "C") + let p3 :: Parser OsString + p3 = osStrOption (short (OsString.unsafeFromChar 'a') <> metavar "A") + p2 = subparser (command [osstr|b|] (info p3 idm) <> metavar "B") + p1 = subparser (command [osstr|c|] (info p3 idm) <> metavar "C") p0 = (,) <$> p2 <*> p1 i = info (p0 <**> helper) idm - in checkHelpTextWith (ExitFailure 1) defaultPrefs "dropback" i ["b", "-aA"] + in checkHelpTextWith (ExitFailure 1) defaultPrefs [osstr|dropback|] i [[osstr|b|], [osstr|-aA|]] prop_context_carry :: Property prop_context_carry = once $ - let p3 :: Parser String - p3 = strOption (short 'a' <> metavar "A") - p2 = subparser (command "b" (info p3 idm) <> metavar "B") - p1 = subparser (command "c" (info p3 idm) <> metavar "C") + let p3 :: Parser OsString + p3 = osStrOption (short (OsString.unsafeFromChar 'a') <> metavar "A") + p2 = subparser (command [osstr|b|] (info p3 idm) <> metavar "B") + p1 = subparser (command [osstr|c|] (info p3 idm) <> metavar "C") p0 = (,) <$> p2 <*> p1 i = info (p0 <**> helper) idm - in checkHelpTextWith (ExitFailure 1) defaultPrefs "carry" i ["b", "-aA", "c"] + in checkHelpTextWith (ExitFailure 1) defaultPrefs [osstr|carry|] i [[osstr|b|], [osstr|-aA|], [osstr|c|]] prop_help_on_empty :: Property prop_help_on_empty = once $ - let p3 :: Parser String - p3 = strOption (short 'a' <> metavar "A") - p2 = subparser (command "b" (info p3 idm) <> metavar "B") - p1 = subparser (command "c" (info p3 idm) <> metavar "C") + let p3 :: Parser OsString + p3 = osStrOption (short (OsString.unsafeFromChar 'a') <> metavar "A") + p2 = subparser (command [osstr|b|] (info p3 idm) <> metavar "B") + p1 = subparser (command [osstr|c|] (info p3 idm) <> metavar "C") p0 = (,) <$> p2 <*> p1 i = info (p0 <**> helper) idm - in checkHelpTextWith (ExitFailure 1) (prefs showHelpOnEmpty) "helponempty" i [] + in checkHelpTextWith (ExitFailure 1) (prefs showHelpOnEmpty) [osstr|helponempty|] i [] prop_help_on_empty_sub :: Property prop_help_on_empty_sub = once $ - let p3 :: Parser String - p3 = strOption (short 'a' <> metavar "A" <> help "both commands require this") - p2 = subparser (command "b" (info p3 idm) <> metavar "B") - p1 = subparser (command "c" (info p3 idm) <> metavar "C") + let p3 :: Parser OsString + p3 = osStrOption (short (OsString.unsafeFromChar 'a') <> metavar "A" <> help "both commands require this") + p2 = subparser (command [osstr|b|] (info p3 idm) <> metavar "B") + p1 = subparser (command [osstr|c|] (info p3 idm) <> metavar "C") p0 = (,) <$> p2 <*> p1 i = info (p0 <**> helper) idm - in checkHelpTextWith (ExitFailure 1) (prefs showHelpOnEmpty) "helponemptysub" i ["b", "-aA", "c"] + in checkHelpTextWith (ExitFailure 1) (prefs showHelpOnEmpty) [osstr|helponemptysub|] i [[osstr|b|], [osstr|-aA|], [osstr|c|]] prop_many_args :: Property prop_many_args = forAll (choose (0,2000)) $ \nargs -> - let p :: Parser [String] - p = many (argument str idm) + let p :: Parser [OsString] + p = many (argument osStr idm) i = info p idm - result = run i (replicate nargs "foo") + result = run i (replicate nargs [osstr|foo|]) in assertResult result (\xs -> nargs === length xs) prop_disambiguate :: Property prop_disambiguate = once $ - let p = flag' (1 :: Int) (long "foo") - <|> flag' 2 (long "bar") - <|> flag' 3 (long "baz") + let p = flag' (1 :: Int) (long [osstr|foo|]) + <|> flag' 2 (long [osstr|bar|]) + <|> flag' 3 (long [osstr|baz|]) i = info p idm - result = execParserPure (prefs disambiguate) i ["--f"] + result = execParserPure (prefs disambiguate) i [[osstr|--f|]] in assertResult result ((===) 1) prop_ambiguous :: Property prop_ambiguous = once $ - let p = flag' (1 :: Int) (long "foo") - <|> flag' 2 (long "bar") - <|> flag' 3 (long "baz") + let p = flag' (1 :: Int) (long [osstr|foo|]) + <|> flag' 2 (long [osstr|bar|]) + <|> flag' 3 (long [osstr|baz|]) i = info p idm - result = execParserPure (prefs disambiguate) i ["--ba"] + result = execParserPure (prefs disambiguate) i [[osstr|--ba|]] in assertError result (\_ -> property succeeded) prop_disambiguate_in_same_subparsers :: Property prop_disambiguate_in_same_subparsers = once $ - let p0 = subparser (command "oranges" (info (pure "oranges") idm) <> command "apples" (info (pure "apples") idm) <> metavar "B") + let p0 = subparser (command [osstr|oranges|] (info (pure [osstr|oranges|]) idm) <> command [osstr|apples|] (info (pure [osstr|apples|]) idm) <> metavar "B") i = info (p0 <**> helper) idm - result = execParserPure (prefs disambiguate) i ["orang"] - in assertResult result ((===) "oranges") + result = execParserPure (prefs disambiguate) i [[osstr|orang|]] + in assertResult result ((===) [osstr|oranges|]) prop_disambiguate_commands_in_separate_subparsers :: Property prop_disambiguate_commands_in_separate_subparsers = once $ - let p2 = subparser (command "oranges" (info (pure "oranges") idm) <> metavar "B") - p1 = subparser (command "apples" (info (pure "apples") idm) <> metavar "C") + let p2 = subparser (command [osstr|oranges|] (info (pure [osstr|oranges|]) idm) <> metavar "B") + p1 = subparser (command [osstr|apples|] (info (pure [osstr|apples|]) idm) <> metavar "C") p0 = p1 <|> p2 i = info (p0 <**> helper) idm - result = execParserPure (prefs disambiguate) i ["orang"] - in assertResult result ((===) "oranges") + result = execParserPure (prefs disambiguate) i [[osstr|orang|]] + in assertResult result ((===) [osstr|oranges|]) prop_fail_ambiguous_commands_in_same_subparser :: Property prop_fail_ambiguous_commands_in_same_subparser = once $ - let p0 = subparser (command "oranges" (info (pure ()) idm) <> command "orangutans" (info (pure ()) idm) <> metavar "B") + let p0 = subparser (command [osstr|oranges|] (info (pure ()) idm) <> command [osstr|orangutans|] (info (pure ()) idm) <> metavar "B") i = info (p0 <**> helper) idm - result = execParserPure (prefs disambiguate) i ["orang"] + result = execParserPure (prefs disambiguate) i [[osstr|orang|]] in assertError result (\_ -> property succeeded) prop_fail_ambiguous_commands_in_separate_subparser :: Property prop_fail_ambiguous_commands_in_separate_subparser = once $ - let p2 = subparser (command "oranges" (info (pure ()) idm) <> metavar "B") - p1 = subparser (command "orangutans" (info (pure ()) idm) <> metavar "C") + let p2 = subparser (command [osstr|oranges|] (info (pure ()) idm) <> metavar "B") + p1 = subparser (command [osstr|orangutans|] (info (pure ()) idm) <> metavar "C") p0 = p1 <|> p2 i = info (p0 <**> helper) idm - result = execParserPure (prefs disambiguate) i ["orang"] + result = execParserPure (prefs disambiguate) i [[osstr|orang|]] in assertError result (\_ -> property succeeded) prop_without_disambiguation_same_named_commands_should_parse_in_order :: Property prop_without_disambiguation_same_named_commands_should_parse_in_order = once $ - let p3 = subparser (command "b" (info (pure ()) idm) <> metavar "B") - p2 = subparser (command "a" (info (pure ()) idm) <> metavar "B") - p1 = subparser (command "a" (info (pure ()) idm) <> metavar "C") + let p3 = subparser (command [osstr|b|] (info (pure ()) idm) <> metavar "B") + p2 = subparser (command [osstr|a|] (info (pure ()) idm) <> metavar "B") + p1 = subparser (command [osstr|a|] (info (pure ()) idm) <> metavar "C") p0 = (,,) <$> p1 <*> p2 <*> p3 i = info (p0 <**> helper) idm - result = execParserPure defaultPrefs i ["b", "a", "a"] + result = execParserPure defaultPrefs i [[osstr|b|], [osstr|a|], [osstr|a|]] in assertResult result ((===) ((), (), ())) prop_completion :: Property prop_completion = once . ioProperty $ let p = (,) - <$> strOption (long "foo" <> value "") - <*> strOption (long "bar" <> value "") + <$> osStrOption (long [osstr|foo|] <> value OsString.empty) + <*> osStrOption (long [osstr|bar|] <> value OsString.empty) i = info p idm - result = run i ["--bash-completion-index", "0"] + result = run i [[osstr|--bash-completion-index|], [osstr|0|]] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- Strict.lines <$> err [osstr|test|] return $ ["--foo", "--bar"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -385,62 +395,62 @@ prop_completion = once . ioProperty $ prop_completion_opt_after_double_dash :: Property prop_completion_opt_after_double_dash = once . ioProperty $ let p = (,) - <$> strOption (long "foo" <> value "") + <$> osStrOption (long [osstr|foo|] <> value OsString.empty) <*> argument readerAsk (completeWith ["bar"]) i = info p idm - result = run i ["--bash-completion-index", "2" - , "--bash-completion-word", "test" - , "--bash-completion-word", "--"] + result = run i [[osstr|--bash-completion-index|], [osstr|2|] + , [osstr|--bash-completion-word|], [osstr|test|] + , [osstr|--bash-completion-word|], [osstr|--|]] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- Strict.lines <$> err [osstr|test|] return $ ["bar"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed prop_completion_only_reachable :: Property prop_completion_only_reachable = once . ioProperty $ - let p :: Parser (String,String) + let p :: Parser (OsString, OsString) p = (,) - <$> strArgument (completeWith ["reachable"]) - <*> strArgument (completeWith ["unreachable"]) + <$> osStrArgument (completeWith ["reachable"]) + <*> osStrArgument (completeWith ["unreachable"]) i = info p idm - result = run i ["--bash-completion-index", "0"] + result = run i [[osstr|--bash-completion-index|], [osstr|0|]] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- Strict.lines <$> err [osstr|test|] return $ ["reachable"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed prop_completion_only_reachable_deep :: Property prop_completion_only_reachable_deep = once . ioProperty $ - let p :: Parser (String,String) + let p :: Parser (OsString, OsString) p = (,) - <$> strArgument (completeWith ["seen"]) - <*> strArgument (completeWith ["now-reachable"]) + <$> osStrArgument (completeWith ["seen"]) + <*> osStrArgument (completeWith ["now-reachable"]) i = info p idm - result = run i [ "--bash-completion-index", "2" - , "--bash-completion-word", "test-prog" - , "--bash-completion-word", "seen" ] + result = run i [ [osstr|--bash-completion-index|], [osstr|2|] + , [osstr|--bash-completion-word|], [osstr|test-prog|] + , [osstr|--bash-completion-word|], [osstr|seen|] ] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- Strict.lines <$> err [osstr|test|] return $ ["now-reachable"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed prop_completion_multi :: Property prop_completion_multi = once . ioProperty $ - let p :: Parser [String] - p = many (strArgument (completeWith ["reachable"])) + let p :: Parser [OsString] + p = many (osStrArgument (completeWith ["reachable"])) i = info p idm - result = run i [ "--bash-completion-index", "3" - , "--bash-completion-word", "test-prog" - , "--bash-completion-word", "nope" ] + result = run i [ [osstr|--bash-completion-index|], [osstr|3|] + , [osstr|--bash-completion-word|], [osstr|test-prog|] + , [osstr|--bash-completion-word|], [osstr|nope|] ] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- Strict.lines <$> err [osstr|test|] return $ ["reachable"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -448,13 +458,13 @@ prop_completion_multi = once . ioProperty $ prop_completion_rich :: Property prop_completion_rich = once . ioProperty $ let p = (,) - <$> option readerAsk (long "foo" <> help "Fo?") - <*> option readerAsk (long "bar" <> help "Ba?") + <$> option readerAsk (long [osstr|foo|] <> help "Fo?") + <*> option readerAsk (long [osstr|bar|] <> help "Ba?") i = info p idm - result = run i ["--bash-completion-enriched", "--bash-completion-index", "0"] + result = run i [[osstr|--bash-completion-enriched|], [osstr|--bash-completion-index|], [osstr|0|]] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- Strict.lines <$> err [osstr|test|] return $ ["--foo\tFo?", "--bar\tBa?"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -462,105 +472,105 @@ prop_completion_rich = once . ioProperty $ prop_completion_rich_lengths :: Property prop_completion_rich_lengths = once . ioProperty $ let p = (,) - <$> option readerAsk (long "foo" <> help "Foo hide this") - <*> option readerAsk (long "bar" <> help "Bar hide this") + <$> option readerAsk (long [osstr|foo|] <> help "Foo hide this") + <*> option readerAsk (long [osstr|bar|] <> help "Bar hide this") i = info p idm - result = run i [ "--bash-completion-enriched" - , "--bash-completion-index=0" - , "--bash-completion-option-desc-length=3" - , "--bash-completion-command-desc-length=30"] + result = run i [ [osstr|--bash-completion-enriched|] + , [osstr|--bash-completion-index=0|] + , [osstr|--bash-completion-option-desc-length=3|] + , [osstr|--bash-completion-command-desc-length=30|]] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- Strict.lines <$> err [osstr|test|] return $ ["--foo\tFoo...", "--bar\tBar..."] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed prop_bind_usage :: Property prop_bind_usage = once $ - let p :: Parser [String] - p = many (argument str (metavar "ARGS...")) + let p :: Parser [OsString] + p = many (argument osStr (metavar "ARGS...")) i = info (p <**> helper) briefDesc - result = run i ["--help"] + result = run i [[osstr|--help|]] in assertError result $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "Usage: test [ARGS...]" === text prop_issue_19 :: Property prop_issue_19 = once $ - let p = option (fmap Just str) - ( short 'x' + let p = option (fmap Just osStr) + ( short (OsString.unsafeFromChar 'x') <> value Nothing ) i = info (p <**> helper) idm - result = run i ["-x", "foo"] - in assertResult result (Just "foo" ===) + result = run i [[osstr|-x|], [osstr|foo|]] + in assertResult result (Just [osstr|foo|] ===) prop_arguments1_none :: Property prop_arguments1_none = - let p :: Parser [String] - p = some (argument str idm) + let p :: Parser [OsString] + p = some (argument osStr idm) i = info (p <**> helper) idm result = run i [] in assertError result $ \_ -> property succeeded prop_arguments1_some :: Property prop_arguments1_some = once $ - let p :: Parser [String] - p = some (argument str idm) + let p :: Parser [OsString] + p = some (argument osStr idm) i = info (p <**> helper) idm - result = run i ["foo", "--", "bar", "baz"] - in assertResult result (["foo", "bar", "baz"] ===) + result = run i [[osstr|foo|], [osstr|--|], [osstr|bar|], [osstr|baz|]] + in assertResult result ([[osstr|foo|], [osstr|bar|], [osstr|baz|]] ===) prop_arguments_switch :: Property prop_arguments_switch = once $ - let p :: Parser [String] - p = switch (short 'x') - *> many (argument str idm) + let p :: Parser [OsString] + p = switch (short (OsString.unsafeFromChar 'x')) + *> many (argument osStr idm) i = info p idm - result = run i ["--", "-x"] - in assertResult result $ \args -> ["-x"] === args + result = run i [[osstr|--|], [osstr|-x|]] + in assertResult result $ \args -> [[osstr|-x|]] === args prop_issue_35 :: Property prop_issue_35 = once $ - let p = flag' True (short 't' <> hidden) - <|> flag' False (short 'f') + let p = flag' True (short (OsString.unsafeFromChar 't') <> hidden) + <|> flag' False (short (OsString.unsafeFromChar 'f')) i = info p idm result = run i [] in assertError result $ \failure -> - let text = lines . fst $ renderFailure failure "test" - in ["Missing: -f", "", "Usage: test -f"] === text + let text = Lazy.lines . fst $ renderFailure failure [osstr|test|] + in ["Missing: -f", Lazy.empty, "Usage: test -f"] === text prop_backtracking :: Property prop_backtracking = once $ - let p2 = switch (short 'a') + let p2 = switch (short (OsString.unsafeFromChar 'a')) p1 = (,) - <$> subparser (command "c" (info p2 idm)) - <*> switch (short 'b') + <$> subparser (command [osstr|c|] (info p2 idm)) + <*> switch (short (OsString.unsafeFromChar 'b')) i = info (p1 <**> helper) idm - result = execParserPure (prefs noBacktrack) i ["c", "-b"] + result = execParserPure (prefs noBacktrack) i [[osstr|c|], [osstr|-b|]] in assertError result $ \_ -> property succeeded prop_subparser_inline :: Property prop_subparser_inline = once $ - let p2 = switch (short 'a') + let p2 = switch (short (OsString.unsafeFromChar 'a')) p1 = (,) - <$> subparser (command "c" (info p2 idm)) - <*> switch (short 'b') + <$> subparser (command [osstr|c|] (info p2 idm)) + <*> switch (short (OsString.unsafeFromChar 'b')) i = info (p1 <**> helper) idm - result = execParserPure (prefs subparserInline) i ["c", "-b", "-a" ] + result = execParserPure (prefs subparserInline) i [[osstr|c|], [osstr|-b|], [osstr|-a|] ] in assertResult result ((True, True) ===) prop_error_context :: Property prop_error_context = once $ - let p = pk <$> option auto (long "port") - <*> option auto (long "key") + let p = pk <$> option auto (long [osstr|port|]) + <*> option auto (long [osstr|key|]) i = info p idm - result = run i ["--port", "foo", "--key", "291"] + result = run i [[osstr|--port|], [osstr|foo|], [osstr|--key|], [osstr|291|]] in assertError result $ \failure -> - let (msg, _) = renderFailure failure "test" - errMsg = head $ lines msg - in conjoin [ counterexample "no context in error message (option)" ("port" `isInfixOf` errMsg) - , counterexample "no context in error message (value)" ("foo" `isInfixOf` errMsg)] + let (msg, _) = renderFailure failure [osstr|test|] + errMsg = head $ Lazy.lines msg + in conjoin [ counterexample "no context in error message (option)" ("port" `Lazy.isInfixOf` errMsg) + , counterexample "no context in error message (value)" ("foo" `Lazy.isInfixOf` errMsg)] where pk :: Int -> Int -> (Int, Int) pk = (,) @@ -577,369 +587,369 @@ prop_arg_order_1 = once $ <$> argument (condr even) idm <*> argument (condr odd) idm i = info p idm - result = run i ["3", "6"] + result = run i [[osstr|3|], [osstr|6|]] in assertError result $ \_ -> property succeeded prop_arg_order_2 :: Property prop_arg_order_2 = once $ let p = (,,) <$> argument (condr even) idm - <*> option (condr even) (short 'a') - <*> option (condr odd) (short 'b') + <*> option (condr even) (short (OsString.unsafeFromChar 'a')) + <*> option (condr odd) (short (OsString.unsafeFromChar 'b')) i = info p idm - result = run i ["2", "-b", "3", "-a", "6"] + result = run i [[osstr|2|], [osstr|-b|], [osstr|3|], [osstr|-a|], [osstr|6|]] in assertResult result ((===) (2, 6, 3)) prop_arg_order_3 :: Property prop_arg_order_3 = once $ let p = (,) <$> ( argument (condr even) idm - <|> option auto (short 'n') ) + <|> option auto (short (OsString.unsafeFromChar 'n')) ) <*> argument (condr odd) idm i = info p idm - result = run i ["-n", "3", "5"] + result = run i [[osstr|-n|], [osstr|3|], [osstr|5|]] in assertResult result ((===) (3, 5)) prop_unix_style :: Int -> Int -> Property prop_unix_style j k = let p = (,) - <$> flag' j (short 'x') - <*> flag' k (short 'c') + <$> flag' j (short (OsString.unsafeFromChar 'x')) + <*> flag' k (short (OsString.unsafeFromChar 'c')) i = info p idm - result = run i ["-xc"] + result = run i [[osstr|-xc|]] in assertResult result ((===) (j,k)) prop_unix_with_options :: Property prop_unix_with_options = once $ let p = (,) - <$> flag' (1 :: Int) (short 'x') - <*> strOption (short 'a') + <$> flag' (1 :: Int) (short (OsString.unsafeFromChar 'x')) + <*> osStrOption (short (OsString.unsafeFromChar 'a')) i = info p idm - result = run i ["-xac"] - in assertResult result ((===) (1, "c")) + result = run i [[osstr|-xac|]] + in assertResult result ((1, [osstr|c|]) ===) prop_count_flags :: Property prop_count_flags = once $ - let p = length <$> many (flag' () (short 't')) + let p = length <$> many (flag' () (short (OsString.unsafeFromChar 't'))) i = info p idm - result = run i ["-ttt"] + result = run i [[osstr|-ttt|]] in assertResult result ((===) 3) prop_issue_47 :: Property prop_issue_47 = once $ - let p = option r (long "test" <> value 9) :: Parser Int + let p = option r (long [osstr|test|] <> value 9) :: Parser Int r = readerError "error message" - result = run (info p idm) ["--test", "x"] + result = run (info p idm) [[osstr|--test|], [osstr|x|]] in assertError result $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" - in counterexample "no error message" ("error message" `isInfixOf` text) + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] + in counterexample "no error message" ("error message" `Lazy.isInfixOf` text) prop_long_help :: Property prop_long_help = once $ let p = Formatting.opts <**> helper i = info p - ( progDesc (concat + ( progDesc (mconcat [ "This is a very long program description. " , "This text should be automatically wrapped " , "to fit the size of the terminal" ]) ) - in checkHelpTextWith ExitSuccess (prefs (columns 50)) "formatting" i ["--help"] + in checkHelpTextWith ExitSuccess (prefs (columns 50)) [osstr|formatting|] i [[osstr|--help|]] prop_issue_50 :: Property prop_issue_50 = once $ - let p = argument str (metavar "INPUT") - <* switch (long "version") - result = run (info p idm) ["--version", "test"] - in assertResult result $ \r -> "test" === r + let p = argument osStr (metavar "INPUT") + <* switch (long [osstr|version|]) + result = run (info p idm) [[osstr|--version|], [osstr|test|]] + in assertResult result $ \r -> [osstr|test|] === r prop_intersperse_1 :: Property prop_intersperse_1 = once $ - let p = many (argument str (metavar "ARGS")) - <* switch (short 'x') + let p = many (argument osStr (metavar "ARGS")) + <* switch (short (OsString.unsafeFromChar 'x')) result = run (info p noIntersperse) - ["a", "-x", "b"] - in assertResult result $ \args -> ["a", "-x", "b"] === args + [[osstr|a|], [osstr|-x|], [osstr|b|]] + in assertResult result $ \args -> [[osstr|a|], [osstr|-x|], [osstr|b|]] === args prop_intersperse_2 :: Property prop_intersperse_2 = once $ let p = subparser - ( command "run" - ( info (many (argument str (metavar "OPTIONS"))) + ( command [osstr|run|] + ( info (many (argument osStr (metavar "OPTIONS"))) noIntersperse ) - <> command "test" - ( info (many (argument str (metavar "ARGS"))) + <> command [osstr|test|] + ( info (many (argument osStr (metavar "ARGS"))) idm ) ) i = info p idm - result1 = run i ["run", "foo", "-x"] - result2 = run i ["test", "bar", "-x"] - in conjoin [ assertResult result1 $ \args -> ["foo", "-x"] === args + result1 = run i [[osstr|run|], [osstr|foo|], [osstr|-x|]] + result2 = run i [[osstr|test|], [osstr|bar|], [osstr|-x|]] + in conjoin [ assertResult result1 $ \args -> [[osstr|foo|], [osstr|-x|]] === args , assertError result2 $ \_ -> property succeeded ] prop_intersperse_3 :: Property prop_intersperse_3 = once $ - let p = (,,) <$> switch ( long "foo" ) - <*> strArgument ( metavar "FILE" ) - <*> many ( strArgument ( metavar "ARGS..." ) ) + let p = (,,) <$> switch ( long [osstr|foo|] ) + <*> osStrArgument ( metavar "FILE" ) + <*> many ( osStrArgument ( metavar "ARGS..." ) ) i = info p noIntersperse - result = run i ["--foo", "myfile", "-a", "-b", "-c"] + result = run i [[osstr|--foo|], [osstr|myfile|], [osstr|-a|], [osstr|-b|], [osstr|-c|]] in assertResult result $ \(b,f,as) -> - conjoin [ ["-a", "-b", "-c"] === as + conjoin [ [[osstr|-a|], [osstr|-b|], [osstr|-c|]] === as , True === b - , "myfile" === f ] + , [osstr|myfile|] === f ] prop_forward_options :: Property prop_forward_options = once $ - let p = (,) <$> switch ( long "foo" ) - <*> many ( strArgument ( metavar "ARGS..." ) ) + let p = (,) <$> switch ( long [osstr|foo|] ) + <*> many ( osStrArgument ( metavar "ARGS..." ) ) i = info p forwardOptions - result = run i ["--fo", "--foo", "myfile"] + result = run i [[osstr|--fo|], [osstr|--foo|], [osstr|myfile|]] in assertResult result $ \(b,a) -> conjoin [ True === b - , ["--fo", "myfile"] === a ] + , [[osstr|--fo|], [osstr|myfile|]] === a ] prop_issue_52 :: Property prop_issue_52 = once $ let p = subparser ( metavar "FOO" - <> command "run" (info (pure "foo") idm) ) + <> command [osstr|run|] (info (pure [osstr|foo|]) idm) ) i = info p idm in assertError (run i []) $ \failure -> do - let text = lines . fst $ renderFailure failure "test" - ["Missing: FOO", "", "Usage: test FOO"] === text + let text = Lazy.lines . fst $ renderFailure failure [osstr|test|] + ["Missing: FOO", Lazy.empty, "Usage: test FOO"] === text prop_multiple_subparsers :: Property prop_multiple_subparsers = once $ let p1 = subparser - (command "add" (info (pure ()) + (command [osstr|add|] (info (pure ()) ( progDesc "Add a file to the repository" ))) p2 = subparser - (command "commit" (info (pure ()) + (command [osstr|commit|] (info (pure ()) ( progDesc "Record changes to the repository" ))) i = info (p1 *> p2 <**> helper) idm - in checkHelpText "subparsers" i ["--help"] + in checkHelpText [osstr|subparsers|] i [[osstr|--help|]] prop_argument_error :: Property prop_argument_error = once $ let r = (auto >>= \x -> x <$ guard (x == 42)) - <|> (str >>= \x -> readerError (x ++ " /= 42")) + <|> (osStr >>= \x -> readerError (osStringToStrictText (x <> [osstr| /= 42|]))) p1 = argument r idm :: Parser Int i = info (p1 *> p1) idm - in assertError (run i ["3", "4"]) $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + in assertError (run i [[osstr|3|], [osstr|4|]]) $ \failure -> + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "3 /= 42" === text prop_reader_error_mplus :: Property prop_reader_error_mplus = once $ let r = (auto >>= \x -> x <$ guard (x == 42)) - <|> (str >>= \x -> readerError (x ++ " /= 42")) + <|> (osStr >>= \x -> readerError (osStringToStrictText (x <> [osstr| /= 42|]))) p1 = argument r idm :: Parser Int i = info p1 idm - in assertError (run i ["foo"]) $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + in assertError (run i [[osstr|foo|]]) $ \failure -> + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "foo /= 42" === text prop_missing_flags_described :: Property prop_missing_flags_described = once $ - let p :: Parser (String, String, Maybe String) + let p :: Parser (OsString, OsString, Maybe OsString) p = (,,) - <$> option str (short 'a') - <*> option str (short 'b') - <*> optional (option str (short 'c')) + <$> option osStr (short (OsString.unsafeFromChar 'a')) + <*> option osStr (short (OsString.unsafeFromChar 'b')) + <*> optional (option osStr (short (OsString.unsafeFromChar 'c'))) i = info p idm - in assertError (run i ["-b", "3"]) $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + in assertError (run i [[osstr|-b|], [osstr|3|]]) $ \failure -> + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "Missing: -a ARG" === text prop_many_missing_flags_described :: Property prop_many_missing_flags_described = once $ - let p :: Parser (String, String) + let p :: Parser (OsString, OsString) p = (,) - <$> option str (short 'a') - <*> option str (short 'b') + <$> option osStr (short (OsString.unsafeFromChar 'a')) + <*> option osStr (short (OsString.unsafeFromChar 'b')) i = info p idm in assertError (run i []) $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "Missing: -a ARG -b ARG" === text prop_alt_missing_flags_described :: Property prop_alt_missing_flags_described = once $ - let p :: Parser String - p = option str (short 'a') <|> option str (short 'b') + let p :: Parser OsString + p = option osStr (short (OsString.unsafeFromChar 'a')) <|> option osStr (short (OsString.unsafeFromChar 'b')) i = info p idm in assertError (run i []) $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "Missing: (-a ARG | -b ARG)" === text prop_missing_option_parameter_err :: Property prop_missing_option_parameter_err = once $ - let p :: Parser String - p = option str (short 'a') + let p :: Parser OsString + p = option osStr (short (OsString.unsafeFromChar 'a')) i = info p idm - in assertError (run i ["-a"]) $ \failure -> - let text = head . lines . fst $ renderFailure failure "test" + in assertError (run i [[osstr|-a|]]) $ \failure -> + let text = head . Lazy.lines . fst $ renderFailure failure [osstr|test|] in "The option `-a` expects an argument." === text prop_many_pairs_success :: Property prop_many_pairs_success = once $ - let p :: Parser [(String, String)] - p = many $ (,) <$> argument str idm <*> argument str idm + let p :: Parser [(OsString, OsString)] + p = many $ (,) <$> argument osStr idm <*> argument osStr idm i = info p idm nargs = 10000 - result = run i (replicate nargs "foo") + result = run i (replicate nargs [osstr|foo|]) in assertResult result $ \xs -> nargs `div` 2 === length xs prop_many_pairs_failure :: Property prop_many_pairs_failure = once $ - let p :: Parser [(String, String)] - p = many $ (,) <$> argument str idm <*> argument str idm + let p :: Parser [(OsString, OsString)] + p = many $ (,) <$> argument osStr idm <*> argument osStr idm i = info p idm nargs = 9999 - result = run i (replicate nargs "foo") + result = run i (replicate nargs [osstr|foo|]) in assertError result $ \_ -> property succeeded prop_many_pairs_lazy_progress :: Property prop_many_pairs_lazy_progress = once $ - let p :: Parser [(Maybe String, String)] - p = many $ (,) <$> optional (option str (short 'a')) <*> argument str idm + let p :: Parser [(Maybe OsString, OsString)] + p = many $ (,) <$> optional (option osStr (short (OsString.unsafeFromChar 'a'))) <*> argument osStr idm i = info p idm - result = run i ["foo", "-abar", "baz"] - in assertResult result $ \xs -> [(Just "bar", "foo"), (Nothing, "baz")] === xs + result = run i [[osstr|foo|], [osstr|-abar|], [osstr|baz|]] + in assertResult result $ \xs -> [(Just [osstr|bar|], [osstr|foo|]), (Nothing, [osstr|baz|])] === xs prop_suggest :: Property prop_suggest = once $ - let p2 = subparser (command "first" (info (pure ()) idm)) - p1 = subparser (command "fst" (info (pure ()) idm)) - p3 = subparser (command "far-off" (info (pure ()) idm)) + let p2 = subparser (command [osstr|first|] (info (pure ()) idm)) + p1 = subparser (command [osstr|fst|] (info (pure ()) idm)) + p3 = subparser (command [osstr|far-off|] (info (pure ()) idm)) p = p2 *> p1 *> p3 i = info p idm - result = run i ["fist"] + result = run i [[osstr|fist|]] in assertError result $ \failure -> - let (msg, _) = renderFailure failure "prog" - in counterexample msg - $ isInfixOf "Did you mean one of these?\n first\n fst" msg + let (msg, _) = renderFailure failure [osstr|prog|] + in counterexample (Lazy.unpack msg) + $ Lazy.isInfixOf "Did you mean one of these?\n first\n fst" msg prop_grouped_some_option_ellipsis :: Property prop_grouped_some_option_ellipsis = once $ - let x :: Parser String - x = strOption (short 'x' <> metavar "X") + let x :: Parser OsString + x = osStrOption (short (OsString.unsafeFromChar 'x') <> metavar "X") p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> some x) in r === "-x X (-x X)..." prop_grouped_many_option_ellipsis :: Property prop_grouped_many_option_ellipsis = once $ - let x :: Parser String - x = strOption (short 'x' <> metavar "X") + let x :: Parser OsString + x = osStrOption (short (OsString.unsafeFromChar 'x') <> metavar "X") p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> many x) in r === "-x X [-x X]..." prop_grouped_some_argument_ellipsis :: Property prop_grouped_some_argument_ellipsis = once $ - let x :: Parser String - x = strArgument (metavar "X") + let x :: Parser OsString + x = osStrArgument (metavar "X") p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> some x) in r === "X X..." prop_grouped_many_argument_ellipsis :: Property prop_grouped_many_argument_ellipsis = once $ - let x :: Parser String - x = strArgument (metavar "X") + let x :: Parser OsString + x = osStrArgument (metavar "X") p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> many x) in r === "X [X]..." prop_grouped_some_pairs_argument_ellipsis :: Property prop_grouped_some_pairs_argument_ellipsis = once $ - let x :: Parser String - x = strArgument (metavar "X") + let x :: Parser OsString + x = osStrArgument (metavar "X") p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> some (x *> x)) in r === "X (X X)..." prop_grouped_many_pairs_argument_ellipsis :: Property prop_grouped_many_pairs_argument_ellipsis = once $ - let x :: Parser String - x = strArgument (metavar "X") + let x :: Parser OsString + x = osStrArgument (metavar "X") p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> many (x *> x)) in r === "X [X X]..." prop_grouped_some_dual_option_ellipsis :: Property prop_grouped_some_dual_option_ellipsis = once $ - let x :: Parser String - x = strOption (short 'a' <> short 'b' <> metavar "X") + let x :: Parser OsString + x = osStrOption (short (OsString.unsafeFromChar 'a') <> short (OsString.unsafeFromChar 'b') <> metavar "X") p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> some x) in r === "(-a|-b X) (-a|-b X)..." prop_grouped_many_dual_option_ellipsis :: Property prop_grouped_many_dual_option_ellipsis = once $ - let x :: Parser String - x = strOption (short 'a' <> short 'b' <> metavar "X") + let x :: Parser OsString + x = osStrOption (short (OsString.unsafeFromChar 'a') <> short (OsString.unsafeFromChar 'b') <> metavar "X") p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> many x) in r === "(-a|-b X) [-a|-b X]..." prop_grouped_some_dual_flag_ellipsis :: Property prop_grouped_some_dual_flag_ellipsis = once $ - let x = flag' () (short 'a' <> short 'b') + let x = flag' () (short (OsString.unsafeFromChar 'a') <> short (OsString.unsafeFromChar 'b')) p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> some x) in r === "(-a|-b) (-a|-b)..." prop_grouped_many_dual_flag_ellipsis :: Property prop_grouped_many_dual_flag_ellipsis = once $ - let x = flag' () (short 'a' <> short 'b') + let x = flag' () (short (OsString.unsafeFromChar 'a') <> short (OsString.unsafeFromChar 'b')) p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p (x *> many x) in r === "(-a|-b) [-a|-b]..." prop_issue_402 :: Property prop_issue_402 = once $ - let x = some (flag' () (short 'a')) <|> some (flag' () (short 'b' <> internal)) + let x = some (flag' () (short (OsString.unsafeFromChar 'a'))) <|> some (flag' () (short (OsString.unsafeFromChar 'b') <> internal)) p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p x in r === "(-a)..." prop_nice_some1 :: Property prop_nice_some1 = once $ - let x = Options.Applicative.NonEmpty.some1 (flag' () (short 'a')) + let x = Options.Applicative.NonEmpty.some1 (flag' () (short (OsString.unsafeFromChar 'a'))) p = prefs (multiSuffix "...") r = show . extractChunk $ H.briefDesc p x in r === "(-a)..." prop_some1_works :: Property prop_some1_works = once $ - let p = Options.Applicative.NonEmpty.some1 (flag' () (short 'a')) + let p = Options.Applicative.NonEmpty.some1 (flag' () (short (OsString.unsafeFromChar 'a'))) i = info p idm - result = run i ["-a", "-a"] + result = run i [[osstr|-a|], [osstr|-a|]] in assertResult result $ \xs -> () :| [()] === xs prop_help_contexts :: Property prop_help_contexts = once $ let grabHelpMessage (Failure failure) = - let (msg, ExitSuccess) = renderFailure failure "" + let (msg, ExitSuccess) = renderFailure failure [osstr||] in msg grabHelpMessage _ = error "Parse did not render help text" i = Cabal.pinfo - pre = run i ["install", "--help"] - post = run i ["--help", "install"] + pre = run i [[osstr|install|], [osstr|--help|]] + post = run i [[osstr|--help|], [osstr|install|]] in grabHelpMessage pre === grabHelpMessage post prop_help_unknown_context :: Property prop_help_unknown_context = once $ let grabHelpMessage (Failure failure) = - let (msg, ExitSuccess) = renderFailure failure "" + let (msg, ExitSuccess) = renderFailure failure [osstr||] in msg grabHelpMessage _ = error "Parse did not render help text" i = Cabal.pinfo - pre = run i ["--help"] - post = run i ["--help", "not-a-command"] + pre = run i [[osstr|--help|]] + post = run i [[osstr|--help|], [osstr|not-a-command|]] in grabHelpMessage pre === grabHelpMessage post @@ -947,56 +957,56 @@ prop_long_command_line_flow :: Property prop_long_command_line_flow = once $ let p = LongSub.sample <**> helper i = info p - ( progDesc (concat + ( progDesc (mconcat [ "This is a very long program description. " , "This text should be automatically wrapped " , "to fit the size of the terminal" ]) ) - in checkHelpTextWith ExitSuccess (prefs (columns 50)) "formatting-long-subcommand" i ["hello-very-long-sub", "--help"] + in checkHelpTextWith ExitSuccess (prefs (columns 50)) [osstr|formatting-long-subcommand|] i [[osstr|hello-very-long-sub|], [osstr|--help|]] prop_parser_group_basic :: Property prop_parser_group_basic = once $ - checkHelpText "parser_group_basic" ParserGroup.Basic.opts ["--help"] + checkHelpText [osstr|parser_group_basic|] ParserGroup.Basic.opts [[osstr|--help|]] prop_parser_group_command_groups :: Property prop_parser_group_command_groups = once $ - checkHelpText "parser_group_command_groups" ParserGroup.CommandGroups.opts ["--help"] + checkHelpText [osstr|parser_group_command_groups|] ParserGroup.CommandGroups.opts [[osstr|--help|]] prop_parser_group_duplicate_command_groups :: Property prop_parser_group_duplicate_command_groups = once $ - checkHelpText "parser_group_duplicate_command_groups" ParserGroup.DuplicateCommandGroups.opts ["--help"] + checkHelpText [osstr|parser_group_duplicate_command_groups|] ParserGroup.DuplicateCommandGroups.opts [[osstr|--help|]] prop_parser_group_duplicates :: Property prop_parser_group_duplicates = once $ - checkHelpText "parser_group_duplicates" ParserGroup.Duplicates.opts ["--help"] + checkHelpText [osstr|parser_group_duplicates|] ParserGroup.Duplicates.opts [[osstr|--help|]] prop_parser_group_all_grouped :: Property prop_parser_group_all_grouped = once $ - checkHelpText "parser_group_all_grouped" ParserGroup.AllGrouped.opts ["--help"] + checkHelpText [osstr|parser_group_all_grouped|] ParserGroup.AllGrouped.opts [[osstr|--help|]] prop_parser_group_nested :: Property prop_parser_group_nested = once $ - checkHelpText "parser_group_nested" ParserGroup.Nested.opts ["--help"] + checkHelpText [osstr|parser_group_nested|] ParserGroup.Nested.opts [[osstr|--help|]] prop_issue_450_subcommand_show_help_on_empty_inline :: Property prop_issue_450_subcommand_show_help_on_empty_inline = once $ let q = (,) - <$> flag' () (short 'a' <> help "supply a") - <*> flag' () (short 'b' <> help "supply b") + <$> flag' () (short (OsString.unsafeFromChar 'a') <> help "supply a") + <*> flag' () (short (OsString.unsafeFromChar 'b') <> help "supply b") p = subparser $ - command "foo" $ info q $ + command [osstr|foo|] $ info q $ progDesc "Foo commands." i = info (p <**> helper) briefDesc - result = execParserPure (prefs (showHelpOnEmpty <> subparserInline)) i ["foo"] + result = execParserPure (prefs (showHelpOnEmpty <> subparserInline)) i [[osstr|foo|]] in assertError result $ \failure -> - let text = lines . fst $ renderFailure failure "test" + let text = Lazy.lines . fst $ renderFailure failure [osstr|test|] in ["Usage: test foo -a -b" - ,"" + , Lazy.empty ," Foo commands." - ,"" + , Lazy.empty ,"Available options:" ," -a supply a" ," -b supply b"] === text @@ -1006,22 +1016,22 @@ prop_issue_450_ensure_missing_still_shows :: Property prop_issue_450_ensure_missing_still_shows = once $ let q = (,) - <$> flag' () (short 'a' <> help "supply a") - <*> flag' () (short 'b' <> help "supply b") + <$> flag' () (short (OsString.unsafeFromChar 'a') <> help "supply a") + <*> flag' () (short (OsString.unsafeFromChar 'b') <> help "supply b") p = subparser $ - command "foo" $ info q $ + command [osstr|foo|] $ info q $ progDesc "Foo commands." i = info (p <**> helper) briefDesc - result = execParserPure (prefs (showHelpOnEmpty <> subparserInline)) i ["foo", "-a"] + result = execParserPure (prefs (showHelpOnEmpty <> subparserInline)) i [[osstr|foo|], [osstr|-a|]] in assertError result $ \failure -> - let text = lines . fst $ renderFailure failure "test" + let text = Lazy.lines . fst $ renderFailure failure [osstr|test|] in ["Missing: -b" - ,"" + , Lazy.empty ,"Usage: test foo -a -b" - ,"" + , Lazy.empty ," Foo commands."] === text @@ -1031,8 +1041,8 @@ deriving instance Arbitrary a => Arbitrary (Chunk a) equalDocs :: Double -> Int -> Doc -> Doc -> Property -equalDocs f w d1 d2 = Doc.prettyString f w d1 - === Doc.prettyString f w d2 +equalDocs f w d1 d2 = Doc.prettyLazyText f w d1 + === Doc.prettyLazyText f w d2 prop_listToChunk_1 :: [String] -> Property prop_listToChunk_1 xs = isEmpty (listToChunk xs) === null xs @@ -1046,16 +1056,16 @@ prop_extractChunk_1 x = extractChunk (pure x) === x prop_extractChunk_2 :: Chunk String -> Property prop_extractChunk_2 x = extractChunk (fmap pure x) === x -prop_stringChunk_1 :: Positive Double -> Positive Int -> String -> Property +prop_stringChunk_1 :: Positive Double -> Positive Int -> Strict.Text -> Property prop_stringChunk_1 (Positive f) (Positive w) s = equalDocs f w (extractChunk (stringChunk s)) (Doc.pretty s) -prop_stringChunk_2 :: String -> Property -prop_stringChunk_2 s = isEmpty (stringChunk s) === null s +prop_stringChunk_2 :: Strict.Text -> Property +prop_stringChunk_2 s = isEmpty (stringChunk s) === Strict.null s -prop_paragraph :: String -> Property -prop_paragraph s = isEmpty (paragraph s) === null (words s) +prop_paragraph :: Strict.Text -> Property +prop_paragraph s = isEmpty (paragraph s) === null (Strict.words s) --- @@ -1090,6 +1100,8 @@ prop_edit_transposition as bs a b = a /= b ==> editDistance (as ++ [a,b] ++ bs) (as ++ [b,a] ++ bs) === 1 --- +instance Arbitrary Strict.Text where + arbitrary = Strict.pack <$> (coerce (arbitrary :: Gen PrintableString)) return [] main :: IO ()