format_engine 0.0.1
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 +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +84 -0
- data/Rakefile +55 -0
- data/format_engine.gemspec +27 -0
- data/format_engine.reek +109 -0
- data/lib/format_engine/attr_formatter.rb +32 -0
- data/lib/format_engine/attr_parser.rb +33 -0
- data/lib/format_engine/engine.rb +59 -0
- data/lib/format_engine/format_spec/literal.rb +36 -0
- data/lib/format_engine/format_spec/variable.rb +64 -0
- data/lib/format_engine/format_spec.rb +61 -0
- data/lib/format_engine/spec_info.rb +83 -0
- data/lib/format_engine/version.rb +5 -0
- data/lib/format_engine.rb +11 -0
- data/mocks/README.md +10 -0
- data/mocks/demo/demo_format.rb +13 -0
- data/mocks/demo/demo_parse.rb +14 -0
- data/mocks/demo.rb +18 -0
- data/mocks/test_person_mock.rb +10 -0
- data/reek.txt +2 -0
- data/tests/engine_base_tests.rb +18 -0
- data/tests/format_engine_tests.rb +29 -0
- data/tests/format_spec_tests.rb +120 -0
- data/tests/formatter_engine_tests.rb +71 -0
- data/tests/literal_spec_tests.rb +23 -0
- data/tests/parser_engine_tests.rb +95 -0
- data/tests/variable_spec_tests.rb +34 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f68de35267cd2cec34ca42b8638cc5035f64ff42
|
4
|
+
data.tar.gz: d0c21b2ba3cd709f75d9e8e036d5848238abbafa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 493f6d819bb9c0ceae8eb2c743c5d99a7d0d3fadc537e69e90e540e06c688e3f303a417f5fd8da9174419a8febac8d65ef21209398cc66c27867ee1de325f212
|
7
|
+
data.tar.gz: 25823819e6fc80c713e1f95cfb0fcd25c3b9bf9034cf7eb0755865217a43a77ebbc6ee0a68dc55ceb22225b9ca1bb234b37fb2e6e64d610ce765546a55a0ce00
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/Gemfile.lock
|
4
|
+
/_yardoc/
|
5
|
+
/coverage/
|
6
|
+
/doc/
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/tmp/
|
10
|
+
*.bundle
|
11
|
+
*.so
|
12
|
+
*.o
|
13
|
+
*.a
|
14
|
+
mkmf.log
|
15
|
+
docs/.~lock.*
|
16
|
+
*.bat
|
17
|
+
*.zip
|
18
|
+
*.tmp
|
19
|
+
rdoc
|
20
|
+
test/tmp
|
21
|
+
test/version_tmp
|
22
|
+
tmp
|
23
|
+
temp.txt
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Peter Camilleri
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# FormatEngine
|
2
|
+
|
3
|
+
The FormatEngine gem contains the common support code needed to support
|
4
|
+
string formatting and parsing routines like strftime and strptime.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'format_engine'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install format_engine
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'format_engine'
|
26
|
+
|
27
|
+
#A demo class for the format_engine gem.
|
28
|
+
class Customer
|
29
|
+
extend FormatEngine::AttrFormatter
|
30
|
+
extend FormatEngine::AttrParser
|
31
|
+
|
32
|
+
#Demo customer first name.
|
33
|
+
attr_reader :first_name
|
34
|
+
|
35
|
+
#Demo customer last name
|
36
|
+
attr_reader :last_name
|
37
|
+
|
38
|
+
attr_formatter :strfmt,
|
39
|
+
{"%f" => lambda {cat src.first_name.ljust(fmt.width) },
|
40
|
+
"%l" => lambda {cat src.last_name.ljust(fmt.width) } }
|
41
|
+
|
42
|
+
attr_parser :strprs,
|
43
|
+
{"%f" => lambda { hsh[:fn] = found if parse(/(\w)+/ ) },
|
44
|
+
"%l" => lambda { hsh[:ln] = found if parse(/(\w)+/ ) },
|
45
|
+
:after => lambda { set dst.new(hsh[:fn], hsh[:ln]) } }
|
46
|
+
|
47
|
+
#Create an instance of the demo customer.
|
48
|
+
def initialize(first_name, last_name)
|
49
|
+
@first_name, @last_name = first_name, last_name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#Then later in the code...
|
54
|
+
|
55
|
+
cust = Customer.new("Jane", "Doe")
|
56
|
+
puts cust.strfmt("%f, %l")) #"Jane, Doe"
|
57
|
+
|
58
|
+
#And elsewhere in Gotham City...
|
59
|
+
|
60
|
+
in_str = "Jane, Doe"
|
61
|
+
agent = Customer.strprs(in_str, "%f, %l")
|
62
|
+
|
63
|
+
#Etc, etc, etc ...
|
64
|
+
|
65
|
+
```
|
66
|
+
|
67
|
+
## Philosophy
|
68
|
+
|
69
|
+
When designing this gem, a concerted effort has been applied to keeping it as
|
70
|
+
simple as possible. To this end, many subtle programming techniques have been
|
71
|
+
avoided in favor of simpler, more obvious approaches. This is based on the
|
72
|
+
observation that I don't trust code that I don't understand and I avoid code
|
73
|
+
I don't trust.
|
74
|
+
|
75
|
+
Feedback on the convenience/clarity balance as well as any other topics are
|
76
|
+
most welcomed.
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
1. Fork it ( https://github.com/[my-github-username]/format_engine/fork )
|
81
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
82
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
83
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
84
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rdoc/task'
|
6
|
+
require 'bundler/gem_tasks'
|
7
|
+
|
8
|
+
#Generate internal documentation with rdoc.
|
9
|
+
RDoc::Task.new do |rdoc|
|
10
|
+
rdoc.rdoc_dir = "rdoc"
|
11
|
+
|
12
|
+
#List out all the files to be documented.
|
13
|
+
rdoc.rdoc_files.include("lib/**/*.rb", "mocks/**/demo*.rb", "license.txt", "README.md")
|
14
|
+
|
15
|
+
#Make all access levels visible.
|
16
|
+
rdoc.options << '--visibility' << 'private'
|
17
|
+
#rdoc.options << '--verbose'
|
18
|
+
#rdoc.options << '--coverage-report'
|
19
|
+
|
20
|
+
#Set a title.
|
21
|
+
rdoc.options << '--title' << 'fOOrth Language Internals'
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
#Run the format_engine unit test suite.
|
26
|
+
Rake::TestTask.new do |t|
|
27
|
+
#List out all the test files.
|
28
|
+
t.test_files = FileList['tests/**/*.rb']
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Run a scan for smelly code!"
|
33
|
+
task :reek do |t|
|
34
|
+
`reek --no-color lib > reek.txt`
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Run an IRB Session with format_engine loaded."
|
38
|
+
task :console do
|
39
|
+
require 'irb'
|
40
|
+
require 'irb/completion'
|
41
|
+
require './lib/format_engine'
|
42
|
+
puts "Starting an IRB console with format_engine."
|
43
|
+
puts "Use 'quit' to exit."
|
44
|
+
puts
|
45
|
+
ARGV.clear
|
46
|
+
IRB.start
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "What version of code is this?"
|
50
|
+
task :vers do |t|
|
51
|
+
puts
|
52
|
+
puts "format_engine version = #{FormatEngine::VERSION}"
|
53
|
+
end
|
54
|
+
|
55
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'format_engine/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "format_engine"
|
8
|
+
spec.version = FormatEngine::VERSION
|
9
|
+
spec.authors = ["Peter Camilleri"]
|
10
|
+
spec.email = ["peter.c.camilleri@gmail.com"]
|
11
|
+
spec.summary = %q{An engine for string formatting and parsing.}
|
12
|
+
spec.description = %q{An engine for string formatting and parsing like the strftime and strptime methods.}
|
13
|
+
spec.homepage = "http://teuthida-technologies.com/"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
raw_list = `git ls-files`.split($/)
|
17
|
+
spec.files = raw_list.keep_if {|entry| !entry.start_with?("docs") }
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency 'minitest', "~> 5.5.1"
|
25
|
+
spec.add_development_dependency 'minitest_visible', "~> 0.0.1"
|
26
|
+
spec.add_development_dependency 'rdoc', "~> 4.0.1"
|
27
|
+
end
|
data/format_engine.reek
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
---
|
2
|
+
Attribute:
|
3
|
+
enabled: false
|
4
|
+
exclude: []
|
5
|
+
BooleanParameter:
|
6
|
+
enabled: true
|
7
|
+
exclude: []
|
8
|
+
ClassVariable:
|
9
|
+
enabled: true
|
10
|
+
exclude: []
|
11
|
+
ControlParameter:
|
12
|
+
enabled: true
|
13
|
+
exclude: []
|
14
|
+
DataClump:
|
15
|
+
enabled: true
|
16
|
+
exclude: []
|
17
|
+
max_copies: 2
|
18
|
+
min_clump_size: 2
|
19
|
+
DuplicateMethodCall:
|
20
|
+
enabled: true
|
21
|
+
exclude: []
|
22
|
+
max_calls: 1
|
23
|
+
allow_calls: []
|
24
|
+
FeatureEnvy:
|
25
|
+
enabled: true
|
26
|
+
exclude: []
|
27
|
+
IrresponsibleModule:
|
28
|
+
enabled: true
|
29
|
+
exclude: []
|
30
|
+
LongParameterList:
|
31
|
+
enabled: true
|
32
|
+
exclude: []
|
33
|
+
max_params: 3
|
34
|
+
overrides:
|
35
|
+
initialize:
|
36
|
+
max_params: 5
|
37
|
+
LongYieldList:
|
38
|
+
enabled: true
|
39
|
+
exclude: []
|
40
|
+
max_params: 3
|
41
|
+
NestedIterators:
|
42
|
+
enabled: true
|
43
|
+
exclude: []
|
44
|
+
max_allowed_nesting: 1
|
45
|
+
ignore_iterators: []
|
46
|
+
NilCheck:
|
47
|
+
enabled: true
|
48
|
+
exclude: []
|
49
|
+
PrimaDonnaMethod:
|
50
|
+
enabled: true
|
51
|
+
exclude: []
|
52
|
+
RepeatedConditional:
|
53
|
+
enabled: true
|
54
|
+
exclude: []
|
55
|
+
max_ifs: 2
|
56
|
+
TooManyInstanceVariables:
|
57
|
+
enabled: true
|
58
|
+
exclude: []
|
59
|
+
max_instance_variables: 9
|
60
|
+
TooManyMethods:
|
61
|
+
enabled: true
|
62
|
+
exclude: []
|
63
|
+
max_methods: 25
|
64
|
+
TooManyStatements:
|
65
|
+
enabled: true
|
66
|
+
exclude:
|
67
|
+
- initialize
|
68
|
+
max_statements: 7
|
69
|
+
UncommunicativeMethodName:
|
70
|
+
enabled: true
|
71
|
+
exclude: []
|
72
|
+
reject:
|
73
|
+
- !ruby/regexp /^[a-z]$/
|
74
|
+
- !ruby/regexp /[0-9]$/
|
75
|
+
- !ruby/regexp /[A-Z]/
|
76
|
+
accept: []
|
77
|
+
UncommunicativeModuleName:
|
78
|
+
enabled: true
|
79
|
+
exclude: []
|
80
|
+
reject:
|
81
|
+
- !ruby/regexp /^.$/
|
82
|
+
- !ruby/regexp /[0-9]$/
|
83
|
+
accept:
|
84
|
+
- Inline::C
|
85
|
+
UncommunicativeParameterName:
|
86
|
+
enabled: true
|
87
|
+
exclude: []
|
88
|
+
reject:
|
89
|
+
- !ruby/regexp /^.$/
|
90
|
+
- !ruby/regexp /[0-9]$/
|
91
|
+
- !ruby/regexp /[A-Z]/
|
92
|
+
- !ruby/regexp /^_/
|
93
|
+
accept: []
|
94
|
+
UncommunicativeVariableName:
|
95
|
+
enabled: true
|
96
|
+
exclude: []
|
97
|
+
reject:
|
98
|
+
- !ruby/regexp /^.$/
|
99
|
+
- !ruby/regexp /[0-9]$/
|
100
|
+
- !ruby/regexp /[A-Z]/
|
101
|
+
accept:
|
102
|
+
- _
|
103
|
+
UnusedParameters:
|
104
|
+
enabled: true
|
105
|
+
exclude: []
|
106
|
+
UtilityFunction:
|
107
|
+
enabled: true
|
108
|
+
exclude: []
|
109
|
+
max_helper_calls: 1
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
module FormatEngine
|
3
|
+
|
4
|
+
# This module adds the \attr_formatter extension to a class.
|
5
|
+
module AttrFormatter
|
6
|
+
|
7
|
+
#Define a formatter for instances of the current class.
|
8
|
+
#<br>Parameters
|
9
|
+
#* method - A symbol used to name the formatting method created by this method.
|
10
|
+
#* library - A hash of formatting rules that define the formatting
|
11
|
+
# capabilities supported by this formatter.
|
12
|
+
#<br>Meta-effects
|
13
|
+
#* Creates a method (named after the symbol in method) that formats the
|
14
|
+
# instance of the class. The created method takes one parameter:
|
15
|
+
#<br>Meta-method Parameters
|
16
|
+
#* spec_str - A format specification string with %x etc qualifiers.
|
17
|
+
#<br>Meta-method Returns
|
18
|
+
#* A formatted string
|
19
|
+
def attr_formatter(method, library)
|
20
|
+
engine = Engine.new(library)
|
21
|
+
|
22
|
+
define_method(method) do |spec_str|
|
23
|
+
spec = FormatEngine::FormatSpec.get_spec(spec_str)
|
24
|
+
|
25
|
+
engine.do_format(self, spec)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module FormatEngine
|
2
|
+
|
3
|
+
# This module adds the \attr_parser extension to a class.
|
4
|
+
module AttrParser
|
5
|
+
|
6
|
+
#Define a parser for the current class.
|
7
|
+
#<br>Parameters
|
8
|
+
#* method - A symbol used to name the parsing method created by this method.
|
9
|
+
#* library - A hash of parsing rules the define the parsing
|
10
|
+
# capabilities supported by this parser.
|
11
|
+
#<br>Meta-effects
|
12
|
+
#* Creates a class method (named after the symbol in method) that parses in
|
13
|
+
# a string and creates an instance of the class. The created method takes
|
14
|
+
# two parameters:
|
15
|
+
#<br>Meta-method Parameters
|
16
|
+
#* src - A string of formatted data to be parsed.
|
17
|
+
#* spec_str - A format specification string with %x etc qualifiers.
|
18
|
+
#<br>Meta-method Returns
|
19
|
+
#* An instance of the host class.
|
20
|
+
def attr_parser(method, library)
|
21
|
+
engine = Engine.new(library)
|
22
|
+
|
23
|
+
define_singleton_method(method) do |src, spec_str|
|
24
|
+
spec = FormatEngine::FormatSpec.get_spec(spec_str)
|
25
|
+
|
26
|
+
engine.do_parse(src, self, spec)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module FormatEngine
|
2
|
+
|
3
|
+
# The engine class of the format engine.
|
4
|
+
class Engine
|
5
|
+
|
6
|
+
#Set up base data structures.
|
7
|
+
def initialize(library)
|
8
|
+
@lib = library
|
9
|
+
|
10
|
+
#Set up defaults for pre and post amble blocks.
|
11
|
+
nop = lambda { }
|
12
|
+
@lib[:before] ||= nop
|
13
|
+
@lib[:after] ||= nop
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get an entry from the library
|
17
|
+
def [](index)
|
18
|
+
@lib[index]
|
19
|
+
end
|
20
|
+
|
21
|
+
#Do the actual work of building the formatted output.
|
22
|
+
#<br>Parameters
|
23
|
+
#* src - The source object being formatted.
|
24
|
+
#* format_spec - The format specification.
|
25
|
+
def do_format(src, format_spec)
|
26
|
+
due_process(src, "", format_spec, :do_format)
|
27
|
+
end
|
28
|
+
|
29
|
+
#Do the actual work of parsing the formatted input.
|
30
|
+
#<br>Parameters
|
31
|
+
#* src - The source string being parsed.
|
32
|
+
#* dst - The class of the object being created.
|
33
|
+
#* format_spec - The format specification.
|
34
|
+
def do_parse(*args)
|
35
|
+
due_process(*args, :do_parse)
|
36
|
+
end
|
37
|
+
|
38
|
+
#Do the actual work of parsing the formatted input.
|
39
|
+
#<br>Parameters
|
40
|
+
#* src - The input to the process.
|
41
|
+
#* dst - The output of the process.
|
42
|
+
#* format_spec - The format specification.
|
43
|
+
#* sym - The symbol applied to each format specification.
|
44
|
+
#<br>Endemic Code Smells
|
45
|
+
#* :reek:LongParameterList
|
46
|
+
def due_process(src, dst, format_spec, sym)
|
47
|
+
spec_info = SpecInfo.new(src, dst, nil, self, {})
|
48
|
+
spec_info.instance_exec(&self[:before])
|
49
|
+
|
50
|
+
format_spec.validate(self).specs.each do |fmt|
|
51
|
+
fmt.send(sym, spec_info)
|
52
|
+
end
|
53
|
+
|
54
|
+
spec_info.instance_exec(&self[:after])
|
55
|
+
spec_info.dst
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module FormatEngine
|
2
|
+
|
3
|
+
# A format engine literal specification.
|
4
|
+
class FormatLiteral
|
5
|
+
|
6
|
+
# The literal text of this literal specification.
|
7
|
+
attr_reader :literal
|
8
|
+
|
9
|
+
# Set up a literal format specification.
|
10
|
+
def initialize(literal)
|
11
|
+
@literal = literal
|
12
|
+
end
|
13
|
+
|
14
|
+
# Is this literal supported by the engine? YES!
|
15
|
+
def validate(_engine)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# Format onto the output string
|
20
|
+
def do_format(spec_info)
|
21
|
+
spec_info.dst << @literal
|
22
|
+
end
|
23
|
+
|
24
|
+
# Parse from the input string
|
25
|
+
def do_parse(spec_info)
|
26
|
+
spec_info.parse!(literal)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Inspect for debugging.
|
30
|
+
def inspect
|
31
|
+
"Literal(#{literal.inspect})"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module FormatEngine
|
2
|
+
|
3
|
+
# A format engine variable specification.
|
4
|
+
class FormatVariable
|
5
|
+
|
6
|
+
# The fixed part of this variable specification.
|
7
|
+
attr_reader :format
|
8
|
+
|
9
|
+
# The (optional) numeric format parameters.
|
10
|
+
attr_reader :parms
|
11
|
+
|
12
|
+
# Setup a variable format specification.
|
13
|
+
def initialize(format)
|
14
|
+
if format =~ /(\d+(\.\d+)?)/
|
15
|
+
@format = $PREMATCH + $POSTMATCH
|
16
|
+
|
17
|
+
if (digits = $MATCH) =~ /\./
|
18
|
+
@parms = [$PREMATCH, $POSTMATCH]
|
19
|
+
else
|
20
|
+
@parms = [digits]
|
21
|
+
end
|
22
|
+
|
23
|
+
else
|
24
|
+
@parms = nil
|
25
|
+
@format = format
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Is this variable supported by the engine?
|
30
|
+
def validate(engine)
|
31
|
+
fail "Unsupported tag = #{format.inspect}" unless engine[format]
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get the width parameter.
|
36
|
+
def width
|
37
|
+
parms ? parms[0].to_i : 0
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the precision parameter.
|
41
|
+
def prec
|
42
|
+
(parms && parms.length > 1) ? parms[1].to_i : 0
|
43
|
+
end
|
44
|
+
|
45
|
+
# Format onto the output string
|
46
|
+
def do_format(spec_info)
|
47
|
+
spec_info.fmt = self
|
48
|
+
spec_info.instance_exec(&spec_info.engine[self.format])
|
49
|
+
end
|
50
|
+
|
51
|
+
# Parse from the input string
|
52
|
+
def do_parse(spec_info)
|
53
|
+
spec_info.fmt = self
|
54
|
+
spec_info.instance_exec(&spec_info.engine[self.format])
|
55
|
+
end
|
56
|
+
|
57
|
+
# Inspect for debugging.
|
58
|
+
def inspect
|
59
|
+
"Variable(#{format.inspect}, #{parms.inspect})"
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'format_spec/literal'
|
2
|
+
require_relative 'format_spec/variable'
|
3
|
+
|
4
|
+
# Format String Specification Syntax (BNF):
|
5
|
+
# spec = { text | item }+
|
6
|
+
# item = "%" {flag}* {parm {"." parm}?}? {command}
|
7
|
+
# flag = { "~" | "@" | "#" | "&" | "^" |
|
8
|
+
# "&" | "*" | "-" | "+" | "=" |
|
9
|
+
# "?" | "_" | "<" | ">" | "\\" |
|
10
|
+
# "/" | "." | "," | "|" | "!" }
|
11
|
+
# parm = { "0" .. "9" }+
|
12
|
+
# command = { "a" .. "z" | "A" .. "Z" }
|
13
|
+
# Sample: x = FormatSpec.get_spec "Elapsed = %*03.1H:%M:%S!"
|
14
|
+
|
15
|
+
module FormatEngine
|
16
|
+
|
17
|
+
#The format string parser.
|
18
|
+
class FormatSpec
|
19
|
+
#Don't use new, use get_spec instead.
|
20
|
+
private_class_method :new
|
21
|
+
|
22
|
+
#Either get a format specification from the pool or create one.
|
23
|
+
def self.get_spec(fmt_string)
|
24
|
+
@spec_pool ||= {}
|
25
|
+
@spec_pool[fmt_string] ||= new(fmt_string)
|
26
|
+
end
|
27
|
+
|
28
|
+
#The array of specifications that were extracted.
|
29
|
+
attr_reader :specs
|
30
|
+
|
31
|
+
#Set up an instance of a format specification.
|
32
|
+
#<br>Note
|
33
|
+
#This is a private method (rdoc gets it wrong). To create new instances of
|
34
|
+
#\FormatSpec do not use \FormatSpec.new, but use \FormatSpec.get_spec instead.
|
35
|
+
def initialize(fmt_string)
|
36
|
+
@specs = []
|
37
|
+
scan_spec(fmt_string)
|
38
|
+
end
|
39
|
+
|
40
|
+
#Scan the format string extracting literals and variables.
|
41
|
+
def scan_spec(fmt_string)
|
42
|
+
until fmt_string.empty?
|
43
|
+
if fmt_string =~ /%[~@#$^&*\-+=?_<>\\\/\.,\|!]*(\d+(\.\d+)?)?[a-zA-Z]/
|
44
|
+
@specs << FormatLiteral.new($PREMATCH) unless $PREMATCH.empty?
|
45
|
+
@specs << FormatVariable.new($MATCH)
|
46
|
+
fmt_string = $POSTMATCH
|
47
|
+
else
|
48
|
+
@specs << FormatLiteral.new(fmt_string)
|
49
|
+
fmt_string = ""
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#Validate the specs of this format against the engine.
|
55
|
+
def validate(engine)
|
56
|
+
specs.each {|item| item.validate(engine)}
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|