]> source.dussan.org Git - gitea.git/commitdiff
Add init support of orgmode document type on file view and readme (#2525)
authorLunny Xiao <xiaolunwen@gmail.com>
Thu, 21 Sep 2017 05:20:14 +0000 (13:20 +0800)
committerGitHub <noreply@github.com>
Thu, 21 Sep 2017 05:20:14 +0000 (13:20 +0800)
* 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

23 files changed:
main.go
models/mail.go
modules/markdown/markdown.go [deleted file]
modules/markdown/markdown_test.go [deleted file]
modules/markup/html_test.go
modules/markup/markdown/markdown.go [new file with mode: 0644]
modules/markup/markdown/markdown_test.go [new file with mode: 0644]
modules/markup/markup_test.go
modules/markup/orgmode/orgmode.go [new file with mode: 0644]
modules/markup/orgmode/orgmode_test.go [new file with mode: 0644]
routers/api/v1/misc/markdown.go
routers/repo/issue.go
routers/repo/release.go
routers/repo/view.go
routers/repo/wiki.go
templates/repo/view_file.tmpl
vendor/github.com/chaseadamsio/goorgeous/LICENSE [new file with mode: 0644]
vendor/github.com/chaseadamsio/goorgeous/README.org [new file with mode: 0644]
vendor/github.com/chaseadamsio/goorgeous/goorgeous.go [new file with mode: 0644]
vendor/github.com/chaseadamsio/goorgeous/gopher.gif [new file with mode: 0644]
vendor/github.com/chaseadamsio/goorgeous/gopher_small.gif [new file with mode: 0644]
vendor/github.com/chaseadamsio/goorgeous/header.go [new file with mode: 0644]
vendor/vendor.json

diff --git a/main.go b/main.go
index 383dbc209316d3233022cb6b3c2f355a6ff24c43..c2acda99afc476420f37f8f7b14d348cd5af1517 100644 (file)
--- 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"
 )
 
