rb_cfg_parser 0.1.0 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +19 -6
- data/lib/parser/cfg_lexer.rex.rb +111 -0
- data/lib/parser/cfg_parser.rb +267 -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: d9103c8c06f978609933e4ffc3c7aaa4e4af44860e5feb1a8d94f6a76daac847
|
4
|
+
data.tar.gz: 5f893f3b5042113a90de05ba58d6b36f0d98c500fdc71ef6938af471bbf0f6e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae76c8dc9b3a1ea6bc380c7c94423da7184b603fd36e522daf1d7959ee710bc4c54d0b76be3fc5b62691e2ea825c7f5fc13cab7b6dcf79beb81fcef94af73e31
|
7
|
+
data.tar.gz: 6af35ac2f6dfaab81984c1018de0c4df97edde92770786cb63cf63b744e97139e32ff993cc2054e16dafcdb0d0e0739787269a1940031f387b2b171cf45c823e
|
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,267 @@
|
|
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
|
+
def initialize
|
13
|
+
@rex = CfgLexer.new
|
14
|
+
@fsm = :start
|
15
|
+
@lineno = 0
|
16
|
+
@token = nil
|
17
|
+
@tok_posn = 0
|
18
|
+
@next_token = nil
|
19
|
+
@next_tok_posn = 0
|
20
|
+
@curr_values = nil
|
21
|
+
@curr_section = nil
|
22
|
+
@curr_section_lineno = 0
|
23
|
+
@log_cb = method(:print_line)
|
24
|
+
@verbose = 0
|
25
|
+
@content = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
###
|
29
|
+
def parse_file(path)
|
30
|
+
file_content = File.read(path)
|
31
|
+
parse_str(file_content)
|
32
|
+
end
|
33
|
+
|
34
|
+
###
|
35
|
+
def parse_str(text)
|
36
|
+
text = text.join("\n") if text.is_a?(Array)
|
37
|
+
@rex.scan_setup(text)
|
38
|
+
get_next_token
|
39
|
+
get_next_token unless @next_token.nil?
|
40
|
+
@fsm = :start
|
41
|
+
@lineno = 1
|
42
|
+
|
43
|
+
loop do
|
44
|
+
break if @token.nil?
|
45
|
+
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))
|
46
|
+
|
47
|
+
if @token[0] == :newline
|
48
|
+
@lineno += 1
|
49
|
+
@tok_posn = 0
|
50
|
+
@next_tok_posn = 1
|
51
|
+
end
|
52
|
+
|
53
|
+
if @token[0] == :comment
|
54
|
+
@lineno += 1
|
55
|
+
@tok_posn = 0
|
56
|
+
@next_tok_posn = 1
|
57
|
+
elsif @fsm == :start
|
58
|
+
fsm_start
|
59
|
+
elsif @fsm == :read_section
|
60
|
+
fsm_read_section
|
61
|
+
elsif @fsm == :assignments
|
62
|
+
fsm_assignments
|
63
|
+
elsif @fsm == :asgmt_read_values
|
64
|
+
fsm_asgmt_read_values
|
65
|
+
else
|
66
|
+
# :nocov: can't be invoked unless there is an unknown fsm state
|
67
|
+
raise_unexpected_token
|
68
|
+
# :nocov:
|
69
|
+
end
|
70
|
+
|
71
|
+
get_next_token
|
72
|
+
end
|
73
|
+
save_curr_assignment unless @curr_vrbl.nil?
|
74
|
+
raise_err('Incomplete section') if @fsm == :read_section
|
75
|
+
|
76
|
+
@content
|
77
|
+
end
|
78
|
+
|
79
|
+
###
|
80
|
+
def fsm_start
|
81
|
+
if @token[0] == :newline
|
82
|
+
@lineno += 1
|
83
|
+
@tok_posn = 0
|
84
|
+
@next_tok_posn = 1
|
85
|
+
elsif @token[0] == :open_bracket && @tok_posn == 1
|
86
|
+
@fsm = :read_section
|
87
|
+
reset_section
|
88
|
+
elsif @token[0] == :vrbl && @next_token[0] == :equal
|
89
|
+
@curr_section = :unnamed
|
90
|
+
save_curr_section
|
91
|
+
@curr_vrbl = @token[1].strip
|
92
|
+
@curr_values = nil
|
93
|
+
@fsm = :asgmt_read_values
|
94
|
+
# skip the equals
|
95
|
+
get_next_token
|
96
|
+
else
|
97
|
+
raise_unexpected_token
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
###
|
102
|
+
def fsm_read_section
|
103
|
+
if @token[0] == :vrbl
|
104
|
+
@curr_section = @token[1]
|
105
|
+
elsif @token[0] == :close_bracket
|
106
|
+
raise_err('missing section name') if @curr_section.nil?
|
107
|
+
@curr_section.strip!
|
108
|
+
save_curr_section
|
109
|
+
print_line(format('%-10s: >>%s<<', 'section', @curr_section))
|
110
|
+
@fsm = :assignments
|
111
|
+
elsif @token[0] == :comma
|
112
|
+
raise_err('invalid section content, did you use square brackets for a value?')
|
113
|
+
elsif @token[0] == :newline
|
114
|
+
raise_err("section must be on one line, see line #{@curr_section_lineno}")
|
115
|
+
else
|
116
|
+
raise_unexpected_token
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
###
|
121
|
+
def fsm_assignments
|
122
|
+
if @token[0] == :newline
|
123
|
+
# skip
|
124
|
+
elsif @token[0] == :open_bracket
|
125
|
+
@fsm = :read_section
|
126
|
+
reset_section
|
127
|
+
# elsif @token[0] == :vrbl && @tok_posn == 1
|
128
|
+
elsif @token[0] == :vrbl && @next_token[0] == :equal
|
129
|
+
@curr_vrbl = @token[1].strip
|
130
|
+
@curr_values = nil
|
131
|
+
@fsm = :asgmt_read_values
|
132
|
+
# skip the equals
|
133
|
+
get_next_token
|
134
|
+
elsif @token[0] == :value && @next_token[0] == :equal
|
135
|
+
raise_err("missing variable, found :value instead: '#{@token[1]}'")
|
136
|
+
elsif %i[value vrbl].include?(@token[0]) && !@next_token.nil? && @next_token[0] == :newline
|
137
|
+
raise_err('newline not allowed after variable')
|
138
|
+
else
|
139
|
+
raise_unexpected_token
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
###
|
144
|
+
def fsm_asgmt_read_values
|
145
|
+
if @token[0] == :newline
|
146
|
+
# handle empty value
|
147
|
+
@curr_values = '' if @curr_values.nil?
|
148
|
+
print_line(format('%-10s: %s.%s = >>%s<<', 'assignment', @curr_section, @curr_vrbl, @curr_values))
|
149
|
+
@lineno += 1
|
150
|
+
elsif @token[0] == :comma
|
151
|
+
# skip
|
152
|
+
elsif @token[0] == :open_bracket
|
153
|
+
# it's a new section
|
154
|
+
save_curr_assignment
|
155
|
+
|
156
|
+
# start a new section
|
157
|
+
reset_section
|
158
|
+
@fsm = :read_section
|
159
|
+
elsif @token[0] == :vrbl && !@next_token.nil? && @next_token[0] == :equal
|
160
|
+
# it's a new assigment
|
161
|
+
save_curr_assignment
|
162
|
+
|
163
|
+
# start a new assignment
|
164
|
+
@curr_vrbl = @token[1].strip
|
165
|
+
@curr_values = nil
|
166
|
+
@fsm = :asgmt_read_values
|
167
|
+
# skip the equals
|
168
|
+
get_next_token
|
169
|
+
elsif %i[value vrbl].include?(@token[0])
|
170
|
+
# if a vrbl is right next to a value (no wspace), then two tokens should be concatenated
|
171
|
+
if @token[0] == :vrbl && !@next_token.nil? && @next_token[0] == :value && @next_tok_posn == @tok_posn + 1
|
172
|
+
val = @token[1] + @next_token[1]
|
173
|
+
# val = val[1..-2] if (val[0] == "'" && val[-1] == "'") || (val[0] == '"' && val[-1] == '"')
|
174
|
+
get_next_token
|
175
|
+
else
|
176
|
+
val = @token[1]
|
177
|
+
end
|
178
|
+
val = strip_quotes(val)
|
179
|
+
|
180
|
+
# it's a value to save
|
181
|
+
if @curr_values.nil?
|
182
|
+
@curr_values = val
|
183
|
+
elsif @curr_values.is_a?(String)
|
184
|
+
@curr_values = [@curr_values]
|
185
|
+
@curr_values.append(val)
|
186
|
+
else
|
187
|
+
# assume it's an array
|
188
|
+
@curr_values.append(val)
|
189
|
+
end
|
190
|
+
else
|
191
|
+
raise_unexpected_token
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
###
|
196
|
+
def strip_quotes(val)
|
197
|
+
if (val[0] == "'" && val[-1] == "'") || (val[0] == '"' && val[-1] == '"')
|
198
|
+
val[1..-2]
|
199
|
+
else
|
200
|
+
val
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
###
|
205
|
+
def reset_section
|
206
|
+
@curr_section = nil
|
207
|
+
@curr_section_lineno = @lineno
|
208
|
+
@curr_vrbl = nil
|
209
|
+
@curr_values = nil
|
210
|
+
end
|
211
|
+
|
212
|
+
###
|
213
|
+
def save_curr_section
|
214
|
+
if @content.include?(@curr_section)
|
215
|
+
raise_err("found duplicate section: \"#{@curr_section}\"")
|
216
|
+
else
|
217
|
+
@content[@curr_section] = {}
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
###
|
222
|
+
def save_curr_assignment
|
223
|
+
if @content[@curr_section].include?(@curr_vrbl)
|
224
|
+
raise_err("found duplicate variable: \"#{@curr_vrbl}\" in section: \"#{@curr_section}\"")
|
225
|
+
else
|
226
|
+
@curr_values = '' if @curr_values.nil?
|
227
|
+
@content[@curr_section][@curr_vrbl] = @curr_values
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
###
|
232
|
+
def get_next_token
|
233
|
+
@token = @next_token
|
234
|
+
@tok_posn = @next_tok_posn
|
235
|
+
@next_token = @rex.next_token
|
236
|
+
@next_tok_posn += 1
|
237
|
+
|
238
|
+
if @token.nil? || @token[0] == :newline
|
239
|
+
@tok_posn = 0
|
240
|
+
@next_tok_posn = 1
|
241
|
+
end
|
242
|
+
|
243
|
+
return unless !@next_token.nil? && @next_token[0] == :wspace
|
244
|
+
@next_token = @rex.next_token
|
245
|
+
@next_tok_posn += 1
|
246
|
+
end
|
247
|
+
|
248
|
+
###
|
249
|
+
def raise_unexpected_token
|
250
|
+
raise_err("Unexpected token #{@token}")
|
251
|
+
end
|
252
|
+
|
253
|
+
###
|
254
|
+
def raise_err(text)
|
255
|
+
raise("\nline: #{@lineno}] in state: #{@fsm}, #{text}")
|
256
|
+
end
|
257
|
+
|
258
|
+
###
|
259
|
+
def print_dbg(msg)
|
260
|
+
@log_cb.call(format('DBG line: %3d] %s', @lineno, msg)) if @verbose > 1
|
261
|
+
end
|
262
|
+
|
263
|
+
###
|
264
|
+
def print_line(msg)
|
265
|
+
@log_cb.call(format(' --> line: %3d] %s', @lineno, msg)) if @verbose.positive?
|
266
|
+
end
|
267
|
+
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.1
|
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-03-
|
11
|
+
date: 2025-03-18 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
|