gitlab-markup 1.5.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.gitlab-ci.yml +27 -0
  4. data/.kick +26 -0
  5. data/.travis.yml +21 -0
  6. data/CONTRIBUTING.md +49 -0
  7. data/Gemfile +13 -0
  8. data/HISTORY.md +128 -0
  9. data/LICENSE +20 -0
  10. data/README.md +58 -0
  11. data/Rakefile +17 -0
  12. data/bin/github-markup +10 -0
  13. data/gitlab-markup.gemspec +25 -0
  14. data/lib/github-markup.rb +6 -0
  15. data/lib/github/commands/rest2html +200 -0
  16. data/lib/github/markup.rb +55 -0
  17. data/lib/github/markup/command_implementation.rb +71 -0
  18. data/lib/github/markup/gem_implementation.rb +30 -0
  19. data/lib/github/markup/implementation.rb +28 -0
  20. data/lib/github/markup/markdown.rb +60 -0
  21. data/lib/github/markup/rdoc.rb +26 -0
  22. data/lib/github/markups.rb +50 -0
  23. data/script/bootstrap +8 -0
  24. data/script/cibuild +20 -0
  25. data/test/fixtures/fail.sh +3 -0
  26. data/test/markup_test.rb +116 -0
  27. data/test/markups/README.asciidoc +23 -0
  28. data/test/markups/README.asciidoc.html +59 -0
  29. data/test/markups/README.creole +34 -0
  30. data/test/markups/README.creole.html +20 -0
  31. data/test/markups/README.litcoffee +59 -0
  32. data/test/markups/README.litcoffee.html +66 -0
  33. data/test/markups/README.markdown +2 -0
  34. data/test/markups/README.markdown.html +4 -0
  35. data/test/markups/README.mediawiki +30 -0
  36. data/test/markups/README.mediawiki.html +60 -0
  37. data/test/markups/README.noformat +2 -0
  38. data/test/markups/README.noformat.html +2 -0
  39. data/test/markups/README.org +131 -0
  40. data/test/markups/README.org.html +139 -0
  41. data/test/markups/README.pod +88 -0
  42. data/test/markups/README.pod.html +85 -0
  43. data/test/markups/README.rdoc +6 -0
  44. data/test/markups/README.rdoc.html +12 -0
  45. data/test/markups/README.rmd +3 -0
  46. data/test/markups/README.rmd.html +6 -0
  47. data/test/markups/README.rst +79 -0
  48. data/test/markups/README.rst.html +91 -0
  49. data/test/markups/README.rst.txt +21 -0
  50. data/test/markups/README.rst.txt.html +37 -0
  51. data/test/markups/README.textile +2 -0
  52. data/test/markups/README.textile.html +4 -0
  53. data/test/markups/README.toc.rst +30 -0
  54. data/test/markups/README.toc.rst.html +32 -0
  55. data/test/markups/README.txt +2 -0
  56. data/test/markups/README.txt.html +2 -0
  57. 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
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ cd $(dirname "$0")/..
6
+
7
+ bundle install
8
+ easy_install docutils
@@ -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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+
3
+ echo "failure message">&2 && false
@@ -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}|&amp;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