format_engine 0.3.2 → 0.4.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: 5d7dfb82f1660f588d8c962202bc052410d0103a
4
- data.tar.gz: afc3a10a88f09024a9d37493bf77308be189473b
3
+ metadata.gz: dbd9eddd61c278dea142573e9edc3ff65aa622f9
4
+ data.tar.gz: 5d4637bb827e30c3230cb44b9f4e02d8640af24e
5
5
  SHA512:
6
- metadata.gz: 5f0d437c4b5d083b40287823fe705cb3a09871b68b8ed8805eb09e64f5d28dbb80779fb5f471f261d1ff9e84d0ba4fcd01c8f0ee78fd32b7bc0fcaa4201a585d
7
- data.tar.gz: 110691ceb8a920df1b626febe8f803402e2b8ad48fe7934bf5cd06a6f0d84e8f04bcacd404c2c885a1fa9da7ccfc4fbf432585ce5089345bf5bac18e432f60a1
6
+ metadata.gz: ec88dfaf16365f492b9ab47f15885a476218e7e610a74726b9199d077a5ae061579304d5f6d7aceb8612d39abb8757c3003731d8f223d04fc07aa188158fbe0d
7
+ data.tar.gz: 8c4926a9a3abba6ac9fc2b36155e08a8f987714201da795891106aab22ade47887b4ed2aa9a731d21158591649272513e742e26739de4b0a22af27bd74dd4d82
data/README.md CHANGED
@@ -88,12 +88,15 @@ puts cust.strfmt('%f %l is %a years old.')
88
88
 
89
89
  Format String Specification Syntax (BNF):
90
90
 
91
- * spec = ( text | item )+
92
- * item = "%" flag* ("+" | "-")?(parm ("." parm)?)? command
93
- * flag = "~"|"@"|"#"|"&"|"^"|"&"|"*"|"="|"?"|"_"|"<"|">"|"\\"|"/"|"."|","|"|"|"!"
94
- * parm = ("0" .. "9")+
95
- * command = ("a" .. "z" | "A" .. "Z")
96
-
91
+ * spec ::= (text | item | set)+
92
+ * item ::= "%" flag* sign? (parm ("." parm)? )? command
93
+ * set ::= "%" flag* parm? "[" chrs "]"
94
+ * flag ::= "~" | "@" | "#" | "&" | "^" | "&" | "*" | "=" | "?" | "_"
95
+ | "<" | ">" | "\\" | "/" | "." | "," | "|" | "!"
96
+ * sign ::= sign = ("+" | "-")
97
+ * parm ::= ("0" .. "9")+
98
+ * chrs ::= (not_any("]") | "\\" "]")+
99
+ * command ::= ("a" .. "z" | "A" .. "Z")
97
100
 
98
101
  ###Samples:
99
102
 
data/lib/format_engine.rb CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  require 'English'
4
4
 
5
- require 'format_engine/format_spec'
6
- require 'format_engine/engine'
7
- require 'format_engine/spec_info'
8
- require 'format_engine/attr_formatter'
9
- require 'format_engine/attr_parser'
5
+ require_relative 'format_engine/format_spec'
6
+ require_relative 'format_engine/engine'
7
+ require_relative 'format_engine/spec_info'
8
+ require_relative 'format_engine/attr_formatter'
9
+ require_relative 'format_engine/attr_parser'
10
10
 
11
- require 'format_engine/version'
11
+ require_relative 'format_engine/version'
@@ -3,24 +3,27 @@ module FormatEngine
3
3
  # The engine class of the format engine.
4
4
  class Engine
5
5
 
6
+ #The parse library
7
+ attr_reader :library
8
+
6
9
  #Set up base data structures.
7
10
  def initialize(library)
8
- @lib = library
11
+ @library = library
9
12
 
10
13
  #Set up defaults for pre and post amble blocks.
11
14
  nop = lambda { }
12
- @lib[:before] ||= nop
13
- @lib[:after] ||= nop
15
+ @library[:before] ||= nop
16
+ @library[:after] ||= nop
14
17
  end
15
18
 
16
19
  # Get an entry from the library
17
20
  def [](index)
18
- @lib[index]
21
+ @library[index]
19
22
  end
