axlsx_report 0.1.1
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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +55 -0
- data/Rakefile +6 -0
- data/axlsx_report-0.1.0.gem +0 -0
- data/axlsx_report.gemspec +24 -0
- data/examples/base_report.rb +21 -0
- data/examples/group_report.rb +28 -0
- data/examples/lambda_report.rb +18 -0
- data/examples/width_report.rb +21 -0
- data/lib/axlsx_report.rb +5 -0
- data/lib/axlsx_report/base.rb +355 -0
- data/lib/axlsx_report/column.rb +55 -0
- data/lib/axlsx_report/column_name_conv.rb +32 -0
- data/lib/axlsx_report/group.rb +11 -0
- data/lib/axlsx_report/version.rb +3 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 45c3786fefc932fcf9f891788cfb0c3fcf4d885d
|
4
|
+
data.tar.gz: 681f81d9bb9042f0da7200688d80229e368574d1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9b7f8e17f841fce40e7d0fdf4f687e9b3ffbd8583a3846eb0ce456ef1e8929ddf2ae536a45ba768cf87bce7f0df5174dd20e1a06677c5b36b28cfdf55e9fe837
|
7
|
+
data.tar.gz: 7f973dd5e6d1a139e46274c2570c8ac89990f6255c45067d146689e3fda510a9d34a6d4584c3e8ed64254687ae39384abf55e9e87834bef64e3fb67d47fa05de
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Alexey Volochnev
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# AxlsxReport
|
2
|
+
|
3
|
+
Declarative excel reports based on [axlsx](https://github.com/randym/axlsx).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'axlsx_report'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install axlsx_report
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'axlsx_report'
|
25
|
+
|
26
|
+
class BaseReport < AxlsxReport::Base
|
27
|
+
column 'Number' do |i|
|
28
|
+
i
|
29
|
+
end
|
30
|
+
|
31
|
+
column 'Square' do |i|
|
32
|
+
i * i
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
report = BaseReport.new
|
37
|
+
|
38
|
+
(1..10).each do |i|
|
39
|
+
report << i
|
40
|
+
end
|
41
|
+
|
42
|
+
report.save('base_report.xlsx')
|
43
|
+
```
|
44
|
+
|
45
|
+
Find more examples in examples folder.
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/avolochnev/axlsx_report.
|
50
|
+
|
51
|
+
|
52
|
+
## License
|
53
|
+
|
54
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
55
|
+
|
data/Rakefile
ADDED
Binary file
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'axlsx_report/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "axlsx_report"
|
8
|
+
spec.version = AxlsxReport::VERSION
|
9
|
+
spec.authors = ["Alexey Volochnev"]
|
10
|
+
spec.email = ["alexey.volochnev@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Declarative excel reports based on axlsx.}
|
13
|
+
spec.homepage = "https://github.com/avolochnev/axlsx_report"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_runtime_dependency 'axlsx', '~> 2.0', '>= 2.0.1'
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
22
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
23
|
+
spec.add_development_dependency 'roo', '~> 1.13', '>= 1.13.2'
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
|
2
|
+
|
3
|
+
require 'axlsx_report'
|
4
|
+
|
5
|
+
class BaseReport < AxlsxReport::Base
|
6
|
+
column 'Integer' do |i|
|
7
|
+
i
|
8
|
+
end
|
9
|
+
|
10
|
+
column 'Square' do |i|
|
11
|
+
i * i
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if __FILE__ == $0
|
16
|
+
report = BaseReport.new
|
17
|
+
(1..10).each { |i| report << i }
|
18
|
+
report.save('base_report.xlsx')
|
19
|
+
end
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
|
2
|
+
|
3
|
+
require 'axlsx_report'
|
4
|
+
|
5
|
+
class GroupReport < AxlsxReport::Base
|
6
|
+
column 'Integer' do |i|
|
7
|
+
i
|
8
|
+
end
|
9
|
+
|
10
|
+
# Define group of columns with merged group header
|
11
|
+
group 'Calculations' do
|
12
|
+
column 'Square' do |i|
|
13
|
+
i * i
|
14
|
+
end
|
15
|
+
|
16
|
+
column 'Sqrt' do |i|
|
17
|
+
Math.sqrt(i)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
if __FILE__ == $0
|
23
|
+
report = GroupReport.new
|
24
|
+
(1..10).each { |i| report << i }
|
25
|
+
report.save('group_report.xlsx')
|
26
|
+
end
|
27
|
+
|
28
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
|
2
|
+
|
3
|
+
require 'axlsx_report'
|
4
|
+
|
5
|
+
class LambdaReport < AxlsxReport::Base
|
6
|
+
# proc without params called in context of given object
|
7
|
+
column 'Integer', -> { self }
|
8
|
+
column 'Odd?', -> { odd? }, width: 7
|
9
|
+
column 'Even?', -> { even? }, width: 7
|
10
|
+
end
|
11
|
+
|
12
|
+
if __FILE__ == $0
|
13
|
+
report = LambdaReport.new
|
14
|
+
(1..10).each { |i| report << i }
|
15
|
+
report.save('lambda_report.xlsx')
|
16
|
+
end
|
17
|
+
|
18
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
|
2
|
+
|
3
|
+
require 'axlsx_report'
|
4
|
+
|
5
|
+
class WidthReport < AxlsxReport::Base
|
6
|
+
column 'Integer', width: 20 do |i|
|
7
|
+
i
|
8
|
+
end
|
9
|
+
|
10
|
+
column 'Square', width: 20 do |i|
|
11
|
+
i * i
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if __FILE__ == $0
|
16
|
+
report = WidthReport.new
|
17
|
+
(1..10).each { |i| report << i }
|
18
|
+
report.save('width_report.xlsx')
|
19
|
+
end
|
20
|
+
|
21
|
+
|
data/lib/axlsx_report.rb
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
require 'axlsx'
|
2
|
+
require_relative 'column_name_conv'
|
3
|
+
require_relative 'group'
|
4
|
+
require_relative 'column'
|
5
|
+
|
6
|
+
module AxlsxReport
|
7
|
+
class Base
|
8
|
+
include ColumnNameConv
|
9
|
+
|
10
|
+
# Creates new report.
|
11
|
+
# By default creates own Axlsx::Package.
|
12
|
+
# @param [Axlsx::Package] package - provide existing package to add report sheet to combined document.
|
13
|
+
def initialize(package = nil)
|
14
|
+
@package = package || Axlsx::Package.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# Define report column.
|
18
|
+
#
|
19
|
+
# Usage:
|
20
|
+
# Human = Struct.new(:first_name, :last_name, :birthday)
|
21
|
+
# class Report < AxlsxReport::Base
|
22
|
+
# # calculate column value with block:
|
23
|
+
# column 'First Name', width: 10 do |human|
|
24
|
+
# human.first_name
|
25
|
+
# end
|
26
|
+
# # or lambda in context of given object
|
27
|
+
# column 'Last Name', -> { last_name }, width: 15
|
28
|
+
# # or chagne context using with:
|
29
|
+
# column 'Age', -> { Date.today.year - year }, with: :birthday
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @see Column#initialize for options description
|
33
|
+
#
|
34
|
+
# @param [String] name - column header
|
35
|
+
# @param [Proc] callable - use proc or lambda instead of block to get cell value. Optional.
|
36
|
+
# @param [Hash] options - last param,
|
37
|
+
# @param block
|
38
|
+
# args: callable = nil, options = {}
|
39
|
+
def self.column(col_name, *args, &block)
|
40
|
+
@columns ||= []
|
41
|
+
options =
|
42
|
+
if args.last.is_a? Hash
|
43
|
+
args.pop
|
44
|
+
else
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
options[:group] = @current_group if @current_group
|
48
|
+
callable = args.first || block
|
49
|
+
@columns << Column.new(col_name, callable, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Define group of columns
|
53
|
+
#
|
54
|
+
# Usage:
|
55
|
+
# Human = Struct.new(:first_name, :last_name, :birthday)
|
56
|
+
# class Report < AxlsxReport::Base
|
57
|
+
# group 'Name' do
|
58
|
+
# column 'First', &:first_name
|
59
|
+
# column 'Last', &:last_name
|
60
|
+
# end
|
61
|
+
# column 'Birthday', &:birthday
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# @param [String] name group name
|
65
|
+
def self.group(name, &block)
|
66
|
+
@columns ||= []
|
67
|
+
unless @groups
|
68
|
+
@groups = []
|
69
|
+
define_method :groups do
|
70
|
+
self.class.instance_eval { @groups }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
raise 'Nested groups are not implemented yet' if @current_group
|
74
|
+
@current_group = Group.new(name, @columns.length)
|
75
|
+
@groups << @current_group
|
76
|
+
instance_exec(&block)
|
77
|
+
@current_group.end_index = @columns.length - 1
|
78
|
+
@current_group = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
# Define group row height.
|
82
|
+
# Used if any groups are defiend. Autoheight is used by default
|
83
|
+
# @param [Integer] num group row height
|
84
|
+
def self.group_height(num)
|
85
|
+
define_method(:group_height) { num }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Define head row height.
|
89
|
+
# Autoheight is used by default
|
90
|
+
# @param [Integer] num head row height
|
91
|
+
def self.head_height(num)
|
92
|
+
define_method(:head_height) { num }
|
93
|
+
end
|
94
|
+
|
95
|
+
# Add 1,2,3...N row below head row
|
96
|
+
def self.numerate_columns
|
97
|
+
define_method(:numerate_columns) { true }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Define sheet name.
|
101
|
+
#
|
102
|
+
# @param [String] name (nil) static sheet name.
|
103
|
+
# @param block dinamic sheet name (if given)
|
104
|
+
def self.sheet_name(name = nil, &block)
|
105
|
+
if block_given?
|
106
|
+
define_method(:sheet_name, &block)
|
107
|
+
else
|
108
|
+
define_method(:sheet_name) { name }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Add row to the report.
|
113
|
+
# @param [Any] obj object providing the data for columns
|
114
|
+
def <<(obj)
|
115
|
+
row = columns.map { |column| column.value(self, obj) }
|
116
|
+
add_totals row
|
117
|
+
sheet.add_row row, row_options
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns sheet name.
|
121
|
+
#
|
122
|
+
# May be overriden to provide custom sheet name
|
123
|
+
# @see Base.sheet_name
|
124
|
+
def sheet_name
|
125
|
+
'Sheet1'
|
126
|
+
end
|
127
|
+
|
128
|
+
# def run(file = nil, enum = nil)
|
129
|
+
# file = file_name unless file
|
130
|
+
# enum = enumerator if enum.nil? && !block_given?
|
131
|
+
# if enum
|
132
|
+
# enum.each do |obj|
|
133
|
+
# self << obj
|
134
|
+
# end
|
135
|
+
# elsif block_given?
|
136
|
+
# yield self
|
137
|
+
# else
|
138
|
+
# raise 'No block or enum given. Use #new -> << -> #save for generating report later'
|
139
|
+
# end
|
140
|
+
# save(file)
|
141
|
+
# end
|
142
|
+
|
143
|
+
# def self.[](*args)
|
144
|
+
# new.run(*args)
|
145
|
+
# end
|
146
|
+
|
147
|
+
# Save xlsx file with provided data
|
148
|
+
# @param [String] file File name the data to be serialized
|
149
|
+
def save(file)
|
150
|
+
done
|
151
|
+
begin
|
152
|
+
@package.serialize(file)
|
153
|
+
rescue Errno::EACCES => e
|
154
|
+
puts "#{file} is protected!"
|
155
|
+
file = file + ".tmp"
|
156
|
+
@package.serialize(file)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Finalize document formatting after data collecting and before saving.
|
161
|
+
# @note Called during #save.
|
162
|
+
# @note Don't call twice. Don't call before all data provided.
|
163
|
+
def done
|
164
|
+
apply_width(sheet)
|
165
|
+
merge_same(sheet)
|
166
|
+
sheet.add_row(@totals) if @totals
|
167
|
+
end
|
168
|
+
|
169
|
+
# Axlsx sheet.
|
170
|
+
# @return [Axlsx::Worksheet]
|
171
|
+
def sheet
|
172
|
+
@sheet ||= init_sheet(sheet_name)
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def columns
|
178
|
+
self.class.instance_eval { @columns }
|
179
|
+
end
|
180
|
+
|
181
|
+
def init_sheet(name = 'Sheet1')
|
182
|
+
sheet = @package.workbook.add_worksheet(:name => sheetify_name(name))
|
183
|
+
if self.respond_to? :title
|
184
|
+
if title.is_a? Array
|
185
|
+
title.each do |t|
|
186
|
+
sheet.add_row [t]
|
187
|
+
end
|
188
|
+
else
|
189
|
+
sheet.add_row [title]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
if defined? groups
|
193
|
+
add_group_head sheet
|
194
|
+
else
|
195
|
+
add_head sheet, *columns.map{ |col| eval_name(col.name) }
|
196
|
+
sheet.rows.last.height = head_height if defined? head_height
|
197
|
+
end
|
198
|
+
if defined? numerate_columns
|
199
|
+
add_frozen_head sheet, *(1..(columns.length)).to_a
|
200
|
+
else
|
201
|
+
froze(sheet, sheet.rows.count)
|
202
|
+
auto_filter(sheet)
|
203
|
+
end
|
204
|
+
@header_rows = sheet.rows.count
|
205
|
+
sheet
|
206
|
+
end
|
207
|
+
|
208
|
+
def auto_filter(sheet)
|
209
|
+
sheet.auto_filter = Axlsx::cell_range(sheet.rows.last.cells, false)
|
210
|
+
end
|
211
|
+
|
212
|
+
def apply_width(sheet)
|
213
|
+
sheet.column_widths *columns.map(&:width).map { |width| width || default_column_width }
|
214
|
+
end
|
215
|
+
|
216
|
+
def merge_same(sheet)
|
217
|
+
columns.map(&:options).each_with_index do |ops, col_num|
|
218
|
+
next unless ops[:merge_same]
|
219
|
+
merged_style ||= sheet.styles.add_style(:alignment => { :vertical => :center })
|
220
|
+
col_char = column_num_to_name(col_num + 1)
|
221
|
+
last, start_row = nil, 0
|
222
|
+
sheet.rows.dup.each_with_index do |row, row_num|
|
223
|
+
next if row_num < @header_rows
|
224
|
+
col_value = row.cells[col_num].value
|
225
|
+
if last && last != col_value && (row_num - start_row) > 1
|
226
|
+
range = "#{col_char}#{start_row + 1}:#{col_char}#{row_num}"
|
227
|
+
sheet.merge_cells(range)
|
228
|
+
sheet[range].each { |c| c.style = merged_style }
|
229
|
+
end
|
230
|
+
if last != col_value
|
231
|
+
last = col_value
|
232
|
+
start_row = row_num
|
233
|
+
end
|
234
|
+
end
|
235
|
+
if last && (sheet.rows.length - start_row) > 1
|
236
|
+
range = "#{col_char}#{start_row + 1}:#{col_char}#{sheet.rows.length}"
|
237
|
+
sheet.merge_cells(range)
|
238
|
+
sheet[range].each { |c| c.style = merged_style }
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def default_column_width
|
244
|
+
16
|
245
|
+
end
|
246
|
+
|
247
|
+
def add_group_head(sheet)
|
248
|
+
head_row = *columns.map{ |col| eval_name(col.name) }
|
249
|
+
groups.each { |g| head_row[g.start_index] = g.name }
|
250
|
+
add_head sheet, *head_row
|
251
|
+
group_row = sheet.rows.length
|
252
|
+
groups.map { |g| "#{column_num_to_name(g.start_index + 1)}#{group_row}:#{column_num_to_name(g.end_index + 1)}#{group_row}" }
|
253
|
+
.each { |rng| sheet.merge_cells(rng) }
|
254
|
+
sheet.rows.last.height = group_height if defined? group_height
|
255
|
+
add_head sheet, *columns.map{ |col| eval_name(col.name) }
|
256
|
+
sheet.rows.last.height = head_height if defined? head_height
|
257
|
+
names_row = sheet.rows.length
|
258
|
+
units = *columns.map(&:units)
|
259
|
+
has_units = !units.all?(&:nil?)
|
260
|
+
if has_units
|
261
|
+
add_head sheet, *units
|
262
|
+
end
|
263
|
+
units_row = sheet.rows.length
|
264
|
+
columns.map(&:options).each_with_index do |ops, index|
|
265
|
+
row_name = column_num_to_name(index + 1)
|
266
|
+
case
|
267
|
+
when ops[:units] && ops[:group]
|
268
|
+
nil
|
269
|
+
when ops[:group]
|
270
|
+
sheet.merge_cells("#{row_name}#{names_row}:#{row_name}#{units_row}") if has_units
|
271
|
+
when ops[:units]
|
272
|
+
sheet.merge_cells("#{row_name}#{group_row}:#{row_name}#{names_row}")
|
273
|
+
else
|
274
|
+
sheet.merge_cells("#{row_name}#{group_row}:#{row_name}#{units_row}")
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def add_totals(row)
|
280
|
+
return unless total_actions
|
281
|
+
@totals ||= [nil] * columns.length
|
282
|
+
total_actions.each_with_index do |action, index|
|
283
|
+
case action
|
284
|
+
when String
|
285
|
+
@totals[index] = action
|
286
|
+
when :sum
|
287
|
+
if row[index]
|
288
|
+
@totals[index] ||= 0.0
|
289
|
+
@totals[index] += row[index]
|
290
|
+
end
|
291
|
+
when :count
|
292
|
+
if row[index]
|
293
|
+
@totals[index] ||= 0
|
294
|
+
@totals[index] += 1
|
295
|
+
end
|
296
|
+
when Proc
|
297
|
+
@totals[index] = action[row]
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def total_actions
|
303
|
+
@total_actions ||=
|
304
|
+
columns.map(&:options).each do |ops|
|
305
|
+
ops[:total]
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def eval_name(name)
|
310
|
+
name.is_a?(Proc) ? instance_exec(&name) : name
|
311
|
+
end
|
312
|
+
|
313
|
+
def sheetify_name(name)
|
314
|
+
name.gsub('/', '_')[0..30]
|
315
|
+
end
|
316
|
+
|
317
|
+
def add_head(sheet, *header)
|
318
|
+
row = nil
|
319
|
+
sheet.workbook.styles do |s|
|
320
|
+
head_style = s.add_style :border => { :style => :thin, :color => "00" },
|
321
|
+
:alignment => { :horizontal => :center,
|
322
|
+
:vertical => :center ,
|
323
|
+
:wrap_text => true}
|
324
|
+
row = sheet.add_row header, style: head_style
|
325
|
+
end
|
326
|
+
row
|
327
|
+
end
|
328
|
+
|
329
|
+
def add_frozen_head(sheet, *header)
|
330
|
+
row = add_head(sheet, *header)
|
331
|
+
froze(sheet, sheet.rows.count)
|
332
|
+
sheet.auto_filter = Axlsx::cell_range(row.cells, false)
|
333
|
+
end
|
334
|
+
|
335
|
+
def separate_sheet(sheet, sheet_name = 'Sheet2', &block)
|
336
|
+
sheet.workbook.add_worksheet(name: sheet_name) { |sheet| yield sheet }
|
337
|
+
end
|
338
|
+
|
339
|
+
def froze(sheet, num_rows = 2)
|
340
|
+
sheet.sheet_view do |vs|
|
341
|
+
vs.pane do |pane|
|
342
|
+
pane.state = :frozen
|
343
|
+
pane.y_split = num_rows
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def row_options
|
349
|
+
options = {}
|
350
|
+
types = columns.map { |c| c.options[:type] }
|
351
|
+
options[:types] = types unless types.all?(&:nil?)
|
352
|
+
options
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module AxlsxReport
|
2
|
+
# Report's column representation
|
3
|
+
class Column
|
4
|
+
attr_reader :name, :callable, :options
|
5
|
+
|
6
|
+
# Creates new column
|
7
|
+
#
|
8
|
+
# @param [String] name Column name
|
9
|
+
# @param [Proc] callable Proc to calculate column value form given object
|
10
|
+
# @param [Hash] options ({}) Column parameters
|
11
|
+
# @option options [Integer] width: (16) column width
|
12
|
+
# @option options [Symbol] transform: report object method to be called after value calculation
|
13
|
+
# @option options [Symbol] with: method name of given object to be used to base for cell value calculation
|
14
|
+
# @option options [String] units: Units text to be added to the header in the column
|
15
|
+
# @option options [Symbol] type: axlsx type to be used in this column. One of Axlsx::Cell::CELL_TYPES
|
16
|
+
def initialize(name, callable, options = {})
|
17
|
+
@name, @callable, @options = name, callable, options
|
18
|
+
end
|
19
|
+
|
20
|
+
# Quick access to options
|
21
|
+
%i{width units}.each do |option|
|
22
|
+
define_method(option) do
|
23
|
+
@options[option]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Calculates column value for given object
|
28
|
+
#
|
29
|
+
# @param [AxlsxReport::Base] report Report the value is calculated for
|
30
|
+
# @param [Any] obj Source object for column value
|
31
|
+
# @return Column value for given object.
|
32
|
+
def value(report, obj)
|
33
|
+
source = extract_source(obj)
|
34
|
+
return nil if source.nil?
|
35
|
+
value =
|
36
|
+
if callable.arity.zero?
|
37
|
+
source.instance_exec &callable
|
38
|
+
else
|
39
|
+
report.instance_exec source, &callable
|
40
|
+
end
|
41
|
+
transform = options[:transform]
|
42
|
+
value = report.send transform, value if transform
|
43
|
+
value
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def extract_source(obj)
|
49
|
+
with = options[:with]
|
50
|
+
return obj unless with
|
51
|
+
return nil unless obj.respond_to?(with)
|
52
|
+
obj.send(with)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module AxlsxReport
|
2
|
+
module ColumnNameConv
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# 'Z' => 25
|
6
|
+
# 'AA' => 26
|
7
|
+
def column_name_to_num(name)
|
8
|
+
num = 0
|
9
|
+
name.upcase.each_char do |c|
|
10
|
+
num *= 26 if num > 0
|
11
|
+
add = c.ord - "A".ord + 1
|
12
|
+
raise "Invalid symbol in Excel column name: '#{c}'" if add < 1 || add > 26
|
13
|
+
num += add
|
14
|
+
end
|
15
|
+
num - 1
|
16
|
+
end
|
17
|
+
|
18
|
+
# 1 => 'А'
|
19
|
+
# 26 => 'Z'
|
20
|
+
# 27 => 'AA'
|
21
|
+
# 28 => 'АB'
|
22
|
+
def column_num_to_name(num)
|
23
|
+
name = ""
|
24
|
+
while num > 0
|
25
|
+
nm = (num - 1) % 26
|
26
|
+
num = (num - 1) / 26
|
27
|
+
name << ("A".ord + nm).chr
|
28
|
+
end
|
29
|
+
name.reverse
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: axlsx_report
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexey Volochnev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: axlsx
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.0.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: bundler
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.11'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.11'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '10.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '10.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: roo
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.13'
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 1.13.2
|
85
|
+
type: :development
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '1.13'
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 1.13.2
|
95
|
+
description:
|
96
|
+
email:
|
97
|
+
- alexey.volochnev@gmail.com
|
98
|
+
executables: []
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- ".gitignore"
|
103
|
+
- ".rspec"
|
104
|
+
- Gemfile
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- Rakefile
|
108
|
+
- axlsx_report-0.1.0.gem
|
109
|
+
- axlsx_report.gemspec
|
110
|
+
- examples/base_report.rb
|
111
|
+
- examples/group_report.rb
|
112
|
+
- examples/lambda_report.rb
|
113
|
+
- examples/width_report.rb
|
114
|
+
- lib/axlsx_report.rb
|
115
|
+
- lib/axlsx_report/base.rb
|
116
|
+
- lib/axlsx_report/column.rb
|
117
|
+
- lib/axlsx_report/column_name_conv.rb
|
118
|
+
- lib/axlsx_report/group.rb
|
119
|
+
- lib/axlsx_report/version.rb
|
120
|
+
homepage: https://github.com/avolochnev/axlsx_report
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.2.5
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: Declarative excel reports based on axlsx.
|
144
|
+
test_files: []
|