rb_cfg_parser 0.1.0 → 0.1.2
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 +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +19 -6
- data/lib/parser/cfg_lexer.rex.rb +111 -0
- data/lib/parser/cfg_parser.rb +313 -0
- data/lib/rb_cfg_parser.rb +1 -1
- data/lib/version.rb +1 -1
- metadata +5 -3
- /data/lib/parser/{config_parser.rb → pf_config_parser.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67671d34a5ab6b2e94b06ca3152ba1b820fb91bbb44a2cd8c75de155eae3da1b
|
4
|
+
data.tar.gz: c98c2337de6184a209f499d29b569f9836350e22c57d09602378cfe76b215382
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6801e106a59a9df556acb29c253b577befe47014f93f3fb215cec04ad232546b2cd27fcde5031e78ea8cee778c51f3bd2995d3faa2e330595496c959dd83c1ec
|
7
|
+
data.tar.gz: c6120c344a91322f903afb7e9ca4700c997eb09d63e115f90474610332bec605206a30307aa7ed3e221a482bda88dcebab52f1824ad43c9bb5e98625950d01a2
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
This Ruby gem parses a .cfg file based on how python's ```configparser``` module works.
|
7
7
|
|
8
8
|
The need for this came from a "cross-platform utilities" (xplat_utils) repo that I use
|
9
|
-
to maintain the many projects for my website. It works on Python, Ruby, C/C++,
|
9
|
+
to maintain the many projects for my website. It works on Python, Ruby, C/C++, Arduino
|
10
10
|
for now.
|
11
11
|
|
12
12
|
Having a common .cfg format is ideal in that scenario.
|
@@ -45,6 +45,20 @@ puts(' >>')
|
|
45
45
|
|
46
46
|
See ut/ut_*.rb for examples of all the possible formats it recognizes.
|
47
47
|
|
48
|
+
#### logging
|
49
|
+
|
50
|
+
A logging callback is allowed. See ut/ut_tp004_puts_callback.rb for example of how to use it:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
@parser.log_cb = method(:logger)
|
54
|
+
@parser.verbose = 1
|
55
|
+
# <snip>
|
56
|
+
|
57
|
+
def logger(msg)
|
58
|
+
# your code to log
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
48
62
|
#### parsing
|
49
63
|
|
50
64
|
A cfg file can be parsed or a string can be parsed:
|
@@ -57,7 +71,8 @@ cfg = parser.parse_str(lines)
|
|
57
71
|
|
58
72
|
The content returned is a hash.
|
59
73
|
The key is the section_name and the variables within that
|
60
|
-
section are held as a hash.
|
74
|
+
section are held as a hash. Spaces are not allowed in a
|
75
|
+
section name.
|
61
76
|
|
62
77
|
```text
|
63
78
|
[section_name]
|
@@ -133,10 +148,9 @@ vrbl1 = 'val1' 'val2' 'val3'
|
|
133
148
|
vrbl1 = "val1" "val2" "val3"
|
134
149
|
```
|
135
150
|
|
136
|
-
|
151
|
+
Mixed quote types works:
|
137
152
|
|
138
153
|
```text
|
139
|
-
# these will return the incorrect array ['val1', 'val3']
|
140
154
|
vrbl1 = 'val1' val2 'val3'
|
141
155
|
vrbl1 = 'val1' "val2" 'val3'
|
142
156
|
vrbl1 = "val1" 'val2' "val3"
|
@@ -151,8 +165,7 @@ vrbl1 = 'val1'
|
|
151
165
|
'val3'
|
152
166
|
```
|
153
167
|
|
154
|
-
|
155
|
-
line will cause errors
|
168
|
+
Mixed quote types works:
|
156
169
|
|
157
170
|
```text
|
158
171
|
# this returns an array ['val1', 'val2', 'val3']
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#--
|
2
|
+
# DO NOT MODIFY!!!!
|
3
|
+
# This file is automatically generated by rex 1.0.8
|
4
|
+
# from lexical definition file "lib/cfg_lexer.rex".
|
5
|
+
#++
|
6
|
+
|
7
|
+
class CfgLexer
|
8
|
+
require 'strscan'
|
9
|
+
|
10
|
+
class ScanError < StandardError ; end
|
11
|
+
|
12
|
+
attr_reader :lineno
|
13
|
+
attr_reader :filename
|
14
|
+
attr_accessor :state
|
15
|
+
|
16
|
+
def scan_setup(str)
|
17
|
+
@ss = StringScanner.new(str)
|
18
|
+
@lineno = 1
|
19
|
+
@state = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def action
|
23
|
+
yield
|
24
|
+
end
|
25
|
+
|
26
|
+
def scan_str(str)
|
27
|
+
scan_setup(str)
|
28
|
+
do_parse
|
29
|
+
end
|
30
|
+
alias :scan :scan_str
|
31
|
+
|
32
|
+
def load_file( filename )
|
33
|
+
@filename = filename
|
34
|
+
File.open(filename, "r") do |f|
|
35
|
+
scan_setup(f.read)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def scan_file( filename )
|
40
|
+
load_file(filename)
|
41
|
+
do_parse
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def next_token
|
46
|
+
return if @ss.eos?
|
47
|
+
|
48
|
+
# skips empty actions
|
49
|
+
until token = _next_token or @ss.eos?; end
|
50
|
+
token
|
51
|
+
end
|
52
|
+
|
53
|
+
def _next_token
|
54
|
+
text = @ss.peek(1)
|
55
|
+
@lineno += 1 if text == "\n"
|
56
|
+
token = case @state
|
57
|
+
when nil
|
58
|
+
case
|
59
|
+
when (text = @ss.scan(/\[\s*VRBL\s*\]/))
|
60
|
+
action { [:section, text] }
|
61
|
+
|
62
|
+
when (text = @ss.scan(/\p{Alpha}+(\p{Alnum}|_|-|\.)*/))
|
63
|
+
action { [:vrbl, text] }
|
64
|
+
|
65
|
+
when (text = @ss.scan(/'.*?'/))
|
66
|
+
action { [:value, text] }
|
67
|
+
|
68
|
+
when (text = @ss.scan(/".*?"/))
|
69
|
+
action { [:value, text] }
|
70
|
+
|
71
|
+
when (text = @ss.scan(/=/))
|
72
|
+
action { [:equal, text] }
|
73
|
+
|
74
|
+
when (text = @ss.scan(/,/))
|
75
|
+
action { [:comma, text] }
|
76
|
+
|
77
|
+
when (text = @ss.scan(/\n/))
|
78
|
+
action { [:newline, ''] }
|
79
|
+
|
80
|
+
when (text = @ss.scan(/\[/))
|
81
|
+
action { [:open_bracket, text] }
|
82
|
+
|
83
|
+
when (text = @ss.scan(/\]/))
|
84
|
+
action { [:close_bracket, text] }
|
85
|
+
|
86
|
+
when (text = @ss.scan(/;.*\n/))
|
87
|
+
action { [:comment, ''] }
|
88
|
+
|
89
|
+
when (text = @ss.scan(/\#.*\n/))
|
90
|
+
action { [:comment, ''] }
|
91
|
+
|
92
|
+
when (text = @ss.scan(/[ \t]+/))
|
93
|
+
action { [:wspace, ''] }
|
94
|
+
|
95
|
+
when (text = @ss.scan(/[^\s]*/))
|
96
|
+
action { [:value, text] }
|
97
|
+
|
98
|
+
|
99
|
+
else
|
100
|
+
text = @ss.string[@ss.pos .. -1]
|
101
|
+
raise ScanError, "can not match: '" + text + "'"
|
102
|
+
end # if
|
103
|
+
|
104
|
+
else
|
105
|
+
raise ScanError, "undefined state: '" + state.to_s + "'"
|
106
|
+
end # case state
|
107
|
+
token
|
108
|
+
end # def _next_token
|
109
|
+
|
110
|
+
end # class
|
111
|
+
|
@@ -0,0 +1,313 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'cfg_lexer.rex'
|
3
|
+
|
4
|
+
###
|
5
|
+
# Parse text incoming sections and assignments
|
6
|
+
# uses rexical, see https://github.com/sparklemotion/rexical/blob/main/DOCUMENTATION.en.rdoc
|
7
|
+
class RbCfgParser
|
8
|
+
attr_writer :verbose
|
9
|
+
attr_writer :log_cb
|
10
|
+
|
11
|
+
###
|
12
|
+
# constructor
|
13
|
+
def initialize
|
14
|
+
@rex = CfgLexer.new
|
15
|
+
@fsm = :start
|
16
|
+
@lineno = 0
|
17
|
+
@token = nil
|
18
|
+
@tok_posn = 0
|
19
|
+
@next_token = nil
|
20
|
+
@next_tok_posn = 0
|
21
|
+
@curr_values = nil
|
22
|
+
@curr_section = nil
|
23
|
+
@curr_section_lineno = 0
|
24
|
+
@log_cb = method(:_print_raw_line)
|
25
|
+
@verbose = 0
|
26
|
+
@content = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
###
|
30
|
+
# parse the given file
|
31
|
+
# @return the content as a hash
|
32
|
+
def parse_file(path)
|
33
|
+
file_content = File.read(path)
|
34
|
+
parse_str(file_content)
|
35
|
+
end
|
36
|
+
|
37
|
+
###
|
38
|
+
# parse the given string
|
39
|
+
# @return the content as a hash
|
40
|
+
def parse_str(text)
|
41
|
+
text = text.join("\n") if text.is_a?(Array)
|
42
|
+
@rex.scan_setup(text)
|
43
|
+
get_next_token
|
44
|
+
get_next_token unless @next_token.nil?
|
45
|
+
@fsm = :start
|
46
|
+
@lineno = 1
|
47
|
+
|
48
|
+
loop do
|
49
|
+
break if @token.nil?
|
50
|
+
# print_dbg(format('fsm:%-17s tok[%d]:%-13s val:%-10s next[%d]=%s', @fsm, @tok_posn, @token[0], @token[1], @next_tok_posn, @next_token))
|
51
|
+
|
52
|
+
if @token[0] == :newline
|
53
|
+
@lineno += 1
|
54
|
+
@tok_posn = 0
|
55
|
+
@next_tok_posn = 1
|
56
|
+
end
|
57
|
+
|
58
|
+
if @token[0] == :comment
|
59
|
+
@lineno += 1
|
60
|
+
@tok_posn = 0
|
61
|
+
@next_tok_posn = 1
|
62
|
+
elsif @fsm == :start
|
63
|
+
fsm_start
|
64
|
+
elsif @fsm == :read_section
|
65
|
+
fsm_read_section
|
66
|
+
elsif @fsm == :assignments
|
67
|
+
fsm_assignments
|
68
|
+
elsif @fsm == :asgmt_read_values
|
69
|
+
fsm_asgmt_read_values
|
70
|
+
else
|
71
|
+
# :nocov: can't be invoked unless there is an unknown fsm state
|
72
|
+
raise_unexpected_token
|
73
|
+
# :nocov:
|
74
|
+
end
|
75
|
+
|
76
|
+
get_next_token
|
77
|
+
end
|
78
|
+
save_curr_assignment unless @curr_vrbl.nil?
|
79
|
+
raise_err('Incomplete section') if @fsm == :read_section
|
80
|
+
|
81
|
+
@content
|
82
|
+
end
|
83
|
+
|
84
|
+
###
|
85
|
+
# in the start fsm state:
|
86
|
+
# read and handle the next value
|
87
|
+
# @return None
|
88
|
+
def fsm_start
|
89
|
+
if @token[0] == :newline
|
90
|
+
@lineno += 1
|
91
|
+
@tok_posn = 0
|
92
|
+
@next_tok_posn = 1
|
93
|
+
elsif @token[0] == :open_bracket && @tok_posn == 1
|
94
|
+
@fsm = :read_section
|
95
|
+
reset_section
|
96
|
+
elsif @token[0] == :vrbl && @next_token[0] == :equal
|
97
|
+
@curr_section = :unnamed
|
98
|
+
save_curr_section
|
99
|
+
@curr_vrbl = @token[1].strip
|
100
|
+
@curr_values = nil
|
101
|
+
@fsm = :asgmt_read_values
|
102
|
+
# skip the equals
|
103
|
+
get_next_token
|
104
|
+
else
|
105
|
+
raise_unexpected_token
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
###
|
110
|
+
# in the read_section fsm state:
|
111
|
+
# read and handle the next value
|
112
|
+
# @return None
|
113
|
+
def fsm_read_section
|
114
|
+
if @token[0] == :vrbl
|
115
|
+
@curr_section = @token[1]
|
116
|
+
elsif @token[0] == :close_bracket
|
117
|
+
raise_err('missing section name') if @curr_section.nil?
|
118
|
+
@curr_section.strip!
|
119
|
+
save_curr_section
|
120
|
+
print_line(format('%-10s: >>%s<<', 'section', @curr_section))
|
121
|
+
@fsm = :assignments
|
122
|
+
elsif @token[0] == :comma
|
123
|
+
raise_err('invalid section content, did you use square brackets for a value?')
|
124
|
+
elsif @token[0] == :newline
|
125
|
+
raise_err("section must be on one line, see line #{@curr_section_lineno}")
|
126
|
+
else
|
127
|
+
raise_unexpected_token
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
###
|
132
|
+
# in the assignments fsm state:
|
133
|
+
# read and handle the next value
|
134
|
+
# @return None
|
135
|
+
def fsm_assignments
|
136
|
+
if @token[0] == :newline
|
137
|
+
# skip
|
138
|
+
elsif @token[0] == :open_bracket
|
139
|
+
@fsm = :read_section
|
140
|
+
reset_section
|
141
|
+
# elsif @token[0] == :vrbl && @tok_posn == 1
|
142
|
+
elsif @token[0] == :vrbl && @next_token[0] == :equal
|
143
|
+
@curr_vrbl = @token[1].strip
|
144
|
+
@curr_values = nil
|
145
|
+
@fsm = :asgmt_read_values
|
146
|
+
# skip the equals
|
147
|
+
get_next_token
|
148
|
+
elsif @token[0] == :value && @next_token[0] == :equal
|
149
|
+
raise_err("missing variable, found :value instead: '#{@token[1]}'")
|
150
|
+
elsif %i[value vrbl].include?(@token[0]) && !@next_token.nil? && @next_token[0] == :newline
|
151
|
+
raise_err('newline not allowed after variable')
|
152
|
+
else
|
153
|
+
raise_unexpected_token
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
###
|
158
|
+
# in the asgmt_read_values fsm state:
|
159
|
+
# read and handle the next value
|
160
|
+
# @return None
|
161
|
+
def fsm_asgmt_read_values
|
162
|
+
if @token[0] == :newline
|
163
|
+
# handle empty value
|
164
|
+
@curr_values = '' if @curr_values.nil?
|
165
|
+
print_line(format('%-10s: %s.%s = >>%s<<', 'assignment', @curr_section, @curr_vrbl, @curr_values))
|
166
|
+
@lineno += 1
|
167
|
+
elsif @token[0] == :comma
|
168
|
+
# skip
|
169
|
+
elsif @token[0] == :open_bracket
|
170
|
+
# it's a new section
|
171
|
+
save_curr_assignment
|
172
|
+
|
173
|
+
# start a new section
|
174
|
+
reset_section
|
175
|
+
@fsm = :read_section
|
176
|
+
elsif @token[0] == :vrbl && !@next_token.nil? && @next_token[0] == :equal
|
177
|
+
# it's a new assigment
|
178
|
+
save_curr_assignment
|
179
|
+
|
180
|
+
# start a new assignment
|
181
|
+
@curr_vrbl = @token[1].strip
|
182
|
+
@curr_values = nil
|
183
|
+
@fsm = :asgmt_read_values
|
184
|
+
# skip the equals
|
185
|
+
get_next_token
|
186
|
+
elsif %i[value vrbl].include?(@token[0])
|
187
|
+
# if a vrbl is right next to a value (no wspace), then two tokens should be concatenated
|
188
|
+
if @token[0] == :vrbl && !@next_token.nil? && @next_token[0] == :value && @next_tok_posn == @tok_posn + 1
|
189
|
+
val = @token[1] + @next_token[1]
|
190
|
+
# val = val[1..-2] if (val[0] == "'" && val[-1] == "'") || (val[0] == '"' && val[-1] == '"')
|
191
|
+
get_next_token
|
192
|
+
else
|
193
|
+
val = @token[1]
|
194
|
+
end
|
195
|
+
val = strip_quotes(val)
|
196
|
+
|
197
|
+
# it's a value to save
|
198
|
+
if @curr_values.nil?
|
199
|
+
@curr_values = val
|
200
|
+
elsif @curr_values.is_a?(String)
|
201
|
+
@curr_values = [@curr_values]
|
202
|
+
@curr_values.append(val)
|
203
|
+
else
|
204
|
+
# assume it's an array
|
205
|
+
@curr_values.append(val)
|
206
|
+
end
|
207
|
+
else
|
208
|
+
raise_unexpected_token
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
###
|
213
|
+
# strip any quotes (single or double) from the given value
|
214
|
+
# @return the updated value
|
215
|
+
def strip_quotes(val)
|
216
|
+
if (val[0] == "'" && val[-1] == "'") || (val[0] == '"' && val[-1] == '"')
|
217
|
+
val[1..-2]
|
218
|
+
else
|
219
|
+
val
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
###
|
224
|
+
# reset the section info
|
225
|
+
# @return None
|
226
|
+
def reset_section
|
227
|
+
@curr_section = nil
|
228
|
+
@curr_section_lineno = @lineno
|
229
|
+
@curr_vrbl = nil
|
230
|
+
@curr_values = nil
|
231
|
+
end
|
232
|
+
|
233
|
+
###
|
234
|
+
# save the current section that was found
|
235
|
+
# @return None
|
236
|
+
def save_curr_section
|
237
|
+
if @content.include?(@curr_section)
|
238
|
+
raise_err("found duplicate section: \"#{@curr_section}\"")
|
239
|
+
else
|
240
|
+
@content[@curr_section] = {}
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
###
|
245
|
+
# save the current assignment that found
|
246
|
+
# @return None
|
247
|
+
def save_curr_assignment
|
248
|
+
if @content[@curr_section].include?(@curr_vrbl)
|
249
|
+
raise_err("found duplicate variable: \"#{@curr_vrbl}\" in section: \"#{@curr_section}\"")
|
250
|
+
else
|
251
|
+
@curr_values = '' if @curr_values.nil?
|
252
|
+
@content[@curr_section][@curr_vrbl] = @curr_values
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
###
|
257
|
+
# get the next token from the input stream, set token and tok_posn
|
258
|
+
# @return None
|
259
|
+
def get_next_token
|
260
|
+
@token = @next_token
|
261
|
+
@tok_posn = @next_tok_posn
|
262
|
+
@next_token = @rex.next_token
|
263
|
+
@next_tok_posn += 1
|
264
|
+
|
265
|
+
if @token.nil? || @token[0] == :newline
|
266
|
+
@tok_posn = 0
|
267
|
+
@next_tok_posn = 1
|
268
|
+
end
|
269
|
+
|
270
|
+
return unless !@next_token.nil? && @next_token[0] == :wspace
|
271
|
+
@next_token = @rex.next_token
|
272
|
+
@next_tok_posn += 1
|
273
|
+
end
|
274
|
+
|
275
|
+
###
|
276
|
+
# raise an exception indicating an unexpected token was found
|
277
|
+
# @return does not return
|
278
|
+
def raise_unexpected_token
|
279
|
+
raise_err("Unexpected token #{@token}")
|
280
|
+
end
|
281
|
+
|
282
|
+
###
|
283
|
+
# raise an exception with the given text
|
284
|
+
# @param msg additional message to print
|
285
|
+
# @return does not return
|
286
|
+
def raise_err(msg)
|
287
|
+
raise("\nline: #{@lineno}] in state: #{@fsm}, #{msg}")
|
288
|
+
end
|
289
|
+
|
290
|
+
###
|
291
|
+
# print a debug line
|
292
|
+
# @param msg line to print
|
293
|
+
# @return None
|
294
|
+
def print_dbg(msg)
|
295
|
+
@log_cb.call(format('DBG line: %3d] %s', @lineno, msg)) if @verbose > 1
|
296
|
+
end
|
297
|
+
|
298
|
+
###
|
299
|
+
# print a line
|
300
|
+
# @param msg line to print
|
301
|
+
# @return None
|
302
|
+
def print_line(msg)
|
303
|
+
@log_cb.call(format(' --> line: %3d] %s', @lineno, msg)) if @verbose.positive?
|
304
|
+
end
|
305
|
+
|
306
|
+
###
|
307
|
+
# default callback to print a simple line to stdout
|
308
|
+
# @param msg line to print
|
309
|
+
# @return None
|
310
|
+
def _print_raw_line(msg)
|
311
|
+
# skip
|
312
|
+
end
|
313
|
+
end
|
data/lib/rb_cfg_parser.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require_relative 'parser/
|
2
|
+
require_relative 'parser/cfg_parser'
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rb_cfg_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- J. Arrizza
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: ruby gem to parse and load .cfg files
|
14
14
|
email: cppgent0@gmail.com
|
@@ -18,7 +18,9 @@ extra_rdoc_files: []
|
|
18
18
|
files:
|
19
19
|
- LICENSE.txt
|
20
20
|
- README.md
|
21
|
-
- lib/parser/
|
21
|
+
- lib/parser/cfg_lexer.rex.rb
|
22
|
+
- lib/parser/cfg_parser.rb
|
23
|
+
- lib/parser/pf_config_parser.rb
|
22
24
|
- lib/rb_cfg_parser.rb
|
23
25
|
- lib/version.rb
|
24
26
|
homepage: https://rubygems.org/gems/rb_cfg_parser
|
File without changes
|