20
23
 
21
24
  # Set an entry in the library
22
25
  def []=(index, value)
23
- @lib[index] = value
26
+ @library[index] = value
24
27
  end
25
28
 
26
29
  #Do the actual work of building the formatted output.
@@ -1,14 +1,20 @@
1
+ #Analysis of format/parse specification strings.
2
+
1
3
  require_relative 'format_spec/literal'
2
4
  require_relative 'format_spec/variable'
5
+ require_relative 'format_spec/set'
3
6
 
4
7
  # Format String Specification Syntax (BNF):
5
- # spec = (text | item)+
6
- # item = "%" flag* (parm ("." parm)? )? command
8
+ # spec = (text | item | set)+
9
+ # item = "%" flag* sign? (parm ("." parm)? )? command
10
+ # set = "%" flag* parm? "[" chrs "]"
7
11
  # flag = ( "~" | "@" | "#" | "&" | "^" |
8
12
  # "&" | "*" | "-" | "+" | "=" |
9
13
  # "?" | "_" | "<" | ">" | "\\" |
10
14
  # "/" | "." | "," | "|" | "!" )
15
+ # sign = ("+" | "-")
11
16
  # parm = ("0" .. "9" )+
17
+ # chrs = (not_any("]") | "\\" "]")+
12
18
  # command = ("a" .. "z" | "A" .. "Z")
13
19
  #
14
20
  # Sample: x = FormatSpec.get_spec "Elapsed = %*3.1H:%02M!"
@@ -18,10 +24,12 @@ module FormatEngine
18
24
  #The format string parser.
19
25
  class FormatSpec
20
26
  #The regex used to parse variable specifications.
