decisive 0.6.2 → 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: 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