gollum-lib 1.0.9 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of gollum-lib might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/README.md +1 -1
- data/gollum-lib.gemspec +13 -5
- data/lib/gollum-lib.rb +2 -2
- data/lib/gollum-lib/filter.rb +78 -0
- data/lib/gollum-lib/filter/code.rb +123 -0
- data/lib/gollum-lib/filter/metadata.rb +27 -0
- data/lib/gollum-lib/filter/plain_text.rb +15 -0
- data/lib/gollum-lib/filter/remote_code.rb +61 -0
- data/lib/gollum-lib/filter/render.rb +18 -0
- data/lib/gollum-lib/filter/sanitize.rb +16 -0
- data/lib/gollum-lib/filter/tags.rb +292 -0
- data/lib/gollum-lib/filter/toc.rb +45 -0
- data/lib/gollum-lib/filter/wsd.rb +54 -0
- data/lib/gollum-lib/markup.rb +41 -621
- data/lib/gollum-lib/markups.rb +1 -0
- data/lib/gollum-lib/wiki.rb +85 -0
- metadata +16 -8
- data/lib/gollum-lib/remote_code.rb +0 -39
- data/lib/gollum-lib/web_sequence_diagram.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e62158d0795431631a6a845ac26a221535c8604
|
4
|
+
data.tar.gz: 35c5ad577b84b262fd492dfc2e5b30f92d5f97ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54dd123e3f0cc5c2dc7df860a289d1ca3a46edd360c4d8ecf0dabf3e9d6ee3b2e68383c4b21b3d87f82e1309df3cfd24515e07e7b219a518557e1043ea2dbaa2
|
7
|
+
data.tar.gz: 5bd2a239672e66b8a0364acdee7806e1be851b821dfb5b471328d8ffeb8e8581bfea27ee5641363138898e4a6c51530fd5c0a948277190cfe779f11f558716af
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -20,7 +20,7 @@ Gollum-lib follows the rules of [Semantic Versioning](http://semver.org/) and us
|
|
20
20
|
## SYSTEM REQUIREMENTS
|
21
21
|
|
22
22
|
- Python 2.5+ (2.7.3 recommended)
|
23
|
-
- Ruby 1.
|
23
|
+
- Ruby 1.9.3+ (1.9.3 recommended)
|
24
24
|
- Unix like operating system (OS X, Ubuntu, Debian, and more)
|
25
25
|
- Will not work on Windows (because of [grit](https://github.com/github/grit))
|
26
26
|
|
data/gollum-lib.gemspec
CHANGED
@@ -5,8 +5,8 @@ Gem::Specification.new do |s|
|
|
5
5
|
s.required_ruby_version = ">= 1.9"
|
6
6
|
|
7
7
|
s.name = 'gollum-lib'
|
8
|
-
s.version = '
|
9
|
-
s.date = '
|
8
|
+
s.version = '2.0.0'
|
9
|
+
s.date = '2014-02-20'
|
10
10
|
s.rubyforge_project = 'gollum-lib'
|
11
11
|
s.license = 'MIT'
|
12
12
|
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
|
25
25
|
s.add_dependency('gitlab-grit', '2.6.0')
|
26
26
|
s.add_dependency('github-markup', ['>= 0.7.5', '< 1.0.0'])
|
27
|
-
s.add_dependency('
|
27
|
+
s.add_dependency('rouge', '~> 1.3.1')
|
28
28
|
s.add_dependency('sanitize', '~> 2.0.6')
|
29
29
|
s.add_dependency('nokogiri', '~> 1.6.0')
|
30
30
|
s.add_dependency('stringex', '~> 2.1.0')
|
@@ -62,6 +62,16 @@ Gem::Specification.new do |s|
|
|
62
62
|
lib/gollum-lib/committer.rb
|
63
63
|
lib/gollum-lib/file.rb
|
64
64
|
lib/gollum-lib/file_view.rb
|
65
|
+
lib/gollum-lib/filter.rb
|
66
|
+
lib/gollum-lib/filter/code.rb
|
67
|
+
lib/gollum-lib/filter/metadata.rb
|
68
|
+
lib/gollum-lib/filter/plain_text.rb
|
69
|
+
lib/gollum-lib/filter/remote_code.rb
|
70
|
+
lib/gollum-lib/filter/render.rb
|
71
|
+
lib/gollum-lib/filter/sanitize.rb
|
72
|
+
lib/gollum-lib/filter/tags.rb
|
73
|
+
lib/gollum-lib/filter/toc.rb
|
74
|
+
lib/gollum-lib/filter/wsd.rb
|
65
75
|
lib/gollum-lib/git_access.rb
|
66
76
|
lib/gollum-lib/gitcode.rb
|
67
77
|
lib/gollum-lib/grit_ext.rb
|
@@ -71,9 +81,7 @@ Gem::Specification.new do |s|
|
|
71
81
|
lib/gollum-lib/markups.rb
|
72
82
|
lib/gollum-lib/page.rb
|
73
83
|
lib/gollum-lib/pagination.rb
|
74
|
-
lib/gollum-lib/remote_code.rb
|
75
84
|
lib/gollum-lib/sanitization.rb
|
76
|
-
lib/gollum-lib/web_sequence_diagram.rb
|
77
85
|
lib/gollum-lib/wiki.rb
|
78
86
|
licenses/licenses.txt
|
79
87
|
]
|
data/lib/gollum-lib.rb
CHANGED
@@ -23,7 +23,7 @@ require File.expand_path('../gollum-lib/file_view', __FILE__)
|
|
23
23
|
require File.expand_path('../gollum-lib/markup', __FILE__)
|
24
24
|
require File.expand_path('../gollum-lib/markups', __FILE__)
|
25
25
|
require File.expand_path('../gollum-lib/sanitization', __FILE__)
|
26
|
-
require File.expand_path('../gollum-lib/
|
26
|
+
require File.expand_path('../gollum-lib/filter', __FILE__)
|
27
27
|
|
28
28
|
# Set ruby to UTF-8 mode
|
29
29
|
# This is required for Ruby 1.8.7 which gollum still supports.
|
@@ -31,7 +31,7 @@ $KCODE = 'U' if RUBY_VERSION[0,3] == '1.8'
|
|
31
31
|
|
32
32
|
module Gollum
|
33
33
|
module Lib
|
34
|
-
VERSION = '
|
34
|
+
VERSION = '2.0.0'
|
35
35
|
end
|
36
36
|
|
37
37
|
def self.assets_path
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# A "filter", in Gollum-speak, is an object which extracts tokens from an
|
2
|
+
# input stream of an arbitrary markup language, then replaces them with a
|
3
|
+
# final form in a rendered form of the same document. Filters are composed
|
4
|
+
# into a "filter chain", which forms the order in which filters are applied
|
5
|
+
# in both the extraction and processing phases (processing happens in the
|
6
|
+
# reverse order to extraction). A single instance of a filter class is used
|
7
|
+
# for both the extraction and processing, so you can store internal state
|
8
|
+
# from the extraction phase for use in the processing phase.
|
9
|
+
#
|
10
|
+
# Any class which is to be used as a filter must have an `initialize` method
|
11
|
+
# which takes a single mandatory argument (the instance of the `Markup`
|
12
|
+
# class we're being called from), and must implement two methods: `extract`
|
13
|
+
# and `process`, both of which must take a string as input and return a
|
14
|
+
# (possibly modified) form of that string as output.
|
15
|
+
#
|
16
|
+
# An example rendering session: consider a filter chain with three filters
|
17
|
+
# in it, :A, :B, and :C (filter chains are specified as symbols, which are
|
18
|
+
# taken to be class names within the Gollum::Filter namespace). An
|
19
|
+
# equivalent of the following will take place:
|
20
|
+
#
|
21
|
+
# a = Gollum::Filter.const_get(:A).new
|
22
|
+
# b = Gollum::Filter.const_get(:B).new
|
23
|
+
# c = Gollum::Filter.const_get(:C).new
|
24
|
+
#
|
25
|
+
# data = "<some markup>"
|
26
|
+
#
|
27
|
+
# data = a.extract(data)
|
28
|
+
# data = b.extract(data)
|
29
|
+
# data = c.extract(data)
|
30
|
+
#
|
31
|
+
# data = c.process(data)
|
32
|
+
# data = b.process(data)
|
33
|
+
# data = a.process(data)
|
34
|
+
#
|
35
|
+
# # `data` now contains the rendered document, ready for processing
|
36
|
+
#
|
37
|
+
# Note how the extraction steps go in the order of the filter chain, while
|
38
|
+
# the processing steps go in the reverse order. There hasn't (yet) been a
|
39
|
+
# case where that is a huge problem.
|
40
|
+
#
|
41
|
+
# If your particular filter doesn't need to do something with either the
|
42
|
+
# original markup or rendered forms, you can simply define the relevant
|
43
|
+
# method to be a pass-through (`def extract(d) d; end`), but you *must*
|
44
|
+
# define both methods yourself.
|
45
|
+
#
|
46
|
+
module Gollum
|
47
|
+
class Filter
|
48
|
+
include Gollum::Helpers
|
49
|
+
|
50
|
+
# Setup the object. Sets `@markup` to be the instance of Gollum::Markup that
|
51
|
+
# is running this filter chain, and sets `@map` to be an empty hash (for use
|
52
|
+
# in your extract/process operations).
|
53
|
+
def initialize(markup)
|
54
|
+
@markup = markup
|
55
|
+
@map = {}
|
56
|
+
end
|
57
|
+
|
58
|
+
def extract(_d)
|
59
|
+
raise RuntimeError,
|
60
|
+
"#{self.class} has not implemented ##extract!"
|
61
|
+
end
|
62
|
+
|
63
|
+
def process(_d)
|
64
|
+
raise RuntimeError,
|
65
|
+
"#{self.class} has not implemented ##process!"
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
# Render a (presumably) non-fatal error as HTML
|
70
|
+
#
|
71
|
+
def html_error(message)
|
72
|
+
"<p class=\"gollum-error\">#{message}</p>"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Load all standard filters
|
78
|
+
Dir[File.expand_path('../filter/*.rb', __FILE__)].each { |f| require f }
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
3
|
+
# Code
|
4
|
+
#
|
5
|
+
# Render a block of code using the Rouge syntax-highlighter.
|
6
|
+
class Gollum::Filter::Code < Gollum::Filter
|
7
|
+
def extract(data)
|
8
|
+
return data if @markup.format == :txt
|
9
|
+
data.gsub!(/^([ \t]*)(~~~+) ?([^\r\n]+)?\r?\n(.+?)\r?\n\1(~~~+)[ \t\r]*$/m) do
|
10
|
+
m_indent = $1
|
11
|
+
m_start = $2 # ~~~
|
12
|
+
m_lang = $3
|
13
|
+
m_code = $4
|
14
|
+
m_end = $5 # ~~~
|
15
|
+
|
16
|
+
# start and finish tilde fence must be the same length
|
17
|
+
next '' if m_start.length != m_end.length
|
18
|
+
|
19
|
+
lang = m_lang ? m_lang.strip : nil
|
20
|
+
id = Digest::SHA1.hexdigest("#{lang}.#{m_code}")
|
21
|
+
cached = @markup.check_cache(:code, id)
|
22
|
+
|
23
|
+
# extract lang from { .ruby } or { #stuff .ruby .indent }
|
24
|
+
# see http://johnmacfarlane.net/pandoc/README.html#delimited-code-blocks
|
25
|
+
|
26
|
+
if lang
|
27
|
+
lang = lang.match(/\.([^}\s]+)/)
|
28
|
+
lang = lang[1] unless lang.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
@map[id] = cached ?
|
32
|
+
{ :output => cached } :
|
33
|
+
{ :lang => lang, :code => m_code, :indent => m_indent }
|
34
|
+
|
35
|
+
"#{m_indent}#{id}" # print the SHA1 ID with the proper indentation
|
36
|
+
end
|
37
|
+
|
38
|
+
data.gsub!(/^([ \t]*)``` ?([^\r\n]+)?\r?\n(.+?)\r?\n\1```[ \t]*\r?$/m) do
|
39
|
+
lang = $2 ? $2.strip : nil
|
40
|
+
id = Digest::SHA1.hexdigest("#{lang}.#{$3}")
|
41
|
+
cached = @markup.check_cache(:code, id)
|
42
|
+
@map[id] = cached ?
|
43
|
+
{ :output => cached } :
|
44
|
+
{ :lang => lang, :code => $3, :indent => $1 }
|
45
|
+
"#{$1}#{id}" # print the SHA1 ID with the proper indentation
|
46
|
+
end
|
47
|
+
|
48
|
+
data
|
49
|
+
end
|
50
|
+
|
51
|
+
# Process all code from the codemap and replace the placeholders with the
|
52
|
+
# final HTML.
|
53
|
+
#
|
54
|
+
# data - The String data (with placeholders).
|
55
|
+
# encoding - Encoding Constant or String.
|
56
|
+
#
|
57
|
+
# Returns the marked up String data.
|
58
|
+
def process(data)
|
59
|
+
return data if data.nil? || data.size.zero? || @map.size.zero?
|
60
|
+
|
61
|
+
blocks = []
|
62
|
+
|
63
|
+
@map.each do |id, spec|
|
64
|
+
next if spec[:output] # cached
|
65
|
+
|
66
|
+
code = spec[:code]
|
67
|
+
|
68
|
+
remove_leading_space(code, /^#{spec[:indent]}/m)
|
69
|
+
remove_leading_space(code, /^( |\t)/m)
|
70
|
+
|
71
|
+
blocks << [spec[:lang], code]
|
72
|
+
end
|
73
|
+
|
74
|
+
highlighted = []
|
75
|
+
blocks.each do |lang, code|
|
76
|
+
encoding = @markup.encoding || 'utf-8'
|
77
|
+
begin
|
78
|
+
if Rouge::Lexer.find(lang).nil?
|
79
|
+
lexer = Rouge::Lexers::PlainText.new
|
80
|
+
formatter = Rouge::Formatters::HTML.new(:wrap => false)
|
81
|
+
hl_code = formatter.format(lexer.lex(code))
|
82
|
+
hl_code = "<pre class='highlight'><span class='err'>#{CGI.escapeHTML(hl_code)}</span></pre>"
|
83
|
+
else
|
84
|
+
hl_code = Rouge.highlight(code, lang, 'html')
|
85
|
+
end
|
86
|
+
rescue
|
87
|
+
hl_code = code
|
88
|
+
end
|
89
|
+
highlighted << hl_code
|
90
|
+
end
|
91
|
+
|
92
|
+
@map.each do |id, spec|
|
93
|
+
body = spec[:output] || begin
|
94
|
+
if (body = highlighted.shift.to_s).size > 0
|
95
|
+
@markup.update_cache(:code, id, body)
|
96
|
+
body
|
97
|
+
else
|
98
|
+
"<pre><code>#{CGI.escapeHTML(spec[:code])}</code></pre>"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
data.gsub!(id) do
|
102
|
+
body
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
data
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
# Remove the leading space from a code block. Leading space
|
111
|
+
# is only removed if every single line in the block has leading
|
112
|
+
# whitespace.
|
113
|
+
#
|
114
|
+
# code - The code block to remove spaces from
|
115
|
+
# regex - A regex to match whitespace
|
116
|
+
def remove_leading_space(code, regex)
|
117
|
+
if code.lines.all? { |line| line =~ /\A\r?\n\Z/ || line =~ regex }
|
118
|
+
code.gsub!(regex) do
|
119
|
+
''
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Extract metadata for data and build metadata table. Metadata consists of
|
2
|
+
# key/value pairs in "key:value" format found between markers. Each
|
3
|
+
# key/value pair must be on its own line. Internal whitespace in keys and
|
4
|
+
# values is preserved, but external whitespace is ignored.
|
5
|
+
#
|
6
|
+
# Because ri and ruby 1.8.7 are awesome, the markers can't
|
7
|
+
# be included in this documentation without triggering
|
8
|
+
# `Unhandled special: Special: type=17`
|
9
|
+
# Please read the source code for the exact markers
|
10
|
+
class Gollum::Filter::Metadata < Gollum::Filter
|
11
|
+
def extract(data)
|
12
|
+
# The markers are `<!-- ---` and `-->`
|
13
|
+
data.gsub(/\<\!--+\s+---(.*?)--+\>/m) do
|
14
|
+
@markup.metadata ||= {}
|
15
|
+
# Split untrusted input on newlines, then remove bits that look like
|
16
|
+
# HTML elements before parsing each line.
|
17
|
+
$1.split("\n").each do |line|
|
18
|
+
line.gsub!(/<[^>]*>/, '')
|
19
|
+
k, v = line.split(':', 2)
|
20
|
+
@markup.metadata[k.strip] = (v ? v.strip : '') if k
|
21
|
+
end
|
22
|
+
''
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def process(d) d; end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
3
|
+
# Plain Text
|
4
|
+
#
|
5
|
+
# Render plain text documents in a <pre> block without any special markup.
|
6
|
+
|
7
|
+
class Gollum::Filter::PlainText < Gollum::Filter
|
8
|
+
|
9
|
+
def extract(data)
|
10
|
+
@markup.format == :txt ? "<pre>#{CGI.escapeHTML(data)}</pre>" : data
|
11
|
+
end
|
12
|
+
|
13
|
+
def process(data); data ; end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https' # ruby 1.8.7 fix, remove at upgrade
|
4
|
+
require 'uri'
|
5
|
+
require 'open-uri'
|
6
|
+
|
7
|
+
# Remote code - fetch code from url and replace the contents to a
|
8
|
+
# code-block that gets run the next parse.
|
9
|
+
# Acceptable formats:
|
10
|
+
# ```language:local-file.ext```
|
11
|
+
# ```language:/abs/other-file.ext```
|
12
|
+
# ```language:https://example.com/somefile.txt```
|
13
|
+
#
|
14
|
+
class Gollum::Filter::RemoteCode < Gollum::Filter
|
15
|
+
def extract(data)
|
16
|
+
return data if @markup.format == :txt
|
17
|
+
data.gsub /^[ \t]*``` ?([^:\n\r]+):((http)?[^`\n\r]+)```/ do
|
18
|
+
language = $1
|
19
|
+
uri = $2
|
20
|
+
protocol = $3
|
21
|
+
|
22
|
+
# Detect local file
|
23
|
+
if protocol.nil?
|
24
|
+
if file = @markup.find_file(uri, @markup.wiki.ref)
|
25
|
+
contents = file.raw_data
|
26
|
+
else
|
27
|
+
# How do we communicate a render error?
|
28
|
+
next html_error("File not found: #{CGI::escapeHTML(uri)}")
|
29
|
+
end
|
30
|
+
else
|
31
|
+
contents = req(uri)
|
32
|
+
end
|
33
|
+
|
34
|
+
"```#{language}\n#{contents}\n```\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def process(d) d; end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def req uri, cut = 1
|
43
|
+
uri = URI(uri)
|
44
|
+
return "Too many redirects or retries" if cut >= 10
|
45
|
+
http = Net::HTTP.new uri.host, uri.port
|
46
|
+
http.use_ssl = true
|
47
|
+
resp = http.get uri.path, {
|
48
|
+
'Accept' => 'text/plain',
|
49
|
+
'Cache-Control' => 'no-cache',
|
50
|
+
'Connection' => 'keep-alive',
|
51
|
+
'User-Agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0'
|
52
|
+
}
|
53
|
+
code = resp.code.to_i
|
54
|
+
return resp.body if code == 200
|
55
|
+
return "Not Found" if code == 404
|
56
|
+
return "Unhandled Response Code #{code}" unless code == 304 or not resp.header['location'].nil?
|
57
|
+
loc = URI.parse resp.header['location']
|
58
|
+
uri2 = loc.relative? ? (uri + loc) : loc # overloads (+)
|
59
|
+
req uri2, (cut + 1)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
3
|
+
class Gollum::Filter::Render < Gollum::Filter
|
4
|
+
def extract(data)
|
5
|
+
begin
|
6
|
+
data = GitHub::Markup.render(@markup.name, data)
|
7
|
+
if data.nil?
|
8
|
+
raise "There was an error converting #{@markup.name} to HTML."
|
9
|
+
end
|
10
|
+
rescue Object => e
|
11
|
+
data = html_error("Failed to render page: #{e.message}")
|
12
|
+
end
|
13
|
+
|
14
|
+
data
|
15
|
+
end
|
16
|
+
|
17
|
+
def process(d) d; end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
3
|
+
class Gollum::Filter::Sanitize < Gollum::Filter
|
4
|
+
def extract(d) d; end
|
5
|
+
|
6
|
+
def process(data)
|
7
|
+
if @markup.sanitize
|
8
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(data)
|
9
|
+
doc = @markup.sanitize.clean_node!(doc)
|
10
|
+
|
11
|
+
doc.to_xml(@markup.to_xml_opts)
|
12
|
+
else
|
13
|
+
data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
3
|
+
# Render all tags (things in double-square-brackets). This one's a biggie.
|
4
|
+
class Gollum::Filter::Tags < Gollum::Filter
|
5
|
+
# Extract all tags into the tagmap and replace with placeholders.
|
6
|
+
def extract(data)
|
7
|
+
return data if @markup.format == :txt || @markup.format == :asciidoc
|
8
|
+
data.gsub!(/(.?)\[\[(.+?)\]\]([^\[]?)/m) do
|
9
|
+
if $1 == "'" && $3 != "'"
|
10
|
+
"[[#{$2}]]#{$3}"
|
11
|
+
elsif $2.include?('][')
|
12
|
+
if $2[0..4] == 'file:'
|
13
|
+
pre = $1
|
14
|
+
post = $3
|
15
|
+
parts = $2.split('][')
|
16
|
+
parts[0][0..4] = ""
|
17
|
+
link = "#{parts[1]}|#{parts[0].sub(/\.org/,'')}"
|
18
|
+
id = Digest::SHA1.hexdigest(link)
|
19
|
+
@map[id] = link
|
20
|
+
"#{pre}#{id}#{post}"
|
21
|
+
else
|
22
|
+
$&
|
23
|
+
end
|
24
|
+
else
|
25
|
+
id = Digest::SHA1.hexdigest($2)
|
26
|
+
@map[id] = $2
|
27
|
+
"#{$1}#{id}#{$3}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
data
|
31
|
+
end
|
32
|
+
|
33
|
+
# Process all tags from the tagmap and replace the placeholders with the
|
34
|
+
# final markup.
|
35
|
+
def process(data)
|
36
|
+
@map.each do |id, tag|
|
37
|
+
# If it's preformatted, just put the tag back
|
38
|
+
if is_preformatted?(data, id)
|
39
|
+
data.gsub!(id) do
|
40
|
+
"[[#{tag}]]"
|
41
|
+
end
|
42
|
+
else
|
43
|
+
data.gsub!(id) do
|
44
|
+
process_tag(tag).gsub('%2F', '/')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
data
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
# Find `id` within `data` and determine if it's within
|
54
|
+
# preformatted tags.
|
55
|
+
#
|
56
|
+
# data - The String data (with placeholders).
|
57
|
+
# id - The String SHA1 hash.
|
58
|
+
PREFORMATTED_TAGS = %w(code tt)
|
59
|
+
def is_preformatted?(data, id)
|
60
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(data)
|
61
|
+
node = doc.search("[text()*='#{id}']").first
|
62
|
+
node && (PREFORMATTED_TAGS.include?(node.name) ||
|
63
|
+
node.ancestors.any? { |a| PREFORMATTED_TAGS.include?(a.name) })
|
64
|
+
end
|
65
|
+
|
66
|
+
# Process a single tag into its final HTML form.
|
67
|
+
#
|
68
|
+
# tag - The String tag contents (the stuff inside the double
|
69
|
+
# brackets).
|
70
|
+
#
|
71
|
+
# Returns the String HTML version of the tag.
|
72
|
+
def process_tag(tag)
|
73
|
+
if tag =~ /^_TOC_$/
|
74
|
+
%{[[#{tag}]]}
|
75
|
+
elsif tag =~ /^_$/
|
76
|
+
%{<div class="clearfloats"></div>}
|
77
|
+
elsif html = process_include_tag(tag)
|
78
|
+
html
|
79
|
+
elsif html = process_image_tag(tag)
|
80
|
+
html
|
81
|
+
elsif html = process_file_link_tag(tag)
|
82
|
+
html
|
83
|
+
else
|
84
|
+
process_page_link_tag(tag)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Attempt to process the tag as an include tag
|
89
|
+
#
|
90
|
+
# tag - The String tag contents (the stuff inside the double brackets).
|
91
|
+
#
|
92
|
+
# Returns the String HTML if the tag is a valid image tag or nil
|
93
|
+
# if it is not.
|
94
|
+
#
|
95
|
+
def process_include_tag(tag)
|
96
|
+
return unless /^include:/.match(tag)
|
97
|
+
page_name = tag[8..-1]
|
98
|
+
resolved_page_name = ::File.expand_path(page_name, "/"+@markup.dir)
|
99
|
+
|
100
|
+
if @markup.include_levels > 0
|
101
|
+
page = find_page_from_name(resolved_page_name)
|
102
|
+
if page
|
103
|
+
page.formatted_data(@markup.encoding, @markup.include_levels-1)
|
104
|
+
else
|
105
|
+
html_error("Cannot include #{process_page_link_tag(resolved_page_name)} - does not exist yet")
|
106
|
+
end
|
107
|
+
else
|
108
|
+
html_error("Too many levels of included pages, will not include #{process_page_link_tag(resolved_page_name)}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Attempt to process the tag as an image tag.
|
113
|
+
#
|
114
|
+
# tag - The String tag contents (the stuff inside the double brackets).
|
115
|
+
#
|
116
|
+
# Returns the String HTML if the tag is a valid image tag or nil
|
117
|
+
# if it is not.
|
118
|
+
def process_image_tag(tag)
|
119
|
+
parts = tag.split('|')
|
120
|
+
return if parts.size.zero?
|
121
|
+
|
122
|
+
name = parts[0].strip
|
123
|
+
path = if file = @markup.find_file(name)
|
124
|
+
::File.join @markup.wiki.base_path, file.path
|
125
|
+
elsif name =~ /^https?:\/\/.+(jpg|png|gif|svg|bmp)$/i
|
126
|
+
name
|
127
|
+
end
|
128
|
+
|
129
|
+
if path
|
130
|
+
opts = parse_image_tag_options(tag)
|
131
|
+
|
132
|
+
containered = false
|
133
|
+
|
134
|
+
classes = [] # applied to whatever the outermost container is
|
135
|
+
attrs = [] # applied to the image
|
136
|
+
|
137
|
+
align = opts['align']
|
138
|
+
if opts['float']
|
139
|
+
containered = true
|
140
|
+
align ||= 'left'
|
141
|
+
if %w{left right}.include?(align)
|
142
|
+
classes << "float-#{align}"
|
143
|
+
end
|
144
|
+
elsif %w{top texttop middle absmiddle bottom absbottom baseline}.include?(align)
|
145
|
+
attrs << %{align="#{align}"}
|
146
|
+
elsif align
|
147
|
+
if %w{left center right}.include?(align)
|
148
|
+
containered = true
|
149
|
+
classes << "align-#{align}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if width = opts['width']
|
154
|
+
if width =~ /^\d+(\.\d+)?(em|px)$/
|
155
|
+
attrs << %{width="#{width}"}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
if height = opts['height']
|
160
|
+
if height =~ /^\d+(\.\d+)?(em|px)$/
|
161
|
+
attrs << %{height="#{height}"}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
if alt = opts['alt']
|
166
|
+
attrs << %{alt="#{alt}"}
|
167
|
+
end
|
168
|
+
|
169
|
+
attr_string = attrs.size > 0 ? attrs.join(' ') + ' ' : ''
|
170
|
+
|
171
|
+
if opts['frame'] || containered
|
172
|
+
classes << 'frame' if opts['frame']
|
173
|
+
%{<span class="#{classes.join(' ')}">} +
|
174
|
+
%{<span>} +
|
175
|
+
%{<img src="#{path}" #{attr_string}/>} +
|
176
|
+
(alt ? %{<span>#{alt}</span>} : '') +
|
177
|
+
%{</span>} +
|
178
|
+
%{</span>}
|
179
|
+
else
|
180
|
+
%{<img src="#{path}" #{attr_string}/>}
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Parse any options present on the image tag and extract them into a
|
186
|
+
# Hash of option names and values.
|
187
|
+
#
|
188
|
+
# tag - The String tag contents (the stuff inside the double brackets).
|
189
|
+
#
|
190
|
+
# Returns the options Hash:
|
191
|
+
# key - The String option name.
|
192
|
+
# val - The String option value or true if it is a binary option.
|
193
|
+
def parse_image_tag_options(tag)
|
194
|
+
tag.split('|')[1..-1].inject({}) do |memo, attr|
|
195
|
+
parts = attr.split('=').map { |x| x.strip }
|
196
|
+
memo[parts[0]] = (parts.size == 1 ? true : parts[1])
|
197
|
+
memo
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Attempt to process the tag as a file link tag.
|
202
|
+
#
|
203
|
+
# tag - The String tag contents (the stuff inside the double
|
204
|
+
# brackets).
|
205
|
+
#
|
206
|
+
# Returns the String HTML if the tag is a valid file link tag or nil
|
207
|
+
# if it is not.
|
208
|
+
def process_file_link_tag(tag)
|
209
|
+
parts = tag.split('|')
|
210
|
+
return if parts.size.zero?
|
211
|
+
|
212
|
+
name = parts[0].strip
|
213
|
+
path = parts[1] && parts[1].strip
|
214
|
+
path = if path && file = @markup.find_file(path)
|
215
|
+
::File.join @markup.wiki.base_path, file.path
|
216
|
+
elsif path =~ %r{^https?://}
|
217
|
+
path
|
218
|
+
else
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
|
222
|
+
if name && path && file
|
223
|
+
%{<a href="#{::File.join @markup.wiki.base_path, file.path}">#{name}</a>}
|
224
|
+
elsif name && path
|
225
|
+
%{<a href="#{path}">#{name}</a>}
|
226
|
+
else
|
227
|
+
nil
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Attempt to process the tag as a page link tag.
|
232
|
+
#
|
233
|
+
# tag - The String tag contents (the stuff inside the double
|
234
|
+
# brackets).
|
235
|
+
#
|
236
|
+
# Returns the String HTML if the tag is a valid page link tag or nil
|
237
|
+
# if it is not.
|
238
|
+
def process_page_link_tag(tag)
|
239
|
+
parts = tag.split('|')
|
240
|
+
parts.reverse! if @markup.format == :mediawiki
|
241
|
+
|
242
|
+
name, page_name = *parts.compact.map(&:strip)
|
243
|
+
cname = @markup.wiki.page_class.cname(page_name || name)
|
244
|
+
|
245
|
+
if name =~ %r{^https?://} && page_name.nil?
|
246
|
+
%{<a href="#{name}">#{name}</a>}
|
247
|
+
else
|
248
|
+
presence = "absent"
|
249
|
+
link_name = cname
|
250
|
+
page, extra = find_page_from_name(cname)
|
251
|
+
if page
|
252
|
+
link_name = @markup.wiki.page_class.cname(page.name)
|
253
|
+
presence = "present"
|
254
|
+
end
|
255
|
+
link = ::File.join(@markup.wiki.base_path, page ? page.escaped_url_path : CGI.escape(link_name))
|
256
|
+
|
257
|
+
# //page is invalid
|
258
|
+
# strip all duplicate forward slashes using helpers.rb trim_leading_slash
|
259
|
+
# //page => /page
|
260
|
+
link = trim_leading_slash link
|
261
|
+
|
262
|
+
%{<a class="internal #{presence}" href="#{link}#{extra}">#{name}</a>}
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Find a page from a given cname. If the page has an anchor (#) and has
|
267
|
+
# no match, strip the anchor and try again.
|
268
|
+
#
|
269
|
+
# cname - The String canonical page name including path.
|
270
|
+
#
|
271
|
+
# Returns a Gollum::Page instance if a page is found, or an Array of
|
272
|
+
# [Gollum::Page, String extra] if a page without the extra anchor data
|
273
|
+
# is found.
|
274
|
+
def find_page_from_name(cname)
|
275
|
+
slash = cname.rindex('/')
|
276
|
+
|
277
|
+
unless slash.nil?
|
278
|
+
name = cname[slash+1..-1]
|
279
|
+
path = cname[0..slash]
|
280
|
+
page = @markup.wiki.paged(name, path)
|
281
|
+
else
|
282
|
+
page = @markup.wiki.paged(cname, '/') || @markup.wiki.page(cname)
|
283
|
+
end
|
284
|
+
|
285
|
+
if page
|
286
|
+
return page
|
287
|
+
end
|
288
|
+
if pos = cname.index('#')
|
289
|
+
[@markup.wiki.page(cname[0...pos]), cname[pos..-1]]
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|