my_wiki_generator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/my_wiki/grep.txt +238 -0
  2. data/my_wiki/my_wiki_generator.rb +248 -0
  3. data/my_wiki/templates/POST_GENERATION_REMINDER +1 -0
  4. data/my_wiki/templates/app/controllers/application.rb +75 -0
  5. data/my_wiki/templates/app/controllers/content_history_controller.rb +49 -0
  6. data/my_wiki/templates/app/controllers/login_controller.rb +93 -0
  7. data/my_wiki/templates/app/controllers/my_wiki_admin_controller.rb +26 -0
  8. data/my_wiki/templates/app/controllers/my_wiki_controller.rb +250 -0
  9. data/my_wiki/templates/app/controllers/page_admin_controller.rb +51 -0
  10. data/my_wiki/templates/app/controllers/user_admin_controller.rb +34 -0
  11. data/my_wiki/templates/app/helpers/application_helper.rb +3 -0
  12. data/my_wiki/templates/app/helpers/content_history_helper.rb +2 -0
  13. data/my_wiki/templates/app/helpers/login_helper.rb +2 -0
  14. data/my_wiki/templates/app/helpers/my_wiki_admin_helper.rb +2 -0
  15. data/my_wiki/templates/app/helpers/my_wiki_helper.rb +94 -0
  16. data/my_wiki/templates/app/helpers/page_admin_helper.rb +2 -0
  17. data/my_wiki/templates/app/helpers/user_admin_helper.rb +2 -0
  18. data/my_wiki/templates/app/models/attachment.rb +55 -0
  19. data/my_wiki/templates/app/models/content.rb +32 -0
  20. data/my_wiki/templates/app/models/content_history.rb +49 -0
  21. data/my_wiki/templates/app/models/content_sweeper.rb +7 -0
  22. data/my_wiki/templates/app/models/my_wiki_mailer.rb +29 -0
  23. data/my_wiki/templates/app/models/role.rb +18 -0
  24. data/my_wiki/templates/app/models/setting.rb +7 -0
  25. data/my_wiki/templates/app/models/user.rb +60 -0
  26. data/my_wiki/templates/app/views/content_history/_form.rhtml +19 -0
  27. data/my_wiki/templates/app/views/content_history/edit.rhtml +9 -0
  28. data/my_wiki/templates/app/views/content_history/list.rhtml +27 -0
  29. data/my_wiki/templates/app/views/content_history/new.rhtml +8 -0
  30. data/my_wiki/templates/app/views/content_history/show.rhtml +21 -0
  31. data/my_wiki/templates/app/views/layouts/my_wiki.rhtml +105 -0
  32. data/my_wiki/templates/app/views/login/edit.rhtml +25 -0
  33. data/my_wiki/templates/app/views/login/login.rhtml +23 -0
  34. data/my_wiki/templates/app/views/login/logout.rhtml +10 -0
  35. data/my_wiki/templates/app/views/login/signup.rhtml +23 -0
  36. data/my_wiki/templates/app/views/login/welcome.rhtml +13 -0
  37. data/my_wiki/templates/app/views/my_wiki/_form.rhtml +11 -0
  38. data/my_wiki/templates/app/views/my_wiki/css.rhtml +224 -0
  39. data/my_wiki/templates/app/views/my_wiki/diff.rhtml +4 -0
  40. data/my_wiki/templates/app/views/my_wiki/edit.rhtml +34 -0
  41. data/my_wiki/templates/app/views/my_wiki/fileinfo.rhtml +22 -0
  42. data/my_wiki/templates/app/views/my_wiki/list.rhtml +1 -0
  43. data/my_wiki/templates/app/views/my_wiki/mails.rhtml +1 -0
  44. data/my_wiki/templates/app/views/my_wiki/new.rhtml +6 -0
  45. data/my_wiki/templates/app/views/my_wiki/recent.rhtml +3 -0
  46. data/my_wiki/templates/app/views/my_wiki/search.rhtml +5 -0
  47. data/my_wiki/templates/app/views/my_wiki/search_result.rhtml +10 -0
  48. data/my_wiki/templates/app/views/my_wiki/show.rhtml +31 -0
  49. data/my_wiki/templates/app/views/my_wiki_admin/index.rhtml +5 -0
  50. data/my_wiki/templates/app/views/my_wiki_admin/setting.rhtml +64 -0
  51. data/my_wiki/templates/app/views/my_wiki_mailer/inform.rhtml +3 -0
  52. data/my_wiki/templates/app/views/page_admin/_form.rhtml +22 -0
  53. data/my_wiki/templates/app/views/page_admin/edit.rhtml +9 -0
  54. data/my_wiki/templates/app/views/page_admin/list.rhtml +55 -0
  55. data/my_wiki/templates/app/views/page_admin/new.rhtml +8 -0
  56. data/my_wiki/templates/app/views/page_admin/show.rhtml +8 -0
  57. data/my_wiki/templates/app/views/user_admin/_form.rhtml +10 -0
  58. data/my_wiki/templates/app/views/user_admin/change_password.rhtml +9 -0
  59. data/my_wiki/templates/app/views/user_admin/list.rhtml +25 -0
  60. data/my_wiki/templates/app/views/user_admin/signup.rhtml +20 -0
  61. data/my_wiki/templates/components/admin/menu/menu.rhtml +5 -0
  62. data/my_wiki/templates/components/admin/menu_controller.rb +3 -0
  63. data/my_wiki/templates/components/list/list/list.rhtml +11 -0
  64. data/my_wiki/templates/components/list/list_controller.rb +11 -0
  65. data/my_wiki/templates/components/sidebar/sidebar/show.rhtml +7 -0
  66. data/my_wiki/templates/components/sidebar/sidebar_controller.rb +9 -0
  67. data/my_wiki/templates/config/routes.rb +35 -0
  68. data/my_wiki/templates/db/migrate/001_my_wiki_migration.rb +75 -0
  69. data/my_wiki/templates/lib/diff/lcs.rb +1105 -0
  70. data/my_wiki/templates/lib/diff/lcs/array.rb +21 -0
  71. data/my_wiki/templates/lib/diff/lcs/block.rb +51 -0
  72. data/my_wiki/templates/lib/diff/lcs/callbacks.rb +322 -0
  73. data/my_wiki/templates/lib/diff/lcs/change.rb +169 -0
  74. data/my_wiki/templates/lib/diff/lcs/hunk.rb +257 -0
  75. data/my_wiki/templates/lib/diff/lcs/ldiff.rb +226 -0
  76. data/my_wiki/templates/lib/diff/lcs/string.rb +19 -0
  77. data/my_wiki/templates/lib/login_system.rb +87 -0
  78. data/my_wiki/templates/lib/markup/simple_markup.rb +489 -0
  79. data/my_wiki/templates/lib/markup/simple_markup/fragments.rb +329 -0
  80. data/my_wiki/templates/lib/markup/simple_markup/inline.rb +338 -0
  81. data/my_wiki/templates/lib/markup/simple_markup/lines.rb +151 -0
  82. data/my_wiki/templates/lib/markup/simple_markup/preprocess.rb +68 -0
  83. data/my_wiki/templates/lib/markup/simple_markup/to_flow.rb +188 -0
  84. data/my_wiki/templates/lib/markup/simple_markup/to_html.rb +302 -0
  85. data/my_wiki/templates/lib/markup/simple_markup/to_latex.rb +333 -0
  86. data/my_wiki/templates/lib/my_wiki_plugin.rb +60 -0
  87. data/my_wiki/templates/lib/my_wiki_plugins/christel.rb +5 -0
  88. data/my_wiki/templates/lib/my_wiki_plugins/join.rb +3 -0
  89. data/my_wiki/templates/lib/my_wiki_plugins/link_to_attach.rb +17 -0
  90. data/my_wiki/templates/lib/my_wiki_plugins/recent.rb +8 -0
  91. data/my_wiki/templates/public/javascripts/my_wiki.js +8 -0
  92. data/my_wiki/templates/public/stylesheets/my_wiki/back.jpg +0 -0
  93. data/my_wiki/templates/public/stylesheets/my_wiki/back1.jpg +0 -0
  94. data/my_wiki/templates/public/stylesheets/my_wiki/back2.jpg +0 -0
  95. data/my_wiki/templates/public/stylesheets/my_wiki/foot.jpg +0 -0
  96. data/my_wiki/templates/public/stylesheets/my_wiki/h1.gif +0 -0
  97. data/my_wiki/templates/public/stylesheets/my_wiki/h1.jpg +0 -0
  98. data/my_wiki/templates/public/stylesheets/my_wiki/menu.jpg +0 -0
  99. data/my_wiki/templates/public/stylesheets/my_wiki/menu2.jpg +0 -0
  100. data/my_wiki/templates/public/stylesheets/my_wiki/menu_c.jpg +0 -0
  101. data/my_wiki/templates/public/stylesheets/my_wiki/my_wiki.css +336 -0
  102. data/my_wiki/templates/public/stylesheets/my_wiki/title.jpg +0 -0
  103. metadata +181 -0
@@ -0,0 +1,329 @@
1
+ require 'markup/simple_markup/lines.rb'
2
+ #require 'markup/simple_markup/to_flow.rb'
3
+
4
+ module SM
5
+
6
+ ##
7
+ # A Fragment is a chunk of text, subclassed as a paragraph, a list
8
+ # entry, or verbatim text
9
+
10
+ class Fragment
11
+ attr_reader :level, :param, :txt
12
+ attr_accessor :type
13
+
14
+ def initialize(level, param, type, txt)
15
+ @level = level
16
+ @param = param
17
+ @type = type
18
+ @txt = ""
19
+ add_text(txt) if txt
20
+ end
21
+
22
+ def add_text(txt)
23
+ @txt << " " if @txt.length > 0
24
+ @txt << txt.tr_s("\n ", " ").strip
25
+ end
26
+
27
+ def to_s
28
+ "L#@level: #{self.class.name.split('::')[-1]}\n#@txt"
29
+ end
30
+
31
+ ######
32
+ # This is a simple factory system that lets us associate fragement
33
+ # types (a string) with a subclass of fragment
34
+
35
+ TYPE_MAP = {}
36
+
37
+ def Fragment.type_name(name)
38
+ TYPE_MAP[name] = self
39
+ end
40
+
41
+ def Fragment.for(line)
42
+ klass = TYPE_MAP[line.type] ||
43
+ raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'")
44
+ return klass.new(line.level, line.param, line.flag, line.text)
45
+ end
46
+ end
47
+
48
+ ##
49
+ # A paragraph is a fragment which gets wrapped to fit. We remove all
50
+ # newlines when we're created, and have them put back on output
51
+
52
+ class Paragraph < Fragment
53
+ type_name Line::PARAGRAPH
54
+ end
55
+
56
+ class BlankLine < Paragraph
57
+ type_name Line::BLANK
58
+ end
59
+
60
+ class Heading < Paragraph
61
+ type_name Line::HEADING
62
+
63
+ def head_level
64
+ @param.to_i
65
+ end
66
+ end
67
+
68
+ ##
69
+ # A List is a fragment with some kind of label
70
+ #
71
+
72
+ class ListBase < Paragraph
73
+ # List types
74
+ BULLET = :BULLET
75
+ NUMBER = :NUMBER
76
+ UPPERALPHA = :UPPERALPHA
77
+ LOWERALPHA = :LOWERALPHA
78
+ LABELED = :LABELED
79
+ NOTE = :NOTE
80
+ TABLE = :TABLE # added by ando
81
+ end
82
+
83
+ class ListItem < ListBase
84
+ type_name Line::LIST
85
+
86
+ # def label
87
+ # am = AttributeManager.new(@param)
88
+ # am.flow
89
+ # end
90
+ end
91
+
92
+ class ListStart < ListBase
93
+ def initialize(level, param, type)
94
+ super(level, param, type, nil)
95
+ end
96
+ end
97
+
98
+ class ListEnd < ListBase
99
+ def initialize(level, type)
100
+ super(level, "", type, nil)
101
+ end
102
+ end
103
+
104
+ ##
105
+ # Verbatim code contains lines that don't get wrapped.
106
+
107
+ class Verbatim < Fragment
108
+ type_name Line::VERBATIM
109
+
110
+ def add_text(txt)
111
+ @txt << txt.chomp << "\n"
112
+ end
113
+
114
+ end
115
+
116
+ ##
117
+ # A horizontal rule
118
+ class Rule < Fragment
119
+ type_name Line::RULE
120
+ end
121
+
122
+
123
+ # Collect groups of lines together. Each group
124
+ # will end up containing a flow of text
125
+
126
+ class LineCollection
127
+
128
+ def initialize
129
+ @fragments = []
130
+ end
131
+
132
+ def add(fragment)
133
+ @fragments << fragment
134
+ end
135
+
136
+ def each(&b)
137
+ @fragments.each(&b)
138
+ end
139
+
140
+ # For testing
141
+ def to_a
142
+ @fragments.map {|fragment| fragment.to_s}
143
+ end
144
+
145
+ # Factory for different fragment types
146
+ def fragment_for(*args)
147
+ Fragment.for(*args)
148
+ end
149
+
150
+ # tidy up at the end
151
+ def normalize
152
+ change_verbatim_blank_lines
153
+ add_list_start_and_ends
154
+ add_list_breaks
155
+ tidy_blank_lines
156
+ end
157
+
158
+ def to_s
159
+ @fragments.join("\n----\n")
160
+ end
161
+
162
+ def accept(am, visitor)
163
+
164
+ visitor.start_accepting
165
+
166
+ @fragments.each do |fragment|
167
+ case fragment
168
+ when Verbatim
169
+ visitor.accept_verbatim(am, fragment)
170
+ when Rule
171
+ visitor.accept_rule(am, fragment)
172
+ when ListStart
173
+ visitor.accept_list_start(am, fragment)
174
+ when ListEnd
175
+ visitor.accept_list_end(am, fragment)
176
+ when ListItem
177
+ visitor.accept_list_item(am, fragment)
178
+ when BlankLine
179
+ visitor.accept_blank_line(am, fragment)
180
+ when Heading
181
+ visitor.accept_heading(am, fragment)
182
+ when Paragraph
183
+ visitor.accept_paragraph(am, fragment)
184
+ end
185
+ end
186
+
187
+ visitor.end_accepting
188
+ end
189
+ #######
190
+ private
191
+ #######
192
+
193
+ # If you have:
194
+ #
195
+ # normal paragraph text.
196
+ #
197
+ # this is code
198
+ #
199
+ # and more code
200
+ #
201
+ # You'll end up with the fragments Paragraph, BlankLine,
202
+ # Verbatim, BlankLine, Verbatim, BlankLine, etc
203
+ #
204
+ # The BlankLine in the middle of the verbatim chunk needs to
205
+ # be changed to a real verbatim newline, and the two
206
+ # verbatim blocks merged
207
+ #
208
+ #
209
+ def change_verbatim_blank_lines
210
+ frag_block = nil
211
+ blank_count = 0
212
+ @fragments.each_with_index do |frag, i|
213
+ if frag_block.nil?
214
+ frag_block = frag if Verbatim === frag
215
+ else
216
+ case frag
217
+ when Verbatim
218
+ blank_count.times { frag_block.add_text("\n") }
219
+ blank_count = 0
220
+ frag_block.add_text(frag.txt)
221
+ @fragments[i] = nil # remove out current fragment
222
+ when BlankLine
223
+ if frag_block
224
+ blank_count += 1
225
+ @fragments[i] = nil
226
+ end
227
+ else
228
+ frag_block = nil
229
+ blank_count = 0
230
+ end
231
+ end
232
+ end
233
+ @fragments.compact!
234
+ end
235
+
236
+ # List nesting is implicit given the level of
237
+ # Make it explicit, just to make life a tad
238
+ # easier for the output processors
239
+
240
+ def add_list_start_and_ends
241
+ level = 0
242
+ res = []
243
+ type_stack = []
244
+
245
+ @fragments.each do |fragment|
246
+ # $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}"
247
+ new_level = fragment.level
248
+ while (level < new_level)
249
+ level += 1
250
+ type = fragment.type
251
+ res << ListStart.new(level, fragment.param, type) if type
252
+ type_stack.push type
253
+ # $stderr.puts "Start: #{level}"
254
+ end
255
+
256
+ while level > new_level
257
+ type = type_stack.pop
258
+ res << ListEnd.new(level, type) if type
259
+ level -= 1
260
+ # $stderr.puts "End: #{level}, #{type}"
261
+ end
262
+
263
+ res << fragment
264
+ level = fragment.level
265
+ end
266
+ level.downto(1) do |i|
267
+ type = type_stack.pop
268
+ res << ListEnd.new(i, type) if type
269
+ end
270
+
271
+ @fragments = res
272
+ end
273
+
274
+ # now insert start/ends between list entries at the
275
+ # same level that have different element types
276
+
277
+ def add_list_breaks
278
+ res = @fragments
279
+
280
+ @fragments = []
281
+ list_stack = []
282
+
283
+ res.each do |fragment|
284
+ case fragment
285
+ when ListStart
286
+ list_stack.push fragment
287
+ when ListEnd
288
+ start = list_stack.pop
289
+ fragment.type = start.type
290
+ when ListItem
291
+ l = list_stack.last
292
+ if fragment.type != l.type
293
+ @fragments << ListEnd.new(l.level, l.type)
294
+ start = ListStart.new(l.level, fragment.param, fragment.type)
295
+ @fragments << start
296
+ list_stack.pop
297
+ list_stack.push start
298
+ end
299
+ else
300
+ ;
301
+ end
302
+ @fragments << fragment
303
+ end
304
+ end
305
+
306
+ # Finally tidy up the blank lines:
307
+ # * change Blank/ListEnd into ListEnd/Blank
308
+ # * remove blank lines at the front
309
+
310
+ def tidy_blank_lines
311
+ (@fragments.size - 1).times do |i|
312
+ if @fragments[i].kind_of?(BlankLine) and
313
+ @fragments[i+1].kind_of?(ListEnd)
314
+ @fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i]
315
+ end
316
+ end
317
+
318
+ # remove leading blanks
319
+ @fragments.each_with_index do |f, i|
320
+ break unless f.kind_of? BlankLine
321
+ @fragments[i] = nil
322
+ end
323
+
324
+ @fragments.compact!
325
+ end
326
+
327
+ end
328
+
329
+ end
@@ -0,0 +1,338 @@
1
+ module SM
2
+
3
+ # We manage a set of attributes. Each attribute has a symbol name
4
+ # and a bit value
5
+
6
+ class Attribute
7
+ SPECIAL = 1
8
+
9
+ @@name_to_bitmap = { :_SPECIAL_ => SPECIAL }
10
+ @@next_bitmap = 2
11
+
12
+ def Attribute.bitmap_for(name)
13
+ bitmap = @@name_to_bitmap[name]
14
+ if !bitmap
15
+ bitmap = @@next_bitmap
16
+ @@next_bitmap <<= 1
17
+ @@name_to_bitmap[name] = bitmap
18
+ end
19
+ bitmap
20
+ end
21
+
22
+ def Attribute.as_string(bitmap)
23
+ return "none" if bitmap.zero?
24
+ res = []
25
+ @@name_to_bitmap.each do |name, bit|
26
+ res << name if (bitmap & bit) != 0
27
+ end
28
+ res.join(",")
29
+ end
30
+
31
+ def Attribute.each_name_of(bitmap)
32
+ @@name_to_bitmap.each do |name, bit|
33
+ next if bit == SPECIAL
34
+ yield name.to_s if (bitmap & bit) != 0
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ # An AttrChanger records a change in attributes. It contains
41
+ # a bitmap of the attributes to turn on, and a bitmap of those to
42
+ # turn off
43
+
44
+ AttrChanger = Struct.new(:turn_on, :turn_off)
45
+ class AttrChanger
46
+ def to_s
47
+ "Attr: +#{Attribute.as_string(@turn_on)}/-#{Attribute.as_string(@turn_on)}"
48
+ end
49
+ end
50
+
51
+ # An array of attributes which parallels the characters in a string
52
+ class AttrSpan
53
+ def initialize(length)
54
+ @attrs = Array.new(length, 0)
55
+ end
56
+
57
+ def set_attrs(start, length, bits)
58
+ for i in start ... (start+length)
59
+ @attrs[i] |= bits
60
+ end
61
+ end
62
+
63
+ def [](n)
64
+ @attrs[n]
65
+ end
66
+ end
67
+
68
+ ##
69
+ # Hold details of a special sequence
70
+
71
+ class Special
72
+ attr_reader :type
73
+ attr_accessor :text
74
+
75
+ def initialize(type, text)
76
+ @type, @text = type, text
77
+ end
78
+
79
+ def ==(o)
80
+ self.text == o.text && self.type == o.type
81
+ end
82
+
83
+ def to_s
84
+ "Special: type=#{type}, text=#{text.dump}"
85
+ end
86
+ end
87
+
88
+ class AttributeManager
89
+
90
+ NULL = "\000".freeze
91
+
92
+ ##
93
+ # We work by substituting non-printing characters in to the
94
+ # text. For now I'm assuming that I can substitute
95
+ # a character in the range 0..8 for a 7 bit character
96
+ # without damaging the encoded string, but this might
97
+ # be optimistic
98
+ #
99
+
100
+ A_PROTECT = 004
101
+ PROTECT_ATTR = A_PROTECT.chr
102
+
103
+ # This maps delimiters that occur around words (such as
104
+ # *bold* or +tt+) where the start and end delimiters
105
+ # and the same. This lets us optimize the regexp
106
+ MATCHING_WORD_PAIRS = {}
107
+
108
+ # And this is used when the delimiters aren't the same. In this
109
+ # case the hash maps a pattern to the attribute character
110
+ WORD_PAIR_MAP = {}
111
+
112
+ # This maps HTML tags to the corresponding attribute char
113
+ HTML_TAGS = {}
114
+
115
+ # And this maps _special_ sequences to a name. A special sequence
116
+ # is something like a WikiWord
117
+ SPECIAL = {}
118
+
119
+ # Return an attribute object with the given turn_on
120
+ # and turn_off bits set
121
+
122
+ def attribute(turn_on, turn_off)
123
+ AttrChanger.new(turn_on, turn_off)
124
+ end
125
+
126
+
127
+ def change_attribute(current, new)
128
+ diff = current ^ new
129
+ attribute(new & diff, current & diff)
130
+ end
131
+
132
+ def changed_attribute_by_name(current_set, new_set)
133
+ current = new = 0
134
+ current_set.each {|name| current |= Attribute.bitmap_for(name) }
135
+ new_set.each {|name| new |= Attribute.bitmap_for(name) }
136
+ change_attribute(current, new)
137
+ end
138
+
139
+ def copy_string(start_pos, end_pos)
140
+ res = @str[start_pos...end_pos]
141
+ res.gsub!(/\000/, '')
142
+ res
143
+ end
144
+
145
+ # Map attributes like <b>text</b>to the sequence \001\002<char>\001\003<char>,
146
+ # where <char> is a per-attribute specific character
147
+
148
+ def convert_attrs(str, attrs)
149
+ # first do matching ones
150
+ tags = MATCHING_WORD_PAIRS.keys.join("")
151
+ re = "(^|\\W)([#{tags}])([A-Za-z_]+?)\\2(\\W|\$)"
152
+ # re = "(^|\\W)([#{tags}])(\\S+?)\\2(\\W|\$)"
153
+ 1 while str.gsub!(Regexp.new(re)) {
154
+ attr = MATCHING_WORD_PAIRS[$2];
155
+ attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr)
156
+ $1 + NULL*$2.length + $3 + NULL*$2.length + $4
157
+ }
158
+
159
+ # then non-matching
160
+ unless WORD_PAIR_MAP.empty?
161
+ WORD_PAIR_MAP.each do |regexp, attr|
162
+ str.gsub!(regexp) {
163
+ attrs.set_attrs($`.length + $1.length, $2.length, attr)
164
+ NULL*$1.length + $2 + NULL*$3.length
165
+ }
166
+ end
167
+ end
168
+ end
169
+
170
+ def convert_html(str, attrs)
171
+ tags = HTML_TAGS.keys.join("|")
172
+ re = "<(#{tags})>(.*?)</\\1>"
173
+ 1 while str.gsub!(Regexp.new(re, Regexp::IGNORECASE)) {
174
+ attr = HTML_TAGS[$1.downcase]
175
+ html_length = $1.length + 2
176
+ seq = NULL * html_length
177
+ attrs.set_attrs($`.length + html_length, $2.length, attr)
178
+ seq + $2 + seq + NULL
179
+ }
180
+ end
181
+
182
+ def convert_specials(str, attrs)
183
+ unless SPECIAL.empty?
184
+ SPECIAL.each do |regexp, attr|
185
+ str.scan(regexp) do
186
+ attrs.set_attrs($`.length, $1.length, attr | Attribute::SPECIAL)
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ # A \ in front of a character that would normally be
193
+ # processed turns off processing. We do this by turning
194
+ # \< into <#{PROTECT}
195
+
196
+ PROTECTABLE = [ "<" << "\\" ] #"
197
+
198
+
199
+ def mask_protected_sequences
200
+ protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])")
201
+ @str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}")
202
+ end
203
+
204
+ def unmask_protected_sequences
205
+ @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000")
206
+ end
207
+
208
+ def initialize
209
+ add_word_pair("*", "*", :BOLD)
210
+ add_word_pair("_", "_", :EM)
211
+ add_word_pair("+", "+", :TT)
212
+
213
+ add_html("em", :EM)
214
+ add_html("i", :EM)
215
+ add_html("b", :BOLD)
216
+ add_html("tt", :TT)
217
+ add_html("code", :TT)
218
+ end
219
+
220
+ def add_word_pair(start, stop, name)
221
+ raise "Word flags may not start '<'" if start[0] == ?<
222
+ bitmap = Attribute.bitmap_for(name)
223
+ if start == stop
224
+ MATCHING_WORD_PAIRS[start] = bitmap
225
+ else
226
+ pattern = Regexp.new("(" + Regexp.escape(start) + ")" +
227
+ # "([A-Za-z]+)" +
228
+ "(\\S+)" +
229
+ "(" + Regexp.escape(stop) +")")
230
+ WORD_PAIR_MAP[pattern] = bitmap
231
+ end
232
+ PROTECTABLE << start[0,1]
233
+ PROTECTABLE.uniq!
234
+ end
235
+
236
+ def add_html(tag, name)
237
+ HTML_TAGS[tag.downcase] = Attribute.bitmap_for(name)
238
+ end
239
+
240
+ def add_special(pattern, name)
241
+ SPECIAL[pattern] = Attribute.bitmap_for(name)
242
+ end
243
+
244
+ def flow(str)
245
+ @str = str
246
+
247
+ puts("Before flow, str='#{@str.dump}'") if $DEBUG
248
+ mask_protected_sequences
249
+
250
+ @attrs = AttrSpan.new(@str.length)
251
+
252
+ puts("After protecting, str='#{@str.dump}'") if $DEBUG
253
+ convert_attrs(@str, @attrs)
254
+ convert_html(@str, @attrs)
255
+ convert_specials(str, @attrs)
256
+ unmask_protected_sequences
257
+ puts("After flow, str='#{@str.dump}'") if $DEBUG
258
+ return split_into_flow
259
+ end
260
+
261
+ def display_attributes
262
+ puts
263
+ puts @str.tr(NULL, "!")
264
+ bit = 1
265
+ 16.times do |bno|
266
+ line = ""
267
+ @str.length.times do |i|
268
+ if (@attrs[i] & bit) == 0
269
+ line << " "
270
+ else
271
+ if bno.zero?
272
+ line << "S"
273
+ else
274
+ line << ("%d" % (bno+1))
275
+ end
276
+ end
277
+ end
278
+ puts(line) unless line =~ /^ *$/
279
+ bit <<= 1
280
+ end
281
+ end
282
+
283
+ def split_into_flow
284
+
285
+ display_attributes if $DEBUG
286
+
287
+ res = []
288
+ current_attr = 0
289
+ str = ""
290
+
291
+
292
+ str_len = @str.length
293
+
294
+ # skip leading invisible text
295
+ i = 0
296
+ i += 1 while i < str_len and @str[i].zero?
297
+ start_pos = i
298
+
299
+ # then scan the string, chunking it on attribute changes
300
+ while i < str_len
301
+ new_attr = @attrs[i]
302
+ if new_attr != current_attr
303
+ if i > start_pos
304
+ res << copy_string(start_pos, i)
305
+ start_pos = i
306
+ end
307
+
308
+ res << change_attribute(current_attr, new_attr)
309
+ current_attr = new_attr
310
+
311
+ if (current_attr & Attribute::SPECIAL) != 0
312
+ i += 1 while i < str_len and (@attrs[i] & Attribute::SPECIAL) != 0
313
+ res << Special.new(current_attr, copy_string(start_pos, i))
314
+ start_pos = i
315
+ next
316
+ end
317
+ end
318
+
319
+ # move on, skipping any invisible characters
320
+ begin
321
+ i += 1
322
+ end while i < str_len and @str[i].zero?
323
+ end
324
+
325
+ # tidy up trailing text
326
+ if start_pos < str_len
327
+ res << copy_string(start_pos, str_len)
328
+ end
329
+
330
+ # and reset to all attributes off
331
+ res << change_attribute(current_attr, 0) if current_attr != 0
332
+
333
+ return res
334
+ end
335
+
336
+ end
337
+
338
+ end