Giter VIP home page Giter VIP logo

prettyprinter's Introduction

A modern Wadler/Leijen Prettyprinter

tl;dr

A prettyprinter/text rendering engine. Easy to use, well-documented, ANSI terminal backend exists, HTML backend is trivial to implement, no name clashes, Text-based, extensible.

let prettyType = align . sep . zipWith (<+>) ("::" : repeat "->")
    prettySig name ty = pretty name <+> prettyType ty
in  prettySig "example" ["Int", "Bool", "Char", "IO ()"]
-- Output for wide enough formats:
example :: Int -> Bool -> Char -> IO ()

-- Output for narrow formats:
example :: Int
        -> Bool
        -> Char
        -> IO ()

Longer; want to read

This package defines a prettyprinter to format text in a flexible and convenient way. The idea is to combine a document out of many small components, then using a layouter to convert it to an easily renderable simple document, which can then be rendered to a variety of formats, for example plain Text, or Markdown. What you are reading right now was generated by this library (see GenerateReadme.hs).

Why another prettyprinter?

Haskell, more specifically Hackage, has a zoo of Wadler/Leijen based prettyprinters already. Each of them addresses a different concern with the classic wl-pprint package. This package solves all these issues, and then some.

Text instead of String

String has exactly one use, and that’s showing Hello World in tutorials. For all other uses, Text is what people should be using. The prettyprinter uses no String definitions anywhere; using a String means an immediate conversion to the internal Text-based format.

Extensive documentation

The library is stuffed with runnable examples, showing use cases for the vast majority of exported values. Many things reference related definitions, everything comes with at least a sentence explaining its purpose.

No name clashes

Many prettyprinters use the legacy API of the first Wadler/Leijen prettyprinter, which used e.g. (<$>) to separate lines, which clashes with the ubiquitous synonym for fmap that’s been in Base for ages. These definitions were either removed or renamed, so there are no name clashes with standard libraries anymore.

Annotation support

Text is not all letters and newlines. Often, we want to add more information, the simplest kind being some form of styling. An ANSI terminal supports coloring, a web browser a plethora of different formattings.

More complex uses of annotations include e.g. adding type annotations for mouse-over hovers when printing a syntax tree, adding URLs to documentation, or adding source locations to show where a certain piece of output comes from. Idris is a project that makes extensive use of such a feature.

Special care has been applied to make annotations unobtrusive, so that if you don’t need or care about them there is no overhead, neither in terms of usability nor performance.

Extensible backends

A document can be rendered in many different ways, for many different clients. There is plain text, there is the ANSI terminal, there is the browser. Each of these speak different languages, and the backend is responsible for the translation to those languages. Backends should be readily available, or easy to implement if a custom solution is desired.

As a result, each backend requires only minimal dependencies; if you don’t want to print to an ANSI terminal for example, there is no need to have a dependency on a terminal library.

Performance

Rendering large documents should be done efficiently, and the library should make it easy to optimize common use cases for the programmer.

Open implementation

The type of documents is abstract in most of the other Wadler/Leijen prettyprinters, making it hard to impossible to write adaptors from one library to another. The type should be exposed for such purposes so it is possible to write adaptors from library to library, or each of them is doomed to live on its own small island of incompatibility. For this reason, the Doc type is fully exposed in a semi-internal module for this specific use case.

The prettyprinter family

The prettyprinter family of packages consists of:

  • prettyprinter is the core package. It defines the language to generate nicely laid out documents, which can then be given to renderers to display them in various ways, e.g. HTML, or plain text.
  • prettyprinter-ansi-terminal provides a renderer suitable for ANSI terminal output including colors (at the cost of a dependency more).
  • prettyprinter-compat-wl-pprint provides a drop-in compatibility layer for previous users of the wl-pprint package. Use it for easy adaption of the new prettyprinter, but don't develop anything new with it.
  • prettyprinter-compat-ansi-wl-pprint is the same, but for previous users of ansi-wl-pprint.
  • prettyprinter-compat-annotated-wl-pprint is the same, but for previous users of annotated-wl-pprint.
  • prettyprinter-convert-ansi-wl-pprint is a converter, not a drop-in replacement, for documents generated by ansi-wl-pprint. Useful for interfacing with other libraries that use the other format, like Trifecta and Optparse-Applicative.

Differences to the old Wadler/Leijen prettyprinters

The library originally started as a fork of ansi-wl-pprint until every line had been touched. The result is still in the same spirit as its predecessors, but modernized to match the current ecosystem and needs.

