lm_docstache 1.1.2 → 1.2.3

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
  SHA256:
3
- metadata.gz: 4da1923074bce295e621bf0fff86882e16fe9f8bef9bf7373e955ce4b1a1f8f7
4
- data.tar.gz: 7fb1709820b0d692c68b0ca8f5b2cea3769170e2387254f309bc77cd2cb9c730
3
+ metadata.gz: c16a6e73e07d070f2c486647c784c04627d746849481c145d66f6735780c65bb
4
+ data.tar.gz: 2fa52f69ea406bfe221bccff2a599af568ea9e0d6c21ab20500d06ad0dbfaeb4
5
5
  SHA512:
6
- metadata.gz: b4d8662856ee7a9b1239b4ca5e1427ae57132b0da6133b7cc955e85966c90facfa20da9261b6d4654a5fa2b96fe5bedf2dd61ea20d70039a276aa9628d412223
7
- data.tar.gz: 65912e5b0715e750de569d69b3da542e1f7ebbe58a58cab115b8090e64de4e8b329b18f7c95e45bc2a234a15045728acab13611303889ba7dde923f54830c86d
6
+ metadata.gz: '0096916f50745ca968b0a2d365a0d279aae3007c4d7e5e6376c87b1d512f082d0646e064d5a84bef9be1b33e53eb3d81d33122a6e868c2e389f67aecfbe00662'
7
+ data.tar.gz: 0723cefa1931bbc2d8b7ab513d1af844f43d05ff4d61fa04d73ad772cbd8a81dea29319a72403b6657ae49688026edc6ace60dc5a0b32a85ed8223c003820901
@@ -0,0 +1,29 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on: push
11
+
12
+ jobs:
13
+ test:
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - name: Set up Ruby
20
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
21
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
22
+ # uses: ruby/setup-ruby@v1
23
+ uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
24
+ with:
25
+ ruby-version: 2.6
26
+ - name: Install dependencies
27
+ run: bundle install
28
+ - name: Run tests
29
+ run: rspec
@@ -1,7 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.2
4
+ * Remove uneccessary runtime dependency on active support.
5
+
6
+ ## 1.2.1
7
+ * Fix inline conditional issues such as no support for multi conditionals.
8
+
9
+ ## 1.2.0
10
+ * Add support for inline conditionals.
11
+ * Add some tests.
12
+
3
13
  ## 1.0.0
4
- Fork the Docstache codebase and rename all references to LMDocstache.
14
+ * Fork the Docstache codebase and rename all references to LMDocstache.
5
15
 
6
16
  ## 0.3.2
7
17
  * [b3b66a0cd7ae67834cdbe7c18cefdb1498bcc5ab] I'm an idiot. Fixed an error in the `Document#unusable_tags` method
@@ -1,7 +1,7 @@
1
1
  module LMDocstache
2
2
  class Block
3
- attr_reader :name, :opening_element, :content_elements, :closing_element, :inverted, :condition
4
- def initialize(name:, data:, opening_element:, content_elements:, closing_element:, inverted:, condition: nil)
3
+ attr_reader :name, :opening_element, :content_elements, :closing_element, :inverted, :condition, :inline
4
+ def initialize(name:, data:, opening_element:, content_elements:, closing_element:, inverted:, condition: nil, inline: false)
5
5
  @name = name
6
6
  @data = data
7
7
  @opening_element = opening_element
@@ -9,6 +9,7 @@ module LMDocstache
9
9
  @closing_element = closing_element
10
10
  @inverted = inverted
11
11
  @condition = condition
12
+ @inline = inline
12
13
  end
13
14
 
14
15
  def type
@@ -31,7 +32,7 @@ module LMDocstache
31
32
  type == :conditional
32
33
  end
33
34
 
34
- def self.find_all(name:, data:, elements:, inverted:, condition: nil, ignore_missing: true)
35
+ def self.find_all(name:, data:, elements:, inverted:, condition: nil, ignore_missing: true, child: false)
35
36
  inverted_op = inverted ? '\^' : '\#'
