gollum-lib 1.0.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|