decisive 0.7.1 → 0.8.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
  SHA256:
3
- metadata.gz: 9ca9f98c729ce8368e606d9e9d83da4e2a574807af3490c3f73a7203950ce585
4
- data.tar.gz: 9cf78cb8f9720de00574f0171bf77324862a262646d8f3d40b5c7f36187d2d0c
3
+ metadata.gz: 396540579e82830745c22839f173f3dcf7886929cef16c023bd85fe2e8eff1e9
4
+ data.tar.gz: 49ed399702320635522c2640a52bc9b65b0563311371a0e2591dafce2b5a8865
5
5
  SHA512:
6
- metadata.gz: 2f3d41825e1c28901ff15d740de4c56e00e742a1e0080290fdb80cb367540fc6546c1be9d0c0aa747f27a1a3c6a3743123a6aa9b1c9a505243131acee7e53dbd
7
- data.tar.gz: 140f98ccc7ddbfaf046b4af54f87fa571b80c9cdf86a599b92c51c7724ebcdbeb9db46c3e3cb6b57f22422e2b06a911004f42f714d541ed0fee5718a71afb9f7
6
+ metadata.gz: 716dce8f3d55a83fed5494f9b033e2a59406758d2a948f023308758535e97075dc7a5c4e3bfe047602e6849dbeec567fc1b6939bc184c2b434528918710ca98f
7
+ data.tar.gz: ad89e1e17edf6fc83d6b44cdf78451d456cda767d62dde058129b06c352d43c03093c411a8fc08195eb165e1179be9b68947d1d552dacd4c37cc593ca832d8d2
@@ -0,0 +1,28 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ strategy:
6
+ fail-fast: false
7
+ matrix:
8
+ gemfile: [ rails_5.1, rails_5.2, rails_6.0, rails_6.1, rails_7.0 ]
9
+ ruby: [ 2.6, 2.7, '3.0' ]
10
+ exclude:
11
+ - gemfile: rails_5.1
12
+ ruby: 3.0
13
+ - gemfile: rails_5.2
14
+ ruby: 3.0
15
+ - gemfile: rails_7.0
16
+ ruby: 2.6
17
+
18
+ runs-on: ubuntu-latest
19
+ env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
20
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
21
+ steps:
22
+ - uses: actions/checkout@v2
23
+ - uses: ruby/setup-ruby@v1
24
+ with:
25
+ ruby-version: ${{ matrix.ruby }}
26
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
27
+ - run: bundle exec rake
28
+
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.6.3
1
+ ruby-2.7.3
data/Appraisals CHANGED
@@ -10,4 +10,11 @@ appraise "rails-6.0" do
10
10
  gem "actionview", "6.0"
11
11
  end
12
12
 
13
+ appraise "rails-6.1" do
14
+ gem "actionview", "6.1"
15
+ end
16
+
17
+ appraise "rails-7.0" do
18
+ gem "actionview", "7.0"
19
+ end
13
20
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  DSL for rendering and streaming CSVs in Rails apps
4
4
 