36
37
  full_tag_regex = /\{\{#{inverted_op}(#{name})\s?#{condition}\}\}.+?\{\{\/\k<1>\}\}/m
37
38
  start_tag_regex = /\{\{#{inverted_op}#{name}\s?#{condition}\}\}/m
@@ -41,7 +42,11 @@ module LMDocstache
41
42
  if elements.any? { |e| e.text.match(full_tag_regex) }
42
43
  matches = elements.select { |e| e.text.match(full_tag_regex) }
43
44
  return matches.flat_map do |match|
44
- find_all(name: name, data: data, elements: match.elements, inverted: inverted, condition: condition)
45
+ if match.elements.any?
46
+ find_all(name: name, data: data, elements: match.elements, inverted: inverted, condition: condition, child: true)
47
+ else
48
+ extract_block_from_element(name, data, match, inverted, condition)
49
+ end
45
50
  end
46
51
  else
47
52
  opening = elements.find { |e| e.text.match(start_tag_regex) }
@@ -58,5 +63,9 @@ module LMDocstache
58
63
  raise "Block not found in given elements" unless ignore_missing
59
64
  end
60
65
  end
66
+
67
+ def self.extract_block_from_element(name, data, element, inverted, condition)
68
+ return Block.new(name: name, data: data, opening_element: element.parent.previous, content_elements: [element.parent], closing_element: element.parent.next, inverted: inverted, condition: condition, inline: true)
69
+ end
61
70
  end
62
71
  end
@@ -68,6 +68,16 @@ module LMDocstache
68
68
  File.open(output, "w") { |f| f.write buffer.string }
69
69
  end
70
70
 
71
+ def render_replace(output, text)
72
+ rendered_documents = Hash[
73
+ @documents.map do |(path, document)|
74
+ [path, LMDocstache::Renderer.new(document.dup, {}).render_replace(text)]
75
+ end
76
+ ]
77
+ buffer = zip_buffer(rendered_documents)
78
+ File.open(output, "w") { |f| f.write buffer.string }
79
+ end
80
+
71
81
  def render_stream(data={})
72
82
  rendered_documents = Hash[
73
83
  @documents.map do |(path, document)|
@@ -79,6 +89,16 @@ module LMDocstache
79
89
  return buffer.sysread
80
90
  end
81
91
 
92
+ def render_xml(data={})
93
+ rendered_documents = Hash[
94
+ @documents.map do |(path, document)|
95
+ [path, LMDocstache::Renderer.new(document.dup, data).render]
96
+ end
97
+ ]
98
+
99
+ rendered_documents
100
+ end
101
+
82
102
  private
83
103
 
84
104
  def problem_paragraphs
@@ -91,7 +111,12 @@ module LMDocstache
91
111
 
92
112
  def flatten_paragraph(p)
93
113
  runs = p.css('w|r')
114
+
94
115
  host_run = runs.shift
116
+ until host_run.at_css('w|t').present? || runs.size == 0 do
117
+ host_run = runs.shift
118
+ end
119
+
95
120
  runs.each do |run|
96
121
  host_run.at_css('w|t').content += run.text
97
122
  run.unlink
@@ -13,6 +13,16 @@ module LMDocstache
13
13
  return @content
14
14
  end
15
15
 
16
+ def render_replace(text)
17
+ @content.css('w|t').each do |text_el|
18
+ rendered_string = text_el.text
19
+ if !(results = rendered_string.scan(/\|-Lawmatics Test-\|/)).empty?
20
+ text_el.content = text
21
+ end
22
+ end
23
+ return @content
24
+ end
25
+
16
26
  private
17
27
 
18
28
  def find_and_expand_blocks
@@ -22,20 +32,18 @@ module LMDocstache
22
32
  Block.find_all(name: block[1], elements: @content.elements, data: @data, inverted: inverted, condition: block[2])
23
33
  end
24
34
  found_blocks.each do |block|
25
- expand_and_replace_block(block) if block.present?
35
+ if block.inline
36
+ replace_conditionals(block)
37
+ else
38
+ expand_and_replace_block(block) if block.present?
39
+ end
26
40
  end
27
41
  end
28
42
 
29
43
  def expand_and_replace_block(block)
30
44
  case block.type
31
45
  when :conditional
32
- case condition = @data.get(block.name, condition: block.condition)
33
- when Array
34
- condition = !condition.empty?
35
- else
36
- condition = !!condition
37
- end
38
- condition = !condition if block.inverted
46
+ condition = get_condition(block.name, block.condition, block.inverted)
39
47
  unless condition
40
48
  block.content_elements.each(&:unlink)
41
49
  end
@@ -60,6 +68,42 @@ module LMDocstache
60
68
  block.closing_element.unlink
61
69
  end
62
70
 
71
+ def replace_conditionals(block)
72
+ @content.css('w|t').each do |text_el|
73
+ rendered_string = text_el.text
74
+
75
+ if !(results = rendered_string.scan(/{{#(.*?)}}(.*?){{\/(.*?)}}/)).empty?
76
+ results.each do |r|
77
+ vals = r[0].split('==')
78
+ condition = get_condition(vals[0].strip, "== #{vals[1]}")
79
+ if condition
80
+ rendered_string.sub!("{{##{r[0]}}}", "")
81
+ rendered_string.sub!("{{/#{r[2]}}}", "")
82
+ else
83
+ rendered_string.sub!("{{##{r[0]}}}#{r[1]}{{/#{r[2]}}}", "")
84
+ end
85
+ end
86
+ end
87
+
88
+ # the only difference in this code block is caret instead of pound in three places,
89
+ # the inverted value passed to get_condition, and the condition being inverted. maybe combine them?
90
+ if !(results = rendered_string.scan(/{{\^(.*?)}}(.*?){{\/(.*?)}}/)).empty?
91
+ results.each do |r|
92
+ vals = r[0].split('==')
93
+ condition = get_condition(vals[0].strip, "== #{vals[1]}", true)
94
+ if condition
95
+ rendered_string.sub!("{{^#{r[0]}}}", "")
96
+ rendered_string.sub!("{{/#{r[2]}}}", "")
97
+ else
98
+ rendered_string.sub!("{{^#{r[0]}}}#{r[1]}{{/#{r[2]}}}", "")
99
+ end
100
+ end
101
+ end
102
+
103
+ text_el.content = rendered_string
104
+ end
105
+ end
106
+
63
107
  def replace_tags(elements, data)
64
108
  elements.css('w|t').each do |text_el|
65
109
  if !(results = text_el.text.scan(/\{\{([\w\.]+)\}\}/).flatten).empty?
@@ -72,5 +116,19 @@ module LMDocstache
72
116
  end
73
117
  return elements
74
118
  end
119
+
120
+ private
121
+
122
+ def get_condition(name, condition, inverted = false)
123
+ case condition = @data.get(name, condition: condition)
124
+ when Array
125
+ condition = !condition.empty?
126
+ else
127
+ condition = !!condition
128
+ end
129
+ condition = !condition if inverted
130
+
131
+ condition
132
+ end
75
133
  end
76
134
  end
@@ -1,3 +1,3 @@
1
1
  module LMDocstache
2
- VERSION = "1.1.2"
2
+ VERSION = "1.2.3"
3
3
  end
@@ -5,8 +5,8 @@ require "lm_docstache/version"
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "lm_docstache"
7
7
  s.version = LMDocstache::VERSION
8
- s.authors = ["Roey Chasman", "Will Cosgrove"]
9
- s.email = ["roey@lawmatics.com", "will@willcosgrove.com"]
8
+ s.authors = ["Roey Chasman", "Jonathan Stevens", "Will Cosgrove"]
9
+ s.email = ["roey@lawmatics.com", "jonathan@lawmatics.com", "will@willcosgrove.com"]
10
10
  s.homepage = "https://www.lawmatics.com"
11
11
  s.summary = %q{Merges Hash of Data into Word docx template files using mustache syntax}
12
12
  s.description = %q{Integrates data into MS Word docx template files. Processing supports loops and replacement of strings of data both outside and within loops.}
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.add_runtime_dependency 'nokogiri', '~> 1.6'
21
21
  s.add_runtime_dependency 'rubyzip', '~> 1.1'
22
22
 
23
- s.add_development_dependency 'rspec', '>= 3.1.0'
24
- s.add_development_dependency 'pry-byebug', '>= 1'
23
+ s.add_development_dependency 'rspec', '~> 3.1', '>= 3.1.0'
24
+ s.add_development_dependency 'pry-byebug', '~> 1'
25
+ s.add_development_dependency 'activesupport', '~> 3'
25
26
  end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+ require 'nokogiri'
3
+
4
+ module LMDocstache
5
+ module BlockTestData
6
+ DATA = {
7
+ gender: 'Male',
8
+ num: '2',
9
+ first_name: 'Hector',
10
+ last_name: 'Jones'
11
+ }
12
+ end
13
+ end
14
+
15
+ describe LMDocstache::Renderer do
16
+ let(:data) { Marshal.load(Marshal.dump(LMDocstache::BlockTestData::DATA)) } # deep copy
17
+ let(:base_path) { SPEC_BASE_PATH.join('example_input') }
18
+ let(:blank_doc_path) { "#{base_path}/blank.docx" }
19
+ let(:blank_doc) { LMDocstache::Document.new(blank_doc_path) }
20
+ let(:output_dir) { "#{base_path}/tmp" }
21
+ let(:temp_file) { "#{output_dir}/temp.docx" }
22
+ let(:result_file) { "#{output_dir}/result.docx" }
23
+
24
+ def render_docx(doc_text)
25
+ # create doc from blank
26
+ blank_doc.render_replace(temp_file, doc_text)
27
+
28
+ doc = LMDocstache::Document.new(temp_file).render_file(result_file, data)
29
+
30
+ result_doc = LMDocstache::Document.new(result_file).render_xml(data)
31
+ result_doc["word/document.xml"].text
32
+ end
33
+
34
+ before do
35
+ FileUtils.rm_rf(output_dir) if File.exist?(output_dir)
36
+ Dir.mkdir(output_dir)
37
+ end
38
+
39
+ after do
40
+ File.delete(temp_file)
41
+ File.delete(result_file)
42
+ end
43
+
44
+ it 'should handle inline conditional tags' do
45
+ result_text = render_docx("Refer to the matter as {{#gender == Male}}he{{/gender}}{{^gender == Male}}she{{/gender}} please")
46
+ expected_text = "Refer to the matter as he please"
47
+ expect(result_text).to eq(expected_text)
48
+ end
49
+
50
+ it 'should handle else statements with inline conditional tags' do
51
+ result_text = render_docx("Refer to the matter as {{#gender == 'Female'}}he{{/gender}}{{^gender == 'Female'}}she{{/gender}} please")
52
+ expected_text = "Refer to the matter as she please"
53
+ expect(result_text).to eq(expected_text)
54
+ end
55
+
56
+ it 'should handle inline conditional tags with no matches' do
57
+ result_text = render_docx("Refer to the matter as {{#gender == 'none'}}he{{/gender}} please")
58
+ expected_text = "Refer to the matter as please"
59
+ expect(result_text).to eq(expected_text)
60
+ end
61
+
62
+ it 'should handle inline conditional tags with tags inside' do
63
+ result_text = render_docx("Refer to the matter as {{#gender == 'Male'}}{{first_name}}{{/gender}}{{^gender == 'Male'}}{{last_name}}{{/gender}} please")
64
+ expected_text = "Refer to the matter as Hector please"
65
+ expect(result_text).to eq(expected_text)
66
+ end
67
+
68
+ it 'should handle multiple positive checks in one line' do
69
+ result_text = render_docx("Refer to the matter as {{#gender == 'Male'}}him{{/gender}}{{#gender == 'Female'}}her{{/gender}} please")
70
+ expected_text = "Refer to the matter as him please"
71
+ expect(result_text).to eq(expected_text)
72
+ end
73
+
74
+ it 'should handle multiple positive checks and multiple negative checks in one line' do
75
+ result_text = render_docx("be {{#num == 1}}1{{/num}}{{#num == 2}}2{{/num}} and {{^num == 2}}!2{{/num}}{{^num == 1}}!1{{/num}} please")
76
+ expected_text = "be 2 and !1 please"
77
+ expect(result_text).to eq(expected_text)
78
+ end
79
+
80
+ it 'should handle multiple types of conditionals in one line' do
81
+ result_text = render_docx("be{{#gender == Male}} he {{/gender}}{{#num == 1}}1{{/num}}{{#num == 2}}2{{/num}} and {{^num == 2}}!2{{/num}}{{^num == 1}}!1{{/num}} please")
82
+ expected_text = "be he 2 and !1 please"
83
+ expect(result_text).to eq(expected_text)
84
+ end
85
+
86
+ it 'should handle multiline conditional tags' do
87
+ text = [
88
+ "Refer to the matter as",
89
+ "{{#gender == 'Male'}}",
90
+ "{{first_name}}",
91
+ "{{/gender}}",
92
+ "{{^gender == 'Male'}}",
93
+ "{{last_name}}",
94
+ "{{/gender}}Thank you"
95
+ ].join("\r\n")
96
+
97
+ result_text = render_docx(text)
98
+ expected_text = "Refer to the matter as\r\rHector\r\rThank you"
99
+ expect(result_text).to eq(expected_text)
100
+ end
101
+ end
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lm_docstache
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roey Chasman
8
+ - Jonathan Stevens
8
9
  - Will Cosgrove
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2020-06-09 00:00:00.000000000 Z
13
+ date: 2020-07-28 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: nokogiri
@@ -46,6 +47,9 @@ dependencies:
46
47
  - - ">="
47
48
  - !ruby/object:Gem::Version
48
49
  version: 3.1.0
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '3.1'
49
53
  type: :development
50
54
  prerelease: false
51
55
  version_requirements: !ruby/object:Gem::Requirement
@@ -53,29 +57,48 @@ dependencies:
53
57
  - - ">="
54
58
  - !ruby/object:Gem::Version
55
59
  version: 3.1.0
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.1'
56
63
  - !ruby/object:Gem::Dependency
57
64
  name: pry-byebug
58
65
  requirement: !ruby/object:Gem::Requirement
59
66
  requirements:
60
- - - ">="
67
+ - - "~>"
61
68
  - !ruby/object:Gem::Version
62
69
  version: '1'
63
70
  type: :development
64
71
  prerelease: false
65
72
  version_requirements: !ruby/object:Gem::Requirement
66
73
  requirements:
67
- - - ">="
74
+ - - "~>"
68
75
  - !ruby/object:Gem::Version
69
76
  version: '1'
77
+ - !ruby/object:Gem::Dependency
78
+ name: activesupport
79
+ requirement: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3'
84
+ type: :development
85
+ prerelease: false
86
+ version_requirements: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '3'
70
91
  description: Integrates data into MS Word docx template files. Processing supports
71
92
  loops and replacement of strings of data both outside and within loops.
72
93
  email:
73
94
  - roey@lawmatics.com
95
+ - jonathan@lawmatics.com
74
96
  - will@willcosgrove.com
75
97
  executables: []
76
98
  extensions: []
77
99
  extra_rdoc_files: []
78
100
  files:
101
+ - ".github/workflows/ruby.yml"
79
102
  - ".gitignore"
80
103
  - CHANGELOG.md
81
104
  - Gemfile
@@ -89,9 +112,11 @@ files:
89
112
  - lib/lm_docstache/renderer.rb
90
113
  - lib/lm_docstache/version.rb
91
114
  - lm_docstache.gemspec
115
+ - spec/conditional_block_spec.rb
92
116
  - spec/data_scope_spec.rb
93
117
  - spec/empty_data_scope_spec.rb
94
118
  - spec/example_input/ExampleTemplate.docx
119
+ - spec/example_input/blank.docx
95
120
  - spec/example_input/word/document.xml
96
121
  - spec/integration_spec.rb
97
122
  - spec/spec_helper.rb
@@ -120,9 +145,11 @@ signing_key:
120
145
  specification_version: 4
121
146
  summary: Merges Hash of Data into Word docx template files using mustache syntax
122
147
  test_files:
148
+ - spec/conditional_block_spec.rb
123
149
  - spec/data_scope_spec.rb
124
150
  - spec/empty_data_scope_spec.rb
125
151
  - spec/example_input/ExampleTemplate.docx
152
+ - spec/example_input/blank.docx
126
153
  - spec/example_input/word/document.xml
127
154
  - spec/integration_spec.rb
128
155
  - spec/spec_helper.rb