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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 12c5ce0dd57482d2d3264712ac15839e67e6398d
4
- data.tar.gz: 3b0b498880df3a86d6fb8a3d09acab8ed629043c
3
+ metadata.gz: 2e62158d0795431631a6a845ac26a221535c8604
4
+ data.tar.gz: 35c5ad577b84b262fd492dfc2e5b30f92d5f97ee
5
5
  SHA512:
6
- metadata.gz: 3d8a845aadfde384b2322851058937d9eb0724d9098b072f604eec6f41054eb3faf86b7430604caa99fd7ea2298db3f8c94b03a53215603fd435eccba956af4a
7
- data.tar.gz: 25974065b8c080aa366aa345891aa51475d43aa424e73f5c08c57a807fcbaeccd07d7d2bdfcb530d682c321d6ce98b23eb5275c4746cef37aed0d293bf2a7fda
6
+ metadata.gz: 54dd123e3f0cc5c2dc7df860a289d1ca3a46edd360c4d8ecf0dabf3e9d6ee3b2e68383c4b21b3d87f82e1309df3cfd24515e07e7b219a518557e1043ea2dbaa2
7
+ data.tar.gz: 5bd2a239672e66b8a0364acdee7806e1be851b821dfb5b471328d8ffeb8e8581bfea27ee5641363138898e4a6c51530fd5c0a948277190cfe779f11f558716af
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
- gem 'rake', '~> 10.0.3'
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.8.7+ (1.9.3 recommended)
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 = '1.0.9'
9
- s.date = '2013-11-02'
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('pygments.rb', '~> 0.5.2')
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/web_sequence_diagram', __FILE__)
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 = '1.0.9'
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