The most significant changes are:

  1. (<$>) is removed as an operator, since it clashes with the common alias for fmap.
  2. All but the essential <> and <+> operators were removed or replaced by ordinary names.
  3. Everything extensively documented, with references to other functions and runnable code examples.
  4. Use of Text instead of String.
  5. A fuse function to optimize often-used documents before rendering for efficiency.
  6. SimpleDoc was renamed SimpleDocStream, to contrast the new SimpleDocTree.
  7. In the ANSI backend, instead of providing an own colorization function for each color/intensity/layer combination, they have been combined in color, colorDull, bgColor, and bgColorDull functions, which can be found in the ANSI terminal specific prettyprinter-ansi-terminal package.

Historical notes

This module is based on previous work by Daan Leijen and Max Bolingbroke, who implemented and significantly extended the prettyprinter given by a paper by Phil Wadler in his 1997 paper »A Prettier Printer«, by adding lots of convenience functions, styling, and new functionality. Their package, ansi-wl-pprint is widely used in the Haskell ecosystem, and is at the time of writing maintained by Edward Kmett.

prettyprinter's People

Contributors

1jajen1 avatar andreasabel avatar batterseapower avatar benjamin-hodgson avatar bodigrim avatar bos avatar bradlarsen avatar cocreature avatar ddrone avatar ddssff avatar dwincort avatar ekmett avatar fumieval avatar georgefst avatar ggreif avatar glguy avatar hvr avatar k0ral avatar mpickering avatar phadej avatar quchen avatar saep avatar sjakobi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

prettyprinter's Issues

ByteString instances?

I'm attempting to convert from wl-pprint-extras and the lack of Pretty ByteString means I can't automatically derive Pretty for ByteString newtypes. Is this intentional? Should I just hand-write the instances?

more performance tests (looking good, but 'align' is expensive)

Hi, just FYI,

I just added prettyprinter to https://github.com/jwaldmann/pretty-test which enumerates and iterates contexts, in order to find documents that are hard to render.

On the subset of combinators that this test uses - it's looking good! prettyprinter beats pretty, and both are fast. The wl-* libs still seem to have problems.

See output at https://travis-ci.org/jwaldmann/pretty-test/jobs/373884017 Well, it's timing data on travis, but still. Also confirmed on a local idle machine.

But align is expesive? Look at this (10 seconds in ghci)

import Data.String
import Data.Text.Prettyprint.Doc
import Data.Text.Prettyprint.Doc.Render.Text

renderLazy  $ layoutPretty defaultLayoutOptions  $ iterate ( \ hole ->  align $ sep [sep [hole], sep []] ) (fromString  "l") !! 500

Remove preemptive upper version bounds

…there is a thread somewhere™ on Github where some important haskellers agree upper version bounds should go. Since this also makes my maintenance considerably easier (all past Stack breakages were because of preemtive upper bounds) prettyprinter should adopt the pattern.

Avoid trailing whitespace when aligning empty lines

Hi! I'm using Dhall, and dhall-format is built using prettyprinter. Using the align function leads to trailing whitespace when empty lines are aligned, as in the example at dhall-lang/dhall-haskell#183.

It's picky, to be sure, but (at least) my brain associates "pretty" with "tidy", so I wonder if it would be straightforward to avoid this case?

Panic induced by use of `unAnnotateS`

Here is a small reproducing example:

$ nix-shell --packages 'haskellPackages.ghcWithPackages (pkgs: [ pkgs.prettyprinter ])' --run ghci
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
>>> :set -XOverloadedStrings
>>> import Data.Text.Prettyprint.Doc
>>> import Data.Text.Prettyprint.Doc.Render.Text
>>> renderStrict (unAnnotateS (layoutSmart defaultLayoutOptions (annotate True "a")))
"*** Exception: An unpaired style terminator was encountered. This is a bug in the layout algorithm! Please report this as a bug
CallStack (from HasCallStack):
  error, called at src/Data/Text/Prettyprint/Doc/Render/Util/Panic.hs:18:20 in prettyprinter-1.1.1-1CDqnG9d6HQ5GZzz2F5LpU:Data.Text.Prettyprint.Doc.Render.Util.Panic

Expose viaShow and unsafeViaShow

These two functions are very useful to write easy new Pretty instances, I think their reexport from the public modules may simply have been forgotten.

Brought up by #47

Add Pretty instance for Doc

