livetext 0.9.51 → 0.9.55
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.
- checksums.yaml +4 -4
- data/imports/bookish.rb +3 -3
- data/lib/livetext/core.rb +108 -53
- data/lib/livetext/expansion.rb +25 -9
- data/lib/livetext/formatter.rb +149 -162
- data/lib/livetext/formatter_component.rb +189 -0
- data/lib/livetext/function_registry.rb +163 -0
- data/lib/livetext/handler/mixin.rb +25 -0
- data/lib/livetext/helpers.rb +25 -7
- data/lib/livetext/reopen.rb +2 -0
- data/lib/livetext/skeleton.rb +0 -3
- data/lib/livetext/standard.rb +118 -70
- data/lib/livetext/variable_manager.rb +65 -0
- data/lib/livetext/variables.rb +4 -0
- data/lib/livetext/version.rb +1 -1
- data/lib/livetext.rb +9 -3
- data/plugin/booktool.rb +8 -8
- data/test/extra/testgen.rb +1 -3
- data/test/snapshots/complex_body/expected-error.txt +0 -0
- data/test/snapshots/complex_body/expected-output.txt +8 -0
- data/test/snapshots/complex_body/source.lt3 +19 -0
- data/test/snapshots/debug_command/expected-error.txt +0 -0
- data/test/snapshots/debug_command/expected-output.txt +1 -0
- data/test/snapshots/debug_command/source.lt3 +3 -0
- data/test/snapshots/def_parameters/expected-error.txt +0 -0
- data/test/snapshots/def_parameters/expected-output.txt +21 -0
- data/test/snapshots/def_parameters/source.lt3 +44 -0
- data/test/snapshots/functions_reflection/expected-error.txt +0 -0
- data/test/snapshots/functions_reflection/expected-output.txt +27 -0
- data/test/snapshots/functions_reflection/source.lt3 +5 -0
- data/test/snapshots/mixin_functions/expected-error.txt +0 -0
- data/test/snapshots/mixin_functions/expected-output.txt +18 -0
- data/test/snapshots/mixin_functions/mixin_functions.rb +11 -0
- data/test/snapshots/mixin_functions/source.lt3 +15 -0
- data/test/snapshots/multiple_functions/expected-error.txt +0 -0
- data/test/snapshots/multiple_functions/expected-output.txt +5 -0
- data/test/snapshots/multiple_functions/source.lt3 +16 -0
- data/test/snapshots/nested_includes/expected-error.txt +0 -0
- data/test/snapshots/nested_includes/expected-output.txt +68 -0
- data/test/snapshots/nested_includes/level2.inc +34 -0
- data/test/snapshots/nested_includes/level3.inc +20 -0
- data/test/snapshots/nested_includes/source.lt3 +49 -0
- data/test/snapshots/parameter_handling/expected-error.txt +0 -0
- data/test/snapshots/parameter_handling/expected-output.txt +7 -0
- data/test/snapshots/parameter_handling/source.lt3 +10 -0
- data/test/snapshots/subset.txt +1 -0
- data/test/snapshots/system_info/expected-error.txt +0 -0
- data/test/snapshots/system_info/expected-output.txt +18 -0
- data/test/snapshots/system_info/source.lt3 +16 -0
- data/test/snapshots.rb +3 -4
- data/test/test_escape.lt3 +1 -0
- data/test/unit/all.rb +4 -0
- data/test/unit/bracketed.rb +2 -2
- data/test/unit/core_methods.rb +137 -0
- data/test/unit/double.rb +1 -3
- data/test/unit/formatter.rb +84 -0
- data/test/unit/formatter_component.rb +84 -0
- data/test/unit/function_registry.rb +132 -0
- data/test/unit/functions.rb +2 -2
- data/test/unit/html.rb +2 -2
- data/test/unit/parser/general.rb +2 -2
- data/test/unit/parser/mixin.rb +2 -2
- data/test/unit/parser/set.rb +2 -2
- data/test/unit/parser/string.rb +2 -2
- data/test/unit/single.rb +1 -3
- data/test/unit/standard.rb +1 -3
- data/test/unit/stringparser.rb +2 -2
- data/test/unit/variable_manager.rb +71 -0
- data/test/unit/variables.rb +2 -2
- metadata +41 -3
- data/lib/livetext/processor.rb +0 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00c147a97637c967777f1a1a8bae52d8e0ca8e65f3e032e9a03f0f3bbc2c5d50
|
4
|
+
data.tar.gz: c931920c669cf05c76176d19bcfb9c06149d3bf6b32167aa2e6da7b50290adef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59836de9481a1239cf0c0f00f55681da634c7e96e337c67d2c4fb8974ec3dad06fa83bbe7eb7f73596c73d97e9359aab613efbf87d0ddfe70e9a3a50c65e931e
|
7
|
+
data.tar.gz: b0e1a74d2d75e8c47afff0881594d1022dbb63e71ec67b8304622b9885bc62429d6782286482a45520587207fc48e4158d0a857f51bcac5596c51cc779f2cf01
|
data/imports/bookish.rb
CHANGED
@@ -205,7 +205,7 @@ api.tty "## sec: cp 4"
|
|
205
205
|
title = api.data
|
206
206
|
delim = " :: "
|
207
207
|
api.out "<br><center><table width=90% cellpadding=5>"
|
208
|
-
lines =
|
208
|
+
lines = body
|
209
209
|
maxw = nil
|
210
210
|
lines.each do |line|
|
211
211
|
api.format(line)
|
@@ -238,8 +238,8 @@ api.tty "## sec: cp 4"
|
|
238
238
|
_debug "Closing TOC"
|
239
239
|
@toc.close
|
240
240
|
rescue => err
|
241
|
-
puts
|
242
|
-
|
241
|
+
puts self.body
|
242
|
+
self.body = ""
|
243
243
|
_errout "Exception: #{err.inspect}"
|
244
244
|
end
|
245
245
|
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
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,7 +78,7 @@ class Livetext
|
|
71
78
|
mix.each do |lib|
|
72
79
|
obj.invoke_dotcmd(:mixin, lib.dup)
|
73
80
|
end
|
74
|
-
call.each {|cmd| obj.
|
81
|
+
call.each {|cmd| obj.send(cmd[1..-1]) } # ignores leading dot, no param
|
75
82
|
obj.api.setvars(vars)
|
76
83
|
obj
|
77
84
|
end
|
@@ -80,22 +87,17 @@ class Livetext
|
|
80
87
|
mix = Array(mix)
|
81
88
|
call = Array(call)
|
82
89
|
mix.each {|lib| mixin(lib) }
|
83
|
-
call.each {|cmd|
|
90
|
+
call.each {|cmd| send(cmd[1..-1]) } # ignores leading dot, no param
|
84
91
|
# vars.each_pair {|var, val| @api.set(var, val.to_s) }
|
85
92
|
api.setvars(vars)
|
86
93
|
self
|
87
94
|
end
|
88
95
|
|
89
|
-
|
96
|
+
def inspect
|
90
97
|
api_abbr = @api ? "(non-nil)" : "(not shown)"
|
91
|
-
main_abbr = @main ? "(non-nil)" : "(not shown)"
|
92
98
|
"Livetext:\n" +
|
93
|
-
" source = #{@source.inspect}\n" +
|
94
|
-
" mixins = #{@_mixins.inspect}\n" +
|
95
|
-
" import = #{@_mixins.inspect}\n" +
|
96
|
-
" main = #{main_abbr}\n" +
|
97
99
|
" indent = #{@indentation.inspect}\n" +
|
98
|
-
" vars = #{@
|
100
|
+
" vars = #{@variables.inspect}\n" +
|
99
101
|
" api = #{api_abbr}\n" +
|
100
102
|
" body = (#{@body.size} bytes)"
|
101
103
|
end
|
@@ -104,23 +106,83 @@ class Livetext
|
|
104
106
|
@api
|
105
107
|
end
|
106
108
|
|
109
|
+
def error(*args)
|
110
|
+
::STDERR.puts *args
|
111
|
+
end
|
112
|
+
|
113
|
+
def disallowed?(name)
|
114
|
+
flag = Disallowed.include?(name.to_sym)
|
115
|
+
flag
|
116
|
+
end
|
117
|
+
|
118
|
+
def output=(io)
|
119
|
+
@output = io
|
120
|
+
end
|
121
|
+
|
122
|
+
def html
|
123
|
+
@html
|
124
|
+
end
|
125
|
+
|
126
|
+
def source(enum, file, line)
|
127
|
+
@sources.push([enum, file, line])
|
128
|
+
end
|
129
|
+
|
130
|
+
def peek_nextline
|
131
|
+
return nil if @sources.empty?
|
132
|
+
source = @sources.last
|
133
|
+
line = source[0].peek
|
134
|
+
line
|
135
|
+
rescue StopIteration
|
136
|
+
@sources.pop
|
137
|
+
nil
|
138
|
+
rescue => err
|
139
|
+
TTY.puts "#{__method__}: RESCUE err = #{err.inspect}"
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
143
|
+
def nextline
|
144
|
+
return nil if @sources.empty?
|
145
|
+
line = @sources.last[0].next
|
146
|
+
@sources.last[2] += 1
|
147
|
+
line
|
148
|
+
rescue StopIteration
|
149
|
+
@sources.pop
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
|
107
153
|
def api=(obj)
|
108
154
|
@api = obj
|
109
155
|
end
|
110
156
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
157
|
+
|
158
|
+
|
159
|
+
def process(text: nil, file: nil, vars: {})
|
160
|
+
# Set variables first
|
161
|
+
@variables.set_multiple(vars) unless vars.empty?
|
162
|
+
|
163
|
+
# Process based on input type
|
164
|
+
case
|
165
|
+
when file && text.nil?
|
166
|
+
process_file(file)
|
167
|
+
when file.nil? && text
|
168
|
+
transform_text(text)
|
169
|
+
when file.nil? && text.nil?
|
170
|
+
raise "Must specify file or text"
|
171
|
+
when file && text
|
172
|
+
raise "Cannot specify file and text"
|
173
|
+
end
|
174
|
+
|
175
|
+
[self.body, @variables.to_h]
|
115
176
|
end
|
116
177
|
|
117
|
-
|
178
|
+
# Keep transform for backward compatibility, but make it private
|
179
|
+
private def transform_text(text)
|
118
180
|
setfile!("(string)")
|
119
181
|
enum = text.each_line
|
120
182
|
front = text.match(/.*?\n/).to_a.first.chomp rescue ""
|
121
|
-
|
183
|
+
source(enum, "STDIN: '#{front}...'", 0)
|
122
184
|
loop do
|
123
|
-
line =
|
185
|
+
line = nextline
|
124
186
|
break if line.nil?
|
125
187
|
process_line(line)
|
126
188
|
end
|
@@ -129,29 +191,22 @@ class Livetext
|
|
129
191
|
result
|
130
192
|
end
|
131
193
|
|
132
|
-
#
|
194
|
+
# Keep for backward compatibility
|
195
|
+
def transform(text)
|
196
|
+
transform_text(text)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Keep for backward compatibility
|
133
200
|
def xform(*args, file: nil, text: nil, vars: {})
|
134
|
-
|
135
|
-
|
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
|
201
|
+
body, _vars = process(file: file, text: text, vars: vars)
|
202
|
+
body
|
146
203
|
end
|
147
204
|
|
205
|
+
# Keep for backward compatibility
|
148
206
|
def xform_file(file, vars: nil)
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
self.process_file(file)
|
153
|
-
# checkpoint! "...returned"
|
154
|
-
self.body
|
207
|
+
vars_hash = vars.nil? ? {} : vars
|
208
|
+
body, _vars = process(file: file, vars: vars_hash)
|
209
|
+
body
|
155
210
|
end
|
156
211
|
|
157
212
|
end
|
data/lib/livetext/expansion.rb
CHANGED
@@ -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 =
|
19
|
+
formatted = @live.formatter.format(with_func)
|
22
20
|
end
|
23
21
|
|
24
22
|
def expand_variables(str)
|
@@ -53,12 +51,30 @@ class Livetext::Expansion
|
|
53
51
|
end
|
54
52
|
|
55
53
|
def funcall(name, param)
|
56
|
-
|
54
|
+
# Use the unified function registry
|
57
55
|
name = name.gsub(/\./, "__")
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
result.
|
56
|
+
result = @live.function_registry.call(name, param)
|
57
|
+
|
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
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
result
|
62
78
|
end
|
63
79
|
|
64
80
|
def expand_function_calls(str)
|
@@ -69,7 +85,7 @@ class Livetext::Expansion
|
|
69
85
|
rbrack = "\\]"
|
70
86
|
space_eol = "( |$)"
|
71
87
|
prx1 = "(?<param>[^ ]+)"
|
72
|
-
prx2 = "(?<param
|
88
|
+
prx2 = "(?<param>.*)"
|
73
89
|
pat2 = "(?<full_param>#{colon}#{prx1})"
|
74
90
|
pat3 = "(?<full_param>#{lbrack}#{prx2}#{rbrack})"
|
75
91
|
rx = Regexp.compile("#{pat1}(#{pat2}|#{pat3})?")
|
data/lib/livetext/formatter.rb
CHANGED
@@ -1,202 +1,189 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
def
|
4
|
-
|
5
|
-
s2 = Double.process(str.chomp)
|
6
|
-
s3 = Bracketed.process(s2)
|
7
|
-
s4 = Single.process(s3)
|
8
|
-
s4
|
9
|
-
end
|
10
|
-
|
11
|
-
## Hmmm...
|
12
|
-
#
|
13
|
-
# Double: b, i, t, s
|
14
|
-
# Single: bits
|
15
|
-
# Brackt: bits
|
16
|
-
#
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
class Livetext::Formatter::Delimited
|
21
|
-
def initialize(str, marker, tag) # Delimited
|
22
|
-
@str, @marker, @tag = str.dup, marker, tag
|
23
|
-
@buffer = ""
|
24
|
-
@cdata = ""
|
25
|
-
@state = :INITIAL
|
1
|
+
# Formatter - Centralized text formatting for Livetext
|
2
|
+
class Livetext::Formatter
|
3
|
+
def initialize(parent)
|
4
|
+
@parent = parent
|
26
5
|
end
|
27
6
|
|
28
|
-
def
|
29
|
-
if
|
30
|
-
|
31
|
-
|
32
|
-
|
7
|
+
def format(text)
|
8
|
+
return "" if text.nil? || text.empty?
|
9
|
+
|
10
|
+
text = text.chomp
|
11
|
+
# First, mark escaped characters so they won't be processed as formatting
|
12
|
+
text = mark_escaped_characters(text)
|
13
|
+
|
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)
|
21
|
+
|
22
|
+
text = unmark_escaped_characters(text)
|
23
|
+
text
|
33
24
|
end
|
34
25
|
|
35
|
-
def
|
36
|
-
|
26
|
+
def format_line(line)
|
27
|
+
return "" if line.nil?
|
28
|
+
format(line.chomp)
|
37
29
|
end
|
38
30
|
|
39
|
-
def
|
40
|
-
|
41
|
-
char
|
31
|
+
def format_multiple(lines)
|
32
|
+
lines.map { |line| format_line(line) }
|
42
33
|
end
|
43
34
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
# Don't? what if searching for space_marker?
|
48
|
-
# @buffer << grab
|
35
|
+
# Convenience methods for common formatting patterns
|
36
|
+
def bold(text)
|
37
|
+
"<b>#{text}</b>"
|
49
38
|
end
|
50
39
|
|
51
|
-
def
|
52
|
-
|
40
|
+
def italic(text)
|
41
|
+
"<i>#{text}</i>"
|
53
42
|
end
|
54
43
|
|
55
|
-
def
|
56
|
-
|
44
|
+
def code(text)
|
45
|
+
"<tt>#{text}</tt>"
|
57
46
|
end
|
58
47
|
|
59
|
-
def
|
60
|
-
|
48
|
+
def strike(text)
|
49
|
+
"<strike>#{text}</strike>"
|
61
50
|
end
|
62
51
|
|
63
|
-
def
|
64
|
-
|
52
|
+
def link(text, url)
|
53
|
+
"<a href='#{url}'>#{text}</a>"
|
65
54
|
end
|
66
55
|
|
67
|
-
def
|
68
|
-
|
56
|
+
def escape_html(text)
|
57
|
+
text.gsub(/[&<>"']/) do |char|
|
58
|
+
case char
|
59
|
+
when '&' then '&'
|
60
|
+
when '<' then '<'
|
61
|
+
when '>' then '>'
|
62
|
+
when '"' then '"'
|
63
|
+
when "'" then '''
|
64
|
+
else char
|
65
|
+
end
|
66
|
+
end
|
69
67
|
end
|
70
68
|
|
71
|
-
|
72
|
-
@str.start_with?(" " + @marker)
|
73
|
-
end
|
69
|
+
private
|
74
70
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
result = "" if @marker[1] == "["
|
79
|
-
return result
|
80
|
-
end
|
81
|
-
"<#{@tag}>#{text}</#{@tag}>"
|
82
|
-
end
|
83
|
-
|
84
|
-
def initial
|
85
|
-
n = @marker.length
|
86
|
-
case
|
87
|
-
when escape?
|
88
|
-
grab # backslash
|
89
|
-
@buffer << grab # char
|
90
|
-
when space_marker?
|
91
|
-
@buffer << grab # append the space
|
92
|
-
grab(n) # eat the marker
|
93
|
-
@state = :CDATA
|
94
|
-
when marker?
|
95
|
-
grab(n) # Eat the marker
|
96
|
-
@state = :CDATA
|
97
|
-
when eol?
|
98
|
-
@state = :FINAL
|
99
|
-
else
|
100
|
-
@state = :BUFFER
|
101
|
-
end
|
71
|
+
def mark_escaped_characters(str)
|
72
|
+
# Replace escaped characters with a null byte marker (safe for internal use)
|
73
|
+
str.gsub(/\\([*_`~])/, "\u0000\\1")
|
102
74
|
end
|
103
75
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
76
|
+
def unmark_escaped_characters(str)
|
77
|
+
# Restore escaped characters
|
78
|
+
str.gsub(/\u0000([*_`~])/, '\1')
|
107
79
|
end
|
108
80
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
81
|
+
def handle_double_markers(str)
|
82
|
+
# **word -> <b>word</b> (terminated by space, comma, period)
|
83
|
+
# But ignore standalone ** or ** surrounded by spaces
|
84
|
+
str.gsub(/(?<=\s|^)\*\*([^\s,.]*)/) do |match|
|
85
|
+
if $1.empty?
|
86
|
+
"**" # standalone ** should be literal
|
114
87
|
else
|
115
|
-
|
88
|
+
"<b>#{$1}</b>"
|
116
89
|
end
|
117
|
-
@state = :FINAL
|
118
|
-
when terminated?
|
119
|
-
@buffer << wrap(@cdata)
|
120
|
-
grab_terminator # "*a *b" case???
|
121
|
-
@cdata = ""
|
122
|
-
@state = :LOOPING
|
123
|
-
else
|
124
|
-
@cdata << grab
|
125
|
-
@state = :CDATA
|
126
90
|
end
|
127
91
|
end
|
128
92
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
93
|
+
def handle_bracketed_markers(str)
|
94
|
+
# Handle all bracketed markers: *[content], _[content], `[content]
|
95
|
+
# *[content] -> <b>content</b>
|
96
|
+
# _[content] -> <i>content</i>
|
97
|
+
# `[content] -> <tt>content</tt>
|
98
|
+
# And handle unclosed brackets with end-of-line termination
|
99
|
+
# Empty brackets disappear
|
100
|
+
# But ignore if the marker was originally escaped
|
101
|
+
# And ignore embedded markers (like abc*[)
|
102
|
+
|
103
|
+
# First handle complete brackets for all marker types
|
104
|
+
str = str.gsub(/(?<!\u0000)([*_`])\[([^\]]*)\]/) do |match|
|
105
|
+
marker, content = $1, $2
|
106
|
+
if content.empty?
|
107
|
+
"" # empty brackets disappear
|
108
|
+
else
|
109
|
+
case marker
|
110
|
+
when "*" then "<b>#{content}</b>"
|
111
|
+
when "_" then "<i>#{content}</i>"
|
112
|
+
when "`" then "<tt>#{content}</tt>"
|
113
|
+
else match # fallback
|
114
|
+
end
|
115
|
+
end
|
143
116
|
end
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
117
|
+
|
118
|
+
# Then handle unclosed brackets (end of line replaces closing bracket)
|
119
|
+
# But only if it's at start of line or preceded by whitespace
|
120
|
+
str = str.gsub(/(?<!\u0000)(?<=\s|^)([*_`])\[([^\]]*)$/) do |match|
|
121
|
+
marker, content = $1, $2
|
122
|
+
if content.empty?
|
123
|
+
"" # standalone marker[ disappears
|
124
|
+
else
|
125
|
+
case marker
|
126
|
+
when "*" then "<b>#{content}</b>"
|
127
|
+
when "_" then "<i>#{content}</i>"
|
128
|
+
when "`" then "<tt>#{content}</tt>"
|
129
|
+
else match # fallback
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
str
|
135
|
+
end
|
136
|
+
|
137
|
+
def handle_single_markers(str)
|
138
|
+
# *word -> <b>word</b> (only at start of word or after space)
|
139
|
+
# But ignore standalone * or * surrounded by spaces
|
140
|
+
# Also ignore * that are part of ** patterns (already processed)
|
141
|
+
# And ignore * that are part of *[ patterns (already processed)
|
142
|
+
str.gsub(/(?<=\s|^)\*(?!\[)([^\s]*)/) do |match|
|
143
|
+
if $1.empty?
|
144
|
+
"*" # standalone * should be literal
|
145
|
+
elsif $1.start_with?('*')
|
146
|
+
# This is part of a ** pattern, leave it as literal
|
147
|
+
match
|
148
|
+
else
|
149
|
+
"<b>#{$1}</b>"
|
150
|
+
end
|
151
151
|
end
|
152
|
-
return @buffer
|
153
|
-
end
|
154
|
-
|
155
|
-
def self.process(str)
|
156
|
-
bold = self.new(str, "*", "b")
|
157
|
-
sb = bold.handle
|
158
|
-
# return sb
|
159
|
-
ital = self.new(sb, "_", "i")
|
160
|
-
si = ital.handle
|
161
|
-
code = self.new(si, "`", "tt")
|
162
|
-
sc = code.handle
|
163
|
-
stri = self.new(sc, "~", "strike")
|
164
|
-
si = stri.handle
|
165
|
-
si
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
class Livetext::Formatter::Single < Livetext::Formatter::Delimited
|
170
|
-
# Yeah, this one is that simple
|
171
|
-
end
|
172
|
-
|
173
|
-
class Livetext::Formatter::Double < Livetext::Formatter::Delimited
|
174
|
-
def initialize(str, sigil, tag) # Double
|
175
|
-
super
|
176
|
-
# Convention: marker is "**", sigil is "*"
|
177
|
-
@marker = sigil + sigil
|
178
|
-
end
|
179
|
-
|
180
|
-
def terminated?
|
181
|
-
terms = [" ", ".", ","]
|
182
|
-
terms.include?(front)
|
183
152
|
end
|
184
|
-
end
|
185
153
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
154
|
+
def handle_underscore_markers(str)
|
155
|
+
# _word -> <i>word</i> (only at start of word or after space)
|
156
|
+
# But ignore standalone _ or _ surrounded by spaces
|
157
|
+
str.gsub(/(?<=\s|^)_([^\s]*)/) do |match|
|
158
|
+
if $1.empty?
|
159
|
+
"_" # standalone _ should be literal
|
160
|
+
else
|
161
|
+
"<i>#{$1}</i>"
|
162
|
+
end
|
163
|
+
end
|
191
164
|
end
|
192
165
|
|
193
|
-
def
|
194
|
-
|
166
|
+
def handle_backtick_markers(str)
|
167
|
+
# `word -> <tt>word</tt> (only at start of word or after space)
|
168
|
+
# But ignore standalone ` or ` surrounded by spaces
|
169
|
+
str.gsub(/(?<=\s|^)`([^\s]*)/) do |match|
|
170
|
+
if $1.empty?
|
171
|
+
"`" # standalone ` should be literal
|
172
|
+
else
|
173
|
+
"<tt>#{$1}</tt>"
|
174
|
+
end
|
175
|
+
end
|
195
176
|
end
|
196
177
|
|
197
|
-
def
|
198
|
-
|
199
|
-
|
178
|
+
def handle_tilde_markers(str)
|
179
|
+
# ~word -> <strike>word</strike> (only at start of word or after space)
|
180
|
+
# But ignore standalone ~ or ~ surrounded by spaces
|
181
|
+
str.gsub(/(?<=\s|^)~([^\s]*)/) do |match|
|
182
|
+
if $1.empty?
|
183
|
+
"~" # standalone ~ should be literal
|
184
|
+
else
|
185
|
+
"<strike>#{$1}</strike>"
|
186
|
+
end
|
187
|
+
end
|
200
188
|
end
|
201
189
|
end
|
202
|
-
|