format_engine 0.4.0 → 0.5.0

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
  SHA1:
3
- metadata.gz: dbd9eddd61c278dea142573e9edc3ff65aa622f9
4
- data.tar.gz: 5d4637bb827e30c3230cb44b9f4e02d8640af24e
3
+ metadata.gz: e58ab6d2894a40beaf8ca35a5ed4483cc577741a
4
+ data.tar.gz: 88e61103fdaf4e4dd5e0eec8c871dee94ff54e17
5
5
  SHA512:
6
- metadata.gz: ec88dfaf16365f492b9ab47f15885a476218e7e610a74726b9199d077a5ae061579304d5f6d7aceb8612d39abb8757c3003731d8f223d04fc07aa188158fbe0d
7
- data.tar.gz: 8c4926a9a3abba6ac9fc2b36155e08a8f987714201da795891106aab22ade47887b4ed2aa9a731d21158591649272513e742e26739de4b0a22af27bd74dd4d82
6
+ metadata.gz: 6e45c594310117150e99b10b89a2f0ed9b2df80a80b5e0126037a90b62e04abbf9a596e1159328b69f6bf504d284423e16c634ce8c376227a19c2dc1e8a50054
7
+ data.tar.gz: 5e9fd537fb2ad8e03b3ce6cef6e4b779a809d514fa859c2d038b6b91c6df014b2a138ef52087ece7309d7303f19b731629826c8335f64b5032bb49dc67bd2322
data/README.md CHANGED
@@ -181,7 +181,7 @@ most welcomed.
181
181
 
182
182
  #### Plan A
183
183
 