21
- VAR_REGEX = %r{(?<flags> [~@#$^&*\=?_<>\\\/\.,\|!]*){0}
22
- (?<parms> [-+]?(\d+(\.\d+)?)?){0}
23
- (%\g<flags>\g<parms>[a-zA-Z])
24
- }x
27
+ REGEX = %r{(?<flags> [~@#$^&*\=?_<>\\\/\.,\|!]*){0}
28
+ (?<parms> [-+]?(\d+(\.\d+)?)?){0}
29
+ (?<var> %\g<flags>\g<parms>[a-zA-Z]){0}
30
+ (?<set> %\g<flags>\d*\[([^\]]|\\\])+\]){0}
31
+ \g<var> | \g<set>
32
+ }x
25
33
 
26
34
  #Don't use new, use get_spec instead.
27
35
  private_class_method :new
@@ -47,10 +55,17 @@ module FormatEngine
47
55
  #Scan the format string extracting literals and variables.
48
56
  def scan_spec(fmt_string)
49
57
  until fmt_string.empty?
50
- if fmt_string =~ VAR_REGEX
51
- @specs << FormatLiteral.new($PREMATCH) unless $PREMATCH.empty?
52
- @specs << FormatVariable.new($MATCH)
53
- fmt_string = $POSTMATCH
58
+ if (match_data = REGEX.match(fmt_string))
59
+ mid = match_data.to_s
60
+ pre = match_data.pre_match
61
+
62
+ @specs << FormatLiteral.new(pre) unless pre.empty?
63
+ @specs << case
64
+ when match_data[:var] then FormatVariable.new(mid)
65
+ when match_data[:set] then FormatSet.new(mid)
66
+ else fail "Impossible case in scan_spec."
67
+ end
68
+ fmt_string = match_data.post_match
54
69
  else
55
70
  @specs << FormatLiteral.new(fmt_string)
56
71
  fmt_string = ""
@@ -0,0 +1,55 @@
1
+ module FormatEngine
2
+
3
+ #A format engine set specification.
4
+ class FormatSet
5
+
6
+ #The fixed part of this set specification.
7
+ attr_reader :format
8
+
9
+ #The regular expression part of this set specification.
10
+ attr_reader :regex
11
+
12
+ #Setup a variable format specification.
13
+ def initialize(format)
14
+ @raw = format
15
+
16
+ if format =~ /(\d+)(?=\[)/
17
+ qualifier = "{1,#{$MATCH}}"
18
+ @format = $PREMATCH + "["
19
+ set = $POSTMATCH
20
+ elsif format =~ /\[/
21
+ qualifier = "+"
22
+ @format = $PREMATCH + $MATCH
23
+ set = $MATCH + $POSTMATCH
24
+ else
25
+ fail "Invalid set string #{format}"
26
+ end
27
+
28
+ @regex = Regexp.new("#{set}#{qualifier}")
29
+ end
30
+
31
+ #Is this variable supported by the engine?
32
+ def validate(engine)
33
+ fail "Unsupported tag = #{format.inspect}" unless engine[format]
34
+ self
35
+ end
36
+
37
+ #Format onto the output string
38
+ def do_format(spec_info)
39
+ fail "The tag %{@raw} may not be used in formatting."
40
+ end
41
+
42
+ #Parse from the input string
43
+ def do_parse(spec_info)
44
+ spec_info.fmt = self
45
+ spec_info.instance_exec(&spec_info.engine[self.format])
46
+ end
47
+
48
+ #Inspect for debugging.
49
+ def inspect
50
+ "Set(#{format.inspect}, #{regex.inspect})"
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -67,6 +67,12 @@ 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
+
70
76
  #Format onto the output string
71
77
  def do_format(spec_info)
72
78
  spec_info.fmt = self
@@ -1,5 +1,5 @@
1
1
 
2
2
  module FormatEngine
3
3
  # The version of the format_engine gem.
4
- VERSION = "0.3.2"
4
+ VERSION = "0.4.0"
5
5
  end
data/sire.rb ADDED
@@ -0,0 +1,85 @@
1
+ # coding: utf-8
2
+ # A Simple Interactive Ruby Environment
3
+
4
+ require_relative 'lib/format_engine'
5
+
6
+ $no_alias_read_line_module = true
7
+ require 'mini_readline'
8
+ require 'pp'
9
+
10
+ class Object
11
+ #Generate the class lineage of the object.
12
+ def classes
13
+ begin
14
+ result = ""
15
+ klass = self.instance_of?(Class) ? self : self.class
16
+
17
+ begin
18
+ result << klass.to_s
19
+ klass = klass.superclass
20
+ result << " < " if klass
21
+ end while klass
22
+
23
+ result
24
+ end
25
+ end
26
+ end
27
+
28
+ class SIRE
29
+ #Set up the interactive session.
30
+ def initialize
31
+ @_done = false
32
+
33
+ puts "Welcome to a Simple Interactive Ruby Environment\n"
34
+ puts "FormatEngine version = #{FormatEngine::VERSION}"
35
+ puts "Use command 'q' to quit.\n\n"
36
+ end
37
+
38
+ #Quit the interactive session.
39
+ def q
40
+ @_done = true
41
+ puts
42
+ "Bye bye for now!"
43
+ end
44
+
45
+ def fs
46
+ FormatEngine::FormatSpec
47
+ end
48
+
49
+ #Execute a single line.
50
+ def exec_line(line)
51
+ result = eval line
52
+ pp result unless line.length == 0
53
+
54
+ rescue Interrupt => e
55
+ @_break = true
56
+ puts "\nExecution Interrupted!"
57
+ puts "\n#{e.class} detected: #{e}\n"
58
+ puts e.backtrace
59
+ puts "\n"
60
+
61
+ rescue Exception => e
62
+ @_break = true
63
+ puts "\n#{e.class} detected: #{e}\n"
64
+ puts e.backtrace
65
+ puts
66
+ end
67
+
68
+ #Run the interactive session.
69
+ def run_sire
70
+ until @_done
71
+ @_break = false
72
+ exec_line(MiniReadline.readline('SIRE>', true))
73
+ end
74
+
75
+ puts "\n\n"
76
+
77
+ rescue Interrupt => e
78
+ puts "\nInterrupted! Program Terminating."
79
+ end
80
+
81
+ end
82
+
83
+ if __FILE__ == $0
84
+ SIRE.new.run_sire
85
+ end
@@ -25,6 +25,43 @@ class FormatSpecTester < Minitest::Test
25
25
  assert_equal(nil, test.specs[0].parms)
26
26
  end
27
27
 
28
+ def test_that_it_scans_set_formats
29
+ test = FormatEngine::FormatSpec.get_spec "%[A]"
30
+ assert_equal(Array, test.specs.class)
31
+ assert_equal(1, test.specs.length)
32
+ assert_equal(FormatEngine::FormatSet, test.specs[0].class)
33
+ assert_equal("%[", test.specs[0].format)
34
+ assert_equal(/[A]+/, test.specs[0].regex)
35
+
36
+ test = FormatEngine::FormatSpec.get_spec "%*[A]"
37
+ assert_equal(Array, test.specs.class)
38
+ assert_equal(1, test.specs.length)
39
+ assert_equal(FormatEngine::FormatSet, test.specs[0].class)
40
+ assert_equal("%*[", test.specs[0].format)
41
+ assert_equal(/[A]+/, test.specs[0].regex)
42
+
43
+ test = FormatEngine::FormatSpec.get_spec "%7[A]"
44
+ assert_equal(Array, test.specs.class)
45
+ assert_equal(1, test.specs.length)
46
+ assert_equal(FormatEngine::FormatSet, test.specs[0].class)
47
+ assert_equal("%[", test.specs[0].format)
48
+ assert_equal(/[A]{1,7}/, test.specs[0].regex)
49
+
50
+ test = FormatEngine::FormatSpec.get_spec "%*7[A]"
51
+ assert_equal(Array, test.specs.class)
52
+ assert_equal(1, test.specs.length)
53
+ assert_equal(FormatEngine::FormatSet, test.specs[0].class)
54
+ assert_equal("%*[", test.specs[0].format)
55
+ assert_equal(/[A]{1,7}/, test.specs[0].regex)
56
+ end
57
+
58
+ def test_a_mixed_set
59
+ test = FormatEngine::FormatSpec.get_spec "%f %l %[age] %a"
60
+ assert_equal(Array, test.specs.class)
61
+ assert_equal(7, test.specs.length)
62
+
63
+ end
64
+
28
65
  def test_that_it_scans_tab_seperators
29
66
  test = FormatEngine::FormatSpec.get_spec "%A\t%B"
30
67
  assert_equal(Array, test.specs.class)
@@ -32,6 +32,10 @@ class FormatterTester < Minitest::Test
32
32
  [make_formatter, make_person, make_spec(str)]
33
33
  end
34
34
 
35
+ def test_crazy
36
+ assert_equal("0.4.0", FormatEngine::VERSION)
37
+ end
38
+
35
39
  def test_that_it_can_format_normally
36
40
  engine, obj, spec = make_all("Name = %f %l %a")
37
41
  assert_equal("Name = Squidly Jones 21", engine.do_format(obj, spec))
@@ -20,6 +20,7 @@ class ParserTester < Minitest::Test
20
20
  "%l" => lambda { tmp[:ln] = found if parse(/(\w)+/ ) },
21
21
  "%L" => lambda { tmp[:ln] = found.upcase if parse(/(\w)+/) },
22
22
  "%-L" => lambda { tmp[:ln] = found.capitalize if parse(/(\w)+/) },
23
+ "%[" => lambda { parse! fmt.regex },
23
24
  "%t" => lambda { parse("\t") },
24
25
  "%!t" => lambda { parse!("\t") },
25
26
 
@@ -92,6 +93,18 @@ class ParserTester < Minitest::Test
92
93
  assert_equal(TestPerson, result.class)
93
94
  assert_equal("Squidly", result.first_name)
94
95
  assert_equal("Jones", result.last_name)
96
+ assert_equal(55, result.age)
97
+ end
98
+
99
+ def test_that_it_can_parse_sets
100
+ engine = make_parser
101
+ spec = "%f %l %[age] %a"
102
+ result = engine.do_parse("Squidly Jones age 55", TestPerson, spec)
103
+
104
+ assert_equal(TestPerson, result.class)
105
+ assert_equal("Squidly", result.first_name)
106
+ assert_equal("Jones", result.last_name)
107
+ assert_equal(55, result.age)
95
108
  end
96
109
 
97
110
  def test_that_it_can_detect_errors
@@ -0,0 +1,95 @@
1
+ require_relative '../lib/format_engine'
2
+ gem 'minitest'
3
+ require 'minitest/autorun'
4
+ require 'minitest_visible'
5
+
6
+
7
+ # Test the internals of the parser engine. This is not the normal interface.
8
+ class ScanTester < Minitest::Test
9
+
10
+ #Track mini-test progress.
11
+ MinitestVisible.track self, __FILE__
12
+
13
+ def make_parser
14
+ FormatEngine::Engine.new(
15
+ "%d" => lambda do
16
+ dst << found.to_i if parse(fmt.regex("\\d"))
17
+ end,
18
+
19
+ "%*d" => lambda do
20
+ parse(fmt.regex("\\d"))
21
+ end,
22
+
23
+ "%[" => lambda do
24
+ dst << found if parse(fmt.regex)
25
+ end,
26
+
27
+ "%*[" => lambda do
28
+ parse(fmt.regex)
29
+ end)
30
+ end
31
+
32
+ def test_that_it_can_scan
33
+ engine = make_parser
34
+ spec = "%d %2d %4d"
35
+ result = engine.do_parse("12 34 56", [], spec)
36
+
37
+ assert_equal(Array, result.class)
38
+ assert_equal([12, 34, 56] , result)
39
+ end
40
+
41
+ def test_missing_data
42
+ engine = make_parser
43
+ spec = "%d %2d %4d %d"
44
+ result = engine.do_parse("12 34 56", [], spec)
45
+
46
+ assert_equal(Array, result.class)
47
+ assert_equal([12, 34, 56] , result)
48
+ end
49
+
50
+ def test_excess_data
51
+ engine = make_parser
52
+ spec = "%d %2d %4d"
53
+ result = engine.do_parse("12 34 56 78", [], spec)
54
+
55
+ assert_equal(Array, result.class)
56
+ assert_equal([12, 34, 56] , result)
57
+ end
58
+
59
+ def test_skipped_data
60
+ engine = make_parser
61
+ spec = "%d %2d %*d %d"
62
+ result = engine.do_parse("12 34 56 78", [], spec)
63
+
64
+ assert_equal(Array, result.class)
65
+ assert_equal([12, 34, 78] , result)
66
+ end
67
+
68
+ def test_packed_data
69
+ engine = make_parser
70
+ spec = "%2d %2d %2d"
71
+ result = engine.do_parse("123456", [], spec)
72
+
73
+ assert_equal(Array, result.class)
74
+ assert_equal([12, 34, 56] , result)
75
+ end
76
+
77
+ def test_some_sets
78
+ engine = make_parser
79
+ spec = "%5[truefals] %5[truefals] %5[truefals]"
80
+ result = engine.do_parse("true false true", [], spec)
81
+
82
+ assert_equal(Array, result.class)
83
+ assert_equal(["true", "false", "true"] , result)
84
+ end
85
+
86
+ def test_some_sets_with_skips
87
+ engine = make_parser
88
+ spec = "%5[truefals] %*5[truefals] %5[truefals]"
89
+ result = engine.do_parse("true false true", [], spec)
90
+
91
+ assert_equal(Array, result.class)
92
+ assert_equal(["true", "true"] , result)
93
+ end
94
+
95
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: format_engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Camilleri
@@ -101,6 +101,7 @@ 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/set.rb
104
105
  - lib/format_engine/format_spec/variable.rb
105
106
  - lib/format_engine/spec_info.rb
106
107
  - lib/format_engine/version.rb
@@ -110,12 +111,14 @@ files:
110
111
  - mocks/demo/demo_parse.rb
111
112
  - mocks/test_person_mock.rb
112
113
  - reek.txt
114
+ - sire.rb
113
115
  - tests/engine_base_tests.rb
114
116
  - tests/format_engine_tests.rb
115
117
  - tests/format_spec_tests.rb
116
118
  - tests/formatter_engine_tests.rb
117
119
  - tests/literal_spec_tests.rb
118
120
  - tests/parser_engine_tests.rb
121
+ - tests/scan_tests.rb
119
122
  - tests/variable_spec_tests.rb
120
123
  homepage: http://teuthida-technologies.com/
121
124
  licenses: