├── go.mod ├── go.sum ├── README.md └── main.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gomarkdown/mdtohtml 2 | 3 | go 1.18 4 | 5 | require github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4= 2 | github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Markdown to HTML cmd-line tool 2 | 3 | If you have Go installed, install with: 4 | 5 | go get -u github.com/gomarkdown/mdtohtml 6 | 7 | To run: 8 | 9 | mdtohtml [options] inputfile [outputfile] 10 | 11 | Run `mdtohtml` to see all options. 12 | 13 | This is also an example of how to use [gomarkdown/markdown](https://github.com/gomarkdown/markdown) library. 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "runtime/pprof" 9 | "strings" 10 | 11 | "github.com/gomarkdown/markdown" 12 | "github.com/gomarkdown/markdown/html" 13 | "github.com/gomarkdown/markdown/parser" 14 | ) 15 | 16 | const defaultTitle = "" 17 | 18 | func main() { 19 | var page, toc, xhtml, latex, smartypants, latexdashes, fractions, attributes, headingIDs bool 20 | var css, cpuprofile string 21 | var repeat int 22 | flag.BoolVar(&page, "page", false, 23 | "Generate a standalone HTML page (implies -latex=false)") 24 | flag.BoolVar(&toc, "toc", false, 25 | "Generate a table of contents (implies -latex=false)") 26 | flag.BoolVar(&xhtml, "xhtml", true, 27 | "Use XHTML-style tags in HTML output") 28 | //flag.BoolVar(&latex, "latex", false, 29 | // "Generate LaTeX output instead of HTML") 30 | flag.BoolVar(&smartypants, "smartypants", true, 31 | "Apply smartypants-style substitutions") 32 | flag.BoolVar(&latexdashes, "latexdashes", true, 33 | "Use LaTeX-style dash rules for smartypants") 34 | flag.BoolVar(&fractions, "fractions", true, 35 | "Use improved fraction rules for smartypants") 36 | flag.StringVar(&css, "css", "", 37 | "Link to a CSS stylesheet (implies -page)") 38 | flag.StringVar(&cpuprofile, "cpuprofile", "", 39 | "Write cpu profile to a file") 40 | flag.IntVar(&repeat, "repeat", 1, 41 | "Process the input multiple times (for benchmarking)") 42 | flag.BoolVar(&attributes, "attributes", false, 43 | "Enable Block level attributes") 44 | flag.BoolVar(&headingIDs, "headingids", false, 45 | "Enable Heading IDs") 46 | flag.Usage = func() { 47 | fmt.Fprintf(os.Stderr, "Markdown Processor "+ 48 | "\nAvailable at http://github.com/gomarkdown/markdown/cmd/mdtohtml\n\n"+ 49 | "Copyright © 2011 Russ Ross \n"+ 50 | "Copyright © 2018 Krzysztof Kowalczyk \n"+ 51 | "Distributed under the Simplified BSD License\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 | } 64 | if page { 65 | latex = false 66 | } 67 | if toc { 68 | latex = false 69 | } 70 | 71 | // turn on profiling? 72 | if cpuprofile != "" { 73 | f, err := os.Create(cpuprofile) 74 | if err != nil { 75 | fmt.Fprintln(os.Stderr, err) 76 | } 77 | pprof.StartCPUProfile(f) 78 | defer pprof.StopCPUProfile() 79 | } 80 | 81 | // read the input 82 | var input []byte 83 | var err error 84 | args := flag.Args() 85 | switch len(args) { 86 | case 0: 87 | if input, err = ioutil.ReadAll(os.Stdin); err != nil { 88 | fmt.Fprintln(os.Stderr, "Error reading from Stdin:", err) 89 | os.Exit(-1) 90 | } 91 | case 1, 2: 92 | if input, err = ioutil.ReadFile(args[0]); err != nil { 93 | fmt.Fprintln(os.Stderr, "Error reading from", args[0], ":", err) 94 | os.Exit(-1) 95 | } 96 | default: 97 | flag.Usage() 98 | os.Exit(-1) 99 | } 100 | 101 | // set up options 102 | var extensions = parser.NoIntraEmphasis | 103 | parser.Tables | 104 | parser.FencedCode | 105 | parser.Autolink | 106 | parser.Strikethrough | 107 | parser.SpaceHeadings 108 | 109 | if attributes { 110 | extensions |= parser.Attributes 111 | } 112 | 113 | if headingIDs { 114 | extensions |= parser.HeadingIDs 115 | } 116 | 117 | var renderer markdown.Renderer 118 | if latex { 119 | // render the data into LaTeX 120 | //renderer = markdown.LatexRenderer(0) 121 | } else { 122 | // render the data into HTML 123 | var htmlFlags html.Flags 124 | if xhtml { 125 | htmlFlags |= html.UseXHTML 126 | } 127 | if smartypants { 128 | htmlFlags |= html.Smartypants 129 | } 130 | if fractions { 131 | htmlFlags |= html.SmartypantsFractions 132 | } 133 | if latexdashes { 134 | htmlFlags |= html.SmartypantsLatexDashes 135 | } 136 | title := "" 137 | if page { 138 | htmlFlags |= html.CompletePage 139 | title = getTitle(input) 140 | } 141 | if toc { 142 | htmlFlags |= html.TOC 143 | } 144 | params := html.RendererOptions{ 145 | Flags: htmlFlags, 146 | Title: title, 147 | CSS: css, 148 | } 149 | renderer = html.NewRenderer(params) 150 | } 151 | 152 | // parse and render 153 | var output []byte 154 | for i := 0; i < repeat; i++ { 155 | parser := parser.NewWithExtensions(extensions) 156 | output = markdown.ToHTML(input, parser, renderer) 157 | } 158 | 159 | // output the result 160 | var out *os.File 161 | if len(args) == 2 { 162 | if out, err = os.Create(args[1]); err != nil { 163 | fmt.Fprintf(os.Stderr, "Error creating %s: %v", args[1], err) 164 | os.Exit(-1) 165 | } 166 | defer out.Close() 167 | } else { 168 | out = os.Stdout 169 | } 170 | 171 | if _, err = out.Write(output); err != nil { 172 | fmt.Fprintln(os.Stderr, "Error writing output:", err) 173 | os.Exit(-1) 174 | } 175 | } 176 | 177 | // try to guess the title from the input buffer 178 | // just check if it starts with an

element and use that 179 | func getTitle(input []byte) string { 180 | i := 0 181 | 182 | // skip blank lines 183 | for i < len(input) && (input[i] == '\n' || input[i] == '\r') { 184 | i++ 185 | } 186 | if i >= len(input) { 187 | return defaultTitle 188 | } 189 | if input[i] == '\r' && i+1 < len(input) && input[i+1] == '\n' { 190 | i++ 191 | } 192 | 193 | // find the first line 194 | start := i 195 | for i < len(input) && input[i] != '\n' && input[i] != '\r' { 196 | i++ 197 | } 198 | line1 := input[start:i] 199 | if input[i] == '\r' && i+1 < len(input) && input[i+1] == '\n' { 200 | i++ 201 | } 202 | i++ 203 | 204 | // check for a prefix header 205 | if len(line1) >= 3 && line1[0] == '#' && (line1[1] == ' ' || line1[1] == '\t') { 206 | return strings.TrimSpace(string(line1[2:])) 207 | } 208 | 209 | // check for an underlined header 210 | if i >= len(input) || input[i] != '=' { 211 | return defaultTitle 212 | } 213 | for i < len(input) && input[i] == '=' { 214 | i++ 215 | } 216 | for i < len(input) && (input[i] == ' ' || input[i] == '\t') { 217 | i++ 218 | } 219 | if i >= len(input) || (input[i] != '\n' && input[i] != '\r') { 220 | return defaultTitle 221 | } 222 | 223 | return strings.TrimSpace(string(line1)) 224 | } 225 | --------------------------------------------------------------------------------