format_engine 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de6ff33cc2d740c1ac1721bc6f2a40bf745208b0
4
- data.tar.gz: 9ef57766216651bd454e32ff5cecb8d0c187c39e
3
+ metadata.gz: f114053c3cff51fcde5ac482b6e27463110adc68
4
+ data.tar.gz: 0721a2c7c92b65dbcb1f6f410f0dbb759530df8a
5
5
  SHA512:
6
- metadata.gz: 915a1c8a1bcda84ae78e197f990a67519511be97174b36574028af18fded89589bf3425cb666c23e85508057fa876c521038c390d2c164a10e8926caa9f3f972
7
- data.tar.gz: 8d80d8e21223443b3806633d7b2ccbf1fbd714a2488c1dd3a67a8096a988eaba26cd04f9b38e0bca0cb42261394bd1927b98213802335d5e30d9ea496e3bf5a0
6
+ metadata.gz: 4ae828117862ee0e38f5b670f4ddeb78b69f1868db1b18978db53dc8b2a97f851cb3ed6c5eca960f53a43ff442fb471e769b743cb7b5e040647883af68680f5d
7
+ data.tar.gz: 6b613688092373d7eec2d7cf66336a23f9cc8dfe45008f88d0d98a74b4d63486327350700d52a4f742472c4bf465acee477d34c41bad85ff5b5ad8261b82a9f7
data/README.md CHANGED
@@ -97,9 +97,10 @@ have been extracted from the input string.
97
97
 
98
98
  (?<var> \g<lead>\g<flags>[-+]?(\d+(\.\d+)?)?[a-zA-Z]){0}
99
99
  (?<set> \g<lead>\g<flags>(\d+(,\d+)?)?\[([^\]\\]|\\.)+\]){0}
100
+ (?<rgx> \g<lead>\g<flags>\/([^\\ \/]|\\.)*\/([imx]*)){0}
100
101
  (?<per> \g<lead>%){0}
101
102
 
102
- \g<var> | \g<set> | \g<per>
103
+ \g<var> | \g<set> | \g<rgx> | \g<per>
103
104
  }x
104
105
 
105
106
  ### Var
@@ -108,12 +109,20 @@ A format specification of the classical form:
108
109
  %[flags][+/-][width[.precision]]letter
109
110
 
110
111
  ### Set
111
- A reguluar expression set (or un-set) of the form:
112
+ A regular expression set (or un-set), used for parsing only, of the form:
112
113
 
113
- %[flags][[min_width,]max_width]"["[^]set_chars"]"
114
+ %[flags][[min_width,]max_width]"["[^]set_chars"]"
115
+
116
+ ### Rgx
117
+ A full blown regular expression, used for parsing only, of the form:
118
+
119
+ %[flags]/regex stuff goes here/[options]
120
+
121
+ Supported options are i, m, and x.
114
122
 
115
123
  ### Per
116
- A %% which evaluates to a literal character %.
124
+ A %% which evaluates to a literal character %. This is the old school
125
+ alternative to \\% which is preferred.
117
126
 
118
127
  ### Literal
119
128
  Text in between the various format specifications is treated as literal text.
@@ -122,12 +131,12 @@ Text in between the various format specifications is treated as literal text.
122
131
 
123
132
  The format specification:
124
133
  ```ruby
125
- "Elapsed = %*02H:%M:%-5.2S %d%% %@1[!?]"
134
+ "Elapsed = %*02H:%M:%-5.2S %d%% %@1[!?] %/a+b+c+/i"
126
135
  ```
127
136
  creates the following format specification array:
128
137
 
129
138
  ```ruby
130
- #<FormatEngine::FormatSpec:0x1b4e288
139
+ #<FormatEngine::FormatSpec:0x1be2140
131
140
  @specs=
132
141
  [Literal("Elapsed = "),
133
142
  Variable("%*H", ["02"]),
@@ -139,7 +148,9 @@ creates the following format specification array:
139
148
  Variable("%d", nil),
140
149
  Literal("%"),
141
150
  Literal(" "),
142
- Set("%@[!?]", "%@[", /[!?]{1,1}/)]>
151
+ Set("%@[!?]", "%@[", /[!?]{1,1}/),
152
+ Literal(" "),
153
+ Regex("%/a+b+c+/i", "%/", /a+b+c+/i)]>
143
154
  ```
144
155
  Where literals are processed as themselves, except:
145
156
  * If that literal ends with a space, that space will parse zero or more spaces.
@@ -153,13 +164,15 @@ precision fields) in the library and executing the corresponding block. The
153
164
  format string can be seen in the above sample as the first string in the
