format_engine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in format_engine.gemspec
4
+ gemspec
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
@@ -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