packages feed

ronn 1.0.0.0 → 1.1.0.0

raw patch · 13 files changed

+414/−521 lines, 13 filesdep +filepathdep +hspec-goldenPVP ok

version bump matches the API change (PVP)

Dependencies added: filepath, hspec-golden

API changes (from Hackage documentation)

- Ronn.AST: ManSection :: Int -> ManSection
- Ronn.AST: RonnBrackets :: RonnPart -> RonnPart
- Ronn.AST: RonnCode :: RonnPart -> RonnPart
- Ronn.AST: RonnConcat :: [RonnPart] -> RonnPart
- Ronn.AST: RonnDefinition :: RonnPart -> RonnLine -> Maybe [RonnContent] -> RonnDefinition
- Ronn.AST: RonnDefinitions :: [RonnDefinition] -> RonnContent
- Ronn.AST: RonnEphasis :: RonnPart -> RonnPart
- Ronn.AST: RonnGroups :: [RonnGroup] -> RonnContent
- Ronn.AST: RonnHeader :: Text -> RonnGroup
- Ronn.AST: RonnLine :: [RonnPart] -> RonnLine
- Ronn.AST: RonnLines :: [RonnLine] -> RonnGroup
- Ronn.AST: RonnParens :: RonnPart -> RonnPart
- Ronn.AST: RonnRaw :: Text -> RonnPart
- Ronn.AST: RonnRef :: ManRef -> RonnPart
- Ronn.AST: RonnSection :: Text -> [RonnContent] -> RonnSection
- Ronn.AST: RonnStrong :: RonnPart -> RonnPart
- Ronn.AST: RonnTitle :: ManRef -> [RonnPart] -> RonnGroup
- Ronn.AST: RonnUserInput :: RonnPart -> RonnPart
- Ronn.AST: RonnVariable :: RonnPart -> RonnPart
- Ronn.AST: [$sel:content:RonnDefinition] :: RonnDefinition -> Maybe [RonnContent]
- Ronn.AST: [$sel:content:RonnSection] :: RonnSection -> [RonnContent]
- Ronn.AST: [$sel:description:RonnDefinition] :: RonnDefinition -> RonnLine
- Ronn.AST: [$sel:name:RonnDefinition] :: RonnDefinition -> RonnPart
- Ronn.AST: [$sel:name:RonnSection] :: RonnSection -> Text
- Ronn.AST: [$sel:unwrap:RonnLine] :: RonnLine -> [RonnPart]
- Ronn.AST: data RonnContent
- Ronn.AST: data RonnDefinition
- Ronn.AST: data RonnGroup
- Ronn.AST: data RonnPart
- Ronn.AST: data RonnSection
- Ronn.AST: instance Data.String.IsString Ronn.AST.RonnContent
- Ronn.AST: instance Data.String.IsString Ronn.AST.RonnGroup
- Ronn.AST: instance Data.String.IsString Ronn.AST.RonnLine
- Ronn.AST: instance Data.String.IsString Ronn.AST.RonnPart
- Ronn.AST: instance GHC.Base.Monoid Ronn.AST.RonnPart
- Ronn.AST: instance GHC.Base.Semigroup Ronn.AST.RonnPart
- Ronn.AST: newtype ManSection
- Ronn.AST: newtype RonnLine
- Ronn.Env: Env :: [String] -> Maybe String -> Maybe String -> Maybe RonnLine -> Env
- Ronn.Env: [$sel:argument:Env] :: Env -> Maybe String
- Ronn.Env: [$sel:default_:Env] :: Env -> Maybe String
- Ronn.Env: [$sel:help:Env] :: Env -> Maybe RonnLine
- Ronn.Env: [$sel:vars:Env] :: Env -> [String]
- Ronn.Env: data Env
- Ronn.Env: envToDefinition :: Env -> RonnDefinition
- Ronn.Env: environmentSection :: [Env] -> RonnSection
- Ronn.Env: instance Ronn.Argument.HasArgument Ronn.Env.Env
- Ronn.Opt: Opt :: [Char] -> [String] -> Maybe String -> Maybe String -> Maybe RonnLine -> Opt
- Ronn.Opt: OptsAnd :: Opts -> Opts
- Ronn.Opt: OptsMany :: [Opts] -> Opts
- Ronn.Opt: OptsOne :: Opt -> Opts
- Ronn.Opt: OptsOr :: Opts -> Opts
- Ronn.Opt: [$sel:argument:Opt] :: Opt -> Maybe String
- Ronn.Opt: [$sel:default_:Opt] :: Opt -> Maybe String
- Ronn.Opt: [$sel:help:Opt] :: Opt -> Maybe RonnLine
- Ronn.Opt: [$sel:longs:Opt] :: Opt -> [String]
- Ronn.Opt: [$sel:shorts:Opt] :: Opt -> [Char]
- Ronn.Opt: data Opt
- Ronn.Opt: data Opts
- Ronn.Opt: instance Ronn.Argument.HasArgument Ronn.Opt.Opt
- Ronn.Opt: optToDefinition :: Opt -> RonnDefinition
- Ronn.Opt: optionsSection :: Opts -> RonnSection
- Ronn.Opt: optsToDefinitions :: Opts -> [RonnDefinition]
- Ronn.Opt: synopsisLine :: String -> Opts -> RonnLine
- Ronn.Opt: synopsisSection :: String -> Opts -> RonnSection
+ Ronn: definitionsSection :: Text -> [Definition] -> Section
+ Ronn: oneLineSection :: Text -> [Part] -> Section
+ Ronn: ronnFilePath :: Ronn -> FilePath
+ Ronn: seeAlsoSection :: [ManRef] -> Section
+ Ronn: synopsisSection :: Text -> [Part] -> Section
+ Ronn.AST: Brackets :: Part -> Part
+ Ronn.AST: Code :: Part -> Part
+ Ronn.AST: Concat :: [Part] -> Part
+ Ronn.AST: Definition :: Part -> Line -> Maybe [Content] -> Definition
+ Ronn.AST: Definitions :: [Definition] -> Content
+ Ronn.AST: Ephasis :: Part -> Part
+ Ronn.AST: Groups :: [Group] -> Content
+ Ronn.AST: Header :: Text -> Group
+ Ronn.AST: Line :: [Part] -> Line
+ Ronn.AST: Lines :: [Line] -> Group
+ Ronn.AST: ManSection1 :: ManSection
+ Ronn.AST: ManSection2 :: ManSection
+ Ronn.AST: ManSection3 :: ManSection
+ Ronn.AST: ManSection4 :: ManSection
+ Ronn.AST: ManSection5 :: ManSection
+ Ronn.AST: ManSection6 :: ManSection
+ Ronn.AST: ManSection7 :: ManSection
+ Ronn.AST: ManSection8 :: ManSection
+ Ronn.AST: Parens :: Part -> Part
+ Ronn.AST: Raw :: Text -> Part
+ Ronn.AST: Ref :: ManRef -> Part
+ Ronn.AST: Section :: Text -> [Content] -> Section
+ Ronn.AST: Strong :: Part -> Part
+ Ronn.AST: Title :: ManRef -> [Part] -> Group
+ Ronn.AST: UserInput :: Part -> Part
+ Ronn.AST: Variable :: Part -> Part
+ Ronn.AST: [$sel:content:Definition] :: Definition -> Maybe [Content]
+ Ronn.AST: [$sel:content:Section] :: Section -> [Content]
+ Ronn.AST: [$sel:description:Definition] :: Definition -> Line
+ Ronn.AST: [$sel:name:Definition] :: Definition -> Part
+ Ronn.AST: [$sel:name:Section] :: Section -> Text
+ Ronn.AST: [$sel:unwrap:Line] :: Line -> [Part]
+ Ronn.AST: data Content
+ Ronn.AST: data Definition
+ Ronn.AST: data Group
+ Ronn.AST: data ManSection
+ Ronn.AST: data Part
+ Ronn.AST: data Section
+ Ronn.AST: instance Data.String.IsString Ronn.AST.Content
+ Ronn.AST: instance Data.String.IsString Ronn.AST.Group
+ Ronn.AST: instance Data.String.IsString Ronn.AST.Line
+ Ronn.AST: instance Data.String.IsString Ronn.AST.Part
+ Ronn.AST: instance GHC.Base.Monoid Ronn.AST.Part
+ Ronn.AST: instance GHC.Base.Semigroup Ronn.AST.Part
+ Ronn.AST: newtype Line
+ Ronn.ManRef: ManRef :: Text -> ManSection -> ManRef
+ Ronn.ManRef: ManSection1 :: ManSection
+ Ronn.ManRef: ManSection2 :: ManSection
+ Ronn.ManRef: ManSection3 :: ManSection
+ Ronn.ManRef: ManSection4 :: ManSection
+ Ronn.ManRef: ManSection5 :: ManSection
+ Ronn.ManRef: ManSection6 :: ManSection
+ Ronn.ManRef: ManSection7 :: ManSection
+ Ronn.ManRef: ManSection8 :: ManSection
+ Ronn.ManRef: [$sel:name:ManRef] :: ManRef -> Text
+ Ronn.ManRef: [$sel:section:ManRef] :: ManRef -> ManSection
+ Ronn.ManRef: data ManRef
+ Ronn.ManRef: data ManSection
+ Ronn.ManRef: instance GHC.Classes.Eq Ronn.ManRef.ManRef
+ Ronn.ManRef: instance GHC.Classes.Eq Ronn.ManRef.ManSection
+ Ronn.ManRef: instance GHC.Classes.Ord Ronn.ManRef.ManRef
+ Ronn.ManRef: instance GHC.Classes.Ord Ronn.ManRef.ManSection
+ Ronn.ManRef: instance GHC.Enum.Bounded Ronn.ManRef.ManSection
+ Ronn.ManRef: instance GHC.Enum.Enum Ronn.ManRef.ManSection
+ Ronn.ManRef: instance GHC.Show.Show Ronn.ManRef.ManRef
+ Ronn.ManRef: instance GHC.Show.Show Ronn.ManRef.ManSection
+ Ronn.ManRef: manSectionNumber :: ManSection -> Int
+ Ronn.Render: ronnGroupToText :: Group -> Text
+ Ronn.Render: ronnLineToText :: Line -> Text
+ Ronn.Render: ronnPartToText :: Part -> Text
- Ronn.AST: Ronn :: ManRef -> [RonnPart] -> [RonnSection] -> Ronn
+ Ronn.AST: Ronn :: ManRef -> [Part] -> [Section] -> Ronn
- Ronn.AST: [$sel:description:Ronn] :: Ronn -> [RonnPart]
+ Ronn.AST: [$sel:description:Ronn] :: Ronn -> [Part]
- Ronn.AST: [$sel:sections:Ronn] :: Ronn -> [RonnSection]
+ Ronn.AST: [$sel:sections:Ronn] :: Ronn -> [Section]
- Ronn.Argument: addArgument :: HasArgument a => Text -> a -> RonnPart -> RonnPart
+ Ronn.Argument: addArgument :: HasArgument a => Text -> a -> Part -> Part

