mirror of https://github.com/docker/cli.git
				
				
				
			Add missing dependencies to vendor, and fix generation imports
Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
		
							parent
							
								
									d2cb97e76d
								
							
						
					
					
						commit
						875daf0130
					
				| 
						 | 
				
			
			@ -8,8 +8,8 @@ import (
 | 
			
		|||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/cli/command"
 | 
			
		||||
	"github.com/docker/docker/cli/command/commands"
 | 
			
		||||
	"github.com/docker/cli/cli/command"
 | 
			
		||||
	"github.com/docker/cli/cli/command/commands"
 | 
			
		||||
	"github.com/docker/docker/pkg/term"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"github.com/spf13/pflag"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,8 +7,8 @@ import (
 | 
			
		|||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/cli/command"
 | 
			
		||||
	"github.com/docker/docker/cli/command/commands"
 | 
			
		||||
	"github.com/docker/cli/cli/command"
 | 
			
		||||
	"github.com/docker/cli/cli/command/commands"
 | 
			
		||||
	"github.com/docker/docker/pkg/term"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"github.com/spf13/cobra/doc"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.
 | 
			
		|||
github.com/Sirupsen/logrus v0.11.0
 | 
			
		||||
github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
 | 
			
		||||
github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
 | 
			
		||||
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
 | 
			
		||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
 | 
			
		||||
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
 | 
			
		||||
github.com/docker/docker 45c6f4262a865a03adaac291a9ce33c0f2190d77 
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +32,8 @@ github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://
 | 
			
		|||
github.com/opencontainers/selinux ba1aefe8057f1d0cfb8e88d0ec1dc85925ef987d
 | 
			
		||||
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
 | 
			
		||||
github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2
 | 
			
		||||
github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name 10ef21a441db47d8b13ebcc5fd2310f636973c77
 | 
			
		||||
github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git
 | 
			
		||||
github.com/spf13/pflag 9ff6c6923cfffbcd502984b8e0c80539a94968b7
 | 
			
		||||
github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
The MIT License (MIT)
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2014 Brian Goff
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
go-md2man
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
** Work in Progress **
 | 
			
		||||
This still needs a lot of help to be complete, or even usable!
 | 
			
		||||
 | 
			
		||||
Uses blackfriday to process markdown into man pages.
 | 
			
		||||
 | 
			
		||||
### Usage
 | 
			
		||||
 | 
			
		||||
./md2man -in /path/to/markdownfile.md -out /manfile/output/path
 | 
			
		||||
 | 
			
		||||
### How to contribute
 | 
			
		||||
 | 
			
		||||
We use [govend](https://github.com/govend/govend) for vendoring Go packages.
 | 
			
		||||
 | 
			
		||||
How to update dependencies: `govend -v -u --prune`
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
package md2man
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/russross/blackfriday"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Render(doc []byte) []byte {
 | 
			
		||||
	renderer := RoffRenderer(0)
 | 
			
		||||
	extensions := 0
 | 
			
		||||
	extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
 | 
			
		||||
	extensions |= blackfriday.EXTENSION_TABLES
 | 
			
		||||
	extensions |= blackfriday.EXTENSION_FENCED_CODE
 | 
			
		||||
	extensions |= blackfriday.EXTENSION_AUTOLINK
 | 
			
		||||
	extensions |= blackfriday.EXTENSION_SPACE_HEADERS
 | 
			
		||||
	extensions |= blackfriday.EXTENSION_FOOTNOTES
 | 
			
		||||
	extensions |= blackfriday.EXTENSION_TITLEBLOCK
 | 
			
		||||
 | 
			
		||||
	return blackfriday.Markdown(doc, renderer, extensions)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,282 @@
 | 
			
		|||
package md2man
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/russross/blackfriday"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type roffRenderer struct{}
 | 
			
		||||
 | 
			
		||||
var listCounter int
 | 
			
		||||
 | 
			
		||||
func RoffRenderer(flags int) blackfriday.Renderer {
 | 
			
		||||
	return &roffRenderer{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) GetFlags() int {
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) TitleBlock(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString(".TH ")
 | 
			
		||||
 | 
			
		||||
	splitText := bytes.Split(text, []byte("\n"))
 | 
			
		||||
	for i, line := range splitText {
 | 
			
		||||
		line = bytes.TrimPrefix(line, []byte("% "))
 | 
			
		||||
		if i == 0 {
 | 
			
		||||
			line = bytes.Replace(line, []byte("("), []byte("\" \""), 1)
 | 
			
		||||
			line = bytes.Replace(line, []byte(")"), []byte("\" \""), 1)
 | 
			
		||||
		}
 | 
			
		||||
		line = append([]byte("\""), line...)
 | 
			
		||||
		line = append(line, []byte("\" ")...)
 | 
			
		||||
		out.Write(line)
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteString("\n")
 | 
			
		||||
 | 
			
		||||
	// disable hyphenation
 | 
			
		||||
	out.WriteString(".nh\n")
 | 
			
		||||
	// disable justification (adjust text to left margin only)
 | 
			
		||||
	out.WriteString(".ad l\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
 | 
			
		||||
	out.WriteString("\n.PP\n.RS\n\n.nf\n")
 | 
			
		||||
	escapeSpecialChars(out, text)
 | 
			
		||||
	out.WriteString("\n.fi\n.RE\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) BlockQuote(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\n.PP\n.RS\n")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\n.RE\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) BlockHtml(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) {
 | 
			
		||||
	marker := out.Len()
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case marker == 0:
 | 
			
		||||
		// This is the doc header
 | 
			
		||||
		out.WriteString(".TH ")
 | 
			
		||||
	case level == 1:
 | 
			
		||||
		out.WriteString("\n\n.SH ")
 | 
			
		||||
	case level == 2:
 | 
			
		||||
		out.WriteString("\n.SH ")
 | 
			
		||||
	default:
 | 
			
		||||
		out.WriteString("\n.SS ")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !text() {
 | 
			
		||||
		out.Truncate(marker)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) HRule(out *bytes.Buffer) {
 | 
			
		||||
	out.WriteString("\n.ti 0\n\\l'\\n(.lu'\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
 | 
			
		||||
	marker := out.Len()
 | 
			
		||||
	if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
 | 
			
		||||
		listCounter = 1
 | 
			
		||||
	}
 | 
			
		||||
	if !text() {
 | 
			
		||||
		out.Truncate(marker)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
 | 
			
		||||
	if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
 | 
			
		||||
		out.WriteString(fmt.Sprintf(".IP \"%3d.\" 5\n", listCounter))
 | 
			
		||||
		listCounter += 1
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString(".IP \\(bu 2\n")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) Paragraph(out *bytes.Buffer, text func() bool) {
 | 
			
		||||
	marker := out.Len()
 | 
			
		||||
	out.WriteString("\n.PP\n")
 | 
			
		||||
	if !text() {
 | 
			
		||||
		out.Truncate(marker)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if marker != 0 {
 | 
			
		||||
		out.WriteString("\n")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: This might now work
 | 
			
		||||
func (r *roffRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
 | 
			
		||||
	out.WriteString(".TS\nallbox;\n")
 | 
			
		||||
 | 
			
		||||
	out.Write(header)
 | 
			
		||||
	out.Write(body)
 | 
			
		||||
	out.WriteString("\n.TE\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) TableRow(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	if out.Len() > 0 {
 | 
			
		||||
		out.WriteString("\n")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
 | 
			
		||||
	if out.Len() > 0 {
 | 
			
		||||
		out.WriteString(" ")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString(" ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: This is probably broken
 | 
			
		||||
func (r *roffRenderer) TableCell(out *bytes.Buffer, text []byte, align int) {
 | 
			
		||||
	if out.Len() > 0 {
 | 
			
		||||
		out.WriteString("\t")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\t")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) Footnotes(out *bytes.Buffer, text func() bool) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
 | 
			
		||||
	out.WriteString("\n\\[la]")
 | 
			
		||||
	out.Write(link)
 | 
			
		||||
	out.WriteString("\\[ra]")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) CodeSpan(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\\fB\\fC")
 | 
			
		||||
	escapeSpecialChars(out, text)
 | 
			
		||||
	out.WriteString("\\fR")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\\fB")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\\fP")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) Emphasis(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\\fI")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\\fP")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) LineBreak(out *bytes.Buffer) {
 | 
			
		||||
	out.WriteString("\n.br\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
 | 
			
		||||
	out.Write(content)
 | 
			
		||||
	r.AutoLink(out, link, 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) RawHtmlTag(out *bytes.Buffer, tag []byte) {
 | 
			
		||||
	out.Write(tag)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\\s+2")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\\s-2")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) StrikeThrough(out *bytes.Buffer, text []byte) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) Entity(out *bytes.Buffer, entity []byte) {
 | 
			
		||||
	out.WriteString(html.UnescapeString(string(entity)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func processFooterText(text []byte) []byte {
 | 
			
		||||
	text = bytes.TrimPrefix(text, []byte("% "))
 | 
			
		||||
	newText := []byte{}
 | 
			
		||||
	textArr := strings.Split(string(text), ") ")
 | 
			
		||||
 | 
			
		||||
	for i, w := range textArr {
 | 
			
		||||
		if i == 0 {
 | 
			
		||||
			w = strings.Replace(w, "(", "\" \"", 1)
 | 
			
		||||
			w = fmt.Sprintf("\"%s\"", w)
 | 
			
		||||
		} else {
 | 
			
		||||
			w = fmt.Sprintf(" \"%s\"", w)
 | 
			
		||||
		}
 | 
			
		||||
		newText = append(newText, []byte(w)...)
 | 
			
		||||
	}
 | 
			
		||||
	newText = append(newText, []byte(" \"\"")...)
 | 
			
		||||
 | 
			
		||||
	return newText
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) NormalText(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	escapeSpecialChars(out, text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) DocumentHeader(out *bytes.Buffer) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *roffRenderer) DocumentFooter(out *bytes.Buffer) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func needsBackslash(c byte) bool {
 | 
			
		||||
	for _, r := range []byte("-_&\\~") {
 | 
			
		||||
		if c == r {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func escapeSpecialChars(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	for i := 0; i < len(text); i++ {
 | 
			
		||||
		// escape initial apostrophe or period
 | 
			
		||||
		if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
 | 
			
		||||
			out.WriteString("\\&")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// directly copy normal characters
 | 
			
		||||
		org := i
 | 
			
		||||
 | 
			
		||||
		for i < len(text) && !needsBackslash(text[i]) {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
		if i > org {
 | 
			
		||||
			out.Write(text[org:i])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// escape a character
 | 
			
		||||
		if i >= len(text) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		out.WriteByte('\\')
 | 
			
		||||
		out.WriteByte(text[i])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
Blackfriday is distributed under the Simplified BSD License:
 | 
			
		||||
 | 
			
		||||
> Copyright © 2011 Russ Ross
 | 
			
		||||
> All rights reserved.
 | 
			
		||||
>
 | 
			
		||||
> Redistribution and use in source and binary forms, with or without
 | 
			
		||||
> modification, are permitted provided that the following conditions
 | 
			
		||||
> are met:
 | 
			
		||||
>
 | 
			
		||||
> 1.  Redistributions of source code must retain the above copyright
 | 
			
		||||
>     notice, this list of conditions and the following disclaimer.
 | 
			
		||||
>
 | 
			
		||||
> 2.  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.
 | 
			
		||||
>
 | 
			
		||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
			
		||||
> "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 HOLDER OR CONTRIBUTORS 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,267 @@
 | 
			
		|||
Blackfriday [](https://travis-ci.org/russross/blackfriday) [](https://godoc.org/github.com/russross/blackfriday)
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
 | 
			
		||||
is paranoid about its input (so you can safely feed it user-supplied
 | 
			
		||||
data), it is fast, it supports common extensions (tables, smart
 | 
			
		||||
punctuation substitutions, etc.), and it is safe for all utf-8
 | 
			
		||||
(unicode) input.
 | 
			
		||||
 | 
			
		||||
HTML output is currently supported, along with Smartypants
 | 
			
		||||
extensions. An experimental LaTeX output engine is also included.
 | 
			
		||||
 | 
			
		||||
It started as a translation from C of [Sundown][3].
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Installation
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
Blackfriday is compatible with Go 1. If you are using an older
 | 
			
		||||
release of Go, consider using v1.1 of blackfriday, which was based
 | 
			
		||||
on the last stable release of Go prior to Go 1. You can find it as a
 | 
			
		||||
tagged commit on github.
 | 
			
		||||
 | 
			
		||||
With Go 1 and git installed:
 | 
			
		||||
 | 
			
		||||
    go get github.com/russross/blackfriday
 | 
			
		||||
 | 
			
		||||
will download, compile, and install the package into your `$GOPATH`
 | 
			
		||||
directory hierarchy. Alternatively, you can achieve the same if you
 | 
			
		||||
import it into a project:
 | 
			
		||||
 | 
			
		||||
    import "github.com/russross/blackfriday"
 | 
			
		||||
 | 
			
		||||
and `go get` without parameters.
 | 
			
		||||
 | 
			
		||||
Usage
 | 
			
		||||
-----
 | 
			
		||||
 | 
			
		||||
For basic usage, it is as simple as getting your input into a byte
 | 
			
		||||
slice and calling:
 | 
			
		||||
 | 
			
		||||
    output := blackfriday.MarkdownBasic(input)
 | 
			
		||||
 | 
			
		||||
This renders it with no extensions enabled. To get a more useful
 | 
			
		||||
feature set, use this instead:
 | 
			
		||||
 | 
			
		||||
    output := blackfriday.MarkdownCommon(input)
 | 
			
		||||
 | 
			
		||||
### Sanitize untrusted content
 | 
			
		||||
 | 
			
		||||
Blackfriday itself does nothing to protect against malicious content. If you are
 | 
			
		||||
dealing with user-supplied markdown, we recommend running blackfriday's output
 | 
			
		||||
through HTML sanitizer such as
 | 
			
		||||
[Bluemonday](https://github.com/microcosm-cc/bluemonday).
 | 
			
		||||
 | 
			
		||||
Here's an example of simple usage of blackfriday together with bluemonday:
 | 
			
		||||
 | 
			
		||||
``` go
 | 
			
		||||
import (
 | 
			
		||||
    "github.com/microcosm-cc/bluemonday"
 | 
			
		||||
    "github.com/russross/blackfriday"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ...
 | 
			
		||||
unsafe := blackfriday.MarkdownCommon(input)
 | 
			
		||||
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Custom options
 | 
			
		||||
 | 
			
		||||
If you want to customize the set of options, first get a renderer
 | 
			
		||||
(currently either the HTML or LaTeX output engines), then use it to
 | 
			
		||||
call the more general `Markdown` function. For examples, see the
 | 
			
		||||
implementations of `MarkdownBasic` and `MarkdownCommon` in
 | 
			
		||||
`markdown.go`.
 | 
			
		||||
 | 
			
		||||
You can also check out `blackfriday-tool` for a more complete example
 | 
			
		||||
of how to use it. Download and install it using:
 | 
			
		||||
 | 
			
		||||
    go get github.com/russross/blackfriday-tool
 | 
			
		||||
 | 
			
		||||
This is a simple command-line tool that allows you to process a
 | 
			
		||||
markdown file using a standalone program.  You can also browse the
 | 
			
		||||
source directly on github if you are just looking for some example
 | 
			
		||||
code:
 | 
			
		||||
 | 
			
		||||
* <http://github.com/russross/blackfriday-tool>
 | 
			
		||||
 | 
			
		||||
Note that if you have not already done so, installing
 | 
			
		||||
`blackfriday-tool` will be sufficient to download and install
 | 
			
		||||
blackfriday in addition to the tool itself. The tool binary will be
 | 
			
		||||
installed in `$GOPATH/bin`.  This is a statically-linked binary that
 | 
			
		||||
can be copied to wherever you need it without worrying about
 | 
			
		||||
dependencies and library versions.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Features
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
All features of Sundown are supported, including:
 | 
			
		||||
 | 
			
		||||
*   **Compatibility**. The Markdown v1.0.3 test suite passes with
 | 
			
		||||
    the `--tidy` option.  Without `--tidy`, the differences are
 | 
			
		||||
    mostly in whitespace and entity escaping, where blackfriday is
 | 
			
		||||
    more consistent and cleaner.
 | 
			
		||||
 | 
			
		||||
*   **Common extensions**, including table support, fenced code
 | 
			
		||||
    blocks, autolinks, strikethroughs, non-strict emphasis, etc.
 | 
			
		||||
 | 
			
		||||
*   **Safety**. Blackfriday is paranoid when parsing, making it safe
 | 
			
		||||
    to feed untrusted user input without fear of bad things
 | 
			
		||||
    happening. The test suite stress tests this and there are no
 | 
			
		||||
    known inputs that make it crash.  If you find one, please let me
 | 
			
		||||
    know and send me the input that does it.
 | 
			
		||||
 | 
			
		||||
    NOTE: "safety" in this context means *runtime safety only*. In order to
 | 
			
		||||
    protect yourself against JavaScript injection in untrusted content, see
 | 
			
		||||
    [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
 | 
			
		||||
 | 
			
		||||
*   **Fast processing**. It is fast enough to render on-demand in
 | 
			
		||||
    most web applications without having to cache the output.
 | 
			
		||||
 | 
			
		||||
*   **Thread safety**. You can run multiple parsers in different
 | 
			
		||||
    goroutines without ill effect. There is no dependence on global
 | 
			
		||||
    shared state.
 | 
			
		||||
 | 
			
		||||
*   **Minimal dependencies**. Blackfriday only depends on standard
 | 
			
		||||
    library packages in Go. The source code is pretty
 | 
			
		||||
    self-contained, so it is easy to add to any project, including
 | 
			
		||||
    Google App Engine projects.
 | 
			
		||||
 | 
			
		||||
*   **Standards compliant**. Output successfully validates using the
 | 
			
		||||
    W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Extensions
 | 
			
		||||
----------
 | 
			
		||||
 | 
			
		||||
In addition to the standard markdown syntax, this package
 | 
			
		||||
implements the following extensions:
 | 
			
		||||
 | 
			
		||||
*   **Intra-word emphasis supression**. The `_` character is
 | 
			
		||||
    commonly used inside words when discussing code, so having
 | 
			
		||||
    markdown interpret it as an emphasis command is usually the
 | 
			
		||||
    wrong thing. Blackfriday lets you treat all emphasis markers as
 | 
			
		||||
    normal characters when they occur inside a word.
 | 
			
		||||
 | 
			
		||||
*   **Tables**. Tables can be created by drawing them in the input
 | 
			
		||||
    using a simple syntax:
 | 
			
		||||
 | 
			
		||||
    ```
 | 
			
		||||
    Name    | Age
 | 
			
		||||
    --------|------
 | 
			
		||||
    Bob     | 27
 | 
			
		||||
    Alice   | 23
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
*   **Fenced code blocks**. In addition to the normal 4-space
 | 
			
		||||
    indentation to mark code blocks, you can explicitly mark them
 | 
			
		||||
    and supply a language (to make syntax highlighting simple). Just
 | 
			
		||||
    mark it like this:
 | 
			
		||||
 | 
			
		||||
        ``` go
 | 
			
		||||
        func getTrue() bool {
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
        ```
 | 
			
		||||
 | 
			
		||||
    You can use 3 or more backticks to mark the beginning of the
 | 
			
		||||
    block, and the same number to mark the end of the block.
 | 
			
		||||
 | 
			
		||||
*   **Definition lists**. A simple definition list is made of a single-line
 | 
			
		||||
    term followed by a colon and the definition for that term.
 | 
			
		||||
 | 
			
		||||
        Cat
 | 
			
		||||
        : Fluffy animal everyone likes
 | 
			
		||||
        
 | 
			
		||||
        Internet
 | 
			
		||||
        : Vector of transmission for pictures of cats
 | 
			
		||||
 | 
			
		||||
    Terms must be separated from the previous definition by a blank line.
 | 
			
		||||
 | 
			
		||||
*   **Footnotes**. A marker in the text that will become a superscript number;
 | 
			
		||||
    a footnote definition that will be placed in a list of footnotes at the
 | 
			
		||||
    end of the document. A footnote looks like this:
 | 
			
		||||
 | 
			
		||||
        This is a footnote.[^1]
 | 
			
		||||
        
 | 
			
		||||
        [^1]: the footnote text.
 | 
			
		||||
 | 
			
		||||
*   **Autolinking**. Blackfriday can find URLs that have not been
 | 
			
		||||
    explicitly marked as links and turn them into links.
 | 
			
		||||
 | 
			
		||||
*   **Strikethrough**. Use two tildes (`~~`) to mark text that
 | 
			
		||||
    should be crossed out.
 | 
			
		||||
 | 
			
		||||
*   **Hard line breaks**. With this extension enabled (it is off by
 | 
			
		||||
    default in the `MarkdownBasic` and `MarkdownCommon` convenience
 | 
			
		||||
    functions), newlines in the input translate into line breaks in
 | 
			
		||||
    the output.
 | 
			
		||||
 | 
			
		||||
*   **Smart quotes**. Smartypants-style punctuation substitution is
 | 
			
		||||
    supported, turning normal double- and single-quote marks into
 | 
			
		||||
    curly quotes, etc.
 | 
			
		||||
 | 
			
		||||
*   **LaTeX-style dash parsing** is an additional option, where `--`
 | 
			
		||||
    is translated into `–`, and `---` is translated into
 | 
			
		||||
    `—`. This differs from most smartypants processors, which
 | 
			
		||||
    turn a single hyphen into an ndash and a double hyphen into an
 | 
			
		||||
    mdash.
 | 
			
		||||
 | 
			
		||||
*   **Smart fractions**, where anything that looks like a fraction
 | 
			
		||||
    is translated into suitable HTML (instead of just a few special
 | 
			
		||||
    cases like most smartypant processors). For example, `4/5`
 | 
			
		||||
    becomes `<sup>4</sup>⁄<sub>5</sub>`, which renders as
 | 
			
		||||
    <sup>4</sup>⁄<sub>5</sub>.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Other renderers
 | 
			
		||||
---------------
 | 
			
		||||
 | 
			
		||||
Blackfriday is structured to allow alternative rendering engines. Here
 | 
			
		||||
are a few of note:
 | 
			
		||||
 | 
			
		||||
*   [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
 | 
			
		||||
    provides a GitHub Flavored Markdown renderer with fenced code block
 | 
			
		||||
    highlighting, clickable header anchor links.
 | 
			
		||||
 | 
			
		||||
    It's not customizable, and its goal is to produce HTML output
 | 
			
		||||
    equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
 | 
			
		||||
    except the rendering is performed locally.
 | 
			
		||||
 | 
			
		||||
*   [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
 | 
			
		||||
    but for markdown.
 | 
			
		||||
 | 
			
		||||
*   LaTeX output: renders output as LaTeX. This is currently part of the
 | 
			
		||||
    main Blackfriday repository, but may be split into its own project
 | 
			
		||||
    in the future. If you are interested in owning and maintaining the
 | 
			
		||||
    LaTeX output component, please be in touch.
 | 
			
		||||
 | 
			
		||||
    It renders some basic documents, but is only experimental at this
 | 
			
		||||
    point. In particular, it does not do any inline escaping, so input
 | 
			
		||||
    that happens to look like LaTeX code will be passed through without
 | 
			
		||||
    modification.
 | 
			
		||||
    
 | 
			
		||||
*   [Md2Vim](https://github.com/FooSoft/md2vim): transforms markdown files into vimdoc format.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Todo
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
*   More unit testing
 | 
			
		||||
*   Improve unicode support. It does not understand all unicode
 | 
			
		||||
    rules (about what constitutes a letter, a punctuation symbol,
 | 
			
		||||
    etc.), so it may fail to detect word boundaries correctly in
 | 
			
		||||
    some instances. It is safe on all utf-8 input.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
License
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   [1]: http://daringfireball.net/projects/markdown/ "Markdown"
 | 
			
		||||
   [2]: http://golang.org/ "Go Language"
 | 
			
		||||
   [3]: https://github.com/vmg/sundown "Sundown"
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,949 @@
 | 
			
		|||
//
 | 
			
		||||
// Blackfriday Markdown Processor
 | 
			
		||||
// Available at http://github.com/russross/blackfriday
 | 
			
		||||
//
 | 
			
		||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
 | 
			
		||||
// Distributed under the Simplified BSD License.
 | 
			
		||||
// See README.md for details.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// HTML rendering backend
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
package blackfriday
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Html renderer configuration options.
 | 
			
		||||
const (
 | 
			
		||||
	HTML_SKIP_HTML                 = 1 << iota // skip preformatted HTML blocks
 | 
			
		||||
	HTML_SKIP_STYLE                            // skip embedded <style> elements
 | 
			
		||||
	HTML_SKIP_IMAGES                           // skip embedded images
 | 
			
		||||
	HTML_SKIP_LINKS                            // skip all links
 | 
			
		||||
	HTML_SAFELINK                              // only link to trusted protocols
 | 
			
		||||
	HTML_NOFOLLOW_LINKS                        // only link with rel="nofollow"
 | 
			
		||||
	HTML_NOREFERRER_LINKS                      // only link with rel="noreferrer"
 | 
			
		||||
	HTML_HREF_TARGET_BLANK                     // add a blank target
 | 
			
		||||
	HTML_TOC                                   // generate a table of contents
 | 
			
		||||
	HTML_OMIT_CONTENTS                         // skip the main contents (for a standalone table of contents)
 | 
			
		||||
	HTML_COMPLETE_PAGE                         // generate a complete HTML page
 | 
			
		||||
	HTML_USE_XHTML                             // generate XHTML output instead of HTML
 | 
			
		||||
	HTML_USE_SMARTYPANTS                       // enable smart punctuation substitutions
 | 
			
		||||
	HTML_SMARTYPANTS_FRACTIONS                 // enable smart fractions (with HTML_USE_SMARTYPANTS)
 | 
			
		||||
	HTML_SMARTYPANTS_DASHES                    // enable smart dashes (with HTML_USE_SMARTYPANTS)
 | 
			
		||||
	HTML_SMARTYPANTS_LATEX_DASHES              // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
 | 
			
		||||
	HTML_SMARTYPANTS_ANGLED_QUOTES             // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
 | 
			
		||||
	HTML_FOOTNOTE_RETURN_LINKS                 // generate a link at the end of a footnote to return to the source
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	alignments = []string{
 | 
			
		||||
		"left",
 | 
			
		||||
		"right",
 | 
			
		||||
		"center",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: improve this regexp to catch all possible entities:
 | 
			
		||||
	htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type HtmlRendererParameters struct {
 | 
			
		||||
	// Prepend this text to each relative URL.
 | 
			
		||||
	AbsolutePrefix string
 | 
			
		||||
	// Add this text to each footnote anchor, to ensure uniqueness.
 | 
			
		||||
	FootnoteAnchorPrefix string
 | 
			
		||||
	// Show this text inside the <a> tag for a footnote return link, if the
 | 
			
		||||
	// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
 | 
			
		||||
	// <sup>[return]</sup> is used.
 | 
			
		||||
	FootnoteReturnLinkContents string
 | 
			
		||||
	// If set, add this text to the front of each Header ID, to ensure
 | 
			
		||||
	// uniqueness.
 | 
			
		||||
	HeaderIDPrefix string
 | 
			
		||||
	// If set, add this text to the back of each Header ID, to ensure uniqueness.
 | 
			
		||||
	HeaderIDSuffix string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Html is a type that implements the Renderer interface for HTML output.
 | 
			
		||||
//
 | 
			
		||||
// Do not create this directly, instead use the HtmlRenderer function.
 | 
			
		||||
type Html struct {
 | 
			
		||||
	flags    int    // HTML_* options
 | 
			
		||||
	closeTag string // how to end singleton tags: either " />" or ">"
 | 
			
		||||
	title    string // document title
 | 
			
		||||
	css      string // optional css file url (used with HTML_COMPLETE_PAGE)
 | 
			
		||||
 | 
			
		||||
	parameters HtmlRendererParameters
 | 
			
		||||
 | 
			
		||||
	// table of contents data
 | 
			
		||||
	tocMarker    int
 | 
			
		||||
	headerCount  int
 | 
			
		||||
	currentLevel int
 | 
			
		||||
	toc          *bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	// Track header IDs to prevent ID collision in a single generation.
 | 
			
		||||
	headerIDs map[string]int
 | 
			
		||||
 | 
			
		||||
	smartypants *smartypantsRenderer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	xhtmlClose = " />"
 | 
			
		||||
	htmlClose  = ">"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HtmlRenderer creates and configures an Html object, which
 | 
			
		||||
// satisfies the Renderer interface.
 | 
			
		||||
//
 | 
			
		||||
// flags is a set of HTML_* options ORed together.
 | 
			
		||||
// title is the title of the document, and css is a URL for the document's
 | 
			
		||||
// stylesheet.
 | 
			
		||||
// title and css are only used when HTML_COMPLETE_PAGE is selected.
 | 
			
		||||
func HtmlRenderer(flags int, title string, css string) Renderer {
 | 
			
		||||
	return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func HtmlRendererWithParameters(flags int, title string,
 | 
			
		||||
	css string, renderParameters HtmlRendererParameters) Renderer {
 | 
			
		||||
	// configure the rendering engine
 | 
			
		||||
	closeTag := htmlClose
 | 
			
		||||
	if flags&HTML_USE_XHTML != 0 {
 | 
			
		||||
		closeTag = xhtmlClose
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if renderParameters.FootnoteReturnLinkContents == "" {
 | 
			
		||||
		renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &Html{
 | 
			
		||||
		flags:      flags,
 | 
			
		||||
		closeTag:   closeTag,
 | 
			
		||||
		title:      title,
 | 
			
		||||
		css:        css,
 | 
			
		||||
		parameters: renderParameters,
 | 
			
		||||
 | 
			
		||||
		headerCount:  0,
 | 
			
		||||
		currentLevel: 0,
 | 
			
		||||
		toc:          new(bytes.Buffer),
 | 
			
		||||
 | 
			
		||||
		headerIDs: make(map[string]int),
 | 
			
		||||
 | 
			
		||||
		smartypants: smartypants(flags),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Using if statements is a bit faster than a switch statement. As the compiler
 | 
			
		||||
// improves, this should be unnecessary this is only worthwhile because
 | 
			
		||||
// attrEscape is the single largest CPU user in normal use.
 | 
			
		||||
// Also tried using map, but that gave a ~3x slowdown.
 | 
			
		||||
func escapeSingleChar(char byte) (string, bool) {
 | 
			
		||||
	if char == '"' {
 | 
			
		||||
		return """, true
 | 
			
		||||
	}
 | 
			
		||||
	if char == '&' {
 | 
			
		||||
		return "&", true
 | 
			
		||||
	}
 | 
			
		||||
	if char == '<' {
 | 
			
		||||
		return "<", true
 | 
			
		||||
	}
 | 
			
		||||
	if char == '>' {
 | 
			
		||||
		return ">", true
 | 
			
		||||
	}
 | 
			
		||||
	return "", false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attrEscape(out *bytes.Buffer, src []byte) {
 | 
			
		||||
	org := 0
 | 
			
		||||
	for i, ch := range src {
 | 
			
		||||
		if entity, ok := escapeSingleChar(ch); ok {
 | 
			
		||||
			if i > org {
 | 
			
		||||
				// copy all the normal characters since the last escape
 | 
			
		||||
				out.Write(src[org:i])
 | 
			
		||||
			}
 | 
			
		||||
			org = i + 1
 | 
			
		||||
			out.WriteString(entity)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if org < len(src) {
 | 
			
		||||
		out.Write(src[org:])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func entityEscapeWithSkip(out *bytes.Buffer, src []byte, skipRanges [][]int) {
 | 
			
		||||
	end := 0
 | 
			
		||||
	for _, rang := range skipRanges {
 | 
			
		||||
		attrEscape(out, src[end:rang[0]])
 | 
			
		||||
		out.Write(src[rang[0]:rang[1]])
 | 
			
		||||
		end = rang[1]
 | 
			
		||||
	}
 | 
			
		||||
	attrEscape(out, src[end:])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) GetFlags() int {
 | 
			
		||||
	return options.flags
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) TitleBlock(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	text = bytes.TrimPrefix(text, []byte("% "))
 | 
			
		||||
	text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1)
 | 
			
		||||
	out.WriteString("<h1 class=\"title\">")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\n</h1>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id string) {
 | 
			
		||||
	marker := out.Len()
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
 | 
			
		||||
	if id == "" && options.flags&HTML_TOC != 0 {
 | 
			
		||||
		id = fmt.Sprintf("toc_%d", options.headerCount)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if id != "" {
 | 
			
		||||
		id = options.ensureUniqueHeaderID(id)
 | 
			
		||||
 | 
			
		||||
		if options.parameters.HeaderIDPrefix != "" {
 | 
			
		||||
			id = options.parameters.HeaderIDPrefix + id
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if options.parameters.HeaderIDSuffix != "" {
 | 
			
		||||
			id = id + options.parameters.HeaderIDSuffix
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id))
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString(fmt.Sprintf("<h%d>", level))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tocMarker := out.Len()
 | 
			
		||||
	if !text() {
 | 
			
		||||
		out.Truncate(marker)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// are we building a table of contents?
 | 
			
		||||
	if options.flags&HTML_TOC != 0 {
 | 
			
		||||
		options.TocHeaderWithAnchor(out.Bytes()[tocMarker:], level, id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteString(fmt.Sprintf("</h%d>\n", level))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	if options.flags&HTML_SKIP_HTML != 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteByte('\n')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) HRule(out *bytes.Buffer) {
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
	out.WriteString("<hr")
 | 
			
		||||
	out.WriteString(options.closeTag)
 | 
			
		||||
	out.WriteByte('\n')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) {
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
 | 
			
		||||
	// parse out the language names/classes
 | 
			
		||||
	count := 0
 | 
			
		||||
	for _, elt := range strings.Fields(lang) {
 | 
			
		||||
		if elt[0] == '.' {
 | 
			
		||||
			elt = elt[1:]
 | 
			
		||||
		}
 | 
			
		||||
		if len(elt) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if count == 0 {
 | 
			
		||||
			out.WriteString("<pre><code class=\"language-")
 | 
			
		||||
		} else {
 | 
			
		||||
			out.WriteByte(' ')
 | 
			
		||||
		}
 | 
			
		||||
		attrEscape(out, []byte(elt))
 | 
			
		||||
		count++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if count == 0 {
 | 
			
		||||
		out.WriteString("<pre><code>")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("\">")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attrEscape(out, text)
 | 
			
		||||
	out.WriteString("</code></pre>\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) BlockQuote(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
	out.WriteString("<blockquote>\n")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("</blockquote>\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
	out.WriteString("<table>\n<thead>\n")
 | 
			
		||||
	out.Write(header)
 | 
			
		||||
	out.WriteString("</thead>\n\n<tbody>\n")
 | 
			
		||||
	out.Write(body)
 | 
			
		||||
	out.WriteString("</tbody>\n</table>\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) TableRow(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
	out.WriteString("<tr>\n")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\n</tr>\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
	switch align {
 | 
			
		||||
	case TABLE_ALIGNMENT_LEFT:
 | 
			
		||||
		out.WriteString("<th align=\"left\">")
 | 
			
		||||
	case TABLE_ALIGNMENT_RIGHT:
 | 
			
		||||
		out.WriteString("<th align=\"right\">")
 | 
			
		||||
	case TABLE_ALIGNMENT_CENTER:
 | 
			
		||||
		out.WriteString("<th align=\"center\">")
 | 
			
		||||
	default:
 | 
			
		||||
		out.WriteString("<th>")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("</th>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) TableCell(out *bytes.Buffer, text []byte, align int) {
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
	switch align {
 | 
			
		||||
	case TABLE_ALIGNMENT_LEFT:
 | 
			
		||||
		out.WriteString("<td align=\"left\">")
 | 
			
		||||
	case TABLE_ALIGNMENT_RIGHT:
 | 
			
		||||
		out.WriteString("<td align=\"right\">")
 | 
			
		||||
	case TABLE_ALIGNMENT_CENTER:
 | 
			
		||||
		out.WriteString("<td align=\"center\">")
 | 
			
		||||
	default:
 | 
			
		||||
		out.WriteString("<td>")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("</td>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) Footnotes(out *bytes.Buffer, text func() bool) {
 | 
			
		||||
	out.WriteString("<div class=\"footnotes\">\n")
 | 
			
		||||
	options.HRule(out)
 | 
			
		||||
	options.List(out, text, LIST_TYPE_ORDERED)
 | 
			
		||||
	out.WriteString("</div>\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
 | 
			
		||||
	if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
 | 
			
		||||
		doubleSpace(out)
 | 
			
		||||
	}
 | 
			
		||||
	slug := slugify(name)
 | 
			
		||||
	out.WriteString(`<li id="`)
 | 
			
		||||
	out.WriteString(`fn:`)
 | 
			
		||||
	out.WriteString(options.parameters.FootnoteAnchorPrefix)
 | 
			
		||||
	out.Write(slug)
 | 
			
		||||
	out.WriteString(`">`)
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	if options.flags&HTML_FOOTNOTE_RETURN_LINKS != 0 {
 | 
			
		||||
		out.WriteString(` <a class="footnote-return" href="#`)
 | 
			
		||||
		out.WriteString(`fnref:`)
 | 
			
		||||
		out.WriteString(options.parameters.FootnoteAnchorPrefix)
 | 
			
		||||
		out.Write(slug)
 | 
			
		||||
		out.WriteString(`">`)
 | 
			
		||||
		out.WriteString(options.parameters.FootnoteReturnLinkContents)
 | 
			
		||||
		out.WriteString(`</a>`)
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteString("</li>\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
 | 
			
		||||
	marker := out.Len()
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
 | 
			
		||||
	if flags&LIST_TYPE_DEFINITION != 0 {
 | 
			
		||||
		out.WriteString("<dl>")
 | 
			
		||||
	} else if flags&LIST_TYPE_ORDERED != 0 {
 | 
			
		||||
		out.WriteString("<ol>")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("<ul>")
 | 
			
		||||
	}
 | 
			
		||||
	if !text() {
 | 
			
		||||
		out.Truncate(marker)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if flags&LIST_TYPE_DEFINITION != 0 {
 | 
			
		||||
		out.WriteString("</dl>\n")
 | 
			
		||||
	} else if flags&LIST_TYPE_ORDERED != 0 {
 | 
			
		||||
		out.WriteString("</ol>\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("</ul>\n")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) {
 | 
			
		||||
	if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) ||
 | 
			
		||||
		flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
 | 
			
		||||
		doubleSpace(out)
 | 
			
		||||
	}
 | 
			
		||||
	if flags&LIST_TYPE_TERM != 0 {
 | 
			
		||||
		out.WriteString("<dt>")
 | 
			
		||||
	} else if flags&LIST_TYPE_DEFINITION != 0 {
 | 
			
		||||
		out.WriteString("<dd>")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("<li>")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	if flags&LIST_TYPE_TERM != 0 {
 | 
			
		||||
		out.WriteString("</dt>\n")
 | 
			
		||||
	} else if flags&LIST_TYPE_DEFINITION != 0 {
 | 
			
		||||
		out.WriteString("</dd>\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("</li>\n")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) {
 | 
			
		||||
	marker := out.Len()
 | 
			
		||||
	doubleSpace(out)
 | 
			
		||||
 | 
			
		||||
	out.WriteString("<p>")
 | 
			
		||||
	if !text() {
 | 
			
		||||
		out.Truncate(marker)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteString("</p>\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) {
 | 
			
		||||
	skipRanges := htmlEntity.FindAllIndex(link, -1)
 | 
			
		||||
	if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) && kind != LINK_TYPE_EMAIL {
 | 
			
		||||
		// mark it but don't link it if it is not a safe link: no smartypants
 | 
			
		||||
		out.WriteString("<tt>")
 | 
			
		||||
		entityEscapeWithSkip(out, link, skipRanges)
 | 
			
		||||
		out.WriteString("</tt>")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteString("<a href=\"")
 | 
			
		||||
	if kind == LINK_TYPE_EMAIL {
 | 
			
		||||
		out.WriteString("mailto:")
 | 
			
		||||
	} else {
 | 
			
		||||
		options.maybeWriteAbsolutePrefix(out, link)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entityEscapeWithSkip(out, link, skipRanges)
 | 
			
		||||
 | 
			
		||||
	var relAttrs []string
 | 
			
		||||
	if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
 | 
			
		||||
		relAttrs = append(relAttrs, "nofollow")
 | 
			
		||||
	}
 | 
			
		||||
	if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
 | 
			
		||||
		relAttrs = append(relAttrs, "noreferrer")
 | 
			
		||||
	}
 | 
			
		||||
	if len(relAttrs) > 0 {
 | 
			
		||||
		out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// blank target only add to external link
 | 
			
		||||
	if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
 | 
			
		||||
		out.WriteString("\" target=\"_blank")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteString("\">")
 | 
			
		||||
 | 
			
		||||
	// Pretty print: if we get an email address as
 | 
			
		||||
	// an actual URI, e.g. `mailto:foo@bar.com`, we don't
 | 
			
		||||
	// want to print the `mailto:` prefix
 | 
			
		||||
	switch {
 | 
			
		||||
	case bytes.HasPrefix(link, []byte("mailto://")):
 | 
			
		||||
		attrEscape(out, link[len("mailto://"):])
 | 
			
		||||
	case bytes.HasPrefix(link, []byte("mailto:")):
 | 
			
		||||
		attrEscape(out, link[len("mailto:"):])
 | 
			
		||||
	default:
 | 
			
		||||
		entityEscapeWithSkip(out, link, skipRanges)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteString("</a>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) CodeSpan(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("<code>")
 | 
			
		||||
	attrEscape(out, text)
 | 
			
		||||
	out.WriteString("</code>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) DoubleEmphasis(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("<strong>")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("</strong>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) Emphasis(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	if len(text) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteString("<em>")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("</em>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) {
 | 
			
		||||
	if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
 | 
			
		||||
		out.WriteString(options.parameters.AbsolutePrefix)
 | 
			
		||||
		if link[0] != '/' {
 | 
			
		||||
			out.WriteByte('/')
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
 | 
			
		||||
	if options.flags&HTML_SKIP_IMAGES != 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteString("<img src=\"")
 | 
			
		||||
	options.maybeWriteAbsolutePrefix(out, link)
 | 
			
		||||
	attrEscape(out, link)
 | 
			
		||||
	out.WriteString("\" alt=\"")
 | 
			
		||||
	if len(alt) > 0 {
 | 
			
		||||
		attrEscape(out, alt)
 | 
			
		||||
	}
 | 
			
		||||
	if len(title) > 0 {
 | 
			
		||||
		out.WriteString("\" title=\"")
 | 
			
		||||
		attrEscape(out, title)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte('"')
 | 
			
		||||
	out.WriteString(options.closeTag)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) LineBreak(out *bytes.Buffer) {
 | 
			
		||||
	out.WriteString("<br")
 | 
			
		||||
	out.WriteString(options.closeTag)
 | 
			
		||||
	out.WriteByte('\n')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
 | 
			
		||||
	if options.flags&HTML_SKIP_LINKS != 0 {
 | 
			
		||||
		// write the link text out but don't link it, just mark it with typewriter font
 | 
			
		||||
		out.WriteString("<tt>")
 | 
			
		||||
		attrEscape(out, content)
 | 
			
		||||
		out.WriteString("</tt>")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) {
 | 
			
		||||
		// write the link text out but don't link it, just mark it with typewriter font
 | 
			
		||||
		out.WriteString("<tt>")
 | 
			
		||||
		attrEscape(out, content)
 | 
			
		||||
		out.WriteString("</tt>")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteString("<a href=\"")
 | 
			
		||||
	options.maybeWriteAbsolutePrefix(out, link)
 | 
			
		||||
	attrEscape(out, link)
 | 
			
		||||
	if len(title) > 0 {
 | 
			
		||||
		out.WriteString("\" title=\"")
 | 
			
		||||
		attrEscape(out, title)
 | 
			
		||||
	}
 | 
			
		||||
	var relAttrs []string
 | 
			
		||||
	if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
 | 
			
		||||
		relAttrs = append(relAttrs, "nofollow")
 | 
			
		||||
	}
 | 
			
		||||
	if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
 | 
			
		||||
		relAttrs = append(relAttrs, "noreferrer")
 | 
			
		||||
	}
 | 
			
		||||
	if len(relAttrs) > 0 {
 | 
			
		||||
		out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// blank target only add to external link
 | 
			
		||||
	if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
 | 
			
		||||
		out.WriteString("\" target=\"_blank")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteString("\">")
 | 
			
		||||
	out.Write(content)
 | 
			
		||||
	out.WriteString("</a>")
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	if options.flags&HTML_SKIP_HTML != 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if options.flags&HTML_SKIP_STYLE != 0 && isHtmlTag(text, "style") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if options.flags&HTML_SKIP_LINKS != 0 && isHtmlTag(text, "a") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) TripleEmphasis(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("<strong><em>")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("</em></strong>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) StrikeThrough(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("<del>")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("</del>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
 | 
			
		||||
	slug := slugify(ref)
 | 
			
		||||
	out.WriteString(`<sup class="footnote-ref" id="`)
 | 
			
		||||
	out.WriteString(`fnref:`)
 | 
			
		||||
	out.WriteString(options.parameters.FootnoteAnchorPrefix)
 | 
			
		||||
	out.Write(slug)
 | 
			
		||||
	out.WriteString(`"><a rel="footnote" href="#`)
 | 
			
		||||
	out.WriteString(`fn:`)
 | 
			
		||||
	out.WriteString(options.parameters.FootnoteAnchorPrefix)
 | 
			
		||||
	out.Write(slug)
 | 
			
		||||
	out.WriteString(`">`)
 | 
			
		||||
	out.WriteString(strconv.Itoa(id))
 | 
			
		||||
	out.WriteString(`</a></sup>`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) Entity(out *bytes.Buffer, entity []byte) {
 | 
			
		||||
	out.Write(entity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) NormalText(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	if options.flags&HTML_USE_SMARTYPANTS != 0 {
 | 
			
		||||
		options.Smartypants(out, text)
 | 
			
		||||
	} else {
 | 
			
		||||
		attrEscape(out, text)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) Smartypants(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	smrt := smartypantsData{false, false}
 | 
			
		||||
 | 
			
		||||
	// first do normal entity escaping
 | 
			
		||||
	var escaped bytes.Buffer
 | 
			
		||||
	attrEscape(&escaped, text)
 | 
			
		||||
	text = escaped.Bytes()
 | 
			
		||||
 | 
			
		||||
	mark := 0
 | 
			
		||||
	for i := 0; i < len(text); i++ {
 | 
			
		||||
		if action := options.smartypants[text[i]]; action != nil {
 | 
			
		||||
			if i > mark {
 | 
			
		||||
				out.Write(text[mark:i])
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			previousChar := byte(0)
 | 
			
		||||
			if i > 0 {
 | 
			
		||||
				previousChar = text[i-1]
 | 
			
		||||
			}
 | 
			
		||||
			i += action(out, &smrt, previousChar, text[i:])
 | 
			
		||||
			mark = i + 1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mark < len(text) {
 | 
			
		||||
		out.Write(text[mark:])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) DocumentHeader(out *bytes.Buffer) {
 | 
			
		||||
	if options.flags&HTML_COMPLETE_PAGE == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ending := ""
 | 
			
		||||
	if options.flags&HTML_USE_XHTML != 0 {
 | 
			
		||||
		out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
 | 
			
		||||
		out.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
 | 
			
		||||
		out.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
 | 
			
		||||
		ending = " /"
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("<!DOCTYPE html>\n")
 | 
			
		||||
		out.WriteString("<html>\n")
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteString("<head>\n")
 | 
			
		||||
	out.WriteString("  <title>")
 | 
			
		||||
	options.NormalText(out, []byte(options.title))
 | 
			
		||||
	out.WriteString("</title>\n")
 | 
			
		||||
	out.WriteString("  <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
 | 
			
		||||
	out.WriteString(VERSION)
 | 
			
		||||
	out.WriteString("\"")
 | 
			
		||||
	out.WriteString(ending)
 | 
			
		||||
	out.WriteString(">\n")
 | 
			
		||||
	out.WriteString("  <meta charset=\"utf-8\"")
 | 
			
		||||
	out.WriteString(ending)
 | 
			
		||||
	out.WriteString(">\n")
 | 
			
		||||
	if options.css != "" {
 | 
			
		||||
		out.WriteString("  <link rel=\"stylesheet\" type=\"text/css\" href=\"")
 | 
			
		||||
		attrEscape(out, []byte(options.css))
 | 
			
		||||
		out.WriteString("\"")
 | 
			
		||||
		out.WriteString(ending)
 | 
			
		||||
		out.WriteString(">\n")
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteString("</head>\n")
 | 
			
		||||
	out.WriteString("<body>\n")
 | 
			
		||||
 | 
			
		||||
	options.tocMarker = out.Len()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) DocumentFooter(out *bytes.Buffer) {
 | 
			
		||||
	// finalize and insert the table of contents
 | 
			
		||||
	if options.flags&HTML_TOC != 0 {
 | 
			
		||||
		options.TocFinalize()
 | 
			
		||||
 | 
			
		||||
		// now we have to insert the table of contents into the document
 | 
			
		||||
		var temp bytes.Buffer
 | 
			
		||||
 | 
			
		||||
		// start by making a copy of everything after the document header
 | 
			
		||||
		temp.Write(out.Bytes()[options.tocMarker:])
 | 
			
		||||
 | 
			
		||||
		// now clear the copied material from the main output buffer
 | 
			
		||||
		out.Truncate(options.tocMarker)
 | 
			
		||||
 | 
			
		||||
		// corner case spacing issue
 | 
			
		||||
		if options.flags&HTML_COMPLETE_PAGE != 0 {
 | 
			
		||||
			out.WriteByte('\n')
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// insert the table of contents
 | 
			
		||||
		out.WriteString("<nav>\n")
 | 
			
		||||
		out.Write(options.toc.Bytes())
 | 
			
		||||
		out.WriteString("</nav>\n")
 | 
			
		||||
 | 
			
		||||
		// corner case spacing issue
 | 
			
		||||
		if options.flags&HTML_COMPLETE_PAGE == 0 && options.flags&HTML_OMIT_CONTENTS == 0 {
 | 
			
		||||
			out.WriteByte('\n')
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// write out everything that came after it
 | 
			
		||||
		if options.flags&HTML_OMIT_CONTENTS == 0 {
 | 
			
		||||
			out.Write(temp.Bytes())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if options.flags&HTML_COMPLETE_PAGE != 0 {
 | 
			
		||||
		out.WriteString("\n</body>\n")
 | 
			
		||||
		out.WriteString("</html>\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) TocHeaderWithAnchor(text []byte, level int, anchor string) {
 | 
			
		||||
	for level > options.currentLevel {
 | 
			
		||||
		switch {
 | 
			
		||||
		case bytes.HasSuffix(options.toc.Bytes(), []byte("</li>\n")):
 | 
			
		||||
			// this sublist can nest underneath a header
 | 
			
		||||
			size := options.toc.Len()
 | 
			
		||||
			options.toc.Truncate(size - len("</li>\n"))
 | 
			
		||||
 | 
			
		||||
		case options.currentLevel > 0:
 | 
			
		||||
			options.toc.WriteString("<li>")
 | 
			
		||||
		}
 | 
			
		||||
		if options.toc.Len() > 0 {
 | 
			
		||||
			options.toc.WriteByte('\n')
 | 
			
		||||
		}
 | 
			
		||||
		options.toc.WriteString("<ul>\n")
 | 
			
		||||
		options.currentLevel++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for level < options.currentLevel {
 | 
			
		||||
		options.toc.WriteString("</ul>")
 | 
			
		||||
		if options.currentLevel > 1 {
 | 
			
		||||
			options.toc.WriteString("</li>\n")
 | 
			
		||||
		}
 | 
			
		||||
		options.currentLevel--
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	options.toc.WriteString("<li><a href=\"#")
 | 
			
		||||
	if anchor != "" {
 | 
			
		||||
		options.toc.WriteString(anchor)
 | 
			
		||||
	} else {
 | 
			
		||||
		options.toc.WriteString("toc_")
 | 
			
		||||
		options.toc.WriteString(strconv.Itoa(options.headerCount))
 | 
			
		||||
	}
 | 
			
		||||
	options.toc.WriteString("\">")
 | 
			
		||||
	options.headerCount++
 | 
			
		||||
 | 
			
		||||
	options.toc.Write(text)
 | 
			
		||||
 | 
			
		||||
	options.toc.WriteString("</a></li>\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) TocHeader(text []byte, level int) {
 | 
			
		||||
	options.TocHeaderWithAnchor(text, level, "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) TocFinalize() {
 | 
			
		||||
	for options.currentLevel > 1 {
 | 
			
		||||
		options.toc.WriteString("</ul></li>\n")
 | 
			
		||||
		options.currentLevel--
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if options.currentLevel > 0 {
 | 
			
		||||
		options.toc.WriteString("</ul>\n")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isHtmlTag(tag []byte, tagname string) bool {
 | 
			
		||||
	found, _ := findHtmlTagPos(tag, tagname)
 | 
			
		||||
	return found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Look for a character, but ignore it when it's in any kind of quotes, it
 | 
			
		||||
// might be JavaScript
 | 
			
		||||
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
 | 
			
		||||
	inSingleQuote := false
 | 
			
		||||
	inDoubleQuote := false
 | 
			
		||||
	inGraveQuote := false
 | 
			
		||||
	i := start
 | 
			
		||||
	for i < len(html) {
 | 
			
		||||
		switch {
 | 
			
		||||
		case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
 | 
			
		||||
			return i
 | 
			
		||||
		case html[i] == '\'':
 | 
			
		||||
			inSingleQuote = !inSingleQuote
 | 
			
		||||
		case html[i] == '"':
 | 
			
		||||
			inDoubleQuote = !inDoubleQuote
 | 
			
		||||
		case html[i] == '`':
 | 
			
		||||
			inGraveQuote = !inGraveQuote
 | 
			
		||||
		}
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	return start
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func findHtmlTagPos(tag []byte, tagname string) (bool, int) {
 | 
			
		||||
	i := 0
 | 
			
		||||
	if i < len(tag) && tag[0] != '<' {
 | 
			
		||||
		return false, -1
 | 
			
		||||
	}
 | 
			
		||||
	i++
 | 
			
		||||
	i = skipSpace(tag, i)
 | 
			
		||||
 | 
			
		||||
	if i < len(tag) && tag[i] == '/' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i = skipSpace(tag, i)
 | 
			
		||||
	j := 0
 | 
			
		||||
	for ; i < len(tag); i, j = i+1, j+1 {
 | 
			
		||||
		if j >= len(tagname) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if strings.ToLower(string(tag[i]))[0] != tagname[j] {
 | 
			
		||||
			return false, -1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if i == len(tag) {
 | 
			
		||||
		return false, -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
 | 
			
		||||
	if rightAngle > i {
 | 
			
		||||
		return true, rightAngle
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func skipUntilChar(text []byte, start int, char byte) int {
 | 
			
		||||
	i := start
 | 
			
		||||
	for i < len(text) && text[i] != char {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	return i
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func skipSpace(tag []byte, i int) int {
 | 
			
		||||
	for i < len(tag) && isspace(tag[i]) {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	return i
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func skipChar(data []byte, start int, char byte) int {
 | 
			
		||||
	i := start
 | 
			
		||||
	for i < len(data) && data[i] == char {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	return i
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doubleSpace(out *bytes.Buffer) {
 | 
			
		||||
	if out.Len() > 0 {
 | 
			
		||||
		out.WriteByte('\n')
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isRelativeLink(link []byte) (yes bool) {
 | 
			
		||||
	// a tag begin with '#'
 | 
			
		||||
	if link[0] == '#' {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// link begin with '/' but not '//', the second maybe a protocol relative link
 | 
			
		||||
	if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// only the root '/'
 | 
			
		||||
	if len(link) == 1 && link[0] == '/' {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// current directory : begin with "./"
 | 
			
		||||
	if bytes.HasPrefix(link, []byte("./")) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// parent directory : begin with "../"
 | 
			
		||||
	if bytes.HasPrefix(link, []byte("../")) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Html) ensureUniqueHeaderID(id string) string {
 | 
			
		||||
	for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] {
 | 
			
		||||
		tmp := fmt.Sprintf("%s-%d", id, count+1)
 | 
			
		||||
 | 
			
		||||
		if _, tmpFound := options.headerIDs[tmp]; !tmpFound {
 | 
			
		||||
			options.headerIDs[id] = count + 1
 | 
			
		||||
			id = tmp
 | 
			
		||||
		} else {
 | 
			
		||||
			id = id + "-1"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, found := options.headerIDs[id]; !found {
 | 
			
		||||
		options.headerIDs[id] = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return id
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,332 @@
 | 
			
		|||
//
 | 
			
		||||
// Blackfriday Markdown Processor
 | 
			
		||||
// Available at http://github.com/russross/blackfriday
 | 
			
		||||
//
 | 
			
		||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
 | 
			
		||||
// Distributed under the Simplified BSD License.
 | 
			
		||||
// See README.md for details.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// LaTeX rendering backend
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
package blackfriday
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Latex is a type that implements the Renderer interface for LaTeX output.
 | 
			
		||||
//
 | 
			
		||||
// Do not create this directly, instead use the LatexRenderer function.
 | 
			
		||||
type Latex struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LatexRenderer creates and configures a Latex object, which
 | 
			
		||||
// satisfies the Renderer interface.
 | 
			
		||||
//
 | 
			
		||||
// flags is a set of LATEX_* options ORed together (currently no such options
 | 
			
		||||
// are defined).
 | 
			
		||||
func LatexRenderer(flags int) Renderer {
 | 
			
		||||
	return &Latex{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) GetFlags() int {
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// render code chunks using verbatim, or listings if we have a language
 | 
			
		||||
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) {
 | 
			
		||||
	if lang == "" {
 | 
			
		||||
		out.WriteString("\n\\begin{verbatim}\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("\n\\begin{lstlisting}[language=")
 | 
			
		||||
		out.WriteString(lang)
 | 
			
		||||
		out.WriteString("]\n")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	if lang == "" {
 | 
			
		||||
		out.WriteString("\n\\end{verbatim}\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("\n\\end{lstlisting}\n")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\n\\begin{quotation}\n")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\n\\end{quotation}\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	// a pretty lame thing to do...
 | 
			
		||||
	out.WriteString("\n\\begin{verbatim}\n")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("\n\\end{verbatim}\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) {
 | 
			
		||||
	marker := out.Len()
 | 
			
		||||
 | 
			
		||||
	switch level {
 | 
			
		||||
	case 1:
 | 
			
		||||
		out.WriteString("\n\\section{")
 | 
			
		||||
	case 2:
 | 
			
		||||
		out.WriteString("\n\\subsection{")
 | 
			
		||||
	case 3:
 | 
			
		||||
		out.WriteString("\n\\subsubsection{")
 | 
			
		||||
	case 4:
 | 
			
		||||
		out.WriteString("\n\\paragraph{")
 | 
			
		||||
	case 5:
 | 
			
		||||
		out.WriteString("\n\\subparagraph{")
 | 
			
		||||
	case 6:
 | 
			
		||||
		out.WriteString("\n\\textbf{")
 | 
			
		||||
	}
 | 
			
		||||
	if !text() {
 | 
			
		||||
		out.Truncate(marker)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteString("}\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) HRule(out *bytes.Buffer) {
 | 
			
		||||
	out.WriteString("\n\\HRule\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) {
 | 
			
		||||
	marker := out.Len()
 | 
			
		||||
	if flags&LIST_TYPE_ORDERED != 0 {
 | 
			
		||||
		out.WriteString("\n\\begin{enumerate}\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("\n\\begin{itemize}\n")
 | 
			
		||||
	}
 | 
			
		||||
	if !text() {
 | 
			
		||||
		out.Truncate(marker)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if flags&LIST_TYPE_ORDERED != 0 {
 | 
			
		||||
		out.WriteString("\n\\end{enumerate}\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("\n\\end{itemize}\n")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) {
 | 
			
		||||
	out.WriteString("\n\\item ")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) {
 | 
			
		||||
	marker := out.Len()
 | 
			
		||||
	out.WriteString("\n")
 | 
			
		||||
	if !text() {
 | 
			
		||||
		out.Truncate(marker)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteString("\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
 | 
			
		||||
	out.WriteString("\n\\begin{tabular}{")
 | 
			
		||||
	for _, elt := range columnData {
 | 
			
		||||
		switch elt {
 | 
			
		||||
		case TABLE_ALIGNMENT_LEFT:
 | 
			
		||||
			out.WriteByte('l')
 | 
			
		||||
		case TABLE_ALIGNMENT_RIGHT:
 | 
			
		||||
			out.WriteByte('r')
 | 
			
		||||
		default:
 | 
			
		||||
			out.WriteByte('c')
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteString("}\n")
 | 
			
		||||
	out.Write(header)
 | 
			
		||||
	out.WriteString(" \\\\\n\\hline\n")
 | 
			
		||||
	out.Write(body)
 | 
			
		||||
	out.WriteString("\n\\end{tabular}\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) TableRow(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	if out.Len() > 0 {
 | 
			
		||||
		out.WriteString(" \\\\\n")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
 | 
			
		||||
	if out.Len() > 0 {
 | 
			
		||||
		out.WriteString(" & ")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) {
 | 
			
		||||
	if out.Len() > 0 {
 | 
			
		||||
		out.WriteString(" & ")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: this
 | 
			
		||||
func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) {
 | 
			
		||||
	out.WriteString("\\href{")
 | 
			
		||||
	if kind == LINK_TYPE_EMAIL {
 | 
			
		||||
		out.WriteString("mailto:")
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(link)
 | 
			
		||||
	out.WriteString("}{")
 | 
			
		||||
	out.Write(link)
 | 
			
		||||
	out.WriteString("}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\\texttt{")
 | 
			
		||||
	escapeSpecialChars(out, text)
 | 
			
		||||
	out.WriteString("}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\\textbf{")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\\textit{")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
 | 
			
		||||
	if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
 | 
			
		||||
		// treat it like a link
 | 
			
		||||
		out.WriteString("\\href{")
 | 
			
		||||
		out.Write(link)
 | 
			
		||||
		out.WriteString("}{")
 | 
			
		||||
		out.Write(alt)
 | 
			
		||||
		out.WriteString("}")
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteString("\\includegraphics{")
 | 
			
		||||
		out.Write(link)
 | 
			
		||||
		out.WriteString("}")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) LineBreak(out *bytes.Buffer) {
 | 
			
		||||
	out.WriteString(" \\\\\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
 | 
			
		||||
	out.WriteString("\\href{")
 | 
			
		||||
	out.Write(link)
 | 
			
		||||
	out.WriteString("}{")
 | 
			
		||||
	out.Write(content)
 | 
			
		||||
	out.WriteString("}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\\textbf{\\textit{")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("}}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	out.WriteString("\\sout{")
 | 
			
		||||
	out.Write(text)
 | 
			
		||||
	out.WriteString("}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: this
 | 
			
		||||
func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func needsBackslash(c byte) bool {
 | 
			
		||||
	for _, r := range []byte("_{}%$&\\~#") {
 | 
			
		||||
		if c == r {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func escapeSpecialChars(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	for i := 0; i < len(text); i++ {
 | 
			
		||||
		// directly copy normal characters
 | 
			
		||||
		org := i
 | 
			
		||||
 | 
			
		||||
		for i < len(text) && !needsBackslash(text[i]) {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
		if i > org {
 | 
			
		||||
			out.Write(text[org:i])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// escape a character
 | 
			
		||||
		if i >= len(text) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		out.WriteByte('\\')
 | 
			
		||||
		out.WriteByte(text[i])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) Entity(out *bytes.Buffer, entity []byte) {
 | 
			
		||||
	// TODO: convert this into a unicode character or something
 | 
			
		||||
	out.Write(entity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) NormalText(out *bytes.Buffer, text []byte) {
 | 
			
		||||
	escapeSpecialChars(out, text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// header and footer
 | 
			
		||||
func (options *Latex) DocumentHeader(out *bytes.Buffer) {
 | 
			
		||||
	out.WriteString("\\documentclass{article}\n")
 | 
			
		||||
	out.WriteString("\n")
 | 
			
		||||
	out.WriteString("\\usepackage{graphicx}\n")
 | 
			
		||||
	out.WriteString("\\usepackage{listings}\n")
 | 
			
		||||
	out.WriteString("\\usepackage[margin=1in]{geometry}\n")
 | 
			
		||||
	out.WriteString("\\usepackage[utf8]{inputenc}\n")
 | 
			
		||||
	out.WriteString("\\usepackage{verbatim}\n")
 | 
			
		||||
	out.WriteString("\\usepackage[normalem]{ulem}\n")
 | 
			
		||||
	out.WriteString("\\usepackage{hyperref}\n")
 | 
			
		||||
	out.WriteString("\n")
 | 
			
		||||
	out.WriteString("\\hypersetup{colorlinks,%\n")
 | 
			
		||||
	out.WriteString("  citecolor=black,%\n")
 | 
			
		||||
	out.WriteString("  filecolor=black,%\n")
 | 
			
		||||
	out.WriteString("  linkcolor=black,%\n")
 | 
			
		||||
	out.WriteString("  urlcolor=black,%\n")
 | 
			
		||||
	out.WriteString("  pdfstartview=FitH,%\n")
 | 
			
		||||
	out.WriteString("  breaklinks=true,%\n")
 | 
			
		||||
	out.WriteString("  pdfauthor={Blackfriday Markdown Processor v")
 | 
			
		||||
	out.WriteString(VERSION)
 | 
			
		||||
	out.WriteString("}}\n")
 | 
			
		||||
	out.WriteString("\n")
 | 
			
		||||
	out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
 | 
			
		||||
	out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
 | 
			
		||||
	out.WriteString("\\parindent=0pt\n")
 | 
			
		||||
	out.WriteString("\n")
 | 
			
		||||
	out.WriteString("\\begin{document}\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (options *Latex) DocumentFooter(out *bytes.Buffer) {
 | 
			
		||||
	out.WriteString("\n\\end{document}\n")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,926 @@
 | 
			
		|||
//
 | 
			
		||||
// Blackfriday Markdown Processor
 | 
			
		||||
// Available at http://github.com/russross/blackfriday
 | 
			
		||||
//
 | 
			
		||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
 | 
			
		||||
// Distributed under the Simplified BSD License.
 | 
			
		||||
// See README.md for details.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// Markdown parsing and processing
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// Blackfriday markdown processor.
 | 
			
		||||
//
 | 
			
		||||
// Translates plain text with simple formatting rules into HTML or LaTeX.
 | 
			
		||||
package blackfriday
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const VERSION = "1.5"
 | 
			
		||||
 | 
			
		||||
// These are the supported markdown parsing extensions.
 | 
			
		||||
// OR these values together to select multiple extensions.
 | 
			
		||||
const (
 | 
			
		||||
	EXTENSION_NO_INTRA_EMPHASIS          = 1 << iota // ignore emphasis markers inside words
 | 
			
		||||
	EXTENSION_TABLES                                 // render tables
 | 
			
		||||
	EXTENSION_FENCED_CODE                            // render fenced code blocks
 | 
			
		||||
	EXTENSION_AUTOLINK                               // detect embedded URLs that are not explicitly marked
 | 
			
		||||
	EXTENSION_STRIKETHROUGH                          // strikethrough text using ~~test~~
 | 
			
		||||
	EXTENSION_LAX_HTML_BLOCKS                        // loosen up HTML block parsing rules
 | 
			
		||||
	EXTENSION_SPACE_HEADERS                          // be strict about prefix header rules
 | 
			
		||||
	EXTENSION_HARD_LINE_BREAK                        // translate newlines into line breaks
 | 
			
		||||
	EXTENSION_TAB_SIZE_EIGHT                         // expand tabs to eight spaces instead of four
 | 
			
		||||
	EXTENSION_FOOTNOTES                              // Pandoc-style footnotes
 | 
			
		||||
	EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK             // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
 | 
			
		||||
	EXTENSION_HEADER_IDS                             // specify header IDs  with {#id}
 | 
			
		||||
	EXTENSION_TITLEBLOCK                             // Titleblock ala pandoc
 | 
			
		||||
	EXTENSION_AUTO_HEADER_IDS                        // Create the header ID from the text
 | 
			
		||||
	EXTENSION_BACKSLASH_LINE_BREAK                   // translate trailing backslashes into line breaks
 | 
			
		||||
	EXTENSION_DEFINITION_LISTS                       // render definition lists
 | 
			
		||||
 | 
			
		||||
	commonHtmlFlags = 0 |
 | 
			
		||||
		HTML_USE_XHTML |
 | 
			
		||||
		HTML_USE_SMARTYPANTS |
 | 
			
		||||
		HTML_SMARTYPANTS_FRACTIONS |
 | 
			
		||||
		HTML_SMARTYPANTS_DASHES |
 | 
			
		||||
		HTML_SMARTYPANTS_LATEX_DASHES
 | 
			
		||||
 | 
			
		||||
	commonExtensions = 0 |
 | 
			
		||||
		EXTENSION_NO_INTRA_EMPHASIS |
 | 
			
		||||
		EXTENSION_TABLES |
 | 
			
		||||
		EXTENSION_FENCED_CODE |
 | 
			
		||||
		EXTENSION_AUTOLINK |
 | 
			
		||||
		EXTENSION_STRIKETHROUGH |
 | 
			
		||||
		EXTENSION_SPACE_HEADERS |
 | 
			
		||||
		EXTENSION_HEADER_IDS |
 | 
			
		||||
		EXTENSION_BACKSLASH_LINE_BREAK |
 | 
			
		||||
		EXTENSION_DEFINITION_LISTS
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// These are the possible flag values for the link renderer.
 | 
			
		||||
// Only a single one of these values will be used; they are not ORed together.
 | 
			
		||||
// These are mostly of interest if you are writing a new output format.
 | 
			
		||||
const (
 | 
			
		||||
	LINK_TYPE_NOT_AUTOLINK = iota
 | 
			
		||||
	LINK_TYPE_NORMAL
 | 
			
		||||
	LINK_TYPE_EMAIL
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// These are the possible flag values for the ListItem renderer.
 | 
			
		||||
// Multiple flag values may be ORed together.
 | 
			
		||||
// These are mostly of interest if you are writing a new output format.
 | 
			
		||||
const (
 | 
			
		||||
	LIST_TYPE_ORDERED = 1 << iota
 | 
			
		||||
	LIST_TYPE_DEFINITION
 | 
			
		||||
	LIST_TYPE_TERM
 | 
			
		||||
	LIST_ITEM_CONTAINS_BLOCK
 | 
			
		||||
	LIST_ITEM_BEGINNING_OF_LIST
 | 
			
		||||
	LIST_ITEM_END_OF_LIST
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// These are the possible flag values for the table cell renderer.
 | 
			
		||||
// Only a single one of these values will be used; they are not ORed together.
 | 
			
		||||
// These are mostly of interest if you are writing a new output format.
 | 
			
		||||
const (
 | 
			
		||||
	TABLE_ALIGNMENT_LEFT = 1 << iota
 | 
			
		||||
	TABLE_ALIGNMENT_RIGHT
 | 
			
		||||
	TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// The size of a tab stop.
 | 
			
		||||
const (
 | 
			
		||||
	TAB_SIZE_DEFAULT = 4
 | 
			
		||||
	TAB_SIZE_EIGHT   = 8
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// blockTags is a set of tags that are recognized as HTML block tags.
 | 
			
		||||
// Any of these can be included in markdown text without special escaping.
 | 
			
		||||
var blockTags = map[string]struct{}{
 | 
			
		||||
	"blockquote": {},
 | 
			
		||||
	"del":        {},
 | 
			
		||||
	"div":        {},
 | 
			
		||||
	"dl":         {},
 | 
			
		||||
	"fieldset":   {},
 | 
			
		||||
	"form":       {},
 | 
			
		||||
	"h1":         {},
 | 
			
		||||
	"h2":         {},
 | 
			
		||||
	"h3":         {},
 | 
			
		||||
	"h4":         {},
 | 
			
		||||
	"h5":         {},
 | 
			
		||||
	"h6":         {},
 | 
			
		||||
	"iframe":     {},
 | 
			
		||||
	"ins":        {},
 | 
			
		||||
	"math":       {},
 | 
			
		||||
	"noscript":   {},
 | 
			
		||||
	"ol":         {},
 | 
			
		||||
	"pre":        {},
 | 
			
		||||
	"p":          {},
 | 
			
		||||
	"script":     {},
 | 
			
		||||
	"style":      {},
 | 
			
		||||
	"table":      {},
 | 
			
		||||
	"ul":         {},
 | 
			
		||||
 | 
			
		||||
	// HTML5
 | 
			
		||||
	"address":    {},
 | 
			
		||||
	"article":    {},
 | 
			
		||||
	"aside":      {},
 | 
			
		||||
	"canvas":     {},
 | 
			
		||||
	"figcaption": {},
 | 
			
		||||
	"figure":     {},
 | 
			
		||||
	"footer":     {},
 | 
			
		||||
	"header":     {},
 | 
			
		||||
	"hgroup":     {},
 | 
			
		||||
	"main":       {},
 | 
			
		||||
	"nav":        {},
 | 
			
		||||
	"output":     {},
 | 
			
		||||
	"progress":   {},
 | 
			
		||||
	"section":    {},
 | 
			
		||||
	"video":      {},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Renderer is the rendering interface.
 | 
			
		||||
// This is mostly of interest if you are implementing a new rendering format.
 | 
			
		||||
//
 | 
			
		||||
// When a byte slice is provided, it contains the (rendered) contents of the
 | 
			
		||||
// element.
 | 
			
		||||
//
 | 
			
		||||
// When a callback is provided instead, it will write the contents of the
 | 
			
		||||
// respective element directly to the output buffer and return true on success.
 | 
			
		||||
// If the callback returns false, the rendering function should reset the
 | 
			
		||||
// output buffer as though it had never been called.
 | 
			
		||||
//
 | 
			
		||||
// Currently Html and Latex implementations are provided
 | 
			
		||||
type Renderer interface {
 | 
			
		||||
	// block-level callbacks
 | 
			
		||||
	BlockCode(out *bytes.Buffer, text []byte, lang string)
 | 
			
		||||
	BlockQuote(out *bytes.Buffer, text []byte)
 | 
			
		||||
	BlockHtml(out *bytes.Buffer, text []byte)
 | 
			
		||||
	Header(out *bytes.Buffer, text func() bool, level int, id string)
 | 
			
		||||
	HRule(out *bytes.Buffer)
 | 
			
		||||
	List(out *bytes.Buffer, text func() bool, flags int)
 | 
			
		||||
	ListItem(out *bytes.Buffer, text []byte, flags int)
 | 
			
		||||
	Paragraph(out *bytes.Buffer, text func() bool)
 | 
			
		||||
	Table(out *bytes.Buffer, header []byte, body []byte, columnData []int)
 | 
			
		||||
	TableRow(out *bytes.Buffer, text []byte)
 | 
			
		||||
	TableHeaderCell(out *bytes.Buffer, text []byte, flags int)
 | 
			
		||||
	TableCell(out *bytes.Buffer, text []byte, flags int)
 | 
			
		||||
	Footnotes(out *bytes.Buffer, text func() bool)
 | 
			
		||||
	FootnoteItem(out *bytes.Buffer, name, text []byte, flags int)
 | 
			
		||||
	TitleBlock(out *bytes.Buffer, text []byte)
 | 
			
		||||
 | 
			
		||||
	// Span-level callbacks
 | 
			
		||||
	AutoLink(out *bytes.Buffer, link []byte, kind int)
 | 
			
		||||
	CodeSpan(out *bytes.Buffer, text []byte)
 | 
			
		||||
	DoubleEmphasis(out *bytes.Buffer, text []byte)
 | 
			
		||||
	Emphasis(out *bytes.Buffer, text []byte)
 | 
			
		||||
	Image(out *bytes.Buffer, link []byte, title []byte, alt []byte)
 | 
			
		||||
	LineBreak(out *bytes.Buffer)
 | 
			
		||||
	Link(out *bytes.Buffer, link []byte, title []byte, content []byte)
 | 
			
		||||
	RawHtmlTag(out *bytes.Buffer, tag []byte)
 | 
			
		||||
	TripleEmphasis(out *bytes.Buffer, text []byte)
 | 
			
		||||
	StrikeThrough(out *bytes.Buffer, text []byte)
 | 
			
		||||
	FootnoteRef(out *bytes.Buffer, ref []byte, id int)
 | 
			
		||||
 | 
			
		||||
	// Low-level callbacks
 | 
			
		||||
	Entity(out *bytes.Buffer, entity []byte)
 | 
			
		||||
	NormalText(out *bytes.Buffer, text []byte)
 | 
			
		||||
 | 
			
		||||
	// Header and footer
 | 
			
		||||
	DocumentHeader(out *bytes.Buffer)
 | 
			
		||||
	DocumentFooter(out *bytes.Buffer)
 | 
			
		||||
 | 
			
		||||
	GetFlags() int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Callback functions for inline parsing. One such function is defined
 | 
			
		||||
// for each character that triggers a response when parsing inline data.
 | 
			
		||||
type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int
 | 
			
		||||
 | 
			
		||||
// Parser holds runtime state used by the parser.
 | 
			
		||||
// This is constructed by the Markdown function.
 | 
			
		||||
type parser struct {
 | 
			
		||||
	r              Renderer
 | 
			
		||||
	refOverride    ReferenceOverrideFunc
 | 
			
		||||
	refs           map[string]*reference
 | 
			
		||||
	inlineCallback [256]inlineParser
 | 
			
		||||
	flags          int
 | 
			
		||||
	nesting        int
 | 
			
		||||
	maxNesting     int
 | 
			
		||||
	insideLink     bool
 | 
			
		||||
 | 
			
		||||
	// Footnotes need to be ordered as well as available to quickly check for
 | 
			
		||||
	// presence. If a ref is also a footnote, it's stored both in refs and here
 | 
			
		||||
	// in notes. Slice is nil if footnotes not enabled.
 | 
			
		||||
	notes []*reference
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *parser) getRef(refid string) (ref *reference, found bool) {
 | 
			
		||||
	if p.refOverride != nil {
 | 
			
		||||
		r, overridden := p.refOverride(refid)
 | 
			
		||||
		if overridden {
 | 
			
		||||
			if r == nil {
 | 
			
		||||
				return nil, false
 | 
			
		||||
			}
 | 
			
		||||
			return &reference{
 | 
			
		||||
				link:     []byte(r.Link),
 | 
			
		||||
				title:    []byte(r.Title),
 | 
			
		||||
				noteId:   0,
 | 
			
		||||
				hasBlock: false,
 | 
			
		||||
				text:     []byte(r.Text)}, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// refs are case insensitive
 | 
			
		||||
	ref, found = p.refs[strings.ToLower(refid)]
 | 
			
		||||
	return ref, found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// Public interface
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// Reference represents the details of a link.
 | 
			
		||||
// See the documentation in Options for more details on use-case.
 | 
			
		||||
type Reference struct {
 | 
			
		||||
	// Link is usually the URL the reference points to.
 | 
			
		||||
	Link string
 | 
			
		||||
	// Title is the alternate text describing the link in more detail.
 | 
			
		||||
	Title string
 | 
			
		||||
	// Text is the optional text to override the ref with if the syntax used was
 | 
			
		||||
	// [refid][]
 | 
			
		||||
	Text string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReferenceOverrideFunc is expected to be called with a reference string and
 | 
			
		||||
// return either a valid Reference type that the reference string maps to or
 | 
			
		||||
// nil. If overridden is false, the default reference logic will be executed.
 | 
			
		||||
// See the documentation in Options for more details on use-case.
 | 
			
		||||
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
 | 
			
		||||
 | 
			
		||||
// Options represents configurable overrides and callbacks (in addition to the
 | 
			
		||||
// extension flag set) for configuring a Markdown parse.
 | 
			
		||||
type Options struct {
 | 
			
		||||
	// Extensions is a flag set of bit-wise ORed extension bits. See the
 | 
			
		||||
	// EXTENSION_* flags defined in this package.
 | 
			
		||||
	Extensions int
 | 
			
		||||
 | 
			
		||||
	// ReferenceOverride is an optional function callback that is called every
 | 
			
		||||
	// time a reference is resolved.
 | 
			
		||||
	//
 | 
			
		||||
	// In Markdown, the link reference syntax can be made to resolve a link to
 | 
			
		||||
	// a reference instead of an inline URL, in one of the following ways:
 | 
			
		||||
	//
 | 
			
		||||
	//  * [link text][refid]
 | 
			
		||||
	//  * [refid][]
 | 
			
		||||
	//
 | 
			
		||||
	// Usually, the refid is defined at the bottom of the Markdown document. If
 | 
			
		||||
	// this override function is provided, the refid is passed to the override
 | 
			
		||||
	// function first, before consulting the defined refids at the bottom. If
 | 
			
		||||
	// the override function indicates an override did not occur, the refids at
 | 
			
		||||
	// the bottom will be used to fill in the link details.
 | 
			
		||||
	ReferenceOverride ReferenceOverrideFunc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarkdownBasic is a convenience function for simple rendering.
 | 
			
		||||
// It processes markdown input with no extensions enabled.
 | 
			
		||||
func MarkdownBasic(input []byte) []byte {
 | 
			
		||||
	// set up the HTML renderer
 | 
			
		||||
	htmlFlags := HTML_USE_XHTML
 | 
			
		||||
	renderer := HtmlRenderer(htmlFlags, "", "")
 | 
			
		||||
 | 
			
		||||
	// set up the parser
 | 
			
		||||
	return MarkdownOptions(input, renderer, Options{Extensions: 0})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Call Markdown with most useful extensions enabled
 | 
			
		||||
// MarkdownCommon is a convenience function for simple rendering.
 | 
			
		||||
// It processes markdown input with common extensions enabled, including:
 | 
			
		||||
//
 | 
			
		||||
// * Smartypants processing with smart fractions and LaTeX dashes
 | 
			
		||||
//
 | 
			
		||||
// * Intra-word emphasis suppression
 | 
			
		||||
//
 | 
			
		||||
// * Tables
 | 
			
		||||
//
 | 
			
		||||
// * Fenced code blocks
 | 
			
		||||
//
 | 
			
		||||
// * Autolinking
 | 
			
		||||
//
 | 
			
		||||
// * Strikethrough support
 | 
			
		||||
//
 | 
			
		||||
// * Strict header parsing
 | 
			
		||||
//
 | 
			
		||||
// * Custom Header IDs
 | 
			
		||||
func MarkdownCommon(input []byte) []byte {
 | 
			
		||||
	// set up the HTML renderer
 | 
			
		||||
	renderer := HtmlRenderer(commonHtmlFlags, "", "")
 | 
			
		||||
	return MarkdownOptions(input, renderer, Options{
 | 
			
		||||
		Extensions: commonExtensions})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Markdown is the main rendering function.
 | 
			
		||||
// It parses and renders a block of markdown-encoded text.
 | 
			
		||||
// The supplied Renderer is used to format the output, and extensions dictates
 | 
			
		||||
// which non-standard extensions are enabled.
 | 
			
		||||
//
 | 
			
		||||
// To use the supplied Html or LaTeX renderers, see HtmlRenderer and
 | 
			
		||||
// LatexRenderer, respectively.
 | 
			
		||||
func Markdown(input []byte, renderer Renderer, extensions int) []byte {
 | 
			
		||||
	return MarkdownOptions(input, renderer, Options{
 | 
			
		||||
		Extensions: extensions})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarkdownOptions is just like Markdown but takes additional options through
 | 
			
		||||
// the Options struct.
 | 
			
		||||
func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
 | 
			
		||||
	// no point in parsing if we can't render
 | 
			
		||||
	if renderer == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	extensions := opts.Extensions
 | 
			
		||||
 | 
			
		||||
	// fill in the render structure
 | 
			
		||||
	p := new(parser)
 | 
			
		||||
	p.r = renderer
 | 
			
		||||
	p.flags = extensions
 | 
			
		||||
	p.refOverride = opts.ReferenceOverride
 | 
			
		||||
	p.refs = make(map[string]*reference)
 | 
			
		||||
	p.maxNesting = 16
 | 
			
		||||
	p.insideLink = false
 | 
			
		||||
 | 
			
		||||
	// register inline parsers
 | 
			
		||||
	p.inlineCallback['*'] = emphasis
 | 
			
		||||
	p.inlineCallback['_'] = emphasis
 | 
			
		||||
	if extensions&EXTENSION_STRIKETHROUGH != 0 {
 | 
			
		||||
		p.inlineCallback['~'] = emphasis
 | 
			
		||||
	}
 | 
			
		||||
	p.inlineCallback['`'] = codeSpan
 | 
			
		||||
	p.inlineCallback['\n'] = lineBreak
 | 
			
		||||
	p.inlineCallback['['] = link
 | 
			
		||||
	p.inlineCallback['<'] = leftAngle
 | 
			
		||||
	p.inlineCallback['\\'] = escape
 | 
			
		||||
	p.inlineCallback['&'] = entity
 | 
			
		||||
 | 
			
		||||
	if extensions&EXTENSION_AUTOLINK != 0 {
 | 
			
		||||
		p.inlineCallback[':'] = autoLink
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if extensions&EXTENSION_FOOTNOTES != 0 {
 | 
			
		||||
		p.notes = make([]*reference, 0)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	first := firstPass(p, input)
 | 
			
		||||
	second := secondPass(p, first)
 | 
			
		||||
	return second
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// first pass:
 | 
			
		||||
// - extract references
 | 
			
		||||
// - expand tabs
 | 
			
		||||
// - normalize newlines
 | 
			
		||||
// - copy everything else
 | 
			
		||||
func firstPass(p *parser, input []byte) []byte {
 | 
			
		||||
	var out bytes.Buffer
 | 
			
		||||
	tabSize := TAB_SIZE_DEFAULT
 | 
			
		||||
	if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
 | 
			
		||||
		tabSize = TAB_SIZE_EIGHT
 | 
			
		||||
	}
 | 
			
		||||
	beg, end := 0, 0
 | 
			
		||||
	lastFencedCodeBlockEnd := 0
 | 
			
		||||
	for beg < len(input) { // iterate over lines
 | 
			
		||||
		if end = isReference(p, input[beg:], tabSize); end > 0 {
 | 
			
		||||
			beg += end
 | 
			
		||||
		} else { // skip to the next line
 | 
			
		||||
			end = beg
 | 
			
		||||
			for end < len(input) && input[end] != '\n' && input[end] != '\r' {
 | 
			
		||||
				end++
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if p.flags&EXTENSION_FENCED_CODE != 0 {
 | 
			
		||||
				// track fenced code block boundaries to suppress tab expansion
 | 
			
		||||
				// inside them:
 | 
			
		||||
				if beg >= lastFencedCodeBlockEnd {
 | 
			
		||||
					if i := p.fencedCode(&out, input[beg:], false); i > 0 {
 | 
			
		||||
						lastFencedCodeBlockEnd = beg + i
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// add the line body if present
 | 
			
		||||
			if end > beg {
 | 
			
		||||
				if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
 | 
			
		||||
					out.Write(input[beg:end])
 | 
			
		||||
				} else {
 | 
			
		||||
					expandTabs(&out, input[beg:end], tabSize)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			out.WriteByte('\n')
 | 
			
		||||
 | 
			
		||||
			if end < len(input) && input[end] == '\r' {
 | 
			
		||||
				end++
 | 
			
		||||
			}
 | 
			
		||||
			if end < len(input) && input[end] == '\n' {
 | 
			
		||||
				end++
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			beg = end
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// empty input?
 | 
			
		||||
	if out.Len() == 0 {
 | 
			
		||||
		out.WriteByte('\n')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return out.Bytes()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// second pass: actual rendering
 | 
			
		||||
func secondPass(p *parser, input []byte) []byte {
 | 
			
		||||
	var output bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	p.r.DocumentHeader(&output)
 | 
			
		||||
	p.block(&output, input)
 | 
			
		||||
 | 
			
		||||
	if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 {
 | 
			
		||||
		p.r.Footnotes(&output, func() bool {
 | 
			
		||||
			flags := LIST_ITEM_BEGINNING_OF_LIST
 | 
			
		||||
			for i := 0; i < len(p.notes); i += 1 {
 | 
			
		||||
				ref := p.notes[i]
 | 
			
		||||
				var buf bytes.Buffer
 | 
			
		||||
				if ref.hasBlock {
 | 
			
		||||
					flags |= LIST_ITEM_CONTAINS_BLOCK
 | 
			
		||||
					p.block(&buf, ref.title)
 | 
			
		||||
				} else {
 | 
			
		||||
					p.inline(&buf, ref.title)
 | 
			
		||||
				}
 | 
			
		||||
				p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags)
 | 
			
		||||
				flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p.r.DocumentFooter(&output)
 | 
			
		||||
 | 
			
		||||
	if p.nesting != 0 {
 | 
			
		||||
		panic("Nesting level did not end at zero")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return output.Bytes()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Link references
 | 
			
		||||
//
 | 
			
		||||
// This section implements support for references that (usually) appear
 | 
			
		||||
// as footnotes in a document, and can be referenced anywhere in the document.
 | 
			
		||||
// The basic format is:
 | 
			
		||||
//
 | 
			
		||||
//    [1]: http://www.google.com/ "Google"
 | 
			
		||||
//    [2]: http://www.github.com/ "Github"
 | 
			
		||||
//
 | 
			
		||||
// Anywhere in the document, the reference can be linked by referring to its
 | 
			
		||||
// label, i.e., 1 and 2 in this example, as in:
 | 
			
		||||
//
 | 
			
		||||
//    This library is hosted on [Github][2], a git hosting site.
 | 
			
		||||
//
 | 
			
		||||
// Actual footnotes as specified in Pandoc and supported by some other Markdown
 | 
			
		||||
// libraries such as php-markdown are also taken care of. They look like this:
 | 
			
		||||
//
 | 
			
		||||
//    This sentence needs a bit of further explanation.[^note]
 | 
			
		||||
//
 | 
			
		||||
//    [^note]: This is the explanation.
 | 
			
		||||
//
 | 
			
		||||
// Footnotes should be placed at the end of the document in an ordered list.
 | 
			
		||||
// Inline footnotes such as:
 | 
			
		||||
//
 | 
			
		||||
//    Inline footnotes^[Not supported.] also exist.
 | 
			
		||||
//
 | 
			
		||||
// are not yet supported.
 | 
			
		||||
 | 
			
		||||
// References are parsed and stored in this struct.
 | 
			
		||||
type reference struct {
 | 
			
		||||
	link     []byte
 | 
			
		||||
	title    []byte
 | 
			
		||||
	noteId   int // 0 if not a footnote ref
 | 
			
		||||
	hasBlock bool
 | 
			
		||||
	text     []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *reference) String() string {
 | 
			
		||||
	return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}",
 | 
			
		||||
		r.link, r.title, r.text, r.noteId, r.hasBlock)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check whether or not data starts with a reference link.
 | 
			
		||||
// If so, it is parsed and stored in the list of references
 | 
			
		||||
// (in the render struct).
 | 
			
		||||
// Returns the number of bytes to skip to move past it,
 | 
			
		||||
// or zero if the first line is not a reference.
 | 
			
		||||
func isReference(p *parser, data []byte, tabSize int) int {
 | 
			
		||||
	// up to 3 optional leading spaces
 | 
			
		||||
	if len(data) < 4 {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	i := 0
 | 
			
		||||
	for i < 3 && data[i] == ' ' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	noteId := 0
 | 
			
		||||
 | 
			
		||||
	// id part: anything but a newline between brackets
 | 
			
		||||
	if data[i] != '[' {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	i++
 | 
			
		||||
	if p.flags&EXTENSION_FOOTNOTES != 0 {
 | 
			
		||||
		if i < len(data) && data[i] == '^' {
 | 
			
		||||
			// we can set it to anything here because the proper noteIds will
 | 
			
		||||
			// be assigned later during the second pass. It just has to be != 0
 | 
			
		||||
			noteId = 1
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	idOffset := i
 | 
			
		||||
	for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	if i >= len(data) || data[i] != ']' {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	idEnd := i
 | 
			
		||||
 | 
			
		||||
	// spacer: colon (space | tab)* newline? (space | tab)*
 | 
			
		||||
	i++
 | 
			
		||||
	if i >= len(data) || data[i] != ':' {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	i++
 | 
			
		||||
	for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
 | 
			
		||||
		i++
 | 
			
		||||
		if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	if i >= len(data) {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		linkOffset, linkEnd   int
 | 
			
		||||
		titleOffset, titleEnd int
 | 
			
		||||
		lineEnd               int
 | 
			
		||||
		raw                   []byte
 | 
			
		||||
		hasBlock              bool
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 {
 | 
			
		||||
		linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
 | 
			
		||||
		lineEnd = linkEnd
 | 
			
		||||
	} else {
 | 
			
		||||
		linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
 | 
			
		||||
	}
 | 
			
		||||
	if lineEnd == 0 {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// a valid ref has been found
 | 
			
		||||
 | 
			
		||||
	ref := &reference{
 | 
			
		||||
		noteId:   noteId,
 | 
			
		||||
		hasBlock: hasBlock,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if noteId > 0 {
 | 
			
		||||
		// reusing the link field for the id since footnotes don't have links
 | 
			
		||||
		ref.link = data[idOffset:idEnd]
 | 
			
		||||
		// if footnote, it's not really a title, it's the contained text
 | 
			
		||||
		ref.title = raw
 | 
			
		||||
	} else {
 | 
			
		||||
		ref.link = data[linkOffset:linkEnd]
 | 
			
		||||
		ref.title = data[titleOffset:titleEnd]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// id matches are case-insensitive
 | 
			
		||||
	id := string(bytes.ToLower(data[idOffset:idEnd]))
 | 
			
		||||
 | 
			
		||||
	p.refs[id] = ref
 | 
			
		||||
 | 
			
		||||
	return lineEnd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
 | 
			
		||||
	// link: whitespace-free sequence, optionally between angle brackets
 | 
			
		||||
	if data[i] == '<' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	linkOffset = i
 | 
			
		||||
	if i == len(data) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	linkEnd = i
 | 
			
		||||
	if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
 | 
			
		||||
		linkOffset++
 | 
			
		||||
		linkEnd--
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
 | 
			
		||||
	for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// compute end-of-line
 | 
			
		||||
	if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
 | 
			
		||||
		lineEnd = i
 | 
			
		||||
	}
 | 
			
		||||
	if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
 | 
			
		||||
		lineEnd++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// optional (space|tab)* spacer after a newline
 | 
			
		||||
	if lineEnd > 0 {
 | 
			
		||||
		i = lineEnd + 1
 | 
			
		||||
		for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// optional title: any non-newline sequence enclosed in '"() alone on its line
 | 
			
		||||
	if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
 | 
			
		||||
		i++
 | 
			
		||||
		titleOffset = i
 | 
			
		||||
 | 
			
		||||
		// look for EOL
 | 
			
		||||
		for i < len(data) && data[i] != '\n' && data[i] != '\r' {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
		if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
 | 
			
		||||
			titleEnd = i + 1
 | 
			
		||||
		} else {
 | 
			
		||||
			titleEnd = i
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// step back
 | 
			
		||||
		i--
 | 
			
		||||
		for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
			i--
 | 
			
		||||
		}
 | 
			
		||||
		if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
 | 
			
		||||
			lineEnd = titleEnd
 | 
			
		||||
			titleEnd = i
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The first bit of this logic is the same as (*parser).listItem, but the rest
 | 
			
		||||
// is much simpler. This function simply finds the entire block and shifts it
 | 
			
		||||
// over by one tab if it is indeed a block (just returns the line if it's not).
 | 
			
		||||
// blockEnd is the end of the section in the input buffer, and contents is the
 | 
			
		||||
// extracted text that was shifted over one tab. It will need to be rendered at
 | 
			
		||||
// the end of the document.
 | 
			
		||||
func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
 | 
			
		||||
	if i == 0 || len(data) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// skip leading whitespace on first line
 | 
			
		||||
	for i < len(data) && data[i] == ' ' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	blockStart = i
 | 
			
		||||
 | 
			
		||||
	// find the end of the line
 | 
			
		||||
	blockEnd = i
 | 
			
		||||
	for i < len(data) && data[i-1] != '\n' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get working buffer
 | 
			
		||||
	var raw bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	// put the first line into the working buffer
 | 
			
		||||
	raw.Write(data[blockEnd:i])
 | 
			
		||||
	blockEnd = i
 | 
			
		||||
 | 
			
		||||
	// process the following lines
 | 
			
		||||
	containsBlankLine := false
 | 
			
		||||
 | 
			
		||||
gatherLines:
 | 
			
		||||
	for blockEnd < len(data) {
 | 
			
		||||
		i++
 | 
			
		||||
 | 
			
		||||
		// find the end of this line
 | 
			
		||||
		for i < len(data) && data[i-1] != '\n' {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// if it is an empty line, guess that it is part of this item
 | 
			
		||||
		// and move on to the next line
 | 
			
		||||
		if p.isEmpty(data[blockEnd:i]) > 0 {
 | 
			
		||||
			containsBlankLine = true
 | 
			
		||||
			blockEnd = i
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		n := 0
 | 
			
		||||
		if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
 | 
			
		||||
			// this is the end of the block.
 | 
			
		||||
			// we don't want to include this last line in the index.
 | 
			
		||||
			break gatherLines
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// if there were blank lines before this one, insert a new one now
 | 
			
		||||
		if containsBlankLine {
 | 
			
		||||
			raw.WriteByte('\n')
 | 
			
		||||
			containsBlankLine = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// get rid of that first tab, write to buffer
 | 
			
		||||
		raw.Write(data[blockEnd+n : i])
 | 
			
		||||
		hasBlock = true
 | 
			
		||||
 | 
			
		||||
		blockEnd = i
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if data[blockEnd-1] != '\n' {
 | 
			
		||||
		raw.WriteByte('\n')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contents = raw.Bytes()
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// Miscellaneous helper functions
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// Test if a character is a punctuation symbol.
 | 
			
		||||
// Taken from a private function in regexp in the stdlib.
 | 
			
		||||
func ispunct(c byte) bool {
 | 
			
		||||
	for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
 | 
			
		||||
		if c == r {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test if a character is a whitespace character.
 | 
			
		||||
func isspace(c byte) bool {
 | 
			
		||||
	return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test if a character is letter.
 | 
			
		||||
func isletter(c byte) bool {
 | 
			
		||||
	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test if a character is a letter or a digit.
 | 
			
		||||
// TODO: check when this is looking for ASCII alnum and when it should use unicode
 | 
			
		||||
func isalnum(c byte) bool {
 | 
			
		||||
	return (c >= '0' && c <= '9') || isletter(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
 | 
			
		||||
// always ends output with a newline
 | 
			
		||||
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
 | 
			
		||||
	// first, check for common cases: no tabs, or only tabs at beginning of line
 | 
			
		||||
	i, prefix := 0, 0
 | 
			
		||||
	slowcase := false
 | 
			
		||||
	for i = 0; i < len(line); i++ {
 | 
			
		||||
		if line[i] == '\t' {
 | 
			
		||||
			if prefix == i {
 | 
			
		||||
				prefix++
 | 
			
		||||
			} else {
 | 
			
		||||
				slowcase = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// no need to decode runes if all tabs are at the beginning of the line
 | 
			
		||||
	if !slowcase {
 | 
			
		||||
		for i = 0; i < prefix*tabSize; i++ {
 | 
			
		||||
			out.WriteByte(' ')
 | 
			
		||||
		}
 | 
			
		||||
		out.Write(line[prefix:])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// the slow case: we need to count runes to figure out how
 | 
			
		||||
	// many spaces to insert for each tab
 | 
			
		||||
	column := 0
 | 
			
		||||
	i = 0
 | 
			
		||||
	for i < len(line) {
 | 
			
		||||
		start := i
 | 
			
		||||
		for i < len(line) && line[i] != '\t' {
 | 
			
		||||
			_, size := utf8.DecodeRune(line[i:])
 | 
			
		||||
			i += size
 | 
			
		||||
			column++
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if i > start {
 | 
			
		||||
			out.Write(line[start:i])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if i >= len(line) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for {
 | 
			
		||||
			out.WriteByte(' ')
 | 
			
		||||
			column++
 | 
			
		||||
			if column%tabSize == 0 {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find if a line counts as indented or not.
 | 
			
		||||
// Returns number of characters the indent is (0 = not indented).
 | 
			
		||||
func isIndented(data []byte, indentSize int) int {
 | 
			
		||||
	if len(data) == 0 {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	if data[0] == '\t' {
 | 
			
		||||
		return 1
 | 
			
		||||
	}
 | 
			
		||||
	if len(data) < indentSize {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	for i := 0; i < indentSize; i++ {
 | 
			
		||||
		if data[i] != ' ' {
 | 
			
		||||
			return 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return indentSize
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Create a url-safe slug for fragments
 | 
			
		||||
func slugify(in []byte) []byte {
 | 
			
		||||
	if len(in) == 0 {
 | 
			
		||||
		return in
 | 
			
		||||
	}
 | 
			
		||||
	out := make([]byte, 0, len(in))
 | 
			
		||||
	sym := false
 | 
			
		||||
 | 
			
		||||
	for _, ch := range in {
 | 
			
		||||
		if isalnum(ch) {
 | 
			
		||||
			sym = false
 | 
			
		||||
			out = append(out, ch)
 | 
			
		||||
		} else if sym {
 | 
			
		||||
			continue
 | 
			
		||||
		} else {
 | 
			
		||||
			out = append(out, '-')
 | 
			
		||||
			sym = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	var a, b int
 | 
			
		||||
	var ch byte
 | 
			
		||||
	for a, ch = range out {
 | 
			
		||||
		if ch != '-' {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for b = len(out) - 1; b > 0; b-- {
 | 
			
		||||
		if out[b] != '-' {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return out[a : b+1]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,400 @@
 | 
			
		|||
//
 | 
			
		||||
// Blackfriday Markdown Processor
 | 
			
		||||
// Available at http://github.com/russross/blackfriday
 | 
			
		||||
//
 | 
			
		||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
 | 
			
		||||
// Distributed under the Simplified BSD License.
 | 
			
		||||
// See README.md for details.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// SmartyPants rendering
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
package blackfriday
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type smartypantsData struct {
 | 
			
		||||
	inSingleQuote bool
 | 
			
		||||
	inDoubleQuote bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func wordBoundary(c byte) bool {
 | 
			
		||||
	return c == 0 || isspace(c) || ispunct(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func tolower(c byte) byte {
 | 
			
		||||
	if c >= 'A' && c <= 'Z' {
 | 
			
		||||
		return c - 'A' + 'a'
 | 
			
		||||
	}
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isdigit(c byte) bool {
 | 
			
		||||
	return c >= '0' && c <= '9'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool {
 | 
			
		||||
	// edge of the buffer is likely to be a tag that we don't get to see,
 | 
			
		||||
	// so we treat it like text sometimes
 | 
			
		||||
 | 
			
		||||
	// enumerate all sixteen possibilities for (previousChar, nextChar)
 | 
			
		||||
	// each can be one of {0, space, punct, other}
 | 
			
		||||
	switch {
 | 
			
		||||
	case previousChar == 0 && nextChar == 0:
 | 
			
		||||
		// context is not any help here, so toggle
 | 
			
		||||
		*isOpen = !*isOpen
 | 
			
		||||
	case isspace(previousChar) && nextChar == 0:
 | 
			
		||||
		// [ "] might be [ "<code>foo...]
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	case ispunct(previousChar) && nextChar == 0:
 | 
			
		||||
		// [!"] hmm... could be [Run!"] or [("<code>...]
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case /* isnormal(previousChar) && */ nextChar == 0:
 | 
			
		||||
		// [a"] is probably a close
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case previousChar == 0 && isspace(nextChar):
 | 
			
		||||
		// [" ] might be [...foo</code>" ]
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case isspace(previousChar) && isspace(nextChar):
 | 
			
		||||
		// [ " ] context is not any help here, so toggle
 | 
			
		||||
		*isOpen = !*isOpen
 | 
			
		||||
	case ispunct(previousChar) && isspace(nextChar):
 | 
			
		||||
		// [!" ] is probably a close
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case /* isnormal(previousChar) && */ isspace(nextChar):
 | 
			
		||||
		// [a" ] this is one of the easy cases
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case previousChar == 0 && ispunct(nextChar):
 | 
			
		||||
		// ["!] hmm... could be ["$1.95] or [</code>"!...]
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case isspace(previousChar) && ispunct(nextChar):
 | 
			
		||||
		// [ "!] looks more like [ "$1.95]
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	case ispunct(previousChar) && ispunct(nextChar):
 | 
			
		||||
		// [!"!] context is not any help here, so toggle
 | 
			
		||||
		*isOpen = !*isOpen
 | 
			
		||||
	case /* isnormal(previousChar) && */ ispunct(nextChar):
 | 
			
		||||
		// [a"!] is probably a close
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case previousChar == 0 /* && isnormal(nextChar) */ :
 | 
			
		||||
		// ["a] is probably an open
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	case isspace(previousChar) /* && isnormal(nextChar) */ :
 | 
			
		||||
		// [ "a] this is one of the easy cases
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	case ispunct(previousChar) /* && isnormal(nextChar) */ :
 | 
			
		||||
		// [!"a] is probably an open
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	default:
 | 
			
		||||
		// [a'b] maybe a contraction?
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte('&')
 | 
			
		||||
	if *isOpen {
 | 
			
		||||
		out.WriteByte('l')
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteByte('r')
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteByte(quote)
 | 
			
		||||
	out.WriteString("quo;")
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	if len(text) >= 2 {
 | 
			
		||||
		t1 := tolower(text[1])
 | 
			
		||||
 | 
			
		||||
		if t1 == '\'' {
 | 
			
		||||
			nextChar := byte(0)
 | 
			
		||||
			if len(text) >= 3 {
 | 
			
		||||
				nextChar = text[2]
 | 
			
		||||
			}
 | 
			
		||||
			if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
 | 
			
		||||
				return 1
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) {
 | 
			
		||||
			out.WriteString("’")
 | 
			
		||||
			return 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(text) >= 3 {
 | 
			
		||||
			t2 := tolower(text[2])
 | 
			
		||||
 | 
			
		||||
			if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) &&
 | 
			
		||||
				(len(text) < 4 || wordBoundary(text[3])) {
 | 
			
		||||
				out.WriteString("’")
 | 
			
		||||
				return 0
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nextChar := byte(0)
 | 
			
		||||
	if len(text) > 1 {
 | 
			
		||||
		nextChar = text[1]
 | 
			
		||||
	}
 | 
			
		||||
	if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte(text[0])
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	if len(text) >= 3 {
 | 
			
		||||
		t1 := tolower(text[1])
 | 
			
		||||
		t2 := tolower(text[2])
 | 
			
		||||
 | 
			
		||||
		if t1 == 'c' && t2 == ')' {
 | 
			
		||||
			out.WriteString("©")
 | 
			
		||||
			return 2
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if t1 == 'r' && t2 == ')' {
 | 
			
		||||
			out.WriteString("®")
 | 
			
		||||
			return 2
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
 | 
			
		||||
			out.WriteString("™")
 | 
			
		||||
			return 3
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte(text[0])
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	if len(text) >= 2 {
 | 
			
		||||
		if text[1] == '-' {
 | 
			
		||||
			out.WriteString("—")
 | 
			
		||||
			return 1
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if wordBoundary(previousChar) && wordBoundary(text[1]) {
 | 
			
		||||
			out.WriteString("–")
 | 
			
		||||
			return 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte(text[0])
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
 | 
			
		||||
		out.WriteString("—")
 | 
			
		||||
		return 2
 | 
			
		||||
	}
 | 
			
		||||
	if len(text) >= 2 && text[1] == '-' {
 | 
			
		||||
		out.WriteString("–")
 | 
			
		||||
		return 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte(text[0])
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
 | 
			
		||||
	if bytes.HasPrefix(text, []byte(""")) {
 | 
			
		||||
		nextChar := byte(0)
 | 
			
		||||
		if len(text) >= 7 {
 | 
			
		||||
			nextChar = text[6]
 | 
			
		||||
		}
 | 
			
		||||
		if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
 | 
			
		||||
			return 5
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if bytes.HasPrefix(text, []byte("�")) {
 | 
			
		||||
		return 3
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte('&')
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	return smartAmpVariant(out, smrt, previousChar, text, 'd')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	return smartAmpVariant(out, smrt, previousChar, text, 'a')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
 | 
			
		||||
		out.WriteString("…")
 | 
			
		||||
		return 2
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
 | 
			
		||||
		out.WriteString("…")
 | 
			
		||||
		return 4
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte(text[0])
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	if len(text) >= 2 && text[1] == '`' {
 | 
			
		||||
		nextChar := byte(0)
 | 
			
		||||
		if len(text) >= 3 {
 | 
			
		||||
			nextChar = text[2]
 | 
			
		||||
		}
 | 
			
		||||
		if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
 | 
			
		||||
			return 1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte(text[0])
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
 | 
			
		||||
		// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
 | 
			
		||||
		// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
 | 
			
		||||
		//       and avoid changing dates like 1/23/2005 into fractions.
 | 
			
		||||
		numEnd := 0
 | 
			
		||||
		for len(text) > numEnd && isdigit(text[numEnd]) {
 | 
			
		||||
			numEnd++
 | 
			
		||||
		}
 | 
			
		||||
		if numEnd == 0 {
 | 
			
		||||
			out.WriteByte(text[0])
 | 
			
		||||
			return 0
 | 
			
		||||
		}
 | 
			
		||||
		denStart := numEnd + 1
 | 
			
		||||
		if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 {
 | 
			
		||||
			denStart = numEnd + 3
 | 
			
		||||
		} else if len(text) < numEnd+2 || text[numEnd] != '/' {
 | 
			
		||||
			out.WriteByte(text[0])
 | 
			
		||||
			return 0
 | 
			
		||||
		}
 | 
			
		||||
		denEnd := denStart
 | 
			
		||||
		for len(text) > denEnd && isdigit(text[denEnd]) {
 | 
			
		||||
			denEnd++
 | 
			
		||||
		}
 | 
			
		||||
		if denEnd == denStart {
 | 
			
		||||
			out.WriteByte(text[0])
 | 
			
		||||
			return 0
 | 
			
		||||
		}
 | 
			
		||||
		if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' {
 | 
			
		||||
			out.WriteString("<sup>")
 | 
			
		||||
			out.Write(text[:numEnd])
 | 
			
		||||
			out.WriteString("</sup>⁄<sub>")
 | 
			
		||||
			out.Write(text[denStart:denEnd])
 | 
			
		||||
			out.WriteString("</sub>")
 | 
			
		||||
			return denEnd - 1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte(text[0])
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
 | 
			
		||||
		if text[0] == '1' && text[1] == '/' && text[2] == '2' {
 | 
			
		||||
			if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
 | 
			
		||||
				out.WriteString("½")
 | 
			
		||||
				return 2
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if text[0] == '1' && text[1] == '/' && text[2] == '4' {
 | 
			
		||||
			if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
 | 
			
		||||
				out.WriteString("¼")
 | 
			
		||||
				return 2
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if text[0] == '3' && text[1] == '/' && text[2] == '4' {
 | 
			
		||||
			if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
 | 
			
		||||
				out.WriteString("¾")
 | 
			
		||||
				return 2
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte(text[0])
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
 | 
			
		||||
	nextChar := byte(0)
 | 
			
		||||
	if len(text) > 1 {
 | 
			
		||||
		nextChar = text[1]
 | 
			
		||||
	}
 | 
			
		||||
	if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
 | 
			
		||||
		out.WriteString(""")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 | 
			
		||||
	i := 0
 | 
			
		||||
 | 
			
		||||
	for i < len(text) && text[i] != '>' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.Write(text[:i+1])
 | 
			
		||||
	return i
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int
 | 
			
		||||
 | 
			
		||||
type smartypantsRenderer [256]smartCallback
 | 
			
		||||
 | 
			
		||||
func smartypants(flags int) *smartypantsRenderer {
 | 
			
		||||
	r := new(smartypantsRenderer)
 | 
			
		||||
	if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
 | 
			
		||||
		r['"'] = smartDoubleQuote
 | 
			
		||||
		r['&'] = smartAmp
 | 
			
		||||
	} else {
 | 
			
		||||
		r['"'] = smartAngledDoubleQuote
 | 
			
		||||
		r['&'] = smartAmpAngledQuote
 | 
			
		||||
	}
 | 
			
		||||
	r['\''] = smartSingleQuote
 | 
			
		||||
	r['('] = smartParens
 | 
			
		||||
	if flags&HTML_SMARTYPANTS_DASHES != 0 {
 | 
			
		||||
		if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
 | 
			
		||||
			r['-'] = smartDash
 | 
			
		||||
		} else {
 | 
			
		||||
			r['-'] = smartDashLatex
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	r['.'] = smartPeriod
 | 
			
		||||
	if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
 | 
			
		||||
		r['1'] = smartNumber
 | 
			
		||||
		r['3'] = smartNumber
 | 
			
		||||
	} else {
 | 
			
		||||
		for ch := '1'; ch <= '9'; ch++ {
 | 
			
		||||
			r[ch] = smartNumberGeneric
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	r['<'] = smartLeftAngle
 | 
			
		||||
	r['`'] = smartBacktick
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
Copyright (c) 2015 Dmitri Shuralyov
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in
 | 
			
		||||
all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
# sanitized_anchor_name [](https://travis-ci.org/shurcooL/sanitized_anchor_name) [](https://godoc.org/github.com/shurcooL/sanitized_anchor_name)
 | 
			
		||||
 | 
			
		||||
Package sanitized_anchor_name provides a func to create sanitized anchor names.
 | 
			
		||||
 | 
			
		||||
Its logic can be reused by multiple packages to create interoperable anchor names and links to those anchors.
 | 
			
		||||
 | 
			
		||||
At this time, it does not try to ensure that generated anchor names are unique, that responsibility falls on the caller.
 | 
			
		||||
 | 
			
		||||
Installation
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
go get -u github.com/shurcooL/sanitized_anchor_name
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Example
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
```Go
 | 
			
		||||
anchorName := sanitized_anchor_name.Create("This is a header")
 | 
			
		||||
 | 
			
		||||
fmt.Println(anchorName)
 | 
			
		||||
 | 
			
		||||
// Output:
 | 
			
		||||
// this-is-a-header
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
License
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
- [MIT License](LICENSE)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
// Package sanitized_anchor_name provides a func to create sanitized anchor names.
 | 
			
		||||
//
 | 
			
		||||
// Its logic can be reused by multiple packages to create interoperable anchor names
 | 
			
		||||
// and links to those anchors.
 | 
			
		||||
//
 | 
			
		||||
// At this time, it does not try to ensure that generated anchor names
 | 
			
		||||
// are unique, that responsibility falls on the caller.
 | 
			
		||||
package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name"
 | 
			
		||||
 | 
			
		||||
import "unicode"
 | 
			
		||||
 | 
			
		||||
// Create returns a sanitized anchor name for the given text.
 | 
			
		||||
func Create(text string) string {
 | 
			
		||||
	var anchorName []rune
 | 
			
		||||
	var futureDash = false
 | 
			
		||||
	for _, r := range []rune(text) {
 | 
			
		||||
		switch {
 | 
			
		||||
		case unicode.IsLetter(r) || unicode.IsNumber(r):
 | 
			
		||||
			if futureDash && len(anchorName) > 0 {
 | 
			
		||||
				anchorName = append(anchorName, '-')
 | 
			
		||||
			}
 | 
			
		||||
			futureDash = false
 | 
			
		||||
			anchorName = append(anchorName, unicode.ToLower(r))
 | 
			
		||||
		default:
 | 
			
		||||
			futureDash = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return string(anchorName)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,231 @@
 | 
			
		|||
// Copyright 2015 Red Hat Inc. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
// http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package doc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	mangen "github.com/cpuguy83/go-md2man/md2man"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"github.com/spf13/pflag"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GenManTree will generate a man page for this command and all descendants
 | 
			
		||||
// in the directory given. The header may be nil. This function may not work
 | 
			
		||||
// correctly if your command names have - in them. If you have `cmd` with two
 | 
			
		||||
// subcmds, `sub` and `sub-third`. And `sub` has a subcommand called `third`
 | 
			
		||||
// it is undefined which help output will be in the file `cmd-sub-third.1`.
 | 
			
		||||
func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
 | 
			
		||||
	return GenManTreeFromOpts(cmd, GenManTreeOptions{
 | 
			
		||||
		Header:           header,
 | 
			
		||||
		Path:             dir,
 | 
			
		||||
		CommandSeparator: "_",
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenManTreeFromOpts generates a man page for the command and all descendants.
 | 
			
		||||
// The pages are written to the opts.Path directory.
 | 
			
		||||
func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
 | 
			
		||||
	header := opts.Header
 | 
			
		||||
	if header == nil {
 | 
			
		||||
		header = &GenManHeader{}
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range cmd.Commands() {
 | 
			
		||||
		if !c.IsAvailableCommand() || c.IsHelpCommand() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if err := GenManTreeFromOpts(c, opts); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	section := "1"
 | 
			
		||||
	if header.Section != "" {
 | 
			
		||||
		section = header.Section
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	separator := "_"
 | 
			
		||||
	if opts.CommandSeparator != "" {
 | 
			
		||||
		separator = opts.CommandSeparator
 | 
			
		||||
	}
 | 
			
		||||
	basename := strings.Replace(cmd.CommandPath(), " ", separator, -1)
 | 
			
		||||
	filename := filepath.Join(opts.Path, basename+"."+section)
 | 
			
		||||
	f, err := os.Create(filename)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	headerCopy := *header
 | 
			
		||||
	return GenMan(cmd, &headerCopy, f)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GenManTreeOptions struct {
 | 
			
		||||
	Header           *GenManHeader
 | 
			
		||||
	Path             string
 | 
			
		||||
	CommandSeparator string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenManHeader is a lot like the .TH header at the start of man pages. These
 | 
			
		||||
// include the title, section, date, source, and manual. We will use the
 | 
			
		||||
// current time if Date if unset and will use "Auto generated by spf13/cobra"
 | 
			
		||||
// if the Source is unset.
 | 
			
		||||
type GenManHeader struct {
 | 
			
		||||
	Title   string
 | 
			
		||||
	Section string
 | 
			
		||||
	Date    *time.Time
 | 
			
		||||
	date    string
 | 
			
		||||
	Source  string
 | 
			
		||||
	Manual  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenMan will generate a man page for the given command and write it to
 | 
			
		||||
// w. The header argument may be nil, however obviously w may not.
 | 
			
		||||
func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
 | 
			
		||||
	if header == nil {
 | 
			
		||||
		header = &GenManHeader{}
 | 
			
		||||
	}
 | 
			
		||||
	fillHeader(header, cmd.CommandPath())
 | 
			
		||||
 | 
			
		||||
	b := genMan(cmd, header)
 | 
			
		||||
	_, err := w.Write(mangen.Render(b))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillHeader(header *GenManHeader, name string) {
 | 
			
		||||
	if header.Title == "" {
 | 
			
		||||
		header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
 | 
			
		||||
	}
 | 
			
		||||
	if header.Section == "" {
 | 
			
		||||
		header.Section = "1"
 | 
			
		||||
	}
 | 
			
		||||
	if header.Date == nil {
 | 
			
		||||
		now := time.Now()
 | 
			
		||||
		header.Date = &now
 | 
			
		||||
	}
 | 
			
		||||
	header.date = (*header.Date).Format("Jan 2006")
 | 
			
		||||
	if header.Source == "" {
 | 
			
		||||
		header.Source = "Auto generated by spf13/cobra"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func manPreamble(out io.Writer, header *GenManHeader, cmd *cobra.Command, dashedName string) {
 | 
			
		||||
	description := cmd.Long
 | 
			
		||||
	if len(description) == 0 {
 | 
			
		||||
		description = cmd.Short
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(out, `%% %s(%s)%s
 | 
			
		||||
%% %s
 | 
			
		||||
%% %s
 | 
			
		||||
# NAME
 | 
			
		||||
`, header.Title, header.Section, header.date, header.Source, header.Manual)
 | 
			
		||||
	fmt.Fprintf(out, "%s \\- %s\n\n", dashedName, cmd.Short)
 | 
			
		||||
	fmt.Fprintf(out, "# SYNOPSIS\n")
 | 
			
		||||
	fmt.Fprintf(out, "**%s**\n\n", cmd.UseLine())
 | 
			
		||||
	fmt.Fprintf(out, "# DESCRIPTION\n")
 | 
			
		||||
	fmt.Fprintf(out, "%s\n\n", description)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func manPrintFlags(out io.Writer, flags *pflag.FlagSet) {
 | 
			
		||||
	flags.VisitAll(func(flag *pflag.Flag) {
 | 
			
		||||
		if len(flag.Deprecated) > 0 || flag.Hidden {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		format := ""
 | 
			
		||||
		if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
 | 
			
		||||
			format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name)
 | 
			
		||||
		} else {
 | 
			
		||||
			format = fmt.Sprintf("**--%s**", flag.Name)
 | 
			
		||||
		}
 | 
			
		||||
		if len(flag.NoOptDefVal) > 0 {
 | 
			
		||||
			format = format + "["
 | 
			
		||||
		}
 | 
			
		||||
		if flag.Value.Type() == "string" {
 | 
			
		||||
			// put quotes on the value
 | 
			
		||||
			format = format + "=%q"
 | 
			
		||||
		} else {
 | 
			
		||||
			format = format + "=%s"
 | 
			
		||||
		}
 | 
			
		||||
		if len(flag.NoOptDefVal) > 0 {
 | 
			
		||||
			format = format + "]"
 | 
			
		||||
		}
 | 
			
		||||
		format = format + "\n\t%s\n\n"
 | 
			
		||||
		fmt.Fprintf(out, format, flag.DefValue, flag.Usage)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func manPrintOptions(out io.Writer, command *cobra.Command) {
 | 
			
		||||
	flags := command.NonInheritedFlags()
 | 
			
		||||
	if flags.HasFlags() {
 | 
			
		||||
		fmt.Fprintf(out, "# OPTIONS\n")
 | 
			
		||||
		manPrintFlags(out, flags)
 | 
			
		||||
		fmt.Fprintf(out, "\n")
 | 
			
		||||
	}
 | 
			
		||||
	flags = command.InheritedFlags()
 | 
			
		||||
	if flags.HasFlags() {
 | 
			
		||||
		fmt.Fprintf(out, "# OPTIONS INHERITED FROM PARENT COMMANDS\n")
 | 
			
		||||
		manPrintFlags(out, flags)
 | 
			
		||||
		fmt.Fprintf(out, "\n")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
 | 
			
		||||
	// something like `rootcmd-subcmd1-subcmd2`
 | 
			
		||||
	dashCommandName := strings.Replace(cmd.CommandPath(), " ", "-", -1)
 | 
			
		||||
 | 
			
		||||
	buf := new(bytes.Buffer)
 | 
			
		||||
 | 
			
		||||
	manPreamble(buf, header, cmd, dashCommandName)
 | 
			
		||||
	manPrintOptions(buf, cmd)
 | 
			
		||||
	if len(cmd.Example) > 0 {
 | 
			
		||||
		fmt.Fprintf(buf, "# EXAMPLE\n")
 | 
			
		||||
		fmt.Fprintf(buf, "\n%s\n\n", cmd.Example)
 | 
			
		||||
	}
 | 
			
		||||
	if hasSeeAlso(cmd) {
 | 
			
		||||
		fmt.Fprintf(buf, "# SEE ALSO\n")
 | 
			
		||||
		seealsos := make([]string, 0)
 | 
			
		||||
		if cmd.HasParent() {
 | 
			
		||||
			parentPath := cmd.Parent().CommandPath()
 | 
			
		||||
			dashParentPath := strings.Replace(parentPath, " ", "-", -1)
 | 
			
		||||
			seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
 | 
			
		||||
			seealsos = append(seealsos, seealso)
 | 
			
		||||
			cmd.VisitParents(func(c *cobra.Command) {
 | 
			
		||||
				if c.DisableAutoGenTag {
 | 
			
		||||
					cmd.DisableAutoGenTag = c.DisableAutoGenTag
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		children := cmd.Commands()
 | 
			
		||||
		sort.Sort(byName(children))
 | 
			
		||||
		for _, c := range children {
 | 
			
		||||
			if !c.IsAvailableCommand() || c.IsHelpCommand() {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
 | 
			
		||||
			seealsos = append(seealsos, seealso)
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(buf, "%s\n", strings.Join(seealsos, ", "))
 | 
			
		||||
	}
 | 
			
		||||
	if !cmd.DisableAutoGenTag {
 | 
			
		||||
		fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006"))
 | 
			
		||||
	}
 | 
			
		||||
	return buf.Bytes()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,175 @@
 | 
			
		|||
//Copyright 2015 Red Hat Inc. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
// http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package doc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func printOptions(w io.Writer, cmd *cobra.Command, name string) error {
 | 
			
		||||
	flags := cmd.NonInheritedFlags()
 | 
			
		||||
	flags.SetOutput(w)
 | 
			
		||||
	if flags.HasFlags() {
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "### Options\n\n```\n"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		flags.PrintDefaults()
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "```\n\n"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	parentFlags := cmd.InheritedFlags()
 | 
			
		||||
	parentFlags.SetOutput(w)
 | 
			
		||||
	if parentFlags.HasFlags() {
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "### Options inherited from parent commands\n\n```\n"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		parentFlags.PrintDefaults()
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "```\n\n"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
 | 
			
		||||
	return GenMarkdownCustom(cmd, w, func(s string) string { return s })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
 | 
			
		||||
	name := cmd.CommandPath()
 | 
			
		||||
 | 
			
		||||
	short := cmd.Short
 | 
			
		||||
	long := cmd.Long
 | 
			
		||||
	if len(long) == 0 {
 | 
			
		||||
		long = short
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := fmt.Fprintf(w, "## %s\n\n", name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := fmt.Fprintf(w, "%s\n\n", short); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := fmt.Fprintf(w, "### Synopsis\n\n"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := fmt.Fprintf(w, "\n%s\n\n", long); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cmd.Runnable() {
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.UseLine()); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(cmd.Example) > 0 {
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "### Examples\n\n"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.Example); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := printOptions(w, cmd, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if hasSeeAlso(cmd) {
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "### SEE ALSO\n"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if cmd.HasParent() {
 | 
			
		||||
			parent := cmd.Parent()
 | 
			
		||||
			pname := parent.CommandPath()
 | 
			
		||||
			link := pname + ".md"
 | 
			
		||||
			link = strings.Replace(link, " ", "_", -1)
 | 
			
		||||
			if _, err := fmt.Fprintf(w, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			cmd.VisitParents(func(c *cobra.Command) {
 | 
			
		||||
				if c.DisableAutoGenTag {
 | 
			
		||||
					cmd.DisableAutoGenTag = c.DisableAutoGenTag
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		children := cmd.Commands()
 | 
			
		||||
		sort.Sort(byName(children))
 | 
			
		||||
 | 
			
		||||
		for _, child := range children {
 | 
			
		||||
			if !child.IsAvailableCommand() || child.IsHelpCommand() {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			cname := name + " " + child.Name()
 | 
			
		||||
			link := cname + ".md"
 | 
			
		||||
			link = strings.Replace(link, " ", "_", -1)
 | 
			
		||||
			if _, err := fmt.Fprintf(w, "* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "\n"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !cmd.DisableAutoGenTag {
 | 
			
		||||
		if _, err := fmt.Fprintf(w, "###### Auto generated by spf13/cobra on %s\n", time.Now().Format("2-Jan-2006")); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenMarkdownTree(cmd *cobra.Command, dir string) error {
 | 
			
		||||
	identity := func(s string) string { return s }
 | 
			
		||||
	emptyStr := func(s string) string { return "" }
 | 
			
		||||
	return GenMarkdownTreeCustom(cmd, dir, emptyStr, identity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
 | 
			
		||||
	for _, c := range cmd.Commands() {
 | 
			
		||||
		if !c.IsAvailableCommand() || c.IsHelpCommand() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if err := GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md"
 | 
			
		||||
	filename := filepath.Join(dir, basename)
 | 
			
		||||
	f, err := os.Create(filename)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	if _, err := io.WriteString(f, filePrepender(filename)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := GenMarkdownCustom(cmd, f, linkHandler); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
// Copyright 2015 Red Hat Inc. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
// http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package doc
 | 
			
		||||
 | 
			
		||||
import "github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
// Test to see if we have a reason to print See Also information in docs
 | 
			
		||||
// Basically this is a test for a parent commend or a subcommand which is
 | 
			
		||||
// both not deprecated and not the autogenerated help command.
 | 
			
		||||
func hasSeeAlso(cmd *cobra.Command) bool {
 | 
			
		||||
	if cmd.HasParent() {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range cmd.Commands() {
 | 
			
		||||
		if !c.IsAvailableCommand() || c.IsHelpCommand() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type byName []*cobra.Command
 | 
			
		||||
 | 
			
		||||
func (s byName) Len() int           { return len(s) }
 | 
			
		||||
func (s byName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 | 
			
		||||
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
 | 
			
		||||
		Loading…
	
		Reference in New Issue