decisive 0.6.2 → 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: 07c8a861463920b0e9aff56cdfe755e8da42beb2b5f4a2c148dcf043a53da1d6
4
- data.tar.gz: 72a5f54dc4b0ac9d9fe374c8f37b0bd5fac1ca488a69231204dd4d8c8ead1522
3
+ metadata.gz: 396540579e82830745c22839f173f3dcf7886929cef16c023bd85fe2e8eff1e9
4
+ data.tar.gz: 49ed399702320635522c2640a52bc9b65b0563311371a0e2591dafce2b5a8865
5
5
  SHA512:
6
- metadata.gz: 5984a60b7c36675c3f84dca2eb057a5619fe0eba332842c745b0d850a3dba57fcb0c437cbcb405128054c92be2931cc77bf056fb528a810e467df53a98f74506
7
- data.tar.gz: 8bc44d8334b85209ec9b2d035f87e9bf2fbd21e5de52f31444f1c64f13df2980327e8a5175349bab6ed60892055a9295988545a4d78dd7cade8296b942fd53c8
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
@@ -11,8 +12,7 @@ module Decisive
11
12
 
12
13
  def self.call template, source=template.source
13
14
  <<~RUBY
14
- extend Decisive::DSL
15
- context = (#{source})
15
+ extend Decisive::DSL; context = (#{source})
16
16
 
17
17
  response.headers["Content-Transfer-Encoding"] = "binary"
18
18
  response.headers["Content-Disposition"] = %(attachment; filename="\#{context.filename}")
@@ -34,7 +34,7 @@ module Decisive
34
34
  end
35
35
 
36
36
  else
37
- response.headers["Content-Type"] = "application/vnd.ms-excel"
37
+ response.headers["Content-Type"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
38
38
  context.to_xls
39
39
  end
40
40
  RUBY
@@ -58,147 +58,14 @@ module Decisive
58
58
  if stream
59
59
  raise StreamingNotEnabledByControllerError unless controller.is_a?(ActionController::Live)
60
60
  raise StreamIncompatibleBlockArgumentError if block.arity != 0
61
- StreamContext.new([], records, filename, &block)
61
+ StreamCSVContext.new(records, filename, block)
62
62
  else
63
- RenderContext.new(records, filename, block)
64
- end
65
- end
66
-
67
- def xls worksheets, filename:, &block
68
- XLSContext.new(worksheets, filename, block)
69
- end
70
- end
71
-
72
- class XLSContext < Struct.new(:worksheets, :filename, :block)
73
- def to_xls
74
- to_string(render(Spreadsheet::Workbook.new))
75
- end
76
-
77
- def csv?
78
- false
79
- end
80
-
81
- private
82
-
83
- def render xls
84
- worksheets.each do |name, enumerable|
85
- sheet = xls.create_worksheet(name: name)
86
-
87
- rows = to_array(enumerable)
88
-
89
- rows.each.with_index do |row, index|
90
- sheet.row(index).concat row
91
- end
63
+ RenderCSVContext.new(records, filename, block)
92
64
  end
93
- xls
94
65
  end
95
66
 
96
- def to_array records
97
- context = RenderContext.new(records, nil, block)
98
- context.send(:header) + context.send(:body)
99
- end
100
-
101
- def to_string xls
102
- io = StringIO.new
103
- xls.write(io)
104
- io.rewind
105
- string = io.read
106
- string.force_encoding(Encoding::ASCII_8BIT)
107
- string
108
- end
109
- end
110
-
111
- class StreamContext < Struct.new(:columns, :records, :filename)
112
- class Column < Struct.new(:label, :block); end
113
-
114
- def initialize *args, &block
115
- super
116
- instance_eval &block
117
- end
118
-
119
- def column label, value=nil, &block # field, label: field.to_s.humanize, &block
120
- value ||= label.parameterize.underscore.to_sym
121
- block ||= ->(record) { record.send(value) }
122
- columns << Column.new(label, block)
123
- end
124
-
125
- def each
126
- yield header
127
-
128
- records.map do |record|
129
- row = columns.map do |column|
130
- column.block.call(record).to_s
131
- end
132
- yield row
133
- end
134
- end
135
-
136
- def csv?
137
- true
138
- end
139
-
140
- private
141
-
142
- def header
143
- columns.map(&:label)
144
- end
145
- end
146
-
147
- class RenderContext < Struct.new(:records, :filename, :block)
148
- def to_csv(*args, **kwargs)
149
- (header + body).map do |row|
150
- row.to_csv(*args, **kwargs)
151
- end.join
152
- end
153
-
154
- def csv?
155
- true
156
- end
157
-
158
- private
159
-
160
- def header
161
- [keys]
162
- end
163
-
164
- def body
165
- hashes.map do |hash|
166
- hash.values_at(*keys)
167
- end
168
- end
169
-
170
- def keys
171
- @keys ||= hashes.flat_map(&:keys).uniq
172
- end
173
-
174
- def hashes
175
- @hashes ||= records.map do |record|
176
- Row.new(record, block).to_hash
177
- end
178
- end
179
-
180
- class Row < Struct.new(:record, :block)
181
- module Nothing; end
182
-
183
- def to_hash
184
- @hash = {}
185
- instance_exec record, &block
186
- @hash
187
- end
188
-
189
- private
190
-
191
- def column key, value=Nothing, &block
192
- @hash[key] = if block_given?
193
- block.call(record)
194
- elsif value.is_a?(Symbol)
195
- record.send(value)
196
- elsif value == Nothing
197
- record.send(key.parameterize.underscore.to_sym)
198
- else
199
- value
200
- end.to_s
201
- end
67
+ def xls worksheets=nil, filename:, &block
68
+ RenderXLSContext.new(worksheets, filename, block)
202
69
  end
203
70
  end
204
71
  end
@@ -1,3 +1,3 @@
1
1
  module Decisive
2
- VERSION = "0.6.2"
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.6.2
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: 2020-05-11 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.3
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