ronn 1.1.2.0 → 1.2.0.0
raw patch · 7 files changed
+215/−249 lines, 7 filesdep +prettyprinterPVP ok
version bump matches the API change (PVP)
Dependencies added: prettyprinter
API changes (from Hackage documentation)
- Ronn.AST: Definitions :: [Definition] -> Content
- Ronn.AST: Groups :: [Group] -> Content
- Ronn.AST: Header :: Text -> Group
- Ronn.AST: Lines :: [Line] -> Group
- Ronn.AST: Title :: ManRef -> [Part] -> Group
- Ronn.AST: [unwrap] :: Line -> [Part]
- Ronn.AST: data Group
- Ronn.AST: instance Data.String.IsString Ronn.AST.Group
- Ronn.AST: instance Data.String.IsString Ronn.AST.Line
- Ronn.AST: newtype Line
- Ronn.Render: ronnGroupToText :: Group -> Text
- Ronn.Render: ronnLineToText :: Line -> Text
- Ronn.Render: ronnPartToText :: Part -> Text
+ Ronn.AST: Defn :: Definition -> Content
+ Ronn.AST: Para :: [Part] -> Content
+ Ronn.AST: instance Prettyprinter.Internal.Pretty Ronn.AST.Content
+ Ronn.AST: instance Prettyprinter.Internal.Pretty Ronn.AST.Definition
+ Ronn.AST: instance Prettyprinter.Internal.Pretty Ronn.AST.Part
+ Ronn.AST: instance Prettyprinter.Internal.Pretty Ronn.AST.Section
+ Ronn.ManRef: instance Prettyprinter.Internal.Pretty Ronn.ManRef.ManRef
+ Ronn.ManRef: instance Prettyprinter.Internal.Pretty Ronn.ManRef.ManSection
+ Ronn.Render: ronnToDoc :: Ronn -> Doc ann
- Ronn.AST: Definition :: Part -> Line -> Maybe [Content] -> Definition
+ Ronn.AST: Definition :: Part -> [Part] -> Maybe [Content] -> Definition
- Ronn.AST: Line :: [Part] -> Line
+ Ronn.AST: Line :: [Part] -> Content
- Ronn.AST: [description] :: Definition -> Line
+ Ronn.AST: [description] :: Definition -> [Part]
Files
- CHANGELOG.md +7/−1
- ronn.cabal +2/−1
- src/Ronn.hs +6/−6
- src/Ronn/AST.hs +59/−27
- src/Ronn/ManRef.hs +7/−0
- src/Ronn/Render.hs +18/−88
- tests/Ronn/RenderSpec.hs +116/−126
CHANGELOG.md view
@@ -1,4 +1,10 @@-## [_Unreleased_](https://github.com/pbrisbin/ronn/compare/ronn-v1.1.1.0...main)+## [_Unreleased_](https://github.com/pbrisbin/ronn/compare/ronn-v1.2.0.0...main)++## [v1.2.0.0](https://github.com/pbrisbin/ronn/compare/ronn-v1.1.2.0...ronn-v1.2.0.0)++- Simplify AST, distinguish lines from paragraphs+- Reflow paragraphs (`Para`) at 80 columns+- Don't print empty trailing line ## [v1.1.2.0](https://github.com/pbrisbin/ronn/compare/ronn-v1.1.1.0...ronn-v1.1.2.0)
ronn.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.18 name: ronn-version: 1.1.2.0+version: 1.2.0.0 license: AGPL-3 maintainer: Pat Brisbin homepage: https://github.com/pbrisbin/ronn#readme@@ -44,6 +44,7 @@ build-depends: base >=4.16.4.0 && <5,+ prettyprinter >=1.7.1, text >=1.2.5.0 if impl(ghc >=9.8)
src/Ronn.hs view
@@ -71,22 +71,22 @@ seeAlsoSection refs = oneLineSection "SEE ALSO"- [ mconcat $- intersperse ", " $- map Ref $- sort refs+ [ mconcat+ $ intersperse ", "+ $ map Ref+ $ sort refs ] oneLineSection :: Text -> [Part] -> Section oneLineSection name ps = Section { name- , content = [Groups [Lines [Line ps]]]+ , content = [Line ps] } definitionsSection :: Text -> [Definition] -> Section definitionsSection name definitions = Section { name- , content = [Definitions definitions]+ , content = map Defn definitions }
src/Ronn/AST.hs view
@@ -11,8 +11,6 @@ , Section (..) , Content (..) , Definition (..)- , Group (..)- , Line (..) , Part (..) -- * References@@ -25,6 +23,8 @@ import Data.String (IsString (..)) import Data.Text (Text, pack)+import Data.Text qualified as T+import Prettyprinter import Ronn.ManRef data Ronn = Ronn@@ -38,47 +38,55 @@ , content :: [Content] } +instance Pretty Section where+ pretty s = vsep $ ("##" <+> pretty s.name) : map pretty s.content+ data Content- = Definitions [Definition]- | Groups [Group]+ = -- | Reflowed line+ Para [Part]+ | -- | Unbroken line+ Line [Part]+ | -- | Single definition+ Defn Definition instance IsString Content where- fromString = Groups . pure . fromString+ fromString = Para . pure . fromString +instance Pretty Content where+ pretty =+ (hardline <>) . \case+ Para ps -> reflow ps+ Line ps -> hsep $ map pretty ps+ Defn dn -> pretty dn+ data Definition = Definition { name :: Part- , description :: Line+ , description :: [Part] -- ^ A line of nested description is required , content :: Maybe [Content] -- ^ More content can be optionally nested } -data Group- = Title ManRef [Part]- | Header Text- | Lines [Line]--instance IsString Group where- fromString = Lines . pure . fromString--newtype Line = Line- { unwrap :: [Part]- }--instance IsString Line where- fromString = Line . pure . fromString+instance Pretty Definition where+ pretty d =+ indent 2+ $ "*"+ <+> align+ ( vsep+ [ pretty d.name <> ":"+ , reflow d.description+ ]+ )+ <> maybe mempty (nest 2 . foldMap ((hardline <>) . pretty)) d.content data Part = -- | 'Concat' joins 'Part's without automaticaly inserting a space --- -- The following expressions are equivalent:- --- -- - @'ronnLineToText' $ 'Line' [p1, p2]@- -- - @'ronnLineToText' $ 'Line' ['Concat' [p1, " ", p2]]@- -- - @'ronnLineToText' $ 'Line' [p1 <> " " <> p2]@+ -- - @'pretty' [p1, p2]@ (may be broken for reflow)+ -- - @'pretty' ['Concat' [p1, " ", p2]]@ (never broken) --- -- Using the 'Semigroup' instance should be preferred, in case the AST- -- changes in the future.+ -- '(<>)' is implemented with 'Concat' and should be preferred, to avoid+ -- unnecessary nesting. Concat [Part] | Code Part | UserInput Part@@ -101,3 +109,27 @@ instance Monoid Part where mempty = Concat []++instance Pretty Part where+ pretty = \case+ Concat ps -> foldMap pretty ps+ Code p -> "`" <> pretty p <> "`"+ UserInput p -> "`" <> pretty p <> "`"+ Strong p -> "**" <> pretty p <> "**"+ Variable p -> "<" <> pretty p <> ">"+ Ephasis p -> "_" <> pretty p <> "_"+ Brackets p -> "[" <> pretty p <> "]"+ Parens p -> "(" <> pretty p <> ")"+ Ref ref -> "**" <> pretty ref <> "**"+ Raw t -> pretty t++-- | Reflow a paragraph by tokenizing words and inserting softlines+--+-- This function will split any 'Raw' parts into multiple 'Raw' parts, one per+-- word, so that 'fillSep' will insert softlines between them.+reflow :: [Part] -> Doc ann+reflow = fillSep . map pretty . concatMap reword+ where+ reword = \case+ Raw t -> map Raw $ T.words t+ p -> [p]
src/Ronn/ManRef.hs view
@@ -17,6 +17,7 @@ import Data.Ord import Data.Text+import Prettyprinter (Pretty (..), parens) data ManRef = ManRef { name :: Text@@ -27,6 +28,9 @@ instance Ord ManRef where compare a b = comparing (.section) a b <> comparing (.name) a b +instance Pretty ManRef where+ pretty (ManRef n s) = pretty n <> parens (pretty s)+ data ManSection = ManSection1 | ManSection2@@ -37,6 +41,9 @@ | ManSection7 | ManSection8 deriving stock (Eq, Ord, Bounded, Enum, Show)++instance Pretty ManSection where+ pretty = pretty . manSectionNumber manSectionNumber :: ManSection -> Int manSectionNumber = (+ 1) . fromEnum
src/Ronn/Render.hs view
@@ -8,101 +8,31 @@ -- Portability : POSIX module Ronn.Render ( ronnToText-- -- * Rendering sub-parts, mostly useful in tests- , ronnGroupToText- , ronnLineToText- , ronnPartToText+ , ronnToDoc ) where import Prelude -import Data.Text (Text, pack)-import Data.Text qualified as T+import Data.Text (Text)+import Prettyprinter+import Prettyprinter.Render.Text (renderStrict) import Ronn.AST ronnToText :: Ronn -> Text-ronnToText ronn =- T.unlines $- map ronnGroupToText $- Title ronn.name ronn.description- : concatMap ronnSectionToGroups ronn.sections--ronnSectionToGroups :: Section -> [Group]-ronnSectionToGroups section =- Header section.name : concatMap (ronnContentToGroups 0) section.content--ronnContentToGroups :: Int -> Content -> [Group]-ronnContentToGroups indentLevel = \case- Definitions defns ->- concatMap (ronnDefinitionToGroups $ indentLevel + 2) defns- Groups gs -> map (indentRonnGroup indentLevel) gs--ronnDefinitionToGroups :: Int -> Definition -> [Group]-ronnDefinitionToGroups indentLevel defn =- [ Lines- [ indentRonnLine indentLevel $ Line ["* " <> defn.name <> ":"]- , indentRonnLine nextIndentLevel defn.description- ]- ]- <> nested- where- nested = maybe [] (concatMap $ ronnContentToGroups nextIndentLevel) defn.content- nextIndentLevel = indentLevel + 2--ronnGroupToText :: Group -> Text-ronnGroupToText = T.unlines . map ronnLineToText . ronnGroupToLines--ronnGroupToLines :: Group -> [Line]-ronnGroupToLines = \case- Title ref description ->- let nameLine = ronnNameLine ref description- in [ nameLine- , Line [Raw $ T.replicate (ronnLineLength nameLine) "="]- ]- Header name -> [Line ["##", Raw name]]- Lines ls -> ls--ronnNameLine :: ManRef -> [Part] -> Line-ronnNameLine ref =- Line . (Raw (manRefToText ref) :) . ("--" :)--ronnLineToText :: Line -> Text-ronnLineToText = T.unwords . map ronnPartToText . (.unwrap)--ronnPartToText :: Part -> Text-ronnPartToText = \case- Concat xs -> mconcat $ map ronnPartToText xs- Code x -> "`" <> ronnPartToText x <> "`"- UserInput x -> "`" <> ronnPartToText x <> "`"- Strong x -> "**" <> ronnPartToText x <> "**"- Variable x -> "<" <> ronnPartToText x <> ">"- Ephasis x -> "_" <> ronnPartToText x <> "_"- Brackets x -> "[" <> ronnPartToText x <> "]"- Parens x -> "(" <> ronnPartToText x <> ")"- Ref ref -> ronnPartToText $ Strong $ Raw $ manRefToText ref- Raw x -> x--manRefToText :: ManRef -> Text-manRefToText ref =- ref.name- <> "("- <> pack (show $ manSectionNumber ref.section)- <> ")"+ronnToText = renderStrict . layoutPretty defaultLayoutOptions . ronnToDoc -ronnLineLength :: Line -> Int-ronnLineLength = T.length . ronnLineToText+ronnToDoc :: Ronn -> Doc ann+ronnToDoc ronn =+ (<> hardline) -- ensure doc ends in final newline+ $ vsep+ $ punctuate hardline+ $ prettyTitle ronn+ : map pretty ronn.sections -indentRonnGroup :: Int -> Group -> Group-indentRonnGroup indentLevel = \case- g@Title {} -> g- g@Header {} -> g- Lines ls -> Lines $ map (indentRonnLine indentLevel) ls+prettyTitle :: Ronn -> Doc ann+prettyTitle ronn =+ underline '='+ $ pretty ronn.name <+> "--" <+> hsep (map pretty ronn.description) --- | Prepends the given number of spaces to the first 'Part' of the line-indentRonnLine :: Int -> Line -> Line-indentRonnLine n = \case- Line [] -> Line []- Line (p : ps) -> Line $ (spaces <> p) : ps- where- spaces = Raw $ pack $ replicate n ' '+underline :: Char -> Doc ann -> Doc ann+underline c x = width x $ \w -> hardline <> pretty (replicate w c)
tests/Ronn/RenderSpec.hs view
@@ -23,134 +23,124 @@ spec :: Spec spec = do describe "ronnToText" $ do- specify "a complete example" $- ronnGolden $- Ronn- { name = ManRef "ronn" ManSection1- , description = ["example ronn man-page"]- , sections =- [ Section- { name = "SYNOPSIS"- , content =- [ Groups- [ Lines- [ Line- [ Code "ronn"- , Brackets $ Code "-h"- , Brackets $ Code "--help"- , Brackets $ mconcat [Code "--debug", "\\|", Code "--trace"]- , Brackets $ mconcat [Code "-o", " ", Variable "FILE"]- , Brackets $ mconcat [Code "--output", "=", Variable "FILE"]- , Variable "INPUT"- ]- ]- ]- ]- }- , Section- { name = "DESCRIPTION"- , content =- [ Groups- [ Lines- [ Line -- test unwords-like behavior- [ "This is an"- , "example man-page to show"- , "how rendering the AST looks."- ]- ]- ]- ]- }- , Section- { name = "OPTIONS"- , content =- [ Definitions- [ Definition- { name =- mconcat- [ Code "-h"- , ", "- , Code "--help"- ]- , description = "Display this help"- , content = Nothing- }- , Definition- { name = Code "--debug"- , description = "Enable debug"- , content = Nothing- }- , Definition- { name = Code "--trace"- , description = "Enable trace"- , content = Nothing- }- , Definition- { name =- mconcat- [ Code "-o"- , ", "- , Code "--output"- , "="- , Variable "FILE"- ]- , description = Line ["Output to", Variable "FILE"]- , content = Nothing- }- , Definition- { name = Variable "INPUT"- , description = "Source input"- , content = Nothing- }- ]- ]- }- , Section- { name = "ENVIRONMENT"- , content =- [ Definitions- [ Definition- { name = Code "HOME"- , description = "User's HOME directory"- , content =- Just- [ "Some further details:"- , Definitions- [ Definition- { name = "foo"- , description = "The foo"- , content = Nothing- }- , Definition- { name = "bar"- , description = "The bar"- , content = Nothing- }- ]- ]- }- ]+ specify "a complete example"+ $ ronnGolden+ $ Ronn+ { name = ManRef "ronn" ManSection1+ , description = ["example ronn man-page"]+ , sections =+ [ Section+ { name = "SYNOPSIS"+ , content =+ [ Line+ [ Code "ronn"+ , Brackets $ Code "-h"+ , Brackets $ Code "--help"+ , Brackets $ mconcat [Code "--debug", "\\|", Code "--trace"]+ , Brackets $ mconcat [Code "-o", " ", Variable "FILE"]+ , Brackets $ mconcat [Code "--output", "=", Variable "FILE"]+ , Variable "INPUT"+ ]+ ]+ }+ , Section+ { name = "DESCRIPTION"+ , content =+ [ Para+ [ "This is an"+ , "example man-page to show"+ , "how rendering the AST looks."+ , "This description wouldn't normally be this"+ , "long, but we want to verify the"+ , "implementation reflows long lines."+ ]+ ]+ }+ , Section+ { name = "OPTIONS"+ , content =+ map+ Defn+ [ Definition+ { name =+ mconcat+ [ Code "-h"+ , ", "+ , Code "--help"+ ]+ , description = ["Display this help"]+ , content = Nothing+ }+ , Definition+ { name = Code "--debug"+ , description = ["Enable debug"]+ , content = Nothing+ }+ , Definition+ { name = Code "--trace"+ , description = ["Enable trace"]+ , content = Nothing+ }+ , Definition+ { name =+ mconcat+ [ Code "-o"+ , ", "+ , Code "--output"+ , "="+ , Variable "FILE"+ ]+ , description = ["Output to", Variable "FILE"]+ , content = Nothing+ }+ , Definition+ { name = Variable "INPUT"+ , description = ["Source input"]+ , content = Nothing+ } ]- }- , Section- { name = "SEE ALSO"- , content =- [ Groups- [ Lines- [ Line- [ mconcat $- intersperse- (Raw ", ")- [ Ref $ ManRef "markdown" ManSection7- , Ref $ ManRef "roff" ManSection7- ]- ]+ }+ , Section+ { name = "ENVIRONMENT"+ , content =+ [ Defn+ Definition+ { name = Code "HOME"+ , description = ["User's HOME directory"]+ , content =+ Just+ [ "Some further details:"+ , Defn+ Definition+ { name = "foo"+ , description = ["The foo"]+ , content = Nothing+ }+ , Defn+ Definition+ { name = "bar"+ , description = ["The bar"]+ , content = Nothing+ }+ ]+ }+ ]+ }+ , Section+ { name = "SEE ALSO"+ , content =+ [ Para+ [ mconcat+ $ intersperse+ (Raw ", ")+ [ Ref $ ManRef "markdown" ManSection7+ , Ref $ ManRef "roff" ManSection7 ]- ]- ]- }- ]- }+ ]+ ]+ }+ ]+ } ronnGolden :: Ronn -> Golden Text ronnGolden ronn =