sheng 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a8b81b58b522c27f9eae0f86940da9f8b92743d5
4
- data.tar.gz: 5ba6ec64dd3891eb409550801fde1dbe95cdb4f5
3
+ metadata.gz: 7c3fd415b575d840f656d108d59c7877453e4b21
4
+ data.tar.gz: cdfe674b6cc940344dc11a98bc7cd6b049b477e3
5
5
  SHA512:
6
- metadata.gz: 9c40035114fcc183da71cb81b1e38e315163399fe7925ef1080f35e414d0f8dd800157e0b027ceb3aafdb5f7eb54b90c1d71d9a9e0374e133d969f008a41173e
7
- data.tar.gz: 50dc168d3817c280d37e916d77893b8d66ebc81b4c201ce6e1fa90eb4676b9357bb7a2699c7cf20d75672d45fb0aa53c574d12b9e692a3a7161cd7a78a7f18f7
6
+ metadata.gz: 22bd647f4447af076046039d90c0b51ef5394e93b26032b960db3724b33566e93aafc6d4bf2f2eefc18185433d47980f85f187d578c7eda9befc9614367cdaca
7
+ data.tar.gz: e63a965701379ab2e5c7082cc88b46909c3945580558adf446a7dbb3d6d43345e900688b5d1e914ebb8721f6105dfefb985bd35f5b3cbe0cf0b53fae9db1a8b4
data/lib/sheng/block.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Sheng
2
2
  class Block < MergeFieldSet
3
- class MissingEndTag < StandardError; end
4
- class ImproperNesting < StandardError; end
3
+ class MissingEndTag < Docx::TemplateError; end
4
+ class ImproperNesting < Docx::TemplateError; end
5
5
 
6
6
  def initialize(merge_field)
7
7
  @start_field = merge_field
@@ -1,6 +1,6 @@
1
1
  module Sheng
2
2
  class CheckBox
3
- attr_reader :element, :xml_document
3
+ attr_reader :element, :xml_document, :errors
4
4
 
5
5
  class << self
6
6
  def from_element(element)
@@ -11,6 +11,7 @@ module Sheng
11
11
  def initialize(element = nil)
12
12
  @element = element
13
13
  @xml_document = element.document
14
+ @errors = []
14
15
  end
15
16
 
16
17
  def ==(other)
@@ -29,7 +30,8 @@ module Sheng
29
30
  value = data_set.fetch(key)
30
31
  checked_attribute = @element.search('.//w:default').first.attribute('val')
31
32
  checked_attribute.value = value_is_truthy?(value) ? '1' : '0'
32
- rescue DataSet::KeyNotFound
33
+ rescue DataSet::KeyNotFound => e
34
+ @errors << e
33
35
  # Ignore this error; if the key for this checkbox is not found in the
34
36
  # data set, we don't want to uncheck the checkbox; we just want to leave
35
37
  # it alone.
@@ -1,6 +1,6 @@
1
1
  module Sheng
2
2
  class DataSet
3
- class KeyNotFound < StandardError; end
3
+ class KeyNotFound < Sheng::Error; end
4
4
 
5
5
  attr_accessor :raw_hash
6
6
 
@@ -29,7 +29,7 @@ module Sheng
29
29
  if options.has_key?(:default)
30
30
  value = options[:default]
31
31
  else
32
- raise KeyNotFound, "did not find in dataset: #{key} (#{key_part} not found)"
32
+ raise KeyNotFound, "#{key} (at #{key_part})"
33
33
  end
34
34
  end
35
35
  if (i + 1) < key_parts.length
data/lib/sheng/docx.rb CHANGED
@@ -4,8 +4,10 @@
4
4
  #
5
5
  module Sheng
6
6
  class Docx
7
- class InvalidFile < StandardError; end
8
- class FileAlreadyExists < StandardError; end
7
+ class InvalidFile < Sheng::Error; end
8
+ class TemplateError < Sheng::Error; end
9
+ class OutputPathAlreadyExists < Sheng::Error; end
10
+ class MergeError < Sheng::Error; end
9
11
 
10
12
  WMLFileNamePatterns = [
11
13
  /word\/document.xml/,
@@ -14,9 +16,12 @@ module Sheng
14
16
  /word\/footer(\d)*.xml/
15
17
  ]
16
18
 