5
- [![Build Status](https://travis-ci.org/botandrose/decisive.svg?branch=master)](https://travis-ci.org/botandrose/decisive)
5
+ [![CI Status](https://github.com/botandrose/decisive/workflows/CI/badge.svg?branch=master)](https://github.com/botandrose/decisive/actions?query=workflow%3ACI+branch%3Amaster)
6
6
  [![Code Climate](https://codeclimate.com/github/botandrose/decisive/badges/gpa.svg)](https://codeclimate.com/github/botandrose/decisive)
7
7
 
8
8
  ## Usage
data/decisive.gemspec CHANGED
@@ -24,11 +24,10 @@ Gem::Specification.new do |spec|
24
24
  spec.require_paths = ["lib"]
25
25
 
26
26
  spec.add_dependency "actionview"
27
- spec.add_dependency "spreadsheet"
27
+ spec.add_dependency "rubyXL"
28
28
 
29
29
  spec.add_development_dependency "bundler"
30
30
  spec.add_development_dependency "appraisal"
31
31
  spec.add_development_dependency "rake"
32
32
  spec.add_development_dependency "rspec", "~> 3.0"
33
- spec.add_development_dependency "simple-spreadsheet"
34
33
  end
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "byebug"
6
+ gem "actionview", "6.1"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "byebug"
6
+ gem "actionview", "7.0"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,18 @@
1
+ require "csv"
2
+ require "active_support/core_ext/string/inflections"
3
+ require "decisive/renderer"
4
+
5
+ module Decisive
6
+ class RenderCSVContext < Struct.new(:records, :filename, :block)
7
+ def to_csv(*args, **kwargs)
8
+ Renderer.new(records, block).map do |row|
9
+ row.to_csv(*args, **kwargs)
10
+ end.join
11
+ end
12
+
13
+ def csv?
14
+ true
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,69 @@
1
+ require "rubyXL"
2
+ require "decisive/renderer"
3
+
4
+ module Decisive
5
+ class RenderXLSContext < Struct.new(:worksheets, :filename, :block)
6
+ class Worksheet < Struct.new(:records, :name, :block); end
7
+
8
+ def initialize *args
9
+ super
10
+
11
+ self.worksheets ||= []
12
+ if worksheets.none?
13
+ instance_eval &block
14
+
15
+ else
16
+ self.worksheets = worksheets.map do |name, records|
17
+ Worksheet.new(records, name, block)
18
+ end
19
+ end
20
+ end
21
+
22
+ def to_xls
23
+ workbook = RubyXL::Workbook.new
24
+ workbook.worksheets.pop # rm default worsheet
25
+ to_string(render(workbook))
26
+ end
27
+
28
+ def csv?
29
+ false
30
+ end
31
+
32
+ private
33
+
34
+ def worksheet records, name:, &block
35
+ worksheets.push Worksheet.new(records, name, block)
36
+ end
37
+
38
+ def render xls
39
+ worksheets.each do |worksheet|
40
+ sheet = xls.add_worksheet(sanitize_name(worksheet.name)).tap do |sheet|
41
+ Renderer.new(worksheet.records, worksheet.block).each.with_index do |row, row_index|
42
+ row.each.with_index do |cell, cell_index|
43
+ if cell[0] == "="
44
+ sheet.add_cell row_index, cell_index, nil, cell[1..]
45
+ else
46
+ sheet.add_cell row_index, cell_index, cell
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ xls
53
+ end
54
+
55
+ def sanitize_name name
56
+ name
57
+ .gsub(/[\[\]\*\?:\/\\\t\n\r]/, " ")
58
+ .gsub(/^'/, "")
59
+ .gsub(/'$/, "")
60
+ .strip
61
+ .slice(0,31)
62
+ end
63
+
64
+ def to_string xls
65
+ xls.stream.string
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,56 @@
1
+ module Decisive
2
+ class Renderer < Struct.new(:records, :block)
3
+ include Enumerable
4
+
5
+ def each &block
6
+ (header + body).each(&block)
7
+ end
8
+
9
+ private
10
+
11
+ def header
12
+ [keys]
13
+ end
14
+
15
+ def body
16
+ hashes.map do |hash|
17
+ hash.values_at(*keys)
18
+ end
19
+ end
20
+
21
+ def keys
22
+ @keys ||= hashes.flat_map(&:keys).uniq
23
+ end
24
+
25
+ def hashes
26
+ @hashes ||= records.map do |record|
27
+ Row.new(record, block).to_hash
28
+ end
29
+ end
30
+
31
+ class Row < Struct.new(:record, :block)
32
+ module Nothing; end
33
+
34
+ def to_hash
35
+ @hash = {}
36
+ instance_exec record, &block
37
+ @hash
38
+ end
39
+
40
+ private
41
+
42
+ def column key, value=Nothing, &block
43
+ @hash[key] = if block_given?
44
+ block.call(record)
45
+ elsif value.is_a?(Symbol)
46
+ record.send(value)
47
+ elsif value == Nothing
48
+ record.send(key.parameterize.underscore.to_sym)
49
+ else
50
+ value
51
+ end.to_s
52
+ end
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,44 @@
1
+ require "csv"
2
+ require "active_support/core_ext/string/inflections"
3
+
4
+ module Decisive
5
+ class StreamCSVContext < Struct.new(:records, :filename, :block)
6
+ class Column < Struct.new(:label, :block); end
7
+
8
+ def initialize *args
9
+ super
10
+ @columns = []
11
+ instance_eval &block
12
+ end
13
+
14
+ attr_reader :columns
15
+
16
+ def column label, value=nil, &block # field, label: field.to_s.humanize, &block
17
+ value ||= label.parameterize.underscore.to_sym
18
+ block ||= ->(record) { record.send(value) }
19
+ columns << Column.new(label, block)
20
+ end
21
+
22
+ def each
23
+ yield header
24
+
25
+ records.map do |record|
26
+ row = columns.map do |column|
27
+ column.block.call(record).to_s
28
+ end
29
+ yield row
30
+ end
31
+ end
32
+
33
+ def csv?
34
+ true
35
+ end
36
+
37
+ private
38
+
39
+ def header
40
+ columns.map(&:label)
41
+ end
42
+ end
43
+ end
44
+
@@ -1,7 +1,8 @@
1
- require "csv"
2
1
  require "action_view"
3
- require "active_support/core_ext/string/inflections"
4
- require "spreadsheet"
2
+
3
+ require "decisive/stream_csv_context"
4
+ require "decisive/render_csv_context"
5
+ require "decisive/render_xls_context"
5
6
 
6
7
  module Decisive
7
8
  class TemplateHandler
@@ -33,7 +34,7 @@ module Decisive
33
34
  end
34
35
 
35
36
  else
36
- response.headers["Content-Type"] = "application/vnd.ms-excel"
37
+ response.headers["Content-Type"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
37
38
  context.to_xls
38
39
  end
39
40
  RUBY
@@ -57,220 +58,14 @@ module Decisive
57
58
  if stream
58
59
  raise StreamingNotEnabledByControllerError unless controller.is_a?(ActionController::Live)
59
60
  raise StreamIncompatibleBlockArgumentError if block.arity != 0
60
- StreamContext.new([], records, filename, &block)
61
+ StreamCSVContext.new(records, filename, block)
61
62
  else
62
- RenderContext.new(records, filename, block)
63
+ RenderCSVContext.new(records, filename, block)
63
64
  end
64
65
  end
65
66
 
66
67
  def xls worksheets=nil, filename:, &block
67
- if worksheets
68
- XLSContext.new(worksheets, filename, block)
69
- else
70
- XLSWithWorksheetsContext.new(filename, [], &block)
71
- end
72
- end
73
- end
74
-
75
- class XLSWithWorksheetsContext < Struct.new(:filename, :worksheets)
76
- class Worksheet < Struct.new(:records, :name, :block); end
77
-
78
- def initialize *args, &block
79
- super
80
- instance_eval &block
81
- end
82
-
83
- def to_xls
84
- to_string(render(Spreadsheet::Workbook.new))
85
- end
86
-
87
- def csv?
88
- false
89
- end
90
-
91
- private
92
-
93
- def worksheet records, name:, &block
94
- worksheets.push Worksheet.new(records, name, block)
95
- end
96
-
97
- def render xls
98
- worksheets.each do |worksheet|
99
- sheet = xls.create_worksheet(name: sanitize_name(worksheet.name))
100
-
101
- rows = to_array(worksheet)
102
-
103
- rows.each.with_index do |row, index|
104
- sheet.row(index).concat row
105
- end
106
- end
107
- xls
108
- end
109
-
110
- def sanitize_name name
111
- name
112
- .gsub(/[\[\]\*\?:\/\\\t\n\r]/, " ")
113
- .gsub(/^'/, "")
114
- .gsub(/'$/, "")
115
- .strip
116
- .slice(0,31)
117
- end
118
-
119
- def to_array worksheet
120
- context = RenderContext.new(worksheet.records, nil, worksheet.block)
121
- context.send(:header) + context.send(:body)
122
- end
123
-
124
- def to_string xls
125
- io = StringIO.new
126
- xls.write(io)
127
- io.rewind
128
- string = io.read
129
- string.force_encoding(Encoding::ASCII_8BIT)
130
- string
131
- end
132
- end
133
-
134
-
135
- class XLSContext < Struct.new(:worksheets, :filename, :block)
136
- def to_xls
137
- to_string(render(Spreadsheet::Workbook.new))
138
- end
139
-
140
- def csv?
141
- false
142
- end
143
-
144
- private
145
-
146
- def render xls
147
- worksheets.each do |name, enumerable|
148
- sheet = xls.create_worksheet(name: sanitize_name(name))
149
-
150
- rows = to_array(enumerable)
151
-
152
- rows.each.with_index do |row, index|
153
- sheet.row(index).concat row
154
- end
155
- end
156
- xls
157
- end
158
-
159
- def sanitize_name name
160
- name
161
- .gsub(/[\[\]\*\?:\/\\\t\n\r]/, " ")
162
- .gsub(/^'/, "")
163
- .gsub(/'$/, "")
164
- .strip
165
- .slice(0,31)
166
- end
167
-
168
- def to_array records
169
- context = RenderContext.new(records, nil, block)
170
- context.send(:header) + context.send(:body)
171
- end
172
-
173
- def to_string xls
174
- io = StringIO.new
175
- xls.write(io)
176
- io.rewind
177
- string = io.read
178
- string.force_encoding(Encoding::ASCII_8BIT)
179
- string
180
- end
181
- end
182
-
183
- class StreamContext < Struct.new(:columns, :records, :filename)
184
- class Column < Struct.new(:label, :block); end
185
-
186
- def initialize *args, &block
187
- super
188
- instance_eval &block
189
- end
190
-
191
- def column label, value=nil, &block # field, label: field.to_s.humanize, &block
192
- value ||= label.parameterize.underscore.to_sym
193
- block ||= ->(record) { record.send(value) }
194
- columns << Column.new(label, block)
195
- end
196
-
197
- def each
198
- yield header
199
-
200
- records.map do |record|
201
- row = columns.map do |column|
202
- column.block.call(record).to_s
203
- end
204
- yield row
205
- end
206
- end
207
-
208
- def csv?
209
- true
210
- end
211
-
212
- private
213
-
214
- def header
215
- columns.map(&:label)
216
- end
217
- end
218
-
219
- class RenderContext < Struct.new(:records, :filename, :block)
220
- def to_csv(*args, **kwargs)
221
- (header + body).map do |row|
222
- row.to_csv(*args, **kwargs)
223
- end.join
224
- end
225
-
226
- def csv?
227
- true
228
- end
229
-
230
- private
231
-
232
- def header
233
- [keys]
234
- end
235
-
236
- def body
237
- hashes.map do |hash|
238
- hash.values_at(*keys)
239
- end
240
- end
241
-
242
- def keys
243
- @keys ||= hashes.flat_map(&:keys).uniq
244
- end
245
-
246
- def hashes
247
- @hashes ||= records.map do |record|
248
- Row.new(record, block).to_hash
249
- end
250
- end
251
-
252
- class Row < Struct.new(:record, :block)
253
- module Nothing; end
254
-
255
- def to_hash
256
- @hash = {}
257
- instance_exec record, &block
258
- @hash
259
- end
260
-
261
- private
262
-
263
- def column key, value=Nothing, &block
264
- @hash[key] = if block_given?
265
- block.call(record)
266
- elsif value.is_a?(Symbol)
267
- record.send(value)
268
- elsif value == Nothing
269
- record.send(key.parameterize.underscore.to_sym)
270
- else
271
- value
272
- end.to_s
273
- end
68
+ RenderXLSContext.new(worksheets, filename, block)
274
69
  end
275
70
  end
276
71
  end
@@ -1,3 +1,3 @@
1
1
  module Decisive
2
- VERSION = "0.7.1"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -0,0 +1,24 @@
1
+ require "rubyXL"
2
+
3
+ module Decisive
4
+ class XLSHasher < Struct.new(:path)
5
+ def to_hash
6
+ spreadsheet = RubyXL::Parser.parse(path)
7
+ spreadsheet.worksheets.reduce({}) do |hash, worksheet|
8
+ actual = []
9
+ worksheet.each do |row|
10
+ cells = row.cells.map do |cell|
11
+ if cell.formula
12
+ "=" + cell.formula.expression
13
+ else
14
+ cell.value
15
+ end
16
+ end
17
+ actual << cells
18
+ end
19
+ hash.merge worksheet.sheet_name => actual
20
+ end
21
+ end
22
+ end
23
+ end
24
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decisive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-06 00:00:00.000000000 Z
11
+ date: 2022-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: spreadsheet
28
+ name: rubyXL
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -94,20 +94,6 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '3.0'
97
- - !ruby/object:Gem::Dependency
98
- name: simple-spreadsheet
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
97
  description: DSL for rendering CSVs from an array of objects
112
98
  email:
113
99
  - micah@botandrose.com
@@ -115,11 +101,11 @@ executables: []
115
101
  extensions: []
116
102
  extra_rdoc_files: []
117
103
  files:
104
+ - ".github/workflows/ci.yml"
118
105
  - ".gitignore"
119
106
  - ".rspec"
120
107
  - ".ruby-gemset"
121
108
  - ".ruby-version"
122
- - ".travis.yml"
123
109
  - Appraisals
124
110
  - Gemfile
125
111
  - LICENSE.txt
@@ -132,9 +118,16 @@ files:
132
118
  - gemfiles/rails_5.1.gemfile
133
119
  - gemfiles/rails_5.2.gemfile
134
120
  - gemfiles/rails_6.0.gemfile
121
+ - gemfiles/rails_6.1.gemfile
122
+ - gemfiles/rails_7.0.gemfile
135
123
  - lib/decisive.rb
124
+ - lib/decisive/render_csv_context.rb
125
+ - lib/decisive/render_xls_context.rb
126
+ - lib/decisive/renderer.rb
127
+ - lib/decisive/stream_csv_context.rb
136
128
  - lib/decisive/template_handler.rb
137
129
  - lib/decisive/version.rb
130
+ - lib/decisive/xls_hasher.rb
138
131
  homepage: https://github.com/botandrose/decisive
139
132
  licenses:
140
133
  - MIT
@@ -154,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
147
  - !ruby/object:Gem::Version
155
148
  version: '0'
156
149
  requirements: []
157
- rubygems_version: 3.0.8
150
+ rubygems_version: 3.1.6
158
151
  signing_key:
159
152
  specification_version: 4
160
153
  summary: DSL for rendering CSVs
data/.travis.yml DELETED
@@ -1,24 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- rvm:
5
- - 2.4
6
- - 2.5
7
- - 2.6
8
-
9
- before_install:
10
- - gem install -v 2.0.2 bundler
11
-
12
- gemfile:
13
- - gemfiles/rails_5.1.gemfile
14
- - gemfiles/rails_5.2.gemfile
15
- - gemfiles/rails_6.0.gemfile
16
-
17
- matrix:
18
- exclude:
19
- - rvm: 2.4
20
- gemfile: gemfiles/rails_6.0.gemfile
21
- - rvm: 2.5
22
- gemfile: gemfiles/rails_6.0.gemfile
23
-
24
- cache: bundler