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 +9/−1
- README.md +5/−80
- ronn.cabal +5/−3
- src/Ronn.hs +48/−5
- src/Ronn/AST.hs +54/−64
- src/Ronn/Argument.hs +2/−2
- src/Ronn/Env.hs +0/−50
- src/Ronn/ManRef.hs +42/−0
- src/Ronn/Opt.hs +0/−105
- src/Ronn/Render.hs +44/−39
- tests/Main.hs +8/−0
- tests/Ronn/ManRefSpec.hs +49/−0
- tests/Ronn/RenderSpec.hs +148/−172
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` [](https://hackage.haskell.org/package/ronn) [](http://stackage.org/nightly/package/ronn) [](http://stackage.org/lts/package/ronn)-[](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`:----And here it is rendered to HTML:----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+ }