19
+ attr_reader :errors
20
+
17
21
  def initialize(input_file_path, params)
18
22
  @input_zip_file = Zip::File.new(input_file_path)
19
23
  @data_set = DataSet.new(params)
24
+ @errors = {}
20
25
  rescue Zip::Error => e
21
26
  raise InvalidFile.new(e.message)
22
27
  end
@@ -39,10 +44,22 @@ module Sheng
39
44
 
40
45
  def generate(path, force: false)
41
46
  if File.exists?(path) && !force
42
- raise FileAlreadyExists, "File at #{path} already exists"
47
+ raise OutputPathAlreadyExists, "File at #{path} already exists"
48
+ end
49
+
50
+ output_buffer = generate_output_buffer
51
+
52
+ if errors.present?
53
+ raise MergeError.new(errors)
43
54
  end
44
55
 
45
- buffer = Zip::OutputStream.write_buffer do |out|
56
+ File.open(path, "w") { |f| f.write(output_buffer.string) }
57
+ end
58
+
59
+ private
60
+
61
+ def generate_output_buffer
62
+ Zip::OutputStream.write_buffer do |out|
46
63
  begin
47
64
  @input_zip_file.entries.each do |entry|
48
65
  write_converted_zip_file_to_buffer(entry, out)
@@ -51,18 +68,15 @@ module Sheng
51
68
  out.close_buffer
52
69
  end
53
70
  end
54
-
55
- File.open(path, "w") { |f| f.write(buffer.string) }
56
71
  end
57
72
 
58
- private
59
-
60
73
  def write_converted_zip_file_to_buffer(entry, buffer)
61
74
  contents = entry.get_input_stream.read
62
75
  buffer.put_next_entry(entry.name)
63
76
  if is_wml_file?(entry.name)
64
77
  wml_file = WMLFile.new(entry.name, contents)
65
78
  buffer.write wml_file.interpolate(@data_set)
79
+ errors.merge!(wml_file.errors)
66
80
  else
67
81
  buffer.write contents
68
82
  end
data/lib/sheng/filters.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Sheng
2
2
  module Filters
3
- class UnsupportedFilterError < StandardError; end
3
+ class UnsupportedFilterError < Sheng::Error; end
4
4
 
5
5
  class << self
6
6
  def registry
@@ -8,7 +8,7 @@ module Sheng
8
8
  key_string: /^(?<prefix>start:|end:|if:|end_if:|unless:|end_unless:)?\s*(?<key>[^\|]+)\s*\|?(?<filters>.*)?/
9
9
  }
10
10
 
11
- class NotAMergeFieldError < StandardError; end
11
+ class NotAMergeFieldError < Sheng::Error; end
12
12
 
13
13
  class << self
14
14
  def from_element(element)
@@ -18,12 +18,13 @@ module Sheng
18
18
  end
19
19
  end
20
20
 
21
- attr_reader :element, :xml_document
21
+ attr_reader :element, :xml_document, :errors
22
22
 
23
23
  def initialize(element)
24
24
  @element = element
25
25
  @xml_document = element.document
26
26
  @instruction_text = Sheng::Support.extract_mergefield_instruction_text(element)
27
+ @errors = []
27
28
  end
28
29
 
29
30
  def ==(other)
@@ -240,7 +241,8 @@ module Sheng
240
241
  def interpolate(data_set)
241
242
  value = get_value(data_set)
242
243
  replace_mergefield(filter_value(value))
243
- rescue DataSet::KeyNotFound, Dentaku::UnboundVariableError, Filters::UnsupportedFilterError
244
+ rescue DataSet::KeyNotFound, Dentaku::UnboundVariableError, Filters::UnsupportedFilterError => e
245
+ @errors << e
244
246
  # Ignore this error; we'll collect all uninterpolated fields later and
245
247
  # raise a new exception, so we can list all the fields in an error
246
248
  # message.
@@ -4,17 +4,25 @@ module Sheng
4
4
  class MergeFieldSet
5
5
  include PathHelpers
6
6
 
7
- attr_reader :xml_fragment, :xml_document, :key
7
+ attr_reader :xml_fragment, :xml_document, :key, :errors
8
8
 
9
9
  def initialize(key, xml_fragment)
10
10
  @key = key
11
11
  @xml_fragment = xml_fragment
