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,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TSK Parser for Ruby
4
+ # Handles TOML-like TSK format parsing and generation
5
+
6
+ module TuskLang
7
+ # Parser for TSK (TuskLang Configuration) format
8
+ class TSKParser
9
+ # Parse TSK content into hash
10
+ def self.parse(content)
11
+ data, _ = parse_with_comments(content)
12
+ data
13
+ end
14
+
15
+ # Parse TSK content with comments preserved
16
+ def self.parse_with_comments(content)
17
+ lines = content.split("\n")
18
+ result = {}
19
+ comments = {}
20
+ current_section = nil
21
+ in_multiline_string = false
22
+ multiline_key = nil
23
+ multiline_content = []
24
+
25
+ lines.each_with_index do |line, i|
26
+ trimmed_line = line.strip
27
+
28
+ # Handle multiline strings
29
+ if in_multiline_string
30
+ if trimmed_line == '"""'
31
+ if current_section && multiline_key
32
+ result[current_section][multiline_key] = multiline_content.join("\n")
33
+ end
34
+ in_multiline_string = false
35
+ multiline_key = nil
36
+ multiline_content = []
37
+ next
38
+ end
39
+ multiline_content << line
40
+ next
41
+ end
42
+
43
+ # Capture comments
44
+ if trimmed_line.start_with?('#')
45
+ comments[i] = trimmed_line
46
+ next
47
+ end
48
+
49
+ # Skip empty lines
50
+ next if trimmed_line.empty?
51
+
52
+ # Section header
53
+ section_match = trimmed_line.match(/^\[(.+)\]$/)
54
+ if section_match
55
+ current_section = section_match[1]
56
+ result[current_section] = {}
57
+ next
58
+ end
59
+
60
+ # Key-value pair
61
+ if current_section && trimmed_line.include?('=')
62
+ separator_index = trimmed_line.index('=')
63
+ key = trimmed_line[0...separator_index].strip
64
+ value_str = trimmed_line[(separator_index + 1)..-1].strip
65
+
66
+ # Check for multiline string start
67
+ if value_str == '"""'
68
+ in_multiline_string = true
69
+ multiline_key = key
70
+ next
71
+ end
72
+
73
+ value = parse_value(value_str)
74
+ result[current_section][key] = value
75
+ end
76
+ end
77
+
78
+ [result, comments]
79
+ end
80
+
81
+ # Parse a TSK value string into appropriate Ruby type
82
+ private_class_method def self.parse_value(value_str)
83
+ # Null
84
+ return nil if value_str == 'null'
85
+
86
+ # Boolean
87
+ return true if value_str == 'true'
88
+ return false if value_str == 'false'
89
+
90
+ # Number
91
+ return value_str.to_i if value_str.match?(/^-?\d+$/)
92
+ return value_str.to_f if value_str.match?(/^-?\d+\.\d+$/)
93
+
94
+ # String
95
+ if value_str.start_with?('"') && value_str.end_with?('"')
96
+ return value_str[1...-1].gsub('\\"', '"').gsub('\\\\', '\\')
97
+ end
98
+
99
+ # Array
100
+ if value_str.start_with?('[') && value_str.end_with?(']')
101
+ array_content = value_str[1...-1].strip
102
+ return [] if array_content.empty?
103
+
104
+ items = split_array_items(array_content)
105
+ return items.map { |item| parse_value(item.strip) }
106
+ end
107
+
108
+ # Object/Hash
109
+ if value_str.start_with?('{') && value_str.end_with?('}')
110
+ obj_content = value_str[1...-1].strip
111
+ return {} if obj_content.empty?
112
+
113
+ pairs = split_object_pairs(obj_content)
114
+ obj = {}
115
+
116
+ pairs.each do |pair|
117
+ if pair.include?('=')
118
+ eq_index = pair.index('=')
119
+ key = pair[0...eq_index].strip
120
+ value = pair[(eq_index + 1)..-1].strip
121
+ # Remove quotes from key if present
122
+ clean_key = key.start_with?('"') && key.end_with?('"') ? key[1...-1] : key
123
+ obj[clean_key] = parse_value(value)
124
+ end
125
+ end
126
+
127
+ return obj
128
+ end
129
+
130
+ # Return as string if no other type matches
131
+ value_str
132
+ end
133
+
134
+ # Split array items considering nested structures
135
+ private_class_method def self.split_array_items(content)
136
+ items = []
137
+ current = ''
138
+ depth = 0
139
+ in_string = false
140
+
141
+ content.each_char.with_index do |ch, i|
142
+ if ch == '"' && (i == 0 || content[i - 1] != '\\')
143
+ in_string = !in_string
144
+ end
145
+
146
+ unless in_string
147
+ depth += 1 if ch == '[' || ch == '{'
148
+ depth -= 1 if ch == ']' || ch == '}'
149
+
150
+ if ch == ',' && depth == 0
151
+ items << current.strip
152
+ current = ''
153
+ next
154
+ end
155
+ end
156
+
157
+ current += ch
158
+ end
159
+
160
+ items << current.strip unless current.strip.empty?
161
+ items
162
+ end
163
+
164
+ # Split object pairs considering nested structures
165
+ private_class_method def self.split_object_pairs(content)
166
+ pairs = []
167
+ current = ''
168
+ depth = 0
169
+ in_string = false
170
+
171
+ content.each_char.with_index do |ch, i|
172
+ if ch == '"' && (i == 0 || content[i - 1] != '\\')
173
+ in_string = !in_string
174
+ end
175
+
176
+ unless in_string
177
+ depth += 1 if ch == '[' || ch == '{'
178
+ depth -= 1 if ch == ']' || ch == '}'
179
+
180
+ if ch == ',' && depth == 0
181
+ pairs << current.strip
182
+ current = ''
183
+ next
184
+ end
185
+ end
186
+
187
+ current += ch
188
+ end
189
+
190
+ pairs << current.strip unless current.strip.empty?
191
+ pairs
192
+ end
193
+
194
+ # Convert hash back to TSK string format
195
+ def self.stringify(data)
196
+ result = []
197
+
198
+ data.each do |section, section_data|
199
+ result << "[#{section}]"
200
+
201
+ if section_data.is_a?(Hash)
202
+ section_data.each do |key, value|
203
+ result << "#{key} = #{format_value(value)}"
204
+ end
205
+ else
206
+ result << "value = #{format_value(section_data)}"
207
+ end
208
+
209
+ result << ''
210
+ end
211
+
212
+ result.join("\n").strip
213
+ end
214
+
215
+ # Format a value for TSK string representation
216
+ private_class_method def self.format_value(value)
217
+ case value
218
+ when nil then 'null'
219
+ when true then 'true'
220
+ when false then 'false'
221
+ when String then "\"#{value.gsub('"', '\\"').gsub('\\', '\\\\')}\""
222
+ when Numeric then value.to_s
223
+ when Hash
224
+ pairs = value.map { |k, v| "\"#{k}\" = #{format_value(v)}" }
225
+ "{#{pairs.join(', ')}}"
226
+ when Array
227
+ items = value.map { |v| format_value(v) }
228
+ "[#{items.join(', ')}]"
229
+ else
230
+ "\"#{value}\""
231
+ end
232
+ end
233
+ end
234
+ end