index afcddb6d239737dfaf92ea2c69bf960be09ed995..98766f69f2cf3724d72d51671dbc72c07a92fabd 100644 (file)
@@ -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/markdown/markdown.go b/modules/markdown/markdown.go
deleted file mode 100644 (file)
index 6cf2d9e..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright 2014 The Gogs 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 markdown
-
-import (
-       "bytes"
-       "strings"
-
-       "code.gitea.io/gitea/modules/markup"
-       "code.gitea.io/gitea/modules/setting"
-
-       "github.com/russross/blackfriday"
-)
-
-// Renderer is a extended version of underlying render object.
-type Renderer struct {
-       blackfriday.Renderer
-       urlPrefix      string
-       isWikiMarkdown bool
-}
-
-// Link defines how formal links should be processed to produce corresponding HTML elements.
-func (r *Renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
-       if len(link) > 0 && !markup.IsLink(link) {
-               if link[0] != '#' {
-                       lnk := string(link)
-                       if r.isWikiMarkdown {
-                               lnk = markup.URLJoin("wiki", lnk)
-                       }
-                       mLink := markup.URLJoin(r.urlPrefix, lnk)
-                       link = []byte(mLink)
-               }
-       }
-
-       r.Renderer.Link(out, link, title, content)
-}
-
-// List renders markdown bullet or digit lists to HTML
-func (r *Renderer) List(out *bytes.Buffer, text func() bool, flags int) {
-       marker := out.Len()
-       if out.Len() > 0 {
-               out.WriteByte('\n')
-       }
-
-       if flags&blackfriday.LIST_TYPE_DEFINITION != 0 {
-               out.WriteString("<dl>")
-       } else if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
-               out.WriteString("<ol class='ui list'>")
-       } else {
-               out.WriteString("<ul class='ui list'>")
-       }
-       if !text() {
-               out.Truncate(marker)
-               return
-       }
-       if flags&blackfriday.LIST_TYPE_DEFINITION != 0 {
-               out.WriteString("</dl>\n")
-       } else if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
-               out.WriteString("</ol>\n")
-       } else {
-               out.WriteString("</ul>\n")
-       }
-}
-
-// ListItem defines how list items should be processed to produce corresponding HTML elements.
-func (r *Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
-       // Detect procedures to draw checkboxes.
-       prefix := ""
-       if bytes.HasPrefix(text, []byte("<p>")) {
-               prefix = "<p>"
-       }
-       switch {
-       case bytes.HasPrefix(text, []byte(prefix+"[ ] ")):
-               text = append([]byte(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></span>`), text[3+len(prefix):]...)
-               if prefix != "" {
-                       text = bytes.Replace(text, []byte(prefix), []byte{}, 1)
-               }
-       case bytes.HasPrefix(text, []byte(prefix+"[x] ")):
-               text = append([]byte(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></span>`), text[3+len(prefix):]...)
-               if prefix != "" {
-                       text = bytes.Replace(text, []byte(prefix), []byte{}, 1)
-               }
-       }
-       r.Renderer.ListItem(out, text, flags)
-}
-
-// Note: this section is for purpose of increase performance and
-// reduce memory allocation at runtime since they are constant literals.
-var (
-       svgSuffix         = []byte(".svg")
-       svgSuffixWithMark = []byte(".svg?")
-)
-
-// 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 = markup.URLJoin(prefix, "wiki", "src")
-       }
-       prefix = strings.Replace(prefix, "/src/", "/raw/", 1)
-       if len(link) > 0 {
-               if markup.IsLink(link) {
-                       // External link with .svg suffix usually means CI status.
-                       // TODO: define a keyword to allow non-svg images render as external link.
-                       if bytes.HasSuffix(link, svgSuffix) || bytes.Contains(link, svgSuffixWithMark) {
-                               r.Renderer.Image(out, link, title, alt)
-                               return
-                       }
-               } else {
-                       lnk := string(link)
-                       lnk = markup.URLJoin(prefix, lnk)
-                       lnk = strings.Replace(lnk, " ", "+", -1)
-                       link = []byte(lnk)
-               }
-       }
-
-       out.WriteString(`<a href="`)
-       out.Write(link)
-       out.WriteString(`">`)
-       r.Renderer.Image(out, link, title, alt)
-       out.WriteString("</a>")
-}
-
-// RenderRaw renders Markdown to HTML without handling special links.
-func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte {
-       htmlFlags := 0
-       htmlFlags |= blackfriday.HTML_SKIP_STYLE
-       htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
-       renderer := &Renderer{
-               Renderer:       blackfriday.HtmlRenderer(htmlFlags, "", ""),
-               urlPrefix:      urlPrefix,
-               isWikiMarkdown: wikiMarkdown,
-       }
-
-       // set up the parser
-       extensions := 0
-       extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
-       extensions |= blackfriday.EXTENSION_TABLES
-       extensions |= blackfriday.EXTENSION_FENCED_CODE
-       extensions |= blackfriday.EXTENSION_STRIKETHROUGH
-       extensions |= blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
-
-       if setting.Markdown.EnableHardLineBreak {
-               extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK
-       }
-
-       body = blackfriday.Markdown(body, renderer, extensions)
-       return body
-}
-
-var (
-       // MarkupName describes markup's name
-       MarkupName = "markdown"
-)
-
-func init() {
-       markup.RegisterParser(Parser{})
-}
-
-// Parser implements markup.Parser
-type Parser struct {
-}
-
-// Name implements markup.Parser
-func (Parser) Name() string {
-       return MarkupName
-}
-
-// Extensions implements markup.Parser
-func (Parser) Extensions() []string {
-       return setting.Markdown.FileExtensions
-}
-
-// Render implements markup.Parser
-func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
-       return RenderRaw(rawBytes, urlPrefix, isWiki)
-}
-
-// Render renders Markdown to HTML with all specific handling stuff.
-func Render(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
-       return markup.Render("a.md", rawBytes, urlPrefix, metas)
-}
-
-// RenderString renders Markdown to HTML with special links and returns string type.
-func RenderString(raw, urlPrefix string, metas map[string]string) string {
-       return markup.RenderString("a.md", raw, urlPrefix, metas)
-}
-
-// RenderWiki renders markdown wiki page to HTML and return HTML string
-func RenderWiki(rawBytes []byte, urlPrefix string, metas map[string]string) string {
-       return markup.RenderWiki("a.md", rawBytes, urlPrefix, metas)
-}
-
-// IsMarkdownFile reports whether name looks like a Markdown file
-// based on its extension.
-func IsMarkdownFile(name string) bool {
-       return markup.IsMarkupFile(name, MarkupName)
-}
diff --git a/modules/markdown/markdown_test.go b/modules/markdown/markdown_test.go
deleted file mode 100644 (file)
index 1b57e4f..0000000
+++ /dev/null
@@ -1,343 +0,0 @@
-// 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 markdown_test
-
-import (
-       "fmt"
-       "strconv"
-       "strings"
-       "testing"
-
-       . "code.gitea.io/gitea/modules/markdown"
-       "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 + "/"
-
-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("<a href=\"%s\">%s</a>", 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
-
-       test := func(input, expected, expectedWiki string) {
-               buffer := RenderString(input, setting.AppSubURL, nil)
-               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
-               bufferWiki := RenderWiki([]byte(input), setting.AppSubURL, nil)
-               assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(bufferWiki))
-       }
-
-       googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>`
-       test("<https://google.com/>", googleRendered, googleRendered)
-
-       lnk := markup.URLJoin(AppSubURL, "WikiPage")
-       lnkWiki := markup.URLJoin(AppSubURL, "wiki", "WikiPage")
-       test("[WikiPage](WikiPage)",
-               `<p><a href="`+lnk+`" rel="nofollow">WikiPage</a></p>`,
-               `<p><a href="`+lnkWiki+`" rel="nofollow">WikiPage</a></p>`)
-}
-
-func TestRender_ShortLinks(t *testing.T) {
-       setting.AppURL = AppURL
-       setting.AppSubURL = AppSubURL
-       tree := markup.URLJoin(AppSubURL, "src", "master")
-
-       test := func(input, expected, expectedWiki string) {
-               buffer := RenderString(input, tree, nil)
-               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
-               buffer = RenderWiki([]byte(input), setting.AppSubURL, nil)
-               assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
-       }
-
-       rawtree := markup.URLJoin(AppSubURL, "raw", "master")
-       url := markup.URLJoin(tree, "Link")
-       otherUrl := markup.URLJoin(tree, "OtherLink")
-       imgurl := markup.URLJoin(rawtree, "Link.jpg")
-       urlWiki := markup.URLJoin(AppSubURL, "wiki", "Link")
-       otherUrlWiki := markup.URLJoin(AppSubURL, "wiki", "OtherLink")
-       imgurlWiki := markup.URLJoin(AppSubURL, "wiki", "raw", "Link.jpg")
-       favicon := "http://google.com/favicon.ico"
-
-       test(
-               "[[Link]]",
-               `<p><a href="`+url+`" rel="nofollow">Link</a></p>`,
-               `<p><a href="`+urlWiki+`" rel="nofollow">Link</a></p>`)
-       test(
-               "[[Link.jpg]]",
-               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="Link.jpg" title="Link.jpg"/></a></p>`,
-               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="Link.jpg" title="Link.jpg"/></a></p>`)
-       test(
-               "[["+favicon+"]]",
-               `<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico"/></a></p>`,
-               `<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico"/></a></p>`)
-       test(
-               "[[Name|Link]]",
-               `<p><a href="`+url+`" rel="nofollow">Name</a></p>`,
-               `<p><a href="`+urlWiki+`" rel="nofollow">Name</a></p>`)
-       test(
-               "[[Name|Link.jpg]]",
-               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="Name" title="Name"/></a></p>`,
-               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="Name" title="Name"/></a></p>`)
-       test(
-               "[[Name|Link.jpg|alt=AltName]]",
-               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="AltName" title="AltName"/></a></p>`,
-               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="AltName" title="AltName"/></a></p>`)
-       test(
-               "[[Name|Link.jpg|title=Title]]",
-               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="Title" title="Title"/></a></p>`,
-               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="Title" title="Title"/></a></p>`)
-       test(
-               "[[Name|Link.jpg|alt=AltName|title=Title]]",
-               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="AltName" title="Title"/></a></p>`,
-               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="AltName" title="Title"/></a></p>`)
-       test(
-               "[[Name|Link.jpg|alt=\"AltName\"|title='Title']]",
-               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="AltName" title="Title"/></a></p>`,
-               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="AltName" title="Title"/></a></p>`)
-       test(
-               "[[Link]] [[OtherLink]]",
-               `<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherUrl+`" rel="nofollow">OtherLink</a></p>`,
-               `<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherUrlWiki+`" rel="nofollow">OtherLink</a></p>`)
-}
-
-func TestMisc_IsMarkdownFile(t *testing.T) {
-       setting.Markdown.FileExtensions = []string{".md", ".markdown", ".mdown", ".mkd"}
-       trueTestCases := []string{
-               "test.md",
-               "wow.MARKDOWN",
-               "LOL.mDoWn",
-       }
-       falseTestCases := []string{
-               "test",
-               "abcdefg",
-               "abcdefghijklmnopqrstuvwxyz",
-               "test.md.test",
-       }
-
-       for _, testCase := range trueTestCases {
-               assert.True(t, IsMarkdownFile(testCase))
-       }
-       for _, testCase := range falseTestCases {
-               assert.False(t, IsMarkdownFile(testCase))
-       }
-}
-
-func TestRender_Images(t *testing.T) {
-       setting.AppURL = AppURL
-       setting.AppSubURL = AppSubURL
-
-       test := func(input, expected string) {
-               buffer := RenderString(input, setting.AppSubURL, nil)
-               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
-       }
-
-       url := "../../.images/src/02/train.jpg"
-       title := "Train"
-       result := markup.URLJoin(AppSubURL, url)
-
-       test(
-               "!["+title+"]("+url+")",
-               `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"></a></p>`)
-
-       test(
-               "[["+title+"|"+url+"]]",
-               `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" alt="`+title+`" title="`+title+`"/></a></p>`)
-}
-
-func TestRegExp_ShortLinkPattern(t *testing.T) {
-       trueTestCases := []string{
-               "[[stuff]]",
-               "[[]]",
-               "[[stuff|title=Difficult name with spaces*!]]",
-       }
-       falseTestCases := []string{
-               "test",
-               "abcdefg",
-               "[[]",
-               "[[",
-               "[]",
-               "]]",
-               "abcdefghijklmnopqrstuvwxyz",
-       }
-
-       for _, testCase := range trueTestCases {
-               assert.True(t, markup.ShortLinkPattern.MatchString(testCase))
-       }
-       for _, testCase := range falseTestCases {
-               assert.False(t, markup.ShortLinkPattern.MatchString(testCase))
-       }
-}
-
-func testAnswers(baseURLContent, baseURLImages string) []string {
-       return []string{
-               `<p>Wiki! Enjoy :)</p>
-
-<ul>
-<li><a href="` + baseURLContent + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
-<li><a href="` + baseURLContent + `/Tips" rel="nofollow">Tips</a></li>
-</ul>
-
-<p>Ideas and codes</p>
-
-<ul>
-<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" rel="nofollow">#786</a></li>
-<li>Node graph editors https://github.com/ocornut/imgui/issues/306</li>
-<li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
-<li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li>
-</ul>
-`,
-               `<h2>What is Wine Staging?</h2>
-
-<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
-
-<h2>Quick Links</h2>
-
-<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
-
-<table>
-<thead>
-<tr>
-<th><a href="` + baseURLImages + `/images/icon-install.png" rel="nofollow"><img src="` + baseURLImages + `/images/icon-install.png" alt="images/icon-install.png" title="icon-install.png"/></a></th>
-<th><a href="` + baseURLContent + `/Installation" rel="nofollow">Installation</a></th>
-</tr>
-</thead>
-
-<tbody>
-<tr>
-<td><a href="` + baseURLImages + `/images/icon-usage.png" rel="nofollow"><img src="` + baseURLImages + `/images/icon-usage.png" alt="images/icon-usage.png" title="icon-usage.png"/></a></td>
-<td><a href="` + baseURLContent + `/Usage" rel="nofollow">Usage</a></td>
-</tr>
-</tbody>
-</table>
-`,
-               `<p><a href="http://www.excelsiorjet.com/" rel="nofollow">Excelsior JET</a> allows you to create native executables for Windows, Linux and Mac OS X.</p>
-
-<ol>
-<li><a href="https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop" rel="nofollow">Package your libGDX application</a>
-<a href="` + baseURLImages + `/images/1.png" rel="nofollow"><img src="` + baseURLImages + `/images/1.png" alt="images/1.png" title="1.png"/></a></li>
-<li>Perform a test run by hitting the Run! button.
-<a href="` + baseURLImages + `/images/2.png" rel="nofollow"><img src="` + baseURLImages + `/images/2.png" alt="images/2.png" title="2.png"/></a></li>
-</ol>
-`,
-       }
-}
-
-// Test cases without ambiguous links
-var sameCases = []string{
-       // dear imgui wiki markdown extract: special wiki syntax
-       `Wiki! Enjoy :)
-- [[Links, Language bindings, Engine bindings|Links]]
-- [[Tips]]
-
-Ideas and codes
-
-- Bezier widget (by @r-lyeh) ` + AppURL + `ocornut/imgui/issues/786
-- Node graph editors https://github.com/ocornut/imgui/issues/306
-- [[Memory Editor|memory_editor_example]]
-- [[Plot var helper|plot_var_example]]`,
-       // wine-staging wiki home extract: tables, special wiki syntax, images
-       `## What is Wine Staging?
-**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
-
-## Quick Links
-Here are some links to the most important topics. You can find the full list of pages at the sidebar.
-
-| [[images/icon-install.png]]    | [[Installation]]                                         |
-|--------------------------------|----------------------------------------------------------|
-| [[images/icon-usage.png]]      | [[Usage]]                                                |
-`,
-       // libgdx wiki page: inline images with special syntax
-       `[Excelsior JET](http://www.excelsiorjet.com/) allows you to create native executables for Windows, Linux and Mac OS X.
-
-1. [Package your libGDX application](https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop)
-[[images/1.png]]
-2. Perform a test run by hitting the Run! button.
-[[images/2.png]]`,
-}
-
-func TestTotal_RenderWiki(t *testing.T) {
-       answers := testAnswers(markup.URLJoin(AppSubURL, "wiki/"), markup.URLJoin(AppSubURL, "wiki", "raw/"))
-
-       for i := 0; i < len(sameCases); i++ {
-               line := RenderWiki([]byte(sameCases[i]), AppSubURL, nil)
-               assert.Equal(t, answers[i], line)
-       }
-
-       testCases := []string{
-               // Guard wiki sidebar: special syntax
-               `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
-               // rendered
-               `<p><a href="` + AppSubURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
-`,
-               // special syntax
-               `[[Name|Link]]`,
-               // rendered
-               `<p><a href="` + AppSubURL + `wiki/Link" rel="nofollow">Name</a></p>
-`,
-       }
-
-       for i := 0; i < len(testCases); i += 2 {
-               line := RenderWiki([]byte(testCases[i]), AppSubURL, nil)
-               assert.Equal(t, testCases[i+1], line)
-       }
-}
-
-func TestTotal_RenderString(t *testing.T) {
-       answers := testAnswers(markup.URLJoin(AppSubURL, "src", "master/"), markup.URLJoin(AppSubURL, "raw", "master/"))
-
-       for i := 0; i < len(sameCases); i++ {
-               line := RenderString(sameCases[i], markup.URLJoin(AppSubURL, "src", "master/"), nil)
-               assert.Equal(t, answers[i], line)
-       }
-
-       testCases := []string{}
-
-       for i := 0; i < len(testCases); i += 2 {
-               line := RenderString(testCases[i], AppSubURL, nil)
-               assert.Equal(t, testCases[i+1], line)
-       }
-}
index 407115526d02e88121524fa635fa8b47ff82e631..ab2ca5ef47ec7566636432942d42c7cd16683264 100644 (file)
@@ -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/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
new file mode 100644 (file)
index 0000000..f0ed0e0
--- /dev/null
@@ -0,0 +1,200 @@
+// Copyright 2014 The Gogs 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 markdown
+
+import (
+       "bytes"
+       "strings"
+
+       "code.gitea.io/gitea/modules/markup"
+       "code.gitea.io/gitea/modules/setting"
+
+       "github.com/russross/blackfriday"
+)
+
+// Renderer is a extended version of underlying render object.
+type Renderer struct {
+       blackfriday.Renderer
+       URLPrefix string
+       IsWiki    bool
+}
+
+// Link defines how formal links should be processed to produce corresponding HTML elements.
+func (r *Renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
+       if len(link) > 0 && !markup.IsLink(link) {
+               if link[0] != '#' {
+                       lnk := string(link)
+                       if r.IsWiki {
+                               lnk = markup.URLJoin("wiki", lnk)
+                       }
+                       mLink := markup.URLJoin(r.URLPrefix, lnk)
+                       link = []byte(mLink)
+               }
+       }
+
+       r.Renderer.Link(out, link, title, content)
+}
+
+// List renders markdown bullet or digit lists to HTML
+func (r *Renderer) List(out *bytes.Buffer, text func() bool, flags int) {
+       marker := out.Len()
+       if out.Len() > 0 {
+               out.WriteByte('\n')
+       }
+
+       if flags&blackfriday.LIST_TYPE_DEFINITION != 0 {
+               out.WriteString("<dl>")
+       } else if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
+               out.WriteString("<ol class='ui list'>")
+       } else {
+               out.WriteString("<ul class='ui list'>")
+       }
+       if !text() {
+               out.Truncate(marker)
+               return
+       }
+       if flags&blackfriday.LIST_TYPE_DEFINITION != 0 {
+               out.WriteString("</dl>\n")
+       } else if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
+               out.WriteString("</ol>\n")
+       } else {
+               out.WriteString("</ul>\n")
+       }
+}
+
+// ListItem defines how list items should be processed to produce corresponding HTML elements.
+func (r *Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
+       // Detect procedures to draw checkboxes.
+       prefix := ""
+       if bytes.HasPrefix(text, []byte("<p>")) {
+               prefix = "<p>"
+       }
+       switch {
+       case bytes.HasPrefix(text, []byte(prefix+"[ ] ")):
+               text = append([]byte(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></span>`), text[3+len(prefix):]...)
+               if prefix != "" {
+                       text = bytes.Replace(text, []byte(prefix), []byte{}, 1)
+               }
+       case bytes.HasPrefix(text, []byte(prefix+"[x] ")):
+               text = append([]byte(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></span>`), text[3+len(prefix):]...)
+               if prefix != "" {
+                       text = bytes.Replace(text, []byte(prefix), []byte{}, 1)
+               }
+       }
+       r.Renderer.ListItem(out, text, flags)
+}
+
+// Note: this section is for purpose of increase performance and
+// reduce memory allocation at runtime since they are constant literals.
+var (
+       svgSuffix         = []byte(".svg")
+       svgSuffixWithMark = []byte(".svg?")
+)
+
+// 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.IsWiki {
+               prefix = markup.URLJoin(prefix, "wiki", "src")
+       }
+       prefix = strings.Replace(prefix, "/src/", "/raw/", 1)
+       if len(link) > 0 {
+               if markup.IsLink(link) {
+                       // External link with .svg suffix usually means CI status.
+                       // TODO: define a keyword to allow non-svg images render as external link.
+                       if bytes.HasSuffix(link, svgSuffix) || bytes.Contains(link, svgSuffixWithMark) {
+                               r.Renderer.Image(out, link, title, alt)
+                               return
+                       }
+               } else {
+                       lnk := string(link)
+                       lnk = markup.URLJoin(prefix, lnk)
+                       lnk = strings.Replace(lnk, " ", "+", -1)
+                       link = []byte(lnk)
+               }
+       }
+
+       out.WriteString(`<a href="`)
+       out.Write(link)
+       out.WriteString(`">`)
+       r.Renderer.Image(out, link, title, alt)
+       out.WriteString("</a>")
+}
+
+// RenderRaw renders Markdown to HTML without handling special links.
+func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte {
+       htmlFlags := 0
+       htmlFlags |= blackfriday.HTML_SKIP_STYLE
+       htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
+       renderer := &Renderer{
+               Renderer:  blackfriday.HtmlRenderer(htmlFlags, "", ""),
+               URLPrefix: urlPrefix,
+               IsWiki:    wikiMarkdown,
+       }
+
+       // set up the parser
+       extensions := 0
+       extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
+       extensions |= blackfriday.EXTENSION_TABLES
+       extensions |= blackfriday.EXTENSION_FENCED_CODE
+       extensions |= blackfriday.EXTENSION_STRIKETHROUGH
+       extensions |= blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
+
+       if setting.Markdown.EnableHardLineBreak {
+               extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK
+       }
+
+       body = blackfriday.Markdown(body, renderer, extensions)
+       return body
+}
+
+var (
+       // MarkupName describes markup's name
+       MarkupName = "markdown"
+)
+
+func init() {
+       markup.RegisterParser(Parser{})
+}
+
+// Parser implements markup.Parser
+type Parser struct {
+}
+
+// Name implements markup.Parser
+func (Parser) Name() string {
+       return MarkupName
+}
+
+// Extensions implements markup.Parser
+func (Parser) Extensions() []string {
+       return setting.Markdown.FileExtensions
+}
+
+// Render implements markup.Parser
+func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
+       return RenderRaw(rawBytes, urlPrefix, isWiki)
+}
+
+// Render renders Markdown to HTML with all specific handling stuff.
+func Render(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
+       return markup.Render("a.md", rawBytes, urlPrefix, metas)
+}
+
+// RenderString renders Markdown to HTML with special links and returns string type.
+func RenderString(raw, urlPrefix string, metas map[string]string) string {
+       return markup.RenderString("a.md", raw, urlPrefix, metas)
+}
+
+// RenderWiki renders markdown wiki page to HTML and return HTML string
+func RenderWiki(rawBytes []byte, urlPrefix string, metas map[string]string) string {
+       return markup.RenderWiki("a.md", rawBytes, urlPrefix, metas)
+}
+
+// IsMarkdownFile reports whether name looks like a Markdown file
+// based on its extension.
+func IsMarkdownFile(name string) bool {
+       return markup.IsMarkupFile(name, MarkupName)
+}
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
new file mode 100644 (file)
index 0000000..9ca3de0
--- /dev/null
@@ -0,0 +1,302 @@
+// 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 markdown_test
+
+import (
+       "strings"
+       "testing"
+
+       "code.gitea.io/gitea/modules/markup"
+       . "code.gitea.io/gitea/modules/markup/markdown"
+       "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, expectedWiki string) {
+               buffer := RenderString(input, setting.AppSubURL, nil)
+               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
+               bufferWiki := RenderWiki([]byte(input), setting.AppSubURL, nil)
+               assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(bufferWiki))
+       }
+
+       googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>`
+       test("<https://google.com/>", googleRendered, googleRendered)
+
+       lnk := markup.URLJoin(AppSubURL, "WikiPage")
+       lnkWiki := markup.URLJoin(AppSubURL, "wiki", "WikiPage")
+       test("[WikiPage](WikiPage)",
+               `<p><a href="`+lnk+`" rel="nofollow">WikiPage</a></p>`,
+               `<p><a href="`+lnkWiki+`" rel="nofollow">WikiPage</a></p>`)
+}
+
+func TestRender_ShortLinks(t *testing.T) {
+       setting.AppURL = AppURL
+       setting.AppSubURL = AppSubURL
+       tree := markup.URLJoin(AppSubURL, "src", "master")
+
+       test := func(input, expected, expectedWiki string) {
+               buffer := RenderString(input, tree, nil)
+               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
+               buffer = RenderWiki([]byte(input), setting.AppSubURL, nil)
+               assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
+       }
+
+       rawtree := markup.URLJoin(AppSubURL, "raw", "master")
+       url := markup.URLJoin(tree, "Link")
+       otherUrl := markup.URLJoin(tree, "OtherLink")
+       imgurl := markup.URLJoin(rawtree, "Link.jpg")
+       urlWiki := markup.URLJoin(AppSubURL, "wiki", "Link")
+       otherUrlWiki := markup.URLJoin(AppSubURL, "wiki", "OtherLink")
+       imgurlWiki := markup.URLJoin(AppSubURL, "wiki", "raw", "Link.jpg")
+       favicon := "http://google.com/favicon.ico"
+
+       test(
+               "[[Link]]",
+               `<p><a href="`+url+`" rel="nofollow">Link</a></p>`,
+               `<p><a href="`+urlWiki+`" rel="nofollow">Link</a></p>`)
+       test(
+               "[[Link.jpg]]",
+               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="Link.jpg" title="Link.jpg"/></a></p>`,
+               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="Link.jpg" title="Link.jpg"/></a></p>`)
+       test(
+               "[["+favicon+"]]",
+               `<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico"/></a></p>`,
+               `<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico"/></a></p>`)
+       test(
+               "[[Name|Link]]",
+               `<p><a href="`+url+`" rel="nofollow">Name</a></p>`,
+               `<p><a href="`+urlWiki+`" rel="nofollow">Name</a></p>`)
+       test(
+               "[[Name|Link.jpg]]",
+               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="Name" title="Name"/></a></p>`,
+               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="Name" title="Name"/></a></p>`)
+       test(
+               "[[Name|Link.jpg|alt=AltName]]",
+               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="AltName" title="AltName"/></a></p>`,
+               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="AltName" title="AltName"/></a></p>`)
+       test(
+               "[[Name|Link.jpg|title=Title]]",
+               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="Title" title="Title"/></a></p>`,
+               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="Title" title="Title"/></a></p>`)
+       test(
+               "[[Name|Link.jpg|alt=AltName|title=Title]]",
+               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="AltName" title="Title"/></a></p>`,
+               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="AltName" title="Title"/></a></p>`)
+       test(
+               "[[Name|Link.jpg|alt=\"AltName\"|title='Title']]",
+               `<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" alt="AltName" title="Title"/></a></p>`,
+               `<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" alt="AltName" title="Title"/></a></p>`)
+       test(
+               "[[Link]] [[OtherLink]]",
+               `<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherUrl+`" rel="nofollow">OtherLink</a></p>`,
+               `<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherUrlWiki+`" rel="nofollow">OtherLink</a></p>`)
+}
+
+func TestMisc_IsMarkdownFile(t *testing.T) {
+       setting.Markdown.FileExtensions = []string{".md", ".markdown", ".mdown", ".mkd"}
+       trueTestCases := []string{
+               "test.md",
+               "wow.MARKDOWN",
+               "LOL.mDoWn",
+       }
+       falseTestCases := []string{
+               "test",
+               "abcdefg",
+               "abcdefghijklmnopqrstuvwxyz",
+               "test.md.test",
+       }
+
+       for _, testCase := range trueTestCases {
+               assert.True(t, IsMarkdownFile(testCase))
+       }
+       for _, testCase := range falseTestCases {
+               assert.False(t, IsMarkdownFile(testCase))
+       }
+}
+
+func TestRender_Images(t *testing.T) {
+       setting.AppURL = AppURL
+       setting.AppSubURL = AppSubURL
+
+       test := func(input, expected string) {
+               buffer := RenderString(input, setting.AppSubURL, nil)
+               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
+       }
+
+       url := "../../.images/src/02/train.jpg"
+       title := "Train"
+       result := markup.URLJoin(AppSubURL, url)
+
+       test(
+               "!["+title+"]("+url+")",
+               `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"></a></p>`)
+
+       test(
+               "[["+title+"|"+url+"]]",
+               `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" alt="`+title+`" title="`+title+`"/></a></p>`)
+}
+
+func TestRegExp_ShortLinkPattern(t *testing.T) {
+       trueTestCases := []string{
+               "[[stuff]]",
+               "[[]]",
+               "[[stuff|title=Difficult name with spaces*!]]",
+       }
+       falseTestCases := []string{
+               "test",
+               "abcdefg",
+               "[[]",
+               "[[",
+               "[]",
+               "]]",
+               "abcdefghijklmnopqrstuvwxyz",
+       }
+
+       for _, testCase := range trueTestCases {
+               assert.True(t, markup.ShortLinkPattern.MatchString(testCase))
+       }
+       for _, testCase := range falseTestCases {
+               assert.False(t, markup.ShortLinkPattern.MatchString(testCase))
+       }
+}
+
+func testAnswers(baseURLContent, baseURLImages string) []string {
+       return []string{
+               `<p>Wiki! Enjoy :)</p>
+
+<ul>
+<li><a href="` + baseURLContent + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
+<li><a href="` + baseURLContent + `/Tips" rel="nofollow">Tips</a></li>
+</ul>
+
+<p>Ideas and codes</p>
+
+<ul>
+<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" rel="nofollow">#786</a></li>
+<li>Node graph editors https://github.com/ocornut/imgui/issues/306</li>
+<li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
+<li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li>
+</ul>
+`,
+               `<h2>What is Wine Staging?</h2>
+
+<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
+
+<h2>Quick Links</h2>
+
+<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
+
+<table>
+<thead>
+<tr>
+<th><a href="` + baseURLImages + `/images/icon-install.png" rel="nofollow"><img src="` + baseURLImages + `/images/icon-install.png" alt="images/icon-install.png" title="icon-install.png"/></a></th>
+<th><a href="` + baseURLContent + `/Installation" rel="nofollow">Installation</a></th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td><a href="` + baseURLImages + `/images/icon-usage.png" rel="nofollow"><img src="` + baseURLImages + `/images/icon-usage.png" alt="images/icon-usage.png" title="icon-usage.png"/></a></td>
+<td><a href="` + baseURLContent + `/Usage" rel="nofollow">Usage</a></td>
+</tr>
+</tbody>
+</table>
+`,
+               `<p><a href="http://www.excelsiorjet.com/" rel="nofollow">Excelsior JET</a> allows you to create native executables for Windows, Linux and Mac OS X.</p>
+
+<ol>
+<li><a href="https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop" rel="nofollow">Package your libGDX application</a>
+<a href="` + baseURLImages + `/images/1.png" rel="nofollow"><img src="` + baseURLImages + `/images/1.png" alt="images/1.png" title="1.png"/></a></li>
+<li>Perform a test run by hitting the Run! button.
+<a href="` + baseURLImages + `/images/2.png" rel="nofollow"><img src="` + baseURLImages + `/images/2.png" alt="images/2.png" title="2.png"/></a></li>
+</ol>
+`,
+       }
+}
+
+// Test cases without ambiguous links
+var sameCases = []string{
+       // dear imgui wiki markdown extract: special wiki syntax
+       `Wiki! Enjoy :)
+- [[Links, Language bindings, Engine bindings|Links]]
+- [[Tips]]
+
+Ideas and codes
+
+- Bezier widget (by @r-lyeh) ` + AppURL + `ocornut/imgui/issues/786
+- Node graph editors https://github.com/ocornut/imgui/issues/306
+- [[Memory Editor|memory_editor_example]]
+- [[Plot var helper|plot_var_example]]`,
+       // wine-staging wiki home extract: tables, special wiki syntax, images
+       `## What is Wine Staging?
+**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
+
+## Quick Links
+Here are some links to the most important topics. You can find the full list of pages at the sidebar.
+
+| [[images/icon-install.png]]    | [[Installation]]                                         |
+|--------------------------------|----------------------------------------------------------|
+| [[images/icon-usage.png]]      | [[Usage]]                                                |
+`,
+       // libgdx wiki page: inline images with special syntax
+       `[Excelsior JET](http://www.excelsiorjet.com/) allows you to create native executables for Windows, Linux and Mac OS X.
+
+1. [Package your libGDX application](https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop)
+[[images/1.png]]
+2. Perform a test run by hitting the Run! button.
+[[images/2.png]]`,
+}
+
+func TestTotal_RenderWiki(t *testing.T) {
+       answers := testAnswers(markup.URLJoin(AppSubURL, "wiki/"), markup.URLJoin(AppSubURL, "wiki", "raw/"))
+
+       for i := 0; i < len(sameCases); i++ {
+               line := RenderWiki([]byte(sameCases[i]), AppSubURL, nil)
+               assert.Equal(t, answers[i], line)
+       }
+
+       testCases := []string{
+               // Guard wiki sidebar: special syntax
+               `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
+               // rendered
+               `<p><a href="` + AppSubURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
+`,
+               // special syntax
+               `[[Name|Link]]`,
+               // rendered
+               `<p><a href="` + AppSubURL + `wiki/Link" rel="nofollow">Name</a></p>
+`,
+       }
+
+       for i := 0; i < len(testCases); i += 2 {
+               line := RenderWiki([]byte(testCases[i]), AppSubURL, nil)
+               assert.Equal(t, testCases[i+1], line)
+       }
+}
+
+func TestTotal_RenderString(t *testing.T) {
+       answers := testAnswers(markup.URLJoin(AppSubURL, "src", "master/"), markup.URLJoin(AppSubURL, "raw", "master/"))
+
+       for i := 0; i < len(sameCases); i++ {
+               line := RenderString(sameCases[i], markup.URLJoin(AppSubURL, "src", "master/"), nil)
+               assert.Equal(t, answers[i], line)
+       }
+
+       testCases := []string{}
+
+       for i := 0; i < len(testCases); i += 2 {
+               line := RenderString(testCases[i], AppSubURL, nil)
+               assert.Equal(t, testCases[i+1], line)
+       }
+}
index 8d061ae39e9a4d997746c35c7b5c39589a649cd3..b0ebfae57d725f18818c04a04bc1b6ba4c24c92b 100644 (file)
@@ -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 (file)
index 0000000..f9223a1
--- /dev/null
@@ -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 (file)
index 0000000..a68ab5d
--- /dev/null
@@ -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 := `<p><a href="https://google.com/" title="https://google.com/">https://google.com/</a></p>`
+       test("[[https://google.com/]]", googleRendered)
+
+       lnk := markup.URLJoin(AppSubURL, "WikiPage")
+       test("[[WikiPage][WikiPage]]",
+               `<p><a href="`+lnk+`" title="WikiPage">WikiPage</a></p>`)
+}
+
+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+"]]",
+               `<p><a href="`+result+`"><img src="`+result+`" alt="`+title+`" title="`+title+`" /></a></p>`)
+}
index a2e65ecb0ad99d6ee05f92a8cf00402ac8b56675..8e3c66841f8f828b4fb2f75e80befab5efedbe80 100644 (file)
@@ -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"
 )
 