12
12
  @xml_document = xml_fragment.document
13
+ @errors = {}
13
14
  end
14
15
 
15
16
  def interpolate(data_set)
16
17
  nodes.each do |node|
17
18
  node.interpolate(data_set)
19
+ add_errors_from_node(node)
20
+ end
21
+ end
22
+
23
+ def add_errors_from_node(node)
24
+ if node.errors.present?
25
+ errors[node.raw_key] = node.errors
18
26
  end
19
27
  end
20
28
 
data/lib/sheng/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Sheng
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -1,37 +1,21 @@
1
1
  module Sheng
2
2
  class WMLFile
3
- class InvalidWML < StandardError; end
4
- class MergefieldNotReplacedError < StandardError
5
- def initialize(unmerged_fields)
6
- unmerged_keys = unmerged_fields.map(&:raw_key)
7
- super("Mergefields not replaced: #{unmerged_keys.join(', ')}")
8
- end
9
- end
3
+ class InvalidWML < Docx::TemplateError; end
10
4
 
11
- attr_reader :xml, :filename
5
+ attr_reader :xml, :filename, :errors
12
6
 
13
7
  def initialize(filename, xml)
14
8
  @filename = filename
15
9
  @xml = Nokogiri::XML(xml)
10
+ @errors = {}
16
11
  end
17
12
 
18
13
  def interpolate(data_set)
19
14
  parent_set.interpolate(data_set)
20
- check_for_full_interpolation!
15
+ errors.merge!(parent_set.errors)
21
16
  parent_set.xml_fragment.to_s
22
17
  end
23
18
 
24
- def check_for_full_interpolation!
25
- modified_parent_set = MergeFieldSet.new('main', xml)
26
- unmerged_fields = modified_parent_set.basic_nodes.reject { |node|
27
- node.is_a?(CheckBox)
28
- }
29
-
30
- unless unmerged_fields.empty?
31
- raise MergefieldNotReplacedError.new(unmerged_fields)
32
- end
33
- end
34
-
35
19
  def parent_set
36
20
  @parent_set ||= MergeFieldSet.new('main', xml)
37
21
  end
data/lib/sheng.rb CHANGED
@@ -1,6 +1,14 @@
1
-
2
1
  require 'active_support/inflector'
3
2
  require 'active_support/core_ext/hash'
3
+ require 'zip'
4
+ require 'nokogiri'
5
+ require 'fileutils'
6
+ require 'json'
7
+
8
+ module Sheng
9
+ class Error < StandardError; end
10
+ end
11
+
4
12
  require 'sheng/support'
5
13
  require 'sheng/version'
6
14
  require 'sheng/data_set'
@@ -12,11 +20,3 @@ require 'sheng/merge_field'
12
20
  require 'sheng/sequence'
13
21
  require 'sheng/conditional_block'
14
22
  require 'sheng/check_box'
15
-
16
- require 'zip'
17
- require 'nokogiri'
18
- require 'fileutils'
19
- require 'json'
20
-
21
- module Sheng
22
- end
@@ -71,17 +71,21 @@ describe Sheng::CheckBox do
71
71
  expect(default_checked.xml_document).to be_equivalent_to fragment_with_unchecked_box(default_checked.xml_document)
72
72
  end
73
73
 
74
- it "does not check the checkbox if key is not found in dataset" do
75
- dataset = Sheng::DataSet.new({})
74
+ it "records error and does not check the checkbox if key is not found in dataset" do
75
+ allow(dataset).to receive(:fetch).with("goats").and_raise(Sheng::DataSet::KeyNotFound, "uhoh")
76
76
  subject.interpolate(dataset)
77
77
  expect(subject.xml_document).to be_equivalent_to fragment_with_unchecked_box(subject.xml_document)
78
+ expect(subject.errors.first).to be_a(Sheng::DataSet::KeyNotFound)
79
+ expect(subject.errors.first.message).to eq("uhoh")
78
80
  end
79
81
 
80
- it "does not uncheck a checked checkbox if key is not found in dataset" do
81
- dataset = Sheng::DataSet.new({})
82
+ it "records error and does not uncheck a checked checkbox if key is not found in dataset" do
83
+ allow(dataset).to receive(:fetch).with("goats").and_raise(Sheng::DataSet::KeyNotFound, "uhoh")
82
84
  default_checked = described_class.new(fragment_with_checked_box(subject.xml_document))
