livetext 0.9.60 → 0.9.62

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: 63cd6b938fdb29f4b900ffb1ded5d378a9ae5d9c6274ec593f8593c2bd47e718
4
- data.tar.gz: d62dcbfc69af01c7dc3d15608e2b95c6c3abc0347769fac24b8b600727608f35
3
+ metadata.gz: 61c11b22070e565c5c25735ea1134d58249a5f441f0ed3c1f9eeb0f8bbf7c628
4
+ data.tar.gz: feff45c934b1ecb8757dcd1ea3fad4947e7383063faa9f3f68506dc73c99ff9e
5
5
  SHA512:
6
- metadata.gz: fb08277e4f424959940ba49f8dee3edc65347038a046b46235b618f70a11dfd92932316f325f83f2930938dd97bee26eae172e17951918638137055fc92599c6
7
- data.tar.gz: 9834c220e9775071b4e2d5769e5aa0b518e0de760940db1d10a837024f879eb449b98da8eff90f9a2696277f780b6e8baa7595439815411c70795ef865328c12
6
+ metadata.gz: 6e9fa6aeec991337d703e223f20daef4b65b0d261be3cdcfbce99854ab263c0660e2288de725411ba3eaa29c1e8e65ae421dddd433c187e813b10b052f658f1b
7
+ data.tar.gz: f899d4de12921521b8d06adb35d10ed1f530cab3f9bc1d8c665ddb9bd7eb54b397d6bf7bc0a8686ecdfbd71a9aba18b470da0da38f2687d09ca03ba662b30f7b
data/lib/livetext/core.rb CHANGED
@@ -60,13 +60,13 @@ class Livetext
60
60
  def initialize(output = ::STDOUT) # Livetext
61
61
  @body = ""
62
62
  @indentation = [0]
63
+ @function_registry = Livetext::FunctionRegistry.new
64
+ @variables = Livetext::VariableManager.new(self)
65
+ @formatter = Livetext::Formatter.new(self)
63
66
  @api = UserAPI.new(self)
64
67
  @output = ::Livetext.output = output
65
68
  @html = Livetext::HTML.new(@api)
66
69
  @sources = []
67
- @function_registry = Livetext::FunctionRegistry.new
68
- @variables = Livetext::VariableManager.new(self)
69
- @formatter = Livetext::Formatter.new(self)
70
70
  # puts "------ init: self = "
71
71
  # p self
72
72
  end
@@ -0,0 +1,36 @@
1
+ # FunctionCaller - Provides simple method-style access to Livetext functions
2
+ class Livetext::FunctionCaller
3
+ def initialize(function_registry, api)
4
+ @registry = function_registry
5
+ @api = api
6
+ end
7
+
8
+ # Dynamically handle method calls to function names
9
+ def method_missing(name, *args)
10
+ # Convert method name to string and call the function registry
11
+ function_name = name.to_s
12
+ param = args.first || ""
13
+
14
+ # Set api on Livetext::Functions so all functions can access it
15
+ Livetext::Functions.api = @api
16
+
17
+ @registry.call(function_name, param)
18
+ rescue => e
19
+ "[Error calling function #{function_name}: #{e.message}]"
20
+ end
21
+
22
+ # Check if a function exists
23
+ def respond_to_missing?(name, include_private = false)
24
+ @registry.function_exists?(name.to_s) || super
25
+ end
26
+
27
+ # List all available functions
28
+ def list
29
+ @registry.list_functions.map { |f| f[:name] }
30
+ end
31
+
32
+ # Check if a specific function exists
33
+ def exists?(name)
34
+ @registry.function_exists?(name.to_s)
35
+ end
36
+ end
@@ -5,7 +5,7 @@ class Livetext::FunctionRegistry
5
5
  @builtin_functions = {}
6
6
  @metadata = {}
7
7
  register_builtin_functions