154
165
  Variable
155
166
 
156
- Sets work by looking into the input string with their regular expression. Sets
157
- are only ever used when parsing, never for formatting. Sets are executed by
158
- first looking up their long name, listed first, and then if the long name is not
159
- found, their short name, listed second is tried.
167
+ Sets and Regexes work by looking into the input string with their regular
168
+ expression. Both are only ever used when parsing, never for formatting.
169
+ They are executed by first looking up their long name, listed first, and
170
+ then if the long name is not found, their short name, listed second is tried.
171
+
172
+ **Note:** If a format specification does not correspond to an entry in the
173
+ library, an exception occurs. For example
160
174
 
161
- **Note:** If a format string does not correspond to an entry in the library,
162
- an exception occurs.
175
+ RuntimeError detected: Unsupported tag = "%!!!d"
163
176
 
164
177
  ## Formatting / Parsing Blocks
165
178
 
@@ -195,7 +208,7 @@ Methods
195
208
  * found? - Did the last parse succeed?
196
209
  * found - The text found by the last parse (or parse!) operation.
197
210
 
198
- ###Format Specifier Attributes
211
+ ###Variable Format Specifier Attributes
199
212
  The format specifier, used in both formatting and parsing and accessed as the
200
213
  fmt attribute, has itself, the following attributes:
201
214
  * has_width? - Was a width specified?
@@ -206,6 +219,10 @@ fmt attribute, has itself, the following attributes:
206
219
  * prec_str - The actual precision text or an empty string.
207
220
  * parm_str - The actual parameter (width and precision) text or an empty string.
208
221
 
222
+ ###Set and Regex Format Specifier Attributes
223
+ * width - Always 0.
224
+ * regex - The internal regex compiled for this specification
225
+
209
226
  ## Contributing
210
227
 
211
228
  #### Plan A
@@ -1,21 +1,15 @@
1
1
  #Analysis of format/parse specification strings.
2
2
 
3
+ require_relative 'format_spec/parse_regex'
3
4
  require_relative 'format_spec/literal'
4
5
  require_relative 'format_spec/variable'
5
6
  require_relative 'format_spec/set'
7
+ require_relative 'format_spec/rgx'
6
8
 
7
9
  module FormatEngine
8
10
 
9
11
  #The format string parser.
10
12
  class FormatSpec
