proforma-extended-evaluator 1.0.0.pre.alpha

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
+ SHA256:
3
+ metadata.gz: 39f0512316231d5d6072f4a26afa88b956d2ae6f1a4b8cf2aa9d45fed2beaba0
4
+ data.tar.gz: f960a2c8956402fd065466c06468d8e8a3b8103a2f5274c72d35c7f8cbf62eec
5
+ SHA512:
6
+ metadata.gz: 7de610f73e925ab605818eb5db4472d2aa5c93f95772c8a4f90040301c6ba80929734a5196d19a6766991c3fdf42e78d0e991d4304fccb3d9f19705f5c820f9b
7
+ data.tar.gz: 98ad38935c497f6c3742d944a5084c6161ea8033a52095ea68d45a491553da3deba260da85b8b306fc8856fe085734c4c60b4b9ae27acf66b01c0fbbbab754d7
data/.editorconfig ADDED
@@ -0,0 +1,8 @@
1
+ # See http://editorconfig.org/
2
+
3
+ [*]
4
+ trim_trailing_whitespace = true
5
+ indent_style = space
6
+ indent_size = 2
7
+ insert_final_newline = true
8
+ end_of_line = lf
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ *.gem
3
+ /tmp
4
+ /coverage
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+
4
+ Metrics/BlockLength:
5
+ ExcludedMethods: ['let', 'it', 'describe', 'context', 'specify']
6
+
7
+ Metrics/MethodLength:
8
+ Max: 25
9
+
10
+ AllCops:
11
+ TargetRubyVersion: 2.3
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.6.0
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ env:
2
+ global:
3
+ - CC_TEST_REPORTER_ID=404bf43b14c0d46bf70b7ccfe096d5ad43d9278d965e4a740c0220ce5a2daa00
4
+ language: ruby
5
+ rvm:
6
+ # Build on the latest stable of all supported Rubies (https://www.ruby-lang.org/en/downloads/):
7
+ - 2.3.8
8
+ - 2.4.5
9
+ - 2.5.3
10
+ - 2.6.0
11
+ cache: bundler
12
+ before_script:
13
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
14
+ - chmod +x ./cc-test-reporter
15
+ - ./cc-test-reporter before-build
16
+ script:
17
+ - bundle exec rubocop
18
+ - bundle exec rspec spec --format documentation
19
+ after_script:
20
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # 1.0.0-alpha (April 19th, 2019)
2
+
3
+ Publishing first candidate of initial release.
4
+
5
+ # 0.0.1 (April 17th, 2019)
6
+
7
+ Library Shell.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,109 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ proforma-extended-evaluator (1.0.0.pre.alpha)
5
+ stringento (~> 2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ acts_as_hashable (1.0.5)
11
+ ansi (1.5.0)
12
+ ast (2.4.0)
13
+ coderay (1.1.2)
14
+ diff-lcs (1.3)
15
+ docile (1.3.1)
16
+ ffi (1.9.25)
17
+ formatador (0.2.5)
18
+ guard (2.15.0)
19
+ formatador (>= 0.2.4)
20
+ listen (>= 2.7, < 4.0)
21
+ lumberjack (>= 1.0.12, < 2.0)
22
+ nenv (~> 0.1)
23
+ notiffany (~> 0.0)
24
+ pry (>= 0.9.12)
25
+ shellany (~> 0.0)
26
+ thor (>= 0.18.1)
27
+ guard-compat (1.2.1)
28
+ guard-rspec (4.7.3)
29
+ guard (~> 2.1)
30
+ guard-compat (~> 1.1)
31
+ rspec (>= 2.99.0, < 4.0)
32
+ hirb (0.7.3)
33
+ jaro_winkler (1.5.2)
34
+ json (2.1.0)
35
+ listen (3.1.5)
36
+ rb-fsevent (~> 0.9, >= 0.9.4)
37
+ rb-inotify (~> 0.9, >= 0.9.7)
38
+ ruby_dep (~> 1.2)
39
+ lumberjack (1.0.13)
40
+ method_source (0.9.2)
41
+ nenv (0.3.0)
42
+ notiffany (0.1.1)
43
+ nenv (~> 0.1)
44
+ shellany (~> 0.0)
45
+ parallel (1.13.0)
46
+ parser (2.6.0.0)
47
+ ast (~> 2.4.0)
48
+ powerpack (0.1.2)
49
+ proforma (1.0.0.pre.alpha)
50
+ acts_as_hashable (~> 1)
51
+ pry (0.12.2)
52
+ coderay (~> 1.1.0)
53
+ method_source (~> 0.9.0)
54
+ rainbow (3.0.0)
55
+ rb-fsevent (0.10.3)
56
+ rb-inotify (0.9.10)
57
+ ffi (>= 0.5.0, < 2)
58
+ rspec (3.8.0)
59
+ rspec-core (~> 3.8.0)
60
+ rspec-expectations (~> 3.8.0)
61
+ rspec-mocks (~> 3.8.0)
62
+ rspec-core (3.8.0)
63
+ rspec-support (~> 3.8.0)
64
+ rspec-expectations (3.8.2)
65
+ diff-lcs (>= 1.2.0, < 2.0)
66
+ rspec-support (~> 3.8.0)
67
+ rspec-mocks (3.8.0)
68
+ diff-lcs (>= 1.2.0, < 2.0)
69
+ rspec-support (~> 3.8.0)
70
+ rspec-support (3.8.0)
71
+ rubocop (0.63.1)
72
+ jaro_winkler (~> 1.5.1)
73
+ parallel (~> 1.10)
74
+ parser (>= 2.5, != 2.5.1.1)
75
+ powerpack (~> 0.1)
76
+ rainbow (>= 2.2.2, < 4.0)
77
+ ruby-progressbar (~> 1.7)
78
+ unicode-display_width (~> 1.4.0)
79
+ ruby-progressbar (1.10.0)
80
+ ruby_dep (1.5.0)
81
+ shellany (0.0.1)
82
+ simplecov (0.16.1)
83
+ docile (~> 1.1)
84
+ json (>= 1.8, < 3)
85
+ simplecov-html (~> 0.10.0)
86
+ simplecov-console (0.4.2)
87
+ ansi
88
+ hirb
89
+ simplecov
90
+ simplecov-html (0.10.2)
91
+ stringento (2.0.0)
92
+ thor (0.20.3)
93
+ unicode-display_width (1.4.1)
94
+
95
+ PLATFORMS
96
+ ruby
97
+
98
+ DEPENDENCIES
99
+ guard-rspec (~> 4.7)
100
+ proforma (>= 1.0.0.pre.alpha)
101
+ proforma-extended-evaluator!
102
+ pry (~> 0)
103
+ rspec (~> 3.8)
104
+ rubocop (~> 0.63.1)
105
+ simplecov (~> 0.16.1)
106
+ simplecov-console (~> 0.4.2)
107
+
108
+ BUNDLED WITH
109
+ 1.17.3
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ guard :rspec, cmd: 'bundle exec rspec' do
4
+ require 'guard/rspec/dsl'
5
+ dsl = Guard::RSpec::Dsl.new(self)
6
+
7
+ # RSpec files
8
+ rspec = dsl.rspec
9
+ watch(rspec.spec_helper) { rspec.spec_dir }
10
+ watch(rspec.spec_support) { rspec.spec_dir }
11
+ watch(rspec.spec_files)
12
+
13
+ # Ruby files
14
+ ruby = dsl.ruby
15
+ dsl.watch_spec_files_for(ruby.lib_files)
16
+ end
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2019 Blue Marble Payroll, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # Proforma Extended Evaluator
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/proforma-extended-evaluator.svg)](https://badge.fury.io/rb/proforma-extended-evaluator) [![Build Status](https://travis-ci.org/bluemarblepayroll/proforma-extended-evaluator.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/proforma-extended-evaluator) [![Maintainability](https://api.codeclimate.com/v1/badges/79e66b596906f633bc95/maintainability)](https://codeclimate.com/github/bluemarblepayroll/proforma-extended-evaluator/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/79e66b596906f633bc95/test_coverage)](https://codeclimate.com/github/bluemarblepayroll/proforma-extended-evaluator/test_coverage) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+
5
+ The core Proforma library intentionally ships with a very weak evaluator. Custom text templating and value resolution is not part of the core library's domain. This library fills that void. The goals of this library are to provide:
6
+
7
+ 1. Nested value resolution using dot-notation: `demographics.contact.first_name`
8
+ 2. Indifferent object types for value resolution: Hash, OpenStruct, any Object subclass, etc.
9
+ 3. Rich text templating: `{demo.last}, {demo.first} {demo.middle}`
10
+ 4. Customizable formatting:
11
+ * `{amount::currency}` -> `$12,345.67 USD`
12
+ * `{dob::date}` -> `2/4/1976`
13
+ * `{user_count::number::0}` -> `12,400,569`
14
+ * `{logins_per_day::number::2}` -> `76,004.45`
15
+ * `{smoker::boolean}` -> `Yes` or `No`
16
+ * `{smoker::boolean::nullable}` -> `Yes` or `No` or `Unknown`
17
+ * `{social_security_number::left_mask}` -> `XXXXXXX1234`
18
+
19
+ ## Installation
20
+
21
+ To install through Rubygems:
22
+
23
+ ````
24
+ gem install install proforma-extended-evaluator
25
+ ````
26
+
27
+ You can also add this to your Gemfile:
28
+
29
+ ````
30
+ bundle add proforma-extended-evaluator
31
+ ````
32
+
33
+ ## Examples
34
+
35
+ ### Connecting to Proforma Rendering Pipeline
36
+
37
+ To use this plugin within Proforma:
38
+
39
+ 1. Install Proforma
40
+ 2. Install this library
41
+ 3. Require both libraries
42
+ 4. Pass in an instance of Proforma::ExtendedEvaluator into the Proforma#render method
43
+
44
+ ````ruby
45
+ require 'proforma'
46
+ require 'proforma/extended_evaluator'
47
+
48
+ data = [
49
+ {
50
+ id: 1,
51
+ person: {
52
+ first: 'James',
53
+ last: 'Bond',
54
+ dob: '1960-05-14',
55
+ smoker: false,
56
+ ssn: '123-45-6789'
57
+ },
58
+ balance: '123.445388'
59
+ }
60
+ ]
61
+
62
+ template = {
63
+ children: [
64
+ {
65
+ type: 'Grouping',
66
+ children: [
67
+ {
68
+ type: 'Header',
69
+ value: 'Details For: {person.last}, {person.first} ({id})'
70
+ },
71
+ {
72
+ type: 'Pane',
73
+ columns: [
74
+ {
75
+ lines: [
76
+ { label: 'ID #', value: '{id::number::0}' },
77
+ { label: 'First Name', value: '{person.first}' },
78
+ { label: 'Last Name', value: '{person.last}' },
79
+ { label: 'Social Security #', value: '{person.ssn::left_mask}' }
80
+ ]
81
+ },
82
+ {
83
+ lines: [
84
+ { label: 'Birthdate', value: '{person.dob::date}' },
85
+ { label: 'Smoker', value: '{person.smoker::boolean}' },
86
+ { label: 'Balance', value: '{balance::currency}' }
87
+ ]
88
+ }
89
+ ]
90
+ }
91
+ ]
92
+ }
93
+ ]
94
+ }
95
+
96
+ documents = Proforma.render(data, template, evaluator: Proforma::ExtendedEvaluator.new)
97
+ ````
98
+
99
+ The `documents` attribute will now be an array with one object:
100
+
101
+ ```ruby
102
+ expected_documents = [
103
+ {
104
+ contents: "DETAILS FOR: BOND, JAMES (1)\nID #: 1\nFirst Name: James\nLast Name:"\
105
+ " Bond\nSocial Security #: XXXXXXX6789\nBirthdate: 05/14/1960\nSmoker:"\
106
+ " No\nBalance: $123.45 USD\n",
107
+ extension: '.txt',
108
+ title: ''
109
+ }
110
+ ]
111
+ ```
112
+
113
+ Notice how all strings are properly formatted as prescribed in the template.
114
+
115
+ ### Advanced Formatting (Customization/Options)
116
+
117
+ Formatter options are biased towards USA localization. You can override any of the options of the Formatter class, here are the options and their defaults:
118
+
119
+ Option | Default
120
+ ---------------- | -------
121
+ currency_code | 'USD'
122
+ currency_round | 2
123
+ currency_symbol | '$'
124
+ date_format | '%m/%d/%Y'
125
+ mask_char | 'X'
126
+ false_value | 'No'
127
+ null_value | 'Unknown'
128
+ true_value | 'Yes'
129
+
130
+ ## Contributing
131
+
132
+ ### Development Environment Configuration
133
+
134
+ Basic steps to take to get this repository compiling:
135
+
136
+ 1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check proforma-extended-evaluator.gemspec for versions supported)
137
+ 2. Install bundler (gem install bundler)
138
+ 3. Clone the repository (git clone git@github.com:bluemarblepayroll/proforma-extended-evaluator.git)
139
+ 4. Navigate to the root folder (cd proforma)
140
+ 5. Install dependencies (bundle)
141
+
142
+ ### Running Tests
143
+
144
+ To execute the test suite run:
145
+
146
+ ````
147
+ bundle exec rspec spec --format documentation
148
+ ````
149
+
150
+ Alternatively, you can have Guard watch for changes:
151
+
152
+ ````
153
+ bundle exec guard
154
+ ````
155
+
156
+ Also, do not forget to run Rubocop:
157
+
158
+ ````
159
+ bundle exec rubocop
160
+ ````
161
+
162
+ ### Publishing
163
+
164
+ Note: ensure you have proper authorization before trying to publish new versions.
165
+
166
+ After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
167
+
168
+ 1. Merge Pull Request into master
169
+ 2. Update `lib/proforma/extended_evaluator/version.rb` using [semantic versioning](https://semver.org/)
170
+ 3. Install dependencies: `bundle`
171
+ 4. Update `CHANGELOG.md` with release notes
172
+ 5. Commit & push master to remote and ensure CI builds master successfully
173
+ 6. Build the project locally: `gem build proforma-extended-evaluator`
174
+ 7. Publish package to RubyGems: `gem push proforma-extended-evaluator-X.gem` where X is the version to push
175
+ 8. Tag master with new version: `git tag <version>`
176
+ 9. Push tags remotely: `git push origin --tags`
177
+
178
+ ## License
179
+
180
+ This project is MIT Licensed.
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'proforma/extended_evaluator'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require 'pry'
11
+ Pry.start
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module Proforma
11
+ class ExtendedEvaluator
12
+ # This library uses Stringento for its string-based formatting. This class is meant to be
13
+ # plugged into Stringento to provide formatting for data types, such as: strings, dates,
14
+ # currency, numbers, etc.
15
+ class Formatter < Stringento::Formatter
16
+ DEFAULTS = {
17
+ currency_code: 'USD',
18
+ currency_round: 2,
19
+ currency_symbol: '$',
20
+ date_format: '%m/%d/%Y',
21
+ mask_char: 'X',
22
+ false_value: 'No',
23
+ null_value: 'Unknown',
24
+ true_value: 'Yes'
25
+ }.freeze
26
+
27
+ ISO_DATE_FORMAT = '%Y-%m-%d'
28
+ NULLISH = /(nil|null)$/i.freeze
29
+ THOUSANDS_WITH_DECIMAL = /(\d)(?=\d{3}+\.)/.freeze
30
+ THOUSANDS_WITHOUT_DECIMAL = /(\d)(?=\d{3}+$)/.freeze
31
+ TRUTHY = /(true|t|yes|y|1)$/i.freeze
32
+
33
+ attr_reader :currency_code,
34
+ :currency_round,
35
+ :currency_symbol,
36
+ :date_format,
37
+ :false_value,
38
+ :mask_char,
39
+ :null_value,
40
+ :true_value
41
+
42
+ def initialize(opts = {})
43
+ opts = DEFAULTS.merge(opts)
44
+
45
+ @currency_code = opts[:currency_code]
46
+ @currency_round = opts[:currency_round]
47
+ @currency_symbol = opts[:currency_symbol]
48
+ @date_format = opts[:date_format]
49
+ @false_value = opts[:false_value]
50
+ @mask_char = opts[:mask_char]
51
+ @null_value = opts[:null_value]
52
+ @true_value = opts[:true_value]
53
+ end
54
+
55
+ def left_mask_formatter(value, arg)
56
+ keep_last = arg.to_s.empty? ? 4 : arg.to_s.to_i
57
+
58
+ string_value = value.to_s
59
+
60
+ return '' if null_or_empty?(string_value)
61
+ return value if string_value.length <= keep_last
62
+
63
+ mask(string_value, keep_last)
64
+ end
65
+
66
+ def date_formatter(value, _arg)
67
+ return '' if null_or_empty?(value)
68
+
69
+ date = Date.strptime(value.to_s, ISO_DATE_FORMAT)
70
+
71
+ date.strftime(date_format)
72
+ end
73
+
74
+ def currency_formatter(value, _arg)
75
+ return '' if null_or_empty?(value)
76
+
77
+ prefix = null_or_empty?(currency_symbol) ? '' : currency_symbol
78
+ suffix = null_or_empty?(currency_code) ? '' : " #{currency_code}"
79
+
80
+ formatted_value = number_formatter(value, currency_round)
81
+
82
+ "#{prefix}#{formatted_value}#{suffix}"
83
+ end
84
+
85
+ def number_formatter(value, arg)
86
+ decimal_places = arg.to_s.empty? ? 6 : arg.to_s.to_i
87
+
88
+ regex = decimal_places.positive? ? THOUSANDS_WITH_DECIMAL : THOUSANDS_WITHOUT_DECIMAL
89
+
90
+ format("%0.#{decimal_places}f", value || 0).gsub(regex, '\1,')
91
+ end
92
+
93
+ def boolean_formatter(value, arg)
94
+ nullable = arg.to_s == 'nullable'
95
+
96
+ if nullable && nully?(value)
97
+ null_value
98
+ elsif truthy?(value)
99
+ true_value
100
+ else
101
+ false_value
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def mask(string, keep_last)
108
+ unmasked_part = string[-keep_last..-1]
109
+ masked_char_count = string.size - keep_last
110
+
111
+ (mask_char * masked_char_count) + unmasked_part
112
+ end
113
+
114
+ def null_or_empty?(val)
115
+ val.nil? || val.to_s.empty?
116
+ end
117
+
118
+ # rubocop:disable Style/DoubleNegation
119
+ def nully?(val)
120
+ null_or_empty?(val) || !!(val.to_s =~ NULLISH)
121
+ end
122
+
123
+ def truthy?(val)
124
+ !!(val.to_s =~ TRUTHY)
125
+ end
126
+ # rubocop:enable Style/DoubleNegation
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module Proforma
11
+ class ExtendedEvaluator
12
+ # This class is also meant to be plugged into Stringento to provide value resolution.
13
+ class Resolver
14
+ DOT_NOTATION_SEPARATOR = '.'
15
+
16
+ def resolve(value, input)
17
+ traverse(input, value.to_s.split(DOT_NOTATION_SEPARATOR))
18
+ end
19
+
20
+ private
21
+
22
+ def traverse(object, through)
23
+ pointer = object
24
+
25
+ through.each do |t|
26
+ next unless pointer
27
+
28
+ pointer = get(pointer, t)
29
+ end
30
+
31
+ pointer
32
+ end
33
+
34
+ def get(object, key)
35
+ if object.is_a?(Hash)
36
+ indifferent_hash_get(object, key)
37
+ elsif object.respond_to?(key)
38
+ object.send(key)
39
+ end
40
+ end
41
+
42
+ def indifferent_hash_get(hash, key)
43
+ if hash.key?(key.to_s)
44
+ hash[key.to_s]
45
+ elsif hash.key?(key.to_s.to_sym)
46
+ hash[key.to_s.to_sym]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module Proforma
11
+ class ExtendedEvaluator
12
+ VERSION = '1.0.0-alpha'
13
+ end
14
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'bigdecimal'
11
+ require 'stringento'
12
+
13
+ require_relative 'extended_evaluator/formatter'
14
+ require_relative 'extended_evaluator/resolver'
15
+
16
+ module Proforma
17
+ # This class provides robust functionality for value resolution and text templating.
18
+ # For value resolution it uses its own dot-notation and
19
+ # message-based object traversal algorithm.
20
+ # For text templating it uses the Stringento library.
21
+ class ExtendedEvaluator
22
+ attr_reader :formatter, :resolver
23
+
24
+ def initialize(formatter: Formatter.new, resolver: Resolver.new)
25
+ raise ArgumentError, 'formatter is required' unless formatter
26
+ raise ArgumentError, 'resolver is required' unless resolver
27
+
28
+ @formatter = formatter
29
+ @resolver = resolver
30
+
31
+ freeze
32
+ end
33
+
34
+ def value(object, expression)
35
+ resolver.resolve(expression, object)
36
+ end
37
+
38
+ def text(object, expression)
39
+ record = object.is_a?(Array) || object.nil? ? {} : object
40
+
41
+ Stringento.evaluate(
42
+ expression.to_s,
43
+ record,
44
+ resolver: resolver,
45
+ formatter: formatter
46
+ )
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './lib/proforma/extended_evaluator/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'proforma-extended-evaluator'
7
+ s.version = Proforma::ExtendedEvaluator::VERSION
8
+ s.summary = 'Proforma evaluator plugin for nested object value resolution and text templating'
9
+
10
+ s.description = <<-DESCRIPTION
11
+ Proforma comes with basic object value resolution and no text templating.
12
+ This library fills these necessities that any reasonably robust document rendering framework should have.
13
+ DESCRIPTION
14
+
15
+ s.authors = ['Matthew Ruggio']
16
+ s.email = ['mruggio@bluemarblepayroll.com']
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
20
+ s.homepage = 'https://github.com/bluemarblepayroll/proforma-extended-evaluator'
21
+ s.license = 'MIT'
22
+
23
+ s.required_ruby_version = '>= 2.3.8'
24
+
25
+ s.add_dependency('stringento', '~>2')
26
+
27
+ s.add_development_dependency('guard-rspec', '~>4.7')
28
+ s.add_development_dependency('proforma', '>=1.0.0-alpha')
29
+ s.add_development_dependency('pry', '~>0')
30
+ s.add_development_dependency('rspec', '~> 3.8')
31
+ s.add_development_dependency('rubocop', '~>0.63.1')
32
+ s.add_development_dependency('simplecov', '~>0.16.1')
33
+ s.add_development_dependency('simplecov-console', '~>0.4.2')
34
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+
12
+ describe Proforma::ExtendedEvaluator::Formatter do
13
+ describe '#left_mask_formatter' do
14
+ specify 'returns empty string if value is null' do
15
+ expect(subject.left_mask_formatter(nil, '')).to eq('')
16
+ end
17
+
18
+ specify 'returns empty string if value is empty string' do
19
+ expect(subject.left_mask_formatter('', '')).to eq('')
20
+ end
21
+
22
+ context 'when arg is blank' do
23
+ let(:arg) { '' }
24
+
25
+ specify 'returns value if length is less than or equal to mask length' do
26
+ expect(subject.left_mask_formatter('a', arg)).to eq('a')
27
+ expect(subject.left_mask_formatter('ab', arg)).to eq('ab')
28
+ expect(subject.left_mask_formatter('abc', arg)).to eq('abc')
29
+ expect(subject.left_mask_formatter('abcd', arg)).to eq('abcd')
30
+ expect(subject.left_mask_formatter('abcde', arg)).to eq('Xbcde')
31
+ expect(subject.left_mask_formatter('abcdef', arg)).to eq('XXcdef')
32
+ end
33
+ end
34
+
35
+ context 'when arg is populated' do
36
+ let(:arg) { '2' }
37
+
38
+ specify 'returns value if length is less than or equal to mask length (arg)' do
39
+ expect(subject.left_mask_formatter('a', arg)).to eq('a')
40
+ expect(subject.left_mask_formatter('ab', arg)).to eq('ab')
41
+ expect(subject.left_mask_formatter('abc', arg)).to eq('Xbc')
42
+ expect(subject.left_mask_formatter('abcd', arg)).to eq('XXcd')
43
+ expect(subject.left_mask_formatter('abcde', arg)).to eq('XXXde')
44
+ expect(subject.left_mask_formatter('abcdef', arg)).to eq('XXXXef')
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '#date_formatter' do
50
+ subject do
51
+ described_class.new(
52
+ date_format: '%m/%d/%Y'
53
+ )
54
+ end
55
+
56
+ let(:arg) { '' }
57
+
58
+ specify 'returns empty string if value is null' do
59
+ expect(subject.date_formatter(nil, '')).to eq('')
60
+ end
61
+
62
+ specify 'returns empty string if value is empty string' do
63
+ expect(subject.date_formatter('', '')).to eq('')
64
+ end
65
+
66
+ specify 'returns formatted date' do
67
+ expect(subject.date_formatter('2018-01-02', arg)).to eq('01/02/2018')
68
+ end
69
+ end
70
+
71
+ describe '#currency_formatter' do
72
+ subject do
73
+ described_class.new(
74
+ currency_code: 'USD',
75
+ currency_round: 2,
76
+ currency_symbol: '$'
77
+ )
78
+ end
79
+
80
+ let(:arg) { '' }
81
+
82
+ specify 'returns empty string if value is null' do
83
+ expect(subject.currency_formatter(nil, '')).to eq('')
84
+ end
85
+
86
+ specify 'returns empty string if value is empty string' do
87
+ expect(subject.currency_formatter('', '')).to eq('')
88
+ end
89
+
90
+ specify 'returns formatted currency' do
91
+ expect(subject.currency_formatter('12345.67', arg)).to eq('$12,345.67 USD')
92
+ end
93
+ end
94
+
95
+ describe '#number_formatter' do
96
+ subject do
97
+ described_class.new(
98
+ currency_code: 'USD',
99
+ currency_round: 2,
100
+ currency_symbol: '$'
101
+ )
102
+ end
103
+
104
+ let(:arg) { '3' }
105
+
106
+ specify 'returns empty string if value is null' do
107
+ expect(subject.currency_formatter(nil, '')).to eq('')
108
+ end
109
+
110
+ specify 'returns empty string if value is empty string' do
111
+ expect(subject.currency_formatter('', '')).to eq('')
112
+ end
113
+
114
+ specify 'returns formatted number' do
115
+ expect(subject.number_formatter('12345.67899', arg)).to eq('12,345.679')
116
+ end
117
+ end
118
+
119
+ describe '#boolean_formatter' do
120
+ context 'non-nullable' do
121
+ let(:arg) { '' }
122
+
123
+ it 'should format truthy' do
124
+ expect(subject.boolean_formatter(true, arg)).to eq('Yes')
125
+ expect(subject.boolean_formatter('true', arg)).to eq('Yes')
126
+ expect(subject.boolean_formatter('True', arg)).to eq('Yes')
127
+ expect(subject.boolean_formatter('t', arg)).to eq('Yes')
128
+ expect(subject.boolean_formatter('1', arg)).to eq('Yes')
129
+ expect(subject.boolean_formatter(1, arg)).to eq('Yes')
130
+ end
131
+
132
+ it 'should format falsy' do
133
+ expect(subject.boolean_formatter(false, arg)).to eq('No')
134
+ expect(subject.boolean_formatter('false', arg)).to eq('No')
135
+ expect(subject.boolean_formatter('False', arg)).to eq('No')
136
+ expect(subject.boolean_formatter(0, arg)).to eq('No')
137
+ expect(subject.boolean_formatter('f', arg)).to eq('No')
138
+ end
139
+
140
+ it 'should format nully' do
141
+ expect(subject.boolean_formatter(nil, arg)).to eq('No')
142
+ expect(subject.boolean_formatter('nil', arg)).to eq('No')
143
+ expect(subject.boolean_formatter('null', arg)).to eq('No')
144
+ end
145
+ end
146
+
147
+ context 'nullable' do
148
+ let(:arg) { 'nullable' }
149
+
150
+ it 'should format nully' do
151
+ expect(subject.boolean_formatter(nil, arg)).to eq('Unknown')
152
+ expect(subject.boolean_formatter('nil', arg)).to eq('Unknown')
153
+ expect(subject.boolean_formatter('null', arg)).to eq('Unknown')
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+
12
+ describe Proforma::ExtendedEvaluator::Resolver do
13
+ subject { described_class.new }
14
+
15
+ context 'when input is a hash' do
16
+ let(:input) { { 'id' => 1, demographics: { 'first' => 'Matt' } } }
17
+
18
+ specify '#resolve gets correct value' do
19
+ expect(subject.resolve(:id, input)).to eq(input['id'])
20
+ expect(subject.resolve('id', input)).to eq(input['id'])
21
+
22
+ expect(subject.resolve('demographics.first', input)).to eq(input.dig(:demographics, 'first'))
23
+ end
24
+ end
25
+
26
+ context 'when input is an OpenStruct' do
27
+ let(:input) do
28
+ OpenStruct.new(
29
+ id: 1,
30
+ demographics: OpenStruct.new(
31
+ first: 'Matt'
32
+ )
33
+ )
34
+ end
35
+
36
+ specify '#resolve gets correct value' do
37
+ expect(subject.resolve(:id, input)).to eq(input.id)
38
+ expect(subject.resolve('id', input)).to eq(input.id)
39
+
40
+ expect(subject.resolve('demographics.first', input)).to eq(input.demographics.first)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+
12
+ describe Proforma::ExtendedEvaluator do
13
+ describe '#initialize' do
14
+ it 'should raise ArgumentError if formatter is null' do
15
+ expect { described_class.new(formatter: nil) }.to raise_error(ArgumentError)
16
+ end
17
+
18
+ it 'should raise ArgumentError if resolver is null' do
19
+ expect { described_class.new(resolver: nil) }.to raise_error(ArgumentError)
20
+ end
21
+
22
+ it 'should assign formatter and resolver' do
23
+ formatter = Proforma::ExtendedEvaluator::Formatter.new
24
+ resolver = Proforma::ExtendedEvaluator::Resolver.new
25
+
26
+ instance = described_class.new(formatter: formatter, resolver: resolver)
27
+
28
+ expect(instance.formatter).to equal(formatter)
29
+ expect(instance.resolver).to equal(resolver)
30
+ end
31
+ end
32
+
33
+ specify '#value is delegated to resolver#resolve' do
34
+ resolver = Proforma::ExtendedEvaluator::Resolver.new
35
+ object = { id: 1 }
36
+ expression = 'id'
37
+
38
+ instance = described_class.new(resolver: resolver)
39
+
40
+ expect(resolver).to receive(:resolve).with(expression, object)
41
+
42
+ instance.value(object, expression)
43
+ end
44
+
45
+ specify '#text will use blank object if an array is passed in' do
46
+ object = [{ id: 1 }]
47
+ expression = '{id}'
48
+
49
+ instance = described_class.new
50
+
51
+ actual_text = instance.text(object, expression)
52
+
53
+ expect(actual_text).to eq('')
54
+ end
55
+
56
+ specify '#text will use object if an object is passed in' do
57
+ object = { id: 1 }
58
+ expression = '{id}'
59
+
60
+ instance = described_class.new
61
+
62
+ actual_text = instance.text(object, expression)
63
+
64
+ expect(actual_text).to eq('1')
65
+ end
66
+
67
+ specify 'Proforma Rendering Example' do
68
+ data = [
69
+ {
70
+ id: 1,
71
+ person: {
72
+ first: 'James',
73
+ last: 'Bond',
74
+ dob: '1960-05-14',
75
+ smoker: false,
76
+ ssn: '123-45-6789'
77
+ },
78
+ balance: '123.445388'
79
+ }
80
+ ]
81
+
82
+ template = {
83
+ children: [
84
+ {
85
+ type: 'Grouping',
86
+ children: [
87
+ {
88
+ type: 'Header',
89
+ value: 'Details For: {person.last}, {person.first} ({id})'
90
+ },
91
+ {
92
+ type: 'Pane',
93
+ columns: [
94
+ {
95
+ lines: [
96
+ { label: 'ID #', value: '{id::number::0}' },
97
+ { label: 'First Name', value: '{person.first}' },
98
+ { label: 'Last Name', value: '{person.last}' },
99
+ { label: 'Social Security #', value: '{person.ssn::left_mask}' }
100
+ ]
101
+ },
102
+ {
103
+ lines: [
104
+ { label: 'Birthdate', value: '{person.dob::date}' },
105
+ { label: 'Smoker', value: '{person.smoker::boolean}' },
106
+ { label: 'Balance', value: '{balance::currency}' }
107
+ ]
108
+ }
109
+ ]
110
+ }
111
+ ]
112
+ }
113
+ ]
114
+ }
115
+
116
+ actual_documents = Proforma.render(data, template, evaluator: Proforma::ExtendedEvaluator.new)
117
+
118
+ expected_documents = [
119
+ Proforma::Document.new(
120
+ contents: "DETAILS FOR: BOND, JAMES (1)\nID #: 1\nFirst Name: James\nLast Name:"\
121
+ " Bond\nSocial Security #: XXXXXXX6789\nBirthdate: 05/14/1960\nSmoker:"\
122
+ " No\nBalance: $123.45 USD\n",
123
+ extension: '.txt',
124
+ title: ''
125
+ )
126
+ ]
127
+
128
+ expect(actual_documents).to eq(expected_documents)
129
+ end
130
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'yaml'
11
+ require 'pry'
12
+ require 'proforma'
13
+
14
+ require 'simplecov'
15
+ require 'simplecov-console'
16
+ SimpleCov.formatter = SimpleCov::Formatter::Console
17
+ SimpleCov.start
18
+
19
+ require './lib/proforma/extended_evaluator'
metadata ADDED
@@ -0,0 +1,183 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: proforma-extended-evaluator
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre.alpha
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Ruggio
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-04-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: stringento
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: guard-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: proforma
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0.pre.alpha
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0.pre.alpha
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.8'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.63.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.63.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.16.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.16.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov-console
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.4.2
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.4.2
125
+ description: |2
126
+ Proforma comes with basic object value resolution and no text templating.
127
+ This library fills these necessities that any reasonably robust document rendering framework should have.
128
+ email:
129
+ - mruggio@bluemarblepayroll.com
130
+ executables:
131
+ - console
132
+ extensions: []
133
+ extra_rdoc_files: []
134
+ files:
135
+ - ".editorconfig"
136
+ - ".gitignore"
137
+ - ".rubocop.yml"
138
+ - ".ruby-version"
139
+ - ".travis.yml"
140
+ - CHANGELOG.md
141
+ - Gemfile
142
+ - Gemfile.lock
143
+ - Guardfile
144
+ - LICENSE
145
+ - README.md
146
+ - bin/console
147
+ - lib/proforma/extended_evaluator.rb
148
+ - lib/proforma/extended_evaluator/formatter.rb
149
+ - lib/proforma/extended_evaluator/resolver.rb
150
+ - lib/proforma/extended_evaluator/version.rb
151
+ - proforma-extended-evaluator.gemspec
152
+ - spec/proforma/extended_evaluator/formatter_spec.rb
153
+ - spec/proforma/extended_evaluator/resolver_spec.rb
154
+ - spec/proforma/extended_evaluator_spec.rb
155
+ - spec/spec_helper.rb
156
+ homepage: https://github.com/bluemarblepayroll/proforma-extended-evaluator
157
+ licenses:
158
+ - MIT
159
+ metadata: {}
160
+ post_install_message:
161
+ rdoc_options: []
162
+ require_paths:
163
+ - lib
164
+ required_ruby_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: 2.3.8
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">"
172
+ - !ruby/object:Gem::Version
173
+ version: 1.3.1
174
+ requirements: []
175
+ rubygems_version: 3.0.1
176
+ signing_key:
177
+ specification_version: 4
178
+ summary: Proforma evaluator plugin for nested object value resolution and text templating
179
+ test_files:
180
+ - spec/proforma/extended_evaluator/formatter_spec.rb
181
+ - spec/proforma/extended_evaluator/resolver_spec.rb
182
+ - spec/proforma/extended_evaluator_spec.rb
183
+ - spec/spec_helper.rb