livetext 0.9.51 → 0.9.52

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1d0ef585b57b9cf3ffc439ab8dc44d67ba5b287219920121694d06a78558872
4
- data.tar.gz: a73d887e9f053977c1c96b0b7ac8b1eb4bd8976180e5fee18ecb5ef5b5ca1e65
3
+ metadata.gz: 4847cd14621a75b3cfa04c887b486a2bfc40ba1d631fb2cbb59730a70a77ea5d
4
+ data.tar.gz: d1ca67209314868f986283e1999c3f6225c1335f5391813c59941af6623a258f
5
5
  SHA512:
6
- metadata.gz: 4ceda09db8d0819b550c485af5fb3d5f6da2be17ce879a07c1199c31554f76550bebaf8bcc77e834ff20466eb859d3712996fd23d0c6812aba799809f6a66ada
7
- data.tar.gz: 97c76c0a601541d78afa39da976fb966ee3a7917c065eb5589c70b755e8120f89916edbc7eacc72f02c4f76edcbadd839ed545e482b097e970e87602b59b4ecf
6
+ metadata.gz: c71410f3eff32c91da1ba414966f01f00acc82ae92184a633137f9d1ba79bee5c9a282dbd7ba4db2cb5511d83859a458c88e006ada07e29dace6edfc76784c96
7
+ data.tar.gz: c8018eaead5bcf4dbffe756c0d6aad2dd1a3cfe2f319b9aefa0ae636ce7828e7528f992156c873a8285ae01a2724654ddbcf9ad8c4bfa84d72159d3774806eb4
@@ -55,10 +55,26 @@ class Livetext::Expansion
55
55
  def funcall(name, param)
56
56
  err = "[Error evaluating $$#{name}(#{param})]"
57
57
  name = name.gsub(/\./, "__")
58
- return if self.send?(name, param)
58
+
59
+ # First check Livetext::Functions (for predefined and inline functions)
59
60
  fobj = ::Livetext::Functions.new
60
- result = fobj.send(name, param) rescue err
61
- result.to_s
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
72
+ end
73
+ return result.to_s
74
+ end
75
+
76
+ # If not found anywhere
77
+ err
62
78
  end
63
79
 
64
80
  def expand_function_calls(str)
@@ -1,24 +1,148 @@
1
1
  module Livetext::Formatter
2
2
 
3
- def self.format(str) # FIXME - unneeded?
3
+ def self.format(str)
4
4
  str = str.chomp
