Pimki 1.3.092 → 1.4.092

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.
Files changed (88) hide show
  1. data/README +145 -131
  2. data/README-PIMKI +15 -5
  3. data/app/controllers/wiki.rb +167 -54
  4. data/app/models/author.rb +3 -3
  5. data/app/models/chunks/chunk.rb +3 -3
  6. data/app/models/chunks/engines.rb +18 -21
  7. data/app/models/chunks/include.rb +29 -29
  8. data/app/models/chunks/literal.rb +20 -20
  9. data/app/models/chunks/match.rb +19 -19
  10. data/app/models/chunks/nowiki.rb +31 -31
  11. data/app/models/chunks/nowiki_test.rb +14 -14
  12. data/app/models/chunks/test.rb +18 -18
  13. data/app/models/chunks/todo.rb +44 -23
  14. data/app/models/chunks/uri.rb +97 -97
  15. data/app/models/chunks/uri_test.rb +92 -92
  16. data/app/models/chunks/wiki.rb +4 -4
  17. data/app/models/chunks/wiki_symbols.rb +22 -22
  18. data/app/models/chunks/wiki_test.rb +36 -36
  19. data/app/models/page.rb +39 -7
  20. data/app/models/page_lock.rb +23 -23
  21. data/app/models/page_set.rb +72 -72
  22. data/app/models/page_test.rb +75 -75
  23. data/app/models/revision.rb +1 -1
  24. data/app/models/revision_test.rb +251 -251
  25. data/app/models/web.rb +19 -6
  26. data/app/models/web_test.rb +52 -52
  27. data/app/models/wiki_content.rb +131 -119
  28. data/app/models/wiki_service.rb +31 -16
  29. data/app/models/wiki_service_test.rb +15 -15
  30. data/app/models/wiki_words.rb +1 -1
  31. data/app/models/wiki_words_test.rb +12 -12
  32. data/app/views/bottom.rhtml +3 -3
  33. data/app/views/markdown_help.rhtml +15 -15
  34. data/app/views/menu.rhtml +20 -20
  35. data/app/views/navigation.rhtml +26 -26
  36. data/app/views/rdoc_help.rhtml +15 -15
  37. data/app/views/static_style_sheet.rhtml +237 -237
  38. data/app/views/style.rhtml +178 -178
  39. data/app/views/textile_help.rhtml +27 -27
  40. data/app/views/top.rhtml +7 -2
  41. data/app/views/wiki/authors.rhtml +15 -15
  42. data/app/views/wiki/bliki.rhtml +101 -101
  43. data/app/views/wiki/bliki_edit.rhtml +3 -0
  44. data/app/views/wiki/bliki_new.rhtml +3 -0
  45. data/app/views/wiki/bliki_revision.rhtml +90 -90
  46. data/app/views/wiki/edit.rhtml +12 -3
  47. data/app/views/wiki/edit_menu.rhtml +64 -47
  48. data/app/views/wiki/edit_web.rhtml +65 -18
  49. data/app/views/wiki/export.rhtml +14 -14
  50. data/app/views/wiki/feeds.rhtml +10 -10
  51. data/app/views/wiki/list.rhtml +17 -15
  52. data/app/views/wiki/locked.rhtml +13 -13
  53. data/app/views/wiki/login.rhtml +10 -10
  54. data/app/views/wiki/mind.rhtml +0 -1
  55. data/app/views/wiki/new.rhtml +8 -3
  56. data/app/views/wiki/new_system.rhtml +77 -77
  57. data/app/views/wiki/new_web.rhtml +63 -63
  58. data/app/views/wiki/page.rhtml +88 -82
  59. data/app/views/wiki/print.rhtml +15 -15
  60. data/app/views/wiki/published.rhtml +2 -1
  61. data/app/views/wiki/recently_revised.rhtml +31 -31
  62. data/app/views/wiki/revision.rhtml +1 -7
  63. data/app/views/wiki/rollback.rhtml +31 -0
  64. data/app/views/wiki/rss_feed.rhtml +21 -21
  65. data/app/views/wiki/search.rhtml +48 -48
  66. data/app/views/wiki/tex.rhtml +22 -22
  67. data/app/views/wiki/tex_web.rhtml +34 -34
  68. data/app/views/wiki/todo.rhtml +90 -67
  69. data/app/views/wiki/web_list.rhtml +12 -12
  70. data/app/views/wiki_words_help.rhtml +1 -1
  71. data/favicon.png +0 -0
  72. data/libraries/action_controller_servlet.rb +17 -2
  73. data/libraries/bluecloth.rb +1127 -1127
  74. data/libraries/diff/diff.rb +474 -474
  75. data/libraries/diff/diff_test.rb +79 -79
  76. data/libraries/erb.rb +490 -490
  77. data/libraries/madeleine/automatic.rb +418 -357
  78. data/libraries/madeleine/clock.rb +94 -94
  79. data/libraries/madeleine/files.rb +19 -0
  80. data/libraries/madeleine/zmarshal.rb +60 -0
  81. data/libraries/madeleine_service.rb +14 -15
  82. data/libraries/rdocsupport.rb +155 -155
  83. data/libraries/redcloth_for_tex.rb +869 -869
  84. data/libraries/redcloth_for_tex_test.rb +40 -40
  85. data/libraries/view_helper.rb +32 -32
  86. data/libraries/web_controller_server.rb +96 -94
  87. data/pimki.rb +47 -6
  88. metadata +18 -4