184
- 1. Fork it ( https://github.com/[my-github-username]/format_engine/fork )
184
+ 1. Fork it ( https://github.com/PeterCamilleri/format_engine/fork )
185
185
  2. Create your feature branch (`git checkout -b my-new-feature`)
186
186
  3. Commit your changes (`git commit -am 'Add some feature'`)
187
187
  4. Push to the branch (`git push origin my-new-feature`)
@@ -19,6 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ["lib"]
21
21
 
22
+ spec.required_ruby_version = '>= 1.9.3'
23
+
22
24
  spec.add_development_dependency "bundler", "~> 1.7"
23
25
  spec.add_development_dependency "rake", "~> 10.0"
24
26
  spec.add_development_dependency 'minitest', "~> 5.5.1"
@@ -31,10 +31,10 @@ module FormatEngine
31
31
  #* src - The source object being formatted.
32
32
  #* format_spec_str - The format specification string.
33
33
  def do_format(src, format_spec_str)
34
- spec_info = SpecInfo.new(src, "", nil, self, {})
34
+ spec_info = SpecInfo.new(src, "", self)
35
35
 
36
- due_process(spec_info, format_spec_str) do |fmt|
37
- fmt.do_format(spec_info)
36
+ due_process(spec_info, format_spec_str) do |format|
37
+ spec_info.do_format(format)
38
38
  end
39
39
  end
40
40
 
@@ -44,23 +44,29 @@ module FormatEngine
44
44
  #* dst - The class of the object being created.
45
45
  #* parse_spec_str - The format specification string.
46
46
  def do_parse(src, dst, parse_spec_str)
47
- spec_info = SpecInfo.new(src, dst, nil, self, {})
47
+ spec_info = SpecInfo.new(src, dst, self)
48
48
 
49
- due_process(spec_info, parse_spec_str) do |fmt|
50
- fmt.do_parse(spec_info)
49
+ due_process(spec_info, parse_spec_str) do |format|
50
+ spec_info.do_parse(format)
51
51
  end
52
52
  end
53
53
 
54
+ private
55
+
54
56
  #Do the actual work of parsing the formatted input.
55
57
  #<br>Parameters
56
58
  #* spec_info - The state of the process.
57
59
  #* spec_str - The format specification string.
58
60
  #* block - A code block performed for each format specification.
59
- def due_process(spec_info, spec_str, &block)
60
- spec = FormatSpec.get_spec(spec_str).validate(self)
61
+ def due_process(spec_info, spec_str)
62
+ format_spec = FormatSpec.get_spec(spec_str).validate(self)
61
63
 
62
64
  spec_info.instance_exec(&self[:before])
63
- spec.specs.each(&block)
65
+
66
+ format_spec.specs.each do |format|
67
+ break if yield(format) == :break
68
+ end
69
+
64
70
  spec_info.instance_exec(&self[:after])
65
71
 
66
72
  spec_info.dst
@@ -13,6 +13,11 @@ module FormatEngine
13
13
  @has_tail = @head != @literal
14
14
  end
15
15
 
16
+ #The width parameter. Handled literally so this is always zero.
17
+ def width
18
+ 0
19
+ end
20
+
16
21
  # Is this literal supported by the engine? YES!
17
22
  def validate(_engine)
18
23
  self
@@ -9,6 +9,11 @@ module FormatEngine
9
9
  #The regular expression part of this set specification.
10
10
  attr_reader :regex
11
11
 
12
+ #The width parameter. Handled internally so this is always zero.
13
+ def width
14
+ 0
15
+ end
16
+
12
17
  #Setup a variable format specification.
13
18
  def initialize(format)
14
19
  @raw = format
@@ -41,7 +46,6 @@ module FormatEngine
41
46
 
42
47
  #Parse from the input string
43
48
  def do_parse(spec_info)
44
- spec_info.fmt = self
45
49
  spec_info.instance_exec(&spec_info.engine[self.format])
46
50
  end
47
51
 
@@ -67,21 +67,13 @@ module FormatEngine
67
67
  has_prec? ? parms[1] : ""
68
68
  end
69
69
 
70
- #Build up a regular expression for parsing.
71
- def regex(base)
72
- qualifier = has_width? ? "{1,#{width}}": "+"
73
- Regexp.new("#{base}#{qualifier}")
74
- end
75
-
76
70
  #Format onto the output string
77
71
  def do_format(spec_info)
78
- spec_info.fmt = self
79
72
  spec_info.instance_exec(&spec_info.engine[self.format])
80
73
  end
81
74
 
82
75
  #Parse from the input string
83
76
  def do_parse(spec_info)
84
- spec_info.fmt = self
85
77
  spec_info.instance_exec(&spec_info.engine[self.format])
86
78
  end
87
79
 
@@ -4,21 +4,6 @@ require_relative 'format_spec/literal'
4
4
  require_relative 'format_spec/variable'
5
5
  require_relative 'format_spec/set'
6
6
 
7
- # Format String Specification Syntax (BNF):
8
- # spec = (text | item | set)+
9
- # item = "%" flag* sign? (parm ("." parm)? )? command
10
- # set = "%" flag* parm? "[" chrs "]"
11
- # flag = ( "~" | "@" | "#" | "&" | "^" |
12
- # "&" | "*" | "-" | "+" | "=" |
13
- # "?" | "_" | "<" | ">" | "\\" |
14
- # "/" | "." | "," | "|" | "!" )
15
- # sign = ("+" | "-")
16
- # parm = ("0" .. "9" )+
17
- # chrs = (not_any("]") | "\\" "]")+
18
- # command = ("a" .. "z" | "A" .. "Z")
19
- #
20
- # Sample: x = FormatSpec.get_spec "Elapsed = %*3.1H:%02M!"
21
-
22
7
  module FormatEngine
23
8
 
24
9
  #The format string parser.
@@ -27,8 +12,9 @@ module FormatEngine
27
12
  REGEX = %r{(?<flags> [~@#$^&*\=?_<>\\\/\.,\|!]*){0}
28
13
  (?<parms> [-+]?(\d+(\.\d+)?)?){0}
29
14
  (?<var> %\g<flags>\g<parms>[a-zA-Z]){0}
30
- (?<set> %\g<flags>\d*\[([^\]]|\\\])+\]){0}
31
- \g<var> | \g<set>
15
+ (?<set> %\g<flags>\d*\[([^\]\\]|\\.)+\]){0}
16
+ (?<per> %%){0}
17
+ \g<var> | \g<set> | \g<per>
32
18
  }x
33
19
 
34
20
  #Don't use new, use get_spec instead.
@@ -63,6 +49,7 @@ module FormatEngine
63
49
  @specs << case
64
50
  when match_data[:var] then FormatVariable.new(mid)
65
51
  when match_data[:set] then FormatSet.new(mid)
52
+ when match_data[:per] then FormatLiteral.new("\%")
66
53
  else fail "Impossible case in scan_spec."
67
54
  end
68
55
  fmt_string = match_data.post_match
@@ -1,43 +1,26 @@
1
1
  module FormatEngine
2
2
 
3
3
  #A little package of info about the engine's progress.
4
- #In the context of a formatting / parsing block, the
5
- #"self" of that block is an instance of SpecInfo.
6
- #<br>
7
- #<br>The components of that instance Struct are:
8
- #<br>
9
- #<br>When Formatting:
10
- #* src - The object that is the source of the data.
11
- #* dst - A string that receives the formatted output.
12
- #* fmt - The format specification currently being processed.
13
- #* engine - The formatting engine. Mostly for access to the library.
14
- #* tmp - A utility hash so that the formatting process can retain state.
15
- #<br>Methods
16
- #* cat - Append the string that follows to the formatted output.
17
- #<br>
18
- #<br>When Parsing:
19
- #* src - A string that is the source of formatted input.
20
- #* dst - The class of the object being created.
21
- #* fmt - The parse specification currently being processed.
22
- #* engine - The parsing engine. Mostly for access to the library.
23
- #* tmp - A utility hash so that the parsing process can retain state.
24
- #<br>Methods
25
- #* set - Set the return value of the parsing operation to the value that follows.
26
- #* parse - Look for the string or regex parm that follows. Return the data found or nil.
27
- #* parse! - Like parse but raises an exception (with optional msg) if not found.
28
- #* found? - Did the last parse succeed?
29
- #* found - The text found by the last parse (or parse!) operation.
30
4
  class SpecInfo
31
5
 
32
- # General readers
33
- attr_reader :src, :dst, :engine, :tmp
6
+ #The source data for formatting, string input for parsing.
7
+ attr_reader :src
34
8
 
35
- #General accessors
36
- attr_accessor :fmt
9
+ #The destination of the process.
10
+ attr_reader :dst
11
+
12
+ #The formatting engine.
13
+ attr_reader :engine
14
+
15
+ #A hash for state storage for the formatting/parsing process.
16
+ attr_reader :tmp
17
+
18
+ #The format specifier currently being processed.
19
+ attr_reader :fmt
37
20
 
38
21
  # Set up the spec info.
39
- def initialize(src, dst, fmt, engine, tmp = {})
40
- @src, @dst, @fmt, @engine, @tmp = src, dst, fmt, engine, tmp
22
+ def initialize(src, dst, engine)
23
+ @src, @dst, @engine, @tmp = src, dst, engine, {}
41
24
  end
42
25
 
43
26
  # Concatenate onto the formatted output string.
@@ -50,34 +33,66 @@ module FormatEngine
50
33
  @dst = obj
51
34
  end
52
35
 
53
- # Parse the source string for a string or regex
54
- def parse(tgt)
55
- @result = src.partition(tgt)
36
+ #Pass the formatting action along to the current format element.
37
+ def do_format(fmt)
38
+ (@fmt = fmt).do_format(self)
39
+ end
40
+
41
+ #Pass the parsing action along to the current format element.
42
+ def do_parse(fmt)
43
+ (@fmt = fmt).do_parse(self)
44
+ end
45
+
46
+ # Parse the source string for a target string or regex or return nil.
47
+ def parse(target)
48
+ #Handle the width option if specified.
49
+ if (width = fmt.width) > 0
50
+ head, tail = src[0...width], src[width..-1] || ""
51
+ else
52
+ head, tail = src, ""
53
+ end
56
54
 
55
+ #Do the parse on the input string.
56
+ @prematch, @match, @postmatch = head.partition(target)
57
+
58
+ #Analyze the results.
57
59
  if found?
58
- @src = @result[2]
59
- @result[1]
60
+ @src = @postmatch + tail
61
+ @match
60
62
  else
61
63
  nil
62
64
  end
63
65
  end
64
66
 
65
- # Parse the source string for a string or regex or raise error.
66
- def parse!(tgt, msg = "#{tgt.inspect} not found")
67
- fail "Parse error: #{msg}" unless parse(tgt)
68
- found
67
+ # Parse the source string for a target string or regex or raise error.
68
+ def parse!(target, msg = "#{target.inspect} not found")
69
+ fail "Parse error: #{msg}" unless parse(target)
70
+ @match
71
+ end
72
+
73
+ #Grab some text
74
+ def grab
75
+ if (width = fmt.width) > 0
76
+ result, @src = src[0...width], src[width..-1] || ""
77
+ elsif width == -1
78
+ result, @src = src, ""
79
+ elsif width < 0
80
+ result, @src = src[0..width], src[(width+1)..-1] || ""
81
+ else
82
+ result, @src = src[0...1], src[1..-1] || ""
83
+ end
84
+
85
+ result
69
86
  end
70
87
 
71
88
  # Was the last parse a success?
72
89
  def found?
73
- @result[0].empty? && !@result[1].empty?
90
+ @prematch.empty? && !@match.empty?
74
91
  end
75
92
 
76
93
  # What was found by the last parse?
77
94
  def found
78
- @result[1]
95
+ @match
79
96
  end
80
-
81
97
  end
82
-
83
- end
98
+ end
@@ -1,5 +1,5 @@
1
1
 
2
2
  module FormatEngine
3
3
  # The version of the format_engine gem.
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -24,16 +24,8 @@ class FormatterTester < Minitest::Test
24
24
  TestPerson.new("Squidly", "Jones", 21)
25
25
  end
26
26
 
27
- def make_spec(str)
28
- str
29
- end
30
-
31
27
  def make_all(str)
32
- [make_formatter, make_person, make_spec(str)]
33
- end
34
-
35
- def test_crazy
36
- assert_equal("0.4.0", FormatEngine::VERSION)
28
+ [make_formatter, make_person, str]
37
29
  end
38
30
 
39
31
  def test_that_it_can_format_normally
@@ -14,7 +14,7 @@ class LiteralSpecTester < Minitest::Test
14
14
  end
15
15
 
16
16
  def test_that_it_formats
17
- spec_info = FormatEngine::SpecInfo.new(nil, "", nil, nil, {})
17
+ spec_info = FormatEngine::SpecInfo.new(nil, "", nil)
18
18
  test = FormatEngine::FormatLiteral.new("Test 1 2 3")
19
19
  test.do_format(spec_info)
20
20
 
@@ -25,7 +25,7 @@ class ParserTester < Minitest::Test
25
25
  "%!t" => lambda { parse!("\t") },
26
26
 
27
27
  :after => lambda do
28
- set TestPerson.new(*[tmp[:fn], tmp[:ln], tmp[:age]].delete_if(&:nil?))
28
+ set dst.new(*[tmp[:fn], tmp[:ln], tmp[:age]].delete_if(&:nil?))
29
29
  end)