index 4c4f9037bf2413f78e7ca2508aa29f17b62e7b91..091268116bf4f4f05d0f37bc1cc7e0606a360df9 100644 (file)
@@ -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"
index fe68f1b6f139d2a862a033a80c82103295572c06..da99dd7713809f162f4c7cb243a9834851ff22fa 100644 (file)
@@ -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"
index d794a574059eb429287f79d7141a369c4807b1ba..bfba7acac82aa94a0b3d9d6fb1bc5a5202764fc6 100644 (file)
@@ -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(`<br>`), -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()))
index 2a73fdc41e9fdb8aac2dca9acf84047c634c8bb4..019c3d5d168f4dde4099118762de7e097bd812e4 100644 (file)
@@ -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 (
index 36fccb00b345588abf577f3ff1550571382b302c..898b9b555763193cc113bf23211e3b2490993b5f 100644 (file)
@@ -36,8 +36,8 @@
                {{end}}
        </h4>
        <div class="ui attached table segment">
-               <div class="file-view {{if .IsMarkdown}}markdown{{else if .IsTextFile}}code-view{{end}} has-emoji">
-                       {{if .IsMarkdown}}
+               <div class="file-view {{if .IsMarkup}}markdown{{else if .IsTextFile}}code-view{{end}} has-emoji">
+                       {{if .IsMarkup}}
                                {{if .FileContent}}{{.FileContent | Str2html}}{{end}}
                        {{else if not .IsTextFile}}
                                <div class="view-raw ui center">
diff --git a/vendor/github.com/chaseadamsio/goorgeous/LICENSE b/vendor/github.com/chaseadamsio/goorgeous/LICENSE
new file mode 100644 (file)
index 0000000..d7a37c6
--- /dev/null
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Chase Adams <realchaseadams@gmail.com>
+
+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 (file)
index 0000000..37e0f2e
--- /dev/null
@@ -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
+  <h1>Some Headline</h1>/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 (file)
index 0000000..f1b2671
--- /dev/null
@@ -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("</pre>\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("</pre>\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("<center>\n")
+                                               p.inline(&tmpBuf, tmpBlock.Bytes())
+                                               output.Write(tmpBuf.Bytes())
+                                               output.WriteString("</center>\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("<p>\n"))
+                                       p.inline(&tmpBuf, data)
+                                       tmpBuf.WriteByte('\n')
+                                       tmpBuf.Write([]byte("</p>\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("<li")
+                       if len(matches[2]) > 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("</li>\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("<pre class=\"example\">\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("</pre>")
+                                               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("</pre>\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("<span class=\"todo " + status + "\">" + status + "</span>")
+                       out.WriteByte(' ')
+               }
+
+               if priority != "" {
+                       out.WriteString("<span class=\"priority " + priority + "\">[" + priority + "]</span>")
+                       out.WriteByte(' ')
+               }
+
+               p.inline(out, headline)
+
+               if tagsFound > 0 {
+                       for _, tag := range tags {
+                               out.WriteByte(' ')
+                               out.WriteString("<span class=\"tags " + tag + "\">" + tag + "</span>")
+                               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("<thead>")
+                       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("</thead>\n")
+               } else if hasTableHeaders && idx == 1 {
+                       continue
+               } else {
+                       if !tbodySet {
+                               table.WriteString("<tbody>")
+                               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("</tbody>\n")
+                               tbodySet = false
+                       }
+               }
+       }
+
+       output.WriteString("\n<table>\n")
+       output.Write(table.Bytes())
+       output.WriteString("</table>\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.Write(data[2:])
+       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("<span style=\"text-decoration: underline;\">")
+               out.Write(text)
+               out.WriteString("</span>")
+       }
+
+       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 (file)
index 0000000..be7567e
Binary files /dev/null and b/vendor/github.com/chaseadamsio/goorgeous/gopher.gif differ
diff --git a/vendor/github.com/chaseadamsio/goorgeous/gopher_small.gif b/vendor/github.com/chaseadamsio/goorgeous/gopher_small.gif
new file mode 100644 (file)
index 0000000..1cd31fd
Binary files /dev/null and b/vendor/github.com/chaseadamsio/goorgeous/gopher_small.gif differ
diff --git a/vendor/github.com/chaseadamsio/goorgeous/header.go b/vendor/github.com/chaseadamsio/goorgeous/header.go
new file mode 100644 (file)
index 0000000..66e8b99
--- /dev/null
@@ -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
+
+}
index d9cb18cd1ac6a2499eaff882f5a200a68955f4e6..101c72c2172b7ff790e9740b6f52ca9c43ee6462 100644 (file)
                        "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",