pax_global_header00006660000000000000000000000064145760632030014520gustar00rootroot0000000000000052 comment=d6816bfbea7506064a28119f805fb79f9bc5aeec go-md2man-2.0.4/000077500000000000000000000000001457606320300133045ustar00rootroot00000000000000go-md2man-2.0.4/.github/000077500000000000000000000000001457606320300146445ustar00rootroot00000000000000go-md2man-2.0.4/.github/dependabot.yml000066400000000000000000000002631457606320300174750ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: / schedule: interval: weekly - package-ecosystem: github-actions directory: / schedule: interval: weekly go-md2man-2.0.4/.github/workflows/000077500000000000000000000000001457606320300167015ustar00rootroot00000000000000go-md2man-2.0.4/.github/workflows/test.yml000066400000000000000000000013531457606320300204050ustar00rootroot00000000000000name: CI on: push: branches: [master] pull_request: branches: [master] jobs: test: name: Build strategy: matrix: go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x] platform: [ubuntu-20.04] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Check out code into the Go module directory uses: actions/checkout@v4 - name: Build run: make build - name: Test run: make test lint: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 - uses: golangci/golangci-lint-action@v4.0.0 with: version: v1.55 go-md2man-2.0.4/.gitignore000066400000000000000000000000161457606320300152710ustar00rootroot00000000000000go-md2man bin go-md2man-2.0.4/.golangci.yml000066400000000000000000000001531457606320300156670ustar00rootroot00000000000000# For documentation, see https://golangci-lint.run/usage/configuration/ linters: enable: - gofumpt go-md2man-2.0.4/Dockerfile000066400000000000000000000007051457606320300153000ustar00rootroot00000000000000ARG GO_VERSION=1.21 FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION} AS build COPY . /go/src/github.com/cpuguy83/go-md2man WORKDIR /go/src/github.com/cpuguy83/go-md2man ARG TARGETOS TARGETARCH TARGETVARIANT RUN \ --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ make build FROM scratch COPY --from=build /go/src/github.com/cpuguy83/go-md2man/bin/go-md2man /go-md2man ENTRYPOINT ["/go-md2man"] go-md2man-2.0.4/LICENSE.md000066400000000000000000000020651457606320300147130ustar00rootroot00000000000000The 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. go-md2man-2.0.4/Makefile000066400000000000000000000012211457606320300147400ustar00rootroot00000000000000GO111MODULE ?= on export GO111MODULE GOOS ?= $(if $(TARGETOS),$(TARGETOS),) GOARCH ?= $(if $(TARGETARCH),$(TARGETARCH),) ifeq ($(TARGETARCH),amd64) GOAMD64 ?= $(TARGETVARIANT) endif ifeq ($(TARGETARCH),arm) GOARM ?= $(TARGETVARIANT:v%=%) endif ifneq ($(GOOS),) export GOOS endif ifneq ($(GOARCH),) export GOARCH endif ifneq ($(GOAMD64),) export GOAMD64 endif ifneq ($(GOARM),) export GOARM endif .PHONY: build: bin/go-md2man .PHONY: clean clean: @rm -rf bin/* .PHONY: test test: @go test $(TEST_FLAGS) ./... bin/go-md2man: go.mod go.sum md2man/* *.go @mkdir -p bin CGO_ENABLED=0 go build $(BUILD_FLAGS) -o $@ .PHONY: mod mod: @go mod tidy go-md2man-2.0.4/README.md000066400000000000000000000004541457606320300145660ustar00rootroot00000000000000go-md2man ========= Converts markdown into roff (man pages). Uses blackfriday to process markdown into man pages. ### Usage ./md2man -in /path/to/markdownfile.md -out /manfile/output/path ### How to contribute We use go modules to manage dependencies. As such you must be using at lest go1.11. go-md2man-2.0.4/go-md2man.1.md000066400000000000000000000014301457606320300155440ustar00rootroot00000000000000go-md2man 1 "January 2015" go-md2man "User Manual" ================================================== # NAME go-md2man - Convert markdown files into manpages # SYNOPSIS **go-md2man** [**-in**=*/path/to/md/file*] [**-out**=*/path/to/output*] # DESCRIPTION **go-md2man** converts standard markdown formatted documents into manpages. It is written purely in Go so as to reduce dependencies on 3rd party libs. By default, the input is stdin and the output is stdout. # EXAMPLES Convert the markdown file *go-md2man.1.md* into a manpage: ``` go-md2man < go-md2man.1.md > go-md2man.1 ``` Same, but using command line arguments instead of shell redirection: ``` go-md2man -in=go-md2man.1.md -out=go-md2man.1 ``` # HISTORY January 2015, Originally compiled by Brian Goff (cpuguy83@gmail.com). go-md2man-2.0.4/go.mod000066400000000000000000000001441457606320300144110ustar00rootroot00000000000000module github.com/cpuguy83/go-md2man/v2 go 1.11 require github.com/russross/blackfriday/v2 v2.1.0 go-md2man-2.0.4/go.sum000066400000000000000000000002731457606320300144410ustar00rootroot00000000000000github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= go-md2man-2.0.4/md2man.go000066400000000000000000000016121457606320300150110ustar00rootroot00000000000000package main import ( "flag" "fmt" "io/ioutil" "os" "github.com/cpuguy83/go-md2man/v2/md2man" ) var ( inFilePath = flag.String("in", "", "Path to file to be processed (default: stdin)") outFilePath = flag.String("out", "", "Path to output processed file (default: stdout)") ) func main() { var err error flag.Parse() inFile := os.Stdin if *inFilePath != "" { inFile, err = os.Open(*inFilePath) if err != nil { fmt.Println(err) os.Exit(1) } } defer inFile.Close() // nolint: errcheck doc, err := ioutil.ReadAll(inFile) if err != nil { fmt.Println(err) os.Exit(1) } out := md2man.Render(doc) outFile := os.Stdout if *outFilePath != "" { outFile, err = os.Create(*outFilePath) if err != nil { fmt.Println(err) os.Exit(1) } defer outFile.Close() // nolint: errcheck } _, err = outFile.Write(out) if err != nil { fmt.Println(err) os.Exit(1) } } go-md2man-2.0.4/md2man/000077500000000000000000000000001457606320300144625ustar00rootroot00000000000000go-md2man-2.0.4/md2man/md2man.go000066400000000000000000000005511457606320300161700ustar00rootroot00000000000000package md2man import ( "github.com/russross/blackfriday/v2" ) // Render converts a markdown document into a roff formatted document. func Render(doc []byte) []byte { renderer := NewRoffRenderer() return blackfriday.Run(doc, []blackfriday.Option{ blackfriday.WithRenderer(renderer), blackfriday.WithExtensions(renderer.GetExtensions()), }...) } go-md2man-2.0.4/md2man/roff.go000066400000000000000000000240531457606320300157510ustar00rootroot00000000000000package md2man import ( "bufio" "bytes" "fmt" "io" "os" "strings" "github.com/russross/blackfriday/v2" ) // roffRenderer implements the blackfriday.Renderer interface for creating // roff format (manpages) from markdown text type roffRenderer struct { extensions blackfriday.Extensions listCounters []int firstHeader bool firstDD bool listDepth int } const ( titleHeader = ".TH " topLevelHeader = "\n\n.SH " secondLevelHdr = "\n.SH " otherHeader = "\n.SS " crTag = "\n" emphTag = "\\fI" emphCloseTag = "\\fP" strongTag = "\\fB" strongCloseTag = "\\fP" breakTag = "\n.br\n" paraTag = "\n.PP\n" hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n" linkTag = "\n\\[la]" linkCloseTag = "\\[ra]" codespanTag = "\\fB" codespanCloseTag = "\\fR" codeTag = "\n.EX\n" codeCloseTag = ".EE\n" // Do not prepend a newline character since code blocks, by definition, include a newline already (or at least as how blackfriday gives us on). quoteTag = "\n.PP\n.RS\n" quoteCloseTag = "\n.RE\n" listTag = "\n.RS\n" listCloseTag = "\n.RE\n" dtTag = "\n.TP\n" dd2Tag = "\n" tableStart = "\n.TS\nallbox;\n" tableEnd = ".TE\n" tableCellStart = "T{\n" tableCellEnd = "\nT}\n" tablePreprocessor = `'\" t` ) // NewRoffRenderer creates a new blackfriday Renderer for generating roff documents // from markdown func NewRoffRenderer() *roffRenderer { // nolint: golint var extensions blackfriday.Extensions extensions |= blackfriday.NoIntraEmphasis extensions |= blackfriday.Tables extensions |= blackfriday.FencedCode extensions |= blackfriday.SpaceHeadings extensions |= blackfriday.Footnotes extensions |= blackfriday.Titleblock extensions |= blackfriday.DefinitionLists return &roffRenderer{ extensions: extensions, } } // GetExtensions returns the list of extensions used by this renderer implementation func (r *roffRenderer) GetExtensions() blackfriday.Extensions { return r.extensions } // RenderHeader handles outputting the header at document start func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) { // We need to walk the tree to check if there are any tables. // If there are, we need to enable the roff table preprocessor. ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus { if node.Type == blackfriday.Table { out(w, tablePreprocessor+"\n") return blackfriday.Terminate } return blackfriday.GoToNext }) // disable hyphenation out(w, ".nh\n") } // RenderFooter handles outputting the footer at the document end; the roff // renderer has no footer information func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) { } // RenderNode is called for each node in a markdown document; based on the node // type the equivalent roff output is sent to the writer func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus { walkAction := blackfriday.GoToNext switch node.Type { case blackfriday.Text: escapeSpecialChars(w, node.Literal) case blackfriday.Softbreak: out(w, crTag) case blackfriday.Hardbreak: out(w, breakTag) case blackfriday.Emph: if entering { out(w, emphTag) } else { out(w, emphCloseTag) } case blackfriday.Strong: if entering { out(w, strongTag) } else { out(w, strongCloseTag) } case blackfriday.Link: // Don't render the link text for automatic links, because this // will only duplicate the URL in the roff output. // See https://daringfireball.net/projects/markdown/syntax#autolink if !bytes.Equal(node.LinkData.Destination, node.FirstChild.Literal) { out(w, string(node.FirstChild.Literal)) } // Hyphens in a link must be escaped to avoid word-wrap in the rendered man page. escapedLink := strings.ReplaceAll(string(node.LinkData.Destination), "-", "\\-") out(w, linkTag+escapedLink+linkCloseTag) walkAction = blackfriday.SkipChildren case blackfriday.Image: // ignore images walkAction = blackfriday.SkipChildren case blackfriday.Code: out(w, codespanTag) escapeSpecialChars(w, node.Literal) out(w, codespanCloseTag) case blackfriday.Document: break case blackfriday.Paragraph: // roff .PP markers break lists if r.listDepth > 0 { return blackfriday.GoToNext } if entering { out(w, paraTag) } else { out(w, crTag) } case blackfriday.BlockQuote: if entering { out(w, quoteTag) } else { out(w, quoteCloseTag) } case blackfriday.Heading: r.handleHeading(w, node, entering) case blackfriday.HorizontalRule: out(w, hruleTag) case blackfriday.List: r.handleList(w, node, entering) case blackfriday.Item: r.handleItem(w, node, entering) case blackfriday.CodeBlock: out(w, codeTag) escapeSpecialChars(w, node.Literal) out(w, codeCloseTag) case blackfriday.Table: r.handleTable(w, node, entering) case blackfriday.TableHead: case blackfriday.TableBody: case blackfriday.TableRow: // no action as cell entries do all the nroff formatting return blackfriday.GoToNext case blackfriday.TableCell: r.handleTableCell(w, node, entering) case blackfriday.HTMLSpan: // ignore other HTML tags case blackfriday.HTMLBlock: if bytes.HasPrefix(node.Literal, []byte("\n\nSecond paragraph\n", ".nh\n\n.PP\nFirst paragraph\n\n.PP\nSecond paragraph\n", } doTestsParam(t, blockTests, TestParams{}) inlineTests := []string{ "Text with a comment in the middle\n", ".nh\n\n.PP\nText with a comment in the middle\n", } doTestsInlineParam(t, inlineTests, TestParams{}) } func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, suite func(candidate *string)) { // Catch and report panics. This is useful when running 'go test -v' on // the integration server. When developing, though, crash dump is often // preferable, so recovery can be easily turned off with doRecover = false. var candidate string const doRecover = true if doRecover { defer func() { if err := recover(); err != nil { t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err) } }() } suite(&candidate) } func runMarkdown(input string, params TestParams) string { renderer := NewRoffRenderer() return string(blackfriday.Run([]byte(input), blackfriday.WithRenderer(renderer), blackfriday.WithExtensions(params.extensions))) } func doTestsParam(t *testing.T, tests []string, params TestParams) { execRecoverableTestSuite(t, tests, params, func(candidate *string) { for i := 0; i+1 < len(tests); i += 2 { input := tests[i] t.Run(input, func(t *testing.T) { *candidate = input expected := tests[i+1] actual := runMarkdown(*candidate, params) if actual != expected { t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]", *candidate, expected, actual) } // now test every substring to stress test bounds checking if !testing.Short() { for start := 0; start < len(input); start++ { for end := start + 1; end <= len(input); end++ { *candidate = input[start:end] runMarkdown(*candidate, params) } } } }) } }) } func doTestsInline(t *testing.T, tests []string) { doTestsInlineParam(t, tests, TestParams{}) } func doTestsInlineParam(t *testing.T, tests []string, params TestParams) { params.extensions |= blackfriday.Strikethrough doTestsParam(t, tests, params) }