sheng 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/README.md +2 -0
- data/docs/creating_templates.docx +0 -0
- data/docs/creating_templates.md +26 -0
- data/lib/sheng/filters/base.rb +23 -0
- data/lib/sheng/filters/currency_formatting_filter.rb +16 -0
- data/lib/sheng/filters/numeric_filter.rb +14 -0
- data/lib/sheng/filters/string_filter.rb +9 -0
- data/lib/sheng/filters.rb +23 -0
- data/lib/sheng/merge_field.rb +19 -21
- data/lib/sheng/support.rb +16 -0
- data/lib/sheng/version.rb +1 -1
- data/lib/sheng.rb +1 -0
- data/spec/fixtures/xml_fragments/input/merge_field/currency_merge_field.xml +14 -0
- data/spec/fixtures/xml_fragments/input/merge_field/math_merge_field.xml +2 -2
- data/spec/fixtures/xml_fragments/output/merge_field/currency_merge_field.xml +12 -0
- data/spec/fixtures/xml_fragments/output/merge_field/math_merge_field.xml +1 -1
- data/spec/lib/sheng/filters/currency_formatting_filter_spec.rb +20 -0
- data/spec/lib/sheng/filters/numeric_filter_spec.rb +19 -0
- data/spec/lib/sheng/filters/string_filter_spec.rb +22 -0
- data/spec/lib/sheng/filters_spec.rb +22 -0
- data/spec/lib/sheng/merge_field_spec.rb +47 -39
- data/spec/lib/sheng/support_spec.rb +53 -0
- data/spec/shared_examples/filters.rb +18 -0
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8b81b58b522c27f9eae0f86940da9f8b92743d5
|
4
|
+
data.tar.gz: 5ba6ec64dd3891eb409550801fde1dbe95cdb4f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c40035114fcc183da71cb81b1e38e315163399fe7925ef1080f35e414d0f8dd800157e0b027ceb3aafdb5f7eb54b90c1d71d9a9e0374e133d969f008a41173e
|
7
|
+
data.tar.gz: 50dc168d3817c280d37e916d77893b8d66ebc81b4c201ce6e1fa90eb4676b9357bb7a2699c7cf20d75672d45fb0aa53c574d12b9e692a3a7161cd7a78a7f18f7
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Sheng
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/renewablefunding/sheng.svg?branch=master)](https://travis-ci.org/renewablefunding/sheng)
|
4
|
+
|
3
5
|
A Ruby gem for data merging Word documents. Given a set of data (as a Hash), and a `.docx` template created in [a specific way](docs/creating_templates.md), you can generate a new Word document with the values from the data substituted in place.
|
4
6
|
|
5
7
|
## Installation
|
Binary file
|
data/docs/creating_templates.md
CHANGED
@@ -18,6 +18,8 @@ Note that any instructions on how specifically to use certain commands in Word a
|
|
18
18
|
* [Basic MergeField Substitution](#basic-mergefield-substitution)
|
19
19
|
* [Inline Basic MergeFields](#inline-basic-mergefields)
|
20
20
|
* [Filters on String Values](#filters-on-string-values)
|
21
|
+
* [Filters on Numeric Values](#filters-on-numeric-values)
|
22
|
+
* [Basic Arithmetic Operations](#basic-arithmetic-operations)
|
21
23
|
* [Checkboxes](#checkboxes)
|
22
24
|
* [Sequences](#sequences)
|
23
25
|
* [Arrays of Primitives](#arrays-of-primitives)
|
@@ -223,10 +225,32 @@ Filters can also be chained, and will be applied in which they appear (from left
|
|
223
225
|
|
224
226
|
«a_basic_integer|downcase»
|
225
227
|
|
228
|
+
## Filters on Numeric Values
|
229
|
+
|
230
|
+
There are also built-in filters available for modifying the output of numeric values (e.g. to round a number or display it as currency). These filters, if used on non-numeric values, will have no effect. Strings that can be read as numeric values (like "145.2") will work with these filters. Unlike the string filters, these numeric filters can accept arguments, to clarify what the filter should do. The filters are:
|
231
|
+
|
232
|
+
| Name | Description | Example Input | Example Command | Example Output |
|
233
|
+
| --- | --- | --- | --- | --- |
|
234
|
+
| round(n) | Rounds number to n decimal places | 2360.7853 | round(2) | 2360.79 |
|
235
|
+
| floor | Truncates decimal portion of number | 2360.7853 | floor | 2360 |
|
236
|
+
| currency(x) | Formats number as currency (2 fixed decimals, thousands separator), prepending optional argument | 149020.5 | currency($) | $149,020.50 |
|
237
|
+
|
238
|
+
Filters can also be chained, and will be applied in which they appear (from left to right).
|
239
|
+
|
240
|
+
### Examples:
|
241
|
+
|
242
|
+
«a_basic_float|round(1)»
|
243
|
+
|
244
|
+
«a_basic_float|currency($)»
|
245
|
+
|
246
|
+
«a_basic_float|floor»
|
247
|
+
|
226
248
|
## Basic Arithmetic Operations
|
227
249
|
|
228
250
|
You can perform basic arithmetic operations within a mergefield, using either hardcoded numeric values or variable names. The operators allowed are + (addition), - (subtraction), * (multiplication), and / (division). The usual order of precedence applies with these operators, and you can use parenthesis to control that precedence.
|
229
251
|
|
252
|
+
Note, however, that adding formatted numbers (like "1,400.50" and "24,000.20") will result in an unformatted number ("25400.7"), so if you want currency formatting or rounding, you'll want to use the numeric formatting methods shown above. You can combine arithmetic with filters to accomplish this, as seen in the last example below.
|
253
|
+
|
230
254
|
### Examples:
|
231
255
|
|
232
256
|
«25 * 6»
|
@@ -237,6 +261,8 @@ You can perform basic arithmetic operations within a mergefield, using either ha
|
|
237
261
|
|
238
262
|
«(hair.length * 2) + head.height»
|
239
263
|
|
264
|
+
«principal + interest | currency($)»
|
265
|
+
|
240
266
|
# Checkboxes
|
241
267
|
|
242
268
|
To create a checkbox:
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Sheng
|
2
|
+
module Filters
|
3
|
+
class Base
|
4
|
+
attr_reader :value, :method, :arguments
|
5
|
+
|
6
|
+
def initialize(method: method, arguments: [])
|
7
|
+
@method = method
|
8
|
+
@arguments = arguments
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.implements(*names)
|
12
|
+
names.each do |name|
|
13
|
+
Sheng::Filters.registry.merge!({ name.to_sym => self })
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def filter(value)
|
18
|
+
return value unless value.respond_to?(method)
|
19
|
+
value.send(method, *arguments)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
|
3
|
+
module Sheng
|
4
|
+
module Filters
|
5
|
+
class CurrencyFormattingFilter < Base
|
6
|
+
implements :currency
|
7
|
+
|
8
|
+
def filter(value)
|
9
|
+
return value unless Sheng::Support.is_numeric?(value)
|
10
|
+
integer, fractional = ("%00.2f" % value).split(".")
|
11
|
+
integer.reverse!.gsub!(/(\d{3})(?=\d)/, '\\1,').reverse!
|
12
|
+
"#{arguments.first}#{integer}.#{fractional}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Sheng
|
2
|
+
module Filters
|
3
|
+
class UnsupportedFilterError < StandardError; end
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def registry
|
7
|
+
@registry ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def filter_for(filter_string)
|
11
|
+
filter_method, args_list = filter_string.split(/[\(\)]/)
|
12
|
+
args = (args_list || "").split(/\s*,\s*/).map { |arg| Sheng::Support.typecast_numeric(arg) }
|
13
|
+
filter_class = registry[filter_method.to_sym]
|
14
|
+
raise UnsupportedFilterError.new(filter_string) unless filter_class
|
15
|
+
filter_class.new(method: filter_method, arguments: args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require_relative "filters/string_filter"
|
22
|
+
require_relative "filters/numeric_filter"
|
23
|
+
require_relative "filters/currency_formatting_filter"
|
data/lib/sheng/merge_field.rb
CHANGED
@@ -5,10 +5,8 @@ module Sheng
|
|
5
5
|
MATH_TOKENS = %w[+ - / * ( )]
|
6
6
|
REGEXES = {
|
7
7
|
instruction_text: /^\s*MERGEFIELD(.*)\\\* MERGEFORMAT\s*$/,
|
8
|
-
key_string: /^(?<prefix>start:|end:|if:|end_if:|unless:|end_unless:)?\s*(?<key>[^\|]+)\s*\|?(?<filters>.*)
|
9
|
-
numeric_string: /^[-+]?[0-9]*\.?[0-9]+$/
|
8
|
+
key_string: /^(?<prefix>start:|end:|if:|end_if:|unless:|end_unless:)?\s*(?<key>[^\|]+)\s*\|?(?<filters>.*)?/
|
10
9
|
}
|
11
|
-
ALLOWED_FILTERS = [:upcase, :downcase, :capitalize, :titleize, :reverse]
|
12
10
|
|
13
11
|
class NotAMergeFieldError < StandardError; end
|
14
12
|
|
@@ -25,7 +23,7 @@ module Sheng
|
|
25
23
|
def initialize(element)
|
26
24
|
@element = element
|
27
25
|
@xml_document = element.document
|
28
|
-
@instruction_text =
|
26
|
+
@instruction_text = Sheng::Support.extract_mergefield_instruction_text(element)
|
29
27
|
end
|
30
28
|
|
31
29
|
def ==(other)
|
@@ -46,11 +44,7 @@ module Sheng
|
|
46
44
|
end
|
47
45
|
|
48
46
|
def raw_key
|
49
|
-
@raw_key ||=
|
50
|
-
end
|
51
|
-
|
52
|
-
def mergefield_instruction_text
|
53
|
-
Sheng::Support.extract_mergefield_instruction_text(element)
|
47
|
+
@raw_key ||= @instruction_text.gsub(REGEXES[:instruction_text], '\1').strip
|
54
48
|
end
|
55
49
|
|
56
50
|
def styling_paragraph
|
@@ -184,15 +178,22 @@ module Sheng
|
|
184
178
|
end
|
185
179
|
|
186
180
|
def replace_mergefield(value)
|
181
|
+
value_as_string = if value.is_a?(BigDecimal)
|
182
|
+
value.to_s("F")
|
183
|
+
else
|
184
|
+
value.to_s
|
185
|
+
end
|
186
|
+
|
187
187
|
new_run = Sheng::Support.new_text_run(
|
188
|
-
|
188
|
+
value_as_string, xml_document: xml_document, style_run: styling_run
|
189
189
|
)
|
190
190
|
xml.before(new_run)
|
191
191
|
xml.remove
|
192
192
|
end
|
193
193
|
|
194
194
|
def key_parts
|
195
|
-
@key_parts ||= key.gsub("
|
195
|
+
@key_parts ||= key.gsub(",", "").
|
196
|
+
gsub(".", "_DOTSEPARATOR_").
|
196
197
|
split(/\b|\s/).
|
197
198
|
map(&:strip).
|
198
199
|
reject(&:empty?).
|
@@ -203,7 +204,7 @@ module Sheng
|
|
203
204
|
|
204
205
|
def required_variables
|
205
206
|
key_parts.reject { |token|
|
206
|
-
|
207
|
+
Support.is_numeric?(token) || MATH_TOKENS.include?(token)
|
207
208
|
}
|
208
209
|
end
|
209
210
|
|
@@ -224,7 +225,7 @@ module Sheng
|
|
224
225
|
|
225
226
|
def get_value(data_set)
|
226
227
|
interpolated_string = key_parts.map { |token|
|
227
|
-
if
|
228
|
+
if Support.is_numeric?(token) || MATH_TOKENS.include?(token)
|
228
229
|
token
|
229
230
|
else
|
230
231
|
data_set.fetch(token)
|
@@ -233,13 +234,13 @@ module Sheng
|
|
233
234
|
|
234
235
|
return interpolated_string unless key_has_math?
|
235
236
|
|
236
|
-
Dentaku::Calculator.new.evaluate!(interpolated_string)
|
237
|
+
Dentaku::Calculator.new.evaluate!(interpolated_string.gsub(",", ""))
|
237
238
|
end
|
238
239
|
|
239
240
|
def interpolate(data_set)
|
240
241
|
value = get_value(data_set)
|
241
242
|
replace_mergefield(filter_value(value))
|
242
|
-
rescue DataSet::KeyNotFound, Dentaku::UnboundVariableError
|
243
|
+
rescue DataSet::KeyNotFound, Dentaku::UnboundVariableError, Filters::UnsupportedFilterError
|
243
244
|
# Ignore this error; we'll collect all uninterpolated fields later and
|
244
245
|
# raise a new exception, so we can list all the fields in an error
|
245
246
|
# message.
|
@@ -247,12 +248,9 @@ module Sheng
|
|
247
248
|
end
|
248
249
|
|
249
250
|
def filter_value(value)
|
250
|
-
filters.inject(value) { |val,
|
251
|
-
|
252
|
-
|
253
|
-
else
|
254
|
-
val
|
255
|
-
end
|
251
|
+
filters.inject(value) { |val, filter_string|
|
252
|
+
filterer = Filters.filter_for(filter_string)
|
253
|
+
filterer.filter(val)
|
256
254
|
}
|
257
255
|
end
|
258
256
|
end
|
data/lib/sheng/support.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Sheng
|
2
2
|
module Support
|
3
3
|
class << self
|
4
|
+
NUMERIC_REGEX = /^(0|[1-9][0-9]*|[1-9][0-9]{0,2}(,[0-9]{3,3})*)(\.[0-9]+)?$/
|
5
|
+
|
4
6
|
def merge_required_hashes(hsh1, hsh2)
|
5
7
|
hsh1.merge(hsh2) do |key, old_value, new_value|
|
6
8
|
if [old_value, new_value].all? { |v| v.is_a?(Hash) }
|
@@ -52,6 +54,20 @@ module Sheng
|
|
52
54
|
end
|
53
55
|
label
|
54
56
|
end
|
57
|
+
|
58
|
+
def is_numeric?(value)
|
59
|
+
value.is_a?(Numeric) || !!NUMERIC_REGEX.match(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def typecast_numeric(value)
|
63
|
+
return value if value.is_a?(Numeric) || !NUMERIC_REGEX.match(value)
|
64
|
+
val = value.gsub(",", "").to_d
|
65
|
+
if val.frac == 0
|
66
|
+
val.to_i
|
67
|
+
else
|
68
|
+
val
|
69
|
+
end
|
70
|
+
end
|
55
71
|
end
|
56
72
|
end
|
57
73
|
end
|
data/lib/sheng/version.rb
CHANGED
data/lib/sheng.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
2
|
+
<w:p>
|
3
|
+
<w:fldSimple w:instr=" MERGEFIELD robots * 6250 + 18.3 | currency(£) \* MERGEFORMAT ">
|
4
|
+
<w:r>
|
5
|
+
<w:rPr>
|
6
|
+
<w:b/>
|
7
|
+
<w:i/>
|
8
|
+
<w:noProof/>
|
9
|
+
</w:rPr>
|
10
|
+
<w:t>«robots * 6250 + 18.3 | currency(£)»</w:t>
|
11
|
+
</w:r>
|
12
|
+
</w:fldSimple>
|
13
|
+
</w:p>
|
14
|
+
</w:document>
|
@@ -1,13 +1,13 @@
|
|
1
1
|
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
2
2
|
<w:p>
|
3
|
-
<w:fldSimple w:instr=" MERGEFIELD baskets.count * (3 + origami) \* MERGEFORMAT ">
|
3
|
+
<w:fldSimple w:instr=" MERGEFIELD baskets.count * (3,500 + 2 - origami) \* MERGEFORMAT ">
|
4
4
|
<w:r>
|
5
5
|
<w:rPr>
|
6
6
|
<w:b/>
|
7
7
|
<w:i/>
|
8
8
|
<w:noProof/>
|
9
9
|
</w:rPr>
|
10
|
-
<w:t>«baskets.count * (3 + origami)»</w:t>
|
10
|
+
<w:t>«baskets.count * (3,500 + 2 - origami)»</w:t>
|
11
11
|
</w:r>
|
12
12
|
</w:fldSimple>
|
13
13
|
</w:p>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "shared_examples/filters"
|
2
|
+
|
3
|
+
describe Sheng::Filters::CurrencyFormattingFilter do
|
4
|
+
test_cases = [
|
5
|
+
{ method: :currency, input: 3452341.826, output: "3,452,341.83" },
|
6
|
+
{ method: :currency, input: "1645.3", output: "1,645.30" },
|
7
|
+
{ method: :currency, arguments: ["¥"], input: "12351.184", output: "¥12,351.18" }
|
8
|
+
]
|
9
|
+
|
10
|
+
it_behaves_like "a filter", test_cases
|
11
|
+
|
12
|
+
context "with non-numeric value" do
|
13
|
+
describe "#filter" do
|
14
|
+
it "returns unmodified value" do
|
15
|
+
subject = described_class.new(method: :currency)
|
16
|
+
expect(subject.filter("apples")).to eq("apples")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "shared_examples/filters"
|
2
|
+
|
3
|
+
describe Sheng::Filters::NumericFilter do
|
4
|
+
test_cases = [
|
5
|
+
{ method: :round, arguments: [2], input: 149.3783, output: 149.38 },
|
6
|
+
{ method: :floor, input: 149.3783, output: 149 }
|
7
|
+
]
|
8
|
+
|
9
|
+
it_behaves_like "a filter", test_cases
|
10
|
+
|
11
|
+
context "with value that does not respond to method" do
|
12
|
+
describe "#filter" do
|
13
|
+
it "returns unmodified value" do
|
14
|
+
subject = described_class.new(method: :round)
|
15
|
+
expect(subject.filter("apples")).to eq("apples")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "shared_examples/filters"
|
2
|
+
|
3
|
+
describe Sheng::Filters::StringFilter do
|
4
|
+
test_cases = [
|
5
|
+
{ method: :upcase, input: "some Good thing", output: "SOME GOOD THING" },
|
6
|
+
{ method: :downcase, input: "Emerald CITY", output: "emerald city" },
|
7
|
+
{ method: :reverse, input: "A Man a Plan", output: "nalP a naM A" },
|
8
|
+
{ method: :capitalize, input: "good will hunting", output: "Good will hunting" },
|
9
|
+
{ method: :titleize, input: "good will hunting", output: "Good Will Hunting" }
|
10
|
+
]
|
11
|
+
|
12
|
+
it_behaves_like "a filter", test_cases
|
13
|
+
|
14
|
+
context "with value that does not respond to method" do
|
15
|
+
describe "#filter" do
|
16
|
+
it "returns unmodified value" do
|
17
|
+
subject = described_class.new(method: :upcase)
|
18
|
+
expect(subject.filter(12345)).to eq(12345)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
describe Sheng::Filters do
|
2
|
+
before(:each) do
|
3
|
+
allow(described_class).to receive(:registry).
|
4
|
+
and_return(bazinga: Sheng::Filters::Base)
|
5
|
+
end
|
6
|
+
|
7
|
+
describe ".filter_for" do
|
8
|
+
it "returns a filter instance that supports the given string" do
|
9
|
+
allow(Sheng::Filters::Base).to receive(:new).
|
10
|
+
with(method: "bazinga", arguments: [12, "hats"]).
|
11
|
+
and_return(:the_filter)
|
12
|
+
expect(described_class.filter_for("bazinga(12, hats)")).
|
13
|
+
to eq(:the_filter)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "raises exception if filter class not found" do
|
17
|
+
expect {
|
18
|
+
described_class.filter_for("unregistered(12, hats)")
|
19
|
+
}.to raise_error(Sheng::Filters::UnsupportedFilterError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -27,6 +27,22 @@ describe Sheng::MergeField do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
describe "mergefield with math and currency formatting" do
|
31
|
+
let(:fragment) { xml_fragment('input/merge_field/currency_merge_field') }
|
32
|
+
let(:element) { fragment.xpath("//w:fldSimple[contains(@w:instr, 'MERGEFIELD')]").first }
|
33
|
+
|
34
|
+
describe '#interpolate' do
|
35
|
+
it 'interpolates values from dataset into mergefield' do
|
36
|
+
dataset = Sheng::DataSet.new({
|
37
|
+
:robots => 3
|
38
|
+
})
|
39
|
+
|
40
|
+
subject.interpolate(dataset)
|
41
|
+
expect(subject.xml_document).to be_equivalent_to xml_fragment('output/merge_field/currency_merge_field')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
30
46
|
describe "mergefield with math operations" do
|
31
47
|
let(:fragment) { xml_fragment('input/merge_field/math_merge_field') }
|
32
48
|
let(:element) { fragment.xpath("//w:fldSimple[contains(@w:instr, 'MERGEFIELD')]").first }
|
@@ -35,9 +51,9 @@ describe Sheng::MergeField do
|
|
35
51
|
it 'interpolates values from dataset into mergefield' do
|
36
52
|
dataset = Sheng::DataSet.new({
|
37
53
|
:baskets => {
|
38
|
-
:count => 2
|
54
|
+
:count => "2,300.40"
|
39
55
|
},
|
40
|
-
:origami => 8
|
56
|
+
:origami => 8.5
|
41
57
|
})
|
42
58
|
|
43
59
|
subject.interpolate(dataset)
|
@@ -157,8 +173,8 @@ describe Sheng::MergeField do
|
|
157
173
|
end
|
158
174
|
|
159
175
|
it "performs math operations on values from dataset" do
|
160
|
-
allow(subject).to receive(:key).and_return("(numbers.first * numbers.second) + 5")
|
161
|
-
expect(subject.get_value(dataset)).to eq(189)
|
176
|
+
allow(subject).to receive(:key).and_return("(numbers.first * numbers.second) + 5.3")
|
177
|
+
expect(subject.get_value(dataset)).to eq(189.3)
|
162
178
|
end
|
163
179
|
|
164
180
|
it "performs math operations with no dataset lookup" do
|
@@ -207,6 +223,13 @@ describe Sheng::MergeField do
|
|
207
223
|
expect(subject).to receive(:replace_mergefield).never
|
208
224
|
expect(subject.interpolate(:a_dataset)).to be_nil
|
209
225
|
end
|
226
|
+
|
227
|
+
it "does not replace and returns nil if unsupported filter requested" do
|
228
|
+
allow(subject).to receive(:get_value).with(:a_dataset).and_return(:got_value)
|
229
|
+
allow(subject).to receive(:filter_value).with(:got_value).and_raise(Sheng::Filters::UnsupportedFilterError)
|
230
|
+
expect(subject).to receive(:replace_mergefield).never
|
231
|
+
expect(subject.interpolate(:a_dataset)).to be_nil
|
232
|
+
end
|
210
233
|
end
|
211
234
|
|
212
235
|
describe "#xml" do
|
@@ -314,44 +337,29 @@ describe Sheng::MergeField do
|
|
314
337
|
end
|
315
338
|
|
316
339
|
describe "#filter_value" do
|
317
|
-
it "
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
expect(subject.filter_value("HorSes")).to eq("
|
325
|
-
end
|
326
|
-
|
327
|
-
it "can reverse" do
|
328
|
-
allow(subject).to receive(:filters).and_return(["reverse"])
|
329
|
-
expect(subject.filter_value("Maple")).to eq("elpaM")
|
330
|
-
end
|
331
|
-
|
332
|
-
it "can titleize" do
|
333
|
-
allow(subject).to receive(:filters).and_return(["titleize"])
|
334
|
-
expect(subject.filter_value("ribbons are grand")).to eq("Ribbons Are Grand")
|
335
|
-
end
|
336
|
-
|
337
|
-
it "can capitalize" do
|
338
|
-
allow(subject).to receive(:filters).and_return(["capitalize"])
|
339
|
-
expect(subject.filter_value("ribbons are grand")).to eq("Ribbons are grand")
|
340
|
+
it "looks up filter and returns filtered result" do
|
341
|
+
filter_double = instance_double(Sheng::Filters::Base)
|
342
|
+
allow(subject).to receive(:filters).and_return(["foo"])
|
343
|
+
allow(Sheng::Filters).to receive(:filter_for).with("foo").
|
344
|
+
and_return(filter_double)
|
345
|
+
allow(filter_double).to receive(:filter).with("HorSes").
|
346
|
+
and_return("ponies")
|
347
|
+
expect(subject.filter_value("HorSes")).to eq("ponies")
|
340
348
|
end
|
341
349
|
|
342
350
|
it "works with multiple filters" do
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
allow(
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
expect(subject.filter_value(
|
351
|
+
filter1_double = instance_double(Sheng::Filters::Base)
|
352
|
+
filter2_double = instance_double(Sheng::Filters::Base)
|
353
|
+
allow(subject).to receive(:filters).and_return(["foo", "bar"])
|
354
|
+
allow(Sheng::Filters).to receive(:filter_for).with("foo").
|
355
|
+
and_return(filter1_double)
|
356
|
+
allow(Sheng::Filters).to receive(:filter_for).with("bar").
|
357
|
+
and_return(filter2_double)
|
358
|
+
allow(filter1_double).to receive(:filter).with("HorSes").
|
359
|
+
and_return("ponies")
|
360
|
+
allow(filter2_double).to receive(:filter).with("ponies").
|
361
|
+
and_return("Scuba Gear")
|
362
|
+
expect(subject.filter_value("HorSes")).to eq("Scuba Gear")
|
355
363
|
end
|
356
364
|
end
|
357
365
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
describe Sheng::Support do
|
2
|
+
describe ".is_numeric?" do
|
3
|
+
it "returns true if given integer" do
|
4
|
+
expect(described_class.is_numeric?(123)).to be true
|
5
|
+
end
|
6
|
+
|
7
|
+
it "returns true if given float" do
|
8
|
+
expect(described_class.is_numeric?(123.4)).to be true
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns true if given BigDecimal" do
|
12
|
+
expect(described_class.is_numeric?(123.4.to_d)).to be true
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns true if given valid numeric string" do
|
16
|
+
expect(described_class.is_numeric?("123.4")).to be true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns false if given invalid numeric string" do
|
20
|
+
expect(described_class.is_numeric?("0123.4")).to be false
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns true if given valid numeric string with appropriate commas" do
|
24
|
+
expect(described_class.is_numeric?("1,234.5")).to be true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns false if given valid numeric string with appropriate commas" do
|
28
|
+
expect(described_class.is_numeric?("12,34.5")).to be false
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns false if given non-numeric string" do
|
32
|
+
expect(described_class.is_numeric?("foobar")).to be false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe ".typecast_numeric" do
|
37
|
+
it "returns given numeric" do
|
38
|
+
expect(described_class.typecast_numeric(123.4)).to eq(123.4)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns given non-numeric string" do
|
42
|
+
expect(described_class.typecast_numeric("foobar")).to eq("foobar")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns numeric version of integer string" do
|
46
|
+
expect(described_class.typecast_numeric("123")).to eq(123)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns numeric version of decimal string" do
|
50
|
+
expect(described_class.typecast_numeric("123.4")).to eq(123.4)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
RSpec.shared_examples_for "a filter" do |test_cases|
|
2
|
+
test_cases.map { |tc| tc[:method] }.uniq.each do |method|
|
3
|
+
it "registers self for #{method} method" do
|
4
|
+
expect(Sheng::Filters.registry[method]).to eq(described_class)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
test_cases.each do |test_case|
|
9
|
+
context "with #{test_case[:method]} method" do
|
10
|
+
describe "#filter" do
|
11
|
+
it "returns filtered output" do
|
12
|
+
subject = described_class.new(method: test_case[:method], arguments: test_case[:arguments] || [])
|
13
|
+
expect(subject.filter(test_case[:input])).to eq(test_case[:output])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sheng
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ravi Gadad
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-03-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- ".gitignore"
|
163
163
|
- ".rspec"
|
164
164
|
- ".ruby-version"
|
165
|
+
- ".travis.yml"
|
165
166
|
- ".watchr"
|
166
167
|
- Gemfile
|
167
168
|
- LICENSE.txt
|
@@ -175,6 +176,11 @@ files:
|
|
175
176
|
- lib/sheng/conditional_block.rb
|
176
177
|
- lib/sheng/data_set.rb
|
177
178
|
- lib/sheng/docx.rb
|
179
|
+
- lib/sheng/filters.rb
|
180
|
+
- lib/sheng/filters/base.rb
|
181
|
+
- lib/sheng/filters/currency_formatting_filter.rb
|
182
|
+
- lib/sheng/filters/numeric_filter.rb
|
183
|
+
- lib/sheng/filters/string_filter.rb
|
178
184
|
- lib/sheng/merge_field.rb
|
179
185
|
- lib/sheng/merge_field_set.rb
|
180
186
|
- lib/sheng/path_helpers.rb
|
@@ -207,6 +213,7 @@ files:
|
|
207
213
|
- spec/fixtures/xml_fragments/input/merge_field/bad/not_a_real_mergefield_new.xml
|
208
214
|
- spec/fixtures/xml_fragments/input/merge_field/bad/not_a_real_mergefield_old.xml
|
209
215
|
- spec/fixtures/xml_fragments/input/merge_field/bad/unclosed_merge_field.xml
|
216
|
+
- spec/fixtures/xml_fragments/input/merge_field/currency_merge_field.xml
|
210
217
|
- spec/fixtures/xml_fragments/input/merge_field/inline_merge_field.xml
|
211
218
|
- spec/fixtures/xml_fragments/input/merge_field/math_merge_field.xml
|
212
219
|
- spec/fixtures/xml_fragments/input/merge_field/merge_field.xml
|
@@ -238,6 +245,7 @@ files:
|
|
238
245
|
- spec/fixtures/xml_fragments/output/conditional_block/inline_exists.xml
|
239
246
|
- spec/fixtures/xml_fragments/output/conditional_block/unless_does_not_exist.xml
|
240
247
|
- spec/fixtures/xml_fragments/output/conditional_block/unless_exists.xml
|
248
|
+
- spec/fixtures/xml_fragments/output/merge_field/currency_merge_field.xml
|
241
249
|
- spec/fixtures/xml_fragments/output/merge_field/inline_merge_field.xml
|
242
250
|
- spec/fixtures/xml_fragments/output/merge_field/math_merge_field.xml
|
243
251
|
- spec/fixtures/xml_fragments/output/merge_field/merge_field.xml
|
@@ -259,10 +267,16 @@ files:
|
|
259
267
|
- spec/lib/sheng/conditional_block_spec.rb
|
260
268
|
- spec/lib/sheng/data_set_spec.rb
|
261
269
|
- spec/lib/sheng/docx_spec.rb
|
270
|
+
- spec/lib/sheng/filters/currency_formatting_filter_spec.rb
|
271
|
+
- spec/lib/sheng/filters/numeric_filter_spec.rb
|
272
|
+
- spec/lib/sheng/filters/string_filter_spec.rb
|
273
|
+
- spec/lib/sheng/filters_spec.rb
|
262
274
|
- spec/lib/sheng/merge_field_set_spec.rb
|
263
275
|
- spec/lib/sheng/merge_field_spec.rb
|
264
276
|
- spec/lib/sheng/sequence_spec.rb
|
277
|
+
- spec/lib/sheng/support_spec.rb
|
265
278
|
- spec/lib/sheng/wml_file_spec.rb
|
279
|
+
- spec/shared_examples/filters.rb
|
266
280
|
- spec/spec_helper.rb
|
267
281
|
- spec/support/path_helper.rb
|
268
282
|
- spec/support/xml_helper.rb
|
@@ -316,6 +330,7 @@ test_files:
|
|
316
330
|
- spec/fixtures/xml_fragments/input/merge_field/bad/not_a_real_mergefield_new.xml
|
317
331
|
- spec/fixtures/xml_fragments/input/merge_field/bad/not_a_real_mergefield_old.xml
|
318
332
|
- spec/fixtures/xml_fragments/input/merge_field/bad/unclosed_merge_field.xml
|
333
|
+
- spec/fixtures/xml_fragments/input/merge_field/currency_merge_field.xml
|
319
334
|
- spec/fixtures/xml_fragments/input/merge_field/inline_merge_field.xml
|
320
335
|
- spec/fixtures/xml_fragments/input/merge_field/math_merge_field.xml
|
321
336
|
- spec/fixtures/xml_fragments/input/merge_field/merge_field.xml
|
@@ -347,6 +362,7 @@ test_files:
|
|
347
362
|
- spec/fixtures/xml_fragments/output/conditional_block/inline_exists.xml
|
348
363
|
- spec/fixtures/xml_fragments/output/conditional_block/unless_does_not_exist.xml
|
349
364
|
- spec/fixtures/xml_fragments/output/conditional_block/unless_exists.xml
|
365
|
+
- spec/fixtures/xml_fragments/output/merge_field/currency_merge_field.xml
|
350
366
|
- spec/fixtures/xml_fragments/output/merge_field/inline_merge_field.xml
|
351
367
|
- spec/fixtures/xml_fragments/output/merge_field/math_merge_field.xml
|
352
368
|
- spec/fixtures/xml_fragments/output/merge_field/merge_field.xml
|
@@ -368,10 +384,16 @@ test_files:
|
|
368
384
|
- spec/lib/sheng/conditional_block_spec.rb
|
369
385
|
- spec/lib/sheng/data_set_spec.rb
|
370
386
|
- spec/lib/sheng/docx_spec.rb
|
387
|
+
- spec/lib/sheng/filters/currency_formatting_filter_spec.rb
|
388
|
+
- spec/lib/sheng/filters/numeric_filter_spec.rb
|
389
|
+
- spec/lib/sheng/filters/string_filter_spec.rb
|
390
|
+
- spec/lib/sheng/filters_spec.rb
|
371
391
|
- spec/lib/sheng/merge_field_set_spec.rb
|
372
392
|
- spec/lib/sheng/merge_field_spec.rb
|
373
393
|
- spec/lib/sheng/sequence_spec.rb
|
394
|
+
- spec/lib/sheng/support_spec.rb
|
374
395
|
- spec/lib/sheng/wml_file_spec.rb
|
396
|
+
- spec/shared_examples/filters.rb
|
375
397
|
- spec/spec_helper.rb
|
376
398
|
- spec/support/path_helper.rb
|
377
399
|
- spec/support/xml_helper.rb
|