30
30
  end
31
31
 
data/tests/scan_tests.rb CHANGED
@@ -10,32 +10,127 @@ class ScanTester < Minitest::Test
10
10
  #Track mini-test progress.
11
11
  MinitestVisible.track self, __FILE__
12
12
 
13
+ DECIMAL = /[+-]?\d+/
14
+ HEX = /[+-]?(0[xX])?\h+/
15
+ OCTAL = /[+-]?(0[oO])?[0-7]+/
16
+ BINARY = /[+-]?(0[bB])?[01]+/
17
+ INTEGER = /[+-]?((0[xX]\h+)|(0[bB][01]+)|(0[oO]?[0-7]*)|([1-9]\d*))/
18
+ FLOAT = /[+-]?\d+(\.\d+)?([eE][+-]?\d+)?/
19
+ RATIONAL = /[+-]?\d+\/\d+(r)?/
20
+ COMPLEX = %r{(?<num> \d+(\.\d+)?([eE][+-]?\d+)?){0}
21
+ [+-]?\g<num>[+-]\g<num>[ij]
22
+ }x
23
+ QUOTED = /("([^\\"]|\\.)*")|('([^\\']|\\.)*')/
24
+
13
25
  def make_parser
14
26
  FormatEngine::Engine.new(
15
- "%d" => lambda do
16
- dst << found.to_i if parse(fmt.regex("\\d"))
17
- end,
27
+ "%b" => lambda {parse(BINARY) ? dst << found.to_i(2) : :break},
28
+ "%*b" => lambda {parse(BINARY) || :break},
18
29
 
19
- "%*d" => lambda do
20
- parse(fmt.regex("\\d"))
21
- end,
30
+ "%c" => lambda {dst << grab},
31
+ "%*c" => lambda {grab},
32
+
33
+ "%d" => lambda {parse(DECIMAL) ? dst << found.to_i : :break},
34
+ "%*d" => lambda {parse(DECIMAL) || :break},
35
+
36
+ "%f" => lambda {parse(FLOAT) ? dst << found.to_f : :break},
37
+ "%*f" => lambda {parse(FLOAT) || :break},
22
38
 
23
- "%[" => lambda do
24
- dst << found if parse(fmt.regex)
39
+ "%i" => lambda {parse(INTEGER) ? dst << found.to_i(0) : :break},
40
+ "%*i" => lambda {parse(INTEGER) || :break},
41
+
42
+ "%j" => lambda {parse(COMPLEX) ? dst << Complex(found) : :break},
43
+ "%*j" => lambda {parse(COMPLEX) || :break},
44
+
45
+ "%o" => lambda {parse(OCTAL) ? dst << found.to_i(8) : :break},
46
+ "%*o" => lambda {parse(OCTAL) || :break},
47
+
48
+ "%q" => lambda do
49
+ parse(QUOTED) ? dst << found[1..-2].gsub(/\\./) {|seq| seq[-1]} : :break
25
50
  end,
51
+ "%*q" => lambda {parse(QUOTED) || :break},
52
+
53
+ "%r" => lambda {parse(RATIONAL) ? dst << found.to_r : :break},
54
+ "%*r" => lambda {parse(RATIONAL) || :break},
26
55
 
27
- "%*[" => lambda do
28
- parse(fmt.regex)
29
- end)
56
+ "%s" => lambda {parse(/\S+/) ? dst << found : :break},
57
+ "%*s" => lambda {parse(/\S+/) || :break},
58
+
59
+ "%u" => lambda {parse(/\d+/) ? dst << found.to_i : :break},
60
+ "%*u" => lambda {parse(/\d+/) || :break},
61
+
62
+ "%x" => lambda {parse(HEX) ? dst << found.to_i(16) : :break},
63
+ "%*x" => lambda {parse(HEX) || :break},
64
+
65
+ "%[" => lambda {parse(fmt.regex) ? dst << found : :break},
66
+ "%*[" => lambda {parse(fmt.regex) || :break})
30
67
  end
