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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4df70690cfafd3255e28fdd3c4954dae237103d97b12eb74f99d8070dd32d0a8
4
- data.tar.gz: eed8ed48485581ec18325ca84315b7eefd2350bcb3eeb220f0d1ed503186df98
3
+ metadata.gz: d9103c8c06f978609933e4ffc3c7aaa4e4af44860e5feb1a8d94f6a76daac847
4
+ data.tar.gz: 5f893f3b5042113a90de05ba58d6b36f0d98c500fdc71ef6938af471bbf0f6e5
5
5
  SHA512:
6
- metadata.gz: 1d657635a43aa04c046a0f96ad369b0d9e9ce705ff2493120af7f2e31ac041480795f774a79fbd0b96ff3597dd6dab4acb9ac19a6f13c7c1a744bf84ae16d0a4
7
- data.tar.gz: e944e9320ba30df3f257e691e2947167d24d5ec7f1bb4cfd70ea6d51ea34a7ca98f95df736da71eb1fc9c6e2eea613c340d27edceea42e87579600b57675615b
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++, Android
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
- Warning: there is currently an issue is inconsistent quoting is done:
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
- Note mixed quoted are okay, but recommended since converting them to a single
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/config_parser'
2
+ require_relative 'parser/cfg_parser'
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
  ## current version
3
- VERSION = '0.1.0'
3
+ VERSION = '0.1.1'
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.0
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-13 00:00:00.000000000 Z
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/config_parser.rb
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