motiro 0.6.8 → 0.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (206) hide show
  1. data/AUTHORS +15 -0
  2. data/README +2 -2
  3. data/README.en +1 -2
  4. data/README.pt-br +1 -2
  5. data/app/controllers/account_controller.rb +1 -1
  6. data/app/controllers/application.rb +13 -12
  7. data/app/controllers/javascript_controller.rb +1 -1
  8. data/app/controllers/report_controller.rb +1 -1
  9. data/app/controllers/wiki_controller.rb +3 -5
  10. data/app/core/cache_reporter.rb +1 -1
  11. data/app/core/cache_reporter_fetcher.rb +1 -1
  12. data/app/core/chief_editor.rb +1 -1
  13. data/app/core/reporter.rb +1 -1
  14. data/app/core/reporter_driver.rb +1 -1
  15. data/app/core/reporter_fetcher.rb +1 -1
  16. data/app/core/settings.rb +1 -1
  17. data/app/core/version.rb +1 -1
  18. data/app/core/wiki_reporter.rb +1 -1
  19. data/app/helpers/application_helper.rb +5 -1
  20. data/app/helpers/default_page_provider.rb +6 -2
  21. data/app/helpers/report_helper.rb +14 -1
  22. data/app/models/change.rb +1 -1
  23. data/app/models/chunk.rb +1 -1
  24. data/app/models/feed_observer.rb +1 -1
  25. data/app/models/headline.rb +1 -1
  26. data/app/models/page.rb +38 -4
  27. data/app/models/revision.rb +1 -1
  28. data/app/models/user.rb +1 -1
  29. data/app/models/wiki_reference.rb +21 -0
  30. data/app/models/wiki_sweeper.rb +17 -2
  31. data/app/ports/chdir_runner.rb +1 -1
  32. data/app/ports/runner.rb +1 -1
  33. data/app/reporters/darcs_connection.rb +1 -1
  34. data/app/reporters/darcs_reporter.rb +1 -1
  35. data/app/reporters/darcs_temp_repo.rb +1 -1
  36. data/app/reporters/events_reporter.rb +1 -1
  37. data/app/reporters/features_reporter.rb +1 -1
  38. data/app/reporters/subversion_reporter.rb +1 -1
  39. data/app/reporters/svn_connection.rb +1 -1
  40. data/app/reporters/svn_settings.rb +1 -1
  41. data/app/views/report/list.rhtml +6 -7
  42. data/app/views/report/older.rhtml +1 -2
  43. data/app/views/report/rss.rxml +1 -1
  44. data/app/views/report/show.rhtml +2 -2
  45. data/app/views/wiki/_edit_event.rhtml +2 -0
  46. data/app/views/wiki/_edit_feature.rhtml +2 -0
  47. data/app/views/wiki/_properties_edit.rhtml +3 -5
  48. data/app/views/wiki/_properties_show.rhtml +4 -5
  49. data/app/views/wiki/_show_event.rhtml +3 -0
  50. data/app/views/wiki/_show_feature.rhtml +2 -0
  51. data/app/views/wiki/show.rhtml +1 -1
  52. data/bin/motiro +3 -3
  53. data/config/routes.rb +1 -1
  54. data/db/migrate/024_add_feature_status.rb +11 -0
  55. data/db/migrate/025_add_page_references.rb +12 -0
  56. data/db/migrate/026_convert_link_syntax.rb +12 -0
  57. data/db/migrate/027_register_page_references.rb +12 -0
  58. data/db/motirodb.sqlite.initial +0 -0
  59. data/db/schema_version +1 -1
  60. data/db/translation/pt-BR.rb +7 -1
  61. data/lib/array_extensions.rb +1 -1
  62. data/lib/diff_chunk_builder.rb +1 -1
  63. data/lib/differ.rb +1 -1
  64. data/lib/string_extensions.rb +13 -6
  65. data/lib/stub_hash.rb +1 -1
  66. data/lib/tasks/packaging.rake +10 -12
  67. data/lib/tasks/testing.rake +1 -1
  68. data/lib/tick_daemon.rb +1 -1
  69. data/lib/{wiki_url_generator.rb → wiki_link_handler.rb} +13 -3
  70. data/lib/wiki_renderer.rb +63 -34
  71. data/public/images/done.png +0 -0
  72. data/public/images/not-done.png +0 -0
  73. data/public/stylesheets/motiro.css +8 -0
  74. data/script/ticker +1 -1
  75. data/test/acceptance/account_test.rb +1 -1
  76. data/test/acceptance/darcs_test.rb +1 -1
  77. data/test/acceptance/events_test.rb +1 -1
  78. data/test/acceptance/main_page_test.rb +1 -1
  79. data/test/acceptance/subversion_test.rb +1 -1
  80. data/test/acceptance/ts_all_suites.rb +1 -1
  81. data/test/acceptance/wiki_test.rb +3 -3
  82. data/test/contract/darcs_test.rb +1 -1
  83. data/test/contract/remote_darcs_test.rb +1 -1
  84. data/test/contract/svn_test.rb +1 -1
  85. data/test/fixtures/pages.yml +20 -1
  86. data/test/fixtures/revisions.yml +40 -1
  87. data/test/fixtures/wiki_references.yml +5 -0
  88. data/test/functional/report_controller_test.rb +1 -1
  89. data/test/functional/report_features_test.rb +8 -1
  90. data/test/functional/report_subversion_test.rb +1 -1
  91. data/test/functional/root_controller_test.rb +1 -1
  92. data/test/functional/wiki_controller_test.rb +67 -2
  93. data/test/lib/acceptance_test_case.rb +1 -1
  94. data/test/lib/darcs_excerpts.rb +1 -1
  95. data/test/lib/darcs_repo.rb +1 -1
  96. data/test/lib/hash_extensions.rb +22 -0
  97. data/test/lib/live_mode_test.rb +1 -1
  98. data/test/lib/netutils.rb +1 -1
  99. data/test/lib/platform_thread.rb +1 -1
  100. data/test/lib/selenium_extensions.rb +1 -1
  101. data/test/lib/stubio.rb +1 -1
  102. data/test/lib/webserver.rb +1 -1
  103. data/test/meta/darcs_repo_test.rb +1 -1
  104. data/test/meta/local_svn_test.rb +1 -1
  105. data/test/meta/platform_thread_test.rb +1 -1
  106. data/test/meta/stubio_test.rb +1 -1
  107. data/test/stubs/{url_generator.rb → wiki_link_handler.rb} +8 -4
  108. data/test/test_helper.rb +3 -1
  109. data/test/unit/array_extensions_test.rb +1 -1
  110. data/test/unit/cache_reporter_fetcher_test.rb +1 -1
  111. data/test/unit/cache_reporter_test.rb +1 -1
  112. data/test/unit/change_test.rb +1 -1
  113. data/test/unit/chdir_runner_test.rb +1 -1
  114. data/test/unit/chief_editor_test.rb +1 -1
  115. data/test/unit/darcs_connection_test.rb +1 -1
  116. data/test/unit/darcs_reporter_test.rb +4 -4
  117. data/test/unit/darcs_temp_repo_test.rb +1 -1
  118. data/test/unit/default_page_provider_test.rb +23 -13
  119. data/test/unit/diff_chunk_builder_test.rb +1 -1
  120. data/test/unit/page_test.rb +67 -4
  121. data/test/unit/reporter_driver_test.rb +1 -1
  122. data/test/unit/reporter_test.rb +1 -1
  123. data/test/unit/revision_test.rb +1 -1
  124. data/test/unit/runner_test.rb +1 -1
  125. data/test/unit/string_extensions_test.rb +15 -3
  126. data/test/unit/svn_connection_test.rb +1 -1
  127. data/test/unit/svn_reporter_interaction_test.rb +1 -1
  128. data/test/unit/svn_reporter_test.rb +1 -1
  129. data/test/unit/svn_settings_test.rb +1 -1
  130. data/test/unit/user_test.rb +1 -1
  131. data/test/unit/{wiki_url_generator_test.rb → wiki_link_handler_test.rb} +3 -3
  132. data/test/unit/wiki_renderer_test.rb +75 -20
  133. data/test/unit/wiki_reporter_test.rb +1 -1
  134. data/vendor/mediacloth-trunk/MIT-LICENSE +20 -0
  135. data/vendor/mediacloth-trunk/README +32 -0
  136. data/vendor/mediacloth-trunk/Rakefile +33 -0
  137. data/vendor/mediacloth-trunk/lib/mediacloth/mediawikiast.rb +122 -0
  138. data/vendor/mediacloth-trunk/lib/mediacloth/mediawikihtmlgenerator.rb +252 -0
  139. data/vendor/mediacloth-trunk/lib/mediacloth/mediawikilexer.rb +821 -0
  140. data/vendor/mediacloth-trunk/lib/mediacloth/mediawikilinkhandler.rb +68 -0
  141. data/vendor/mediacloth-trunk/lib/mediacloth/mediawikiparams.rb +33 -0
  142. data/vendor/mediacloth-trunk/lib/mediacloth/mediawikiparser.rb +1218 -0
  143. data/vendor/mediacloth-trunk/lib/mediacloth/mediawikiparser.y +493 -0
  144. data/vendor/mediacloth-trunk/lib/mediacloth/mediawikiwalker.rb +146 -0
  145. data/vendor/mediacloth-trunk/lib/mediacloth.rb +29 -0
  146. data/vendor/mediacloth-trunk/mediacloth.gemspec +24 -0
  147. data/vendor/mediacloth-trunk/mediacloth.kdevelop +117 -0
  148. data/vendor/mediacloth-trunk/setup.rb +1585 -0
  149. data/vendor/mediacloth-trunk/test/data/html1 +26 -0
  150. data/vendor/mediacloth-trunk/test/data/html10 +130 -0
  151. data/vendor/mediacloth-trunk/test/data/html11 +17 -0
  152. data/vendor/mediacloth-trunk/test/data/html12 +12 -0
  153. data/vendor/mediacloth-trunk/test/data/html13 +11 -0
  154. data/vendor/mediacloth-trunk/test/data/html2 +2 -0
  155. data/vendor/mediacloth-trunk/test/data/html3 +1 -0
  156. data/vendor/mediacloth-trunk/test/data/html4 +47 -0
  157. data/vendor/mediacloth-trunk/test/data/html5 +57 -0
  158. data/vendor/mediacloth-trunk/test/data/html6 +8 -0
  159. data/vendor/mediacloth-trunk/test/data/html7 +45 -0
  160. data/vendor/mediacloth-trunk/test/data/html8 +1 -0
  161. data/vendor/mediacloth-trunk/test/data/html9 +14 -0
  162. data/vendor/mediacloth-trunk/test/data/input1 +34 -0
  163. data/vendor/mediacloth-trunk/test/data/input10 +124 -0
  164. data/vendor/mediacloth-trunk/test/data/input11 +17 -0
  165. data/vendor/mediacloth-trunk/test/data/input12 +15 -0
  166. data/vendor/mediacloth-trunk/test/data/input13 +10 -0
  167. data/vendor/mediacloth-trunk/test/data/input2 +2 -0
  168. data/vendor/mediacloth-trunk/test/data/input3 +2 -0
  169. data/vendor/mediacloth-trunk/test/data/input4 +50 -0
  170. data/vendor/mediacloth-trunk/test/data/input5 +63 -0
  171. data/vendor/mediacloth-trunk/test/data/input6 +8 -0
  172. data/vendor/mediacloth-trunk/test/data/input7 +45 -0
  173. data/vendor/mediacloth-trunk/test/data/input8 +1 -0
  174. data/vendor/mediacloth-trunk/test/data/input9 +14 -0
  175. data/vendor/mediacloth-trunk/test/data/lex1 +26 -0
  176. data/vendor/mediacloth-trunk/test/data/lex10 +85 -0
  177. data/vendor/mediacloth-trunk/test/data/lex11 +17 -0
  178. data/vendor/mediacloth-trunk/test/data/lex12 +15 -0
  179. data/vendor/mediacloth-trunk/test/data/lex13 +3 -0
  180. data/vendor/mediacloth-trunk/test/data/lex2 +2 -0
  181. data/vendor/mediacloth-trunk/test/data/lex3 +1 -0
  182. data/vendor/mediacloth-trunk/test/data/lex4 +47 -0
  183. data/vendor/mediacloth-trunk/test/data/lex5 +57 -0
  184. data/vendor/mediacloth-trunk/test/data/lex6 +8 -0
  185. data/vendor/mediacloth-trunk/test/data/lex7 +45 -0
  186. data/vendor/mediacloth-trunk/test/data/lex8 +1 -0
  187. data/vendor/mediacloth-trunk/test/data/lex9 +14 -0
  188. data/vendor/mediacloth-trunk/test/data/result1 +48 -0
  189. data/vendor/mediacloth-trunk/test/dataproducers/html.rb +18 -0
  190. data/vendor/mediacloth-trunk/test/dataproducers/lex.rb +15 -0
  191. data/vendor/mediacloth-trunk/test/debugwalker.rb +68 -0
  192. data/vendor/mediacloth-trunk/test/htmlgenerator.rb +97 -0
  193. data/vendor/mediacloth-trunk/test/lexer.rb +542 -0
  194. data/vendor/mediacloth-trunk/test/linkhandler.rb +39 -0
  195. data/vendor/mediacloth-trunk/test/parser.rb +22 -0
  196. data/vendor/mediacloth-trunk/test/testhelper.rb +27 -0
  197. data/vendor/mediacloth.rb +3 -0
  198. data/vendor/motiro-installer.rb +1 -1
  199. data/vendor/plugins/cache_test-0.2/CHANGELOG +14 -0
  200. data/vendor/plugins/cache_test-0.2/MIT-LICENSE +20 -0
  201. data/vendor/plugins/cache_test-0.2/README +124 -0
  202. data/vendor/plugins/cache_test-0.2/init.rb +8 -0
  203. data/vendor/plugins/cache_test-0.2/lib/fragment_cache_test.rb +205 -0
  204. data/vendor/plugins/cache_test-0.2/lib/page_cache_test.rb +140 -0
  205. data/vendor/plugins/global_routing/init.rb +1 -1
  206. metadata +517 -455