5
- s2 = Double.process(str.chomp)
6
- s3 = Bracketed.process(s2)
7
- s4 = Single.process(s3)
8
- s4
5
+ # First, mark escaped characters so they won't be processed as formatting
6
+ str = mark_escaped_characters(str)
7
+
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)
15
+
16
+ str = unmark_escaped_characters(str)
17
+ str
18
+ end
19
+
20
+ private
21
+
22
+ def self.mark_escaped_characters(str)
23
+ # Replace escaped characters with a null byte marker (safe for internal use)
24
+ str.gsub(/\\([*_`~])/, "\u0000\\1")
25
+ end
26
+
27
+ def self.unmark_escaped_characters(str)
28
+ # Restore escaped characters
29
+ str.gsub(/\u0000([*_`~])/, '\1')
30
+ end
31
+
32
+ def self.handle_double_markers(str)
33
+ # **word -> <b>word</b> (terminated by space, comma, period)
34
+ # But ignore standalone ** or ** surrounded by spaces
35
+ str.gsub(/(?<=\s|^)\*\*([^\s,.]*)/) do |match|
36
+ if $1.empty?
37
+ "**" # standalone ** should be literal
38
+ else
39
+ "<b>#{$1}</b>"
40
+ end
41
+ end
42
+ end
43
+
44
+ def self.handle_bracketed_markers(str)
45
+ # Handle all bracketed markers: *[content], _[content], `[content]
46
+ # *[content] -> <b>content</b>
47
+ # _[content] -> <i>content</i>
48
+ # `[content] -> <tt>content</tt>
49
+ # And handle unclosed brackets with end-of-line termination
50
+ # Empty brackets disappear
51
+ # But ignore if the marker was originally escaped
52
+ # And ignore embedded markers (like abc*[)
53
+
54
+ # First handle complete brackets for all marker types
55
+ str = str.gsub(/(?<!\u0000)([*_`])\[([^\]]*)\]/) do |match|
56
+ marker, content = $1, $2
57
+ if content.empty?
58
+ "" # empty brackets disappear
59
+ else
60
+ case marker
61
+ when "*" then "<b>#{content}</b>"
62
+ when "_" then "<i>#{content}</i>"
63
+ when "`" then "<tt>#{content}</tt>"
64
+ else match # fallback
65
+ end
66
+ end
67
+ end
68
+
69
+ # Then handle unclosed brackets (end of line replaces closing bracket)
70
+ # But only if it's at start of line or preceded by whitespace
71
+ str = str.gsub(/(?<!\u0000)(?<=\s|^)([*_`])\[([^\]]*)$/) do |match|
72
+ marker, content = $1, $2
73
+ if content.empty?
74
+ "" # standalone marker[ disappears
75
+ else
76
+ case marker
77
+ when "*" then "<b>#{content}</b>"
78
+ when "_" then "<i>#{content}</i>"
79
+ when "`" then "<tt>#{content}</tt>"
80
+ else match # fallback
81
+ end
82
+ end
83
+ end
84
+
85
+ str
86
+ end
87
+
88
+ def self.handle_single_markers(str)
89
+ # *word -> <b>word</b> (only at start of word or after space)
90
+ # But ignore standalone * or * surrounded by spaces
91
+ # Also ignore * that are part of ** patterns (already processed)
92
+ # And ignore * that are part of *[ patterns (already processed)
93
+ str.gsub(/(?<=\s|^)\*(?!\[)([^\s]*)/) do |match|
94
+ if $1.empty?
95
+ "*" # standalone * should be literal
96
+ elsif $1.start_with?('*')
97
+ # This is part of a ** pattern, leave it as literal
98
+ match
99
+ else
100
+ "<b>#{$1}</b>"
101
+ end
102
+ end
103
+ end
104
+
105
+ def self.handle_underscore_markers(str)
106
+ # _word -> <i>word</i> (only at start of word or after space)
107
+ # But ignore standalone _ or _ surrounded by spaces
108
+ str.gsub(/(?<=\s|^)_([^\s]*)/) do |match|
109
+ if $1.empty?
110
+ "_" # standalone _ should be literal
111
+ else
112
+ "<i>#{$1}</i>"
113
+ end
114
+ end
115
+ end
116
+
117
+ def self.handle_backtick_markers(str)
118
+ # `word -> <tt>word</tt> (only at start of word or after space)
119
+ # But ignore standalone ` or ` surrounded by spaces
120
+ str.gsub(/(?<=\s|^)`([^\s]*)/) do |match|
121
+ if $1.empty?
122
+ "`" # standalone ` should be literal
123
+ else
124
+ "<tt>#{$1}</tt>"
125
+ end
126
+ end
127
+ end
128
+
129
+ def self.handle_tilde_markers(str)
130
+ # ~word -> <strike>word</strike> (only at start of word or after space)
131
+ # But ignore standalone ~ or ~ surrounded by spaces
132
+ str.gsub(/(?<=\s|^)~([^\s]*)/) do |match|
133
+ if $1.empty?
134
+ "~" # standalone ~ should be literal
135
+ else
136
+ "<strike>#{$1}</strike>"
137
+ end
138
+ end
9
139
  end
10
-
11
- ## Hmmm...
12
- #
13
- # Double: b, i, t, s
14
- # Single: bits
15
- # Brackt: bits
16
- #
17
140
 
18
141
  end
19
142
 
143
+ # Legacy classes - kept for compatibility but not used
20
144
  class Livetext::Formatter::Delimited
21
- def initialize(str, marker, tag) # Delimited
145
+ def initialize(str, marker, tag)
22
146
  @str, @marker, @tag = str.dup, marker, tag
23
147
  @buffer = ""
24
148
  @cdata = ""
@@ -37,15 +161,12 @@ class Livetext::Formatter::Delimited
37
161
  end
38
162
 
39
163
  def grab(n=1)
40
- char = @str.slice!(0..(n-1)) # grab n chars
164
+ char = @str.slice!(0..(n-1))
41
165
  char
42
166
  end
43
167
 
44
168
  def grab_terminator
45
169
  @state = :LOOPING
46
- # goes onto buffer by default
47
- # Don't? what if searching for space_marker?
48
- # @buffer << grab
49
170
  end
50
171
 
51
172
  def eol?
@@ -61,7 +182,7 @@ class Livetext::Formatter::Delimited
61
182
  end
62
183
 
63
184
  def terminated?
64
- space? # Will be overridden except in Single
185
+ space?
65
186
  end
66
187
 
67
188
  def marker?
@@ -85,14 +206,14 @@ class Livetext::Formatter::Delimited
85
206
  n = @marker.length
86
207
  case
87
208
  when escape?
88
- grab # backslash
89
- @buffer << grab # char
209
+ grab
210
+ @buffer << grab
90
211
  when space_marker?
91
- @buffer << grab # append the space
92
- grab(n) # eat the marker
212
+ @buffer << grab
213
+ grab(n)
93
214
  @state = :CDATA
94
215
  when marker?
95
- grab(n) # Eat the marker
216
+ grab(n)
96
217
  @state = :CDATA
97
218
  when eol?
98
219
  @state = :FINAL
@@ -117,7 +238,7 @@ class Livetext::Formatter::Delimited
117
238
  @state = :FINAL
118
239
  when terminated?
119
240
  @buffer << wrap(@cdata)
120
- grab_terminator # "*a *b" case???
241
+ grab_terminator
121
242
  @cdata = ""
122
243
  @state = :LOOPING
123
244
  else
@@ -130,15 +251,15 @@ class Livetext::Formatter::Delimited
130
251
  n = @marker.length
131
252
  case
132
253
  when escape?
133
- grab # backslash
134
- @buffer << grab # char
254
+ grab
255
+ @buffer << grab
135
256
  when space_marker?
136
- @buffer << grab # append the space
137
- grab(n) # eat the marker
257
+ @buffer << grab
258
+ grab(n)
138
259
  @state = :CDATA
139
260
  when eol?
140
261
  @state = :FINAL
141
- else # includes marker not preceded by space!
262
+ else
142
263
  @buffer << grab
143
264
  end
144
265
  end
@@ -155,7 +276,6 @@ class Livetext::Formatter::Delimited
155
276
  def self.process(str)
156
277
  bold = self.new(str, "*", "b")
157
278
  sb = bold.handle
158
- # return sb
159
279
  ital = self.new(sb, "_", "i")
160
280
  si = ital.handle
161
281
  code = self.new(si, "`", "tt")
@@ -167,13 +287,11 @@ class Livetext::Formatter::Delimited
167
287
  end
168
288
 
169
289
  class Livetext::Formatter::Single < Livetext::Formatter::Delimited
170
- # Yeah, this one is that simple
171
290
  end
172
291
 
173
292
  class Livetext::Formatter::Double < Livetext::Formatter::Delimited
174
- def initialize(str, sigil, tag) # Double
293
+ def initialize(str, sigil, tag)
175
294
  super
176
- # Convention: marker is "**", sigil is "*"
177
295
  @marker = sigil + sigil
178
296
  end
179
297
 
@@ -184,9 +302,8 @@ class Livetext::Formatter::Double < Livetext::Formatter::Delimited
184
302
  end
185
303
 
186
304
  class Livetext::Formatter::Bracketed < Livetext::Formatter::Delimited
187
- def initialize(str, sigil, tag) # Bracketed
305
+ def initialize(str, sigil, tag)
188
306
  super
189
- # Convention: marker is "*[", sigil is "*"
190
307
  @marker = sigil + "["
191
308
  end
192
309
 
@@ -2,5 +2,5 @@
2
2
  # Defining VERSION
3
3
 
4
4
  class Livetext
5
- VERSION = "0.9.51"
5
+ VERSION = "0.9.52"
6
6
  end
@@ -41,13 +41,11 @@ end
41
41
  output.puts <<~RUBY
42
42
  require 'minitest/autorun'
43
43
 
44
- MiniTest = Minitest unless defined?(MiniTest)
45
-
46
44
  require 'livetext'
47
45
 
48
46
  # Just another testing class. Chill.
49
47
 
50
- class TestingLivetext#{classname} < MiniTest::Test
48
+ class TestingLivetext#{classname} < Minitest::Test
51
49
 
52
50
  def setup
53
51
  @live = Livetext.new
File without changes
@@ -0,0 +1,18 @@
1
+ Testing functions defined in external .rb files
2
+ <p>
3
+
4
+ Now calling a function defined in the .rb file:
5
+ External function called with: test_parameter
6
+ <p>
7
+
8
+ And another one with brackets:
9
+ Another function called with: with brackets
10
+ <p>
11
+
12
+ And a function with no parameters:
13
+ Simple function with no parameters
14
+ <p>
15
+
16
+ That's all.
17
+ <p>
18
+
@@ -0,0 +1,11 @@
1
+ def external_func(param)
2
+ "External function called with: #{param}"
3
+ end
4
+
5
+ def another_func(param)
6
+ "Another function called with: #{param}"
7
+ end
8
+
9
+ def simple_func
10
+ "Simple function with no parameters"
11
+ end
@@ -0,0 +1,15 @@
1
+ Testing functions defined in external .rb files
2
+
3
+ .mixin mixin_functions
4
+
5
+ Now calling a function defined in the .rb file:
6
+ $$external_func:test_parameter
7
+
8
+ And another one with brackets:
9
+ $$another_func[with brackets]
10
+
11
+ And a function with no parameters:
12
+ $$simple_func
13
+
14
+ That's all.
15
+
data/test/snapshots.rb CHANGED
@@ -9,8 +9,6 @@
9
9
 
10
10
  require 'minitest/autorun'
11
11
 
12
- MiniTest = Minitest unless defined?(MiniTest)
13
-
14
12
  require_relative '../lib/livetext'
15
13
 
16
14
  =begin
@@ -45,7 +43,7 @@ It works this way:
45
43
 
46
44
  # Just a testing class. Chill.
47
45
 
48
- class TestingLivetext < MiniTest::Test
46
+ class TestingLivetext < Minitest::Test
49
47
 
50
48
  class Snapshot
51
49
  SOURCE = "source.lt3"
@@ -67,9 +65,10 @@ class TestingLivetext < MiniTest::Test
67
65
  end
68
66
 
69
67
  Args = ARGV - ["cmdline"]
68
+ cmdline = ARGV.first == "cmdline"
70
69
  dir = self.get_dir
71
70
  # Data = "#{dir}/test/snapshots"
72
- Data = "../../test/snapshots"
71
+ Data = cmdline ? "../../test/snapshots" : "test/snapshots"
73
72
  Dir.chdir(Data)
74
73
  TestDirs = Dir.entries(".").reject {|fname| ! File.directory?(fname) } - %w[. ..]
75
74
 
@@ -0,0 +1 @@
1
+ \*escaped
@@ -1,12 +1,12 @@
1
1
  require 'minitest/autorun'
2
2
 
3
- MiniTest = Minitest unless defined?(MiniTest)
3
+
4
4
 
5
5
  require 'livetext'
6
6
 
7
7
  # Just another testing class. Chill.
8
8
 
9
- class TestingLivetextBracketed < MiniTest::Test
9
+ class TestingLivetextBracketed < Minitest::Test
10
10
 
11
11
  def setup
12
12
  @live = Livetext.new
data/test/unit/double.rb CHANGED
@@ -1,12 +1,10 @@
1
1
  require 'minitest/autorun'
2
2
 
3
- MiniTest = Minitest unless defined?(MiniTest)
4
-
5
3
  require 'livetext'
6
4
 
7
5
  # Just another testing class. Chill.
8
6
 
9
- class TestingLivetextDouble < MiniTest::Test
7
+ class TestingLivetextDouble < Minitest::Test
10
8
 
11
9
  def setup
12
10
  @live = Livetext.new
@@ -1,12 +1,12 @@
1
1
  require 'minitest/autorun'
2
2
 
3
- MiniTest = Minitest unless defined?(MiniTest)
3
+
4
4
 
5
5
  require 'livetext'
6
6
 
7
7
  # Just another testing class. Chill.
8
8
 
9
- class TestingLivetextFunctions < MiniTest::Test
9
+ class TestingLivetextFunctions < Minitest::Test
10
10
 
11
11
  def setup
12
12
  @live = Livetext.new
data/test/unit/html.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'minitest/autorun'
2
2
 
3
- MiniTest = Minitest unless defined?(MiniTest)
3
+
4
4
 
5
5
  require 'livetext'
6
6
 
7
- class TestingLivetext < MiniTest::Test
7
+ class TestingLivetext < Minitest::Test
8
8
  include Livetext::Standard
9
9
 
10
10
  # Some of these methods being tested "really" belong elsewhere?
@@ -1,13 +1,13 @@
1
1
 
2
2
  require 'minitest/autorun'
3
3
 
4
- MiniTest = Minitest unless defined?(MiniTest)
4
+
5
5
 
6
6
  require_relative '../parser' # nested
7
7
 
8
8
  ParseGeneral = ::Livetext::ParseGeneral
9
9
 
10
- class TestParseGeneral < MiniTest::Test
10
+ class TestParseGeneral < Minitest::Test
11
11
 
12
12
  def setup
13
13
  end
@@ -1,11 +1,11 @@
1
1
 
2
2
  require 'minitest/autorun'
3
3
 
4
- MiniTest = Minitest unless defined?(MiniTest)
4
+
5
5
 
6
6
  require_relative '../parser' # nested
7
7
 
8
- class TestParseSet < MiniTest::Test
8
+ class TestParseSet < Minitest::Test
9
9
 
10
10
  def setup
11
11
  end
@@ -1,13 +1,13 @@
1
1
 
2
2
  require 'minitest/autorun'
3
3
 
4
- MiniTest = Minitest unless defined?(MiniTest)
4
+
5
5
 
6
6
  require_relative '../parser' # nested
7
7
 
8
8
  ParseSet = ::Livetext::ParseSet
9
9
 
10
- class TestParseSet < MiniTest::Test
10
+ class TestParseSet < Minitest::Test
11
11
 
12
12
  def setup
13
13
  end
@@ -1,10 +1,10 @@
1
1
  require 'minitest/autorun'
2
2
 
3
- MiniTest = Minitest unless defined?(MiniTest)
3
+
4
4
 
5
5
  require_relative '../parser' # nested
6
6
 
7
- class TestStringParser < MiniTest::Test
7
+ class TestStringParser < Minitest::Test
8
8
 
9
9
  def setup
10
10
  # Lengths: zero, one, arbitrary
data/test/unit/single.rb CHANGED
@@ -1,12 +1,10 @@
1
1
  require 'minitest/autorun'
2
2
 
3
- MiniTest = Minitest unless defined?(MiniTest)
4
-
5
3
  require 'livetext'
6
4
 
7
5
  # Just another testing class. Chill.
8
6
 
9
- class TestingLivetextSingle < MiniTest::Test
7
+ class TestingLivetextSingle < Minitest::Test
10
8
  def setup
11
9
  @live = Livetext.new
12
10
  end
@@ -1,10 +1,8 @@
1
1
  require 'minitest/autorun'
2
2
 
3
- MiniTest = Minitest
4
-
5
3
  require_relative '../../lib/livetext'
6
4
 
7
- class TestingLivetext < MiniTest::Test
5
+ class TestingLivetext < Minitest::Test
8
6
  include Livetext::Standard
9
7
 
10
8
  # Only method here "really" belongs elsewhere? FIXME
@@ -1,10 +1,10 @@
1
1
  require 'minitest/autorun'
2
2
 
3
- MiniTest = Minitest unless defined?(MiniTest)
3
+
4
4
 
5
5
  require_relative '../../lib/stringparser'
6
6
 
7
- class TestStringParser < MiniTest::Test
7
+ class TestStringParser < Minitest::Test
8
8
 
9
9
  def setup
10
10
  # Lengths: zero, one, arbitrary
@@ -1,12 +1,12 @@
1
1
  require 'minitest/autorun'
2
2
 
3
- MiniTest = Minitest unless defined?(MiniTest)
3
+
4
4
 
5
5
  require 'livetext'
6
6
 
7
7
  # Just another testing class. Chill.
8
8
 
9
- class TestingLivetextVariables < MiniTest::Test
9
+ class TestingLivetextVariables < Minitest::Test
10
10
 
11
11
  def setup
12
12
  @live = Livetext.new
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: livetext
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.51
4
+ version: 0.9.52
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hal Fulton
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-22 00:00:00.000000000 Z
10
+ date: 2025-08-06 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: A smart text processor extensible in Ruby
13
13
  email: rubyhacker@gmail.com
@@ -139,6 +139,10 @@ files:
139
139
  - test/snapshots/mixin_booktool/expected-output.txt
140
140
  - test/snapshots/mixin_booktool/source.lt3
141
141
  - test/snapshots/mixin_booktool/toc.tmp
142
+ - test/snapshots/mixin_functions/expected-error.txt
143
+ - test/snapshots/mixin_functions/expected-output.txt
144
+ - test/snapshots/mixin_functions/mixin_functions.rb
145
+ - test/snapshots/mixin_functions/source.lt3
142
146
  - test/snapshots/more_complex_vars/expected-error.txt
143
147
  - test/snapshots/more_complex_vars/expected-output.txt
144
148
  - test/snapshots/more_complex_vars/source.lt3
@@ -185,6 +189,7 @@ files:
185
189
  - test/snapshots/var_into_func/expected-error.txt
186
190
  - test/snapshots/var_into_func/expected-output.txt
187
191
  - test/snapshots/var_into_func/source.lt3
192
+ - test/test_escape.lt3
188
193
  - test/unit/all.rb
189
194
  - test/unit/bracketed.rb
190
195
  - test/unit/double.rb