excel_templating 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rubocop.hound.yml +261 -0
- data/.rubocop.ph.yml +44 -0
- data/.rubocop.yml +3 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +8 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +3 -0
- data/README.md +133 -0
- data/Rakefile +43 -0
- data/excel_templating.gemspec +32 -0
- data/lib/excel_templating/document/data_source_registry/registry_list.rb +48 -0
- data/lib/excel_templating/document/data_source_registry/registry_renderer.rb +74 -0
- data/lib/excel_templating/document/data_source_registry.rb +64 -0
- data/lib/excel_templating/document/sheet/repeated_row.rb +39 -0
- data/lib/excel_templating/document/sheet.rb +133 -0
- data/lib/excel_templating/document.rb +71 -0
- data/lib/excel_templating/document_dsl.rb +85 -0
- data/lib/excel_templating/excel_abstraction/active_cell_reference.rb +59 -0
- data/lib/excel_templating/excel_abstraction/cell.rb +23 -0
- data/lib/excel_templating/excel_abstraction/cell_range.rb +26 -0
- data/lib/excel_templating/excel_abstraction/cell_reference.rb +39 -0
- data/lib/excel_templating/excel_abstraction/date.rb +36 -0
- data/lib/excel_templating/excel_abstraction/row.rb +29 -0
- data/lib/excel_templating/excel_abstraction/sheet.rb +102 -0
- data/lib/excel_templating/excel_abstraction/spread_sheet.rb +28 -0
- data/lib/excel_templating/excel_abstraction/time.rb +42 -0
- data/lib/excel_templating/excel_abstraction/work_book.rb +47 -0
- data/lib/excel_templating/excel_abstraction.rb +16 -0
- data/lib/excel_templating/render_helper.rb +14 -0
- data/lib/excel_templating/renderer.rb +251 -0
- data/lib/excel_templating/rspec_excel_matcher.rb +129 -0
- data/lib/excel_templating/version.rb +4 -0
- data/lib/excel_templating.rb +4 -0
- data/spec/assets/alphalist_7_4.mustache.xlsx +0 -0
- data/spec/assets/alphalist_seven_four_expected.xlsx +0 -0
- data/spec/assets/valid_cell.mustache.xlsx +0 -0
- data/spec/assets/valid_cell_expected.xlsx +0 -0
- data/spec/assets/valid_cell_expected_inline.xlsx +0 -0
- data/spec/assets/valid_column_expected.xlsx +0 -0
- data/spec/cell_validation_spec.rb +114 -0
- data/spec/column_validation_spec.rb +47 -0
- data/spec/excel_abstraction/active_cell_reference_spec.rb +73 -0
- data/spec/excel_abstraction/cell_range_spec.rb +36 -0
- data/spec/excel_abstraction/cell_reference_spec.rb +69 -0
- data/spec/excel_abstraction/cell_spec.rb +54 -0
- data/spec/excel_abstraction/date_spec.rb +27 -0
- data/spec/excel_abstraction/row_spec.rb +42 -0
- data/spec/excel_abstraction/sheet_spec.rb +83 -0
- data/spec/excel_abstraction/spread_sheet_spec.rb +35 -0
- data/spec/excel_abstraction/time_spec.rb +27 -0
- data/spec/excel_abstraction/work_book_spec.rb +22 -0
- data/spec/excel_helper.rb +16 -0
- data/spec/excel_templating_spec.rb +141 -0
- data/spec/spec_helper.rb +13 -0
- metadata +281 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module ExcelTemplating
|
2
|
+
# A registry for validation data sources within the excel spreadsheet DSL
|
3
|
+
# Supports Enumerable#each for iterating the registry entries.
|
4
|
+
class Document::DataSourceRegistry
|
5
|
+
include Enumerable
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
# Create an empty DataSourceRegistry
|
9
|
+
def initialize
|
10
|
+
@source_symbols = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [Hash] data
|
14
|
+
# @return [RegistryRenderer]
|
15
|
+
def renderer(data:)
|
16
|
+
RegistryRenderer.new(self, data)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Symbol] source_symbol
|
20
|
+
# @param [String] title
|
21
|
+
# @param [Array<String>|Symbol] list
|
22
|
+
# @param [TrueClass|FalseClass] inline
|
23
|
+
def add_list(source_symbol, title, list, inline)
|
24
|
+
source_symbols[source_symbol] = RegistryList.new(source_symbols.size + 1, source_symbol, title, list, inline)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Symbol] source_symbol
|
28
|
+
# @return [RegistryList]
|
29
|
+
def [](source_symbol)
|
30
|
+
source_symbols[source_symbol]
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [Symbol] source_symbol
|
34
|
+
def has_registry?(source_symbol)
|
35
|
+
source_symbols.has_key?(source_symbol)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [TrueClass|FalseClass]
|
39
|
+
def any_data_sheet_symbols?
|
40
|
+
select {|info|
|
41
|
+
info.data_sheet?
|
42
|
+
}.any?
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Array<Symbol>]
|
46
|
+
def supported_registries
|
47
|
+
source_symbols.keys
|
48
|
+
end
|
49
|
+
|
50
|
+
delegate [:each] => :ordered_registries
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :source_symbols
|
55
|
+
|
56
|
+
def ordered_registries
|
57
|
+
source_symbols.values.sort_by(&:order)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
require_relative 'data_source_registry/registry_renderer'
|
64
|
+
require_relative 'data_source_registry/registry_list'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ExcelTemplating
|
2
|
+
# Simple class for representing a repeated row on a sheet.
|
3
|
+
class Document::Sheet::RepeatedRow
|
4
|
+
# @param [Integer] row_number
|
5
|
+
# @param [Symol] data_attribute
|
6
|
+
def initialize(row_number, data_attribute)
|
7
|
+
@row_number = row_number
|
8
|
+
@data_attribute = data_attribute
|
9
|
+
@column_validations = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
### Dsl Methods ###
|
13
|
+
|
14
|
+
# Validate a particular row in a repeated set as being part of a declared data source
|
15
|
+
# @example
|
16
|
+
# validate_column 5, with: :valid_foos
|
17
|
+
# @param [Integer] column_number
|
18
|
+
# @param [Symbol] with
|
19
|
+
def validate_column(column_number, with:)
|
20
|
+
@column_validations[column_number] = with
|
21
|
+
end
|
22
|
+
|
23
|
+
### Non Dsl Methods ###
|
24
|
+
|
25
|
+
attr_reader :row_number, :data_attribute
|
26
|
+
|
27
|
+
# @param [Integer] column_number
|
28
|
+
# @return [Symbol] Registered source at that column
|
29
|
+
def validated_column_source(column_number)
|
30
|
+
@column_validations[column_number]
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [Integer] column_number
|
34
|
+
# @return [TrueClass|FalseClass]
|
35
|
+
def validated_column?(column_number)
|
36
|
+
@column_validations.has_key?(column_number)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module ExcelTemplating
|
2
|
+
# Define a sheet on a document
|
3
|
+
# @example
|
4
|
+
# sheet 1 do
|
5
|
+
# repeat_row 17, with: :people
|
6
|
+
# end
|
7
|
+
class Document::Sheet
|
8
|
+
# @param [Integer] sheet_number
|
9
|
+
def initialize(sheet_number)
|
10
|
+
@sheet_number = sheet_number
|
11
|
+
@repeated_rows = {}
|
12
|
+
@validated_cells = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
### Sheet Dsl Methods ####
|
16
|
+
|
17
|
+
# @param [Float] decimal_inches
|
18
|
+
# @return [Float] inches converted to excel integer size.
|
19
|
+
def inches(decimal_inches)
|
20
|
+
# empirically determined number. 30.0 seems to be the measurement for 2.6 inches
|
21
|
+
# in open office.
|
22
|
+
(30.0 / 2.6) * decimal_inches
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [Hash] default default styling for all columns.
|
26
|
+
# @param [Hash] columns specific styling for numbered columns.
|
27
|
+
def style_columns(default:, columns: {})
|
28
|
+
@default_column_style = default
|
29
|
+
@column_styles = columns
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param [Hash] default default styling for all rows.
|
33
|
+
# @param [Hash] rows specific styling for numbered rows.
|
34
|
+
def style_rows(default:, rows: {})
|
35
|
+
@default_row_style = default
|
36
|
+
@row_styles = rows
|
37
|
+
end
|
38
|
+
|
39
|
+
# Repeat a numbered row in the template using an array from the data
|
40
|
+
# will result in expanding the produced excel document by a number of rows.
|
41
|
+
# it is expected that the sheet specific data will contain :with as an Array.
|
42
|
+
# @example
|
43
|
+
# repeat_row 17, with: :employee_data
|
44
|
+
# @param [Integer] row_number
|
45
|
+
# @param [Symbol] with
|
46
|
+
def repeat_row(row_number, with:, &block)
|
47
|
+
repeated_rows[row_number] = RepeatedRow.new(row_number, with)
|
48
|
+
repeated_rows[row_number].instance_eval(&block) if block_given?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Validate a particular cell using a declared data source
|
52
|
+
# @example
|
53
|
+
# validate_cell row: 1, column :5, with: :valid_foos
|
54
|
+
# @param [Integer] row
|
55
|
+
# @param [Integer] column
|
56
|
+
# @param [Symbol] with
|
57
|
+
def validate_cell(row:, column:, with:)
|
58
|
+
validated_cells["#{row}:#{column}"] = with
|
59
|
+
end
|
60
|
+
|
61
|
+
#### Non DSL Methods ###
|
62
|
+
|
63
|
+
def default_column_style
|
64
|
+
@default_column_style || {}
|
65
|
+
end
|
66
|
+
|
67
|
+
def column_styles
|
68
|
+
@column_styles || {}
|
69
|
+
end
|
70
|
+
|
71
|
+
def default_row_style
|
72
|
+
@default_row_style || {}
|
73
|
+
end
|
74
|
+
|
75
|
+
def row_styles
|
76
|
+
@row_styles || {}
|
77
|
+
end
|
78
|
+
|
79
|
+
def sheet_data(data)
|
80
|
+
data[sheet_number] || {}
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param [Integer] row_number
|
84
|
+
def repeated_row?(row_number)
|
85
|
+
repeated_rows.has_key?(row_number)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param [Integer] row_number
|
89
|
+
# @param [Integer] column_number
|
90
|
+
def validated_cell?(row_number, column_number)
|
91
|
+
(repeated_row?(row_number) && repeated_rows[row_number].validated_column?(column_number)) ||
|
92
|
+
validated_cells.has_key?("#{row_number}:#{column_number}")
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param [Integer] row_number
|
96
|
+
# @param [Integer] column_number
|
97
|
+
# @return [Symbol] The registered symbol for that row & column or Nil
|
98
|
+
def validation_source_name(row_number, column_number)
|
99
|
+
if repeated_row?(row_number)
|
100
|
+
repeated_rows[row_number].validated_column_source(column_number)
|
101
|
+
else
|
102
|
+
validated_cells["#{row_number}:#{column_number}"]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Repeat each row of the data if it is repeated, yielding each item in succession.
|
107
|
+
# @param [Integer] row_number
|
108
|
+
# @param [Hash] sheet_data Data for this sheet
|
109
|
+
def each_row_at(row_number, sheet_data)
|
110
|
+
if repeated_row?(row_number)
|
111
|
+
repeater = repeated_rows[row_number]
|
112
|
+
verify_array!(sheet_data, repeater.data_attribute)
|
113
|
+
sheet_data[repeater.data_attribute].each_with_index do |row_data, index|
|
114
|
+
yield({ index: index }.merge(row_data).merge(sheet_data))
|
115
|
+
end
|
116
|
+
else
|
117
|
+
yield sheet_data
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
attr_reader :sheet_number, :repeated_rows, :validated_cells
|
124
|
+
|
125
|
+
def verify_array!(sheet_data, attribute)
|
126
|
+
unless sheet_data[attribute].is_a?(Array)
|
127
|
+
raise ArgumentError, "Data for sheet #{sheet_number} did not contain #{attribute} array as expected!"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
require_relative 'sheet/repeated_row'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative 'document_dsl'
|
2
|
+
|
3
|
+
module ExcelTemplating
|
4
|
+
# The base document class for an ExcelTemplating.
|
5
|
+
# Inherit from document to create your own ExcelTemplating that you may then use to generate
|
6
|
+
# Excel Spreadsheets from the template you supply.
|
7
|
+
# @example
|
8
|
+
# class MyTemplate < ExcelTemplating::Document
|
9
|
+
# template "my_template.mustache.xlsx"
|
10
|
+
# title "My title: {{my_company}}"
|
11
|
+
# organization "{{organization_name}}"
|
12
|
+
# default_styling(
|
13
|
+
# text_wrap: 0,
|
14
|
+
# font: "Calibri",
|
15
|
+
# size: 10,
|
16
|
+
# align: :left,
|
17
|
+
# )
|
18
|
+
# sheet 1 do
|
19
|
+
# repeat_row 17, with: :people
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
class Document
|
23
|
+
extend DocumentDsl
|
24
|
+
class << self
|
25
|
+
## The non Dsl Methods, not expected to be used as part of the document description
|
26
|
+
# @return [String] The document title
|
27
|
+
def document_title
|
28
|
+
@document_title
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] The document organization
|
32
|
+
def document_organization
|
33
|
+
@document_organization
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Hash] The default styling for the document
|
37
|
+
def document_default_styling
|
38
|
+
@default_styling || default_styling
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# Create a new document with given data. 'all_sheets' is available to the template on each sheet.
|
42
|
+
# otherwise each numeric key in 'sheet_data' provides the data for that specific sheet.
|
43
|
+
# For example {all_sheets: {foo: 'bar'}, 1 => {var1: "foo"}}
|
44
|
+
# @param [Hash] data Hash with variables for rendering.
|
45
|
+
def initialize(data)
|
46
|
+
@data = data
|
47
|
+
end
|
48
|
+
|
49
|
+
# Render this template.
|
50
|
+
# @example
|
51
|
+
# instance = MyTemplate.new(all_sheets: {foo: 1},1 => {bar: "foo"})
|
52
|
+
# instance.render do |file_path|
|
53
|
+
# FileUtils.cp(file_path, somewhere_else)
|
54
|
+
# end
|
55
|
+
def render(&block)
|
56
|
+
new_renderer.render(&block)
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :data
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def new_renderer
|
64
|
+
ExcelTemplating::Renderer.new(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
require_relative 'document/sheet'
|
71
|
+
require_relative 'document/data_source_registry'
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module ExcelTemplating
|
2
|
+
# The descriptor module for how to define your template class
|
3
|
+
module DocumentDsl
|
4
|
+
# @return [String] The template path.
|
5
|
+
def template_path
|
6
|
+
@template_path
|
7
|
+
end
|
8
|
+
|
9
|
+
# @param [String] path Set the path to the template for this document class.
|
10
|
+
def template(path)
|
11
|
+
raise ArgumentError, "Template path must be a string." unless path.is_a?(String)
|
12
|
+
@template_path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Array<ExcelTemplating::Document::Sheet>] The sheets defined for this document class.
|
16
|
+
def sheets
|
17
|
+
@sheets ||= []
|
18
|
+
end
|
19
|
+
|
20
|
+
# Define a sheet on this document
|
21
|
+
# @example
|
22
|
+
# sheet 1 do
|
23
|
+
# repeat_row 17, with: :people
|
24
|
+
# end
|
25
|
+
# @param [Integer] sheet_number
|
26
|
+
# @param [Proc] block
|
27
|
+
def sheet(sheet_number, &block)
|
28
|
+
sheet = ExcelTemplating::Document::Sheet.new(sheet_number)
|
29
|
+
sheets << sheet
|
30
|
+
sheet.instance_eval(&block)
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def protect_document(protect=true)
|
35
|
+
@protected = protect
|
36
|
+
end
|
37
|
+
|
38
|
+
def protected?
|
39
|
+
!!@protected
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add a list validator to the excel document
|
43
|
+
# @example
|
44
|
+
# list_source :valid_foos, title: "Valid Foos", list: ['foo','bar'], inline: false
|
45
|
+
# @param [Symbol] source_symbol symbol to registry for the validator
|
46
|
+
# @param [String] title Title to show when displaying this validator
|
47
|
+
# @param [Array<String>|Symbol] list items to use for validation, you may also use :from_data and at render time
|
48
|
+
# the validation items will be fetched from key 'source_symbol'
|
49
|
+
# @param [TrueClass|FalseClass] inline If true then the validator will be written to the document inline.
|
50
|
+
# Otherwise it will be written to a 'DataSheet'
|
51
|
+
def list_source(source_symbol, title:, list: :from_data, inline: false)
|
52
|
+
data_source_registry.add_list(source_symbol, title, list, inline)
|
53
|
+
end
|
54
|
+
|
55
|
+
def data_source_registry
|
56
|
+
@data_source_registry ||= ExcelTemplating::Document::DataSourceRegistry.new
|
57
|
+
end
|
58
|
+
|
59
|
+
# Define a title for this workbook. You may use mustaching here.
|
60
|
+
# @param [String] string
|
61
|
+
def title(string)
|
62
|
+
@document_title = string
|
63
|
+
end
|
64
|
+
|
65
|
+
# Define the default styling to use when writing
|
66
|
+
# a column to the worksheet. See Writeexcel::Format
|
67
|
+
# @param [String] font Set the font name
|
68
|
+
# @param [Integer] size font size
|
69
|
+
# @param [Symbol] align :left, :right, or :center
|
70
|
+
# @param [Hash] options Additional options to pass to Format
|
71
|
+
def default_styling(font: "Calibri", size: 10, align: :left, ** options)
|
72
|
+
@default_styling = {
|
73
|
+
name: font,
|
74
|
+
size: size,
|
75
|
+
align: align
|
76
|
+
}.merge(options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Define an organization for the workbook. May use mustaching.
|
80
|
+
# @param [String] string
|
81
|
+
def organization(string)
|
82
|
+
@document_organization = string
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class ActiveCellReference
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :position, :row, :col
|
6
|
+
|
7
|
+
def initialize(attrs = {})
|
8
|
+
@position = ExcelAbstraction::CellReference.new(attrs)
|
9
|
+
end
|
10
|
+
|
11
|
+
def move(directions = {})
|
12
|
+
directions.each do |type, times|
|
13
|
+
self.respond_to?(type) ? self.__send__(type, times) : raise(ArgumentError.new("Movement direction is not valid."))
|
14
|
+
end
|
15
|
+
position
|
16
|
+
end
|
17
|
+
|
18
|
+
def up(times = 1)
|
19
|
+
goto(row - times, col)
|
20
|
+
end
|
21
|
+
|
22
|
+
def down(times = 1)
|
23
|
+
goto(row + times, col)
|
24
|
+
end
|
25
|
+
|
26
|
+
def left(times = 1)
|
27
|
+
goto(row, col - times)
|
28
|
+
end
|
29
|
+
|
30
|
+
def right(times = 1)
|
31
|
+
goto(row, col + times)
|
32
|
+
end
|
33
|
+
|
34
|
+
def carriage_return
|
35
|
+
goto(row, 0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def linefeed
|
39
|
+
down
|
40
|
+
end
|
41
|
+
|
42
|
+
def newline
|
43
|
+
carriage_return
|
44
|
+
linefeed
|
45
|
+
end
|
46
|
+
|
47
|
+
def goto(row, col)
|
48
|
+
self.position = ExcelAbstraction::CellReference.new(row: row, col: col)
|
49
|
+
end
|
50
|
+
|
51
|
+
def reset
|
52
|
+
self.position = ExcelAbstraction::CellReference.new(row: 0, col: 0)
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
attr_accessor :position
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class Cell
|
3
|
+
attr_reader :position, :val, :styles
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
@position = Integer(attrs.fetch(:position) { raise ArgumentError.new("Position absent for ExcelAbstraction cell") })
|
7
|
+
@val = attrs.fetch(:val) { raise ArgumentError.new("Value absent for ExcelAbstraction cell") }
|
8
|
+
@styles = attrs.fetch(:styles) { {} }
|
9
|
+
end
|
10
|
+
|
11
|
+
def <=>(other)
|
12
|
+
position <=> other.position
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
position == other.position && val == other.val && styles == other.styles
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_cell
|
20
|
+
self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class CellRange
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
alias_method :first, :min
|
6
|
+
alias_method :last, :max
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@cell_references = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
cell_references.each { |cell_reference| yield(cell_reference) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def <<(attrs)
|
17
|
+
cell_reference = ExcelAbstraction::CellReference.new(attrs)
|
18
|
+
raise(ArgumentError, "Must be a CellReference belonging to the same row") if last && last.row != cell_reference.row
|
19
|
+
self.cell_references << cell_reference
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
attr_reader :cell_references
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class CellReference
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
COLS = ('A'..'ZZ').to_a
|
6
|
+
|
7
|
+
attr_accessor :row, :col
|
8
|
+
|
9
|
+
def initialize(attrs = {})
|
10
|
+
@row = attrs.fetch(:row) { 0 }
|
11
|
+
@col = attrs.fetch(:col) { 0 }
|
12
|
+
end
|
13
|
+
|
14
|
+
def <=>(other)
|
15
|
+
other = other.to_cell_reference
|
16
|
+
(self.row == other.row) ? (self.col <=> other.col) : (self.row <=> other.row)
|
17
|
+
end
|
18
|
+
|
19
|
+
def succ
|
20
|
+
self.class.new(row: self.row, col: self.col + 1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
COLS[col] + (row + 1).to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_cell_reference
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_ary
|
32
|
+
[row, col]
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_a
|
36
|
+
to_ary
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module ExcelAbstraction
|
4
|
+
class Date < DelegateClass(Float)
|
5
|
+
ADJUSTMENT = ::Date.parse("1900-03-01")
|
6
|
+
REFERENCE = ::Date.parse("1900-01-01")
|
7
|
+
|
8
|
+
attr_reader :value
|
9
|
+
|
10
|
+
def initialize(raw_value)
|
11
|
+
super(convert(raw_value))
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_excel_date
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def reference
|
21
|
+
REFERENCE
|
22
|
+
end
|
23
|
+
|
24
|
+
def adjustment
|
25
|
+
ADJUSTMENT
|
26
|
+
end
|
27
|
+
|
28
|
+
def adjust(raw_value)
|
29
|
+
adjustment < raw_value ? 2 : 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def convert(raw_value)
|
33
|
+
(raw_value - reference + adjust(raw_value)).to_f
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class Row
|
3
|
+
include Enumerable
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
attr_accessor :styles
|
7
|
+
delegate [:each] => :cells
|
8
|
+
|
9
|
+
alias :first :min
|
10
|
+
alias :last :max
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@cells = []
|
14
|
+
@styles = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](index)
|
18
|
+
find { |cell| cell.position == index }
|
19
|
+
end
|
20
|
+
|
21
|
+
def <<(attrs)
|
22
|
+
@cells << ExcelAbstraction::Cell.new(attrs)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
attr_accessor :cells
|
28
|
+
end
|
29
|
+
end
|