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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4df70690cfafd3255e28fdd3c4954dae237103d97b12eb74f99d8070dd32d0a8
4
- data.tar.gz: eed8ed48485581ec18325ca84315b7eefd2350bcb3eeb220f0d1ed503186df98
3
+ metadata.gz: 67671d34a5ab6b2e94b06ca3152ba1b820fb91bbb44a2cd8c75de155eae3da1b
4
+ data.tar.gz: c98c2337de6184a209f499d29b569f9836350e22c57d09602378cfe76b215382
5
5
  SHA512:
6
- metadata.gz: 1d657635a43aa04c046a0f96ad369b0d9e9ce705ff2493120af7f2e31ac041480795f774a79fbd0b96ff3597dd6dab4acb9ac19a6f13c7c1a744bf84ae16d0a4
7
- data.tar.gz: e944e9320ba30df3f257e691e2947167d24d5ec7f1bb4cfd70ea6d51ea34a7ca98f95df736da71eb1fc9c6e2eea613c340d27edceea42e87579600b57675615b
6
+ metadata.gz: 6801e106a59a9df556acb29c253b577befe47014f93f3fb215cec04ad232546b2cd27fcde5031e78ea8cee778c51f3bd2995d3faa2e330595496c959dd83c1ec
7
+ data.tar.gz: c6120c344a91322f903afb7e9ca4700c997eb09d63e115f90474610332bec605206a30307aa7ed3e221a482bda88dcebab52f1824ad43c9bb5e98625950d01a2
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) since 2025: rb-cfg-parser ruby App
3
+ Copyright (c) since 2025: rb-cfg-parser Ruby Gem
4
4
  description: ruby gem to parse and load .cfg files
5
5
  author: J. Arrizza email: cppgent0@gmail.com
6
6
 
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,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/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.2'
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.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-03-13 00:00:00.000000000 Z
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/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