tusktsk 2.0.1

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.
@@ -0,0 +1,501 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TuskLang Configuration SDK for Ruby
4
+ # Handles TOML-like TSK format with full FUJSEN (function serialization) support
5
+
6
+ require 'json'
7
+ require 'zlib'
8
+ require 'base64'
9
+ require 'securerandom'
10
+ require 'net/http'
11
+ require 'uri'
12
+ require 'date'
13
+
14
+ module TuskLang
15
+ # Main TSK class for parsing, generating, and executing TSK files
16
+ class TSK
17
+ attr_reader :data, :comments, :metadata
18
+
19
+ def initialize(data = {})
20
+ @data = data
21
+ @fujsen_cache = {}
22
+ @comments = {}
23
+ @metadata = {}
24
+ end
25
+
26
+ # Create TSK instance from string content
27
+ def self.from_string(content)
28
+ parser = TSKParser.new
29
+ data, comments = parser.parse_with_comments(content)
30
+ tsk = new(data)
31
+ tsk.instance_variable_set(:@comments, comments)
32
+ tsk
33
+ end
34
+
35
+ # Create TSK instance from file
36
+ def self.from_file(filepath)
37
+ content = File.read(filepath)
38
+ from_string(content)
39
+ end
40
+
41
+ # Get a section by name
42
+ def get_section(name)
43
+ @data[name]
44
+ end
45
+
46
+ # Get a value from a section
47
+ def get_value(section, key)
48
+ section_data = get_section(section)
49
+ section_data&.dig(key)
50
+ end
51
+
52
+ # Set a section with values
53
+ def set_section(name, values)
54
+ @data[name] = values
55
+ end
56
+
57
+ # Set a value in a section
58
+ def set_value(section, key, value)
59
+ @data[section] ||= {}
60
+ @data[section][key] = value
61
+ end
62
+
63
+ # Convert TSK to string representation
64
+ def to_s
65
+ TSKParser.stringify(@data)
66
+ end
67
+
68
+ # Convert TSK to hash
69
+ def to_hash
70
+ @data.dup
71
+ end
72
+
73
+ # Execute a FUJSEN function from a section
74
+ def execute_fujsen(section, key = 'fujsen', *args)
75
+ section_data = get_section(section)
76
+ raise ArgumentError, "Section '#{section}' not found" unless section_data
77
+ raise ArgumentError, "FUJSEN function '#{key}' not found in section '#{section}'" unless section_data.key?(key)
78
+
79
+ code = section_data[key].to_s
80
+ raise ArgumentError, "FUJSEN code is empty for '#{section}.#{key}'" if code.empty?
81
+
82
+ cache_key = "#{section}.#{key}"
83
+ unless @fujsen_cache.key?(cache_key)
84
+ @fujsen_cache[cache_key] = compile_fujsen(code)
85
+ end
86
+
87
+ @fujsen_cache[cache_key].call(*args)
88
+ end
89
+
90
+ # Execute FUJSEN with context injection
91
+ def execute_fujsen_with_context(section, key, context, *args)
92
+ section_data = get_section(section)
93
+ raise ArgumentError, "Section '#{section}' not found" unless section_data
94
+ raise ArgumentError, "FUJSEN function '#{key}' not found in section '#{section}'" unless section_data.key?(key)
95
+
96
+ code = section_data[key].to_s
97
+ raise ArgumentError, "FUJSEN code is empty for '#{section}.#{key}'" if code.empty?
98
+
99
+ # Inject context into the function
100
+ context_code = inject_context_into_code(code, context)
101
+
102
+ cache_key = "#{section}.#{key}.#{context_hash(context)}"
103
+ unless @fujsen_cache.key?(cache_key)
104
+ @fujsen_cache[cache_key] = compile_fujsen(context_code)
105
+ end
106
+
107
+ @fujsen_cache[cache_key].call(*args)
108
+ end
109
+
110
+ # Compile JavaScript FUJSEN code to Ruby proc
111
+ private def compile_fujsen(code)
112
+ # Convert JavaScript to Ruby-like code
113
+ ruby_code = convert_javascript_to_ruby(code)
114
+
115
+ # Create a safe execution environment
116
+ binding = create_safe_binding
117
+
118
+ # Compile and return proc
119
+ eval("proc { |*args| #{ruby_code} }", binding)
120
+ rescue => e
121
+ raise "FUJSEN compilation failed: #{e.message}"
122
+ end
123
+
124
+ # Convert JavaScript code to Ruby-like code
125
+ private def convert_javascript_to_ruby(js_code)
126
+ # Basic JavaScript to Ruby conversion
127
+ ruby_code = js_code
128
+ .gsub(/function\s+(\w+)\s*\(([^)]*)\)\s*\{/, 'lambda { |\2|')
129
+ .gsub(/=>/, '->')
130
+ .gsub(/throw new Error/, 'raise')
131
+ .gsub(/Date\.now\(\)/, 'Time.now.to_i * 1000')
132
+ .gsub(/new Date\(\)\.toISOString\(\)/, 'Time.now.iso8601')
133
+ .gsub(/console\.log/, 'puts')
134
+ .gsub(/\.length/, '.length')
135
+ .gsub(/\.forEach\(/, '.each { |')
136
+ .gsub(/\.map\(/, '.map { |')
137
+ .gsub(/\.filter\(/, '.select { |')
138
+ .gsub(/\.push\(/, '.push(')
139
+ .gsub(/Math\.max/, '[].max')
140
+ .gsub(/Math\.min/, '[].min')
141
+ .gsub(/Array\.isArray/, '->(obj) { obj.is_a?(Array) }')
142
+ .gsub(/typeof\s+(\w+)\s*===/, '\1.is_a?')
143
+ .gsub(/null/, 'nil')
144
+ .gsub(/true/, 'true')
145
+ .gsub(/false/, 'false')
146
+
147
+ # Handle function declarations
148
+ if ruby_code.match?(/lambda \{ \|/)
149
+ ruby_code = ruby_code.gsub(/\}\s*$/, '}')
150
+ else
151
+ # Convert arrow functions
152
+ ruby_code = ruby_code.gsub(/\(([^)]*)\)\s*=>\s*\{?([^}]*)\}?/, 'lambda { |\1| \2 }')
153
+ end
154
+
155
+ ruby_code
156
+ end
157
+
158
+ # Inject context variables into FUJSEN code
159
+ private def inject_context_into_code(code, context)
160
+ context_vars = context.map { |k, v| "#{k} = #{serialize_value(v)}" }.join("\n")
161
+ "#{context_vars}\n#{code}"
162
+ end
163
+
164
+ # Serialize value for Ruby context
165
+ private def serialize_value(value)
166
+ case value
167
+ when nil then 'nil'
168
+ when String then "\"#{value.gsub('"', '\\"')}\""
169
+ when Numeric then value.to_s
170
+ when true then 'true'
171
+ when false then 'false'
172
+ when Hash then "{#{value.map { |k, v| "\"#{k}\" => #{serialize_value(v)}" }.join(', ')}}"
173
+ when Array then "[#{value.map { |v| serialize_value(v) }.join(', ')}]"
174
+ else "\"#{value}\""
175
+ end
176
+ end
177
+
178
+ # Get hash for context caching
179
+ private def context_hash(context)
180
+ context_str = context.sort.map { |k, v| "#{k}=#{v}" }.join('|')
181
+ Base64.strict_encode64(context_str)
182
+ end
183
+
184
+ # Get all FUJSEN functions from a section
185
+ def get_fujsen_map(section)
186
+ section_data = get_section(section)
187
+ return {} unless section_data
188
+
189
+ fujsen_map = {}
190
+ section_data.each do |key, value|
191
+ next unless value.is_a?(String) && !value.empty?
192
+
193
+ begin
194
+ compiled_function = compile_fujsen(value)
195
+ fujsen_map[key] = compiled_function
196
+ rescue
197
+ # Skip non-FUJSEN strings
198
+ end
199
+ end
200
+ fujsen_map
201
+ end
202
+
203
+ # Set a FUJSEN function in a section
204
+ def set_fujsen(section, key, function)
205
+ code = "# Function: #{function.class}\n# Parameters: #{function.parameters.map(&:last).join(', ')}"
206
+ set_value(section, key, code)
207
+ end
208
+
209
+ # Execute FUJSEN @ operators
210
+ def execute_operators(value, context = {})
211
+ if value.is_a?(String) && value.include?('@')
212
+ execute_operator(value, context)
213
+ else
214
+ value
215
+ end
216
+ end
217
+
218
+ # Execute a single @ operator
219
+ private def execute_operator(expression, context)
220
+ operator_match = expression.match(/@(\w+)\s*\(([^)]*)\)/)
221
+ return expression unless operator_match
222
+
223
+ operator_name = operator_match[1].downcase
224
+ operator_args = operator_match[2]
225
+
226
+ case operator_name
227
+ when 'query' then execute_query(operator_args, context)
228
+ when 'cache' then execute_cache(operator_args, context)
229
+ when 'metrics' then execute_metrics(operator_args, context)
230
+ when 'if' then execute_if(operator_args, context)
231
+ when 'date' then execute_date(operator_args, context)
232
+ when 'env' then execute_env(operator_args, context)
233
+ when 'optimize' then execute_optimize(operator_args, context)
234
+ when 'learn' then execute_learn(operator_args, context)
235
+ when 'feature' then execute_feature(operator_args, context)
236
+ when 'json' then execute_json(operator_args, context)
237
+ when 'request' then execute_request(operator_args, context)
238
+ when 'file' then execute_file(operator_args, context)
239
+ when 'flex' then execute_flex(operator_args, context)
240
+ else expression
241
+ end
242
+ end
243
+
244
+ # @ Operator implementations
245
+ private def execute_query(expression, context)
246
+ # Mock database query - in real implementation, connect to actual database
247
+ {
248
+ 'results' => [],
249
+ 'count' => 0,
250
+ 'query' => expression
251
+ }
252
+ end
253
+
254
+ private def execute_cache(expression, context)
255
+ # Mock cache implementation
256
+ parts = expression.split(',')
257
+ ttl = parts.length > 0 ? parse_ttl(parts[0].strip) : 300.0
258
+ key = parts.length > 1 ? parts[1].strip.gsub(/^"|"$/, '') : 'default'
259
+
260
+ {
261
+ 'cached' => true,
262
+ 'ttl' => ttl,
263
+ 'key' => key,
264
+ 'value' => context['cache_value'] || 'default'
265
+ }
266
+ end
267
+
268
+ private def execute_metrics(expression, context)
269
+ parts = expression.split(',')
270
+ metric_name = parts[0].strip.gsub(/^"|"$/, '')
271
+ value = parts.length > 1 ? parts[1].strip.to_f : 0.0
272
+
273
+ {
274
+ 'metric' => metric_name,
275
+ 'value' => value,
276
+ 'timestamp' => Time.now.to_i * 1000
277
+ }
278
+ end
279
+
280
+ private def execute_if(expression, context)
281
+ parts = expression.split(',')
282
+ return false if parts.length < 3
283
+
284
+ condition = parts[0].strip
285
+ true_value = parts[1].strip.gsub(/^"|"$/, '')
286
+ false_value = parts[2].strip.gsub(/^"|"$/, '')
287
+
288
+ # Simple condition evaluation
289
+ result = evaluate_condition(condition, context)
290
+ result ? true_value : false_value
291
+ end
292
+
293
+ private def execute_date(expression, context)
294
+ format = expression.empty? ? '%Y-%m-%d %H:%M:%S' : expression.strip.gsub(/^"|"$/, '')
295
+ Time.now.strftime(format)
296
+ end
297
+
298
+ private def execute_env(expression, context)
299
+ var_name = expression.strip.gsub(/^"|"$/, '')
300
+ ENV[var_name]
301
+ end
302
+
303
+ private def execute_optimize(expression, context)
304
+ {
305
+ 'optimized' => true,
306
+ 'parameter' => expression.strip.gsub(/^"|"$/, ''),
307
+ 'value' => context['optimize_value'] || 'default'
308
+ }
309
+ end
310
+
311
+ private def execute_learn(expression, context)
312
+ {
313
+ 'learned' => true,
314
+ 'parameter' => expression.strip.gsub(/^"|"$/, ''),
315
+ 'value' => context['learn_value'] || 'default'
316
+ }
317
+ end
318
+
319
+ private def execute_feature(expression, context)
320
+ feature_name = expression.strip.gsub(/^"|"$/, '')
321
+ # Mock feature detection
322
+ case feature_name
323
+ when 'redis' then true
324
+ when 'postgresql' then true
325
+ when 'sqlite' then true
326
+ when 'azure' then true
327
+ when 'unity' then true
328
+ when 'rails' then true
329
+ when 'jekyll' then true
330
+ else false
331
+ end
332
+ end
333
+
334
+ private def execute_json(expression, context)
335
+ context.to_json
336
+ rescue
337
+ '{}'
338
+ end
339
+
340
+ private def execute_request(expression, context)
341
+ # Mock HTTP request - in real implementation, use Net::HTTP
342
+ {
343
+ 'status' => 200,
344
+ 'data' => {},
345
+ 'url' => expression.strip.gsub(/^"|"$/, '')
346
+ }
347
+ end
348
+
349
+ private def execute_file(expression, context)
350
+ filepath = expression.strip.gsub(/^"|"$/, '')
351
+ File.read(filepath)
352
+ rescue
353
+ nil
354
+ end
355
+
356
+ private def execute_flex(expression, context)
357
+ # FlexChain integration
358
+ parts = expression.split(',')
359
+ operation = parts[0].strip.gsub(/^"|"$/, '')
360
+
361
+ case operation
362
+ when 'balance' then get_flex_balance(parts.length > 1 ? parts[1].strip.gsub(/^"|"$/, '') : '')
363
+ when 'transfer' then execute_flex_transfer(parts[1..-1])
364
+ when 'stake' then execute_flex_stake(parts[1..-1])
365
+ when 'unstake' then execute_flex_unstake(parts[1..-1])
366
+ when 'reward' then get_flex_reward(parts.length > 1 ? parts[1].strip.gsub(/^"|"$/, '') : '')
367
+ when 'status' then get_flex_status
368
+ when 'delegate' then execute_flex_delegate(parts[1..-1])
369
+ when 'vote' then execute_flex_vote(parts[1..-1])
370
+ else { 'error' => "Unknown Flex operation: #{operation}" }
371
+ end
372
+ end
373
+
374
+ # FlexChain operation implementations
375
+ private def get_flex_balance(address)
376
+ {
377
+ 'address' => address,
378
+ 'balance' => 1000.0,
379
+ 'currency' => 'FLEX'
380
+ }
381
+ end
382
+
383
+ private def execute_flex_transfer(args)
384
+ {
385
+ 'success' => true,
386
+ 'transaction_id' => "tx_#{Time.now.to_i * 1000}",
387
+ 'amount' => args.length > 0 ? args[0].to_f : 0.0,
388
+ 'to' => args.length > 1 ? args[1].strip.gsub(/^"|"$/, '') : '',
389
+ 'from' => args.length > 2 ? args[2].strip.gsub(/^"|"$/, '') : ''
390
+ }
391
+ end
392
+
393
+ private def execute_flex_stake(args)
394
+ {
395
+ 'success' => true,
396
+ 'staked_amount' => args.length > 0 ? args[0].to_f : 0.0,
397
+ 'validator' => args.length > 1 ? args[1].strip.gsub(/^"|"$/, '') : ''
398
+ }
399
+ end
400
+
401
+ private def execute_flex_unstake(args)
402
+ {
403
+ 'success' => true,
404
+ 'unstaked_amount' => args.length > 0 ? args[0].to_f : 0.0,
405
+ 'validator' => args.length > 1 ? args[1].strip.gsub(/^"|"$/, '') : ''
406
+ }
407
+ end
408
+
409
+ private def get_flex_reward(address)
410
+ {
411
+ 'address' => address,
412
+ 'reward' => 50.0,
413
+ 'currency' => 'FLEX'
414
+ }
415
+ end
416
+
417
+ private def get_flex_status
418
+ {
419
+ 'network' => 'FlexChain',
420
+ 'status' => 'active',
421
+ 'block_height' => 12345,
422
+ 'validators' => 10,
423
+ 'total_staked' => 1000000.0
424
+ }
425
+ end
426
+
427
+ private def execute_flex_delegate(args)
428
+ {
429
+ 'success' => true,
430
+ 'delegated_amount' => args.length > 0 ? args[0].to_f : 0.0,
431
+ 'validator' => args.length > 1 ? args[1].strip.gsub(/^"|"$/, '') : '',
432
+ 'delegator' => args.length > 2 ? args[2].strip.gsub(/^"|"$/, '') : ''
433
+ }
434
+ end
435
+
436
+ private def execute_flex_vote(args)
437
+ {
438
+ 'success' => true,
439
+ 'proposal' => args.length > 0 ? args[0].strip.gsub(/^"|"$/, '') : '',
440
+ 'vote' => args.length > 1 ? args[1].strip.gsub(/^"|"$/, '') : '',
441
+ 'voter' => args.length > 2 ? args[2].strip.gsub(/^"|"$/, '') : ''
442
+ }
443
+ end
444
+
445
+ # Helper methods
446
+ private def parse_ttl(ttl)
447
+ return ttl.to_f if ttl.match?(/^\d+$/)
448
+
449
+ match = ttl.match(/^(\d+)([smhd])$/)
450
+ return 300.0 unless match
451
+
452
+ value = match[1].to_f
453
+ unit = match[2]
454
+
455
+ case unit
456
+ when 's' then value
457
+ when 'm' then value * 60
458
+ when 'h' then value * 3600
459
+ when 'd' then value * 86400
460
+ else 300.0
461
+ end
462
+ end
463
+
464
+ private def evaluate_condition(condition, context)
465
+ if condition.include?('==')
466
+ parts = condition.split('==')
467
+ left = parts[0].strip.gsub(/^"|"$/, '')
468
+ right = parts[1].strip.gsub(/^"|"$/, '')
469
+ (context[left] || '').to_s == right
470
+ elsif condition.include?('!=')
471
+ parts = condition.split('!=')
472
+ left = parts[0].strip.gsub(/^"|"$/, '')
473
+ right = parts[1].strip.gsub(/^"|"$/, '')
474
+ (context[left] || '').to_s != right
475
+ else
476
+ false
477
+ end
478
+ end
479
+
480
+ # Create a safe binding for FUJSEN execution
481
+ private def create_safe_binding
482
+ binding
483
+ end
484
+
485
+ # Store data with Shell format
486
+ def store_with_shell(data, metadata = nil)
487
+ shell_data = ShellStorage.create_shell_data(data, SecureRandom.uuid, metadata)
488
+ ShellStorage.pack(shell_data)
489
+ end
490
+
491
+ # Retrieve data from Shell format
492
+ def retrieve_from_shell(shell_data)
493
+ ShellStorage.unpack(shell_data)
494
+ end
495
+
496
+ # Detect type of Shell data
497
+ def detect_type(shell_data)
498
+ ShellStorage.detect_type(shell_data)
499
+ end
500
+ end
501
+ end