├── LICENSE.md
├── README.md
├── examples
├── cat.jpg
└── test.md
├── go.mod
├── go.sum
└── mdcat.go
/LICENSE.md:
--------------------------------------------------------------------------------
1 | mdcat is distributed under the BSD 2-Clause License:
2 |
3 | Copyright (c) 2017, Shawn Rutledge
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mdcat
2 | a 'cat' which formats markdown, good for viewing READMEs and checklists on the terminal
3 |
--------------------------------------------------------------------------------
/examples/cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ec1oud/mdcat/cb87fff899ccb5091fe440c4f6ceb63b3edd07b6/examples/cat.jpg
--------------------------------------------------------------------------------
/examples/test.md:
--------------------------------------------------------------------------------
1 | # Markdown test
2 |
3 | - This is a `bullet`
4 | + This might be an indented bullet
5 |
6 | 1. one
7 | 1. two
8 | 1. three
9 |
10 | These *words* get **quite** some ***emphasis***
11 |
12 | "an often broken web site"
13 |
14 | ## Tasks ##
15 | - [ ] This is an incomplete task.
16 | - [x] This is done. (yeah right, when is _anything_ ever done?)
17 |
18 | ## Definitions
19 |
20 | Cat
21 | : Fluffy animal everyone likes
22 | : Unix utility for conCATenating files, or simply sending them to stdout
23 |
24 | 
25 |
26 | ## Quotes
27 | > Our liberty depends on the freedom of the press, and that cannot be limited without being lost.
28 | > --- Thomas Jefferson
29 |
30 | Recipes
31 | -------
32 |
33 | ### Pie Crust
34 |
35 | - 1½ c Flour
36 | - ¼ ts Baking powder
37 | - ½ ts salt
38 | - ⅔ c Lard or butter ~~or shortening~~
39 | - ⅓ c Cold water[^1]
40 |
41 | Sift together dry ingredients. Add fat and cut
42 | with two knives or pastry blender until mealy.
43 | Stir in water until pastry leaves the sides of
44 | bowl. Divide into 2 balls. Roll one to fit the pie
45 | tin, use the other for the top of the pie.
46 |
47 | [^1]: approximately
48 |
49 | ## Tables
50 |
51 | Syntax:
52 |
53 | ``` markdown
54 | |Name | Age |
55 | |--------|------|
56 | |Bob | 27 |
57 | |Alice | 23 |
58 | ```
59 |
60 | Yields:
61 |
62 | |Name | Age |
63 | |--------|------|
64 | |Bob | 27 |
65 | |Alice | 23 |
66 |
67 | - - -
68 |
69 | And after the fold, comes the end.
70 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/ec1oud/mdcat
2 |
3 | go 1.18
4 |
5 | require github.com/ec1oud/blackfriday v0.0.0-20170301190602-4575f80c9153
6 |
7 | require github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
8 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/ec1oud/blackfriday v0.0.0-20170301190602-4575f80c9153 h1:SV6NsaQN+6LwFz9KyjSUFHfDDdjhVgYTGJw/QFxFT7Q=
2 | github.com/ec1oud/blackfriday v0.0.0-20170301190602-4575f80c9153/go.mod h1:RHsLyg+obmMTp8zmSEW25XveZxSJmUCxSo+dKxpD3Dg=
3 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
4 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
5 |
--------------------------------------------------------------------------------
/mdcat.go:
--------------------------------------------------------------------------------
1 | //
2 | // mdcat
3 | // Available at http://github.com/ec1oud/mdcat
4 | // based on example code from http://github.com/russross/blackfriday-tool
5 | //
6 | // Copyright © 2017 Shawn Rutledge .
7 | // Distributed under the Simplified BSD License.
8 | // See README.md for details.
9 | //
10 |
11 | package main
12 |
13 | import (
14 | "flag"
15 | "fmt"
16 | "github.com/ec1oud/blackfriday"
17 | //~ "github.com/russross/blackfriday"
18 | "io/ioutil"
19 | "os"
20 | "strings"
21 | )
22 |
23 | const DEFAULT_TITLE = ""
24 |
25 | func main() {
26 | // parse command-line options
27 | var ansi, html, page, toc, toconly, xhtml, latex, smartypants, latexdashes, fractions bool
28 | var css string
29 | flag.BoolVar(&ansi, "ansi", true,
30 | "Generate ANSI terminal escape codes")
31 | flag.BoolVar(&page, "page", false,
32 | "Generate a standalone HTML page (implies -latex=false)")
33 | flag.BoolVar(&toc, "toc", false,
34 | "Generate a table of contents (implies -latex=false)")
35 | flag.BoolVar(&toconly, "toconly", false,
36 | "Generate a table of contents only (implies -toc)")
37 | flag.BoolVar(&xhtml, "xhtml", true,
38 | "Use XHTML-style tags in HTML output")
39 | flag.BoolVar(&latex, "latex", false,
40 | "Generate LaTeX output instead of HTML")
41 | flag.BoolVar(&smartypants, "smartypants", true,
42 | "Apply smartypants-style substitutions")
43 | flag.BoolVar(&latexdashes, "latexdashes", true,
44 | "Use LaTeX-style dash rules for smartypants")
45 | flag.BoolVar(&fractions, "fractions", true,
46 | "Use improved fraction rules for smartypants")
47 | flag.StringVar(&css, "css", "",
48 | "Link to a CSS stylesheet (implies -page)")
49 | flag.Usage = func() {
50 | fmt.Fprintf(os.Stderr, "mdcat Markdown Renderer using Blackfriday v"+blackfriday.VERSION+
51 | "\nAvailable at http://github.com/ec1oud/mdcat\n\n"+
52 | "Usage:\n"+
53 | " %s [options] [inputfile [outputfile]]\n\n"+
54 | "Options:\n",
55 | os.Args[0])
56 | flag.PrintDefaults()
57 | }
58 | flag.Parse()
59 |
60 | // enforce implied options
61 | if css != "" {
62 | page = true
63 | html = true
64 | ansi = false
65 | }
66 | if page {
67 | latex = false
68 | html = true
69 | ansi = false
70 | }
71 | if toconly {
72 | toc = true
73 | }
74 | if toc {
75 | latex = false
76 | html = true
77 | ansi = false
78 | }
79 |
80 | // read the input
81 | var input []byte
82 | var err error
83 | args := flag.Args()
84 | switch len(args) {
85 | case 0:
86 | if input, err = ioutil.ReadAll(os.Stdin); err != nil {
87 | fmt.Fprintln(os.Stderr, "Error reading from Stdin:", err)
88 | os.Exit(-1)
89 | }
90 | case 1, 2:
91 | if input, err = ioutil.ReadFile(args[0]); err != nil {
92 | fmt.Fprintln(os.Stderr, "Error reading from", args[0], ":", err)
93 | os.Exit(-1)
94 | }
95 | default:
96 | flag.Usage()
97 | os.Exit(-1)
98 | }
99 |
100 | // set up options
101 | extensions := 0
102 | extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
103 | extensions |= blackfriday.EXTENSION_TABLES
104 | extensions |= blackfriday.EXTENSION_FENCED_CODE
105 | extensions |= blackfriday.EXTENSION_AUTOLINK
106 | extensions |= blackfriday.EXTENSION_STRIKETHROUGH
107 | extensions |= blackfriday.EXTENSION_SPACE_HEADERS
108 |
109 | var renderer blackfriday.Renderer
110 | if latex {
111 | // render the data into LaTeX
112 | renderer = blackfriday.LatexRenderer(0)
113 | } else if html {
114 | // render the data into HTML
115 | htmlFlags := 0
116 | if xhtml {
117 | htmlFlags |= blackfriday.HTML_USE_XHTML
118 | }
119 | if smartypants {
120 | htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
121 | }
122 | if fractions {
123 | htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
124 | }
125 | if latexdashes {
126 | htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
127 | }
128 | title := ""
129 | if page {
130 | htmlFlags |= blackfriday.HTML_COMPLETE_PAGE
131 | title = getTitle(input)
132 | }
133 | if toconly {
134 | htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
135 | }
136 | if toc {
137 | htmlFlags |= blackfriday.HTML_TOC
138 | }
139 | renderer = blackfriday.HtmlRenderer(htmlFlags, title, css)
140 | } else {
141 | ansiFlags := 0
142 | if smartypants {
143 | ansiFlags |= blackfriday.ANSI_USE_SMARTYPANTS
144 | }
145 | if fractions {
146 | ansiFlags |= blackfriday.ANSI_SMARTYPANTS_FRACTIONS
147 | }
148 | if latexdashes {
149 | ansiFlags |= blackfriday.ANSI_SMARTYPANTS_LATEX_DASHES
150 | }
151 | renderer = blackfriday.AnsiRenderer(80, ansiFlags) // TODO get terminal width
152 | }
153 |
154 | // parse and render
155 | var output []byte = blackfriday.Markdown(input, renderer, extensions)
156 |
157 | // output the result
158 | var out *os.File
159 | if len(args) == 2 {
160 | if out, err = os.Create(args[1]); err != nil {
161 | fmt.Fprintf(os.Stderr, "Error creating %s: %v", args[1], err)
162 | os.Exit(-1)
163 | }
164 | defer out.Close()
165 | } else {
166 | out = os.Stdout
167 | }
168 |
169 | if _, err = out.Write(output); err != nil {
170 | fmt.Fprintln(os.Stderr, "Error writing output:", err)
171 | os.Exit(-1)
172 | }
173 | }
174 |
175 | // try to guess the title from the input buffer
176 | // just check if it starts with an element and use that
177 | func getTitle(input []byte) string {
178 | i := 0
179 |
180 | // skip blank lines
181 | for i < len(input) && (input[i] == '\n' || input[i] == '\r') {
182 | i++
183 | }
184 | if i >= len(input) {
185 | return DEFAULT_TITLE
186 | }
187 | if input[i] == '\r' && i+1 < len(input) && input[i+1] == '\n' {
188 | i++
189 | }
190 |
191 | // find the first line
192 | start := i
193 | for i < len(input) && input[i] != '\n' && input[i] != '\r' {
194 | i++
195 | }
196 | line1 := input[start:i]
197 | if input[i] == '\r' && i+1 < len(input) && input[i+1] == '\n' {
198 | i++
199 | }
200 | i++
201 |
202 | // check for a prefix header
203 | if len(line1) >= 3 && line1[0] == '#' && (line1[1] == ' ' || line1[1] == '\t') {
204 | return strings.TrimSpace(string(line1[2:]))
205 | }
206 |
207 | // check for an underlined header
208 | if i >= len(input) || input[i] != '=' {
209 | return DEFAULT_TITLE
210 | }
211 | for i < len(input) && input[i] == '=' {
212 | i++
213 | }
214 | for i < len(input) && (input[i] == ' ' || input[i] == '\t') {
215 | i++
216 | }
217 | if i >= len(input) || (input[i] != '\n' && input[i] != '\r') {
218 | return DEFAULT_TITLE
219 | }
220 |
221 | return strings.TrimSpace(string(line1))
222 | }
223 |
--------------------------------------------------------------------------------