83
85
  default_checked.interpolate(dataset)
84
86
  expect(default_checked.xml_document).to be_equivalent_to xml_fragment('output/check_box/check_box')
87
+ expect(default_checked.errors.first).to be_a(Sheng::DataSet::KeyNotFound)
88
+ expect(default_checked.errors.first.message).to eq("uhoh")
85
89
  end
86
90
  end
87
91
  end
@@ -43,7 +43,7 @@ describe Sheng::DataSet do
43
43
  it 'raises a KeyNotFound error if key not found' do
44
44
  expect {
45
45
  subject.fetch('rabbits.funky')
46
- }.to raise_error(described_class::KeyNotFound, "did not find in dataset: rabbits.funky (funky not found)")
46
+ }.to raise_error(described_class::KeyNotFound, "rabbits.funky (at funky)")
47
47
  end
48
48
 
49
49
  it 'does not raise error on key not found if default given' do
@@ -26,7 +26,7 @@ describe Sheng::Docx do
26
26
  File.open(output_file, "w").write("nothing")
27
27
  expect {
28
28
  subject.generate(output_file)
29
- }.to raise_error(described_class::FileAlreadyExists)
29
+ }.to raise_error(described_class::OutputPathAlreadyExists)
30
30
  end
31
31
 
32
32
  it 'overwrites file if file already exists but force option is true' do
@@ -63,12 +63,14 @@ describe Sheng::Docx do
63
63
  end
64
64
  end
65
65
 
66
- it "should raise an error when one or more mergefields isn't merged" do
66
+ it "should raise an exception and set errors when one or more mergefields isn't merged" do
67
67
  incomplete_hash = JSON.parse(File.read(fixture_path("inputs/incomplete.json")))
68
68
  doc = described_class.new(input_file, incomplete_hash)
69
69
  expect {
70
70
  doc.generate(output_file)
71
- }.to raise_error(Sheng::WMLFile::MergefieldNotReplacedError, "Mergefields not replaced: first_name, last_name")
71
+ }.to raise_error(Sheng::Docx::MergeError)
72
+ expect(doc.errors.keys).to eq(["first_name", "last_name"])
73
+ expect(doc.errors["first_name"].map(&:message)).to eq(["first_name (at first_name)"])
72
74
  end
73
75
 
74
76
  shared_examples_for 'a bad document' do |filename, error, error_message = nil|
@@ -81,16 +83,16 @@ describe Sheng::Docx do
81
83
  end
82
84
 
83
85
  it_should_behave_like 'a bad document', 'with_field_not_in_dataset.docx',
84
- Sheng::WMLFile::MergefieldNotReplacedError
86
+ Sheng::Docx::MergeError, {"extra_name"=>[Sheng::DataSet::KeyNotFound.new("extra_name (at extra_name)")] }.to_s
85
87
 
86
88
  it_should_behave_like 'a bad document', 'with_unended_sequence.docx',
87
- Sheng::Sequence::MissingEndTag, "no end tag for start:owner_signature"
89
+ Sheng::Docx::TemplateError, "no end tag for start:owner_signature"
88
90
 
89
91
  it_should_behave_like 'a bad document', 'with_missing_sequence_start.docx',
90
- Sheng::WMLFile::MergefieldNotReplacedError
92
+ Sheng::Docx::MergeError, {"end:owner_signature"=>[Sheng::DataSet::KeyNotFound.new("owner_signature (at owner_signature)")] }.to_s
91
93
 
92
94
  it_should_behave_like 'a bad document', 'with_poorly_nested_sequences.docx',
93
- Sheng::Sequence::ImproperNesting, "expected end tag for start:birds, got end:animals"
95
+ Sheng::Docx::TemplateError, "expected end tag for start:birds, got end:animals"
94
96
  end
95
97
 
96
98
  describe '#new' do
@@ -4,11 +4,35 @@ describe Sheng::MergeFieldSet do
4
4
 
5
5
  describe '#interpolate' do
6
6
  it 'iterates through nodes and calls interpolate on each' do
7
- node1, node2 = double('Node'), double('Node')
7
+ node1, node2 = double('Node', errors: []), double('Node', errors: [])
8
8
  expect(node1).to receive(:interpolate).with(:the_data_set)