It seems that in general such instance cannot be added due to annotation mismatch between input and output. However, limited version can be introduced and still would be useful:

instance Pretty (Doc Void)

Readme fixes

  • Add link to Idris
  • Few, but not no, WL packages have an exposed Doc type
  • section with "colorDull": fix Markdown, mention this affects the ANSI module only
  • backticks around "Text"
  • Add shields for Hackage, Stackage

Migrate Idris to the new prettyprinter *as a test*

Idris is one of the biggest publich projects using a Wadler/Leijen prettyprinter. Because of that, it’s an excellent playground for testing how much effort it takes to migrate it to the present library, and to figure out what might break.

prettyprinter-ansi-terminal: There should be a way to reset a color/style to terminal default

Currently, it's possible to keep some colors and override others (e.g. keep foreground color, but change background color), but it's impossible to revert to terminal default for one or the other. This is particularly inconvenient when trying to use a »default« color like black or white in a color scheme that is required to work for both dark and light backgrounds.

This behavior is encoded in the type AnsiStyle:

data AnsiStyle = SetAnsiStyle
    { ansiForeground  :: Maybe (Intensity, Color) -- ^ Set the foreground color, or keep the old one.
    , ansiBackground  :: Maybe (Intensity, Color) -- ^ Set the background color, or keep the old one.
    , ansiBold        :: Maybe Bold               -- ^ Switch on boldness, or don’t do anything.
    , ansiItalics     :: Maybe Italicized         -- ^ Switch on italics, or don’t do anything.
    , ansiUnderlining :: Maybe Underlined         -- ^ Switch on underlining, or don’t do anything.
    } deriving (Eq, Ord, Show)

Instead of Maybe a, this should be a ternary Keep | Default | SetTo a just like vty's Attr does it with its MaybeDefault type.

Add the simple stack machine display functions from other annotated prettyprinters

This is a simpler kind of stack machine based rendering, when all we’re concerned about is printing output, pushing and popping based on certain SimpleDoc constructors. This won’t allow fancy stack machines that peek, double-push and what not.

e.g. annotated-wl-pprint contains:

displayDecoratedA :: (Applicative f, Monoid b)
                  => (String -> f b) -> (a -> f b) -> (a -> f b)
                  -> SimpleDoc a -> f b
displayDecoratedA str start end sd = display [] sd
  where display []        SEmpty              = pure mempty
        display stk       (SChar c x)         = (str [c]) <++> (display stk x)
        display stk       (SText l s x)       = (str s) <++> (display stk x)
        display stk       (SLine ind x)       = (str ('\n':indentation ind)) <++> (display stk x)
        display stk       (SAnnotStart ann x) = (start ann) <++> (display (ann:stk) x)
        display (ann:stk) (SAnnotStop x)      = (end ann) <++> (display stk x)

        -- malformed documents
        display []        (SAnnotStop _)      = error "stack underflow"
        display stk       SEmpty              = error "stack not consumed by rendering"

        (<++>) = liftA2 mappend

Convinced this is a good idea in #12.

prettyprinter-ansi-terminal: renderIO doesn't print trailing newline

Hello,
The documentation for the renderIO function in prettyprinter-ansi-terminal states that:

'renderIO' h sdoc = 'TL.hPutStrLn' h ('renderLazy' sdoc)

Running the following code:

import qualified Data.Text.Lazy.IO as TL
import qualified Data.Text.Lazy as TL
import Data.Text.Prettyprint.Doc
import Data.Text.Prettyprint.Doc.Render.Terminal
import System.IO (stdout)

main :: IO ()
main = do
     renderIO stdout sdoc
     TL.hPutStrLn stdout (renderLazy sdoc)
     putStrLn "done"
  where
    sdoc = layoutPretty defaultLayoutOptions $ pretty (TL.pack "test")

I get the output:

testtest
done

But I expect:

test
test
done

I'd be happy to submit a PR, but not sure if I should be fixing the documentation or the code.

Allow multiple formatting modes in a single AnsiTerminal value

It might be nice if a single AnsiTerminal value could fully describe all the possible formatting options in a single value. This would allow me to store set of these in a single record as a palette for formatting Doc values.

Additionally this would allow me to use reAnnotate to annotate a Doc with formatting that is more interesting than only applying a single effect like Bold or a single color.

For a similar example, see the vty package's Attr type http://hackage.haskell.org/package/vty-5.15.1/docs/Graphics-Vty-Attributes.html#t:Attr

Here's an example of what I'm trying to do. I have a Doc Ann value where data Ann = Key | Atom | Required | Description | Primitive

Next I'd like to write a function palette :: Ann -> AnsiTerminal. Currently I can only set one AnsiMode when I do this. Whether or not allowing Bold and Underlined is going to look good, this should probably be possible.

screen shot 2017-06-04 at 2 20 58 pm

Possible alternative (names and types certainly revisable):

data AnsiTerminal = AnsiTerminal
  { bold, underlined, italicized :: Bool
  , foreground, background :: Maybe (Intensity, Color)
  }

It might be better to use a three-way type instead of Bool and Maybe allowing for Set, Clear, and Ignore so that bold could be turned on, off, or left alone.

"atLeastAlign" (or should tupled etc *not* align)?

I have a problem at the moment with the interaction between hang and align. Fundamentally, the problem is that tupled calls align internally, but align can undo the indentation caused by hang. Consider the following:

λ> tupled ["aaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaa"]
( aaaaaaaaaaaaaaaaaaaa
, aaaaaaaaaaaaaaaaaa )
λ> hang 2 $ tupled ["aaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaa"]
( aaaaaaaaaaaaaaaaaaaa
, aaaaaaaaaaaaaaaaaa )

As we can see, tupled is not respecting the hanging. Options:

  1. Introduce atLeastAlign, which tries to align, but never decreases indentation.
  2. encloseSep should not call align.

Lifting Pretty to * -> * & * -> * -> *

Would you be open to a PR defining liftings of Pretty to * -> * and * -> * -> *, a la Show1 &c. from Data.Functor.Classes? It makes (decidable) recursive instances for Fix, Free, Cofree &c. much easier to define:

instance Pretty1 f => Pretty (Fix f) where
  pretty (Fix f) = liftPretty pretty prettyList f

If so, I’ll be happy to submit one 😊

Doc fixes

  • "Useful in particularly"
  • docs still mention TutorialExample module
  • the most common use for unAnnotate is not really "just before rendering"
  • unAnnotateS has an "S" too much in the link in its doc
  • duplicate imports in layoutSmart's doctests

Write huge benchmark

GHC has issues with the performance of its ASM code dumper, which uses the prettyprinter. A performance comparison with the other packages that just uses a crazy amount of lines would be nice, for example something that generates random LLVM code.

Empty old repo

Renamed it to prettyprinter-fork; it should be cleaned, with only an explanatory README left over.

negative indentation for current line

I'm generating some code which includes preprocessor macros which must begin on column 0.

It would be nice to be able to do:

guarded :: Text -> Doc () -> Doc ()
guarded g d = vcat
  [ indent minBound $ "#if defined(" <> pretty g <> ")"
  , d
  , indent minBound "#endif"
  ]

This obviously doesn't work because the negative indent could "reverse" over existing text. However it would be nice to have some kind of primitive which can "deindent" the current line as long as it's just erasing spaces.

This probably doesn't fit in the existing renderer at all though!

SimpleDocTree via SimpleDoc

Some other question that came up while I was reviewing your code. Why do you go via SimpleDoc for SimpleDocTree?

Would it not be possible to have the following two paths?

Doc -> StreamDoc -> TextLike
Doc -> TreeDoc -> HtmlLike

Consolidate special character modules

The main module predefines lots of characters, such as semi = ";", that clutter up the docs. Additionally, there is an (unshipped, but committed) Unicode module that contains non-ASCII definitions, such as »€«. These should be consolidated into something that’s easier to use and less noisy somehow.

Pretend that text is shorter than it actually is

I’m rendering text that I know will only be viewed in editors that use some sort of pretty symbols mode that replaces certain words by their unicode equivalents (e.g. forall is displayed as ). Therefore I’de like indentation to treat forall like a single character. Currently, I just use the internal Text constructor to do so and it works just fine but based on our discussion it seems like this is actually a safe operation so it would be nice if this was possible without resorting to internals.

Make available via Stackage

I'd love to make use of this library, but unless I'm mistaken it's not currently available via the Haskell Stack. From the project's page on Stackage:

This package is not currently in any snapshots. If you're interested in using it, we recommend adding it to Stackage Nightly. Doing so will make builds more reliable, and allow stackage.org to host generated Haddocks.

Some comments

Hi David,

I just saw the release of your package on hackage. Looks pretty nice! I did something similar a short while ago based on ekmetts wl-pprint-extras since I needed something working with annotations: https://github.com/minad/wl-pprint-annotated and https://github.com/minad/wl-pprint-console. However your package looks better and more ambitious, so I might switch over :)

Some comments:

  • There is no Functor instance, but instead unAnnotate/reAnnotate. I find it much nicer to have a Functor instance since this is what I expect. What about adding one?
  • You use Lists instead of Foldable everywhere probably to support type inference? I have usually a slight preference to the more general.
  • I looked only shortly into your different rendering mechanisms, but displayDecorated and displayDecoratedA functions are only provided by the compat . These are the functions I am using to render the SimpleDoc in https://github.com/minad/wl-pprint-console/blob/master/src/Text/PrettyPrint/Console/WL.hs. However for ANSI coloring I am using some intermediate representation of another library which tries to generate only minimal escape sequences.
  • There are some empty packages which essentially only reexport very few definitions. I think these confuse more than help when clicking through hackage or the source, like Internal.Type, ShowS etc.

Daniel

Missing build-dependency constraints

The package looks interesting, but the lack of constraints in the build-depends suggests that it's a throw away package. I'd be great if you were planning on maintaining this at all if you could reflect what versions of things it works with in the build-depends.

Pretty instance for Data.Scientific

Would you be open to add a Pretty instance for Data.Scientific ?

It can easily be added by users at the expense of an orphan instance and the use of the default implementation of pretty or the internal unsafeViaShow.

I can make a PR but I guess you might have concerns about the added dependency.

Unbounded page width behaves like 0 available columns

According to the documentation of softline:

softline behaves like space if the resulting output fits the page, otherwise like line.

... and this behaves correctly if the document is rendered using the defaultLayoutOptions (i.e. 80 columns):

$ nix-shell --packages 'haskellPackages.ghcWithPackages (pkgs: [ pkgs.prettyprinter ])'
...
$ ghc-pkg list | grep prettyprinter
    prettyprinter-1.1.1
$ ghci
...
Prelude> import Data.Text.Prettyprint.Doc
Prelude Data.Text.Prettyprint.Doc> :set -XOverloadedStrings
Prelude Data.Text.Prettyprint.Doc> let x = "a" <> softline <> "b"
Prelude Data.Text.Prettyprint.Doc> layoutSmart defaultLayoutOptions x
SChar 'a' (SChar ' ' (SChar 'b' SEmpty))

... but when I render with the Unbounded page width then a line is inserted:

Prelude Data.Text.Prettyprint.Doc> layoutSmart (defaultLayoutOptions { layoutPageWidth = Unbounded }) x
SChar 'a' (SLine 0 (SChar 'b' SEmpty))

... which is surprising since I would interpret Unbounded to mean that it would never run out of columns and therefore never need to insert a line according to the softline documentation. Also, the documentation for Unbounded seems to confirm that softline should not be inserting a line break:

Layouters should not introduce line breaks on their own

Can Boxes be integrated?

There's this great boxes library that does exactly what prettyprinter cannot. If only we could integrate them, so that Docs could be combined side by side as if they were Boxes!

What I mean is something around the lines of:

λ columns = pretty <$> ["l\na", "l\na", "f\na"]
λ boxToDoc . Boxes.hsep 1 Boxes.top $ docToBox <$> columns
l l f
a a a

− Which I achieved with:

docToBox :: Doc ann -> Box
docToBox = Boxes.vcat Boxes.left . fmap Boxes.text . lines . show

boxToDoc :: Box -> Doc ann
boxToDoc = pretty . Boxes.render

Consider adding pretty’s $$ operator, and maybe $+$

$$ has emptyDoc as the identity, so that "a" $$ emptyDoc $$ emptyDoc $$ b == a $$ "b", and both prettyprinter as

a
b

The pretty package offers this function, as well as a possibly useful sibling $+$.

Would this be a good new feature for prettyprinter? Pros: convenient, and GHC uses it; cons: I suspect it leads to carelessly glued-together documents where $$ just gets rid of the newlines, and then there’s also the slippery slope of maybe adding $$$ that keeps a single blank line between arguments, and so on.

Fix the nightly build

This seems to be an error in the Travis config and not in the library, so I’m not marking it as a bug

right align?

align implicitly uses left padding. Is there a way to do right padding instead? I don't see any way. Can this be added a a feature?

Consider a larger default ribbon width

I was confused for a while why my pretty-printing only ever took < 40 chars, until I realized the culprit is the defaultLayoutOptions ribbon width of 0.4. I think 1 is what I expected.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.