8
- puts "DEBUG: Registered #{@builtin_functions.size} builtin functions" if ENV['DEBUG']
8
+ # puts "DEBUG: Registered #{@builtin_functions.size} builtin functions" if ENV['DEBUG']
9
9
  end
10
10
 
11
11
  def register_user(name, function, source: :inline, filename: nil)
@@ -28,6 +28,18 @@ class Livetext::FunctionRegistry
28
28
  elsif @builtin_functions[name_sym]
29
29
  call_function(name, @builtin_functions[name_sym], param)
30
30
  else
31
+ # Fall back to Livetext::Functions for backward compatibility
32
+ fobj = ::Livetext::Functions.new
33
+ if fobj.respond_to?(name_sym)
34
+ method = fobj.method(name_sym)
35
+ if method.parameters.empty?
36
+ result = fobj.send(name_sym)
37
+ else
38
+ result = fobj.send(name_sym, param)
39
+ end
40
+ return result.to_s if result
41
+ end
42
+
31
43
  "[Error evaluating $$#{name}(#{param})]"
32
44
  end
33
45
  end
@@ -98,7 +110,7 @@ class Livetext::FunctionRegistry
98
110
  end
99
111
  end)
100
112
  register_builtin(:br, ->(param) do
101
- n = (param || "1").to_i
113
+ n = (param && !param.empty?) ? param.to_i : 1
102
114
  "<br>" * n
103
115
  end)
