format_engine 0.4.0 → 0.5.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: 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
  - - ">="