11
- #The regex used to parse variable specifications.
12
- REGEX = %r{(?<lead> (^|(?<=[^\\]))%){0}
13
- (?<flags> [~@#$^&*=?_<>|!]*){0}
14
- (?<var> \g<lead>\g<flags>[-+]?(\d+(\.\d+)?)?[a-zA-Z]){0}
15
- (?<set> \g<lead>\g<flags>(\d+(,\d+)?)?\[([^\]\\]|\\.)+\]){0}
16
- (?<per> \g<lead>%){0}
17
- \g<var> | \g<set> | \g<per>
18
- }x
19
13
 
20
14
  #The array of specifications that were extracted.
21
15
  attr_reader :specs
@@ -43,6 +37,7 @@ module FormatEngine
43
37
  @specs << case
44
38
  when match_data[:var] then FormatVariable.new(mid)
45
39
  when match_data[:set] then FormatSet.new(mid)
40
+ when match_data[:rgx] then FormatRgx.new(mid)
46
41
  when match_data[:per] then FormatLiteral.new("\%")
47
42
  else fail "Impossible case in scan_spec."
48
43
  end
@@ -0,0 +1,16 @@
1
+ module FormatEngine
2
+
3
+ #The format string parser.
4
+ class FormatSpec
5
+ #The regex used to parse variable specifications.
6
+ REGEX = %r{(?<lead> (^|(?<=[^\\]))%){0}
7
+ (?<flags> [~@#$^&*=?_<>|!]*){0}
8
+ (?<var> \g<lead>\g<flags>[-+]?(\d+(\.\d+)?)?[a-zA-Z]){0}
9
+ (?<set> \g<lead>\g<flags>(\d+(,\d+)?)?\[([^\]\\]|\\.)+\]){0}
10
+ (?<rgx> \g<lead>\g<flags>\/([^\\ \/]|\\.)*\/([imx]*)){0}
11
+ (?<per> \g<lead>%){0}
12
+ \g<var> | \g<set> | \g<rgx> | \g<per>
13
+ }x
14
+ end
15
+
16
+ end
@@ -0,0 +1,58 @@
1
+ module FormatEngine
2
+
3
+ #A format engine set specification.
4
+ class FormatRgx
5
+
6
+ #The full name of the set.
7
+ attr_reader :long_name
8
+
9
+ #The short form name of the set.
10
+ attr_reader :short_name
11
+
12
+ #The regular expression part of this set specification.
13
+ attr_reader :regex
14
+
15
+ #The width parameter. Handled internally so this is always zero.
16
+ def width
17
+ 0
18
+ end
19
+
20
+ #Setup a variable format specification.
21
+ def initialize(format)
22
+ @long_name = format
23
+ pre, _, post = format.partition('/')
24
+ @short_name = pre + '/'
25
+
26
+ exp, _, options = post.partition(/(?<=[^\\])\// )
27
+ opt = (options.include?('x') ? Regexp::EXTENDED : 0) |
28
+ (options.include?('i') ? Regexp::IGNORECASE : 0) |
29
+ (options.include?('m') ? Regexp::MULTILINE : 0)
30
+
31
+ @regex = Regexp.new(exp.gsub(/\\\//, '/'), opt)
32
+ end
33
+
34
+ #Is this format item supported by the engine's library?
35
+ def validate(engine)
36
+ @block = engine[@long_name] || engine[@short_name]
37
+ fail "Unsupported tag = #{@raw.inspect}" unless @block
38
+ true
39
+ end
40
+
41
+ #Format onto the output string
42
+ def do_format(spec_info)
43
+ fail "The tag %{@raw} may not be used in formatting."
44
+ end
45
+
46
+ #Parse from the input string
47
+ def do_parse(spec_info)
48
+ spec_info.instance_exec(&@block)
49
+ end
50
+
51
+ #Inspect for debugging.
52
+ def inspect
53
+ "Regex(#{@long_name.inspect}, #{@short_name.inspect}, #{regex.inspect})"
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -1,5 +1,5 @@
1
1
 
2
2
  module FormatEngine
3
3
  # The version of the format_engine gem.
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -13,6 +13,30 @@ class EngineBaseTester < Minitest::Test
13
13
 
14
14
  assert_equal(42, test["%A"])
15
15
  assert_equal(nil, test["%B"])
16
+ assert(test[:before])
17
+ assert(test[:after])
18
+ assert_equal(3, test.library.length)
19
+ end
20
+
21
+ def test_the_engine_cache
22
+ lib = {
23
+ "%A" => lambda { puts "Processing %A" },
24
+ "%B" => lambda { puts "Processing %B" },
25
+ "%C" => lambda { puts "Processing %C" }
26
+ }
27
+
28
+ spec = "%A %B %C"
29
+
30
+ e1 = FormatEngine::Engine.new(lib)
31
+
32
+ s1a = e1.send(:get_spec, spec)
33
+ s1b = e1.send(:get_spec, spec)
34
+ assert_equal(s1a, s1b)
35
+
36
+ e2 = FormatEngine::Engine.new(lib)
37
+
38
+ s2 = e2.send(:get_spec, spec)
39
+ refute_equal(s1a, s2)
16
40
  end
17
41
 
18
42
  end
@@ -17,7 +17,6 @@ class FormatEngineTester < Minitest::Test
17
17
  assert_equal("Jane, Doe 21", cust.strfmt("%f, %l %a"))
18
18
  end
19
19
 
20
-
21
20
  def test_basic_parsing
22
21
  cust = Customer.strprs("Jane, Doe 21", "%f, %l %a")
23
22
 
@@ -194,4 +194,14 @@ class FormatSpecTester < Minitest::Test
194
194
  assert_equal(")", test.specs[6].literal)
195
195
  end
196
196
 
197
+ def test_that_it_scans_regex_formats
198
+ test = FormatEngine::FormatSpec.new "%/ABC/"
199
+ assert_equal(Array, test.specs.class)
200
+ assert_equal(1, test.specs.length)
201
+ assert_equal(FormatEngine::FormatRgx, test.specs[0].class)
202
+ assert_equal(/ABC/, test.specs[0].regex)
203
+ end
204
+
205
+
206
+
197
207
  end
@@ -13,16 +13,21 @@ class ParserTester < Minitest::Test
13
13
 
14
14
  def make_parser
15
15
  FormatEngine::Engine.new(
16
- "%a" => lambda { tmp[:age] = found.to_i if parse(/\d+/) },
17
- "%f" => lambda { tmp[:fn] = found if parse(/(\w)+/) },
18
- "%F" => lambda { tmp[:fn] = found.upcase if parse(/(\w)+/) },
19
- "%-F" => lambda { tmp[:fn] = found.capitalize if parse(/(\w)+/) },
20
- "%l" => lambda { tmp[:ln] = found if parse(/(\w)+/ ) },
21
- "%L" => lambda { tmp[:ln] = found.upcase if parse(/(\w)+/) },
22
- "%-L" => lambda { tmp[:ln] = found.capitalize if parse(/(\w)+/) },
23
- "%[" => lambda { parse! fmt.regex },
24
- "%t" => lambda { parse("\t") },
25
- "%!t" => lambda { parse!("\t") },
16
+ "%a" => lambda { tmp[:age] = found.to_i if parse(/\d+/) },
17
+ "%f" => lambda { tmp[:fn] = found if parse(/(\w)+/) },
18
+ "%F" => lambda { tmp[:fn] = found.upcase if parse(/(\w)+/) },
19
+ "%-F" => lambda { tmp[:fn] = found.capitalize if parse(/(\w)+/) },
20
+ "%l" => lambda { tmp[:ln] = found if parse(/(\w)+/ ) },
21
+ "%L" => lambda { tmp[:ln] = found.upcase if parse(/(\w)+/) },
22
+ "%-L" => lambda { tmp[:ln] = found.capitalize if parse(/(\w)+/) },
23
+ "%[" => lambda { parse! fmt.regex },
24
+ "%[A-Z]" =>
25
+ lambda { tmp[:ln] = found if parse! fmt.regex },
26
+ "%/" => lambda { parse! fmt.regex },
27
+ "%/[A-Z]+/i" =>
28
+ lambda { tmp[:ln] = found if parse! fmt.regex },
29
+ "%t" => lambda { parse("\t") },
30
+ "%!t" => lambda { parse!("\t") },
26
31
 
27
32
  :after => lambda do
28
33
  set dst.new(*[tmp[:fn], tmp[:ln], tmp[:age]].delete_if(&:nil?))
@@ -105,6 +110,33 @@ class ParserTester < Minitest::Test
105
110
  assert_equal("Squidly", result.first_name)
106
111
  assert_equal("Jones", result.last_name)
107
112
  assert_equal(55, result.age)
113
+
114
+ spec = "%f %[A-Z] %a"
115
+ result = engine.do_parse("Squidly JONES 55", TestPerson, spec)
116
+
117
+ assert_equal(TestPerson, result.class)
118
+ assert_equal("Squidly", result.first_name)
119
+ assert_equal("JONES", result.last_name)
120
+ assert_equal(55, result.age)
121
+ end
122
+
123
+ def test_that_it_can_parse_regexes
124
+ engine = make_parser
125
+ spec = "%f %l %/[Aa]ge/ %a"
126
+ result = engine.do_parse("Squidly Jones age 55", TestPerson, spec)
127
+
128
+ assert_equal(TestPerson, result.class)
129
+ assert_equal("Squidly", result.first_name)
130
+ assert_equal("Jones", result.last_name)
131
+ assert_equal(55, result.age)
132
+
133
+ spec = "%f %/[A-Z]+/i %a"
134
+ result = engine.do_parse("Squidly Jones 55", TestPerson, spec)
135
+
136
+ assert_equal(TestPerson, result.class)
137
+ assert_equal("Squidly", result.first_name)
138
+ assert_equal("Jones", result.last_name)
139
+ assert_equal(55, result.age)
108
140
  end
109
141
 
110
142
  def test_that_it_can_detect_errors
@@ -0,0 +1,73 @@
1
+ # coding: utf-8
2
+
3
+ require_relative '../lib/format_engine'
4
+ gem 'minitest'
5
+ require 'minitest/autorun'
6
+ require 'minitest_visible'
7
+
8
+ class RgxSpecTester < Minitest::Test
9
+
10
+ #Track mini-test progress.
11
+ include MinitestVisible
12
+
13
+ def test_the_parms
14
+ test = FormatEngine::FormatRgx.new("%/ABC/")
15
+ assert_equal(0, test.width)
16
+ assert_equal(/ABC/, test.regex)
17
+ assert_equal('Regex("%/ABC/", "%/", /ABC/)', test.inspect)
18
+
19
+ test = FormatEngine::FormatRgx.new("%*/ABC/")
20
+ assert_equal(0, test.width)
21
+ assert_equal(/ABC/, test.regex)
22
+ assert_equal('Regex("%*/ABC/", "%*/", /ABC/)', test.inspect)
23
+
24
+ test = FormatEngine::FormatRgx.new("%/ABC/x")
25
+ assert_equal(0, test.width)
26
+ assert_equal(/ABC/x, test.regex)
27
+ assert_equal('Regex("%/ABC/x", "%/", /ABC/x)', test.inspect)
28
+
29
+ test = FormatEngine::FormatRgx.new("%/ABC/i")
30
+ assert_equal(0, test.width)
31
+ assert_equal(/ABC/i, test.regex)
32
+ assert_equal('Regex("%/ABC/i", "%/", /ABC/i)', test.inspect)
33
+
34
+ test = FormatEngine::FormatRgx.new("%/ABC/m")
35
+ assert_equal(0, test.width)
36
+ assert_equal(/ABC/m, test.regex)
37
+ assert_equal('Regex("%/ABC/m", "%/", /ABC/m)', test.inspect)
38
+
39
+ test = FormatEngine::FormatRgx.new("%/ABC/xi")
40
+ assert_equal(0, test.width)
41
+ assert_equal(/ABC/ix, test.regex)
42
+ assert_equal('Regex("%/ABC/xi", "%/", /ABC/ix)', test.inspect)
43
+
44
+ test = FormatEngine::FormatRgx.new("%/ABC/mi")
45
+ assert_equal(0, test.width)
46
+ assert_equal(/ABC/mi, test.regex)
47
+ assert_equal('Regex("%/ABC/mi", "%/", /ABC/mi)', test.inspect)
48
+
49
+ test = FormatEngine::FormatRgx.new("%/ABC/xim")
50
+ assert_equal(0, test.width)
51
+ assert_equal(/ABC/ixm, test.regex)
52
+ assert_equal('Regex("%/ABC/xim", "%/", /ABC/mix)', test.inspect)
53
+
54
+ test = FormatEngine::FormatRgx.new("%/A\\/C/")
55
+ assert_equal(0, test.width)
56
+ assert_equal('Regex("%/A\\\\/C/", "%/", /A\/C/)', test.inspect)
57
+ assert_equal(/A\/C/, test.regex)
58
+
59
+ end
60
+
61
+ def test_unsupported_methods
62
+ test = FormatEngine::FormatRgx.new("%/ABC/")
63
+
64
+ assert_raises() {test.has_width?}
65
+ assert_raises() {test.width_str}
66
+ assert_raises() {test.has_prec?}
67
+ assert_raises() {test.prec}
68
+ assert_raises() {test.prec_str}
69
+ assert_raises() {test.parm_str}
70
+ end
71
+
72
+
73
+ end
@@ -21,5 +21,21 @@ class SetSpecTester < Minitest::Test
21
21
  assert_equal(0, test.width)
22
22
  assert_equal(/[ABC]{5,10}/, test.regex)
23
23
 
24
+ test = FormatEngine::FormatSet.new("%0,10[ABC]")
25
+ assert_equal(0, test.width)
26
+ assert_equal(/[ABC]{0,10}/, test.regex)
27
+
24
28
  end
29
+
30
+ def test_unsupported_methods
31
+ test = FormatEngine::FormatSet.new("%[ABC]")
32
+
33
+ assert_raises() {test.has_width?}
34
+ assert_raises() {test.width_str}
35
+ assert_raises() {test.has_prec?}
36
+ assert_raises() {test.prec}
37
+ assert_raises() {test.prec_str}
38
+ assert_raises() {test.parm_str}
39
+ end
40
+
25
41
  end
@@ -46,4 +46,11 @@ class VariableSpecTester < Minitest::Test
46
46
  assert_equal("-10.5", test.parm_str)
47
47
 
48
48
  end
49
+
50
+ def test_unsupported_methods
51
+ test = FormatEngine::FormatVariable.new("%B")
52
+
53
+ assert_raises() {test.regex}
54
+ end
55
+
49
56
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: format_engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Camilleri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-19 00:00:00.000000000 Z
11
+ date: 2016-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -101,6 +101,8 @@ files:
101
101
  - lib/format_engine/engine.rb
102
102
  - lib/format_engine/format_spec.rb
103
103
  - lib/format_engine/format_spec/literal.rb
104
+ - lib/format_engine/format_spec/parse_regex.rb
105
+ - lib/format_engine/format_spec/rgx.rb
104
106
  - lib/format_engine/format_spec/set.rb
105
107
  - lib/format_engine/format_spec/variable.rb
106
108
  - lib/format_engine/spec_info.rb
@@ -118,6 +120,7 @@ files:
118
120
  - tests/formatter_engine_tests.rb
119
121
  - tests/literal_spec_tests.rb
120
122
  - tests/parser_engine_tests.rb
123
+ - tests/rgx_spec_tests.rb
121
124
  - tests/scan_tests.rb
122
125
  - tests/set_spec_tests.rb
123
126
  - tests/variable_spec_tests.rb