├── 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 | ![Alt text](cat.jpg "cat from wikipedia https://commons.wikimedia.org/wiki/File:Cat_August_2010-4.jpg") 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 | --------------------------------------------------------------------------------