Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to Pretty Printer Proper #474

Merged
merged 2 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .github/workflows/haskell-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,6 @@ jobs:
compilerVersion: 7.4.2
setup-method: hvr-ppa
allow-failure: false
- compiler: ghc-7.2.2
compilerKind: ghc
compilerVersion: 7.2.2
setup-method: hvr-ppa
allow-failure: false
- compiler: ghc-7.0.4
compilerKind: ghc
compilerVersion: 7.0.4
setup-method: hvr-ppa
allow-failure: false
fail-fast: false
steps:
- name: apt
Expand Down
30 changes: 22 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
## Unreleased
## Version 0.18.0.0 (22 May 2023)

- Move to 'prettyprinter` library for pretty printing.

This is a potentially breaking change when one uses the '*Doc' family of functions
(like `headerDoc`) from `Options.Applicative`. However, as versions of
'ansi-wl-pprint > 1.0' export a compatible `Doc` type, this can be mitigated by
using a recent version.

One can also either import directly from `Options.Applicative.Help` or from the
`Prettyprinter` module of 'prettyprinter'.

- Allow commands to be disambiguated in a similar manner to flags when the
`disambiguate` modifier is used.

This is a potentially breaking change as the internal `CmdReader` constructor
has been adapted so it is able to be inspected to a greater degree to support
finding prefix matches.

## Version 0.17.1.0 (22 May 2023)

- Widen bounds for `ansi-wl-pprint`. This supports the use of `prettyprinter`
in a non-breaking way, as the `ansi-wl-pprint > 1.0` support the newer
Expand All @@ -10,15 +29,10 @@

- Add `simpleVersioner` utility for adding a '--version' option to a parser.

- Allow commands to be disambiguated in a similar manner to flags when the
`disambiguate` modifier is used.

This is a potentially breaking change as the internal `CmdReader` constructor
has been adapted so it is able to be inspected to a greater degree to support
finding submatches.

- Improve documentation.

- Drop support for GHC 7.0 and 7.2.

## Version 0.17.0.0 (1 Feb 2022)

- Make tabulation width configurable in usage texts.
Expand Down
7 changes: 4 additions & 3 deletions optparse-applicative.cabal
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: optparse-applicative
version: 0.17.0.0
version: 0.18.0.0
synopsis: Utilities and combinators for parsing command line options
description:
optparse-applicative is a haskell library for parsing options
Expand Down Expand Up @@ -100,10 +100,11 @@ library
, Options.Applicative.Types
, Options.Applicative.Internal

build-depends: base == 4.*
build-depends: base >= 4.5 && < 5
, transformers >= 0.2 && < 0.7
, transformers-compat >= 0.3 && < 0.8
, ansi-wl-pprint >= 0.6.8 && < 1.1
, prettyprinter >= 1.7 && < 1.8
, prettyprinter-ansi-terminal >= 1.1 && < 1.2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prettyprinter-ansi-terminal is problematic for my use cases, because it depends on text unconditionally.

Needless to say that my use cases are quite extreme and exotic. You are doing an excellent job in this PR.

Sorry, I don't remember, does optparse-applicative use any ANSI capabilities like colors or bold/italic?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly. I didn't want to add an annotation type parameter to Parser, so went with one which is capable enough (and which would still support what downstream users might have in their doc strings).

So for your use-case we could make a later release where this import is behind a flag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggests that ideally ParserInfo a should be additionally parameterized by ann from Doc ann, but it's probably too much of a breakage for a stable package.

Sorry, I don't have much time to delve into it right now, so I suggest you go ahead as is, and I'll try to figure out workarounds for my use case later. Thanks for remembering about my concerns, it's a pleasure to deal with you.


if flag(process)
build-depends: process >= 1.0 && < 1.7
Expand Down
3 changes: 1 addition & 2 deletions src/Options/Applicative/BashCompletion.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
-- | You don't need to import this module to enable bash completion.
--
-- See
Expand Down Expand Up @@ -150,7 +149,7 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre
-- 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 (displayS (renderPretty 1 len doc) "") of
render_line len doc = case lines (prettyString 1 len doc) of
[] -> ""
[x] -> x
x : _ -> x ++ "..."
Expand Down
3 changes: 1 addition & 2 deletions src/Options/Applicative/Help/Chunk.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
module Options.Applicative.Help.Chunk
( Chunk(..)
, chunked
Expand Down Expand Up @@ -116,7 +115,7 @@ isEmpty = isNothing . unChunk
-- > extractChunk . stringChunk = string
stringChunk :: String -> Chunk Doc
stringChunk "" = mempty
stringChunk s = pure (string s)
stringChunk s = 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
Expand Down
17 changes: 8 additions & 9 deletions src/Options/Applicative/Help/Core.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
module Options.Applicative.Help.Core (
cmdDesc,
briefDesc,
Expand Down Expand Up @@ -58,7 +57,7 @@ optDesc pprefs style _reachability opt =
meta =
stringChunk $ optMetaVar opt
descs =
map (string . showOption) names
map (pretty . showOption) names
descriptions =
listToChunk (intersperse (descSep style) descs)
desc
Expand Down Expand Up @@ -98,7 +97,7 @@ cmdDesc pprefs = mapParser desc
CmdReader gn cmds ->
(,) gn $
tabulate (prefTabulateFill pprefs)
[ (string nm, align (extractChunk (infoProgDesc cmd)))
[ (pretty nm, align (extractChunk (infoProgDesc cmd)))
| (nm, cmd) <- reverse cmds
]
_ -> mempty
Expand Down Expand Up @@ -127,7 +126,7 @@ briefDesc' showOptional pprefs =
| otherwise =
filterOptional
style = OptDescStyle
{ descSep = string "|",
{ descSep = pretty '|',
descHidden = False,
descGlobal = False
}
Expand Down Expand Up @@ -204,9 +203,9 @@ optionsDesc global pprefs = tabulate (prefTabulateFill pprefs) . catMaybes . map
n = fst $ optDesc pprefs style info opt
h = optHelp opt
hdef = Chunk . fmap show_def . optShowDefault $ opt
show_def s = parens (string "default:" <+> string s)
show_def s = parens (pretty "default:" <+> pretty s)
style = OptDescStyle
{ descSep = string ",",
{ descSep = pretty ',',
descHidden = True,
descGlobal = global
}
Expand Down Expand Up @@ -251,7 +250,7 @@ parserHelp pprefs p =
group_title _ = mempty

with_title :: String -> Chunk Doc -> Chunk Doc
with_title title = fmap (string title .$.)
with_title title = fmap (pretty title .$.)


parserGlobals :: ParserPrefs -> Parser a -> ParserHelp
Expand All @@ -267,8 +266,8 @@ parserUsage :: ParserPrefs -> Parser a -> String -> Doc
parserUsage pprefs p progn =
group $
hsep
[ string "Usage:",
string progn,
[ pretty "Usage:",
pretty progn,
hangAtIfOver 9 35 (extractChunk (briefDesc pprefs p))
]

Expand Down
57 changes: 36 additions & 21 deletions src/Options/Applicative/Help/Pretty.hs
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
module Options.Applicative.Help.Pretty
( module Text.PrettyPrint.ANSI.Leijen
( module Prettyprinter
, module Prettyprinter.Render.Terminal
, Doc
, indent
, renderPretty
, displayS
, SimpleDoc

, (.$.)
, (</>)

, groupOrNestLine
, altSep
, hangAtIfOver

, prettyString
) where

#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup ((<>))
import Data.Semigroup ((<>), mempty)
#endif

import Text.PrettyPrint.ANSI.Leijen hiding (Doc, (<$>), (<>), columns, indent, renderPretty, displayS)
import qualified Text.PrettyPrint.ANSI.Leijen as PP
import Prettyprinter hiding (Doc)
import qualified Prettyprinter as PP
import qualified Prettyprinter.Render.String as PP
import Prettyprinter.Render.Terminal

import Prelude

type Doc = PP.Doc
type Doc = PP.Doc Prettyprinter.Render.Terminal.AnsiStyle
type SimpleDoc = SimpleDocStream AnsiStyle

indent :: Int -> PP.Doc -> PP.Doc
indent = PP.indent

renderPretty :: Float -> Int -> PP.Doc -> SimpleDoc
renderPretty = PP.renderPretty

displayS :: SimpleDoc -> ShowS
displayS = PP.displayS
linebreak :: Doc
linebreak = flatAlt line mempty

(.$.) :: Doc -> Doc -> Doc
(.$.) = (PP.<$>)

x .$. y = x <> line <> y
(</>) :: Doc -> Doc -> Doc
x </> y = x <> softline <> y

-- | Apply the function if we're not at the
-- start of our nesting level.
Expand All @@ -58,7 +59,6 @@ ifElseAtRoot f g doc =
then f doc
else g doc


-- | Render flattened text on this line, or start
-- a new line before rendering any text.
--
Expand All @@ -81,7 +81,7 @@ groupOrNestLine =
-- next line.
altSep :: Doc -> Doc -> Doc
altSep x y =
group (x <+> char '|' <> line) <//> y
group (x <+> pretty '|' <> line) <> group linebreak <> y


-- | Printer hacks to get nice indentation for long commands
Expand All @@ -102,3 +102,18 @@ hangAtIfOver i j d =
align d
else
linebreak <> ifAtRoot (indent i) d


renderPretty :: Double -> Int -> Doc -> SimpleDocStream AnsiStyle
renderPretty ribbonFraction lineWidth
= layoutSmart LayoutOptions
{ layoutPageWidth = AvailablePerLine lineWidth ribbonFraction }

prettyString :: Double -> Int -> Doc -> String
prettyString ribbonFraction lineWidth
= streamToString
. renderPretty ribbonFraction lineWidth

streamToString :: SimpleDocStream AnsiStyle -> String
streamToString stream =
PP.renderShowS stream ""
3 changes: 1 addition & 2 deletions src/Options/Applicative/Help/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,5 @@ helpText (ParserHelp e s h u d b g f) =
-- | Convert a help text to 'String'.
renderHelp :: Int -> ParserHelp -> String
renderHelp cols
= (`displayS` "")
. renderPretty 1.0 cols
= prettyString 1.0 cols
. helpText
12 changes: 6 additions & 6 deletions tests/test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import qualified Options.Applicative.NonEmpty


import qualified Options.Applicative.Help as H
import Options.Applicative.Help.Pretty (Doc, SimpleDoc(..))
import Options.Applicative.Help.Pretty (Doc)
import qualified Options.Applicative.Help.Pretty as Doc
import Options.Applicative.Help.Chunk
import Options.Applicative.Help.Levenshtein
Expand Down Expand Up @@ -951,9 +951,9 @@ prop_long_command_line_flow = once $
deriving instance Arbitrary a => Arbitrary (Chunk a)


equalDocs :: Float -> Int -> Doc -> Doc -> Property
equalDocs f w d1 d2 = Doc.displayS (Doc.renderPretty f w d1) ""
=== Doc.displayS (Doc.renderPretty f w d2) ""
equalDocs :: Double -> Int -> Doc -> Doc -> Property
equalDocs f w d1 d2 = Doc.prettyString f w d1
=== Doc.prettyString f w d2

prop_listToChunk_1 :: [String] -> Property
prop_listToChunk_1 xs = isEmpty (listToChunk xs) === null xs
Expand All @@ -967,10 +967,10 @@ 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 Float -> Positive Int -> String -> Property
prop_stringChunk_1 :: Positive Double -> Positive Int -> String -> Property
prop_stringChunk_1 (Positive f) (Positive w) s =
equalDocs f w (extractChunk (stringChunk s))
(Doc.string s)
(Doc.pretty s)

prop_stringChunk_2 :: String -> Property
prop_stringChunk_2 s = isEmpty (stringChunk s) === null s
Expand Down