motiro 0.6.8 → 0.6.9

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 (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