frostedmd

package module
v0.0.0-...-321a9f4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 26, 2018 License: BSD-3-Clause Imports: 11 Imported by: 0

README

Frosted Markdown

GoDoc Build Status Coverage Status

WARNING: this is alpha software and should be considered unstable.

In other words, a work in progress. Like most other software.

Synopsis

package main

import (
    "fmt"
    "github.com/biztos/frostedmd"
)

func main() {

	input := `# My Markdown

    # Meta:
    Tags: ["fee","fi","foe"]

Obscurantism threatens clean data.
`

	res, err := frostedmd.MarkdownCommon([]byte(input))
	if err != nil {
		panic(err)
	}
	mm := res.Meta()
	fmt.Println("Title:", mm["Title"])
	fmt.Println("Tags:", mm["Tags"])
	fmt.Println("HTML:", string(res.Content()))
}

Output:

Title: My Markdown
Tags: [fee fi foe]
HTML: <h1>My Markdown</h1>

<p>Obscurantism threatens clean data.</p>

Description

Frosted Markdown is Markdown with useful extensions and tasty data frosting on top, written in Go.

Markdown is a lightweight plain-text markup language originally written by John Gruber. It makes it easy to write blog posts and similar things in plain-ish text, and convert the result to a fragment of HTML that can be inserted into, say, your blog template. And as you probably know, it has become very popular.

The original Markdown feature set being intentionally minimalist, many extenensions have been implemented by various authors -- and there being no Markdown standard, it's a bit chaotic. At present Frosted Markdown relies on the extensions supported by the excellent BlackFriday, plus of course the "frosting."

The "frosting" (also known as "icing") that makes Frosted Markdown special is a code block (or any preformatted text block) either at the beginning of the file, or following the first heading.

This block, called the Meta Block, is parsed and (usually) removed from the rendered HTML fragment. Thus you end up with a data structure extracted from the Meta Block and an HTML fragment, instead of just the HTML fragment.

So, why is this useful?

  • Data specific to the file is kept in the file, simplifying management.
  • In non-Frosted contexts, e.g. editor previews, your data is still at hand.

It's important to remember that all Markdown is Frosted Markdown -- but some of it doesn't have any frosting. If there is no Meta Block, or the apparent Meta Block can't be parsed, then it is empty -- except for one thing: Frosted Markdown will set the Title if the Markdown file leads with a top-level heading.

Likewise, all Frosted Markdown is Markdown (with a caveat for extension conflicts). Anything frostedmd can parse can be parsed just as well by blackfriday. You'll just expose any Meta Block as a code block.

FAQ

Why frosting?

Because it's sweet and goes on top! And you normally eat the frosting first and the cake later, right?

Why not icing?

Because the author is from the United States, and also happens to be named Frost.

Can I put the Meta Block at the end instead?

Yes, but you have to tell the parser that's what you want.

Is this production-ready?

Probably not. Note the version number in the package. Until it reaches 1.0 the set of extensions and even the API itself may change.

Is this a database?

No. If you're building something database-like off of Frosted Markdown -- which isn't as exotic an idea as you might think -- you will have to parse all the files up front, and then do your databasey things with the set of meta structures thus harvested.

Licenses

Frosted Markdown is (c) Copyright 2016 Kevin A. Frost, with humble acknowledgement of the various authors who did most of the real work (see below).

Frosted Markdown itself is made available under the BSD License -- see the file LICENSE in this repository. This license also applies to most standard Go packages.

Note however that this package relies heavily on these additional packages, which have their own licenses:

TODO

  • Upgrade to blackfriday v2.
  • Add footnotes to the default extension set.
  • More documentation for the long-suffering laity.
  • Better test coverage (and arguably better tests). 100% at minimum!
  • LOTS more edge cases etc.
  • At least CONSIDER less-canonical, but faster, parsing.
    • Specifically, just strip off the top of the file w/o MD-processing it.
  • Uniform error handling, e.g. for parse failures in unknown meta blocks.
  • Also sane handling of potentially innocent errors.
  • Disable title guessing if not already possible (as an option).
  • Vendor-in deps, once I grok how that works with Travis et al.

Documentation

Overview

Package frostedmd converts Markdown files to structured data and HTML.

The structured data is extracted from a Meta Block if present: a code block at the beginning (or, optionally, the end) of the file. At the beginning of the file, the Meta Block may optionally be preceded by a single heading.

# Sample Doc

    # Meta:
    AmIYaml: true
    Tags: [foo, bar, baz, baloney]

There you are.

Parsing and rendering are handled by the excellent Blackfriday package: https://godoc.org/github.com/russross/blackfriday

YAML processing is handled with the nearly canonical YAML package from Canonical: https://godoc.org/gopkg.in/yaml.v2

The Meta Block position can be reversed globally by setting MetaBlockAtEnd to true, or at the Parser level. In reversed order the meta code block must be the last element in the Markdown source.

