Skip to content

Commit d5ed490

Browse files
committed
[#46] generalize extensions and renderer
1 parent 30742a3 commit d5ed490

File tree

5 files changed

+133
-79
lines changed

5 files changed

+133
-79
lines changed

Text/MMark/Extension.hs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,18 @@ module Text.MMark.Extension
8282
Block (..),
8383
CellAlign (..),
8484
blockTrans,
85+
blockTransM,
8586
blockRender,
87+
blockRenderM,
8688
Ois,
8789
getOis,
8890

8991
-- ** Inline-level manipulation
9092
Inline (..),
9193
inlineTrans,
94+
inlineTransM,
9295
inlineRender,
96+
inlineRenderM,
9397

9498
-- * Scanner construction
9599
scanner,
@@ -114,40 +118,62 @@ import Text.MMark.Util
114118
-- upwards. This has the benefit that the result of any transformation is
115119
-- final in the sense that sub-elements of resulting block won't be
116120
-- traversed again.
121+
blockTransM :: Monad m => (Bni -> m Bni) -> ExtensionT m
122+
blockTransM f = mempty {extBlockTrans = EndoM f}
123+
124+
-- | 'blockTransM' specialized to `Identity`.
117125
blockTrans :: (Bni -> Bni) -> Extension
118-
blockTrans f = mempty {extBlockTrans = Endo f}
126+
blockTrans f = blockTransM (pure . f)
119127

120128
-- | Create an extension that replaces or augments rendering of 'Block's of
121-
-- markdown document. The argument of 'blockRender' will be given the
122-
-- rendering function constructed so far @'Block' ('Ois', 'Html' ()) ->
123-
-- 'Html' ()@ as well as an actual block to render—@'Block' ('Ois', 'Html'
124-
-- ())@. The user can then decide whether to replace\/reuse that function to
125-
-- get the final rendering of the type @'Html' ()@.
129+
-- markdown document. The argument of 'blockRenderM' will be given the
130+
-- rendering function constructed so far @'Block' ('Ois', 'HtmlT' m ()) ->
131+
-- 'HtmlT' m ()@ as well as an actual block to render—@'Block' ('Ois', 'HtmlT'
132+
-- m ())@. The user can then decide whether to replace\/reuse that function to
133+
-- get the final rendering of the type @'HtmlT' m ()@.
126134
--
127-
-- The argument of 'blockRender' can also be thought of as a function that
135+
-- The argument of 'blockRenderM' can also be thought of as a function that
128136
-- transforms the rendering function constructed so far:
129137
--
130-
-- > (Block (Ois, Html ()) -> Html ()) -> (Block (Ois, Html ()) -> Html ())
138+
-- > (Block (Ois, HtmlT m ()) -> HtmlT m ()) -> (Block (Ois, HtmlT m ()) -> HtmlT m ())
131139
--
132140
-- See also: 'Ois' and 'getOis'.
141+
blockRenderM ::
142+
Monad m =>
143+
((Block (Ois, HtmlT m ()) -> HtmlT m ()) -> Block (Ois, HtmlT m ()) -> HtmlT m ()) ->
144+
ExtensionT m
145+
blockRenderM f = mempty {extBlockRender = Endo f}
146+
147+
-- | 'blockRenderM' specialized to `Identity`.
133148
blockRender ::
134149
((Block (Ois, Html ()) -> Html ()) -> Block (Ois, Html ()) -> Html ()) ->
135150
Extension
136-
blockRender f = mempty {extBlockRender = Render f}
151+
blockRender = blockRenderM
137152

138153
-- | Create an extension that performs a transformation on 'Inline'
139-
-- components in entire markdown document. Similarly to 'blockTrans' the
154+
-- components in entire markdown document. Similarly to 'blockTransM' the
140155
-- transformation is applied from the most deeply nested elements moving
141156
-- upwards.
157+
inlineTransM :: Monad m => (Inline -> m Inline) -> ExtensionT m
158+
inlineTransM f = mempty {extInlineTrans = EndoM f}
159+
160+
-- | 'blockTransM' specialized to `Identity`.
142161
inlineTrans :: (Inline -> Inline) -> Extension
143-
inlineTrans f = mempty {extInlineTrans = Endo f}
162+
inlineTrans f = inlineTransM (pure . f)
144163

145164
-- | Create an extension that replaces or augments rendering of 'Inline's of
146-
-- markdown document. This works like 'blockRender'.
165+
-- markdown document. This works like 'blockRenderM'.
166+
inlineRenderM ::
167+
Monad m =>
168+
((Inline -> HtmlT m ()) -> Inline -> HtmlT m ()) ->
169+
ExtensionT m
170+
inlineRenderM f = mempty {extInlineRender = Endo f}
171+
172+
-- | 'inlineRender' specialized to `Identity`.
147173
inlineRender ::
148174
((Inline -> Html ()) -> Inline -> Html ()) ->
149175
Extension
150-
inlineRender f = mempty {extInlineRender = Render f}
176+
inlineRender = inlineRenderM
151177

152178
-- | Create a 'L.Fold' from an initial state and a folding function.
153179
scanner ::

Text/MMark/Internal/Type.hs

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@
1818
--
1919
-- @since 0.0.8.0
2020
module Text.MMark.Internal.Type
21-
( MMark (..),
22-
Extension (..),
23-
Render (..),
21+
( MMark,
22+
MMarkT (..),
23+
Endo (..),
24+
EndoM (..),
25+
Extension,
26+
ExtensionT (..),
27+
Render,
28+
RenderT,
2429
Bni,
2530
Block (..),
2631
CellAlign (..),
@@ -32,6 +37,7 @@ module Text.MMark.Internal.Type
3237
where
3338

3439
import Control.DeepSeq
40+
import Control.Foldl (EndoM (..))
3541
import Data.Aeson
3642
import Data.Data (Data)
3743
import Data.Function (on)
@@ -42,26 +48,30 @@ import Data.Typeable (Typeable)
4248
import GHC.Generics
4349
import Lucid
4450
import Text.URI (URI (..))
51+
import Data.Functor.Identity
4552

4653
-- | Representation of complete markdown document. You can't look inside of
47-
-- 'MMark' on purpose. The only way to influence an 'MMark' document you
54+
-- 'MMarkT' on purpose. The only way to influence an 'MMarkT' document you
4855
-- obtain as a result of parsing is via the extension mechanism.
49-
data MMark = MMark
56+
data MMarkT m = MMark
5057
{ -- | Parsed YAML document at the beginning (optional)
5158
mmarkYaml :: Maybe Value,
5259
-- | Actual contents of the document
5360
mmarkBlocks :: [Bni],
5461
-- | Extension specifying how to process and render the blocks
55-
mmarkExtension :: Extension
62+
mmarkExtension :: ExtensionT m
5663
}
5764

58-
instance NFData MMark where
65+
-- | 'MMarkT' specialized to `Identity`.
66+
type MMark = MMarkT Identity
67+
68+
instance NFData (MMarkT m) where
5969
rnf MMark {..} = rnf mmarkYaml `seq` rnf mmarkBlocks
6070

6171
-- | Dummy instance.
6272
--
6373
-- @since 0.0.5.0
64-
instance Show MMark where
74+
instance Show (MMarkT m) where
6575
show = const "MMark {..}"
6676

6777
-- | An extension. You can apply extensions with 'Text.MMark.useExtension'
@@ -85,18 +95,21 @@ instance Show MMark where
8595
-- Here, @e0@ will be applied first, then @e1@, then @e2@. The same applies
8696
-- to expressions involving 'mconcat'—extensions closer to beginning of the
8797
-- list passed to 'mconcat' will be applied later.
88-
data Extension = Extension
98+
data ExtensionT m = Extension
8999
{ -- | Block transformation
90-
extBlockTrans :: Endo Bni,
100+
extBlockTrans :: EndoM m Bni,
91101
-- | Block render
92-
extBlockRender :: Render (Block (Ois, Html ())),
102+
extBlockRender :: RenderT m (Block (Ois, HtmlT m ())),
93103
-- | Inline transformation
94-
extInlineTrans :: Endo Inline,
104+
extInlineTrans :: EndoM m Inline,
95105
-- | Inline render
96-
extInlineRender :: Render Inline
106+
extInlineRender :: RenderT m Inline
97107
}
98108

99-
instance Semigroup Extension where
109+
-- | 'ExtensionT' specialized to `Identity`.
110+
type Extension = ExtensionT Identity
111+
112+
instance Monad m => Semigroup (ExtensionT m) where
100113
x <> y =
101114
Extension
102115
{ extBlockTrans = on (<>) extBlockTrans x y,
@@ -105,7 +118,7 @@ instance Semigroup Extension where
105118
extInlineRender = on (<>) extInlineRender x y
106119
}
107120

108-
instance Monoid Extension where
121+
instance Monad m => Monoid (ExtensionT m) where
109122
mempty =
110123
Extension
111124
{ extBlockTrans = mempty,
@@ -117,18 +130,13 @@ instance Monoid Extension where
117130

118131
-- | An internal type that captures the extensible rendering process we use.
119132
-- 'Render' has a function inside which transforms a rendering function of
120-
-- the type @a -> Html ()@.
133+
-- the type @a -> HtmlT m ()@.
121134
--
122135
-- @since 0.0.8.0
123-
newtype Render a = Render
124-
{runRender :: (a -> Html ()) -> a -> Html ()}
136+
type RenderT m a = Endo (a -> HtmlT m ())
125137

126-
instance Semigroup (Render a) where
127-
Render f <> Render g = Render (f . g)
128-
129-
instance Monoid (Render a) where
130-
mempty = Render id
131-
mappend = (<>)
138+
-- | 'RenderT' specialized to `Identity`.
139+
type Render a = RenderT Identity a
132140

133141
-- | A shortcut for the frequently used type @'Block' ('NonEmpty'
134142
-- 'Inline')@.
@@ -178,7 +186,7 @@ data Block a
178186
--
179187
-- @since 0.0.4.0
180188
Table (NonEmpty CellAlign) (NonEmpty (NonEmpty a))
181-
deriving (Show, Eq, Ord, Data, Typeable, Generic, Functor, Foldable)
189+
deriving (Show, Eq, Ord, Data, Typeable, Generic, Functor, Foldable, Traversable)
182190

183191
instance (NFData a) => NFData (Block a)
184192

Text/MMark/Parser.hs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
module Text.MMark.Parser
2222
( MMarkErr (..),
2323
parse,
24+
parseM
2425
)
2526
where
2627

@@ -94,14 +95,15 @@ data InlineState
9495

9596
-- | Parse a markdown document in the form of a strict 'Text' value and
9697
-- either report parse errors or return an 'MMark' document.
97-
parse ::
98+
parseM ::
99+
Monad m =>
98100
-- | File name (only to be used in error messages), may be empty
99101
FilePath ->
100102
-- | Input to parse
101103
Text ->
102104
-- | Parse errors or parsed document
103-
Either (ParseErrorBundle Text MMarkErr) MMark
104-
parse file input =
105+
Either (ParseErrorBundle Text MMarkErr) (MMarkT m)
106+
parseM file input =
105107
case runBParser pMMark file input of
106108
Left bundle -> Left bundle
107109
Right ((myaml, rawBlocks), defs) ->
@@ -133,6 +135,13 @@ parse file input =
133135
}
134136
}
135137

138+
-- | 'parseM' specialized to `Identity`.
139+
parse ::
140+
FilePath ->
141+
Text ->
142+
Either (ParseErrorBundle Text MMarkErr) MMark
143+
parse = parseM
144+
136145
----------------------------------------------------------------------------
137146
-- Block parser
138147

Text/MMark/Render.hs

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ where
2626

2727
import Control.Arrow
2828
import Control.Monad
29+
import Control.Monad.Trans
2930
import Data.Char (isSpace)
3031
import Data.Function (fix)
3132
import Data.List.NonEmpty (NonEmpty (..))
@@ -43,36 +44,46 @@ import Text.URI qualified as URI
4344
-- * to lazy 'Data.Taxt.Lazy.Text' with 'renderText'
4445
-- * to lazy 'Data.ByteString.Lazy.ByteString' with 'renderBS'
4546
-- * directly to file with 'renderToFile'
46-
render :: MMark -> Html ()
47-
render MMark {..} =
47+
renderM :: forall m. Monad m => MMarkT m -> HtmlT m ()
48+
renderM MMark {..} =
4849
mapM_ rBlock mmarkBlocks
4950
where
5051
Extension {..} = mmarkExtension
51-
rBlock =
52-
applyBlockRender extBlockRender
53-
. fmap rInlines
54-
. applyBlockTrans extBlockTrans
55-
rInlines =
56-
(mkOisInternal &&& mapM_ (applyInlineRender extInlineRender))
57-
. fmap (applyInlineTrans extInlineTrans)
52+
53+
rBlock :: Monad m => Bni -> HtmlT m ()
54+
rBlock x0 = do
55+
x1 <- lift $ applyBlockTrans extBlockTrans x0
56+
x2 <- lift $ traverse rInlines x1
57+
applyBlockRender extBlockRender x2
58+
59+
rInlines :: Monad m => NonEmpty Inline -> m (Ois, HtmlT m ())
60+
rInlines x0 = do
61+
x1 <- traverse (applyInlineTrans extInlineTrans) x0
62+
pure $ (mkOisInternal &&& mapM_ (applyInlineRender extInlineRender)) x1
63+
64+
-- | 'renderM' specialized to `Identity`.
65+
render :: MMark -> Html ()
66+
render = renderM
5867

5968
-- | Apply a 'Render' to a given @'Block' 'Html' ()@.
6069
--
6170
-- @since 0.0.8.0
6271
applyBlockRender ::
63-
Render (Block (Ois, Html ())) ->
64-
Block (Ois, Html ()) ->
65-
Html ()
66-
applyBlockRender r = fix (runRender r . defaultBlockRender)
72+
Monad m =>
73+
RenderT m (Block (Ois, HtmlT m ())) ->
74+
Block (Ois, HtmlT m ()) ->
75+
HtmlT m ()
76+
applyBlockRender r = fix (appEndo r . defaultBlockRender)
6777

6878
-- | The default 'Block' render.
6979
--
7080
-- @since 0.0.8.0
7181
defaultBlockRender ::
82+
Monad m =>
7283
-- | Rendering function to use to render sub-blocks
73-
(Block (Ois, Html ()) -> Html ()) ->
74-
Block (Ois, Html ()) ->
75-
Html ()
84+
(Block (Ois, HtmlT m ()) -> HtmlT m ()) ->
85+
Block (Ois, HtmlT m ()) ->
86+
HtmlT m ()
7687
defaultBlockRender blockRender = \case
7788
ThematicBreak ->
7889
hr_ [] >> newline
@@ -144,17 +155,18 @@ defaultBlockRender blockRender = \case
144155
-- | Apply a render to a given 'Inline'.
145156
--
146157
-- @since 0.0.8.0
147-
applyInlineRender :: Render Inline -> Inline -> Html ()
148-
applyInlineRender r = fix (runRender r . defaultInlineRender)
158+
applyInlineRender :: Monad m => RenderT m Inline -> Inline -> HtmlT m ()
159+
applyInlineRender r = fix (appEndo r . defaultInlineRender)
149160

150161
-- | The default render for 'Inline' elements.
151162
--
152163
-- @since 0.0.8.0
153164
defaultInlineRender ::
165+
Monad m =>
154166
-- | Rendering function to use to render sub-inlines
155-
(Inline -> Html ()) ->
167+
(Inline -> HtmlT m ()) ->
156168
Inline ->
157-
Html ()
169+
HtmlT m ()
158170
defaultInlineRender inlineRender = \case
159171
Plain txt ->
160172
toHtml txt
@@ -182,5 +194,5 @@ defaultInlineRender inlineRender = \case
182194
-- | HTML containing a newline.
183195
--
184196
-- @since 0.0.8.0
185-
newline :: Html ()
197+
newline :: Monad m => HtmlT m ()
186198
newline = "\n"

0 commit comments

Comments
 (0)