@@ -1,4 +1,4 @@
1
- class Author < String
2
- attr_accessor :ip
3
- def initialize(name, ip) @ip = ip; super(name) end
1
+ class Author < String
2
+ attr_accessor :ip
3
+ def initialize(name, ip) @ip = ip; super(name) end
4
4
  end
@@ -11,9 +11,9 @@ module Chunk
11
11
  attr_reader :text, :revision
12
12
 
13
13
  def initialize(match_data, revision) @text = match_data[0]; @revision = revision end
14
- def pre_mask() "chunk#{self.id}start " end
15
- def post_mask() " chunk#{self.id}end" end
16
- def mask(content) "chunk#{self.id}chunk" end
14
+ def pre_mask() "chunk#{self.object_id}start " end
15
+ def post_mask() " chunk#{self.object_id}end" end
16
+ def mask(content) "chunk#{self.object_id}chunk" end
17
17
  def revert(content) content.sub!( Regexp.new(mask(content)), text ) end
18
18
  def unmask(content) self if revert(content) end
19
19
  end
@@ -5,43 +5,40 @@ require 'chunks/chunk'
5
5
  # or RDoc to convert text. This markup occurs when the chunk is required
6
6
  # to mask itself.
7
7
  module Engines
8
- class Textile < Chunk::Abstract
8
+
9
+ class MarkupEngine < Chunk::Abstract
9
10
  def self.pattern() /^(.*)$/m end
11
+ def unmask(content) self end
12
+ end
13
+
14
+ class Textile < MarkupEngine
10
15
  def mask(content)
11
- #RedCloth.new(text,content.options[:engine_opts]).to_html
12
16
  rc = RedCloth.new(text,content.options[:engine_opts])
13
17
  rc.rules = [:textile]
14
18
  rc.to_html
15
19
  end
16
- def unmask(content) self end
17
20
  end
18
21
 
19
- class Markdown < Chunk::Abstract
20
- def self.pattern() /^(.*)$/m end
21
-
22
- if RedCloth::VERSION >= '3.0.0'
23
- def mask(content)
24
- rc = RedCloth.new(text,content.options[:engine_opts])
25
- rc.rules = [:markdown]
26
- rc.to_html
27
- end
28
- else
29
- def mask(content)
30
- BlueCloth.new(text,content.options[:engine_opts]).to_html
31
- end
22
+ class RedMarkdown < MarkupEngine
23
+ def mask(content)
24
+ rc = RedCloth.new(text,content.options[:engine_opts])
25
+ rc.rules = [:markdown]
26
+ rc.to_html
32
27
  end
28
+ end
33
29
 