If the Meta contains no Title (nor "title" nor "TITLE") then the first heading is used, if and only if that heading was not preceded by any other block besides the Meta Block.

Supported languages for the meta block are JSON and YAML (the default); additional languages as well as custom parsers are planned for the future.

If an appropriate meta block is found it will be excluded from the rendered HTML content.

Example
package main

import (
	"fmt"

	"github.com/biztos/frostedmd"
)

func main() {

	// NOTE: due to a godoc bug, the following text may not appear correctly.
	// The "# Meta" line and the "Tags" line are both indented (four spaces).
	// https://github.com/golang/go/issues/18446

	// The easiest way to get things done:
	input := `# My Markdown

    # Meta:
    Tags: ["fee","fi","foe"]

Obscurantism threatens clean data.
`

	res, err := frostedmd.MarkdownCommon([]byte(input))
	if err != nil {
		panic(err)
	}
	mm := res.Meta
	fmt.Println("Title:", mm["Title"])
	fmt.Println("Tags:", mm["Tags"])
	fmt.Println("HTML:", string(res.Content))

}
Output:

Title: My Markdown
Tags: [fee fi foe]
HTML: <h1>My Markdown</h1>

<p>Obscurantism threatens clean data.</p>

Index

Examples

Constants

View Source
const (
	CMD_OPTIONS_ERROR       = 1
	CMD_FILE_ERROR          = 2
	CMD_PARSE_ERROR         = 3
	CMD_SERIALIZATION_ERROR = 4
	CMD_OTHER_ERROR         = 99
)
View Source
const BlackFridayCommonExtensions = 0 |
	blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
	blackfriday.EXTENSION_TABLES |
	blackfriday.EXTENSION_FENCED_CODE |
	blackfriday.EXTENSION_AUTOLINK |
	blackfriday.EXTENSION_STRIKETHROUGH |
	blackfriday.EXTENSION_SPACE_HEADERS |
	blackfriday.EXTENSION_HEADER_IDS |
	blackfriday.EXTENSION_BACKSLASH_LINE_BREAK |
	blackfriday.EXTENSION_DEFINITION_LISTS

BlackFridayCommonExtensions defines the "Common" set of Blackfriday extensions, which are highly recommended for the productive use of Markdown.

View Source
const BlackFridayCommonHTMLFlags = 0 |
	blackfriday.HTML_USE_XHTML |
	blackfriday.HTML_USE_SMARTYPANTS |
	blackfriday.HTML_SMARTYPANTS_FRACTIONS |
	blackfriday.HTML_SMARTYPANTS_DASHES |
	blackfriday.HTML_SMARTYPANTS_LATEX_DASHES

BlackFridayCommonHTMLFlags defines the "Common" set of Blackfriday HTML flags; also highly recommended.

Variables

View Source
var CmdUsage = `` /* 832-byte string literal not displayed */

Standard DocOpt specification covering all known options. Additional explanatory text is up to the caller of NewCmd.

View Source
var DefaultExitFunction = os.Exit

Exit function; override for testing main()

View Source
var MetaBlockAtEnd = false

MetaBlockAtEnd defines whether the block of data is expected at the end of the Markdown file, or (the default) at the beginning.

Functions

func LicenseFullText

func LicenseFullText() string

LicenseFullText returns the full license text for Frosted Markdown and all known included licenses, to the best of the author's knowledge.

func RendererTestCoverageShim

func RendererTestCoverageShim() int

RendererTestCoverageShim is, as its name hopefully implies, a function that minimally exercises some hard (impossible?) to reach code in our internal renderer. Removing the code is not really an option, since it is necessary for the blackfriday.Renderer interface; however it has not yet proven reachable from within document parsing. This function is the lesser of two evils. The greater evil would be to expose the entire rendering subsystem just to make it testable; or (worse) live with less than 100% test coverage.

Types

type Cmd

type Cmd struct {
	Name    string
	Version string
	Usage   string
	Options *CmdOptions
	Result  *ParseResult

	// In order to make testing realistically possible in the command context
	// we make these standard things overrideable:
	Exit   func(int)
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}

Cmd defines a command-line program or its equivalent.

func NewCmd

func NewCmd(name, version, usage string) *Cmd

NewCmd returns a new Cmd for the given name, version and DocOpt usage specification. The Exit property is set to the DefaultExitFunction, allowing a global override for testing.

func (*Cmd) Fail

func (c *Cmd) Fail(err error)

Fail fails with a useful message based on err; if err is a CmdError and has an exit code set, that is used, otherwise the "other" code is used: CMD_OTHER_ERROR.

func (*Cmd) ParseFile

func (c *Cmd) ParseFile() error

ParseFile parses a single file contained in the Options, according to the other options therein, and returning any error. Note that a useful result *may* be returned together with an error. If the Options.File is the empty string, input is read from the command's Stdin (os.Stdin by default).

