From 0d80af649a50c4b9e5e4ba764399872fc92f70f2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 21 Sep 2017 13:20:14 +0800 Subject: [PATCH] Add init support of orgmode document type on file view and readme (#2525) * add init support of orgmode document type on file view and readme * fix imports * fix imports and readmeExist * fix imports order * fix format * remove unnecessary convert --- main.go | 4 + models/mail.go | 2 +- modules/markup/html_test.go | 2 +- modules/{ => markup}/markdown/markdown.go | 18 +- .../{ => markup}/markdown/markdown_test.go | 43 +- modules/markup/markup_test.go | 2 +- modules/markup/orgmode/orgmode.go | 56 ++ modules/markup/orgmode/orgmode_test.go | 54 ++ routers/api/v1/misc/markdown.go | 2 +- routers/repo/issue.go | 2 +- routers/repo/release.go | 2 +- routers/repo/view.go | 10 +- routers/repo/wiki.go | 2 +- templates/repo/view_file.tmpl | 4 +- .../github.com/chaseadamsio/goorgeous/LICENSE | 21 + .../chaseadamsio/goorgeous/README.org | 66 ++ .../chaseadamsio/goorgeous/goorgeous.go | 803 ++++++++++++++++++ .../chaseadamsio/goorgeous/gopher.gif | Bin 0 -> 15232 bytes .../chaseadamsio/goorgeous/gopher_small.gif | Bin 0 -> 3270 bytes .../chaseadamsio/goorgeous/header.go | 70 ++ vendor/vendor.json | 6 + 21 files changed, 1103 insertions(+), 66 deletions(-) rename modules/{ => markup}/markdown/markdown.go (95%) rename modules/{ => markup}/markdown/markdown_test.go (89%) create mode 100644 modules/markup/orgmode/orgmode.go create mode 100644 modules/markup/orgmode/orgmode_test.go create mode 100644 vendor/github.com/chaseadamsio/goorgeous/LICENSE create mode 100644 vendor/github.com/chaseadamsio/goorgeous/README.org create mode 100644 vendor/github.com/chaseadamsio/goorgeous/goorgeous.go create mode 100644 vendor/github.com/chaseadamsio/goorgeous/gopher.gif create mode 100644 vendor/github.com/chaseadamsio/goorgeous/gopher_small.gif create mode 100644 vendor/github.com/chaseadamsio/goorgeous/header.go diff --git a/main.go b/main.go index 383dbc20931..c2acda99afc 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,10 @@ import ( "code.gitea.io/gitea/cmd" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + // register supported doc types + _ "code.gitea.io/gitea/modules/markup/markdown" + _ "code.gitea.io/gitea/modules/markup/orgmode" + "github.com/urfave/cli" ) diff --git a/models/mail.go b/models/mail.go index afcddb6d239..98766f69f2c 100644 --- a/models/mail.go +++ b/models/mail.go @@ -13,8 +13,8 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/mailer" - "code.gitea.io/gitea/modules/markdown" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "gopkg.in/gomail.v2" "gopkg.in/macaron.v1" diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 407115526d0..ab2ca5ef47e 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -10,8 +10,8 @@ import ( "strings" "testing" - _ "code.gitea.io/gitea/modules/markdown" . "code.gitea.io/gitea/modules/markup" + _ "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" diff --git a/modules/markdown/markdown.go b/modules/markup/markdown/markdown.go similarity index 95% rename from modules/markdown/markdown.go rename to modules/markup/markdown/markdown.go index 6cf2d9eaa16..f0ed0e03ab5 100644 --- a/modules/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -17,8 +17,8 @@ import ( // Renderer is a extended version of underlying render object. type Renderer struct { blackfriday.Renderer - urlPrefix string - isWikiMarkdown bool + URLPrefix string + IsWiki bool } // Link defines how formal links should be processed to produce corresponding HTML elements. @@ -26,10 +26,10 @@ func (r *Renderer) Link(out *bytes.Buffer, link []byte, title []byte, content [] if len(link) > 0 && !markup.IsLink(link) { if link[0] != '#' { lnk := string(link) - if r.isWikiMarkdown { + if r.IsWiki { lnk = markup.URLJoin("wiki", lnk) } - mLink := markup.URLJoin(r.urlPrefix, lnk) + mLink := markup.URLJoin(r.URLPrefix, lnk) link = []byte(mLink) } } @@ -95,8 +95,8 @@ var ( // Image defines how images should be processed to produce corresponding HTML elements. func (r *Renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { - prefix := r.urlPrefix - if r.isWikiMarkdown { + prefix := r.URLPrefix + if r.IsWiki { prefix = markup.URLJoin(prefix, "wiki", "src") } prefix = strings.Replace(prefix, "/src/", "/raw/", 1) @@ -129,9 +129,9 @@ func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte { htmlFlags |= blackfriday.HTML_SKIP_STYLE htmlFlags |= blackfriday.HTML_OMIT_CONTENTS renderer := &Renderer{ - Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""), - urlPrefix: urlPrefix, - isWikiMarkdown: wikiMarkdown, + Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""), + URLPrefix: urlPrefix, + IsWiki: wikiMarkdown, } // set up the parser diff --git a/modules/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go similarity index 89% rename from modules/markdown/markdown_test.go rename to modules/markup/markdown/markdown_test.go index 1b57e4f203f..9ca3de01cae 100644 --- a/modules/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -5,13 +5,11 @@ package markdown_test import ( - "fmt" - "strconv" "strings" "testing" - . "code.gitea.io/gitea/modules/markdown" "code.gitea.io/gitea/modules/markup" + . "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" @@ -21,45 +19,6 @@ const AppURL = "http://localhost:3000/" const Repo = "gogits/gogs" const AppSubURL = AppURL + Repo + "/" -var numericMetas = map[string]string{ - "format": "https://someurl.com/{user}/{repo}/{index}", - "user": "someUser", - "repo": "someRepo", - "style": markup.IssueNameStyleNumeric, -} - -var alphanumericMetas = map[string]string{ - "format": "https://someurl.com/{user}/{repo}/{index}", - "user": "someUser", - "repo": "someRepo", - "style": markup.IssueNameStyleAlphanumeric, -} - -// numericLink an HTML to a numeric-style issue -func numericIssueLink(baseURL string, index int) string { - return link(markup.URLJoin(baseURL, strconv.Itoa(index)), fmt.Sprintf("#%d", index)) -} - -// alphanumLink an HTML link to an alphanumeric-style issue -func alphanumIssueLink(baseURL string, name string) string { - return link(markup.URLJoin(baseURL, name), name) -} - -// urlContentsLink an HTML link whose contents is the target URL -func urlContentsLink(href string) string { - return link(href, href) -} - -// link an HTML link -func link(href, contents string) string { - return fmt.Sprintf("%s", href, contents) -} - -func testRenderIssueIndexPattern(t *testing.T, input, expected string, metas map[string]string) { - assert.Equal(t, expected, - string(markup.RenderIssueIndexPattern([]byte(input), AppSubURL, metas))) -} - func TestRender_StandardLinks(t *testing.T) { setting.AppURL = AppURL setting.AppSubURL = AppSubURL diff --git a/modules/markup/markup_test.go b/modules/markup/markup_test.go index 8d061ae39e9..b0ebfae57d7 100644 --- a/modules/markup/markup_test.go +++ b/modules/markup/markup_test.go @@ -7,8 +7,8 @@ package markup_test import ( "testing" - _ "code.gitea.io/gitea/modules/markdown" . "code.gitea.io/gitea/modules/markup" + _ "code.gitea.io/gitea/modules/markup/markdown" "github.com/stretchr/testify/assert" ) diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go new file mode 100644 index 00000000000..f9223a18b52 --- /dev/null +++ b/modules/markup/orgmode/orgmode.go @@ -0,0 +1,56 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package markup + +import ( + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" + + "github.com/chaseadamsio/goorgeous" + "github.com/russross/blackfriday" +) + +func init() { + markup.RegisterParser(Parser{}) +} + +// Parser implements markup.Parser for orgmode +type Parser struct { +} + +// Name implements markup.Parser +func (Parser) Name() string { + return "orgmode" +} + +// Extensions implements markup.Parser +func (Parser) Extensions() []string { + return []string{".org"} +} + +// Render renders orgmode rawbytes to HTML +func Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte { + htmlFlags := blackfriday.HTML_USE_XHTML + htmlFlags |= blackfriday.HTML_SKIP_STYLE + htmlFlags |= blackfriday.HTML_OMIT_CONTENTS + renderer := &markdown.Renderer{ + Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""), + URLPrefix: urlPrefix, + IsWiki: isWiki, + } + + result := goorgeous.Org(rawBytes, renderer) + return result +} + +// RenderString reners orgmode string to HTML string +func RenderString(rawContent string, urlPrefix string, metas map[string]string, isWiki bool) string { + return string(Render([]byte(rawContent), urlPrefix, metas, isWiki)) +} + +// Render implements markup.Parser +func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte { + return Render(rawBytes, urlPrefix, metas, isWiki) +} diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go new file mode 100644 index 00000000000..a68ab5d3afd --- /dev/null +++ b/modules/markup/orgmode/orgmode_test.go @@ -0,0 +1,54 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package markup + +import ( + "strings" + "testing" + + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +const AppURL = "http://localhost:3000/" +const Repo = "gogits/gogs" +const AppSubURL = AppURL + Repo + "/" + +func TestRender_StandardLinks(t *testing.T) { + setting.AppURL = AppURL + setting.AppSubURL = AppSubURL + + test := func(input, expected string) { + buffer := RenderString(input, setting.AppSubURL, nil, false) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) + } + + googleRendered := `

https://google.com/

` + test("[[https://google.com/]]", googleRendered) + + lnk := markup.URLJoin(AppSubURL, "WikiPage") + test("[[WikiPage][WikiPage]]", + `

WikiPage

`) +} + +func TestRender_Images(t *testing.T) { + setting.AppURL = AppURL + setting.AppSubURL = AppSubURL + + test := func(input, expected string) { + buffer := RenderString(input, setting.AppSubURL, nil, false) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) + } + + url := "../../.images/src/02/train.jpg" + title := "Train" + result := markup.URLJoin(AppSubURL, url) + + test( + "[[file:"+url+"]["+title+"]]", + `

`+title+`

`) +} diff --git a/routers/api/v1/misc/markdown.go b/routers/api/v1/misc/markdown.go index a2e65ecb0ad..8e3c66841f8 100644 --- a/routers/api/v1/misc/markdown.go +++ b/routers/api/v1/misc/markdown.go @@ -8,8 +8,8 @@ import ( api "code.gitea.io/sdk/gitea" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/markdown" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" ) diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 4c4f9037bf2..091268116bf 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -24,7 +24,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/indexer" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markdown" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" diff --git a/routers/repo/release.go b/routers/repo/release.go index fe68f1b6f13..da99dd77138 100644 --- a/routers/repo/release.go +++ b/routers/repo/release.go @@ -12,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markdown" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "github.com/Unknwon/paginater" diff --git a/routers/repo/view.go b/routers/repo/view.go index d794a574059..bfba7acac82 100644 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -95,11 +95,11 @@ func renderDirectory(ctx *context.Context, treeLink string) { buf = append(buf, d...) newbuf := markup.Render(readmeFile.Name(), buf, treeLink, ctx.Repo.Repository.ComposeMetas()) if newbuf != nil { - ctx.Data["IsMarkdown"] = true + ctx.Data["IsMarkup"] = true } else { // FIXME This is the only way to show non-markdown files // instead of a broken "View Raw" link - ctx.Data["IsMarkdown"] = true + ctx.Data["IsMarkup"] = false newbuf = bytes.Replace(buf, []byte("\n"), []byte(`
`), -1) } ctx.Data["FileContent"] = string(newbuf) @@ -197,10 +197,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st tp := markup.Type(blob.Name()) isSupportedMarkup := tp != "" - // FIXME: currently set IsMarkdown for compatible - ctx.Data["IsMarkdown"] = isSupportedMarkup - - readmeExist := isSupportedMarkup || markup.IsReadmeFile(blob.Name()) + ctx.Data["IsMarkup"] = isSupportedMarkup + readmeExist := markup.IsReadmeFile(blob.Name()) ctx.Data["ReadmeExist"] = readmeExist if readmeExist && isSupportedMarkup { ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas())) diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index 2a73fdc41e9..019c3d5d168 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -18,8 +18,8 @@ import ( "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/markdown" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" ) const ( diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index 36fccb00b34..898b9b55576 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -36,8 +36,8 @@ {{end}}
-
- {{if .IsMarkdown}} +
+ {{if .IsMarkup}} {{if .FileContent}}{{.FileContent | Str2html}}{{end}} {{else if not .IsTextFile}}
diff --git a/vendor/github.com/chaseadamsio/goorgeous/LICENSE b/vendor/github.com/chaseadamsio/goorgeous/LICENSE new file mode 100644 index 00000000000..d7a37c6a3b3 --- /dev/null +++ b/vendor/github.com/chaseadamsio/goorgeous/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Chase Adams + +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. diff --git a/vendor/github.com/chaseadamsio/goorgeous/README.org b/vendor/github.com/chaseadamsio/goorgeous/README.org new file mode 100644 index 00000000000..37e0f2ec730 --- /dev/null +++ b/vendor/github.com/chaseadamsio/goorgeous/README.org @@ -0,0 +1,66 @@ +#+TITLE: chaseadamsio/goorgeous + +[[https://travis-ci.org/chaseadamsio/goorgeous.svg?branch=master]] +[[https://coveralls.io/repos/github/chaseadamsio/goorgeous/badge.svg?branch=master]] + +/goorgeous is a Go Org to HTML Parser./ + +[[file:gopher_small.gif]] + +*Pronounced: Go? Org? Yes!* + +#+BEGIN_QUOTE +"Org mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system." + +- [[orgmode.org]] +#+END_QUOTE + +The purpose of this package is to come as close as possible as parsing an =*.org= document into HTML, the same way one might publish [[http://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html][with org-publish-html from Emacs]]. + +* Installation + +#+BEGIN_SRC sh + go get -u github.com/chaseadamsio/goorgeous +#+END_SRC + +* Usage + +** Org Headers + +To retrieve the headers from a =[]byte=, call =OrgHeaders= and it will return a =map[string]interface{}=: + +#+BEGIN_SRC go + input := "#+title: goorgeous\n* Some Headline\n" + out := goorgeous.OrgHeaders(input) +#+END_SRC + +#+BEGIN_SRC go + map[string]interface{}{ + "title": "goorgeous" + } +#+END_SRC + +** Org Content + +After importing =github.com/chaseadamsio/goorgeous=, you can call =Org= with a =[]byte= and it will return an =html= version of the content as a =[]byte= + +#+BEGIN_SRC go + input := "#+TITLE: goorgeous\n* Some Headline\n" + out := goorgeous.Org(input) +#+END_SRC + +=out= will be: + +#+BEGIN_SRC html +

Some Headline

/n +#+END_SRC + +* Why? + +First off, I've become an unapologetic user of Emacs & ever since finding =org-mode= I use it for anything having to do with writing content, organizing my life and keeping documentation of my days/weeks/months. + +Although I like Emacs & =emacs-lisp=, I publish all of my html sites with [[https://gohugo.io][Hugo Static Site Generator]] and wanted to be able to write my content in =org-mode= in Emacs rather than markdown. + +Hugo's implementation of templating and speed are unmatched, so the only way I knew for sure I could continue to use Hugo and write in =org-mode= seamlessly was to write a golang parser for org content and submit a PR for Hugo to use it. +* Acknowledgements +I leaned heavily on russross' [[https://github.com/russross/blackfriday][blackfriday markdown renderer]] as both an example of how to write a parser (with some updates to leverage the go we know today) and reusing the blackfriday HTML Renderer so I didn't have to write my own! diff --git a/vendor/github.com/chaseadamsio/goorgeous/goorgeous.go b/vendor/github.com/chaseadamsio/goorgeous/goorgeous.go new file mode 100644 index 00000000000..f1b2671d657 --- /dev/null +++ b/vendor/github.com/chaseadamsio/goorgeous/goorgeous.go @@ -0,0 +1,803 @@ +package goorgeous + +import ( + "bufio" + "bytes" + "regexp" + + "github.com/russross/blackfriday" + "github.com/shurcooL/sanitized_anchor_name" +) + +type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int + +type footnotes struct { + id string + def string +} + +type parser struct { + r blackfriday.Renderer + inlineCallback [256]inlineParser + notes []footnotes +} + +// NewParser returns a new parser with the inlineCallbacks required for org content +func NewParser(renderer blackfriday.Renderer) *parser { + p := new(parser) + p.r = renderer + + p.inlineCallback['='] = generateVerbatim + p.inlineCallback['~'] = generateCode + p.inlineCallback['/'] = generateEmphasis + p.inlineCallback['_'] = generateUnderline + p.inlineCallback['*'] = generateBold + p.inlineCallback['+'] = generateStrikethrough + p.inlineCallback['['] = generateLinkOrImg + + return p +} + +// OrgCommon is the easiest way to parse a byte slice of org content and makes assumptions +// that the caller wants to use blackfriday's HTMLRenderer with XHTML +func OrgCommon(input []byte) []byte { + renderer := blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML, "", "") + return OrgOptions(input, renderer) +} + +// Org is a convenience name for OrgOptions +func Org(input []byte, renderer blackfriday.Renderer) []byte { + return OrgOptions(input, renderer) +} + +// OrgOptions takes an org content byte slice and a renderer to use +func OrgOptions(input []byte, renderer blackfriday.Renderer) []byte { + // in the case that we need to render something in isEmpty but there isn't a new line char + input = append(input, '\n') + var output bytes.Buffer + + p := NewParser(renderer) + + scanner := bufio.NewScanner(bytes.NewReader(input)) + // used to capture code blocks + marker := "" + syntax := "" + listType := "" + inParagraph := false + inList := false + inTable := false + inFixedWidthArea := false + var tmpBlock bytes.Buffer + + for scanner.Scan() { + data := scanner.Bytes() + + if !isEmpty(data) && isComment(data) || IsKeyword(data) { + switch { + case inList: + if tmpBlock.Len() > 0 { + p.generateList(&output, tmpBlock.Bytes(), listType) + } + inList = false + listType = "" + tmpBlock.Reset() + case inTable: + if tmpBlock.Len() > 0 { + p.generateTable(&output, tmpBlock.Bytes()) + } + inTable = false + tmpBlock.Reset() + case inParagraph: + if tmpBlock.Len() > 0 { + p.generateParagraph(&output, tmpBlock.Bytes()[:len(tmpBlock.Bytes())-1]) + } + inParagraph = false + tmpBlock.Reset() + case inFixedWidthArea: + if tmpBlock.Len() > 0 { + tmpBlock.WriteString("\n") + output.Write(tmpBlock.Bytes()) + } + inFixedWidthArea = false + tmpBlock.Reset() + } + + } + + switch { + case isEmpty(data): + switch { + case inList: + if tmpBlock.Len() > 0 { + p.generateList(&output, tmpBlock.Bytes(), listType) + } + inList = false + listType = "" + tmpBlock.Reset() + case inTable: + if tmpBlock.Len() > 0 { + p.generateTable(&output, tmpBlock.Bytes()) + } + inTable = false + tmpBlock.Reset() + case inParagraph: + if tmpBlock.Len() > 0 { + p.generateParagraph(&output, tmpBlock.Bytes()[:len(tmpBlock.Bytes())-1]) + } + inParagraph = false + tmpBlock.Reset() + case inFixedWidthArea: + if tmpBlock.Len() > 0 { + tmpBlock.WriteString("\n") + output.Write(tmpBlock.Bytes()) + } + inFixedWidthArea = false + tmpBlock.Reset() + case marker != "": + tmpBlock.WriteByte('\n') + default: + continue + } + case isPropertyDrawer(data) || marker == "PROPERTIES": + if marker == "" { + marker = "PROPERTIES" + } + if bytes.Equal(data, []byte(":END:")) { + marker = "" + } + continue + case isBlock(data) || marker != "": + matches := reBlock.FindSubmatch(data) + if len(matches) > 0 { + if string(matches[1]) == "END" { + switch marker { + case "QUOTE": + var tmpBuf bytes.Buffer + p.inline(&tmpBuf, tmpBlock.Bytes()) + p.r.BlockQuote(&output, tmpBuf.Bytes()) + case "CENTER": + var tmpBuf bytes.Buffer + output.WriteString("
\n") + p.inline(&tmpBuf, tmpBlock.Bytes()) + output.Write(tmpBuf.Bytes()) + output.WriteString("
\n") + default: + tmpBlock.WriteByte('\n') + p.r.BlockCode(&output, tmpBlock.Bytes(), syntax) + } + marker = "" + tmpBlock.Reset() + continue + } + + } + if marker != "" { + if marker != "SRC" && marker != "EXAMPLE" { + var tmpBuf bytes.Buffer + tmpBuf.Write([]byte("

\n")) + p.inline(&tmpBuf, data) + tmpBuf.WriteByte('\n') + tmpBuf.Write([]byte("

\n")) + tmpBlock.Write(tmpBuf.Bytes()) + + } else { + tmpBlock.WriteByte('\n') + tmpBlock.Write(data) + } + + } else { + marker = string(matches[2]) + syntax = string(matches[3]) + } + case isFootnoteDef(data): + matches := reFootnoteDef.FindSubmatch(data) + for i := range p.notes { + if p.notes[i].id == string(matches[1]) { + p.notes[i].def = string(matches[2]) + } + } + case isTable(data): + if inTable != true { + inTable = true + } + tmpBlock.Write(data) + tmpBlock.WriteByte('\n') + case IsKeyword(data): + continue + case isComment(data): + p.generateComment(&output, data) + case isHeadline(data): + p.generateHeadline(&output, data) + case isDefinitionList(data): + if inList != true { + listType = "dl" + inList = true + } + var work bytes.Buffer + flags := blackfriday.LIST_TYPE_DEFINITION + matches := reDefinitionList.FindSubmatch(data) + flags |= blackfriday.LIST_TYPE_TERM + p.inline(&work, matches[1]) + p.r.ListItem(&tmpBlock, work.Bytes(), flags) + work.Reset() + flags &= ^blackfriday.LIST_TYPE_TERM + p.inline(&work, matches[2]) + p.r.ListItem(&tmpBlock, work.Bytes(), flags) + case isUnorderedList(data): + if inList != true { + listType = "ul" + inList = true + } + matches := reUnorderedList.FindSubmatch(data) + var work bytes.Buffer + p.inline(&work, matches[2]) + p.r.ListItem(&tmpBlock, work.Bytes(), 0) + case isOrderedList(data): + if inList != true { + listType = "ol" + inList = true + } + matches := reOrderedList.FindSubmatch(data) + var work bytes.Buffer + tmpBlock.WriteString(" 0 { + tmpBlock.WriteString(" value=\"") + tmpBlock.Write(matches[2]) + tmpBlock.WriteString("\"") + matches[3] = matches[3][1:] + } + p.inline(&work, matches[3]) + tmpBlock.WriteString(">") + tmpBlock.Write(work.Bytes()) + tmpBlock.WriteString("\n") + case isHorizontalRule(data): + p.r.HRule(&output) + case isExampleLine(data): + if inParagraph == true { + if len(tmpBlock.Bytes()) > 0 { + p.generateParagraph(&output, tmpBlock.Bytes()[:len(tmpBlock.Bytes())-1]) + inParagraph = false + } + tmpBlock.Reset() + } + if inFixedWidthArea != true { + tmpBlock.WriteString("
\n")
+				inFixedWidthArea = true
+			}
+			matches := reExampleLine.FindSubmatch(data)
+			tmpBlock.Write(matches[1])
+			tmpBlock.WriteString("\n")
+			break
+		default:
+			if inParagraph == false {
+				inParagraph = true
+				if inFixedWidthArea == true {
+					if tmpBlock.Len() > 0 {
+						tmpBlock.WriteString("
") + output.Write(tmpBlock.Bytes()) + } + inFixedWidthArea = false + tmpBlock.Reset() + } + } + tmpBlock.Write(data) + tmpBlock.WriteByte('\n') + } + } + + if len(tmpBlock.Bytes()) > 0 { + if inParagraph == true { + p.generateParagraph(&output, tmpBlock.Bytes()[:len(tmpBlock.Bytes())-1]) + } else if inFixedWidthArea == true { + tmpBlock.WriteString("\n") + output.Write(tmpBlock.Bytes()) + } + } + + // Writing footnote def. list + if len(p.notes) > 0 { + flags := blackfriday.LIST_ITEM_BEGINNING_OF_LIST + p.r.Footnotes(&output, func() bool { + for i := range p.notes { + p.r.FootnoteItem(&output, []byte(p.notes[i].id), []byte(p.notes[i].def), flags) + } + return true + }) + } + + return output.Bytes() +} + +// Org Syntax has been broken up into 4 distinct sections based on +// the org-syntax draft (http://orgmode.org/worg/dev/org-syntax.html): +// - Headlines +// - Greater Elements +// - Elements +// - Objects + +// Headlines +func isHeadline(data []byte) bool { + if !charMatches(data[0], '*') { + return false + } + level := 0 + for level < 6 && charMatches(data[level], '*') { + level++ + } + return charMatches(data[level], ' ') +} + +func (p *parser) generateHeadline(out *bytes.Buffer, data []byte) { + level := 1 + status := "" + priority := "" + + for level < 6 && data[level] == '*' { + level++ + } + + start := skipChar(data, level, ' ') + + data = data[start:] + i := 0 + + // Check if has a status so it can be rendered as a separate span that can be hidden or + // modified with CSS classes + if hasStatus(data[i:4]) { + status = string(data[i:4]) + i += 5 // one extra character for the next whitespace + } + + // Check if the next byte is a priority marker + if data[i] == '[' && hasPriority(data[i+1]) { + priority = string(data[i+1]) + i += 4 // for "[c]" + ' ' + } + + tags, tagsFound := findTags(data, i) + + headlineID := sanitized_anchor_name.Create(string(data[i:])) + + generate := func() bool { + dataEnd := len(data) + if tagsFound > 0 { + dataEnd = tagsFound + } + + headline := bytes.TrimRight(data[i:dataEnd], " \t") + + if status != "" { + out.WriteString("" + status + "") + out.WriteByte(' ') + } + + if priority != "" { + out.WriteString("[" + priority + "]") + out.WriteByte(' ') + } + + p.inline(out, headline) + + if tagsFound > 0 { + for _, tag := range tags { + out.WriteByte(' ') + out.WriteString("" + tag + "") + out.WriteByte(' ') + } + } + return true + } + + p.r.Header(out, generate, level, headlineID) +} + +func hasStatus(data []byte) bool { + return bytes.Contains(data, []byte("TODO")) || bytes.Contains(data, []byte("DONE")) +} + +func hasPriority(char byte) bool { + return (charMatches(char, 'A') || charMatches(char, 'B') || charMatches(char, 'C')) +} + +func findTags(data []byte, start int) ([]string, int) { + tags := []string{} + tagOpener := 0 + tagMarker := tagOpener + for tIdx := start; tIdx < len(data); tIdx++ { + if tagMarker > 0 && data[tIdx] == ':' { + tags = append(tags, string(data[tagMarker+1:tIdx])) + tagMarker = tIdx + } + if data[tIdx] == ':' && tagOpener == 0 && data[tIdx-1] == ' ' { + tagMarker = tIdx + tagOpener = tIdx + } + } + return tags, tagOpener +} + +// Greater Elements +// ~~ Definition Lists +var reDefinitionList = regexp.MustCompile(`^\s*-\s+(.+?)\s+::\s+(.*)`) + +func isDefinitionList(data []byte) bool { + return reDefinitionList.Match(data) +} + +// ~~ Example lines +var reExampleLine = regexp.MustCompile(`^\s*:\s(\s*.*)|^\s*:$`) + +func isExampleLine(data []byte) bool { + return reExampleLine.Match(data) +} + +// ~~ Ordered Lists +var reOrderedList = regexp.MustCompile(`^(\s*)\d+\.\s+\[?@?(\d*)\]?(.+)`) + +func isOrderedList(data []byte) bool { + return reOrderedList.Match(data) +} + +// ~~ Unordered Lists +var reUnorderedList = regexp.MustCompile(`^(\s*)[-\+]\s+(.+)`) + +func isUnorderedList(data []byte) bool { + return reUnorderedList.Match(data) +} + +// ~~ Tables +var reTableHeaders = regexp.MustCompile(`^[|+-]*$`) + +func isTable(data []byte) bool { + return charMatches(data[0], '|') +} + +func (p *parser) generateTable(output *bytes.Buffer, data []byte) { + var table bytes.Buffer + rows := bytes.Split(bytes.Trim(data, "\n"), []byte("\n")) + hasTableHeaders := len(rows) > 1 + if len(rows) > 1 { + hasTableHeaders = reTableHeaders.Match(rows[1]) + } + tbodySet := false + + for idx, row := range rows { + var rowBuff bytes.Buffer + if hasTableHeaders && idx == 0 { + table.WriteString("") + for _, cell := range bytes.Split(row[1:len(row)-1], []byte("|")) { + p.r.TableHeaderCell(&rowBuff, bytes.Trim(cell, " \t"), 0) + } + p.r.TableRow(&table, rowBuff.Bytes()) + table.WriteString("\n") + } else if hasTableHeaders && idx == 1 { + continue + } else { + if !tbodySet { + table.WriteString("") + tbodySet = true + } + if !reTableHeaders.Match(row) { + for _, cell := range bytes.Split(row[1:len(row)-1], []byte("|")) { + var cellBuff bytes.Buffer + p.inline(&cellBuff, bytes.Trim(cell, " \t")) + p.r.TableCell(&rowBuff, cellBuff.Bytes(), 0) + } + p.r.TableRow(&table, rowBuff.Bytes()) + } + if tbodySet && idx == len(rows)-1 { + table.WriteString("\n") + tbodySet = false + } + } + } + + output.WriteString("\n\n") + output.Write(table.Bytes()) + output.WriteString("
\n") +} + +// ~~ Property Drawers + +func isPropertyDrawer(data []byte) bool { + return bytes.Equal(data, []byte(":PROPERTIES:")) +} + +// ~~ Dynamic Blocks +var reBlock = regexp.MustCompile(`^#\+(BEGIN|END)_(\w+)\s*([0-9A-Za-z_\-]*)?`) + +func isBlock(data []byte) bool { + return reBlock.Match(data) +} + +// ~~ Footnotes +var reFootnoteDef = regexp.MustCompile(`^\[fn:([\w]+)\] +(.+)`) + +func isFootnoteDef(data []byte) bool { + return reFootnoteDef.Match(data) +} + +// Elements +// ~~ Keywords +func IsKeyword(data []byte) bool { + return len(data) > 2 && charMatches(data[0], '#') && charMatches(data[1], '+') && !charMatches(data[2], ' ') +} + +// ~~ Comments +func isComment(data []byte) bool { + return charMatches(data[0], '#') && charMatches(data[1], ' ') +} + +func (p *parser) generateComment(out *bytes.Buffer, data []byte) { + var work bytes.Buffer + work.WriteString("") + work.WriteByte('\n') + out.Write(work.Bytes()) +} + +// ~~ Horizontal Rules +var reHorizontalRule = regexp.MustCompile(`^\s*?-----\s?$`) + +func isHorizontalRule(data []byte) bool { + return reHorizontalRule.Match(data) +} + +// ~~ Paragraphs +func (p *parser) generateParagraph(out *bytes.Buffer, data []byte) { + generate := func() bool { + p.inline(out, bytes.Trim(data, " ")) + return true + } + p.r.Paragraph(out, generate) +} + +func (p *parser) generateList(output *bytes.Buffer, data []byte, listType string) { + generateList := func() bool { + output.WriteByte('\n') + p.inline(output, bytes.Trim(data, " ")) + return true + } + switch listType { + case "ul": + p.r.List(output, generateList, 0) + case "ol": + p.r.List(output, generateList, blackfriday.LIST_TYPE_ORDERED) + case "dl": + p.r.List(output, generateList, blackfriday.LIST_TYPE_DEFINITION) + } +} + +// Objects + +func (p *parser) inline(out *bytes.Buffer, data []byte) { + i, end := 0, 0 + + for i < len(data) { + for end < len(data) && p.inlineCallback[data[end]] == nil { + end++ + } + + p.r.Entity(out, data[i:end]) + + if end >= len(data) { + break + } + i = end + + handler := p.inlineCallback[data[i]] + + if consumed := handler(p, out, data, i); consumed > 0 { + i += consumed + end = i + continue + } + + end = i + 1 + } +} + +func isAcceptablePreOpeningChar(dataIn, data []byte, offset int) bool { + if len(dataIn) == len(data) { + return true + } + + char := dataIn[offset-1] + return charMatches(char, ' ') || isPreChar(char) +} + +func isPreChar(char byte) bool { + return charMatches(char, '>') || charMatches(char, '(') || charMatches(char, '{') || charMatches(char, '[') +} + +func isAcceptablePostClosingChar(char byte) bool { + return charMatches(char, ' ') || isTerminatingChar(char) +} + +func isTerminatingChar(char byte) bool { + return charMatches(char, '.') || charMatches(char, ',') || charMatches(char, '?') || charMatches(char, '!') || charMatches(char, ')') || charMatches(char, '}') || charMatches(char, ']') +} + +func findLastCharInInline(data []byte, char byte) int { + timesFound := 0 + last := 0 + // Start from character after the inline indicator + for i := 1; i < len(data); i++ { + if timesFound == 1 { + break + } + if data[i] == char { + if len(data) == i+1 || (len(data) > i+1 && isAcceptablePostClosingChar(data[i+1])) { + last = i + timesFound += 1 + } + } + } + return last +} + +func generator(p *parser, out *bytes.Buffer, dataIn []byte, offset int, char byte, doInline bool, renderer func(*bytes.Buffer, []byte)) int { + data := dataIn[offset:] + c := byte(char) + start := 1 + i := start + if len(data) <= 1 { + return 0 + } + + lastCharInside := findLastCharInInline(data, c) + + // Org mode spec says a non-whitespace character must immediately follow. + // if the current char is the marker, then there's no text between, not a candidate + if isSpace(data[i]) || lastCharInside == i || !isAcceptablePreOpeningChar(dataIn, data, offset) { + return 0 + } + + if lastCharInside > 0 { + var work bytes.Buffer + if doInline { + p.inline(&work, data[start:lastCharInside]) + renderer(out, work.Bytes()) + } else { + renderer(out, data[start:lastCharInside]) + } + next := lastCharInside + 1 + return next + } + + return 0 +} + +// ~~ Text Markup +func generateVerbatim(p *parser, out *bytes.Buffer, data []byte, offset int) int { + return generator(p, out, data, offset, '=', false, p.r.CodeSpan) +} + +func generateCode(p *parser, out *bytes.Buffer, data []byte, offset int) int { + return generator(p, out, data, offset, '~', false, p.r.CodeSpan) +} + +func generateEmphasis(p *parser, out *bytes.Buffer, data []byte, offset int) int { + return generator(p, out, data, offset, '/', true, p.r.Emphasis) +} + +func generateUnderline(p *parser, out *bytes.Buffer, data []byte, offset int) int { + underline := func(out *bytes.Buffer, text []byte) { + out.WriteString("") + out.Write(text) + out.WriteString("") + } + + return generator(p, out, data, offset, '_', true, underline) +} + +func generateBold(p *parser, out *bytes.Buffer, data []byte, offset int) int { + return generator(p, out, data, offset, '*', true, p.r.DoubleEmphasis) +} + +func generateStrikethrough(p *parser, out *bytes.Buffer, data []byte, offset int) int { + return generator(p, out, data, offset, '+', true, p.r.StrikeThrough) +} + +// ~~ Images and Links (inc. Footnote) +var reLinkOrImg = regexp.MustCompile(`\[\[(.+?)\]\[?(.*?)\]?\]`) + +func generateLinkOrImg(p *parser, out *bytes.Buffer, data []byte, offset int) int { + data = data[offset+1:] + start := 1 + i := start + var hyperlink []byte + isImage := false + isFootnote := false + closedLink := false + hasContent := false + + if bytes.Equal(data[0:3], []byte("fn:")) { + isFootnote = true + } else if data[0] != '[' { + return 0 + } + + if bytes.Equal(data[1:6], []byte("file:")) { + isImage = true + } + + for i < len(data) { + currChar := data[i] + switch { + case charMatches(currChar, ']') && closedLink == false: + if isImage { + hyperlink = data[start+5 : i] + } else if isFootnote { + refid := data[start+2 : i] + if bytes.Equal(refid, bytes.Trim(refid, " ")) { + p.notes = append(p.notes, footnotes{string(refid), "DEFINITION NOT FOUND"}) + p.r.FootnoteRef(out, refid, len(p.notes)) + return i + 2 + } else { + return 0 + } + } else if bytes.Equal(data[i-4:i], []byte(".org")) { + orgStart := start + if bytes.Equal(data[orgStart:orgStart+2], []byte("./")) { + orgStart = orgStart + 1 + } + hyperlink = data[orgStart : i-4] + } else { + hyperlink = data[start:i] + } + closedLink = true + case charMatches(currChar, '['): + start = i + 1 + hasContent = true + case charMatches(currChar, ']') && closedLink == true && hasContent == true && isImage == true: + p.r.Image(out, hyperlink, data[start:i], data[start:i]) + return i + 3 + case charMatches(currChar, ']') && closedLink == true && hasContent == true: + var tmpBuf bytes.Buffer + p.inline(&tmpBuf, data[start:i]) + p.r.Link(out, hyperlink, tmpBuf.Bytes(), tmpBuf.Bytes()) + return i + 3 + case charMatches(currChar, ']') && closedLink == true && hasContent == false && isImage == true: + p.r.Image(out, hyperlink, hyperlink, hyperlink) + return i + 2 + case charMatches(currChar, ']') && closedLink == true && hasContent == false: + p.r.Link(out, hyperlink, hyperlink, hyperlink) + return i + 2 + } + i++ + } + + return 0 +} + +// Helpers +func skipChar(data []byte, start int, char byte) int { + i := start + for i < len(data) && charMatches(data[i], char) { + i++ + } + return i +} + +func isSpace(char byte) bool { + return charMatches(char, ' ') +} + +func isEmpty(data []byte) bool { + if len(data) == 0 { + return true + } + + for i := 0; i < len(data) && !charMatches(data[i], '\n'); i++ { + if !charMatches(data[i], ' ') && !charMatches(data[i], '\t') { + return false + } + } + return true +} + +func charMatches(a byte, b byte) bool { + return a == b +} diff --git a/vendor/github.com/chaseadamsio/goorgeous/gopher.gif b/vendor/github.com/chaseadamsio/goorgeous/gopher.gif new file mode 100644 index 0000000000000000000000000000000000000000..be7567e3cfd666ee40c56b1d808f97116c092053 GIT binary patch literal 15232 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 AcmMzZ literal 0 HcmV?d00001 diff --git a/vendor/github.com/chaseadamsio/goorgeous/gopher_small.gif b/vendor/github.com/chaseadamsio/goorgeous/gopher_small.gif new file mode 100644 index 0000000000000000000000000000000000000000..1cd31fdd0c1cdd945e36871c78525bef41b7a1b9 GIT binary patch literal 3270 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 literal 0 HcmV?d00001 diff --git a/vendor/github.com/chaseadamsio/goorgeous/header.go b/vendor/github.com/chaseadamsio/goorgeous/header.go new file mode 100644 index 00000000000..66e8b993219 --- /dev/null +++ b/vendor/github.com/chaseadamsio/goorgeous/header.go @@ -0,0 +1,70 @@ +package goorgeous + +import ( + "bufio" + "bytes" + "regexp" + "strings" +) + +// ExtractOrgHeaders finds and returns all of the headers +// from a bufio.Reader and returns them as their own byte slice +func ExtractOrgHeaders(r *bufio.Reader) (fm []byte, err error) { + var out bytes.Buffer + endOfHeaders := true + for endOfHeaders { + p, err := r.Peek(2) + if err != nil { + return nil, err + } + if !charMatches(p[0], '#') && !charMatches(p[1], '+') { + endOfHeaders = false + break + } + line, _, err := r.ReadLine() + if err != nil { + return nil, err + } + out.Write(line) + out.WriteByte('\n') + } + return out.Bytes(), nil +} + +var reHeader = regexp.MustCompile(`^#\+(\w+?): (.*)`) + +// OrgHeaders find all of the headers from a byte slice and returns +// them as a map of string interface +func OrgHeaders(input []byte) (map[string]interface{}, error) { + out := make(map[string]interface{}) + scanner := bufio.NewScanner(bytes.NewReader(input)) + + for scanner.Scan() { + data := scanner.Bytes() + if !charMatches(data[0], '#') && !charMatches(data[1], '+') { + return out, nil + } + matches := reHeader.FindSubmatch(data) + + if len(matches) < 3 { + continue + } + + key := string(matches[1]) + val := matches[2] + switch { + case strings.ToLower(key) == "tags" || strings.ToLower(key) == "categories" || strings.ToLower(key) == "aliases": + bTags := bytes.Split(val, []byte(" ")) + tags := make([]string, len(bTags)) + for idx, tag := range bTags { + tags[idx] = string(tag) + } + out[key] = tags + default: + out[key] = string(val) + } + + } + return out, nil + +} diff --git a/vendor/vendor.json b/vendor/vendor.json index d9cb18cd1ac..101c72c2172 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -299,6 +299,12 @@ "revision": "fb1f79c6b65acda83063cbc69f6bba1522558bfc", "revisionTime": "2016-01-17T19:21:50Z" }, + { + "checksumSHA1": "x1svIugw39oEZGU5/HMUHzgRUZM=", + "path": "github.com/chaseadamsio/goorgeous", + "revision": "098da33fde5f9220736531b3cb26a2dec86a8367", + "revisionTime": "2017-09-01T13:22:37Z" + }, { "checksumSHA1": "agNqSytP0indDCoGizlMyC1L/m4=", "path": "github.com/coreos/etcd/error",