diff --git a/CHANGELOG.md b/CHANGELOG.md index e9138e2..495c3e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Helpers - `Ema.Helpers.PathTree` moved to separate package *pathtree*. - `Ema.Helpers.FileSystem` moved to separate package *unionmount*. + - `Ema.Helpers.Markdown` moved to separate package *commonmark-simple*. ## 0.4.0.0 -- 2022-01-19 diff --git a/docs/guide/helpers.md b/docs/guide/helpers.md index b9da40c..67237d4 100644 --- a/docs/guide/helpers.md +++ b/docs/guide/helpers.md @@ -1,10 +1,10 @@ --- order: 5 --- -# Helpers +# Howto -Beyond the model and route types, Ema leaves it up to you as to how to develop your site. The following are not *required* when using Ema; nevertheless they are useful inasmuch as they capture common patterns in writing a good static site: +Beyond the model and route types, Ema leaves it up to you as to how to develop your site. Here are some common aspects of writing a static site: -* [Blaze](guide/helpers/blaze.md) -- [We recommend--but not mandate--Tailwind for CSS and blaze-html as HTML DSL]{.item-intro} +* [Blaze HTML DSL](guide/helpers/blaze.md) -- [Use blaze-html with Tailwind CSS]{.item-intro} * [Working with files](guide/helpers/filesystem.md) -- [Use `unionmount` to support hot-reload on files]{.item-intro} * [Converting Markdown](guide/helpers/markdown.md) -- [Pointers on how to work with Markdown files]{.item-intro} \ No newline at end of file diff --git a/docs/guide/helpers/markdown.md b/docs/guide/helpers/markdown.md index 7d764cc..c42a1ac 100644 --- a/docs/guide/helpers/markdown.md +++ b/docs/guide/helpers/markdown.md @@ -8,14 +8,15 @@ There are quite a few packages to convert Markdown to HTML, - [Pandoc](https://hackage.haskell.org/package/pandoc) -- [Supports formats other than Markdown]{.item-intro} - [commonmark-hs](https://github.com/jgm/commonmark-hs) -- [Lightweight parser by the same author of Pandoc]{.item-intro} + - [commonmark-simple](https://hackage.haskell.org/package/commonmark-simple-0.1.0.0) -- [Simpler interface to the above, with frontmatter support]{.item-intro} - [mmark](https://github.com/mmark-md/mmark) -- [*Strict* Markdown parser]{.item-intro} -## Helper +## `commonmark-simple` -Ema provides a helper to parse Markdown files with YAML frontmatter, using commonmark-hs. If you are parsing front matter, you can use any type that has a [`FromYAML`](https://hackage.haskell.org/package/HsYAML-0.2.1.0/docs/Data-YAML.html#t:FromYAML) instance. +`commonmark-simple` uses commonmark-hs to provide a simpler API, along with front matter support. If you are parsing front matter, you can use any type that has a [`FromYAML`](https://hackage.haskell.org/package/HsYAML-0.2.1.0/docs/Data-YAML.html#t:FromYAML) instance. ```haskell -import qualified Ema.Helper.Markdown as Markdown +import qualified Commonmark.Simple as CS -- Front matter metadata can be any type with a `FromYAML` instance -- @@ -24,10 +25,10 @@ import qualified Ema.Helper.Markdown as Markdown type Metadata = Map Text Text -- Returns `Either Text (Metadata, Pandoc)` -Markdown.parseMarkdownWithFrontMatter @Metadata +CS.parseMarkdownWithFrontMatter @Metadata "test.md" "Hello *world*" ``` The template repo, as well as [Emanote](https://github.com/srid/emanote) (used to generate this site), uses this helper to parse Markdown files into Pandoc AST. Consult [the template repo's source code](https://github.com/srid/ema-template/blob/master/src/Main.hs) for details. -Note that with Ema you can get [hot reload](concepts/hot-reload.md) support for your Markdown files using [filesystem notifications](guide/helpers/filesystem.md). +Note that with Ema you can get [hot reload](concepts/hot-reload.md) support for your Markdown files using [the `unionmount` package](guide/helpers/filesystem.md). diff --git a/ema.cabal b/ema.cabal index 04eeaaf..fde1f08 100644 --- a/ema.cabal +++ b/ema.cabal @@ -1,6 +1,6 @@ cabal-version: 2.4 name: ema -version: 0.5.2.0 +version: 0.5.3.0 license: AGPL-3.0-only copyright: 2021 Sridhar Ratnakumar maintainer: srid@srid.ca @@ -68,15 +68,6 @@ library build-depends: , blaze-html , blaze-markup - , commonmark - , commonmark-extensions - , commonmark-pandoc - , megaparsec - , pandoc-types - , parsec - , parser-combinators - , time - , yaml if flag(with-examples) build-depends: time @@ -139,7 +130,6 @@ library if (flag(with-helpers) || flag(with-examples)) exposed-modules: Ema.Helper.Blaze - Ema.Helper.Markdown other-modules: Ema.App diff --git a/src/Ema/Helper/Markdown.hs b/src/Ema/Helper/Markdown.hs deleted file mode 100644 index a97d3c5..0000000 --- a/src/Ema/Helper/Markdown.hs +++ /dev/null @@ -1,132 +0,0 @@ --- | Helper to deal with Markdown files --- --- TODO: Publish this eventually to Hackage, along with wiki-link stuff from --- emanote (maybe as separate package). -module Ema.Helper.Markdown - ( -- Parsing - -- TODO: Publish to Hackage as commonmark-pandoc-simple? - parseMarkdownWithFrontMatter, - parseMarkdown, - fullMarkdownSpec, - -- Utilities - plainify, - ) -where - -import Commonmark qualified as CM -import Commonmark.Extensions qualified as CE -import Commonmark.Pandoc qualified as CP -import Control.Monad.Combinators (manyTill) -import Data.Aeson (FromJSON) -import Data.Yaml qualified as Y -import Text.Megaparsec qualified as M -import Text.Megaparsec.Char qualified as M -import Text.Pandoc.Builder qualified as B -import Text.Pandoc.Definition (Pandoc (..)) -import Text.Pandoc.Walk qualified as W - --- | Parse a Markdown file using commonmark-hs with all extensions enabled -parseMarkdownWithFrontMatter :: - forall meta m il bl. - ( FromJSON meta, - m ~ Either CM.ParseError, - bl ~ CP.Cm () B.Blocks, - il ~ CP.Cm () B.Inlines - ) => - CM.SyntaxSpec m il bl -> - -- | Path to file associated with this Markdown - FilePath -> - -- | Markdown text to parse - Text -> - Either Text (Maybe meta, Pandoc) -parseMarkdownWithFrontMatter spec fn s = do - (mMeta, markdown) <- partitionMarkdown fn s - mMetaVal <- first show $ (Y.decodeEither' . encodeUtf8) `traverse` mMeta - blocks <- first show $ join $ CM.commonmarkWith @(Either CM.ParseError) spec fn markdown - let doc = Pandoc mempty $ B.toList . CP.unCm @() @B.Blocks $ blocks - pure (mMetaVal, doc) - -parseMarkdown :: FilePath -> Text -> Either Text Pandoc -parseMarkdown fn s = do - cmBlocks <- first show $ join $ CM.commonmarkWith @(Either CM.ParseError) fullMarkdownSpec fn s - let blocks = B.toList . CP.unCm @() @B.Blocks $ cmBlocks - pure $ Pandoc mempty blocks - -type SyntaxSpec' m il bl = - ( Monad m, - CM.IsBlock il bl, - CM.IsInline il, - Typeable m, - Typeable il, - Typeable bl, - CE.HasEmoji il, - CE.HasStrikethrough il, - CE.HasPipeTable il bl, - CE.HasTaskList il bl, - CM.ToPlainText il, - CE.HasFootnote il bl, - CE.HasMath il, - CE.HasDefinitionList il bl, - CE.HasDiv bl, - CE.HasQuoted il, - CE.HasSpan il - ) - --- | GFM + official commonmark extensions -fullMarkdownSpec :: - SyntaxSpec' m il bl => - CM.SyntaxSpec m il bl -fullMarkdownSpec = - mconcat - [ CE.gfmExtensions, - CE.fancyListSpec, - CE.footnoteSpec, - CE.mathSpec, - CE.smartPunctuationSpec, - CE.definitionListSpec, - CE.attributesSpec, - CE.rawAttributeSpec, - CE.fencedDivSpec, - CE.bracketedSpanSpec, - CE.autolinkSpec, - CM.defaultSyntaxSpec, - -- as the commonmark documentation states, pipeTableSpec should be placed after - -- fancyListSpec and defaultSyntaxSpec to avoid bad results when parsing - -- non-table lines - CE.pipeTableSpec - ] - --- | Identify metadata block at the top, and split it from markdown body. --- --- FIXME: https://github.com/srid/neuron/issues/175 -partitionMarkdown :: FilePath -> Text -> Either Text (Maybe Text, Text) -partitionMarkdown = - parse (M.try splitP <|> fmap (Nothing,) M.takeRest) - where - separatorP :: M.Parsec Void Text () - separatorP = - void $ M.string "---" <* M.eol - splitP :: M.Parsec Void Text (Maybe Text, Text) - splitP = do - separatorP - a <- toText <$> manyTill M.anySingle (M.try $ M.eol *> separatorP) - b <- M.takeRest - pure (Just a, b) - parse :: M.Parsec Void Text a -> String -> Text -> Either Text a - parse p fn s = - first (toText . M.errorBundlePretty) $ - M.parse (p <* M.eof) fn s - --- | Convert Pandoc AST inlines to raw text. -plainify :: [B.Inline] -> Text -plainify = W.query $ \case - B.Str x -> x - B.Code _attr x -> x - B.Space -> " " - B.SoftBreak -> " " - B.LineBreak -> " " - B.RawInline _fmt s -> s - B.Math _mathTyp s -> s - -- Ignore the rest of AST nodes, as they are recursively defined in terms of - -- `Inline` which `W.query` will traverse again. - _ -> ""