livetext 0.9.52 → 0.9.56

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/imports/bookish.rb +3 -3
  3. data/lib/livetext/ast/show_ast_clean.rb +10 -0
  4. data/lib/livetext/ast/show_ast_result.rb +60 -0
  5. data/lib/livetext/ast/show_raw_arrays.rb +13 -0
  6. data/lib/livetext/ast.rb +464 -0
  7. data/lib/livetext/ast_to_html.rb +32 -0
  8. data/lib/livetext/core.rb +110 -53
  9. data/lib/livetext/errors.rb +1 -0
  10. data/lib/livetext/expansion.rb +21 -21
  11. data/lib/livetext/formatter.rb +70 -200
  12. data/lib/livetext/formatter_component.rb +189 -0
  13. data/lib/livetext/function_registry.rb +163 -0
  14. data/lib/livetext/functions.rb +26 -0
  15. data/lib/livetext/handler/mixin.rb +53 -0
  16. data/lib/livetext/helpers.rb +33 -16
  17. data/lib/livetext/reopen.rb +2 -0
  18. data/lib/livetext/skeleton.rb +0 -3
  19. data/lib/livetext/standard.rb +120 -72
  20. data/lib/livetext/userapi.rb +20 -1
  21. data/lib/livetext/variable_manager.rb +78 -0
  22. data/lib/livetext/variables.rb +9 -1
  23. data/lib/livetext/version.rb +1 -1
  24. data/lib/livetext.rb +9 -3
  25. data/plugin/booktool.rb +14 -14
  26. data/plugin/lt3scriptor.rb +914 -0
  27. data/plugin/mixin_functions_class.rb +33 -0
  28. data/test/snapshots/complex_body/expected-error.txt +0 -0
  29. data/test/snapshots/complex_body/expected-output.txt +8 -0
  30. data/test/snapshots/complex_body/source.lt3 +19 -0
  31. data/test/snapshots/debug_command/expected-error.txt +0 -0
  32. data/test/snapshots/debug_command/expected-output.txt +1 -0
  33. data/test/snapshots/debug_command/source.lt3 +3 -0
  34. data/test/snapshots/def_parameters/expected-error.txt +0 -0
  35. data/test/snapshots/def_parameters/expected-output.txt +21 -0
  36. data/test/snapshots/def_parameters/source.lt3 +44 -0
  37. data/test/snapshots/error_missing_end/match-error.txt +1 -1
  38. data/test/snapshots/functions_reflection/expected-error.txt +0 -0
  39. data/test/snapshots/functions_reflection/expected-output.txt +27 -0
  40. data/test/snapshots/functions_reflection/source.lt3 +5 -0
  41. data/test/snapshots/mixin_functions_class/expected-error.txt +0 -0
  42. data/test/snapshots/mixin_functions_class/expected-output.txt +20 -0
  43. data/test/snapshots/mixin_functions_class/mixin_functions_class.rb +33 -0
  44. data/test/snapshots/mixin_functions_class/source.lt3 +17 -0
  45. data/test/snapshots/multiple_functions/expected-error.txt +0 -0
  46. data/test/snapshots/multiple_functions/expected-output.txt +5 -0
  47. data/test/snapshots/multiple_functions/source.lt3 +16 -0
  48. data/test/snapshots/nested_includes/expected-error.txt +0 -0
  49. data/test/snapshots/nested_includes/expected-output.txt +68 -0
  50. data/test/snapshots/nested_includes/level2.inc +34 -0
  51. data/test/snapshots/nested_includes/level3.inc +20 -0
  52. data/test/snapshots/nested_includes/source.lt3 +49 -0
  53. data/test/snapshots/parameter_handling/expected-error.txt +0 -0
  54. data/test/snapshots/parameter_handling/expected-output.txt +7 -0
  55. data/test/snapshots/parameter_handling/source.lt3 +10 -0
  56. data/test/snapshots/subset.txt +1 -0
  57. data/test/snapshots/system_info/expected-error.txt +0 -0
  58. data/test/snapshots/system_info/match-output.txt +18 -0
  59. data/test/snapshots/system_info/source.lt3 +16 -0
  60. data/test/unit/all.rb +7 -0
  61. data/test/unit/ast.rb +90 -0
  62. data/test/unit/ast_directives.rb +104 -0
  63. data/test/unit/ast_variables.rb +71 -0
  64. data/test/unit/core_methods.rb +317 -0
  65. data/test/unit/formatter.rb +84 -0
  66. data/test/unit/formatter_component.rb +84 -0
  67. data/test/unit/function_registry.rb +132 -0
  68. data/test/unit/mixin_functions_class.rb +131 -0
  69. data/test/unit/stringparser.rb +14 -32
  70. data/test/unit/variable_manager.rb +71 -0
  71. metadata +51 -5
  72. data/imports/markdown.rb +0 -44
  73. data/lib/livetext/processor.rb +0 -88
  74. data/plugin/markdown.rb +0 -43
