Pimki 1.3.092 → 1.4.092

Sign up to get free protection for your applications and to get access to all the features.
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