decisive 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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