@@ -0,0 +1,821 @@
1
+ require 'strscan'
2
+
3
+ class String
4
+ def is_empty_token?
5
+ self.size == 0 or self == "\n" or self == "\r\n"
6
+ end
7
+ end
8
+
9
+
10
+ class MediaWikiLexer
11
+
12
+ INLINE_ELEMENTS = [:LINK, :INTLINK, :BOLD, :ITALIC]
13
+ BLOCK_ELEMENTS = [:PARA, :PRE, :UL, :OL, :DL, :LI, :SECTION, :TABLE, :ROW, :CELL, :HEAD]
14
+ PARA_BREAK_ELEMENTS = [:UL, :OL, :DL, :PRE, :SECTION, :TABLE, :HLINE, :KEYWORD]
15
+
16
+ NAME_CHAR_TABLE = (0 .. 255).collect{|n| n.chr =~ /[a-zA-Z0-9_\-]/ ? true : false}
17
+ TOKEN_CHAR_TABLE = (0 .. 255).collect{|n| n.chr =~ /[a-zA-Z0-9_\-.;?&~=#%\/]/ ? true : false}
18
+
19
+
20
+ def initialize
21
+ # Current position in token list
22
+ @position = 0
23
+
24
+ # Lexer table of methods that handle only formatting, e.g. bold or italicized
25
+ # text; or spans of XHTML, or wiki-escape, markup
26
+ @formatting_lexer_table = {}
27
+ @formatting_lexer_table["'"] = method(:match_quote)
28
+ @formatting_lexer_table["<"] = method(:match_right_angle)
29
+ @formatting_lexer_table["&"] = method(:match_ampersand)
30
+ @formatting_lexer_table["{"] = method(:match_left_curly)
31
+
32
+ # Lexer table of methods that handle everything that may occur in-line in
33
+ # addition to formatting, i.e. links and signatures
34
+ @inline_lexer_table = @formatting_lexer_table.dup
35
+ @inline_lexer_table["["] = method(:match_left_square)
36
+ @inline_lexer_table["~"] = method(:match_tilde)
37
+ @inline_lexer_table["h"] = method(:match_h_char)
38
+
39
+ # Default lexer table, which includes all in-line formatting and links, plus
40
+ # methods that handle constructs that begin on a newline
41
+ @default_lexer_table = @inline_lexer_table.dup
42
+ @default_lexer_table[" "] = method(:match_space)
43
+ @default_lexer_table["="] = method(:match_equal)
44
+ @default_lexer_table["*"] = method(:match_star)
45
+ @default_lexer_table["#"] = method(:match_hash)
46
+ @default_lexer_table[":"] = method(:match_colon)
47
+ @default_lexer_table[";"] = method(:match_semicolon)
48
+ @default_lexer_table["-"] = method(:match_dash)
49
+ @default_lexer_table["_"] = method(:match_underscore)
50
+ @default_lexer_table["\n"] = method(:match_newline)
51
+ @default_lexer_table["\r"] = method(:match_newline)
52
+
53
+ # Lexer table used inside spans of markup, wherein spans of newlines are not
54
+ # automatically treated as paragraphs.
55
+ @markup_lexer_table = @default_lexer_table.dup
56
+ @markup_lexer_table["\n"] = nil
57
+ @markup_lexer_table["\r"] = nil
58
+
59
+ # Lexer table used inside of headings
60
+ @heading_lexer_table = @inline_lexer_table.dup
61
+ @heading_lexer_table["="] = method(:match_equal_in_heading)
62
+ @heading_lexer_table["\n"] = method(:match_newline_in_heading)
63
+
64
+ # Lexer table used inside the left half of an external link
65
+ @link_lexer_table = {}
66
+ @link_lexer_table["]"] = method(:match_right_square_in_link)
67
+ @link_lexer_table["\n"] = method(:match_newline_in_link)
68
+ @link_lexer_table["\r"] = method(:match_newline_in_link)
69
+ @link_lexer_table[" "] = method(:match_space_in_link)
70
+
71
+ # Lexer table used inside the right half of an external link, or the right
72
+ # half of an internal link
73
+ @link_opt_lexer_table = @inline_lexer_table.dup
74
+ @link_opt_lexer_table["]"] = method(:match_right_square_in_link)
75
+ @link_opt_lexer_table["\n"] = method(:match_newline_in_link)
76
+ @link_opt_lexer_table["\r"] = method(:match_newline_in_link)
77
+
78
+ # Lexer table used inside the left half of an internal link or internal
79
+ # resource link
80
+ @intlink_lexer_table = {}
81
+ @intlink_lexer_table["]"] = method(:match_right_square_in_intlink)
82
+ @intlink_lexer_table["\r"] = method(:match_newline_in_intlink)
83
+ @intlink_lexer_table["\n"] = method(:match_newline_in_intlink)
84
+ @intlink_lexer_table[":"] = method(:match_colon_in_intlink)
85
+ @intlink_lexer_table["|"] = method(:match_pipe_in_intlink)
86
+
87
+ # Lexer table used inside the right half of an internal link
88
+ @intlink_opt_lexer_table = @formatting_lexer_table.dup
89
+ @intlink_opt_lexer_table["]"] = method(:match_right_square_in_intlink)
90
+ @intlink_opt_lexer_table["\n"] = method(:match_newline_in_intlink)
91
+ @intlink_opt_lexer_table["\r"] = method(:match_newline_in_intlink)
92
+
93
+ # Lexer table used inside the right half of an internal resource link
94
+ @resourcelink_opt_lexer_table = @inline_lexer_table.dup
95
+ @resourcelink_opt_lexer_table["]"] = method(:match_right_square_in_intlink)
96
+ @resourcelink_opt_lexer_table["\n"] = method(:match_newline_in_intlink)
97
+ @resourcelink_opt_lexer_table["\r"] = method(:match_newline_in_intlink)
98
+ @resourcelink_opt_lexer_table["|"] = method(:match_pipe_in_intlink)
99
+
100
+ # Lexer table used to parse tables
101
+ @table_lexer_table = @inline_lexer_table.dup
102
+ @table_lexer_table["*"] = method(:match_star)
103
+ @table_lexer_table["#"] = method(:match_hash)
104
+ @table_lexer_table["|"] = method(:match_pipe_in_table)
105
+ @table_lexer_table["!"] = method(:match_bang_in_table)
106
+ @table_lexer_table["{"] = method(:match_left_curly)
107
+
108
+ # Lexer table used to parse ordered and unordered list items (which may nest)
109
+ @items_lexer_table = @inline_lexer_table.dup
110
+ @items_lexer_table["\n"] = method(:match_newline_in_items)
111
+
112
+ # Lexer table used to parse entries in a definition list (which may not nest)
113
+ @entries_lexer_table = @inline_lexer_table.dup
114
+ @entries_lexer_table["\n"] = method(:match_newline_in_entries)
115
+ @entries_lexer_table[":"] = method(:match_colon_in_entries)
116
+
117
+ # Lexer table used inside spans of pre-formatted text
118
+ @pre_lexer_table = @inline_lexer_table.dup
119
+ @pre_lexer_table["\n"] = method(:match_newline_in_pre)
120
+
121
+ # Lexer table used when inside spans of wiki-escaped text
122
+ @escape_lexer_table = {}
123
+ @escape_lexer_table["<"] = method(:match_right_angle_in_tag)
124
+
125
+ # Lexer table used when inside a wiki variable reference
126
+ @variable_lexer_table = {}
127
+ @variable_lexer_table["}"] = method(:match_right_curly_in_variable)
128
+
129
+ # Begin lexing in default state
130
+ @lexer_table = LexerTable.new
131
+ @lexer_table.push(@default_lexer_table)
132
+ end
133
+
134
+
135
+ def tokenize(input)
136
+ @text = input
137
+ # Current position in the input text
138
+ @cursor = 0
139
+ # Tokens to be returned
140
+ @tokens = []
141
+ # Stack of open token spans
142
+ @context = []
143
+ # Already lexed character data, not yet added to a TEXT token
144
+ @pending = ''
145
+ # List symbols from the most recent line item of a list, e.g. '***'
146
+ @list = ''
147
+
148
+ start_span(:PARA)
149
+
150
+ while (@cursor < @text.length)
151
+ @char = @text[@cursor, 1]
152
+ if @lexer_table[@char]
153
+ @lexer_table[@char].call
154
+ else
155
+ @pending += @char
156
+ @cursor += 1
157
+ end
158
+ end
159
+
160
+ if @pending.is_empty_token?
161
+ if @context.size > 0 and @tokens.last[0] == :PARA_START
162
+ @context.pop
163
+ @tokens.pop
164
+ end
165
+ else
166
+ @tokens << [:TEXT, @pending]
167
+ @pending = ''
168
+ end
169
+ while(@context.size > 0) do
170
+ @tokens << [(@context.pop.to_s + '_END').to_sym, '']
171
+ end
172
+ @tokens << [false, false]
173
+ @tokens
174
+
175
+ end
176
+
177
+ #Returns the next token from the stream. Useful for RACC parsers.
178
+ def lex
179
+ token = @tokens[@position]
180
+ @position += 1
181
+ return token
182
+ end
183
+
184
+
185
+ private
186
+
187
+ def match_text
188
+ @pending += @char
189
+ @cursor += 1
190
+ end
191
+
192
+ def match_ampersand
193
+ i = @cursor + 1
194
+ i += 1 while i < @text.size and NAME_CHAR_TABLE[@text[i]]
195
+ if @text[i, 1] == ';'
196
+ append_to_tokens([:CHAR_ENT, @text[(@cursor + 1) ... i]])
197
+ @cursor = i + 1
198
+ else
199
+ match_text
200
+ end
201
+ end
202
+
203
+ def match_quote
204
+ if @text[@cursor, 5] == "'''''"
205
+ if @context.last == :BOLD
206
+ match_bold
207
+ @cursor += 3
208
+ else
209
+ match_italic
210
+ @cursor += 2
211
+ end
212
+ elsif @text[@cursor, 3] == "'''"
213
+ match_bold
214
+ @cursor += 3
215
+ elsif @text[@cursor, 2] == "''"
216
+ match_italic
217
+ @cursor += 2
218
+ else
219
+ match_text
220
+ end
221
+ end
222
+
223
+ def match_bold
224
+ if @context.last == :BOLD
225
+ end_span(:BOLD, "'''")
226
+ else
227
+ start_span(:BOLD, "'''")
228
+ end
229
+ end
230
+
231
+ def match_italic
232
+ if @context.last == :ITALIC
233
+ end_span(:ITALIC, "''")
234
+ else
235
+ start_span(:ITALIC, "''")
236
+ end
237
+ end
238
+
239
+ def match_tilde
240
+ if @text[@cursor, 5] == "~~~~~"
241
+ empty_span(:SIGNATURE_DATE, "~~~~~")
242
+ @cursor += 5
243
+ elsif @text[@cursor, 4] == "~~~~"
244
+ empty_span(:SIGNATURE_FULL, "~~~~")
245
+ @cursor += 4
246
+ elsif @text[@cursor, 3] == "~~~"
247
+ empty_span(:SIGNATURE_NAME, "~~~")
248
+ @cursor += 3
249
+ else
250
+ match_text
251
+ end
252
+ end
253
+
254
+ def match_right_angle
255
+ next_char = @text[@cursor + 1]
256
+ if next_char == 47
257
+ # Might be an XHTML end tag
258
+ if @text[@cursor .. -1] =~ %r{</([a-zA-Z][a-zA-Z0-9\-_]*)(\s*)>} and @context.include?(:TAG)
259
+ # Found an XHTML end tag
260
+ tag_name = $1
261
+ end_span(:TAG, $1)
262
+ @lexer_table.pop
263
+ @cursor += $1.length + $2.length + 3
264
+ else
265
+ match_text
266
+ end
267
+ elsif next_char > 64 and next_char < 123
268
+ # Might be an XHTML open or empty tag
269
+ scanner = StringScanner.new(@text[@cursor .. -1])
270
+ if scanner.scan(%r{<([a-zA-Z][a-zA-Z0-9\-_]*)})
271
+ # Sequence begins with a valid tag name, so check for attributes
272
+ tag_name = scanner[1]
273
+ attrs = {}
274
+ while scanner.scan(%r{\s+([a-zA-Z][a-zA-Z0-9\-_]*)\s*=\s*('([^']+)'|"([^"]+)")}) do
275
+ attrs[scanner[1]] = scanner[3] ? scanner[3] : scanner[4]
276
+ end
277
+ scanner.scan(%r{\s*})
278
+ if ((c = scanner.get_byte) == '>' or (c == '/' and scanner.get_byte == '>'))
279
+ # Found an XHTML start or empty tag
280
+ if tag_name == 'nowiki'
281
+ @lexer_table.push(@escape_lexer_table)
282
+ else
283
+ start_span(:TAG, tag_name)
284
+ attrs.collect do
285
+ |(name, value)|
286
+ append_to_tokens([:ATTR_NAME, name])
287
+ append_to_tokens([:ATTR_VALUE, value]) if value
288
+ end
289
+ if c == '/'
290
+ end_span(:TAG, tag_name)
291
+ else
292
+ @lexer_table.push(@markup_lexer_table)
293
+ end
294
+ end
295
+ @cursor += scanner.pos
296
+ else
297
+ match_text
298
+ end
299
+ else
300
+ match_text
301
+ end
302
+ else
303
+ match_text
304
+ end
305
+ end
306
+
307
+ def match_equal
308
+ if at_start_of_line?
309
+ @heading = extract_char_sequence('=')
310
+ start_span(:SECTION, @heading)
311
+ @lexer_table.push(@heading_lexer_table)
312
+ else
313
+ match_text
314
+ end
315
+ end
316
+
317
+ def match_equal_in_heading
318
+ heading = extract_char_sequence('=')
319
+ if @heading.length <= heading.length
320
+ end_span(:SECTION, heading)
321
+ @lexer_table.pop
322
+ else
323
+ @pending << heading
324
+ end
325
+ end
326
+
327
+ def match_newline_in_heading
328
+ end_span(:SECTION)
329
+ @lexer_table.pop
330
+ end
331
+
332
+ def match_left_square
333
+ if @text[@cursor, 2] == "[["
334
+ if @text[@cursor + 2, 1] != "]"
335
+ start_span(:INTLINK, "[[")
336
+ @cursor += 2
337
+ @lexer_table.push(@intlink_lexer_table)
338
+ else
339
+ match_text
340
+ end
341
+ elsif @text[@cursor + 1 .. -1] =~ %r{\A\s*((http|https|file)://|mailto:)}
342
+ start_span(:LINK, "[")
343
+ @cursor += 1
344
+ skip_whitespace
345
+ @lexer_table.push(@link_lexer_table)
346
+ else
347
+ match_text
348
+ end
349
+ end
350
+
351
+ def match_right_square_in_link
352
+ end_span(:LINK, "]")
353
+ @cursor += 1
354
+ @lexer_table.pop
355
+ end
356
+
357
+ def match_right_square_in_intlink
358
+ if @text[@cursor, 2] == "]]"
359
+ end_span(:INTLINK, "]]")
360
+ @cursor += 2
361
+ @lexer_table.pop
362
+ else
363
+ match_text
364
+ end
365
+ end
366
+
367
+ def match_space_in_link
368
+ skip_whitespace
369
+ append_to_tokens([:LINKSEP, ' ']) unless @text[@cursor, 1] == ']'
370
+ @lexer_table.pop
371
+ @lexer_table.push(@link_opt_lexer_table)
372
+ end
373
+
374
+ def match_pipe_in_intlink
375
+ if @tokens.last[0] == :INTLINK_START
376
+ @lexer_table.pop
377
+ @lexer_table.push(@intlink_opt_lexer_table)
378
+ end
379
+ append_to_tokens([:INTLINKSEP, "|"])
380
+ @cursor += 1
381
+ end
382
+
383
+ def match_colon_in_intlink
384
+ append_to_tokens([:RESOURCESEP, ":"])
385
+ @lexer_table.pop
386
+ @lexer_table.push(@resourcelink_opt_lexer_table)
387
+ @cursor += 1
388
+ end
389
+
390
+ def match_newline_in_link
391
+ end_span(:LINK)
392
+ @lexer_table.pop
393
+ end
394
+
395
+ def match_newline_in_intlink
396
+ end_span(:INTLINK)
397
+ @lexer_table.pop
398
+ end
399
+
400
+ def match_h_char
401
+ if @text[@cursor, 7] == 'http://'
402
+ text = @text[@cursor, 7]
403
+ @cursor += 7
404
+ while @cursor < @text.size and TOKEN_CHAR_TABLE[@text[@cursor]] do
405
+ text << @text[@cursor, 1]
406
+ @cursor += 1
407
+ end
408
+ start_span(:LINK)
409
+ @pending = text
410
+ end_span(:LINK)
411
+ else
412
+ match_text
413
+ end
414
+ end
415
+
416
+ def match_space
417
+ if at_start_of_line?
418
+ start_span(:PRE)
419
+ @lexer_table.push(@pre_lexer_table)
420
+ match_text
421
+ else
422
+ match_text
423
+ end
424
+ end
425
+
426
+ def match_newline_in_pre
427
+ match_text
428
+ unless @text[@cursor, 1] == " "
429
+ @tokens << [:TEXT, @pending]
430
+ @pending = ''
431
+ end_span(:PRE)
432
+ @lexer_table.pop
433
+ end
434
+ end
435
+
436
+ def match_star
437
+ if at_start_of_line?
438
+ @list = extract_char_sequence('#*')
439
+ open_list(@list)
440
+ @lexer_table.push(@items_lexer_table)
441
+ else
442
+ match_text
443
+ end
444
+ end
445
+
446
+ def match_hash
447
+ if at_start_of_line?
448
+ @list = extract_char_sequence('#*')
449
+ open_list(@list)
450
+ @lexer_table.push(@items_lexer_table)
451
+ else
452
+ match_text
453
+ end
454
+ end
455
+
456
+ def match_underscore
457
+ if @text[@cursor, 7] == '__TOC__'
458
+ empty_span(:KEYWORD, 'TOC')
459
+ @cursor += 7
460
+ else
461
+ match_text
462
+ end
463
+ end
464
+
465
+ def match_newline_in_items
466
+ if @text[@cursor, 1] == "\n"
467
+ newline = "\n"
468
+ char = @text[@cursor + 1, 1]
469
+ else
470
+ newline = "\r\n"
471
+ char = @text[@cursor + 2, 1]
472
+ end
473
+ @pending << newline
474
+ @cursor += newline.length
475
+ if (char == @list[0, 1])
476
+ list = extract_char_sequence('#*')
477
+ if list == @list
478
+ end_span(:LI)
479
+ start_span(:LI)
480
+ else
481
+ l = @list.length > list.length ? list.length : @list.length
482
+ i = 0
483
+ i += 1 while (i < l and @list[i] == list[i])
484
+ if i < @list.length
485
+ close_list(@list[i .. -1])
486
+ if @context.last == :LI
487
+ end_span(:LI)
488
+ start_span(:LI)
489
+ end
490
+ end
491
+ if i < list.length
492
+ start_span(:LI) if @context.last != :LI
493
+ open_list(list[i .. -1])
494
+ end
495
+ @list = list
496
+ end
497
+ else
498
+ close_list(@list)
499
+ @lexer_table.pop
500
+ end
501
+ end
502
+
503
+ def match_dash
504
+ if at_start_of_line? and @text[@cursor, 4] == "----"
505
+ empty_span(:HLINE, "----")
506
+ @cursor += 4
507
+ else
508
+ match_text
509
+ end
510
+ end
511
+
512
+ def match_right_angle_in_tag
513
+ if @text[@cursor, 9] == '</nowiki>'
514
+ @cursor += 9
515
+ @lexer_table.pop
516
+ else
517
+ match_text
518
+ end
519
+ end
520
+
521
+ def match_left_curly
522
+ if at_start_of_line? and @text[@cursor + 1, 1] == '|'
523
+ start_span(:TABLE, "{|")
524
+ @cursor += 2
525
+ @lexer_table.push(@table_lexer_table)
526
+ elsif @text[@cursor + 1, 1] == '{' and @text[@cursor + 2, 2] != "}}"
527
+ start_span(:VARIABLE, "{{")
528
+ @cursor += 2
529
+ @lexer_table.push(@variable_lexer_table)
530
+ else
531
+ match_text
532
+ end
533
+ end
534
+
535
+ def match_right_curly_in_variable
536
+ if @text[@cursor + 1, 1] == '}'
537
+ end_span(:VARIABLE, "}}")
538
+ @cursor += 2
539
+ @lexer_table.pop
540
+ else
541
+ match_text
542
+ end
543
+ end
544
+
545
+ def match_bang_in_table
546
+ if at_start_of_line?
547
+ @cursor += 1
548
+ if @context.last == :CELL
549
+ end_span(:CELL)
550
+ elsif @context.last == :HEAD
551
+ end_span(:HEAD)
552
+ elsif @context.last != :ROW
553
+ start_span(:ROW)
554
+ end
555
+ start_span(:HEAD, "!")
556
+ else
557
+ match_text
558
+ end
559
+ end
560
+
561
+ def match_pipe_in_table
562
+ if at_start_of_line?
563
+ @cursor += 1
564
+ context = @context[@context.rindex(:TABLE) + 1 .. -1]
565
+ if @text[@cursor, 1] == '-'
566
+ end_span(:ROW) if context.include? :ROW
567
+ start_span(:ROW, "|-")
568
+ @cursor += 1
569
+ elsif @text[@cursor, 1] == '}'
570
+ end_span(:TABLE, "|}")
571
+ @cursor += 1
572
+ @lexer_table.pop
573
+ else
574
+ if context.include? :CELL
575
+ end_span(:CELL)
576
+ elsif context.include? :HEAD
577
+ end_span(:HEAD)
578
+ end
579
+ start_span(:ROW) unless @context.last == :ROW
580
+ start_span(:CELL, "|")
581
+ end
582
+ elsif @text[@cursor + 1, 1] == '|'
583
+ @cursor += 2
584
+ context = @context[@context.rindex(:TABLE) + 1 .. -1]
585
+ if context.include?:CELL
586
+ end_span(:CELL)
587
+ start_span(:CELL, "||")
588
+ elsif context.include? :HEAD
589
+ end_span(:HEAD)
590
+ start_span(:HEAD, "||")
591
+ end
592
+ else
593
+ match_text
594
+ end
595
+ end
596
+
597
+ def match_newline
598
+ if @text[@cursor, 2] == "\n\n"
599
+ @pending << "\n\n"
600
+ @cursor += 2
601
+ end_span(:PARA)
602
+ start_span(:PARA)
603
+ elsif @text[@cursor, 4] == "\r\n\r\n"
604
+ @pending << "\r\n\r\n"
605
+ @cursor += 4
606
+ end_span(:PARA)
607
+ start_span(:PARA)
608
+ else
609
+ match_text
610
+ end
611
+ end
612
+
613
+ def match_newline_in_table
614
+ if @text[@cursor, 2] == "\n\n"
615
+ start_span(:PARA)
616
+ append_to_tokens([:TEXT, "\n\n"])
617
+ end_span(:PARA)
618
+ @cursor += 2
619
+ elsif @text[@cursor, 4] == "\r\n\r\n"
620
+ start_span(:PARA)
621
+ append_to_tokens([:TEXT, "\r\n\r\n"])
622
+ end_span(:PARA)
623
+ @cursor += 4
624
+ else
625
+ match_text
626
+ end
627
+ end
628
+
629
+ def match_semicolon
630
+ if at_start_of_line?
631
+ start_span(:DL)
632
+ start_span(:DT, ';')
633
+ @lexer_table.push(@entries_lexer_table)
634
+ @cursor += 1
635
+ else
636
+ match_text
637
+ end
638
+ end
639
+
640
+ def match_colon
641
+ if at_start_of_line?
642
+ start_span(:DL)
643
+ start_span(:DD, ':')
644
+ @lexer_table.push(@entries_lexer_table)
645
+ @cursor += 1
646
+ else
647
+ match_text
648
+ end
649
+ end
650
+
651
+ def match_colon_in_entries
652
+ if @context.include? :DD
653
+ end_span(:DD)
654
+ elsif @context.include? :DT
655
+ end_span(:DT)
656
+ end
657
+ start_span(:DD, ':')
658
+ @cursor += 1
659
+ end
660
+
661
+ def match_newline_in_entries
662
+ match_text
663
+ unless @text[@cursor, 1] == ':'
664
+ if @context.include? :DD
665
+ end_span(:DD)
666
+ elsif @context.include? :DT
667
+ end_span(:DT)
668
+ end
669
+ end_span(:DL)
670
+ @lexer_table.pop
671
+ end
672
+ end
673
+
674
+
675
+ #-- ================== Helper methods ================== ++#
676
+
677
+ # Returns true if the text cursor is on the first character of a line
678
+ def at_start_of_line?
679
+ @cursor == 0 or @text[@cursor - 1, 1] == "\n"
680
+ end
681
+
682
+ # Advances the text cursor to the next non-blank character, without appending
683
+ # any of the blank characters to the pending text buffer
684
+ def skip_whitespace
685
+ @cursor += 1 while @text[@cursor, 1] == ' '
686
+ end
687
+
688
+ # Extracts from the input text the sequence of characters consisting of the
689
+ # character or characters specified, and returns the sequence as a string. The
690
+ # text cursor is advanaced to point to the next character after the sequence.
691
+ def extract_char_sequence(char)
692
+ sequence = ''
693
+ if char.length == 1
694
+ while @text[@cursor, 1] == char do
695
+ sequence << char
696
+ @cursor += 1
697
+ end
698
+ else
699
+ chars = char.split('')
700
+ while chars.include?(@text[@cursor, 1]) do
701
+ sequence << @text[@cursor, 1]
702
+ @cursor += 1
703
+ end
704
+ end
705
+ sequence
706
+ end
707
+
708
+ # Opens list and list item spans for each item symbol in the string specified.
709
+ def open_list(symbols)
710
+ symbols.split('').each do
711
+ |symbol|
712
+ if symbol == '*'
713
+ start_span(:UL)
714
+ else
715
+ start_span(:OL)
716
+ end
717
+ start_span(:LI)
718
+ end
719
+ end
720
+
721
+ # Closes list and list item spans for each item symbol in the string specified.
722
+ def close_list(symbols)
723
+ symbols.split('').reverse.each do
724
+ |symbol|
725
+ end_span(:LI)
726
+ if symbol == '*'
727
+ end_span(:UL)
728
+ else
729
+ end_span(:OL)
730
+ end
731
+ end
732
+ end
733
+
734
+ # Open a token span for the symbol specified. This will append a token start
735
+ # to the list of output tokens, and push the symbol onto the context stack. If
736
+ # there is an open paragraph, and the symbol is a block element, then the
737
+ # open paragraph will be closed (or, if empty, removed) before the token start
738
+ # is appended.
739
+ def start_span(symbol, text='')
740
+ maybe_close_para(symbol)
741
+ @context << symbol
742
+ append_to_tokens [(symbol.to_s + '_START').to_sym, text]
743
+ end
744
+
745
+ # Close a token span for the symbol specified. This will append an end token
746
+ # to the list of output tokens, and pop the symbol from the context stack. Any
747
+ # unclosed contexts on top of this symbol's context will also be close (this
748
+ # generally happens when in-line markup is not terminated before a new block
749
+ # begins). If the context is empty as a result, a new paragraph will be opened.
750
+ def end_span(symbol, text='')
751
+ while(@context.size > 0 and @context.last != symbol) do
752
+ append_to_tokens [(@context.pop.to_s + '_END').to_sym, '']
753
+ end
754
+ @context.pop
755
+ append_to_tokens [(symbol.to_s + '_END').to_sym, text]
756
+ maybe_open_para(symbol)
757
+ end
758
+
759
+ def empty_span(symbol, text='')
760
+ maybe_close_para(symbol)
761
+ append_to_tokens [symbol, text]
762
+ maybe_open_para(symbol)
763
+ end
764
+
765
+ def maybe_close_para(symbol)
766
+ if @context.size > 0 and PARA_BREAK_ELEMENTS.include?(symbol)
767
+ i = 1
768
+ i += 1 while INLINE_ELEMENTS.include?(@context[-i])
769
+ if @context[-i] == :PARA
770
+ if @pending.is_empty_token? and @tokens.last[0] == :PARA_START
771
+ @context.pop
772
+ @tokens.pop
773
+ else
774
+ (1 .. i).each do
775
+ symbol = @context.pop
776
+ append_to_tokens [(symbol.to_s + '_END').to_sym, '']
777
+ end
778
+ end
779
+ end
780
+ end
781
+ end
782
+
783
+ def maybe_open_para(symbol)
784
+ if @context.size == 0 and symbol != :PARA
785
+ @tokens << [:PARA_START, '']
786
+ @context << :PARA
787
+ end
788
+ end
789
+
790
+ def append_to_tokens(token)
791
+ unless @pending.is_empty_token?
792
+ @tokens << [:TEXT, @pending]
793
+ end
794
+ @pending = ''
795
+ @tokens << token
796
+ end
797
+
798
+
799
+ class LexerTable
800
+
801
+ def initialize
802
+ @tables = []
803
+ end
804
+
805
+ def push(table)
806
+ @tables << table
807
+ @table = table
808
+ end
809
+
810
+ def pop
811
+ @tables.pop
812
+ @table = @tables.last
813
+ end
814
+
815
+ def[] (char)
816
+ @table[char]
817
+ end
818
+
819
+ end
820
+
821
+ end