31
68
 
32
69
  def test_that_it_can_scan
33
70
  engine = make_parser
34
71
  spec = "%d %2d %4d"
35
- result = engine.do_parse("12 34 56", [], spec)
72
+ result = engine.do_parse("12 34 -56", [], spec)
73
+ assert_equal([12, 34, -56] , result)
36
74
 
37
- assert_equal(Array, result.class)
38
- assert_equal([12, 34, 56] , result)
75
+ spec = "%i %i %i %i %i"
76
+ result = engine.do_parse("255 0b11111111 0377 0xFF 0 ", [], spec)
77
+ assert_equal([255, 255, 255, 255, 0] , result)
78
+
79
+ spec = "%o %o %o"
80
+ result = engine.do_parse("7 10 377", [], spec)
81
+ assert_equal([7, 8, 255] , result)
82
+
83
+ spec = "%b %b %b"
84
+ result = engine.do_parse("10 10011 11110000", [], spec)
85
+ assert_equal([2, 19, 240] , result)
86
+
87
+ spec = "%x %x %x %x %x"
88
+ result = engine.do_parse("0 F FF FFF FFFF", [], spec)
89
+ assert_equal([0, 15, 255, 4095, 65535] , result)
90
+
91
+ spec = "%s %*s %s"
92
+ result = engine.do_parse("Hello Silly World", [], spec)
93
+ assert_equal(["Hello", "World"] , result)
94
+
95
+ spec = "%5c %*5c %5c"
96
+ result = engine.do_parse("Hello Silly World", [], spec)
97
+ assert_equal(["Hello", "World"] , result)
98
+
99
+ spec = "%i %-1c"
100
+ result = engine.do_parse("42 The secret is X", [], spec)
101
+ assert_equal([42, "The secret is X"] , result)
102
+
103
+ spec = "%i %-2c%c"
104
+ result = engine.do_parse("42 The secret is X", [], spec)
105
+ assert_equal([42, "The secret is ", "X"] , result)
106
+
107
+ spec = "%i %*-2c%c"
108
+ result = engine.do_parse("42 The secret is X", [], spec)
109
+ assert_equal([42, "X"] , result)
110
+
111
+ spec = "%f %f %f"
112
+ result = engine.do_parse("9.99 1.234e56 -1e100", [], spec)
113
+ assert_equal([9.99, 1.234e56, -1e100] , result)
114
+
115
+ spec = "%f%% %f%%"
116
+ result = engine.do_parse("85% 75%", [], spec)
117
+ assert_equal([85, 75] , result)
118
+
119
+ spec = "%u %u %u"
120
+ result = engine.do_parse("12 34 -56", [], spec)
121
+ assert_equal([12, 34] , result)
122
+
123
+ spec = "%r %r %r"
124
+ result = engine.do_parse("1/2 3/4r -5/6", [], spec)
125
+ assert_equal(['1/2'.to_r, '3/4'.to_r, '-5/6'.to_r] , result)
126
+
127
+ spec = "%j %j %j"
128
+ result = engine.do_parse("1+2i 3+4j -5e10-6.2i", [], spec)
129
+ assert_equal([Complex('1+2i'), Complex('3+4j'), Complex('-5e10-6.2i')] , result)
130
+
131
+ spec = "%q %*q %q %q"
132
+ result = engine.do_parse("'quote' 'silly' \"un quote\" 'a \\'' ", [], spec)
133
+ assert_equal(["quote", "un quote", "a '"] , result)
39
134
  end
40
135
 
41
136
  def test_missing_data
@@ -56,6 +151,15 @@ class ScanTester < Minitest::Test
56
151
  assert_equal([12, 34, 56] , result)
57
152
  end
58
153
 
154
+ def test_malformed_data
155
+ engine = make_parser
156
+ spec = "%d %d %d"
157
+ result = engine.do_parse("12 igloo 34 56", [], spec)
158
+
159
+ assert_equal(Array, result.class)
160
+ assert_equal([12] , result)
161
+ end
162
+
59
163
  def test_skipped_data
60
164
  engine = make_parser
61
165
  spec = "%d %2d %*d %d"
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.4.0
4
+ version: 0.5.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-03 00:00:00.000000000 Z
11
+ date: 2016-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -132,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
132
  requirements:
133
133
  - - ">="
134
134
  - !ruby/object:Gem::Version
135
- version: '0'
135
+ version: 1.9.3
136
136
  required_rubygems_version: !ruby/object:Gem::Requirement
137
137
  requirements:
138
138
  - - ">="