data/lib/livetext/core.rb CHANGED
@@ -8,7 +8,23 @@ class Livetext
8
8
 
9
9
  TTY = ::File.open("/dev/tty", "w")
10
10
 
11
- attr_reader :main, :sources
11
+ Disallowed =
12
+ %i[ __binding__ __id__ __send__ class
13
+ clone display dup enum_for
14
+ eql? equal? extend freeze
15
+ frozen? hash inspect instance_eval
16
+ instance_exec instance_of? is_a? kind_of?
17
+ method methods nil? object_id
18
+ pretty_inspect private_methods protected_methods public_method
19
+ public_methods public_send respond_to? send
20
+ singleton_class singleton_method singleton_methods taint
21
+ tainted? tap to_enum to_s
22
+ trust untaint untrust untrusted?
23
+ define_singleton_method instance_variable_defined?
24
+ instance_variable_get instance_variable_set
25
+ remove_instance_variable instance_variables ]
26
+
27
+ attr_reader :sources, :function_registry, :variables, :formatter
12
28
  attr_accessor :nopass, :nopara
13
29
  attr_accessor :body, :indentation
14
30
 
@@ -17,7 +33,7 @@ class Livetext
17
33
  end
18
34
 
19
35
  def vars
20
- @_vars
36
+ @variables
21
37
  end
22
38
 
23
39
  def self.interpolate(str)
@@ -27,16 +43,10 @@ class Livetext
27
43
  str3
28
44
  end
29
45
 
30
- def peek_nextline
31
- @main.peek_nextline # delegate
32
- end
33
46
 
34
- def nextline
35
- @main.nextline # delegate
36
- end
37
47
 
38
48
  def sources
39
- @main.sources # delegate
49
+ @sources # delegate
40
50
  end
41
51
 
42
52
  def save_location
@@ -48,18 +58,15 @@ class Livetext
48
58
  end
49
59
 
50
60
  def initialize(output = ::STDOUT) # Livetext
51
- @source = nil
52
- @_mixins = []
53
- @_imports = []
54
- @_outdir = "."
55
- @no_puts = output.nil?
56
61
  @body = ""
57
- @main = Processor.new(self, output) # nil = make @main its own parent??
58
- @parent = @main
59
62
  @indentation = [0]
60
- @_vars = Livetext::Vars
61
63
  @api = UserAPI.new(self)
62
- initial_vars
64
+ @output = ::Livetext.output = output
65
+ @html = Livetext::HTML.new(@api)
66
+ @sources = []
67
+ @function_registry = Livetext::FunctionRegistry.new
68
+ @variables = Livetext::VariableManager.new(self)
69
+ @formatter = Livetext::Formatter.new(self)
63
70
  # puts "------ init: self = "
64
71
  # p self
65
72
  end
@@ -71,8 +78,10 @@ class Livetext
71
78
  mix.each do |lib|
72
79
  obj.invoke_dotcmd(:mixin, lib.dup)
73
80
  end
