jcl 1.0.0

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 (6) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.toml +20 -0
  3. data/README.md +518 -0
  4. data/ext/jcl/extconf.rb +4 -0
  5. data/lib/jcl.rb +12 -0
  6. metadata +110 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 97b378edcc86445b770dba35e3fc3cc9c496e7795da0a6397cb9c2deed925cbb
4
+ data.tar.gz: 6108344b578fa4de6f960d20943aab27ae390839b5bcdab412bfa374402510a8
5
+ SHA512:
6
+ metadata.gz: d75fe410fe2cea2547f0a2bca47513a4617057d1a6b1c11bf6f58ed3ab7bac05d5cba05ebbe47994759e52e0e7c0f4b8ff5a3229db3c203f8991060fdcd5c3b0
7
+ data.tar.gz: d098faf0fd5dfda77d7387b69f430b366c31e1838c7ea0f046ff26e49241ac6ad7b8234ddab718392f239cc7e003fd1a388a0c74d9a0440bb0f2cd95eef71689
data/Cargo.toml ADDED
@@ -0,0 +1,20 @@
1
+ [package]
2
+ name = "jcl-ruby"
3
+ version = "1.0.0"
4
+ edition = "2021"
5
+ authors = ["Hemmer IO <info@hemmer.io>"]
6
+ description = "Ruby bindings for JCL (Jack-of-All Configuration Language)"
7
+ license = "MIT OR Apache-2.0"
8
+ repository = "https://github.com/hemmer-io/jcl"
9
+
10
+ [lib]
11
+ crate-type = ["cdylib"]
12
+ name = "jcl"
13
+
14
+ [dependencies]
15
+ jcl = { path = "../..", version = "1.0.0" }
16
+ magnus = "0.6"
17
+ serde_json = "1.0"
18
+
19
+ [package.metadata.rb_sys]
20
+ required_ruby_version = ">= 2.7.0"
data/README.md ADDED
@@ -0,0 +1,518 @@
1
+ # JCL Ruby Bindings
2
+
3
+ Ruby bindings for JCL (Jack-of-All Configuration Language) using Magnus.
4
+
5
+ ## Installation
6
+
7
+ ### From RubyGems (once published)
8
+
9
+ ```bash
10
+ gem install jcl
11
+ ```
12
+
13
+ Or add to your `Gemfile`:
14
+
15
+ ```ruby
16
+ gem 'jcl'
17
+ ```
18
+
19
+ Then run:
20
+
21
+ ```bash
22
+ bundle install
23
+ ```
24
+
25
+ ### Building from Source
26
+
27
+ 1. Ensure you have Rust installed
28
+ 2. Build the gem:
29
+
30
+ ```bash
31
+ cargo build --release --features ruby
32
+ ```
33
+
34
+ 3. The compiled extension will be in `target/release/`
35
+
36
+ ## Usage
37
+
38
+ ### Basic Example
39
+
40
+ ```ruby
41
+ require 'jcl'
42
+
43
+ # Evaluate JCL code
44
+ jcl_code = <<~JCL
45
+ version = "1.0.0"
46
+ port = 8080
47
+ debug = true
48
+ JCL
49
+
50
+ config = JCL.eval(jcl_code)
51
+
52
+ puts "Version: #{config['version']}"
53
+ puts "Port: #{config['port']}"
54
+ puts "Debug: #{config['debug']}"
55
+ ```
56
+
57
+ ### Parsing
58
+
59
+ ```ruby
60
+ # Parse JCL code to check syntax
61
+ result = JCL.parse('version = "1.0.0"')
62
+ puts result # "Parsed 1 statements"
63
+ ```
64
+
65
+ ### Evaluation
66
+
67
+ ```ruby
68
+ # Evaluate JCL and get results as Hash
69
+ jcl_code = <<~JCL
70
+ app = {
71
+ name = "MyApp"
72
+ port = 8080
73
+ features = ["auth", "api", "admin"]
74
+ }
75
+ JCL
76
+
77
+ config = JCL.eval(jcl_code)
78
+
79
+ # Access nested values
80
+ puts "App name: #{config['app']['name']}"
81
+ puts "Port: #{config['app']['port']}"
82
+ puts "Features: #{config['app']['features'].join(', ')}"
83
+
84
+ # Evaluate from file
85
+ file_config = JCL.eval_file('config.jcl')
86
+ ```
87
+
88
+ ### Formatting
89
+
90
+ ```ruby
91
+ unformatted = 'version="1.0.0" port=8080'
92
+ formatted = JCL.format(unformatted)
93
+ puts formatted
94
+ # Output:
95
+ # version = "1.0.0"
96
+ # port = 8080
97
+ ```
98
+
99
+ ### Linting
100
+
101
+ ```ruby
102
+ jcl_code = <<~JCL
103
+ unused_var = 42
104
+ result = 10
105
+ JCL
106
+
107
+ issues = JCL.lint(jcl_code)
108
+
109
+ issues.each do |issue|
110
+ puts "#{issue['severity']}: #{issue['message']}"
111
+ puts " Rule: #{issue['rule']}"
112
+ puts " Suggestion: #{issue['suggestion']}" if issue['suggestion']
113
+ end
114
+ ```
115
+
116
+ ### Version Information
117
+
118
+ ```ruby
119
+ version = JCL.version
120
+ puts "JCL version: #{version}"
121
+ ```
122
+
123
+ ## API Reference
124
+
125
+ ### Module Methods
126
+
127
+ #### `JCL.parse(source)`
128
+ Parse JCL source code and return a status message.
129
+ - **Parameters:** `source` (String) - The JCL source code
130
+ - **Returns:** String - Status message with number of statements parsed
131
+ - **Raises:** `RuntimeError` on parse error
132
+
133
+ #### `JCL.eval(source)`
134
+ Evaluate JCL source code and return variables as a Hash.
135
+ - **Parameters:** `source` (String) - The JCL source code
136
+ - **Returns:** Hash - Evaluated variables
137
+ - **Raises:** `RuntimeError` on evaluation error
138
+
139
+ #### `JCL.eval_file(path)`
140
+ Evaluate JCL from a file and return variables as a Hash.
141
+ - **Parameters:** `path` (String) - Path to the JCL file
142
+ - **Returns:** Hash - Evaluated variables
143
+ - **Raises:** `RuntimeError` on file read or evaluation error
144
+
145
+ #### `JCL.format(source)`
146
+ Format JCL source code.
147
+ - **Parameters:** `source` (String) - The JCL source code
148
+ - **Returns:** String - Formatted JCL source code
149
+ - **Raises:** `RuntimeError` on format error
150
+
151
+ #### `JCL.lint(source)`
152
+ Lint JCL source code and return issues as an Array.
153
+ - **Parameters:** `source` (String) - The JCL source code
154
+ - **Returns:** Array<Hash> - Lint issues with keys: `rule`, `message`, `severity`, `suggestion` (optional)
155
+ - **Raises:** `RuntimeError` on lint error
156
+
157
+ #### `JCL.version`
158
+ Get the JCL version.
159
+ - **Returns:** String - Version string
160
+
161
+ ## Type Conversions
162
+
163
+ JCL types are converted to Ruby types as follows:
164
+
165
+ | JCL Type | Ruby Type |
166
+ |----------|-----------|
167
+ | String | `String` |
168
+ | Int | `Integer` |
169
+ | Float | `Float` |
170
+ | Bool | `TrueClass` / `FalseClass` |
171
+ | Null | `NilClass` |
172
+ | List | `Array` |
173
+ | Map | `Hash` |
174
+ | Function | `String` ("<function>") |
175
+
176
+ ## Use Cases
177
+
178
+ ### Rails Configuration
179
+
180
+ ```ruby
181
+ # config/initializers/jcl_config.rb
182
+ require 'jcl'
183
+
184
+ module MyApp
185
+ class Application < Rails::Application
186
+ jcl_config = JCL.eval_file(Rails.root.join('config', 'app.jcl'))
187
+
188
+ config.app_name = jcl_config['app']['name']
189
+ config.api_endpoint = jcl_config['api']['endpoint']
190
+ config.feature_flags = jcl_config['features']
191
+ end
192
+ end
193
+ ```
194
+
195
+ ### Chef Recipe
196
+
197
+ ```ruby
198
+ # recipes/default.rb
199
+ require 'jcl'
200
+
201
+ # Load configuration from JCL
202
+ config = JCL.eval_file('/etc/app/config.jcl')
203
+
204
+ # Use configuration in Chef resources
205
+ template '/etc/nginx/nginx.conf' do
206
+ source 'nginx.conf.erb'
207
+ variables(
208
+ port: config['server']['port'],
209
+ worker_processes: config['server']['workers'],
210
+ ssl_enabled: config['server']['ssl']
211
+ )
212
+ notifies :reload, 'service[nginx]'
213
+ end
214
+
215
+ service 'nginx' do
216
+ action [:enable, :start]
217
+ end
218
+ ```
219
+
220
+ ### Puppet Module
221
+
222
+ ```ruby
223
+ # lib/puppet/functions/jcl_eval.rb
224
+ Puppet::Functions.create_function(:jcl_eval) do
225
+ dispatch :jcl_eval do
226
+ param 'String', :path
227
+ return_type 'Hash'
228
+ end
229
+
230
+ def jcl_eval(path)
231
+ require 'jcl'
232
+ JCL.eval_file(path)
233
+ end
234
+ end
235
+ ```
236
+
237
+ Then in your manifest:
238
+
239
+ ```puppet
240
+ # manifests/config.pp
241
+ $config = jcl_eval('/etc/app/config.jcl')
242
+
243
+ file { '/etc/app/settings.json':
244
+ ensure => file,
245
+ content => inline_template('<%= JSON.pretty_generate(@config) %>'),
246
+ }
247
+ ```
248
+
249
+ ### Rake Task
250
+
251
+ ```ruby
252
+ # lib/tasks/jcl.rake
253
+ require 'jcl'
254
+
255
+ namespace :jcl do
256
+ desc 'Validate JCL configuration files'
257
+ task :validate do
258
+ Dir.glob('config/**/*.jcl').each do |file|
259
+ puts "Validating #{file}..."
260
+
261
+ begin
262
+ content = File.read(file)
263
+ issues = JCL.lint(content)
264
+
265
+ if issues.any? { |i| i['severity'] == 'error' }
266
+ puts " ✗ Errors found:"
267
+ issues.each do |issue|
268
+ puts " [#{issue['severity']}] #{issue['message']}"
269
+ end
270
+ exit 1
271
+ else
272
+ puts " ✓ Valid"
273
+ end
274
+ rescue StandardError => e
275
+ puts " ✗ Error: #{e.message}"
276
+ exit 1
277
+ end
278
+ end
279
+ end
280
+
281
+ desc 'Format JCL configuration files'
282
+ task :format do
283
+ Dir.glob('config/**/*.jcl').each do |file|
284
+ puts "Formatting #{file}..."
285
+
286
+ begin
287
+ content = File.read(file)
288
+ formatted = JCL.format(content)
289
+ File.write(file, formatted)
290
+ puts " ✓ Formatted"
291
+ rescue StandardError => e
292
+ puts " ✗ Error: #{e.message}"
293
+ end
294
+ end
295
+ end
296
+ end
297
+ ```
298
+
299
+ ### Sinatra Application
300
+
301
+ ```ruby
302
+ require 'sinatra'
303
+ require 'jcl'
304
+
305
+ configure do
306
+ config = JCL.eval_file('config/app.jcl')
307
+
308
+ set :port, config['server']['port']
309
+ set :environment, config['environment']
310
+ set :api_key, config['api']['key']
311
+ end
312
+
313
+ get '/config' do
314
+ content_type :json
315
+ config = JCL.eval_file('config/app.jcl')
316
+ config.to_json
317
+ end
318
+ ```
319
+
320
+ ### Configuration Loader Class
321
+
322
+ ```ruby
323
+ # lib/config_loader.rb
324
+ require 'jcl'
325
+
326
+ class ConfigLoader
327
+ def initialize(path)
328
+ @path = path
329
+ @config = nil
330
+ end
331
+
332
+ def load
333
+ @config ||= JCL.eval_file(@path)
334
+ end
335
+
336
+ def reload
337
+ @config = nil
338
+ load
339
+ end
340
+
341
+ def get(key_path)
342
+ keys = key_path.split('.')
343
+ keys.reduce(load) { |hash, key| hash[key] }
344
+ end
345
+
346
+ def method_missing(method_name, *args)
347
+ if args.empty? && load.key?(method_name.to_s)
348
+ load[method_name.to_s]
349
+ else
350
+ super
351
+ end
352
+ end
353
+
354
+ def respond_to_missing?(method_name, include_private = false)
355
+ load.key?(method_name.to_s) || super
356
+ end
357
+ end
358
+
359
+ # Usage
360
+ config = ConfigLoader.new('config/app.jcl')
361
+ puts config.version
362
+ puts config.get('app.name')
363
+ ```
364
+
365
+ ### RSpec Integration
366
+
367
+ ```ruby
368
+ # spec/support/jcl_helper.rb
369
+ require 'jcl'
370
+
371
+ RSpec.configure do |config|
372
+ config.before(:suite) do
373
+ test_config = JCL.eval_file('spec/fixtures/test_config.jcl')
374
+ ENV['TEST_CONFIG'] = test_config.to_json
375
+ end
376
+ end
377
+
378
+ # spec/models/config_spec.rb
379
+ require 'rails_helper'
380
+
381
+ RSpec.describe 'Configuration' do
382
+ let(:jcl_code) do
383
+ <<~JCL
384
+ app = {
385
+ name = "TestApp"
386
+ version = "1.0.0"
387
+ }
388
+ JCL
389
+ end
390
+
391
+ it 'parses JCL configuration' do
392
+ config = JCL.eval(jcl_code)
393
+ expect(config['app']['name']).to eq('TestApp')
394
+ expect(config['app']['version']).to eq('1.0.0')
395
+ end
396
+
397
+ it 'validates JCL syntax' do
398
+ invalid_jcl = 'invalid syntax here'
399
+ expect { JCL.eval(invalid_jcl) }.to raise_error(RuntimeError)
400
+ end
401
+
402
+ it 'formats JCL code' do
403
+ unformatted = 'app={name="Test"}'
404
+ formatted = JCL.format(unformatted)
405
+ expect(formatted).to include("app = {")
406
+ expect(formatted).to include('name = "Test"')
407
+ end
408
+ end
409
+ ```
410
+
411
+ ## Error Handling
412
+
413
+ All methods raise `RuntimeError` on errors. It's recommended to catch and handle these appropriately:
414
+
415
+ ```ruby
416
+ begin
417
+ config = JCL.eval_file('config.jcl')
418
+ rescue RuntimeError => e
419
+ if e.message.include?('Parse error')
420
+ puts "Invalid JCL syntax: #{e.message}"
421
+ elsif e.message.include?('Failed to read file')
422
+ puts "File not found: #{e.message}"
423
+ else
424
+ puts "Evaluation error: #{e.message}"
425
+ end
426
+ end
427
+ ```
428
+
429
+ ### Using Rescue Blocks
430
+
431
+ ```ruby
432
+ # Inline rescue
433
+ config = JCL.eval_file('config.jcl') rescue {}
434
+
435
+ # Method with rescue
436
+ def load_config(path)
437
+ JCL.eval_file(path)
438
+ rescue RuntimeError => e
439
+ Rails.logger.error("Failed to load config: #{e.message}")
440
+ default_config
441
+ end
442
+ ```
443
+
444
+ ## Performance Considerations
445
+
446
+ - The native extension is loaded once per Ruby process
447
+ - Parsing and evaluation are performed in native Rust code for optimal performance
448
+ - For repeated evaluations, consider caching parsed results:
449
+
450
+ ```ruby
451
+ class JCLCache
452
+ def initialize
453
+ @cache = {}
454
+ end
455
+
456
+ def eval_file(path)
457
+ mtime = File.mtime(path)
458
+ cache_key = "#{path}:#{mtime}"
459
+
460
+ @cache[cache_key] ||= JCL.eval_file(path)
461
+ end
462
+
463
+ def clear
464
+ @cache.clear
465
+ end
466
+ end
467
+ ```
468
+
469
+ ## Thread Safety
470
+
471
+ The JCL Ruby bindings are thread-safe. Multiple threads can safely call JCL methods concurrently:
472
+
473
+ ```ruby
474
+ threads = 10.times.map do |i|
475
+ Thread.new do
476
+ config = JCL.eval_file("config/env#{i}.jcl")
477
+ # Process config...
478
+ end
479
+ end
480
+
481
+ threads.each(&:join)
482
+ ```
483
+
484
+ ## Development
485
+
486
+ ### Running Tests
487
+
488
+ ```bash
489
+ # Build the extension
490
+ cargo build --release --features ruby
491
+
492
+ # Run Ruby tests
493
+ ruby test/jcl_test.rb
494
+ ```
495
+
496
+ ### Benchmarking
497
+
498
+ ```ruby
499
+ require 'benchmark'
500
+ require 'jcl'
501
+
502
+ jcl_code = File.read('large_config.jcl')
503
+
504
+ Benchmark.bm do |x|
505
+ x.report('parse:') { 100.times { JCL.parse(jcl_code) } }
506
+ x.report('eval:') { 100.times { JCL.eval(jcl_code) } }
507
+ x.report('format:') { 100.times { JCL.format(jcl_code) } }
508
+ x.report('lint:') { 100.times { JCL.lint(jcl_code) } }
509
+ end
510
+ ```
511
+
512
+ ## Contributing
513
+
514
+ Contributions are welcome! Please feel free to submit a Pull Request.
515
+
516
+ ## License
517
+
518
+ MIT OR Apache-2.0
@@ -0,0 +1,4 @@
1
+ require "mkmf"
2
+ require "rb_sys/mkmf"
3
+
4
+ create_rust_makefile("jcl/jcl")
data/lib/jcl.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "jcl/jcl"
4
+
5
+ module JCL
6
+ class Error < StandardError; end
7
+ class SyntaxError < Error; end
8
+ class RuntimeError < Error; end
9
+ class TypeError < Error; end
10
+
11
+ VERSION = "1.0.0"
12
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jcl
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Hemmer IO
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-11-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake-compiler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.12'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rb_sys
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.9'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.9'
69
+ description: JCL is a general-purpose configuration language with powerful built-in
70
+ functions, static type inference, and multi-language bindings.
71
+ email:
72
+ - info@hemmer.io
73
+ executables: []
74
+ extensions:
75
+ - ext/jcl/extconf.rb
76
+ extra_rdoc_files: []
77
+ files:
78
+ - Cargo.toml
79
+ - README.md
80
+ - ext/jcl/extconf.rb
81
+ - lib/jcl.rb
82
+ homepage: https://hemmer-io.github.io/jcl/
83
+ licenses:
84
+ - MIT OR Apache-2.0
85
+ metadata:
86
+ homepage_uri: https://hemmer-io.github.io/jcl/
87
+ source_code_uri: https://github.com/hemmer-io/jcl
88
+ bug_tracker_uri: https://github.com/hemmer-io/jcl/issues
89
+ documentation_uri: https://hemmer-io.github.io/jcl/
90
+ changelog_uri: https://github.com/hemmer-io/jcl/blob/main/CHANGELOG.md
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 2.7.0
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.4.19
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Ruby bindings for JCL (Jack-of-All Configuration Language)
110
+ test_files: []