mmark-cli (empty) → 0.0.1.0
raw patch · 6 files changed
+647/−0 lines, 6 filesdep +aesondep +basedep +bytestringsetup-changed
Dependencies added: aeson, base, bytestring, directory, gitrev, lucid, megaparsec, mmark, mmark-ext, optparse-applicative, skylighting, stache, text, unordered-containers
Files
- CHANGELOG.md +3/−0
- LICENSE.md +28/−0
- README.md +315/−0
- Setup.hs +6/−0
- app/Main.hs +246/−0
- mmark-cli.cabal +49/−0
+ CHANGELOG.md view
@@ -0,0 +1,3 @@+## MMark CLI 0.0.1.0++* Initial release.
+ LICENSE.md view
@@ -0,0 +1,28 @@+Copyright © 2018 Mark Karpov++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice,+ this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name Mark Karpov nor the names of contributors may be used to+ endorse or promote products derived from this software without specific+ prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS “AS IS” AND ANY EXPRESS+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN+NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,315 @@+# MMark CLI++[](http://opensource.org/licenses/BSD-3-Clause)+[](https://hackage.haskell.org/package/mmark-cli)+[](http://stackage.org/nightly/package/mmark-cli)+[](http://stackage.org/lts/package/mmark-cli)+[](https://travis-ci.org/mmark-md/mmark-cli)++* [Templates](#tepmlates)+* [Extensions](#extensions)+ * [Comment paragraph](#comment-paragraph)+ * [Font Awesome](#font-awesome)+ * [Kbd tags](#kbd-tags)+ * [Link targets](#link-targets)+ * [Email obfuscation](#email-obfuscation)+ * [Punctuation prettifier](#punctuation-prettifier)+ * [Skylighting](#skylighting)+ * [Table of contents](#table-of-contents)+* [Contribution](#contribution)+* [License](#license)++This is a command line application serving as an interface to MMark markdown+processor.++```+mmark—command line interface to MMark markdown processor++Usage: mmark [-v|--version] [-i|--ifile IFILE] [-o|--ofile OFILE] [-j|--json]+ [-t|--template FILE] [--ext-comment PREFIX] [--ext-font-awesome]+ [--ext-kbd] [--ext-link-target] [--ext-obfuscate-email CLASS]+ [--ext-punctuation] [--ext-skylighting] [--ext-toc RANGE]+ Command line interface to MMark markdown processor++Available options:+ -h,--help Show this help text+ -v,--version Print version of the program+ -i,--ifile IFILE Read markdown source from this file (otherwise read+ from stdin)+ -o,--ofile OFILE Save rendered HTML document to this file (otherwise+ write to stdout)+ -j,--json Output parse errors and result in JSON format+ -t,--template FILE Use the template located at this path+ --ext-comment PREFIX Remove paragraphs that start with the given prefix+ --ext-font-awesome Enable support for inserting font awesome icons+ --ext-kbd Enable support for wrapping things in kbd tags+ --ext-link-target Enable support for specifying link targets+ --ext-obfuscate-email CLASS+ Obfuscate email addresses assigning the specified+ class+ --ext-punctuation Enable punctuation prettifier+ --ext-skylighting Enable syntax highlighting of code snippets with+ Skylighting+ --ext-toc RANGE Enable generation of table of contents using the+ supplied range of headers to include, e.g. "1-6" or+ "2-4"+```++## Templates++By using the `--template` argument, it's possible to create a standalone+HTML page. The templating system we use is+[Mustache](https://mustache.github.io/mustache.5.html), as implemented by+the [stache](https://hackage.haskell.org/package/stache) library. The+library conforms to the version 1.1.3 of the official [Mustache+specification](https://github.com/mustache/spec), but does not implement+lambdas (which is an optional feature is the specification) for simplify and+other technical reasons we won't touch here.++If markdown source file has a YAML section, its contents will be provided as+context for rendering of the template. In addition to that, a new top-level+value bound to the variable named `output` will be available. That variable+contains the HTML rendition of markdown document. It's best to interpolate+it without HTML escaping, like so: `{{& output }}`.++## Extensions++Here we list how to use the available extensions. The extensions come from+the [`mmark-ext`](https://hackage.haskell.org/package/mmark-ext) package.++### Comment paragraph++* Option: `--ext-comment PREFIX`++This extension removes paragraphs that start with the given `PREFIX`. For+example:++```+$ mmark --ext-comment REM+First.++REM Second.++Third.+----------------------- Control-D+<p>First.</p>+<p>Third.</p>+```++### Font awesome++* Option: `--ext-font-awesome`++This allows to turn autolinks with `fa` scheme into font awesome icons:++```+$ mmark --ext-font-awesome+Here is the user icon: <fa:user>.++A more interesting example: <fa:quote-left/3x/pull-left/border>.+----------------------- Control-D+<p>Here is the user icon: <span class="fa fa-user"></span>.</p>+<p>A more interesting example:+ <span class="fa fa-quote-left fa-3x fa-pull-left fa-border"></span>.+</p>+```++In general, all path components in URIs that go after the name of icon will+be prefixed with `"fa-"` and added as classes, so you can do a lot of fancy+stuff, see <http://fontawesome.io/examples/>.++### Kbd tags++* Option: `--ext-kbd`++Introduce kbd tags into resulting HTML document by wrapping content in links+with URL with kbd scheme. For example:++```+$ mmark --ext-kbd+To enable that mode press [Ctrl+A][kbd].++[kbd]: kbd:+----------------------- Control-D+<p>To enable that mode press <kbd>Ctrl+A</kbd>.</p>+```++The use of reference-style links seems more aesthetically pleasant to the+author, but you can of course do something like this instead:++```+To enable that mode press [Ctrl+A](kbd:).+```++### Link targets++* Option: `--ext-link-target`++When title of a link starts with the word `"_blank"`, `"_self"`,+`"_parent"`, or `"_top"`, it's stripped from title (as well as all+whitespace after it) and added as the value of target attribute of the+resulting link. For example:++```+$ mmark --ext-kbd+This [link](/url '_blank My title') opens in new tab.+----------------------- Control-D+<p>This <a href="/url" title="My title" target="_blank">link</a>+opens in new tab.</p>+```++### Email obfuscation++* Option: `--obfuscate-email CLASS`++This extension makes email addresses in autolinks be rendered as something+like this:++```+[mark@arch ~]$ mmark --ext-obfuscate-email protected-email+Send all your spam to <someone@example.org>, if you can!+----------------------- Control-D+<p>Send all your spam to+ <a href="javascript:void(0)"+ class="protected-email"+ data-email="someone@example.org">+ Enable JavaScript to see this email</a>, if you can!+</p>+```++You'll also need to include jQuery and this bit of JS code for the magic to+work:++```java-script+$(document).ready(function () {+ $(".protected-email").each(function () {+ var item = $(this);+ var email = item.data('email');+ item.attr('href', 'mailto:' + email);+ item.html(email);+ });+});+```++### Punctuation prettifier++* Option: `--ext-punctuation`++This makes MMark prettify punctuation (only affects plain text in inlines),+the effect is the following:++* Replace `...` with ellipsis `…`+* Replace `---` with em-dash `—`+* Replace `--` with en-dash `–`+* Replace `"` with left double quote `“` when previous character was a space+ character, otherwise replace it with right double quote `”`+* Replace `'` with left single quote `‘` when previous character was a space+ character, otherwise replace it with right single quote `’` aka apostrophe++For example (not sure this is the correct punctuation to use here, but it+demonstrates the effect):++```+[mark@arch ~]$ mmark --ext-punctuation+Something---we don't know what, happened...+----------------------- Control-D+<p>Something—we don’t know what, happened…</p>+```++### Skylighting++* Option: `--ext-skylighting`++Use the [skylighting](https://hackage.haskell.org/package/skylighting)+package to render code blocks with info strings that result in a successful+lookup from syntax table that comes with the library.++The resulting markup is wrapped with spans as described in the docs for+[`formatHtmlInline`](https://hackage.haskell.org/package/skylighting/docs/Skylighting-Format-HTML.html#v:formatHtmlInline).++Example:++````+[mark@arch ~]$ mmark --ext-skylighting+Some Haskell:++```haskell+main :: IO ()+main = return ()+```+----------------------- Control-D+<p>Some Haskell:</p>+<div class="sourceCode">+ <pre class="sourceCode">+ <code class="sourceCode">+ <a class="sourceLine" id="1" data-line-number="1">+ <span class="ot">main ::</span> <span class="dt">IO</span> ()+ </a>+ <a class="sourceLine" id="2" data-line-number="2">main <span class="fu">=</span> return ()</a>+ </code>+ </pre>+</div>+````++### Table of contents++* Option: `--ext-toc RANGE`++Replace the code block with info string `"toc"` by table of contents+assembled from headings with levels from `N` to `M`, where `N-M` is `RANGE`.++For example:++````+[mark@arch ~]$ mmark --ext-toc 2-4+# Story of my life++```toc+```++## Charpter 1++Foo.++## Chapter 2++Bar.++### Something++Baz.+----------------------- Control-D+<h1 id="story-of-my-life">Story of my life</h1>+<ul>+<li>+<a href="#charpter-1">Charpter 1</a>+</li>+<li>+<a href="#chapter-2">Chapter 2</a>+<ul>+<li>+<a href="#something">Something</a>+</li>+</ul>+</li>+</ul>+<h2 id="charpter-1">Charpter 1</h2>+<p>Foo.</p>+<h2 id="chapter-2">Chapter 2</h2>+<p>Bar.</p>+<h3 id="something">Something</h3>+<p>Baz.</p>+````++## Contribution++Issues, bugs, and questions may be reported in [the GitHub issue tracker for+this project](https://github.com/mmark-md/mmark-cli/issues).++Pull requests are also welcome and will be reviewed quickly.++## License++Copyright © 2018 Mark Karpov++Distributed under BSD 3 clause license.
+ Setup.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import Distribution.Simple++main :: IO ()+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,246 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TupleSections #-}++module Main (main) where++import Control.Applicative+import Control.Monad+import Data.Aeson ((.=), Value (..))+import Data.List (intercalate)+import Data.List.NonEmpty (NonEmpty (..))+import Data.Text (Text)+import Data.Version (showVersion)+import Data.Void+import Development.GitRev+import Options.Applicative hiding (ParseError)+import Paths_mmark_cli (version)+import Skylighting (defaultFormatOpts)+import System.Directory (makeAbsolute)+import System.Exit (exitFailure)+import Text.MMark (MMarkErr)+import Text.Megaparsec (Parsec, ParseError, SourcePos (..))+import qualified Data.Aeson as Aeson+import qualified Data.ByteString.Lazy.Char8 as BL+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T+import qualified Data.Text.IO as T+import qualified Data.Text.Lazy as TL+import qualified Lucid as L+import qualified Text.MMark as MMark+import qualified Text.MMark.Extension.Common as Ext+import qualified Text.Megaparsec as M+import qualified Text.Megaparsec.Char as MC+import qualified Text.Megaparsec.Char.Lexer as MCL+import qualified Text.Mustache as U++-- | Entry point of the program.++main :: IO ()+main = do+ Opts {..} <- execParser optsParserInfo+ (mdFileName, mdInput) <-+ case optInputFile of+ Nothing -> ("<stdin>",) <$> T.getContents+ Just file -> do+ absFile <- makeAbsolute file+ (absFile,) <$> T.readFile absFile+ case MMark.parse mdFileName mdInput of+ Left errs -> do+ if optJson+ then (BL.putStrLn . Aeson.encode . parseErrorsJson) errs+ else putStr (MMark.parseErrorsPretty mdInput errs)+ exitFailure+ Right doc -> do+ let exts = mconcat+ [ g optExtComment Ext.commentParagraph+ , f optExtFontAwesome Ext.fontAwesome+ , f optExtKbd Ext.kbd+ , f optExtLinkTarget Ext.linkTarget+ , g optExtObfuscateEmail Ext.obfuscateEmail+ , f optExtPunctuationPrettifier Ext.punctuationPrettifier+ , f optExtSkylighting Ext.skylighting defaultFormatOpts+ , g optExtToc $ \(from,to) ->+ Ext.toc "toc" . MMark.runScanner doc . Ext.tocScanner $ \x ->+ from <= x && x <= to+ ]+ f p x = if p then x else mempty+ g p x = maybe mempty x p+ applyTemplate tfile output = do+ t <- U.compileMustacheFile tfile+ return . TL.toStrict . U.renderMustache t $+ case MMark.projectYaml doc of+ Just (Object m) -> Object $+ HM.insert "output" (String output) m+ _ -> Aeson.object+ [ "output" .= output+ ]+ htmlOutput <- maybe return applyTemplate optTemplate+ . TL.toStrict+ . L.renderText+ . MMark.render+ . MMark.useExtension exts+ $ doc+ if optJson+ then maybe BL.putStrLn BL.writeFile optOutputFile $+ Aeson.encode (htmlDocJson htmlOutput)+ else maybe T.putStr T.writeFile optOutputFile htmlOutput++----------------------------------------------------------------------------+-- Command line options parsing++-- | Command line options.++data Opts = Opts+ { optInputFile :: !(Maybe FilePath)+ -- ^ File from which to read input (otherwise use stdin)+ , optOutputFile :: !(Maybe FilePath)+ -- ^ File to which to save output (otherwise use stdout)+ , optJson :: !Bool+ -- ^ Whether to output JSON+ , optTemplate :: !(Maybe FilePath)+ -- ^ Use the template located at this path++ , optExtComment :: !(Maybe Text)+ -- ^ Enable extension: 'Ext.commentParagraph'+ , optExtFontAwesome :: !Bool+ -- ^ Enable extension: 'Ext.fontAwesome'+ , optExtKbd :: !Bool+ -- ^ Enable extension: 'Ext.kbd'+ , optExtLinkTarget :: !Bool+ -- ^ Enable extension: 'Ext.linkTarget'+ , optExtObfuscateEmail :: !(Maybe Text)+ -- ^ Enable extension: 'Ext.obfuscateEmail'+ , optExtPunctuationPrettifier :: !Bool+ -- ^ Enable extension: 'Ext.punctuationPrettifier'+ , optExtSkylighting :: !Bool+ -- ^ Enable extension: 'Ext.skylighting'+ , optExtToc :: !(Maybe (Int, Int))+ -- ^ Enable extension: 'Ext.toc'+ }++optsParserInfo :: ParserInfo Opts+optsParserInfo = info (helper <*> ver <*> optsParser) . mconcat $+ [ fullDesc+ , progDesc "Command line interface to MMark markdown processor"+ , header "mmark—command line interface to MMark markdown processor"+ ]+ where+ ver :: Parser (a -> a)+ ver = infoOption verStr . mconcat $+ [ long "version"+ , short 'v'+ , help "Print version of the program"+ ]+ verStr = intercalate "\n"+ [ unwords+ [ "mmark"+ , showVersion version+ , $gitBranch+ , $gitHash+ ]+ , "using mmark (library) " ++ VERSION_mmark+ , "using mmark-ext (library) " ++ VERSION_mmark_ext+ ]++optsParser :: Parser Opts+optsParser = Opts+ <$> (optional . strOption . mconcat)+ [ long "ifile"+ , short 'i'+ , metavar "IFILE"+ , help "Read markdown source from this file (otherwise read from stdin)"+ ]+ <*> (optional . strOption . mconcat)+ [ long "ofile"+ , short 'o'+ , metavar "OFILE"+ , help "Save rendered HTML document to this file (otherwise write to stdout)"+ ]+ <*> (switch . mconcat)+ [ long "json"+ , short 'j'+ , help "Output parse errors and result in JSON format"+ ]+ <*> (optional . strOption . mconcat)+ [ long "template"+ , short 't'+ , metavar "FILE"+ , help "Use the template located at this path"+ ]+ <*> (optional . fmap T.pack . strOption . mconcat)+ [ long "ext-comment"+ , metavar "PREFIX"+ , help "Remove paragraphs that start with the given prefix"+ ]+ <*> (switch . mconcat)+ [ long "ext-font-awesome"+ , help "Enable support for inserting font awesome icons"+ ]+ <*> (switch . mconcat)+ [ long "ext-kbd"+ , help "Enable support for wrapping things in kbd tags"+ ]+ <*> (switch . mconcat)+ [ long "ext-link-target"+ , help "Enable support for specifying link targets"+ ]+ <*> (optional . fmap T.pack . strOption . mconcat)+ [ long "ext-obfuscate-email"+ , metavar "CLASS"+ , help "Obfuscate email addresses assigning the specified class"+ ]+ <*> (switch . mconcat)+ [ long "ext-punctuation"+ , help "Enable punctuation prettifier"+ ]+ <*> (switch . mconcat)+ [ long "ext-skylighting"+ , help "Enable syntax highlighting of code snippets with Skylighting"+ ]+ <*> (optional . option parseRange . mconcat)+ [ long "ext-toc"+ , metavar "RANGE"+ , help ("Enable generation of table of contents using the supplied "+ ++ "range of headers to include, e.g. \"1-6\" or \"2-4\"")+ ]++----------------------------------------------------------------------------+-- Helpers++-- | Represent the given collection of parse errors as 'Value'.++parseErrorsJson :: NonEmpty (ParseError Char MMarkErr) -> Value+parseErrorsJson = Aeson.toJSON . fmap parseErrorObj+ where+ parseErrorObj :: ParseError Char MMarkErr -> Value+ parseErrorObj err = let (SourcePos {..}:|_) = M.errorPos err in Aeson.object+ [ "file" .= sourceName+ , "line" .= M.unPos sourceLine+ , "column" .= M.unPos sourceColumn+ , "text" .= M.parseErrorTextPretty err+ ]++-- | Represent the given rendered HTML document as 'Aeson.Value'.++htmlDocJson :: Text -> Value+htmlDocJson html = Aeson.object+ [ "html" .= html+ ]++-- | Parse a range as two positive numbers separated by a hyphen.++parseRange :: ReadM (Int, Int)+parseRange = eitherReader $ \s ->+ case M.parse p "" s of+ Left err -> Left (M.parseErrorTextPretty err)+ Right x -> Right x+ where+ p :: Parsec Void String (Int, Int)+ p = do+ from <- MCL.decimal+ void (MC.char '-')+ to <- MCL.decimal+ return (from, to)
+ mmark-cli.cabal view
@@ -0,0 +1,49 @@+name: mmark-cli+version: 0.0.1.0+cabal-version: >= 1.18+tested-with: GHC==8.0.2, GHC==8.2.2+license: BSD3+license-file: LICENSE.md+author: Mark Karpov <markkarpov92@gmail.com>+maintainer: Mark Karpov <markkarpov92@gmail.com>+homepage: https://github.com/mmark-md/mmark-cli+bug-reports: https://github.com/mmark-md/mmark-cli/issues+category: Categories+synopsis: Description+build-type: Simple+description: Description.+extra-doc-files: CHANGELOG.md+ , README.md++source-repository head+ type: git+ location: https://github.com/mmark-md/mmark-cli.git++flag dev+ description: Turn on development settings.+ manual: True+ default: False++executable mmark+ main-is: Main.hs+ hs-source-dirs: app+ build-depends: aeson >= 0.11 && < 1.3+ , base >= 4.9 && < 5.0+ , bytestring >= 0.9.2 && < 0.11+ , directory >= 1.2.2 && < 1.4+ , gitrev >= 1.3 && < 1.4+ , lucid >= 2.6 && < 3.0+ , megaparsec >= 6.4 && < 7.0+ , mmark >= 0.0.4 && < 0.1+ , mmark-ext >= 0.1 && < 0.2+ , optparse-applicative >= 0.14 && < 0.15+ , skylighting >= 0.5 && < 0.6+ , stache >= 1.2 && < 1.3+ , text >= 0.2 && < 1.3+ , unordered-containers >= 0.2.5 && < 0.3+ other-modules: Paths_mmark_cli+ if flag(dev)+ ghc-options: -Wall -Werror+ else+ ghc-options: -O2 -Wall+ default-language: Haskell2010