34
- def unmask(content) self end
30
+ class BlueMarkdown < MarkupEngine
31
+ def mask(content)
32
+ BlueCloth.new(text,content.options[:engine_opts]).to_html
33
+ end
35
34
  end
36
35
 
37
- class RDoc < Chunk::Abstract
38
- def self.pattern() /^(.*)$/m end
36
+ class RDoc < MarkupEngine
39
37
  def mask(content)
40
38
  RDocSupport::RDocFormatter.new(text).to_html
41
39
  end
42
- def unmask(content) self end
43
40
  end
44
41
 
45
- MAP = { :textile => Textile, :markdown => Markdown, :rdoc => RDoc }
42
+ MAP = { :textile => Textile, :red_markdown => RedMarkdown, :blue_markdown => BlueMarkdown, :rdoc => RDoc }
46
43
  end
47
44
 
@@ -1,29 +1,29 @@
1
- require 'chunks/wiki'
2
-
3
- # Includes the contents of another page for rendering.
4
- # The include command looks like this: "[[!include PageName]]".
5
- # It is a WikiLink since it refers to another page (PageName)
6
- # and the wiki content using this command must be notified
7
- # of changes to that page.
8
- # If the included page could not be found, a warning is displayed.
9
- class Include < WikiChunk::WikiLink
10
- def self.pattern() /^\[\[!include(.*)\]\]\s*$/i end
11
-
12
- attr_reader :page_name
13
-
14
- def initialize(match_data, revision)
15
- super(match_data, revision)
16
- @page_name = match_data[1].strip
17
- end
18
-
19
- # This replaces the [[!include PageName]] text with
20
- # the contents of PageName if it exists. Otherwise
21
- # a warning is displayed.
22
- def mask(content)
23
- page = content.web.pages[page_name]
24
- (page ? page.content : "<em>Could not include #{page_name}</em>")
25
- end
26
-
27
- # Keep this chunk regardless of what happens.
28
- def unmask(content) self end
29
- end
1
+ require 'chunks/wiki'
2
+
3
+ # Includes the contents of another page for rendering.
4
+ # The include command looks like this: "[[!include PageName]]".
5
+ # It is a WikiLink since it refers to another page (PageName)
6
+ # and the wiki content using this command must be notified
7
+ # of changes to that page.
8
+ # If the included page could not be found, a warning is displayed.
9
+ class Include < WikiChunk::WikiLink
10
+ def self.pattern() /^\[\[!include(.*)\]\]\s*$/i end
11
+
12
+ attr_reader :page_name
13
+
14
+ def initialize(match_data, revision)
15
+ super(match_data, revision)
16
+ @page_name = match_data[1].strip
17
+ end
18
+
19
+ # This replaces the [[!include PageName]] text with
20
+ # the contents of PageName if it exists. Otherwise
21
+ # a warning is displayed.
22
+ def mask(content)
23
+ page = content.web.pages[page_name]
24
+ (page ? page.content : "<em>Could not include #{page_name}</em>")
25
+ end
26
+
27
+ # Keep this chunk regardless of what happens.
28
+ def unmask(content) self end
29
+ end
@@ -1,20 +1,20 @@
1
- require 'chunks/chunk'
2
-
3
- # These are basic chunks that have a pattern and can be protected.
4
- # They are used by rendering process to prevent wiki rendering
5
- # occuring within literal areas such as <code> and <pre> blocks
6
- # and within HTML tags.
7
- module Literal
8
- # A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
9
- class Pre < Chunk::Abstract
10
- PRE_BLOCKS = "a|pre|code"
11
- def self.pattern() Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?</\1>', Regexp::MULTILINE) end
12
- end
13
-
14
- # A literal chunk that protects HTML tags from wiki rendering.
15
- class Tags < Chunk::Abstract
16
- TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
17
- def self.pattern() Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE) end
18
- end
19
- end
20
-
1
+ require 'chunks/chunk'
2
+
3
+ # These are basic chunks that have a pattern and can be protected.
4
+ # They are used by rendering process to prevent wiki rendering
5
+ # occuring within literal areas such as <code> and <pre> blocks
6
+ # and within HTML tags.
7
+ module Literal
8
+ # A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
9
+ class Pre < Chunk::Abstract
10
+ PRE_BLOCKS = "a|pre|code"
11
+ def self.pattern() Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?</\1>', Regexp::MULTILINE) end
12
+ end
13
+
14
+ # A literal chunk that protects HTML tags from wiki rendering.
15
+ class Tags < Chunk::Abstract
16
+ TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
17
+ def self.pattern() Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE) end
18
+ end
19
+ end
20
+
@@ -1,19 +1,19 @@
1
- # This module is to be included in unit tests that involve matching chunks.
2
- # It provides a easy way to test whether a chunk matches a particular string
3
- # and any the values of any fields that should be set after a match.
4
- module ChunkMatch
5
-
6
- # Asserts a number of tests for the given type and text.
7
- def match(type, test_text, expected)
8
- pattern = type.pattern
9
- assert_match(pattern, test_text)
10
- pattern =~ test_text # Previous assertion guarantees match
11
- chunk = type.new($~)
12
-
13
- # Test if requested parts are correct.
14
- for method_sym, value in expected do
15
- assert_respond_to(chunk, method_sym)
16
- assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
17
- end
18
- end
19
- end
1
+ # This module is to be included in unit tests that involve matching chunks.
2
+ # It provides a easy way to test whether a chunk matches a particular string
3
+ # and any the values of any fields that should be set after a match.
4
+ module ChunkMatch
5
+
6
+ # Asserts a number of tests for the given type and text.
7
+ def match(type, test_text, expected)
8
+ pattern = type.pattern
9
+ assert_match(pattern, test_text)
10
+ pattern =~ test_text # Previous assertion guarantees match
11
+ chunk = type.new($~)
12
+
13
+ # Test if requested parts are correct.
14
+ for method_sym, value in expected do
15
+ assert_respond_to(chunk, method_sym)
16
+ assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
17
+ end
18
+ end
19
+ end
@@ -1,31 +1,31 @@
1
- require 'chunks/chunk'
2
-
3
- # This chunks allows certain parts of a wiki page to be hidden from the
4
- # rest of the rendering pipeline. It should be run at the beginning
5
- # of the pipeline in `wiki_content.rb`.
6
- #
7
- # An example use of this chunk is to markup double brackets or
8
- # auto URI links:
9
- # <nowiki>Here are [[double brackets]] and a URI: www.uri.org</nowiki>
10
- #
11
- # The contents of the chunks will not be processed by any other chunk
12
- # so the `www.uri.org` and the double brackets will appear verbatim.
13
- #
14
- # Author: Mark Reid <mark at threewordslong dot com>
15
- # Created: 8th June 2004
16
- class NoWiki < Chunk::Abstract
17
-
18
- def self.pattern() Regexp.new('<nowiki>(.*?)</nowiki>') end
19
-
20
- attr_reader :plain_text
21
-
22
- def initialize(match_data, revision)
23
- super(match_data, revision)
24
- @plain_text = match_data[1]
25
- end
26
-
27
- # The nowiki content is not unmasked. This means the chunk will be reverted
28
- # using the plain text.
29
- def unmask(content) nil end
30
- def revert(content) content.sub!( Regexp.new(mask(content)), plain_text ) end
31
- end
1
+ require 'chunks/chunk'
2
+
3
+ # This chunks allows certain parts of a wiki page to be hidden from the
4
+ # rest of the rendering pipeline. It should be run at the beginning
5
+ # of the pipeline in `wiki_content.rb`.
6
+ #
7
+ # An example use of this chunk is to markup double brackets or
8
+ # auto URI links:
9
+ # <nowiki>Here are [[double brackets]] and a URI: www.uri.org</nowiki>
10
+ #
11
+ # The contents of the chunks will not be processed by any other chunk
12
+ # so the `www.uri.org` and the double brackets will appear verbatim.
13
+ #
14
+ # Author: Mark Reid <mark at threewordslong dot com>
15
+ # Created: 8th June 2004
16
+ class NoWiki < Chunk::Abstract
17
+
18
+ def self.pattern() Regexp.new('<nowiki>(.*?)</nowiki>') end
19
+
20
+ attr_reader :plain_text
21
+
22
+ def initialize(match_data, revision)
23
+ super(match_data, revision)
24
+ @plain_text = match_data[1]
25
+ end
26
+
27
+ # The nowiki content is not unmasked. This means the chunk will be reverted
28
+ # using the plain text.
29
+ def unmask(content) nil end
30
+ def revert(content) content.sub!( Regexp.new(mask(content)), plain_text ) end
31
+ end
@@ -1,14 +1,14 @@
1
- require 'chunks/nowiki'
2
- require 'chunks/match'
3
- require 'test/unit'
4
-
5
- class NoWikiTest < Test::Unit::TestCase
6
- include ChunkMatch
7
-
8
- def test_simple_nowiki
9
- match(NoWiki, 'This sentence contains <nowiki>[[raw text]]</nowiki>. Do not touch!',
10
- :plain_text => '[[raw text]]'
11
- )
12
- end
13
-
14
- end
1
+ require 'chunks/nowiki'
2
+ require 'chunks/match'
3
+ require 'test/unit'
4
+
5
+ class NoWikiTest < Test::Unit::TestCase
6
+ include ChunkMatch
7
+
8
+ def test_simple_nowiki
9
+ match(NoWiki, 'This sentence contains <nowiki>[[raw text]]</nowiki>. Do not touch!',
10
+ :plain_text => '[[raw text]]'
11
+ )
12
+ end
13
+
14
+ end
@@ -1,18 +1,18 @@
1
- require 'test/unit'
2
-
3
- class ChunkTest < Test::Unit::TestCase
4
-
5
- # Asserts a number of tests for the given type and text.
6
- def match(type, test_text, expected)
7
- pattern = type.pattern
8
- assert_match(pattern, test_text)
9
- pattern =~ test_text # Previous assertion guarantees match
10
- chunk = type.new($~)
11
-
12
- # Test if requested parts are correct.
13
- for method_sym, value in expected do
14
- assert_respond_to(chunk, method_sym)
15
- assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
16
- end
17
- end
18
- end
1
+ require 'test/unit'
2
+
3
+ class ChunkTest < Test::Unit::TestCase
4
+
5
+ # Asserts a number of tests for the given type and text.
6
+ def match(type, test_text, expected)
7
+ pattern = type.pattern
8
+ assert_match(pattern, test_text)
9
+ pattern =~ test_text # Previous assertion guarantees match
10
+ chunk = type.new($~)
11
+
12
+ # Test if requested parts are correct.
13
+ for method_sym, value in expected do
14
+ assert_respond_to(chunk, method_sym)
15
+ assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
16
+ end
17
+ end
18
+ end
@@ -1,23 +1,44 @@
1
- require 'chunks/wiki'
2
- require 'date'
3
- require 'parsedate'
4
-
5
- # ToDo items.
6
- class Todo < Chunk::Abstract
7
- def self.pattern() /todo: (.*?)(?=<br|\r|\n|\z)/i end
8
-
9
- def initialize(match_data, revision)
10
- super(match_data, revision)
11
- @text = match_data[1]
12
- end
13
-
14
- def escaped_text() nil end
15
-
16
- def unmask(content)
17
- return self if content.gsub!( Regexp.new(mask(content)),
18
- # the style 'todo' is bright-red to be eye catching. It is not expected that
19
- # there will be too many items on one page, but each is supposed to stand out.
20
- # The ToDo special page differentiates between the 'todo' and 'todoFuture' styles.
21
- "<span class=\"todo\"><strong>TODO:</strong> #{@text}</span>" )
22
- end
23
- end
1
+ require 'chunks/wiki'
2
+ require 'date'
3
+ require 'parsedate'
4
+
5
+ # ToDo items.
6
+ class Todo < Chunk::Abstract
7
+ def self.pattern() /todo(@[\w,]+)?: (.*?)(?=<br|\r|\n|\z)/i end
8
+
9
+ attr_accessor :context, :due_date
10
+
11
+ def initialize(match_data, revision)
12
+ super(match_data, revision)
13
+ @context = match_data[1]
14
+ @context = @context.nil? || @context.empty? ? [] : @context.delete('@').split(',')
15
+ @text = match_data[2]
16
+ begin
17
+ d = ParseDate.parsedate(@text)
18
+ # see if there's a date in the todo:
19
+ if not d.all? { |x| x.nil? }
20
+ d = d[0..2]
21
+ # sanity check the order retured from ParseDate: stuff like 'Jan 2005'
22
+ # will be returned in inverse order to '12 Jan 2005'. (ie. no automatic bounds checking)
23
+ d.reverse! if d[2] > 31
24
+ # get the [year,month,date] with sane values if you miss the day/year.
25
+ # this should allow users to specify stuff like 'Dec 2005' or 'Dec 21'.
26
+ d = [ d[0] || Date.today.year, d[1], d[2] || 1 ]
27
+ @due_date = Date.new(*d)
28
+ end
29
+ rescue => detail
30
+ p ['==>', detail, @text]
31
+ @due_date = nil
32
+ end
33
+ end
34
+
35
+ def escaped_text() nil end
36
+
37
+ def unmask(content)
38
+ return self if content.gsub!( Regexp.new(mask(content)),
39
+ # the style 'todo' is bright-red to be eye catching. It is not expected that
40
+ # there will be too many items on one page, but each is supposed to stand out.
41
+ # The ToDo special page differentiates between the 'todo' and 'todoFuture' styles.
42
+ "<todo-tag context='#{@context.join(',')}' due_date='#{@due_date}'><span class=\"todo\"><strong>TODO#{ " @ #{@context.join(', ')}" unless @context.empty?}:</strong> #{@text}</span></todo-tag>" )
43
+ end
44
+ end
@@ -1,97 +1,97 @@
1
- require 'chunks/chunk'
2
-
3
- # This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI modules.
4
- # It parses out a variety of fields that could be used by renderers to format
5
- # the links in various ways (shortening domain names, hiding email addresses)
6
- # It matches email addresses and host.com.au domains without schemes (http://)
7
- # but adds these on as required.
8
- #
9
- # The heuristic used to match a URI is designed to err on the side of caution.
10
- # That is, it is more likely to not autolink a URI than it is to accidently
11
- # autolink something that is not a URI. The reason behind this is it is easier
12
- # to force a URI link by prefixing 'http://' to it than it is to escape and
13
- # incorrectly marked up non-URI.
14
- #
15
- # I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name suffixes.
16
- # The generic names are from www.bnoack.com/data/countrycode2.html)
17
- # [iso3166]: http://geotags.com/iso3166/
18
- class URIChunk < Chunk::Abstract
19
- include URI::REGEXP::PATTERN
20
-
21
- GENERIC = '(?:aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org)'
22
- COUNTRY = '(?:au|at|be|ca|ch|de|dk|fr|hk|in|ir|it|jp|nl|no|pt|ru|se|sw|tv|tw|uk|us)'
23
-
24
- # These are needed otherwise HOST will match almost anything
25
- TLDS = "\\.(?:#{GENERIC}|#{COUNTRY})\b"
26
-
27
- # Redefine USERINFO so that it must have non-zero length
28
- USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+"
29
-
30
- # Pattern of legal URI endings to stop interference with some Textile
31
- # markup. (Images: !URI!) and other punctuation eg, (http://wiki.com/)
32
- URI_ENDING = '[)!]'
33
-
34
- # The basic URI expression as a string
35
- URI_PATTERN =
36
- "(?:(#{SCHEME})://)?" + # Optional scheme:// (\1|\8)
37
- "(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2|\9)
38
- "(#{HOSTNAME}#{TLDS})" + # Mandatory host eg, HOST.com.au (\3|\10)
39
- "(?::(#{PORT}))?" + # Optional :port (\4|\11)
40
- "(#{ABS_PATH})?" + # Optional absolute path (\5|\12)
41
- "(?:\\?(#{QUERY}))?" + # Optional ?query (\6|\13)
42
- "(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7|\14)
43
-
44
- def self.pattern()
45
- # This pattern first tries to match the URI_PATTERN that ends with
46
- # punctuation that is a valid URI character (eg, ')', '!'). If
47
- # such a match occurs, there should be no backtracking (hence the ?> ).
48
- # If the string cannot match a URI ending with URI_ENDING, then a second
49
- # attempt is tried.
50
- Regexp.new("(?>#{URI_PATTERN}(?=#{URI_ENDING}))|#{URI_PATTERN}", Regexp::EXTENDED, 'N')
51
- end
52
-
53
- attr_reader :uri, :scheme, :user, :host, :port, :path, :query, :fragment, :link_text
54
-
55
- def initialize(match_data, revision)
56
- super(match_data, revision)
57
- # Since the URI_PATTERN is tried twice, there are two sets of
58
- # groups, one from \1 to \7 and the second from \8 to \14.
59
- # The fields are set by which ever group matches.
60
- @scheme = match_data[1] || match_data[8]
61
- @user = match_data[2] || match_data[9]
62
- @host = match_data[3] || match_data[10]
63
- @port = match_data[4] || match_data[11]
64
- @path = match_data[5] || match_data[12]
65
- @query = match_data[6] || match_data[13]
66
- @fragment = match_data[7] || match_data[14]
67
-
68
- # If there is no scheme, add an appropriate one, otherwise
69
- # set the URI to the matched text.
70
- @text_scheme = scheme
71
- @uri = (scheme ? match_data[0] : nil )
72
- @scheme = scheme || ( user ? 'mailto' : 'http' )
73
- @delimiter = ( scheme == 'mailto' ? ':' : '://' )
74
- @uri ||= scheme + @delimiter + match_data[0]
75
-
76
- # Build up the link text. Schemes are omitted unless explicitly given.
77
- @link_text = ''
78
- @link_text << "#{@scheme}#{@delimiter}" if @text_scheme
79
- @link_text << "#{@user}@" if @user
80
- @link_text << "#{@host}" if @host
81
- @link_text << ":#{@port}" if @port
82
- @link_text << "#{@path}" if @path
83
- @link_text << "?#{@query}" if @query
84
- end
85
-
86
- # If the text should be escaped then don't keep this chunk.
87
- # Otherwise only keep this chunk if it was substituted back into the
88
- # content.
89
- def unmask(content)
90
- return nil if escaped_text
91
- return self if content.sub!( Regexp.new(mask(content)), "<a href=\"#{uri}\">#{link_text}</a>" )
92
- end
93
-
94
- # If there is no hostname in the URI, do not render it
95
- # It's probably only contains the scheme, eg 'something:'
96
- def escaped_text() ( host.nil? ? @uri : nil ) end
97
- end
1
+ require 'chunks/chunk'
2
+
3
+ # This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI modules.
4
+ # It parses out a variety of fields that could be used by renderers to format
5
+ # the links in various ways (shortening domain names, hiding email addresses)
6
+ # It matches email addresses and host.com.au domains without schemes (http://)
7
+ # but adds these on as required.
8
+ #
9
+ # The heuristic used to match a URI is designed to err on the side of caution.
10
+ # That is, it is more likely to not autolink a URI than it is to accidently
11
+ # autolink something that is not a URI. The reason behind this is it is easier
12
+ # to force a URI link by prefixing 'http://' to it than it is to escape and
13
+ # incorrectly marked up non-URI.
14
+ #
15
+ # I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name suffixes.
16
+ # The generic names are from www.bnoack.com/data/countrycode2.html)
17
+ # [iso3166]: http://geotags.com/iso3166/
18
+ class URIChunk < Chunk::Abstract
19
+ include URI::REGEXP::PATTERN
20
+
21
+ GENERIC = '(?:aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org)'
22
+ COUNTRY = '(?:au|at|be|ca|ch|de|dk|fr|hk|in|ir|it|jp|nl|no|pt|ru|se|sw|tv|tw|uk|us)'
23
+
24
+ # These are needed otherwise HOST will match almost anything
25
+ TLDS = "\\.(?:#{GENERIC}|#{COUNTRY})\b"
26
+
27
+ # Redefine USERINFO so that it must have non-zero length
28
+ USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+"
29
+
30
+ # Pattern of legal URI endings to stop interference with some Textile
31
+ # markup. (Images: !URI!) and other punctuation eg, (http://wiki.com/)
32
+ URI_ENDING = '[)!]'
33
+
34
+ # The basic URI expression as a string
35
+ URI_PATTERN =
36
+ "(?:(#{SCHEME})://)?" + # Optional scheme:// (\1|\8)
37
+ "(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2|\9)
38
+ "(#{HOSTNAME}#{TLDS})" + # Mandatory host eg, HOST.com.au (\3|\10)
39
+ "(?::(#{PORT}))?" + # Optional :port (\4|\11)
40
+ "(#{ABS_PATH})?" + # Optional absolute path (\5|\12)
41
+ "(?:\\?(#{QUERY}))?" + # Optional ?query (\6|\13)
42
+ "(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7|\14)
43
+
44
+ def self.pattern()
45
+ # This pattern first tries to match the URI_PATTERN that ends with
46
+ # punctuation that is a valid URI character (eg, ')', '!'). If
47
+ # such a match occurs, there should be no backtracking (hence the ?> ).
48
+ # If the string cannot match a URI ending with URI_ENDING, then a second
49
+ # attempt is tried.
50
+ Regexp.new("(?>#{URI_PATTERN}(?=#{URI_ENDING}))|#{URI_PATTERN}", Regexp::EXTENDED, 'N')
51
+ end
52
+
53
+ attr_reader :uri, :scheme, :user, :host, :port, :path, :query, :fragment, :link_text
54
+
55
+ def initialize(match_data, revision)
56
+ super(match_data, revision)
57
+ # Since the URI_PATTERN is tried twice, there are two sets of
58
+ # groups, one from \1 to \7 and the second from \8 to \14.
59
+ # The fields are set by which ever group matches.
60
+ @scheme = match_data[1] || match_data[8]
61
+ @user = match_data[2] || match_data[9]
62
+ @host = match_data[3] || match_data[10]
63
+ @port = match_data[4] || match_data[11]
64
+ @path = match_data[5] || match_data[12]
65
+ @query = match_data[6] || match_data[13]
66
+ @fragment = match_data[7] || match_data[14]
67
+
68
+ # If there is no scheme, add an appropriate one, otherwise
69
+ # set the URI to the matched text.
70
+ @text_scheme = scheme
71
+ @uri = (scheme ? match_data[0] : nil )
72
+ @scheme = scheme || ( user ? 'mailto' : 'http' )
73
+ @delimiter = ( scheme == 'mailto' ? ':' : '://' )
74
+ @uri ||= scheme + @delimiter + match_data[0]
75
+
76
+ # Build up the link text. Schemes are omitted unless explicitly given.
77
+ @link_text = ''
78
+ @link_text << "#{@scheme}#{@delimiter}" if @text_scheme
79
+ @link_text << "#{@user}@" if @user
80
+ @link_text << "#{@host}" if @host
81
+ @link_text << ":#{@port}" if @port
82
+ @link_text << "#{@path}" if @path
83
+ @link_text << "?#{@query}" if @query
84
+ end
85
+
86
+ # If the text should be escaped then don't keep this chunk.
87
+ # Otherwise only keep this chunk if it was substituted back into the
88
+ # content.
89
+ def unmask(content)
90
+ return nil if escaped_text
91
+ return self if content.sub!( Regexp.new(mask(content)), "<a href=\"#{uri}\">#{link_text}</a>" )
92
+ end
93
+
94
+ # If there is no hostname in the URI, do not render it
95
+ # It's probably only contains the scheme, eg 'something:'
96
+ def escaped_text() ( host.nil? ? @uri : nil ) end
97
+ end