74
- call.each {|cmd| obj.main.send(cmd[1..-1]) } # ignores leading dot, no param
81
+ call.each {|cmd| obj.handle_dotcmd(cmd) } # Use handle_dotcmd for proper command parsing
75
82
  obj.api.setvars(vars)
83
+ # Also set variables in global Livetext::Vars for backward compatibility
84
+ vars.each {|var, val| Vars[var.to_sym] = val.to_s }
76
85
  obj
77
86
  end
78
87
 
@@ -80,22 +89,19 @@ class Livetext
80
89
  mix = Array(mix)
81
90
  call = Array(call)
82
91
  mix.each {|lib| mixin(lib) }
83
- call.each {|cmd| @main.send(cmd[1..-1]) } # ignores leading dot, no param
92
+ call.each {|cmd| handle_dotcmd(cmd) } # Use handle_dotcmd for proper command parsing
84
93
  # vars.each_pair {|var, val| @api.set(var, val.to_s) }
85
94
  api.setvars(vars)
95
+ # Also set variables in global Livetext::Vars for backward compatibility
96
+ vars.each {|var, val| Vars[var.to_sym] = val.to_s }
86
97
  self
87
98
  end
88
99
 
89
- def inspect
100
+ def inspect
90
101
  api_abbr = @api ? "(non-nil)" : "(not shown)"
91
- main_abbr = @main ? "(non-nil)" : "(not shown)"
92
102
  "Livetext:\n" +
93
- " source = #{@source.inspect}\n" +
94
- " mixins = #{@_mixins.inspect}\n" +
95
- " import = #{@_mixins.inspect}\n" +
96
- " main = #{main_abbr}\n" +
97
103
  " indent = #{@indentation.inspect}\n" +
98
- " vars = #{@_vars.inspect}\n" +
104
+ " vars = #{@variables.inspect}\n" +
99
105
  " api = #{api_abbr}\n" +
100
106
  " body = (#{@body.size} bytes)"
101
107
  end
@@ -104,23 +110,81 @@ class Livetext
104
110
  @api
105
111
  end
106
112
 
113
+ def error(*args)
114
+ ::STDERR.puts *args
115
+ end
116
+
117
+ def disallowed?(name)
118
+ flag = Disallowed.include?(name.to_sym)
119
+ flag
120
+ end
121
+
122
+ def output=(io)
123
+ @output = io
124
+ end
125
+
126
+ def html
127
+ @html
128
+ end
129
+
130
+ def source(enum, file, line)
131
+ @sources.push([enum, file, line])
132
+ end
133
+
134
+ def peek_nextline
135
+ return nil if @sources.empty?
136
+ source = @sources.last
137
+ line = source[0].peek
138
+ line
139
+ rescue StopIteration
140
+ @sources.pop
141
+ nil
142
+ rescue => err
143
+ TTY.puts "#{__method__}: RESCUE err = #{err.inspect}"
144
+ nil
145
+ end
146
+
147
+ def nextline
148
+ return nil if @sources.empty?
149
+ line = @sources.last[0].next
150
+ @sources.last[2] += 1
151
+ line
152
+ rescue StopIteration
153
+ @sources.pop
154
+ nil
155
+ end
156
+
107
157
  def api=(obj)
108
158
  @api = obj
109
159
  end
110
160
 
111
- def initial_vars
112
- # Other predefined variables (see also setfile)
113
- @api.setvar(:User, `whoami`.chomp)
114
- @api.setvar(:Version, Livetext::VERSION)
161
+ def process(text: nil, file: nil, vars: {})
162
+ # Set variables first
163
+ @variables.set_multiple(vars) unless vars.empty?
164
+
165
+ # Process based on input type
166
+ case
167
+ when file && text.nil?
168
+ process_file(file)
169
+ when file.nil? && text
170
+ transform_text(text)
171
+ when file.nil? && text.nil?
172
+ raise "Must specify file or text"
173
+ when file && text
174
+ raise "Cannot specify file and text"
175
+ end
176
+
177
+ [self.body, @variables.to_h]
115
178
  end
116
179
 
117
- def transform(text)
180
+ # Keep transform for backward compatibility, but make it private
181
+ private def transform_text(text)
118
182
  setfile!("(string)")
119
183
  enum = text.each_line
120
184
  front = text.match(/.*?\n/).to_a.first.chomp rescue ""
121
- @main.source(enum, "STDIN: '#{front}...'", 0)
185
+ source(enum, "STDIN: '#{front}...'", 0)
122
186
  loop do
123
- line = @main.nextline
187
+ line = nextline
124
188
  break if line.nil?
125
189
  process_line(line)
126
190
  end
@@ -129,29 +193,22 @@ class Livetext
129
193
  result
130
194
  end
131
195
 
132
- # EXPERIMENTAL and incomplete
196
+ # Keep for backward compatibility
197
+ def transform(text)
198
+ transform_text(text)
199
+ end
200
+
201
+ # Keep for backward compatibility
133
202
  def xform(*args, file: nil, text: nil, vars: {})
134
- case
135
- when file && text.nil?
136
- xform_file(file)
137
- when file.nil? && text
138
- transform(text)
139
- when file.nil? && text.nil?
140
- raise "Must specify file or text"
141
- when file && text
142
- raise "Cannot specify file and text"
143
- end
144
- self.process_file(file)
145
- self.body
203
+ body, _vars = process(file: file, text: text, vars: vars)
204
+ body
146
205
  end
147
206
 
207
+ # Keep for backward compatibility
148
208
  def xform_file(file, vars: nil)
149
- Livetext::Vars.replace(vars) unless vars.nil?
150
- @_vars.replace(vars) unless vars.nil?
151
- # checkpoint! "Calling process_file..."
152
- self.process_file(file)
153
- # checkpoint! "...returned"
154
- self.body
209
+ vars_hash = vars.nil? ? {} : vars
210
+ body, _vars = process(file: file, vars: vars_hash)
211
+ body
155
212
  end
156
213
 
157
214
  end
@@ -67,6 +67,7 @@ end
67
67
  make_exception(:EndWithoutOpening, "Error: found .end with no opening command")
68
68
  make_exception(:UnknownMethod, "Error: name '%1' is unknown")
69
69
  make_exception(:NoSuchFile, "Error: can't find file '%1' (method '%2')")
70
+ make_exception(:ExpectedEnd, "Error: expected .end but found end of file")
70
71
 
71
72
  # Move others here? DisallowedName, etc.
72
73
 
@@ -8,8 +8,6 @@ class Livetext::Expansion
8
8
  Lbrack = "\\["
9
9
  Colon = ":"
10
10
 
11
- Formatter = ::Livetext::Formatter
12
-
13
11
  def initialize(instance) # Livetext::Expansion
14
12
  @live = instance
15
13
  end
@@ -18,7 +16,7 @@ class Livetext::Expansion
18
16
  return "" if line == "\n" || line.nil?
19
17
  with_vars = expand_variables(line)
20
18
  with_func = expand_function_calls(with_vars)
21
- formatted = Formatter.format(with_func)
19
+ formatted = @live.formatter.format(with_func)
22
20
  end
23
21
 
24
22
  def expand_variables(str)
@@ -53,28 +51,30 @@ class Livetext::Expansion
53
51
  end
54
52
 
55
53
  def funcall(name, param)
56
- err = "[Error evaluating $$#{name}(#{param})]"
54
+ # Use the unified function registry
57
55
  name = name.gsub(/\./, "__")
56
+ result = @live.function_registry.call(name, param)
58
57
 
59
- # First check Livetext::Functions (for predefined and inline functions)
60
- fobj = ::Livetext::Functions.new
61
- result = fobj.send(name, param) rescue nil
62
- return result.to_s if result
63
-
64
- # Then check the processor instance (for mixin functions)
65
- if @live.main.respond_to?(name)
66
- # Check if the method expects parameters
67
- method = @live.main.method(name)
68
- if method.parameters.empty?
69
- result = @live.main.send(name) rescue err
70
- else
71
- result = @live.main.send(name, param) rescue err
58
+ # If not found in registry, fall back to old system for backward compatibility
59
+ if result.start_with?("[Error evaluating $$#{name}(")
60
+ # Try old Livetext::Functions system
61
+ fobj = ::Livetext::Functions.new
62
+ old_result = fobj.send(name, param) rescue nil
63
+ return old_result.to_s if old_result
64
+
65
+ # Try Livetext instance (for mixin functions)
66
+ if @live.respond_to?(name)
67
+ method = @live.method(name)
68
+ if method.parameters.empty?
69
+ old_result = @live.send(name) rescue nil
70
+ else
71
+ old_result = @live.send(name, param) rescue nil
72
+ end
73
+ return old_result.to_s if old_result
72
74
  end
73
- return result.to_s
74
75
  end
75
76
 
76
- # If not found anywhere
77
- err
77
+ result
78
78
  end
79
79
 
80
80
  def expand_function_calls(str)
@@ -85,7 +85,7 @@ class Livetext::Expansion
85
85
  rbrack = "\\]"
86
86
  space_eol = "( |$)"
87
87
  prx1 = "(?<param>[^ ]+)"
88
- prx2 = "(?<param>.+)"
88
+ prx2 = "(?<param>.*)"
89
89
  pat2 = "(?<full_param>#{colon}#{prx1})"
90
90
  pat3 = "(?<full_param>#{lbrack}#{prx2}#{rbrack})"
91
91
  rx = Regexp.compile("#{pat1}(#{pat2}|#{pat3})?")
@@ -1,35 +1,84 @@
1
- module Livetext::Formatter
1
+ # Formatter - Centralized text formatting for Livetext
2
+ class Livetext::Formatter
3
+ def initialize(parent)
4
+ @parent = parent
5
+ end
2
6
 
3
- def self.format(str)
4
- str = str.chomp
7
+ def format(text)
8
+ return "" if text.nil? || text.empty?
9
+
10
+ text = text.chomp
5
11
  # First, mark escaped characters so they won't be processed as formatting
6
- str = mark_escaped_characters(str)
12
+ text = mark_escaped_characters(text)
7
13
 
8
- # Process all marker types in sequence (like the original formatter)
9
- str = handle_double_markers(str)
10
- str = handle_bracketed_markers(str)
11
- str = handle_single_markers(str)
12
- str = handle_underscore_markers(str)
13
- str = handle_backtick_markers(str)
14
- str = handle_tilde_markers(str)
14
+ # Process all marker types in sequence
15
+ text = handle_double_markers(text)
16
+ text = handle_bracketed_markers(text)
17
+ text = handle_single_markers(text)
18
+ text = handle_underscore_markers(text)
19
+ text = handle_backtick_markers(text)
20
+ text = handle_tilde_markers(text)
15
21
 