func (*Cmd) PrintResult

func (c *Cmd) PrintResult() error

PrintResult prints the Result according to the Options, with output going to c.Stdout. Any error returned should be considered fatal. If Result is nil, nothing is printed; this is normal if the Test option is set.

func (*Cmd) Run

func (c *Cmd) Run() error

Run calls the methods used for a standard command run in order: SetOptions, ParseFile, and finally PrintResult. The first error encountered is returned, to (normally) be passed to Fail. Note that in the interest of simplicity, docopt is allowed to exit directly from within SetOptions.

func (*Cmd) SetOptions

func (c *Cmd) SetOptions() error

SetOptions sets the Options struct according to the Usage. If a --license boolean option is found, the LicenseFullText is printed and the program exits with success. The --help and --version (-h and -v) options are handled similarly by docopt, and it will also fail directly to os.Exit on any option errors.

type CmdError

type CmdError struct {
	Err    error  // The source error.
	File   string // The file to include in the error message.
	Code   int    // The exit code, if Force is not set.
	Silent bool   // If true, do not print the error.
	Force  bool   // If true, do not exit.
}

CmdError defines an error in the command-running context.

func (CmdError) Error

func (e CmdError) Error() string

Error stringifies the error per the error interface.

type CmdOptions

type CmdOptions struct {
	File          string
	Format        string
	Indent        bool
	NoBase64      bool
	ContentOnly   bool
	MetaOnly      bool
	PlainMarkdown bool
	Force         bool
	Silent        bool
	Test          bool
}

CmdOptions describes the options available to the command. The standard fmd command exposes all of them.

type CmdYamlRes

type CmdYamlRes struct {
	Meta    map[string]interface{}
	Content string
}

CmdYamlRes is a shim to handle output serialization to YAML.

type ParseResult

type ParseResult struct {
	Meta    map[string]interface{} `json:"meta"`
	Content []byte                 `json:"content"`
}

ParseResult defines the result of a Parse operation.

func MarkdownBasic

func MarkdownBasic(input []byte) (*ParseResult, error)

MarkdownBasic converts Markdown input using the same options as blackfriday.MarkdownBasic. This is simply a convenience method for:

NewBasic().Parse(input)

func MarkdownCommon

func MarkdownCommon(input []byte) (*ParseResult, error)

MarkdownCommon converts Markdown input using the same options as blackfriday.MarkdownCommon. This is simply a convenience method for:

New().Parse(input)

type Parser

type Parser struct {
	MetaAtEnd          bool
	MarkdownExtensions int // uses blackfriday EXTENSION_* constants
	HTMLFlags          int // uses blackfridy HTML_* constants
}

Parser defines a parser-renderer used for converting source data to HTML and metadata.

func New

func New() *Parser

New returns a new Parser with the common flags and extensions enabled.

Example
package main

import (
	"fmt"

	"github.com/biztos/frostedmd"
)

func main() {

	input := `# Lots of Data

Here we have a full dataset, which (for instance) a template engine
will turn into something cool and dynamic.  Thus we put it at the end
so we can read our nice summary using the *head* command.

    {
        "datasets": {
             "numbers": [11,22,33,44,55,66],
             "letters": ["a","B","ß","í"]
        }
    }

`

	parser := frostedmd.New()
	parser.MetaAtEnd = true
	res, err := parser.Parse([]byte(input))
	if err != nil {
		panic(err)
	}
	mm := res.Meta
	fmt.Println("Title:", mm["Title"])
	fmt.Println("HTML:", string(res.Content))

	// Order within a map is random in Go, so let's make it explicit.
	fmt.Println("Data sets:")
	if ds, ok := mm["datasets"].(map[string]interface{}); ok {
		fmt.Println("  numbers:", ds["numbers"])
		fmt.Println("  letters:", ds["letters"])
	} else {
		fmt.Printf("NOT A MAP: %T\n", mm["datasets"])
	}

}
Output:

Title: Lots of Data
HTML: <h1>Lots of Data</h1>

<p>Here we have a full dataset, which (for instance) a template engine
will turn into something cool and dynamic.  Thus we put it at the end
so we can read our nice summary using the <em>head</em> command.</p>

Data sets:
  numbers: [11 22 33 44 55 66]
  letters: [a B ß í]

func NewBasic

func NewBasic() *Parser

NewBasic returns a new Parser without the common flags and extensions.

func (*Parser) Parse

func (p *Parser) Parse(input []byte) (*ParseResult, error)

Parse converts Markdown input into a meta map and HTML content fragment. If an error is encountered while parsing the meta block, the rendered content is still returned. Thus the caller may choose to handle meta errors without interrupting flow.

Directories

Path Synopsis
cmd
fmd
The Frosted Markdown Tool.
The Frosted Markdown Tool.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL