my_wiki_generator 0.0.1

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