16
- str = unmark_escaped_characters(str)
17
- str
22
+ text = unmark_escaped_characters(text)
23
+ text
24
+ end
25
+
26
+ def format_line(line)
27
+ return "" if line.nil?
28
+ format(line.chomp)
29
+ end
30
+
31
+ def format_multiple(lines)
32
+ lines.map { |line| format_line(line) }
33
+ end
34
+
35
+ # Convenience methods for common formatting patterns
36
+ def bold(text)
37
+ "<b>#{text}</b>"
38
+ end
39
+
40
+ def italic(text)
41
+ "<i>#{text}</i>"
42
+ end
43
+
44
+ def code(text)
45
+ "<tt>#{text}</tt>"
46
+ end
47
+
48
+ def strike(text)
49
+ "<strike>#{text}</strike>"
50
+ end
51
+
52
+ def link(text, url)
53
+ "<a href='#{url}'>#{text}</a>"
54
+ end
55
+
56
+ def escape_html(text)
57
+ text.gsub(/[&<>"']/) do |char|
58
+ case char
59
+ when '&' then '&amp;'
60
+ when '<' then '&lt;'
61
+ when '>' then '&gt;'
62
+ when '"' then '&quot;'
63
+ when "'" then '&#39;'
64
+ else char
65
+ end
66
+ end
18
67
  end
19
68
 
20
69
  private
21
70
 
22
- def self.mark_escaped_characters(str)
71
+ def mark_escaped_characters(str)
23
72
  # Replace escaped characters with a null byte marker (safe for internal use)
24
73
  str.gsub(/\\([*_`~])/, "\u0000\\1")
25
74
  end
26
75
 
27
- def self.unmark_escaped_characters(str)
76
+ def unmark_escaped_characters(str)
28
77
  # Restore escaped characters
29
78
  str.gsub(/\u0000([*_`~])/, '\1')
30
79
  end
31
80
 
32
- def self.handle_double_markers(str)
81
+ def handle_double_markers(str)
33
82
  # **word -> <b>word</b> (terminated by space, comma, period)
34
83
  # But ignore standalone ** or ** surrounded by spaces
35
84
  str.gsub(/(?<=\s|^)\*\*([^\s,.]*)/) do |match|
@@ -41,7 +90,7 @@ module Livetext::Formatter
41
90
  end
42
91
  end
43
92
 
44
- def self.handle_bracketed_markers(str)
93
+ def handle_bracketed_markers(str)
45
94
  # Handle all bracketed markers: *[content], _[content], `[content]
46
95
  # *[content] -> <b>content</b>
47
96
  # _[content] -> <i>content</i>
@@ -85,7 +134,7 @@ module Livetext::Formatter
85
134
  str
86
135
  end
87
136
 
88
- def self.handle_single_markers(str)
137
+ def handle_single_markers(str)
89
138
  # *word -> <b>word</b> (only at start of word or after space)
90
139
  # But ignore standalone * or * surrounded by spaces
91
140
  # Also ignore * that are part of ** patterns (already processed)
@@ -102,7 +151,7 @@ module Livetext::Formatter
102
151
  end
103
152
  end
104
153
 
105
- def self.handle_underscore_markers(str)
154
+ def handle_underscore_markers(str)
106
155
  # _word -> <i>word</i> (only at start of word or after space)
107
156
  # But ignore standalone _ or _ surrounded by spaces
108
157
  str.gsub(/(?<=\s|^)_([^\s]*)/) do |match|
@@ -114,7 +163,7 @@ module Livetext::Formatter
114
163
  end
115
164
  end
116
165
 
117
- def self.handle_backtick_markers(str)
166
+ def handle_backtick_markers(str)
118
167
  # `word -> <tt>word</tt> (only at start of word or after space)
119
168
  # But ignore standalone ` or ` surrounded by spaces
120
169
  str.gsub(/(?<=\s|^)`([^\s]*)/) do |match|
@@ -126,7 +175,7 @@ module Livetext::Formatter
126
175
  end
127
176
  end
128
177
 
129
- def self.handle_tilde_markers(str)
178
+ def handle_tilde_markers(str)
130
179
  # ~word -> <strike>word</strike> (only at start of word or after space)
131
180
  # But ignore standalone ~ or ~ surrounded by spaces
132
181
  str.gsub(/(?<=\s|^)~([^\s]*)/) do |match|
@@ -137,183 +186,4 @@ module Livetext::Formatter
137
186
  end
138
187
  end
139
188
  end
140
-
141
189
  end
142
-
143
- # Legacy classes - kept for compatibility but not used
144
- class Livetext::Formatter::Delimited
145
- def initialize(str, marker, tag)
146
- @str, @marker, @tag = str.dup, marker, tag
147
- @buffer = ""
148
- @cdata = ""
149
- @state = :INITIAL
150
- end
151
-
152
- def status(where)
153
- if $debug
154
- STDERR.printf "%-11s %-7s #{@marker.inspect} \n #{' '*11} state = %-8s str = %-20s buffer = %-20s cdata = %-20s\n",
155
- where, self.class, @state, @str.inspect, @buffer.inspect, @cdata.inspect
156
- end
157
- end
158
-
159
- def front
160
- @str[0]
161
- end
162
-
163
- def grab(n=1)
164
- char = @str.slice!(0..(n-1))
165
- char
166
- end
167
-
168
- def grab_terminator
169
- @state = :LOOPING
170
- end
171
-
172
- def eol?
173
- @str.empty?
174
- end
175
-
176
- def space?
177
- front == " "
178
- end
179
-
180
- def escape?
181
- front == "\\"
182
- end
183
-
184
- def terminated?
185
- space?
186
- end
187
-
188
- def marker?
189
- @str.start_with?(@marker)
190
- end
191
-
192
- def space_marker?
193
- @str.start_with?(" " + @marker)
194
- end
195
-
196
- def wrap(text)
197
- if text.empty?
198
- result = @marker
199
- result = "" if @marker[1] == "["
200
- return result
201
- end
202
- "<#{@tag}>#{text}</#{@tag}>"
203
- end
204
-
205
- def initial
206
- n = @marker.length
207
- case
208
- when escape?
209
- grab
210
- @buffer << grab
211
- when space_marker?
212
- @buffer << grab
213
- grab(n)
214
- @state = :CDATA
215
- when marker?
216
- grab(n)
217
- @state = :CDATA
218
- when eol?
219
- @state = :FINAL
220
- else
221
- @state = :BUFFER
222
- end
223
- end
224
-
225
- def buffer
226
- @buffer << grab
227
- @state = :LOOPING
228
- end
229
-
230
- def cdata
231
- case
232
- when eol?
233
- if @cdata.empty?
234
- @buffer << @marker unless @marker[1] == "["
235
- else
236
- @buffer << wrap(@cdata)
237
- end
238
- @state = :FINAL
239
- when terminated?
240
- @buffer << wrap(@cdata)
241
- grab_terminator
242
- @cdata = ""
243
- @state = :LOOPING
244
- else
245
- @cdata << grab
246
- @state = :CDATA
247
- end
248
- end
249
-
250
- def looping
251
- n = @marker.length
252
- case
253
- when escape?
254
- grab
255
- @buffer << grab
256
- when space_marker?
257
- @buffer << grab
258
- grab(n)
259
- @state = :CDATA
260
- when eol?
261
- @state = :FINAL
262
- else
263
- @buffer << grab
264
- end
265
- end
266
-
267
- def handle
268
- loop do
269
- break if @state == :FINAL
270
- meth = @state.downcase
271
- send(meth)
272
- end
273
- return @buffer
274
- end
275
-
276
- def self.process(str)
277
- bold = self.new(str, "*", "b")
278
- sb = bold.handle
279
- ital = self.new(sb, "_", "i")
280
- si = ital.handle
281
- code = self.new(si, "`", "tt")
282
- sc = code.handle
283
- stri = self.new(sc, "~", "strike")
284
- si = stri.handle
285
- si
286
- end
287
- end
288
-
289
- class Livetext::Formatter::Single < Livetext::Formatter::Delimited
290
- end
291
-
292
- class Livetext::Formatter::Double < Livetext::Formatter::Delimited
293
- def initialize(str, sigil, tag)
294
- super
295
- @marker = sigil + sigil
296
- end
297
-
298
- def terminated?
299
- terms = [" ", ".", ","]
300
- terms.include?(front)
301
- end
302
- end
303
-
304
- class Livetext::Formatter::Bracketed < Livetext::Formatter::Delimited
305
- def initialize(str, sigil, tag)
306
- super
307
- @marker = sigil + "["
308
- end
309
-
310
- def terminated?
311
- front == "]" || eol?
312
- end
313
-
314
- def grab_terminator
315
- @state = :LOOPING
316
- grab
317
- end
318
- end
319
-