104
116
  register_builtin(:reverse, ->(param) do
@@ -9,6 +9,7 @@ class Livetext::Functions
9
9
 
10
10
  class << self
11
11
  attr_accessor :param # kill this?
12
+ attr_accessor :api
12
13
  end
13
14
 
14
15
  # Instance variables for accessing the Livetext instance and its variables
@@ -1,5 +1,6 @@
1
1
  require_relative 'expansion'
2
2
  require_relative 'html'
3
+ require_relative 'function_caller'
3
4
 
4
5
  # Encapsulate the UserAPI as a class
5
6
 
@@ -19,6 +20,7 @@ class Livetext::UserAPI
19
20
  @vars = live.vars
20
21
  @html = Livetext::HTML.new(self)
21
22
  @expander = Livetext::Expansion.new(live)
23
+ @funcs = Livetext::FunctionCaller.new(live.function_registry, self)
22
24
  end
23
25
 
24
26
  def api
@@ -53,7 +55,7 @@ class Livetext::UserAPI
53
55
  end
54
56
 
55
57
  def expand_functions(str)
56
- @expander.expand_functions(str)
58
+ @expander.expand_function_calls(str)
57
59
  end
58
60
 
59
61
  def setvar(var, val) # FIXME
@@ -244,5 +246,9 @@ class Livetext::UserAPI
244
246
  def debug(*args)
245
247
  TTY.puts *args if @live.debug
246
248
  end
249
+
250
+ def funcs
251
+ @funcs
252
+ end
247
253
  end
248
254
 
@@ -2,5 +2,5 @@
2
2
  # Defining VERSION
3
3
 
4
4
  class Livetext
5
- VERSION = "0.9.60"
5
+ VERSION = "0.9.62"
6
6
  end
File without changes
@@ -0,0 +1,18 @@
1
+ 1 <h3>Testing Simple Function API</h3>
2
+ 2 <p>Date: 2025-09-09</p>
3
+ 3 /<p>Time: \d{2}:\d{2}:\d{2}</p>/
4
+ 4 /<p>Directory: .*livetext.*</p>/
5
+ 5 <p>Platform: universal.x86_64-darwin22</p>
6
+ 6 <p>Ruby version: 2.6.10</p>
7
+ 7 <p>Livetext version: 0.9.61</p>
8
+ 8 <p>Reverse of 'hello': olleh</p>
9
+ 9 <p>Square root of 16: 4</p>
10
+ 10 /<p>Random \(1-10\): \d+</p>/
11
+ 11 /<p>Link: <a style='text-decoration: none' href='https://google\.com'>Google</a></p>/
12
+ 12 <p>Line breaks: <br><br></p>
13
+ 13 <p>Date exists: true</p>
14
+ 14 <p>Nonexistent exists: false</p>
15
+ 15 <p>Total functions: 23</p>
16
+ 16 /<p>First 5 functions: br, date, day, days_ago, days_from_now</p>/
17
+ 17 <p><strong>Simple API works!</strong></p>
18
+ 18 </p>
@@ -0,0 +1,32 @@
1
+ .dot_def test_simple_func_api args
2
+ api.out "<h3>Testing Simple Function API</h3>"
3
+
4
+ # Test basic function calls
5
+ api.out "<p>Date: #{api.funcs.date()}</p>"
6
+ api.out "<p>Time: #{api.funcs.time()}</p>"
7
+ api.out "<p>Directory: #{api.funcs.pwd()}</p>"
8
+ api.out "<p>Platform: #{api.funcs.platform()}</p>"
9
+ api.out "<p>Ruby version: #{api.funcs.ruby_version()}</p>"
10
+ api.out "<p>Livetext version: #{api.funcs.livetext_version()}</p>"
11
+
12
+ # Test functions with parameters
13
+ api.out "<p>Reverse of 'hello': #{api.funcs.reverse('hello')}</p>"
14
+ api.out "<p>Square root of 16: #{api.funcs.isqrt('16')}</p>"
15
+ api.out "<p>Random (1-10): #{api.funcs.rand('1 10')}</p>"
16
+ api.out "<p>Link: #{api.funcs.link('Google|https://google.com')}</p>"
17
+ api.out "<p>Line breaks: #{api.funcs.br('2')}</p>"
18
+
19
+ # Test function existence
20
+ api.out "<p>Date exists: #{api.funcs.exists?('date')}</p>"
21
+ api.out "<p>Nonexistent exists: #{api.funcs.exists?('nonexistent')}</p>"
22
+
23
+ # Test function listing
24
+ functions = api.funcs.list
25
+ api.out "<p>Total functions: #{functions.length}</p>"
26
+ api.out "<p>First 5 functions: #{functions.first(5).join(', ')}</p>"
27
+
28
+ api.out "<p><strong>Simple API works!</strong></p>"
29
+ .end
30
+
31
+ .test_simple_func_api
32
+
File without changes
@@ -3,7 +3,7 @@
3
3
  3 Hostname: HAL9000
4
4
  4 /Platform: .*x86_64-darwin\d+/
5
5
  5 /Ruby Version: \d+\.\d+\.\d+/
6
- 6 Livetext Version: 0.9.59
6
+ 6 Livetext Version: 0.9.61
7
7
  7 </p>
8
8
  8
9
9
  9 <p>
data/test/unit/all.rb CHANGED
@@ -10,6 +10,8 @@ require_relative 'variables'
10
10
  require_relative 'variable_manager'
11
11
  require_relative 'functions'
12
12
  require_relative 'function_registry'
13
+ require_relative 'function_caller'
14
+ require_relative 'function_api_access'
13
15
  require_relative 'mixin_functions_class'
14
16
  require_relative 'formatter'
15
17
  require_relative 'formatter_component'
@@ -0,0 +1,82 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../../lib/livetext'
3
+
4
+ class TestFunctionAPIAccess < Minitest::Test
5
+ def setup
6
+ @live = Livetext.new
7
+ @live.vars.set(:View, "test_view")
8
+ @live.vars.set(:"post.id", "0001")
9
+ end
10
+
11
+ def test_scriptorium_image_scenario
12
+ # This test mimics the exact scenario from Scriptorium's image function
13
+ # Define the asset function directly in Livetext::Functions (like in Scriptorium)
14
+ Livetext::Functions.class_eval do
15
+ def asset(param)
16
+ # Get view and post information (exactly like in Scriptorium)
17
+ vname = self.class.api.vars.to_h[:View]
18
+ postid = self.class.api.vars.to_h[:"post.id"]
19
+ "assets/#{postid}/#{param}" # Simplified version of the asset function
20
+ end
21
+ end
22
+
23
+ # Test calling it through api.funcs (like the image function in Scriptorium)
24
+ result = @live.api.funcs.asset("test.jpg")
25
+
26
+ assert_equal "assets/0001/test.jpg", result
27
+ end
28
+
29
+ def test_function_can_access_api_vars
30
+ # Define a function that needs access to api.vars (like the asset function in Scriptorium)
31
+ Livetext::Functions.class_eval do
32
+ def test_asset_function(param)
33
+ # This mimics the asset function from Scriptorium that needs api.vars
34
+ vname = self.class.api.vars.to_h[:View]
35
+ postid = self.class.api.vars.to_h[:"post.id"]
36
+ "view=#{vname}, post=#{postid}, file=#{param}"
37
+ end
38
+ end
39
+
40
+ # Test calling it through api.funcs (like the image function in Scriptorium)
41
+ result = @live.api.funcs.test_asset_function("test.jpg")
42
+
43
+ assert_equal "view=test_view, post=0001, file=test.jpg", result
44
+ end
45
+
46
+ def test_function_can_access_api_vars_without_params
47
+ # Define a function that needs access to api.vars but takes no parameters
48
+ Livetext::Functions.class_eval do
49
+ def test_view_info
50
+ vname = self.class.api.vars.to_h[:View]
51
+ postid = self.class.api.vars.to_h[:"post.id"]
52
+ "view=#{vname}, post=#{postid}"
53
+ end
54
+ end
55
+
56
+ # Test calling it through api.funcs
57
+ result = @live.api.funcs.test_view_info()
58
+
59
+ assert_equal "view=test_view, post=0001", result
60
+ end
61
+
62
+ def test_function_handles_missing_api_gracefully
63
+ # Define a function that tries to access api but it's not set
64
+ Livetext::Functions.class_eval do
65
+ def test_no_api
66
+ if self.class.api
67
+ "api available"
68
+ else
69
+ "no api"
70
+ end
71
+ end
72
+ end
73
+
74
+ # Clear the api to test the fallback
75
+ Livetext::Functions.api = nil
76
+
77
+ # Test calling it through api.funcs - should still work because we set it in method_missing
78
+ result = @live.api.funcs.test_no_api()
79
+
80
+ assert_equal "api available", result
81
+ end
82
+ end
@@ -0,0 +1,102 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../../lib/livetext'
3
+ require_relative '../../lib/livetext/function_caller'
4
+
5
+ class TestFunctionCaller < Minitest::Test
6
+ def setup
7
+ @registry = Livetext::FunctionRegistry.new
8
+ @api = Object.new # Mock api object for testing
9
+ @caller = Livetext::FunctionCaller.new(@registry, @api)
10
+ end
11
+
12
+ def test_calls_builtin_functions
13
+ # Test date function
14
+ result = @caller.date
15
+ assert_match(/\d{4}-\d{2}-\d{2}/, result)
16
+
17
+ # Test time function
18
+ result = @caller.time
19
+ assert_match(/\d{2}:\d{2}:\d{2}/, result)
20
+
21
+ # Test pwd function
22
+ result = @caller.pwd
23
+ assert_equal(Dir.pwd, result)
24
+ end
25
+
26
+ def test_calls_functions_with_parameters
27
+ # Test reverse function
28
+ result = @caller.reverse("hello")
29
+ assert_equal("olleh", result)
30
+
31
+ # Test isqrt function
32
+ result = @caller.isqrt("16")
33
+ assert_equal("4", result)
34
+
35
+ # Test rand function
36
+ result = @caller.rand("1 10")
37
+ num = result.to_i
38
+ assert(num >= 1 && num <= 10, "Random number should be between 1 and 10, got #{result}")
39
+ end
40
+
41
+ def test_calls_functions_with_no_parameters
42
+ # Test platform function
43
+ result = @caller.platform
44
+ assert_equal(RUBY_PLATFORM, result)
45
+
46
+ # Test ruby_version function
47
+ result = @caller.ruby_version
48
+ assert_equal(RUBY_VERSION, result)
49
+
50
+ # Test livetext_version function
51
+ result = @caller.livetext_version
52
+ assert_equal(Livetext::VERSION, result)
53
+ end
54
+
55
+ def test_handles_nonexistent_functions
56
+ result = @caller.nonexistent_function("param")
57
+ assert_match(/Error evaluating \$\$nonexistent_function/, result)
58
+ end
59
+
60
+ def test_function_existence_check
61
+ assert(@caller.exists?("date"))
62
+ assert(@caller.exists?("time"))
63
+ assert(@caller.exists?("pwd"))
64
+ refute(@caller.exists?("nonexistent"))
65
+ end
66
+
67
+ def test_list_functions
68
+ functions = @caller.list
69
+ assert_instance_of(Array, functions)
70
+ assert(functions.include?("date"))
71
+ assert(functions.include?("time"))
72
+ assert(functions.include?("pwd"))
73
+ assert(functions.include?("reverse"))
74
+ assert(functions.include?("isqrt"))
75
+ end
76
+
77
+ def test_respond_to_missing
78
+ assert(@caller.respond_to?(:date))
79
+ assert(@caller.respond_to?(:time))
80
+ assert(@caller.respond_to?(:pwd))
81
+ refute(@caller.respond_to?(:nonexistent))
82
+ end
83
+
84
+ def test_link_function
85
+ result = @caller.link("Google|https://google.com")
86
+ expected = "<a style='text-decoration: none' href='https://google.com'>Google</a>"
87
+ assert_equal(expected, result)
88
+ end
89
+
90
+ def test_br_function
91
+ result = @caller.br("3")
92
+ assert_equal("<br><br><br>", result)
93
+
94
+ # Test with empty parameter (default to 1)
95
+ result = @caller.br("")
96
+ assert_equal("<br>", result)
97
+
98
+ # Test with nil parameter (default to 1)
99
+ result = @caller.br(nil)
100
+ assert_equal("<br>", result)
101
+ end
102
+ end
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.60
4
+ version: 0.9.62
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hal Fulton
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-09-06 00:00:00.000000000 Z
10
+ date: 2025-09-09 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: A smart text processor extensible in Ruby
13
13
  email: rubyhacker@gmail.com
@@ -35,6 +35,7 @@ files:
35
35
  - lib/livetext/expansion.rb
36
36
  - lib/livetext/formatter.rb
37
37
  - lib/livetext/formatter_component.rb
38
+ - lib/livetext/function_caller.rb
38
39
  - lib/livetext/function_registry.rb
39
40
  - lib/livetext/functions.rb
40
41
  - lib/livetext/global_helpers.rb
@@ -197,6 +198,10 @@ files:
197
198
  - test/snapshots/simple_copy/expected-output.txt
198
199
  - test/snapshots/simple_copy/simplefile.inc
199
200
  - test/snapshots/simple_copy/source.lt3
201
+ - test/snapshots/simple_func_api/expected-error.txt
202
+ - test/snapshots/simple_func_api/match-output.txt
203
+ - test/snapshots/simple_func_api/source.lt3
204
+ - test/snapshots/simple_func_api/stderr.txt
200
205
  - test/snapshots/simple_import/expected-error.txt
201
206
  - test/snapshots/simple_import/expected-output.txt
202
207
  - test/snapshots/simple_import/simple_import.rb
@@ -236,6 +241,8 @@ files:
236
241
  - test/unit/double.rb
237
242
  - test/unit/formatter.rb
238
243
  - test/unit/formatter_component.rb
244
+ - test/unit/function_api_access.rb
245
+ - test/unit/function_caller.rb
239
246
  - test/unit/function_registry.rb
240
247
  - test/unit/functions.rb
241
248
  - test/unit/html.rb