Files

CHANGELOG.md view
@@ -1,4 +1,12 @@-## [_Unreleased_](https://github.com/pbrisbin/ronn/compare/v1.0.0.0...main)+## [_Unreleased_](https://github.com/pbrisbin/ronn/compare/ronn-v1.1.0.0...main)++## [v1.1.0.0](https://github.com/pbrisbin/ronn/tree/ronn-v1.1.0.0)++- Export more `...ToText` functions+- Strengthen `ManRef` types+- Add `ronnFilePath`+- Un-prefix types (`RonnSection` renamed `Section`, etc)+- Remove `.Opt` and `.Env` intermediate types  ## [v1.0.0.0](https://github.com/pbrisbin/ronn/tree/v1.0.0.0) 
README.md view
@@ -1,93 +1,18 @@-## Ronn+## `ronn`  [![Hackage](https://img.shields.io/hackage/v/ronn.svg?style=flat)](https://hackage.haskell.org/package/ronn) [![Stackage Nightly](http://stackage.org/package/ronn/badge/nightly)](http://stackage.org/nightly/package/ronn) [![Stackage LTS](http://stackage.org/package/ronn/badge/lts)](http://stackage.org/lts/package/ronn)-[![CI](https://github.com/pbrisbin/ronn/actions/workflows/ci.yml/badge.svg)](https://github.com/pbrisbin/ronn/actions/workflows/ci.yml)  Describe and render Ronn documentation. -## Why Ronn?--Ronn is a [markdown-like format][ronn-format.7] for describing man-pages. Files-written in Ronn can be viewed as markdown (e.g. rendered on GitHub), or-converted to Roff or HTML using the `ronn` command-line.--[ronn-format.7]: https://github.com/apjanke/ronn-ng/blob/main/man/ronn-format.7.ronn--There are _many_ ways to author or produce man-pages in Roff and HTML formats-from a single source, but Ronn produces *by far* the highest-quality outputs-from the simplest source. Tools like `man2html` or even Pandoc often produce-sub-standard output in certain cases, or lack features such as cross-references.--Here is some example Ronn:--```ronn-ronn(1) -- example ronn man-page-================================--## SYNOPSIS--`ronn` [options] <argument>--## DESCRIPTION--This is an example man-page to show how rendering the AST Looks--## OPTIONS--  * `-h`, `--help`:-    Display this help--  * `-o`, `--output`=<FILE>:-    Output to `FILE`--## ENVIRONMENT--  * `HOME`:-    User's HOME directory--    Some further details:--      * foo:-        The foo--      * bar:-        The bar--## SEE ALSO--**markdown(7)**, **roff(7)**-```--Here is it rendered to Roff and opened with `man`:--![](./examples/roff.png)--And here it is rendered to HTML:--![](./examples/html.png)--This package contains the `Ronn` AST type and a rendering function. Companion-packages are planned to automatically convert any `optparse-applicative`,-`envparse`, or `opt-env-conf` `Parser` type into a `Ronn` value. This will allow-any application using one of these libraries to maintain high-quality man-pages-automatically.- ## Usage -For an example of building an AST by hand, and what the rendered `Text` looks-like, see the [test suite](./tests/Ronn/RenderSpec.hs)--Once you've created a `.ronn` file from the rendered `Text`, you can use the-[`ronn-ng`][ronn-ng] gem produce a high-quality man-page and HTML:--```console-% ronn --style toc --roff --html path/*.ronn-```+See [the test][test] and [the result][golden]. -[ronn-ng]: https://github.com/apjanke/ronn-ng+[test]: ./ronn/tests/Ronn/RenderSpec.hs+[golden]: ./doc/ronn.1.ronn  --- -[LICENSE](./LICENSE) | [CHANGELOG](./CHANGELOG.md)+[CHANGELOG](./CHANGELOG.md)
ronn.cabal view
@@ -1,6 +1,6 @@ cabal-version:   1.18 name:            ronn-version:         1.0.0.0+version:         1.1.0.0 license:         AGPL-3 maintainer:      Pat Brisbin homepage:        https://github.com/pbrisbin/ronn#readme@@ -22,8 +22,7 @@         Ronn         Ronn.Argument         Ronn.AST-        Ronn.Env-        Ronn.Opt+        Ronn.ManRef         Ronn.Render      hs-source-dirs:     src@@ -56,6 +55,7 @@     main-is:            Main.hs     hs-source-dirs:     tests     other-modules:+        Ronn.ManRefSpec         Ronn.RenderSpec         Paths_ronn @@ -76,7 +76,9 @@      build-depends:         base >=4.16.4.0 && <5,+        filepath >=1.4.2.2,         hspec >=2.9.7,+        hspec-golden >=0.2.1.0,         ronn,         text >=1.2.5.0 
src/Ronn.hs view
@@ -7,11 +7,54 @@ -- Stability   : experimental -- Portability : POSIX module Ronn-  ( module X+  ( module Ronn.AST+  , module Ronn.Render+  , ronnFilePath++    -- * Higher-level builders+  , synopsisSection+  , seeAlsoSection+  , oneLineSection+  , definitionsSection   ) where -import Ronn.AST as X-import Ronn.Env as X-import Ronn.Opt as X-import Ronn.Render as X+import Prelude++import Data.List (intersperse, sort)+import Data.Text (Text, unpack)+import Ronn.AST+import Ronn.Render++ronnFilePath :: Ronn -> FilePath+ronnFilePath ronn =+  unpack ref.name <> "." <> show (manSectionNumber ref.section) <> ".ronn"+ where+  ref = ronn.name++synopsisSection :: Text -> [Part] -> Section+synopsisSection name = oneLineSection "SYNOPSIS" . (Code (Raw name) :)++seeAlsoSection :: [ManRef] -> Section+seeAlsoSection refs =+  oneLineSection+    "SEE ALSO"+    [ mconcat $+        intersperse ", " $+          map Ref $+            sort refs+    ]++oneLineSection :: Text -> [Part] -> Section+oneLineSection name ps =+  Section+    { name+    , content = [Groups [Lines [Line ps]]]+    }++definitionsSection :: Text -> [Definition] -> Section+definitionsSection name definitions =+  Section+    { name+    , content = [Definitions definitions]+    }
src/Ronn/AST.hs view
@@ -8,12 +8,12 @@ -- Portability : POSIX module Ronn.AST   ( Ronn (..)-  , RonnSection (..)-  , RonnContent (..)-  , RonnDefinition (..)-  , RonnGroup (..)-  , RonnLine (..)-  , RonnPart (..)+  , Section (..)+  , Content (..)+  , Definition (..)+  , Group (..)+  , Line (..)+  , Part (..)      -- * References   , ManRef (..)@@ -25,89 +25,79 @@  import Data.String (IsString (..)) import Data.Text (Text, pack)+import Ronn.ManRef  data Ronn = Ronn   { name :: ManRef-  , description :: [RonnPart]-  , sections :: [RonnSection]+  , description :: [Part]+  , sections :: [Section]   } -data RonnSection = RonnSection+data Section = Section   { name :: Text-  , content :: [RonnContent]+  , content :: [Content]   } -data RonnContent-  = RonnDefinitions [RonnDefinition]-  | RonnGroups [RonnGroup]+data Content+  = Definitions [Definition]+  | Groups [Group] -instance IsString RonnContent where-  fromString = RonnGroups . pure . fromString+instance IsString Content where+  fromString = Groups . pure . fromString -data RonnDefinition = RonnDefinition-  { name :: RonnPart-  , description :: RonnLine+data Definition = Definition+  { name :: Part+  , description :: Line   -- ^ A line of nested description is required-  , content :: Maybe [RonnContent]+  , content :: Maybe [Content]   -- ^ More content can be optionally nested   } -data RonnGroup-  = RonnTitle ManRef [RonnPart]-  | RonnHeader Text-  | RonnLines [RonnLine]+data Group+  = Title ManRef [Part]+  | Header Text+  | Lines [Line] -instance IsString RonnGroup where-  fromString = RonnLines . pure . fromString+instance IsString Group where+  fromString = Lines . pure . fromString -newtype RonnLine = RonnLine-  { unwrap :: [RonnPart]+newtype Line = Line+  { unwrap :: [Part]   } -instance IsString RonnLine where-  fromString = RonnLine . pure . fromString+instance IsString Line where+  fromString = Line . pure . fromString -data RonnPart-  = -- | 'RonnConcat' joins 'RonnPart's without automaticaly inserting a space+data Part+  = -- | 'Concat' joins 'Part's without automaticaly inserting a space     --     -- The following expressions are equivalent:     ---    -- - @'ronnLineToText' $ 'RonnLine' [p1, p2]@-    -- - @'ronnLineToText' $ 'RonnLine' ['RonnConcat' [p1, " ", p2]]@-    -- - @'ronnLineToText' $ 'RonnLine' [p1 <> " " <> p2]@+    -- - @'ronnLineToText' $ 'Line' [p1, p2]@+    -- - @'ronnLineToText' $ 'Line' ['Concat' [p1, " ", p2]]@+    -- - @'ronnLineToText' $ 'Line' [p1 <> " " <> p2]@     --     -- Using the 'Semigroup' instance should be preferred, in case the AST     -- changes in the future.-    RonnConcat [RonnPart]-  | RonnCode RonnPart-  | RonnUserInput RonnPart-  | RonnStrong RonnPart-  | RonnVariable RonnPart-  | RonnEphasis RonnPart-  | RonnBrackets RonnPart-  | RonnParens RonnPart-  | RonnRef ManRef-  | RonnRaw Text--instance IsString RonnPart where-  fromString = RonnRaw . pack--instance Semigroup RonnPart where-  RonnConcat as <> RonnConcat bs = RonnConcat $ as <> bs-  RonnConcat as <> b = RonnConcat $ as <> [b]-  a <> RonnConcat bs = RonnConcat $ a : bs-  a <> b = RonnConcat [a, b]--instance Monoid RonnPart where-  mempty = RonnConcat []+    Concat [Part]+  | Code Part+  | UserInput Part+  | Strong Part+  | Variable Part+  | Ephasis Part+  | Brackets Part+  | Parens Part+  | Ref ManRef+  | Raw Text -data ManRef = ManRef-  { name :: Text-  , section :: ManSection-  }+instance IsString Part where+  fromString = Raw . pack --- TODO: enum?-newtype ManSection = ManSection Int+instance Semigroup Part where+  Concat as <> Concat bs = Concat $ as <> bs+  Concat as <> b = Concat $ as <> [b]+  a <> Concat bs = Concat $ a : bs+  a <> b = Concat [a, b] -manSectionNumber :: ManSection -> Int-manSectionNumber (ManSection n) = n+instance Monoid Part where+  mempty = Concat []
src/Ronn/Argument.hs view
@@ -21,7 +21,7 @@ class HasArgument a where   getArgument :: a -> Maybe String -addArgument :: HasArgument a => Text -> a -> RonnPart -> RonnPart+addArgument :: HasArgument a => Text -> a -> Part -> Part addArgument sep a p = case getArgument a of   Nothing -> p-  Just arg -> mconcat [p, RonnRaw sep, RonnVariable $ fromString arg]+  Just arg -> mconcat [p, Raw sep, Variable $ fromString arg]
− src/Ronn/Env.hs
@@ -1,50 +0,0 @@--- |------ Module      : Ronn.Env--- Copyright   : (c) 2024 Patrick Brisbin--- License     : AGPL-3--- Maintainer  : pbrisbin@gmail.com--- Stability   : experimental--- Portability : POSIX-module Ronn.Env-  ( Env (..)-  , envToDefinition-  , environmentSection-  ) where--import Prelude--import Data.List (intersperse)-import Data.Maybe (fromMaybe)-import Data.String (IsString (..))-import Ronn.AST-import Ronn.Argument--data Env = Env-  { vars :: [String]-  , argument :: Maybe String-  , default_ :: Maybe String-  , help :: Maybe RonnLine-  }--instance HasArgument Env where-  getArgument = (.argument)--envToDefinition :: Env -> RonnDefinition-envToDefinition env =-  RonnDefinition-    { name =-        addArgument "=" env $-          RonnConcat $-            intersperse "|" $-              map (RonnCode . fromString) env.vars-    , description = fromMaybe (RonnLine []) env.help-    , content = Nothing-    }--environmentSection :: [Env] -> RonnSection-environmentSection envs =-  RonnSection-    { name = "ENVIRONMENT"-    , content = [RonnDefinitions $ map envToDefinition envs]-    }
+ src/Ronn/ManRef.hs view
@@ -0,0 +1,42 @@+-- |+--+-- Module      : Ronn.ManRef+-- Copyright   : (c) 2024 Patrick Brisbin+-- License     : AGPL-3+-- Maintainer  : pbrisbin@gmail.com+-- Stability   : experimental+-- Portability : POSIX+module Ronn.ManRef+  ( ManRef (..)+  , ManSection (..)+  , manSectionNumber+  )+where++import Prelude++import Data.Ord+import Data.Text++data ManRef = ManRef+  { name :: Text+  , section :: ManSection+  }+  deriving stock (Eq, Show)++instance Ord ManRef where+  compare a b = comparing (.section) a b <> comparing (.name) a b++data ManSection+  = ManSection1+  | ManSection2+  | ManSection3+  | ManSection4+  | ManSection5+  | ManSection6+  | ManSection7+  | ManSection8+  deriving stock (Eq, Ord, Bounded, Enum, Show)++manSectionNumber :: ManSection -> Int+manSectionNumber = (+ 1) . fromEnum
− src/Ronn/Opt.hs
@@ -1,105 +0,0 @@--- |------ Module      : Ronn.Opt--- Copyright   : (c) 2024 Patrick Brisbin--- License     : AGPL-3--- Maintainer  : pbrisbin@gmail.com--- Stability   : experimental--- Portability : POSIX-module Ronn.Opt-  ( Opts (..)-  , optsToDefinitions-  , Opt (..)-  , optToDefinition-  , optionsSection-  , synopsisSection-  , synopsisLine-  ) where--import Prelude--import Data.List (intersperse)-import Data.Maybe (fromMaybe, isJust)-import Data.String (IsString (..))-import Ronn.AST-import Ronn.Argument--data Opts-  = OptsOr Opts-  | OptsAnd Opts-  | OptsMany [Opts]-  | OptsOne Opt--optsToDefinitions :: Opts -> [RonnDefinition]-optsToDefinitions = \case-  OptsOr opts -> optsToDefinitions opts-  OptsAnd opts -> optsToDefinitions opts-  OptsMany opts -> concatMap optsToDefinitions opts-  OptsOne opts -> [optToDefinition opts]--data Opt = Opt-  { shorts :: [Char]-  , longs :: [String]-  , argument :: Maybe String-  , default_ :: Maybe String-  , help :: Maybe RonnLine-  }--instance HasArgument Opt where-  getArgument = (.argument)--optToDefinition :: Opt -> RonnDefinition-optToDefinition opt =-  RonnDefinition-    { name = mconcat $ intersperse ", " $ optParts opt-    , description = fromMaybe (RonnLine []) opt.help-    , content = Nothing-    }--optionsSection :: Opts -> RonnSection-optionsSection opts =-  RonnSection-    { name = "OPTIONS"-    , content = [RonnDefinitions $ optsToDefinitions opts]-    }--synopsisSection-  :: String-  -- ^ Program name-  -> Opts-  -> RonnSection-synopsisSection name opts =-  RonnSection-    { name = "SYNOPSIS"-    , content = [RonnGroups [RonnLines [synopsisLine name opts]]]-    }--synopsisLine :: String -> Opts -> RonnLine-synopsisLine name = RonnLine . (RonnCode (fromString name) :) . go- where-  go :: Opts -> [RonnPart]-  go = \case-    OptsOr os -> [RonnBrackets $ mconcat $ intersperse " \\| " $ go os]-    OptsAnd os -> [RonnParens $ mconcat $ intersperse " " $ go os]-    OptsMany os -> concatMap go os-    OptsOne opt -> map (bracketize opt) $ optParts opt--  bracketize :: Opt -> RonnPart -> RonnPart-  bracketize opt-    | isJust opt.default_ = RonnBrackets-    | otherwise = id--optParts :: Opt -> [RonnPart]-optParts opt-  | null opt.shorts && null opt.longs-  , Just arg <- opt.argument =-      [RonnVariable $ fromString arg]-  | otherwise =-      map (addArgument " " opt . short) opt.shorts-        <> map (addArgument "=" opt . long) opt.longs- where-  short :: Char -> RonnPart-  short = RonnCode . fromString . ('-' :) . pure--  long :: String -> RonnPart-  long = RonnCode . fromString . ("--" <>)
src/Ronn/Render.hs view
@@ -8,6 +8,11 @@ -- Portability : POSIX module Ronn.Render   ( ronnToText++    -- * Rendering sub-parts, mostly useful in tests+  , ronnGroupToText+  , ronnLineToText+  , ronnPartToText   ) where  import Prelude@@ -20,23 +25,23 @@ ronnToText ronn =   T.unlines $     map ronnGroupToText $-      RonnTitle ronn.name ronn.description+      Title ronn.name ronn.description         : concatMap ronnSectionToGroups ronn.sections -ronnSectionToGroups :: RonnSection -> [RonnGroup]+ronnSectionToGroups :: Section -> [Group] ronnSectionToGroups section =-  RonnHeader section.name : concatMap (ronnContentToGroups 0) section.content+  Header section.name : concatMap (ronnContentToGroups 0) section.content -ronnContentToGroups :: Int -> RonnContent -> [RonnGroup]+ronnContentToGroups :: Int -> Content -> [Group] ronnContentToGroups indentLevel = \case-  RonnDefinitions defns ->+  Definitions defns ->     concatMap (ronnDefinitionToGroups $ indentLevel + 2) defns-  RonnGroups gs -> map (indentRonnGroup indentLevel) gs+  Groups gs -> map (indentRonnGroup indentLevel) gs -ronnDefinitionToGroups :: Int -> RonnDefinition -> [RonnGroup]+ronnDefinitionToGroups :: Int -> Definition -> [Group] ronnDefinitionToGroups indentLevel defn =-  [ RonnLines-      [ indentRonnLine indentLevel $ RonnLine ["* " <> defn.name <> ":"]+  [ Lines+      [ indentRonnLine indentLevel $ Line ["* " <> defn.name <> ":"]       , indentRonnLine nextIndentLevel defn.description       ]   ]@@ -45,38 +50,38 @@   nested = maybe [] (concatMap $ ronnContentToGroups nextIndentLevel) defn.content   nextIndentLevel = indentLevel + 2 -ronnGroupToText :: RonnGroup -> Text+ronnGroupToText :: Group -> Text ronnGroupToText = T.unlines . map ronnLineToText . ronnGroupToLines -ronnGroupToLines :: RonnGroup -> [RonnLine]+ronnGroupToLines :: Group -> [Line] ronnGroupToLines = \case-  RonnTitle ref description ->+  Title ref description ->     let nameLine = ronnNameLine ref description     in  [ nameLine-        , RonnLine [RonnRaw $ T.replicate (ronnLineLength nameLine) "="]+        , Line [Raw $ T.replicate (ronnLineLength nameLine) "="]         ]-  RonnHeader name -> [RonnLine ["##", RonnRaw name]]-  RonnLines ls -> ls+  Header name -> [Line ["##", Raw name]]+  Lines ls -> ls -ronnNameLine :: ManRef -> [RonnPart] -> RonnLine+ronnNameLine :: ManRef -> [Part] -> Line ronnNameLine ref =-  RonnLine . (RonnRaw (manRefToText ref) :) . ("--" :)+  Line . (Raw (manRefToText ref) :) . ("--" :) -ronnLineToText :: RonnLine -> Text+ronnLineToText :: Line -> Text ronnLineToText = T.unwords . map ronnPartToText . (.unwrap) -ronnPartToText :: RonnPart -> Text+ronnPartToText :: Part -> Text ronnPartToText = \case-  RonnConcat xs -> mconcat $ map ronnPartToText xs-  RonnCode x -> "`" <> ronnPartToText x <> "`"-  RonnUserInput x -> "`" <> ronnPartToText x <> "`"-  RonnStrong x -> "**" <> ronnPartToText x <> "**"-  RonnVariable x -> "<" <> ronnPartToText x <> ">"-  RonnEphasis x -> "_" <> ronnPartToText x <> "_"-  RonnBrackets x -> "[" <> ronnPartToText x <> "]"-  RonnParens x -> "(" <> ronnPartToText x <> ")"-  RonnRef ref -> ronnPartToText $ RonnStrong $ RonnRaw $ manRefToText ref-  RonnRaw x -> x+  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 =@@ -85,19 +90,19 @@     <> pack (show $ manSectionNumber ref.section)     <> ")" -ronnLineLength :: RonnLine -> Int+ronnLineLength :: Line -> Int ronnLineLength = T.length . ronnLineToText -indentRonnGroup :: Int -> RonnGroup -> RonnGroup+indentRonnGroup :: Int -> Group -> Group indentRonnGroup indentLevel = \case-  g@RonnTitle {} -> g-  g@RonnHeader {} -> g-  RonnLines ls -> RonnLines $ map (indentRonnLine indentLevel) ls+  g@Title {} -> g+  g@Header {} -> g+  Lines ls -> Lines $ map (indentRonnLine indentLevel) ls --- | Prepends the given number of spaces to the first 'RonnPart' of the line-indentRonnLine :: Int -> RonnLine -> RonnLine+-- | Prepends the given number of spaces to the first 'Part' of the line+indentRonnLine :: Int -> Line -> Line indentRonnLine n = \case-  RonnLine [] -> RonnLine []-  RonnLine (p : ps) -> RonnLine $ (spaces <> p) : ps+  Line [] -> Line []+  Line (p : ps) -> Line $ (spaces <> p) : ps  where-  spaces = RonnRaw $ pack $ replicate n ' '+  spaces = Raw $ pack $ replicate n ' '
tests/Main.hs view
@@ -1,1 +1,9 @@+-- |+--+-- Module      : Main+-- Copyright   : (c) 2024 Patrick Brisbin+-- License     : AGPL-3+-- Maintainer  : pbrisbin@gmail.com+-- Stability   : experimental+-- Portability : POSIX {-# OPTIONS_GHC -F -pgmF hspec-discover -Wno-missing-export-lists #-}
+ tests/Ronn/ManRefSpec.hs view
@@ -0,0 +1,49 @@+-- |+--+-- Module      : Ronn.ManRefSpec+-- Copyright   : (c) 2024 Patrick Brisbin+-- License     : AGPL-3+-- Maintainer  : pbrisbin@gmail.com+-- Stability   : experimental+-- Portability : POSIX+module Ronn.ManRefSpec+  ( spec+  ) where++import Prelude++import Data.List (sort)+import Ronn.ManRef+import Test.Hspec++spec :: Spec+spec = do+  describe "ManRef" $ do+    describe "Ord" $ do+      it "sorts section then name" $ do+        let+          unsorted =+            [ ManRef "groff_man" ManSection7+            , ManRef "man" ManSection1+            , ManRef "man2html" ManSection1+            , ManRef "mdoc" ManSection7+            , ManRef "groff" ManSection7+            , ManRef "man" ManSection7+            , ManRef "attributes" ManSection7+            ]+          sorted =+            [ ManRef "man" ManSection1+            , ManRef "man2html" ManSection1+            , ManRef "attributes" ManSection7+            , ManRef "groff" ManSection7+            , ManRef "groff_man" ManSection7+            , ManRef "man" ManSection7+            , ManRef "mdoc" ManSection7+            ]++        sort unsorted `shouldBe` sorted++  describe "ManSection" $ do+    describe "manSectionNumber" $ do+      it "can be converted to numbers 1 though 8" $ do+        map manSectionNumber [minBound .. maxBound] `shouldBe` [1 .. 8]
tests/Ronn/RenderSpec.hs view
@@ -1,5 +1,11 @@-{-# OPTIONS_GHC -Wno-ambiguous-fields #-}-+-- |+--+-- Module      : Ronn.RenderSpec+-- Copyright   : (c) 2024 Patrick Brisbin+-- License     : AGPL-3+-- Maintainer  : pbrisbin@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Ronn.RenderSpec   ( spec   ) where@@ -7,183 +13,153 @@ import Prelude  import Data.List (intersperse)-import Data.Text qualified as T-import Ronn.AST-import Ronn.Env-import Ronn.Opt-import Ronn.Render+import Data.Text (Text, unpack)+import Data.Text.IO qualified as T+import Ronn+import System.FilePath ((</>)) import Test.Hspec+import Test.Hspec.Golden  spec :: Spec spec = do   describe "ronnToText" $ do-    specify "a complete example" $ do-      let-        opts =-          OptsMany-            [ OptsOne $-                Opt-                  { shorts = ['h']-                  , longs = ["help"]-                  , argument = Nothing-                  , default_ = Just ""-                  , help = Just "Display this help"-                  }-            , OptsOr $-                OptsMany-                  [ OptsOne $-                      Opt-                        { shorts = []-                        , longs = ["debug"]-                        , argument = Nothing-                        , default_ = Nothing-                        , help = Just "Enable debug"-                        }-                  , OptsOne $-                      Opt-                        { shorts = []-                        , longs = ["trace"]-                        , argument = Nothing-                        , default_ = Nothing-                        , help = Just "Enable trace"-                        }-                  ]-            , OptsOne $-                Opt-                  { shorts = ['o']-                  , longs = ["output"]-                  , argument = Just "FILE"-                  , default_ = Just "-"-                  , help = Just $ RonnLine ["Output to", RonnVariable "FILE"]+    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"+                                  ]+                              ]+                          ]+                      ]                   }-            , OptsOne $-                Opt-                  { shorts = []-                  , longs = []-                  , argument = Just "INPUT"-                  , default_ = Nothing-                  , help = Just "Source 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."+                                  ]+                              ]+                          ]+                      ]                   }-            ]--        ronn =-          Ronn-            { name = ManRef "ronn" $ ManSection 1-            , description = ["example ronn man-page"]-            , sections =-                [ synopsisSection "ronn" opts-                , RonnSection-                    { name = "DESCRIPTION"-                    , content =-                        [ RonnGroups-                            [ RonnLines-                                [ RonnLine -- 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"                                     ]-                                ]-                            ]-                        ]-                    }-                , optionsSection opts-                , RonnSection-                    { name = "ENVIRONMENT"-                    , content =-                        [ RonnDefinitions-                            [ ( envToDefinition $-                                  Env-                                    { vars = ["HOME"]-                                    , argument = Nothing-                                    , default_ = Nothing-                                    , help = Just "User's HOME directory"-                                    }-                              )-                                { content =-                                    Just-                                      [ "Some further details:"-                                      , RonnDefinitions-                                          [ RonnDefinition-                                              { name = "foo"-                                              , description = "The foo"-                                              , content = Nothing-                                              }-                                          , RonnDefinition-                                              { name = "bar"-                                              , description = "The bar"-                                              , content = Nothing-                                              }-                                          ]-                                      ]-                                }-                            ]-                        ]-                    }-                , RonnSection-                    { name = "SEE ALSO"-                    , content =-                        [ RonnGroups-                            [ RonnLines-                                [ RonnLine-                                    [ RonnConcat $-                                        intersperse-                                          (RonnRaw ", ")-                                          [ RonnRef $ ManRef "markdown" $ ManSection 7-                                          , RonnRef $ ManRef "roff" $ ManSection 7-                                          ]+                              , 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+                                            }+                                        ]+                                    ]+                              }+                          ]+                      ]+                  }+              , Section+                  { name = "SEE ALSO"+                  , content =+                      [ Groups+                          [ Lines+                              [ Line+                                  [ mconcat $+                                      intersperse+                                        (Raw ", ")+                                        [ Ref $ ManRef "markdown" ManSection7+                                        , Ref $ ManRef "roff" ManSection7+                                        ]+                                  ]+                              ]+                          ]+                      ]+                  }+              ]+          } -      ronnToText ronn-        `shouldBe` T.unlines-          [ "ronn(1) -- example ronn man-page"-          , "================================"-          , ""-          , "## SYNOPSIS"-          , ""-          , "`ronn` [`-h`] [`--help`] [`--debug` \\| `--trace`] [`-o` <FILE>] [`--output`=<FILE>] <INPUT>"-          , ""-          , "## DESCRIPTION"-          , ""-          , "This is an example man-page to show how rendering the AST looks."-          , ""-          , "## OPTIONS"-          , ""-          , "  * `-h`, `--help`:"-          , "    Display this help"-          , ""-          , "  * `--debug`:"-          , "    Enable debug"-          , ""-          , "  * `--trace`:"-          , "    Enable trace"-          , ""-          , "  * `-o` <FILE>, `--output`=<FILE>:"-          , "    Output to <FILE>"-          , ""-          , "  * <INPUT>:"-          , "    Source input"-          , ""-          , "## ENVIRONMENT"-          , ""-          , "  * `HOME`:"-          , "    User's HOME directory"-          , ""-          , "    Some further details:"-          , ""-          , "      * foo:"-          , "        The foo"-          , ""-          , "      * bar:"-          , "        The bar"-          , ""-          , "## SEE ALSO"-          , ""-          , "**markdown(7)**, **roff(7)**"-          , ""-          ]+ronnGolden :: Ronn -> Golden Text+ronnGolden ronn =+  Golden+    { output = ronnToText ronn+    , encodePretty = unpack+    , writeToFile = T.writeFile+    , readFromFile = T.readFile+    , goldenFile = "../doc" </> ronnFilePath ronn+    , actualFile = Nothing+    , failFirstTime = False+    }