9
9
  expect(node2).to receive(:interpolate).with(:the_data_set)
10
10
  allow(subject).to receive(:nodes).and_return([node1, node2])
11
11
  subject.interpolate(:the_data_set)
12
+ expect(subject.errors).to be_empty
13
+ end
14
+
15
+ context "with mergefields that raise errors on interpolation" do
16
+ it "should compile all errors" do
17
+ node1, node2, node3 =
18
+ double('Node', raw_key: "node1"),
19
+ double('Node', raw_key: "node2"),
20
+ double('Node', raw_key: "node3")
21
+ allow(node1).to receive(:interpolate).with(:the_data_set)
22
+ allow(node2).to receive(:interpolate).with(:the_data_set)
23
+ allow(node3).to receive(:interpolate).with(:the_data_set)
24
+ allow(node1).to receive(:errors).and_return(["stupid", "oops"])
25
+ allow(node2).to receive(:errors).and_return({ "node4" => ["uhoh", "darnit"] })
26
+ allow(node3).to receive(:errors).and_return([])
27
+ allow(subject).to receive(:nodes).and_return([node1, node2, node3])
28
+ subject.interpolate(:the_data_set)
29
+ expect(subject.errors).to eq({
30
+ "node1" => ["stupid", "oops"],
31
+ "node2" => {
32
+ "node4" => ["uhoh", "darnit"]
33
+ }
34
+ })
35
+ end
12
36
  end
13
37
 
14
38
  context "with fields that are not valid mergefields" do
@@ -212,23 +212,34 @@ describe Sheng::MergeField do
212
212
  expect(subject.xml_document).to be_equivalent_to xml_fragment('output/merge_field/merge_field')
213
213
  end
214
214
 
215
- it "does not replace and returns nil if any keys not found" do
216
- allow(subject).to receive(:get_value).with(:a_dataset).and_raise(Sheng::DataSet::KeyNotFound)
215
+ it "does not replace, records error, and returns nil if any keys not found" do
216
+ allow(subject).to receive(:get_value).with(:a_dataset).and_raise(Sheng::DataSet::KeyNotFound.new("horses"))
217
217
  expect(subject).to receive(:replace_mergefield).never
218
218
  expect(subject.interpolate(:a_dataset)).to be_nil
219
+ expect(subject.errors.first).to be_a(Sheng::DataSet::KeyNotFound)
220
+ expect(subject.errors.first.message).to eq("horses")
219
221
  end
220
222
 
221
- it "does not replace and returns nil if calculation error encountered" do
222
- allow(subject).to receive(:get_value).with(:a_dataset).and_raise(Dentaku::UnboundVariableError.new([]))
223
+ it "does not replace, records error, and returns nil if calculation error encountered" do
224
+ allow(subject).to receive(:get_value).with(:a_dataset).and_raise(Dentaku::UnboundVariableError.new([:foo, :bar]))
223
225
  expect(subject).to receive(:replace_mergefield).never
224
226
  expect(subject.interpolate(:a_dataset)).to be_nil
227
+ expect(subject.errors.first).to be_a(Dentaku::UnboundVariableError)
228
+ expect(subject.errors.first.unbound_variables).to eq([:foo, :bar])
225
229
  end
226
230
 
227
- it "does not replace and returns nil if unsupported filter requested" do
231
+ it "does not replace, records error, and returns nil if unsupported filter requested" do
228
232
  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)
233
+ allow(subject).to receive(:filter_value).with(:got_value).and_raise(Sheng::Filters::UnsupportedFilterError.new("woof"))
230
234
  expect(subject).to receive(:replace_mergefield).never
231
235
  expect(subject.interpolate(:a_dataset)).to be_nil
236
+ expect(subject.errors.first).to be_a(Sheng::Filters::UnsupportedFilterError)
237
+ expect(subject.errors.first.message).to eq("woof")
238
+ end
239
+
240
+ it "does not rescue other exceptions" do
241
+ allow(subject).to receive(:get_value).with(:a_dataset).and_raise(NoMethodError)
242
+ expect { subject.interpolate(:a_dataset) }.to raise_error(NoMethodError)
232
243
  end
233
244
  end
234
245
 
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.5.0
4
+ version: 0.6.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-03-24 00:00:00.000000000 Z
12
+ date: 2016-05-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler