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 383dbc2093..c2acda99af 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 afcddb6d23..98766f69f2 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 407115526d..ab2ca5ef47 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 6cf2d9eaa1..f0ed0e03ab 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 1b57e4f203..9ca3de01ca 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 8d061ae39e..b0ebfae57d 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 0000000000..f9223a18b5 --- /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 0000000000..a68ab5d3af --- /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 a2e65ecb0a..8e3c66841f 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 4c4f9037bf..091268116b 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 fe68f1b6f1..da99dd7713 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 d794a57405..bfba7acac8 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 2a73fdc41e..019c3d5d16 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 36fccb00b3..898b9b5557 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 0000000000..d7a37c6a3b --- /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 0000000000..37e0f2ec73 --- /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 0000000000..f1b2671d65 --- /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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 vTwbHjFEt;&5pDgN=1cw&96@-xsG_RGKMSARK% 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