gitlab-markup 1.5.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.gitlab-ci.yml +27 -0
- data/.kick +26 -0
- data/.travis.yml +21 -0
- data/CONTRIBUTING.md +49 -0
- data/Gemfile +13 -0
- data/HISTORY.md +128 -0
- data/LICENSE +20 -0
- data/README.md +58 -0
- data/Rakefile +17 -0
- data/bin/github-markup +10 -0
- data/gitlab-markup.gemspec +25 -0
- data/lib/github-markup.rb +6 -0
- data/lib/github/commands/rest2html +200 -0
- data/lib/github/markup.rb +55 -0
- data/lib/github/markup/command_implementation.rb +71 -0
- data/lib/github/markup/gem_implementation.rb +30 -0
- data/lib/github/markup/implementation.rb +28 -0
- data/lib/github/markup/markdown.rb +60 -0
- data/lib/github/markup/rdoc.rb +26 -0
- data/lib/github/markups.rb +50 -0
- data/script/bootstrap +8 -0
- data/script/cibuild +20 -0
- data/test/fixtures/fail.sh +3 -0
- data/test/markup_test.rb +116 -0
- data/test/markups/README.asciidoc +23 -0
- data/test/markups/README.asciidoc.html +59 -0
- data/test/markups/README.creole +34 -0
- data/test/markups/README.creole.html +20 -0
- data/test/markups/README.litcoffee +59 -0
- data/test/markups/README.litcoffee.html +66 -0
- data/test/markups/README.markdown +2 -0
- data/test/markups/README.markdown.html +4 -0
- data/test/markups/README.mediawiki +30 -0
- data/test/markups/README.mediawiki.html +60 -0
- data/test/markups/README.noformat +2 -0
- data/test/markups/README.noformat.html +2 -0
- data/test/markups/README.org +131 -0
- data/test/markups/README.org.html +139 -0
- data/test/markups/README.pod +88 -0
- data/test/markups/README.pod.html +85 -0
- data/test/markups/README.rdoc +6 -0
- data/test/markups/README.rdoc.html +12 -0
- data/test/markups/README.rmd +3 -0
- data/test/markups/README.rmd.html +6 -0
- data/test/markups/README.rst +79 -0
- data/test/markups/README.rst.html +91 -0
- data/test/markups/README.rst.txt +21 -0
- data/test/markups/README.rst.txt.html +37 -0
- data/test/markups/README.textile +2 -0
- data/test/markups/README.textile.html +4 -0
- data/test/markups/README.toc.rst +30 -0
- data/test/markups/README.toc.rst.html +32 -0
- data/test/markups/README.txt +2 -0
- data/test/markups/README.txt.html +2 -0
- metadata +216 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
require "github/markup/command_implementation"
|
2
|
+
require "github/markup/gem_implementation"
|
3
|
+
|
4
|
+
module GitHub
|
5
|
+
module Markup
|
6
|
+
extend self
|
7
|
+
@@markups = []
|
8
|
+
|
9
|
+
def markups
|
10
|
+
@@markups
|
11
|
+
end
|
12
|
+
|
13
|
+
def preload!
|
14
|
+
markups.each do |markup|
|
15
|
+
markup.load
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def render(filename, content = nil)
|
20
|
+
content ||= File.read(filename)
|
21
|
+
|
22
|
+
if impl = renderer(filename)
|
23
|
+
impl.render(content)
|
24
|
+
else
|
25
|
+
content
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def markup(file, pattern, opts = {}, &block)
|
30
|
+
markups << GemImplementation.new(pattern, file, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def command(command, regexp, name, &block)
|
34
|
+
if File.exist?(file = File.dirname(__FILE__) + "/commands/#{command}")
|
35
|
+
command = file
|
36
|
+
end
|
37
|
+
|
38
|
+
markups << CommandImplementation.new(regexp, command, name, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def can_render?(filename)
|
42
|
+
!!renderer(filename)
|
43
|
+
end
|
44
|
+
|
45
|
+
def renderer(filename)
|
46
|
+
markups.find { |impl|
|
47
|
+
impl.match?(filename)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Define markups
|
52
|
+
markups_rb = File.dirname(__FILE__) + '/markups.rb'
|
53
|
+
instance_eval File.read(markups_rb), markups_rb
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
begin
|
2
|
+
require "posix-spawn"
|
3
|
+
rescue LoadError
|
4
|
+
require "open3"
|
5
|
+
end
|
6
|
+
|
7
|
+
require "github/markup/implementation"
|
8
|
+
|
9
|
+
module GitHub
|
10
|
+
module Markup
|
11
|
+
class CommandError < RuntimeError
|
12
|
+
end
|
13
|
+
|
14
|
+
class CommandImplementation < Implementation
|
15
|
+
attr_reader :command, :block, :name
|
16
|
+
|
17
|
+
def initialize(regexp, command, name, &block)
|
18
|
+
super regexp
|
19
|
+
@command = command.to_s
|
20
|
+
@block = block
|
21
|
+
@name = name
|
22
|
+
end
|
23
|
+
|
24
|
+
def render(content)
|
25
|
+
rendered = execute(command, content)
|
26
|
+
rendered = rendered.to_s.empty? ? content : rendered
|
27
|
+
call_block(rendered, content)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def call_block(rendered, content)
|
32
|
+
if block && block.arity == 2
|
33
|
+
block.call(rendered, content)
|
34
|
+
elsif block
|
35
|
+
block.call(rendered)
|
36
|
+
else
|
37
|
+
rendered
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if defined?(POSIX::Spawn)
|
42
|
+
def execute(command, target)
|
43
|
+
spawn = POSIX::Spawn::Child.new(*command, :input => target)
|
44
|
+
if spawn.status.success?
|
45
|
+
sanitize(spawn.out, target.encoding)
|
46
|
+
else
|
47
|
+
raise CommandError.new(spawn.err.strip)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
def execute(command, target)
|
52
|
+
output = Open3.popen3(*command) do |stdin, stdout, stderr, wait_thr|
|
53
|
+
stdin.puts target
|
54
|
+
stdin.close
|
55
|
+
if wait_thr.value.success?
|
56
|
+
stdout.readlines
|
57
|
+
else
|
58
|
+
raise CommandError.new(stderr.readlines.join('').strip)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
sanitize(output.join(''), target.encoding)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def sanitize(input, encoding)
|
66
|
+
input.gsub("\r", '').force_encoding(encoding)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "github/markup/implementation"
|
2
|
+
|
3
|
+
module GitHub
|
4
|
+
module Markup
|
5
|
+
class GemImplementation < Implementation
|
6
|
+
attr_reader :gem_name, :renderer
|
7
|
+
|
8
|
+
def initialize(regexp, gem_name, &renderer)
|
9
|
+
super regexp
|
10
|
+
@gem_name = gem_name.to_s
|
11
|
+
@renderer = renderer
|
12
|
+
end
|
13
|
+
|
14
|
+
def load
|
15
|
+
return if @loaded
|
16
|
+
require gem_name
|
17
|
+
@loaded = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def render(content)
|
21
|
+
load
|
22
|
+
renderer.call(content)
|
23
|
+
end
|
24
|
+
|
25
|
+
def name
|
26
|
+
gem_name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module GitHub
|
2
|
+
module Markup
|
3
|
+
class Implementation
|
4
|
+
attr_reader :regexp
|
5
|
+
|
6
|
+
def initialize(regexp)
|
7
|
+
@regexp = regexp
|
8
|
+
end
|
9
|
+
|
10
|
+
def load
|
11
|
+
# no-op by default
|
12
|
+
end
|
13
|
+
|
14
|
+
def render(content)
|
15
|
+
raise NotImplementedError, "subclasses of GitHub::Markup::Implementation must define #render"
|
16
|
+
end
|
17
|
+
|
18
|
+
def match?(filename)
|
19
|
+
file_ext_regexp =~ filename
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def file_ext_regexp
|
24
|
+
@file_ext_regexp ||= /\.(#{regexp})\z/
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "github/markup/implementation"
|
2
|
+
|
3
|
+
module GitHub
|
4
|
+
module Markup
|
5
|
+
class Markdown < Implementation
|
6
|
+
MARKDOWN_GEMS = {
|
7
|
+
"github/markdown" => proc { |content|
|
8
|
+
GitHub::Markdown.render(content)
|
9
|
+
},
|
10
|
+
"redcarpet" => proc { |content|
|
11
|
+
Redcarpet::Markdown.new(Redcarpet::Render::HTML).render(content)
|
12
|
+
},
|
13
|
+
"rdiscount" => proc { |content|
|
14
|
+
RDiscount.new(content).to_html
|
15
|
+
},
|
16
|
+
"maruku" => proc { |content|
|
17
|
+
Maruku.new(content).to_html
|
18
|
+
},
|
19
|
+
"kramdown" => proc { |content|
|
20
|
+
Kramdown::Document.new(content).to_html
|
21
|
+
},
|
22
|
+
"bluecloth" => proc { |content|
|
23
|
+
BlueCloth.new(content).to_html
|
24
|
+
},
|
25
|
+
}
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
super(/md|rmd|mkdn?|mdwn|mdown|markdown|litcoffee/i)
|
29
|
+
end
|
30
|
+
|
31
|
+
def load
|
32
|
+
return if @renderer
|
33
|
+
MARKDOWN_GEMS.each do |gem_name, renderer|
|
34
|
+
if try_require(gem_name)
|
35
|
+
@renderer = renderer
|
36
|
+
return
|
37
|
+
end
|
38
|
+
end
|
39
|
+
raise LoadError, "no suitable markdown gem found"
|
40
|
+
end
|
41
|
+
|
42
|
+
def render(content)
|
43
|
+
load
|
44
|
+
@renderer.call(content)
|
45
|
+
end
|
46
|
+
|
47
|
+
def name
|
48
|
+
"markdown"
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def try_require(file)
|
53
|
+
require file
|
54
|
+
true
|
55
|
+
rescue LoadError
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "github/markup/implementation"
|
2
|
+
require "rdoc"
|
3
|
+
require "rdoc/markup/to_html"
|
4
|
+
|
5
|
+
module GitHub
|
6
|
+
module Markup
|
7
|
+
class RDoc < Implementation
|
8
|
+
def initialize
|
9
|
+
super /rdoc/
|
10
|
+
end
|
11
|
+
|
12
|
+
def render(content)
|
13
|
+
if ::RDoc::VERSION.to_i >= 4
|
14
|
+
h = ::RDoc::Markup::ToHtml.new(::RDoc::Options.new)
|
15
|
+
else
|
16
|
+
h = ::RDoc::Markup::ToHtml.new
|
17
|
+
end
|
18
|
+
h.convert(content)
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
"rdoc"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "github/markup/markdown"
|
2
|
+
require "github/markup/rdoc"
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
markups << GitHub::Markup::Markdown.new
|
6
|
+
|
7
|
+
markup(:redcloth, /textile/) do |content|
|
8
|
+
RedCloth.new(content).to_html
|
9
|
+
end
|
10
|
+
|
11
|
+
markups << GitHub::Markup::RDoc.new
|
12
|
+
|
13
|
+
markup('org-ruby', /org/) do |content|
|
14
|
+
Orgmode::Parser.new(content, {
|
15
|
+
:allow_include_files => false,
|
16
|
+
:skip_syntax_highlight => true
|
17
|
+
}).to_html
|
18
|
+
end
|
19
|
+
|
20
|
+
markup(:creole, /creole/) do |content|
|
21
|
+
Creole.creolize(content)
|
22
|
+
end
|
23
|
+
|
24
|
+
markup(:wikicloth, /mediawiki|wiki/) do |content|
|
25
|
+
wikicloth = WikiCloth::WikiCloth.new(:data => content)
|
26
|
+
WikiCloth::WikiBuffer::HTMLElement::ESCAPED_TAGS << 'tt'
|
27
|
+
wikicloth.to_html(:noedit => true)
|
28
|
+
end
|
29
|
+
|
30
|
+
markup(:asciidoctor, /adoc|asc(iidoc)?/) do |content|
|
31
|
+
Asciidoctor::Compliance.unique_id_start_index = 1
|
32
|
+
Asciidoctor.convert(content, :safe => :secure, :attributes => %w(showtitle=@ idprefix idseparator=- env=github env-github source-highlighter=html-pipeline))
|
33
|
+
end
|
34
|
+
|
35
|
+
command(
|
36
|
+
"python2 -S #{Shellwords.escape(File.dirname(__FILE__))}/commands/rest2html",
|
37
|
+
/re?st(\.txt)?/,
|
38
|
+
"restructuredtext"
|
39
|
+
)
|
40
|
+
|
41
|
+
# pod2html is nice enough to generate a full-on HTML document for us,
|
42
|
+
# so we return the favor by ripping out the good parts.
|
43
|
+
#
|
44
|
+
# Any block passed to `command` will be handed the command's STDOUT for
|
45
|
+
# post processing.
|
46
|
+
command('/usr/bin/env perl -MPod::Simple::HTML -e Pod::Simple::HTML::go', /pod/, "pod") do |rendered|
|
47
|
+
if rendered =~ /<!-- start doc -->\s*(.+)\s*<!-- end doc -->/mi
|
48
|
+
$1
|
49
|
+
end
|
50
|
+
end
|
data/script/bootstrap
ADDED
data/script/cibuild
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
# GC customizations
|
6
|
+
export RUBY_GC_MALLOC_LIMIT=79000000
|
7
|
+
export RUBY_HEAP_MIN_SLOTS=800000
|
8
|
+
export RUBY_HEAP_FREE_MIN=100000
|
9
|
+
export RUBY_HEAP_SLOTS_INCREMENT=400000
|
10
|
+
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1
|
11
|
+
|
12
|
+
export PATH="/usr/share/rbenv/shims:$PATH"
|
13
|
+
export RBENV_VERSION="1.9.3"
|
14
|
+
|
15
|
+
# bootstrap gem environment changes
|
16
|
+
echo "Bootstrapping gem environment ..."
|
17
|
+
|
18
|
+
script/bootstrap --local
|
19
|
+
|
20
|
+
rake
|
data/test/markup_test.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
|
4
|
+
|
5
|
+
require 'github/markup'
|
6
|
+
require 'minitest/autorun'
|
7
|
+
require 'html/pipeline'
|
8
|
+
require 'nokogiri'
|
9
|
+
require 'nokogiri/diff'
|
10
|
+
|
11
|
+
def normalize_html(text)
|
12
|
+
text.strip!
|
13
|
+
text.gsub!(/\s\s+/,' ')
|
14
|
+
text.gsub!(/\p{Pi}|\p{Pf}|&quot;/u,'"')
|
15
|
+
text.gsub!("\u2026",'...')
|
16
|
+
text
|
17
|
+
end
|
18
|
+
|
19
|
+
def assert_html_equal(expected, actual, msg = nil)
|
20
|
+
assertion = Proc.new do
|
21
|
+
expected_doc = Nokogiri::HTML(expected) {|config| config.noblanks}
|
22
|
+
actual_doc = Nokogiri::HTML(actual) {|config| config.noblanks}
|
23
|
+
|
24
|
+
expected_doc.search('//text()').each {|node| node.content = normalize_html node.content}
|
25
|
+
actual_doc.search('//text()').each {|node| node.content = normalize_html node.content}
|
26
|
+
|
27
|
+
ignore_changes = {"+" => Regexp.union(/^\s*id=".*"\s*$/), "-" => nil}
|
28
|
+
expected_doc.diff(actual_doc) do |change, node|
|
29
|
+
if change != ' ' && !node.blank? then
|
30
|
+
break unless node.to_html =~ ignore_changes[change]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
assert(assertion.call, msg)
|
35
|
+
end
|
36
|
+
|
37
|
+
class MarkupTest < Minitest::Test
|
38
|
+
class MarkupFilter < HTML::Pipeline::Filter
|
39
|
+
def call
|
40
|
+
filename = context[:filename]
|
41
|
+
GitHub::Markup.render(filename, File.read(filename)).strip.force_encoding("utf-8")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Pipeline = HTML::Pipeline.new [
|
46
|
+
MarkupFilter,
|
47
|
+
HTML::Pipeline::SanitizationFilter
|
48
|
+
]
|
49
|
+
|
50
|
+
Dir['test/markups/README.*'].each do |readme|
|
51
|
+
next if readme =~ /html$/
|
52
|
+
markup = readme.split('/').last.gsub(/^README\./, '')
|
53
|
+
|
54
|
+
define_method "test_#{markup}" do
|
55
|
+
skip "Skipping MediaWiki test because wikicloth is currently not compatible with JRuby." if markup == "mediawiki" && RUBY_PLATFORM == "java"
|
56
|
+
|
57
|
+
source = File.read(readme)
|
58
|
+
expected_file = "#{readme}.html"
|
59
|
+
expected = File.read(expected_file).rstrip
|
60
|
+
actual = Pipeline.to_html(nil, :filename => readme)
|
61
|
+
|
62
|
+
if source != expected
|
63
|
+
assert(source != actual, "#{markup} did not render anything")
|
64
|
+
end
|
65
|
+
|
66
|
+
diff = IO.popen("diff -u - #{expected_file}", 'r+') do |f|
|
67
|
+
f.write actual
|
68
|
+
f.close_write
|
69
|
+
f.read
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_html_equal expected, actual, <<message
|
73
|
+
#{File.basename expected_file}'s contents are not html equal to output:
|
74
|
+
#{diff}
|
75
|
+
message
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_knows_what_it_can_and_cannot_render
|
80
|
+
assert_equal false, GitHub::Markup.can_render?('README.html')
|
81
|
+
assert_equal true, GitHub::Markup.can_render?('README.markdown')
|
82
|
+
assert_equal true, GitHub::Markup.can_render?('README.rmd')
|
83
|
+
assert_equal true, GitHub::Markup.can_render?('README.Rmd')
|
84
|
+
assert_equal false, GitHub::Markup.can_render?('README.cmd')
|
85
|
+
assert_equal true, GitHub::Markup.can_render?('README.litcoffee')
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_each_render_has_a_name
|
89
|
+
assert_equal "markdown", GitHub::Markup.renderer('README.md').name
|
90
|
+
assert_equal "redcloth", GitHub::Markup.renderer('README.textile').name
|
91
|
+
assert_equal "rdoc", GitHub::Markup.renderer('README.rdoc').name
|
92
|
+
assert_equal "org-ruby", GitHub::Markup.renderer('README.org').name
|
93
|
+
assert_equal "creole", GitHub::Markup.renderer('README.creole').name
|
94
|
+
assert_equal "wikicloth", GitHub::Markup.renderer('README.wiki').name
|
95
|
+
assert_equal "asciidoctor", GitHub::Markup.renderer('README.adoc').name
|
96
|
+
assert_equal "restructuredtext", GitHub::Markup.renderer('README.rst').name
|
97
|
+
assert_equal "pod", GitHub::Markup.renderer('README.pod').name
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_raises_error_if_command_exits_non_zero
|
101
|
+
GitHub::Markup.command('test/fixtures/fail.sh', /fail/, 'fail')
|
102
|
+
assert GitHub::Markup.can_render?('README.fail')
|
103
|
+
begin
|
104
|
+
GitHub::Markup.render('README.fail', "stop swallowing errors")
|
105
|
+
rescue GitHub::Markup::CommandError => e
|
106
|
+
assert_equal "failure message", e.message
|
107
|
+
else
|
108
|
+
fail "an exception was expected but was not raised"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_preserve_markup
|
113
|
+
content = "Noël"
|
114
|
+
assert_equal content.encoding.name, GitHub::Markup.render('Foo.rst', content).encoding.name
|
115
|
+
end
|
116
|
+
end
|