zaxcel 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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +9 -0
  4. data/CHANGELOG.md +29 -0
  5. data/CONTRIBUTING.md +110 -0
  6. data/LICENSE +22 -0
  7. data/QUICK_START.md +187 -0
  8. data/README.md +372 -0
  9. data/Rakefile +18 -0
  10. data/SETUP.md +178 -0
  11. data/lib/enumerable.rb +47 -0
  12. data/lib/zaxcel/README.md +37 -0
  13. data/lib/zaxcel/arithmetic.rb +88 -0
  14. data/lib/zaxcel/binary_expression.rb +74 -0
  15. data/lib/zaxcel/binary_expressions/addition.rb +36 -0
  16. data/lib/zaxcel/binary_expressions/division.rb +24 -0
  17. data/lib/zaxcel/binary_expressions/multiplication.rb +24 -0
  18. data/lib/zaxcel/binary_expressions/subtraction.rb +41 -0
  19. data/lib/zaxcel/binary_expressions.rb +38 -0
  20. data/lib/zaxcel/cell.rb +141 -0
  21. data/lib/zaxcel/cell_formula.rb +16 -0
  22. data/lib/zaxcel/column.rb +142 -0
  23. data/lib/zaxcel/document.rb +136 -0
  24. data/lib/zaxcel/function.rb +6 -0
  25. data/lib/zaxcel/functions/abs.rb +18 -0
  26. data/lib/zaxcel/functions/and.rb +23 -0
  27. data/lib/zaxcel/functions/average.rb +17 -0
  28. data/lib/zaxcel/functions/choose.rb +20 -0
  29. data/lib/zaxcel/functions/concatenate.rb +20 -0
  30. data/lib/zaxcel/functions/if.rb +38 -0
  31. data/lib/zaxcel/functions/if_error.rb +25 -0
  32. data/lib/zaxcel/functions/index.rb +20 -0
  33. data/lib/zaxcel/functions/len.rb +16 -0
  34. data/lib/zaxcel/functions/match/match_type.rb +13 -0
  35. data/lib/zaxcel/functions/match.rb +27 -0
  36. data/lib/zaxcel/functions/max.rb +17 -0
  37. data/lib/zaxcel/functions/min.rb +17 -0
  38. data/lib/zaxcel/functions/negate.rb +26 -0
  39. data/lib/zaxcel/functions/or.rb +23 -0
  40. data/lib/zaxcel/functions/round.rb +20 -0
  41. data/lib/zaxcel/functions/sum.rb +18 -0
  42. data/lib/zaxcel/functions/sum_if.rb +20 -0
  43. data/lib/zaxcel/functions/sum_ifs.rb +34 -0
  44. data/lib/zaxcel/functions/sum_product.rb +18 -0
  45. data/lib/zaxcel/functions/text.rb +17 -0
  46. data/lib/zaxcel/functions/unique.rb +23 -0
  47. data/lib/zaxcel/functions/x_lookup.rb +28 -0
  48. data/lib/zaxcel/functions/xirr.rb +27 -0
  49. data/lib/zaxcel/functions.rb +169 -0
  50. data/lib/zaxcel/if_builder.rb +22 -0
  51. data/lib/zaxcel/lang.rb +23 -0
  52. data/lib/zaxcel/reference.rb +28 -0
  53. data/lib/zaxcel/references/cell.rb +42 -0
  54. data/lib/zaxcel/references/column.rb +49 -0
  55. data/lib/zaxcel/references/range.rb +35 -0
  56. data/lib/zaxcel/references/row.rb +34 -0
  57. data/lib/zaxcel/references.rb +5 -0
  58. data/lib/zaxcel/roundable.rb +14 -0
  59. data/lib/zaxcel/row.rb +93 -0
  60. data/lib/zaxcel/sheet.rb +425 -0
  61. data/lib/zaxcel/sorbet/enumerizable_enum.rb +50 -0
  62. data/lib/zaxcel/version.rb +6 -0
  63. data/lib/zaxcel.rb +73 -0
  64. data/zaxcel.gemspec +73 -0
  65. metadata +266 -0
@@ -0,0 +1,425 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ class Zaxcel::Sheet
5
+ extend T::Sig
6
+
7
+ HEADER_ROW_KEY = :header_row
8
+
9
+ sig { returns(String) }
10
+ attr_reader :name
11
+
12
+ sig { returns(Zaxcel::Document) }
13
+ attr_reader :document
14
+
15
+ sig { returns(T::Hash[Zaxcel::Cell::CellExtractionKey, Zaxcel::Cell]) }
16
+ attr_reader :cells_by_extraction_key
17
+
18
+ class SheetVisibility < T::Enum
19
+ include Sorbet::EnumerizableEnum
20
+
21
+ enums do
22
+ Visible = new(:visible)
23
+ Hidden = new(:hidden) # Can be unhidden from Excel UI
24
+ # VeryHidden is here if we really need it at some point, but pay attention to:
25
+ # - who is potentially going to be accessing this sheet and how will they react to a sheet referenced which cannot be accessed via UI
26
+ # VeryHidden = new(:very_hidden) # Requires code to unhide (e.g. VBA)
27
+ end
28
+ end
29
+
30
+ sig { params(name: String, document: Zaxcel::Document, worksheet: Axlsx::Worksheet, visibility: Zaxcel::Sheet::SheetVisibility).void }
31
+ def initialize(name:, document:, worksheet:, visibility: Zaxcel::Sheet::SheetVisibility::Visible)
32
+ @name = name
33
+ @document = document
34
+ @worksheet = worksheet
35
+ @visibility = visibility
36
+ @cells_by_extraction_key = T.let({}, T::Hash[Zaxcel::Cell::CellExtractionKey, Zaxcel::Cell])
37
+ end
38
+
39
+ # returns the underlying axlsx worksheet. this an escape hatch to allow operations not supported by zaxcel.
40
+ # this is unsafe because it allows you to mutate the worksheet in ways that zaxcel doesn't know about.
41
+ sig { returns(Axlsx::Worksheet) }
42
+ def unsafe_axlsx_worksheet
43
+ @worksheet
44
+ end
45
+
46
+ sig { returns(String) }
47
+ def to_excel
48
+ @worksheet.name
49
+ end
50
+
51
+ sig { void }
52
+ def position_rows!
53
+ rows.each_with_index do |row, i|
54
+ row_position = i + 1 # 1 indexed not 0 indexed
55
+ row.position!(row_position)
56
+
57
+ # First persist the location of each cell
58
+ columns.each_with_index do |column, j|
59
+ column.position!(j)
60
+
61
+ # add an empty cell anywhere that's missing. this makes cell references work more intuitively.
62
+ # for instance, if I want to reference the top left cell in some area so I can define a merge range,
63
+ # it should resolve whether I define that cell or not.
64
+ cell = column.cell(row.name)
65
+ if cell.nil?
66
+ column.add_cell!(row: row)
67
+ elsif cell.to_extract?
68
+ @cells_by_extraction_key[cell.extraction_key] = cell
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ sig { returns(T::Array[T.nilable(T.any(Float, Integer))]) }
75
+ def column_widths
76
+ columns.map do |col|
77
+ next T.cast(col.width, T.nilable(T.any(Float, Integer))) if !Zaxcel::Column::ComputedColumnWidth.values.include?(col.width)
78
+
79
+ character_length = case col.width
80
+ when Zaxcel::Column::ComputedColumnWidth::MaxContent
81
+ rows.map do |row|
82
+ cell = col.cell(row.name)
83
+ next 0 if cell.nil? || cell.value.is_a?(Zaxcel::References::Column)
84
+
85
+ cell.estimated_formatted_character_length
86
+ end.compact.max
87
+ when Zaxcel::Column::ComputedColumnWidth::Header
88
+ col.cell(HEADER_ROW_KEY)&.estimated_formatted_character_length || 0
89
+ when Zaxcel::Column::ComputedColumnWidth::HeaderTwoLines
90
+ # if we ever have to do fit to more than two lines (don't think we ever would),
91
+ # just refac this to support N lines and pass N in somehow
92
+ uneven_line_break_buffer = 3
93
+ (((col.cell(HEADER_ROW_KEY)&.estimated_formatted_character_length || 0) / 2) + uneven_line_break_buffer)
94
+ end
95
+
96
+ next if character_length.nil?
97
+
98
+ [column_width_from_content_length(character_length), col.min_width].max
99
+ end
100
+ end
101
+
102
+ # An alternative to allowing CAXLSX to auto-calculate the width of a column,
103
+ # since it sometimes gives way too much extra space.
104
+ sig { params(content_length: Integer).returns(Integer) }
105
+ def column_width_from_content_length(content_length)
106
+ (document.width_units_by_default_character * content_length).ceil
107
+ end
108
+
109
+ sig { void }
110
+ def add_rows_to_worksheet!
111
+ rows.each_with_index do |row, _rowi|
112
+ cells_in_row = columns.map { |col| col.cell(row.name) }
113
+ formatted_cells = cells_in_row.map { |cell| format_cell_contents(cell) }
114
+ @worksheet.add_row(
115
+ formatted_cells,
116
+ style: cells_in_row.map { |c| @document.style(c&.style || :default_cell) },
117
+ height: row.height,
118
+ )
119
+ @worksheet.column_widths(*T.unsafe(column_widths))
120
+ end
121
+ end
122
+
123
+ sig { returns(T.nilable(Zaxcel::Column)) }
124
+ def print_boundary_column
125
+ columns.find(&:print_boundary?)
126
+ end
127
+
128
+ sig { returns(T.nilable(Zaxcel::Row)) }
129
+ def print_boundary_row
130
+ rows.find(&:print_boundary?)
131
+ end
132
+
133
+ sig { void }
134
+ def set_print_area!
135
+ first_column_to_print = columns.first!
136
+ last_column_to_print = print_boundary_column || T.must(columns.last)
137
+
138
+ first_row_to_print = rows.first!
139
+ last_row_to_print = print_boundary_row || T.must(rows.last)
140
+
141
+ print_area_range = "'#{to_excel}'!#{first_column_to_print.to_excel}#{first_row_to_print.to_excel}:#{last_column_to_print.to_excel}#{last_row_to_print.to_excel}"
142
+
143
+ @document.set_print_area(sheet: @worksheet, range_to_include: print_area_range)
144
+ end
145
+
146
+ sig { void }
147
+ def apply_conditional_formatting!
148
+ conditional_formatting.each do |formatting|
149
+ range = formatting[:range]
150
+ if range.is_a?(Zaxcel::References::Column)
151
+ first_cell = range.cells.first!
152
+ last_cell = T.must(range.cells.last)
153
+ else
154
+ range = T.cast(range, T::Array[Zaxcel::References::Cell])
155
+ first_cell = range.first!.cell
156
+ last_cell = T.must(range.last).cell
157
+ end
158
+
159
+ @worksheet.add_conditional_formatting(
160
+ "#{T.must(first_cell).to_excel}:#{T.must(last_cell).to_excel}",
161
+ type: formatting[:rule],
162
+ dxfId: @document.style(formatting[:style]),
163
+ priority: 1, # i don't know what this means :)
164
+ )
165
+ end
166
+ end
167
+
168
+ sig { void }
169
+ def apply_cell_merges!
170
+ ranges_to_merge.each do |range|
171
+ cells = range.map(&:cell).compact
172
+
173
+ first_column_cell = cells.min_by { |cell| T.must(cell.col_position) }
174
+ first_row_cell = cells.min_by { |cell| T.must(cell.row_position) }
175
+ last_column_cell = cells.max_by { |cell| T.must(cell.col_position) }
176
+ last_row_cell = cells.max_by { |cell| T.must(cell.row_position) }
177
+ # if any of the references don't resolve, then just skip this range
178
+ next if first_column_cell.nil? || first_row_cell.nil? || last_column_cell.nil? || last_row_cell.nil?
179
+
180
+ top_left_cell_position = "#{first_column_cell.column.to_excel}#{first_row_cell.row.to_excel}"
181
+ bottom_right_cell_position = "#{last_column_cell.column.to_excel}#{last_row_cell.row.to_excel}"
182
+
183
+ @worksheet.merge_cells("#{top_left_cell_position}:#{bottom_right_cell_position}")
184
+ end
185
+ end
186
+
187
+ sig { void }
188
+ def hide_columns!
189
+ columns.select(&:hidden?).each do |column|
190
+ @worksheet.column_info[column.position].hidden = true
191
+ end
192
+ end
193
+
194
+ sig { void }
195
+ def hide_rows!
196
+ rows.each_with_index do |row, i|
197
+ next if !row.hidden?
198
+
199
+ @worksheet.rows[i].hidden = true
200
+ end
201
+ end
202
+
203
+ sig { void }
204
+ def apply_sheet_visibility!
205
+ @worksheet.state = @visibility.serialize
206
+ end
207
+
208
+ # it woudl be cool to specify a cell as an image instead...
209
+ sig do
210
+ params(image_path: String, width: Integer, height: Integer, row_position: Integer, column_position: Integer).void
211
+ end
212
+ def add_image_to_worksheet!(image_path, width:, height:, row_position:, column_position:)
213
+ @worksheet.add_image(
214
+ image_src: image_path,
215
+ noMove: true,
216
+ ) do |image|
217
+ image.width = width
218
+ image.height = height
219
+ image.start_at(column_position, row_position)
220
+ end
221
+ end
222
+
223
+ sig { void }
224
+ def generate_sheet!
225
+ add_rows_to_worksheet!
226
+ set_print_area!
227
+
228
+ apply_cell_merges!
229
+ apply_conditional_formatting!
230
+ hide_columns!
231
+ hide_rows!
232
+ apply_sheet_visibility!
233
+ end
234
+
235
+ sig { params(row_name: T.any(Symbol, String)).returns(T.nilable(Zaxcel::Row)) }
236
+ def row(row_name)
237
+ rows_by_name[row_name.to_sym]
238
+ end
239
+
240
+ sig { params(column_name: T.any(Symbol, String)).returns(T.nilable(Zaxcel::Column)) }
241
+ def column(column_name)
242
+ columns_by_name[column_name.to_sym]
243
+ end
244
+
245
+ sig do
246
+ params(
247
+ name: T.any(Symbol, String, Numeric),
248
+ style_group: Symbol,
249
+ height: T.nilable(Integer),
250
+ hidden: T::Boolean,
251
+ ).returns(Zaxcel::Row)
252
+ end
253
+ def add_row!(name, style_group: :row_style, height: nil, hidden: false)
254
+ name = name.to_s if name.is_a?(Numeric)
255
+ rows_by_name[name.to_sym] ||= Zaxcel::Row.new(
256
+ name.to_sym,
257
+ sheet: self,
258
+ style_group: style_group,
259
+ height: height,
260
+ hidden: hidden,
261
+ )
262
+ end
263
+
264
+ sig { params(style_group: Symbol).returns(Zaxcel::Row) }
265
+ def add_empty_row!(style_group: :row_style)
266
+ add_row!(SecureRandom.uuid.delete('-'), style_group: style_group)
267
+ end
268
+
269
+ sig do
270
+ params(
271
+ name: T.any(Symbol, String),
272
+ header: String,
273
+ header_style: Symbol,
274
+ row_style: Symbol,
275
+ first_row_style: Symbol,
276
+ total_style: T.nilable(Symbol),
277
+ width: T.nilable(T.any(Integer, Float, Zaxcel::Column::ComputedColumnWidth)),
278
+ alt_row_style: T.nilable(Symbol),
279
+ min_width: T.nilable(T.any(Integer, Float)),
280
+ ).returns(Zaxcel::Column)
281
+ end
282
+ def add_column!(
283
+ name,
284
+ header: '',
285
+ header_style: :default_cell,
286
+ row_style: :default_cell,
287
+ first_row_style: :default_cell,
288
+ total_style: nil,
289
+ width: Zaxcel::Column::DEFAULT_COLUMN_WIDTH,
290
+ alt_row_style: nil,
291
+ min_width: 0
292
+ )
293
+ columns_by_name[name.to_sym] ||= Zaxcel::Column.new(
294
+ sheet: self,
295
+ name: name.to_sym,
296
+ header: header,
297
+ header_style: header_style,
298
+ row_style: row_style,
299
+ first_row_style: first_row_style,
300
+ total_style: total_style,
301
+ width: width,
302
+ alt_row_style: alt_row_style,
303
+ min_width: min_width,
304
+ )
305
+ end
306
+
307
+ sig { params(width: T.nilable(T.any(Integer, Float))).returns(Zaxcel::Column) }
308
+ def add_empty_column!(width: Zaxcel::Column::DEFAULT_COLUMN_WIDTH)
309
+ add_column!(SecureRandom.uuid.delete('-'), width: width)
310
+ end
311
+
312
+ sig do
313
+ params(
314
+ range: T.any(Zaxcel::References::Column, T::Array[Zaxcel::References::Cell]),
315
+ rule: Symbol,
316
+ style: Symbol,
317
+ ).void
318
+ end
319
+ def add_conditional_formatting!(range:, rule:, style:)
320
+ conditional_formatting << { range: range, rule: rule, style: style }
321
+ end
322
+
323
+ sig { params(range: T::Array[Zaxcel::References::Cell]).void }
324
+ def merge_cells!(range)
325
+ ranges_to_merge << range
326
+ end
327
+
328
+ sig { returns(T::Array[T::Array[Zaxcel::References::Cell]]) }
329
+ def ranges_to_merge
330
+ @ranges_to_merge ||= T.let([], T.nilable(T::Array[T::Array[Zaxcel::References::Cell]]))
331
+ end
332
+
333
+ sig { returns(T::Array[Zaxcel::Column]) }
334
+ def columns
335
+ columns_by_name.values
336
+ end
337
+
338
+ sig { returns(T::Array[Zaxcel::Row]) }
339
+ def rows
340
+ rows_by_name.values
341
+ end
342
+
343
+ sig { params(height: T.nilable(Integer)).returns(Zaxcel::Row) }
344
+ def add_column_header_row!(height: nil)
345
+ add_row!(HEADER_ROW_KEY, style_group: :header_style, height: height)
346
+ .add_many!(columns.map { |c| [c.name, c.header] }.to_h)
347
+ end
348
+
349
+ sig { returns(T::Hash[Symbol, Zaxcel::Row]) }
350
+ def rows_by_name
351
+ @rows_by_name ||= T.let({}, T.nilable(T::Hash[Symbol, Zaxcel::Row]))
352
+ end
353
+
354
+ sig { returns(T::Hash[Symbol, Zaxcel::Column]) }
355
+ def columns_by_name
356
+ @columns_by_name ||= T.let({}, T.nilable(T::Hash[Symbol, Zaxcel::Column]))
357
+ end
358
+
359
+ sig { returns(T::Array[{ range: T.any(Zaxcel::References::Column, T::Array[Zaxcel::References::Cell]), rule: Symbol, style: Symbol }]) }
360
+ def conditional_formatting
361
+ @conditional_formatting ||= T.let(
362
+ [],
363
+ T.nilable(T::Array[{
364
+ range: T.any(Zaxcel::References::Column, T::Array[Zaxcel::References::Cell]),
365
+ rule: Symbol,
366
+ style: Symbol,
367
+ }]),
368
+ )
369
+ end
370
+
371
+ sig { void }
372
+ def hide_gridlines!
373
+ @worksheet.sheet_view.show_grid_lines = false
374
+ end
375
+
376
+ sig { void }
377
+ def print_landscape!
378
+ @worksheet.print_options.horizontal_centered = true
379
+ @worksheet.page_setup.orientation = :landscape
380
+ end
381
+
382
+ sig do
383
+ params(
384
+ col_name: T.any(Symbol, String),
385
+ row_name: T.any(Symbol, String, Numeric),
386
+ sheet_name: T.nilable(String),
387
+ ).returns(Zaxcel::References::Cell)
388
+ end
389
+ def cell_ref(col_name, row_name, sheet_name: nil)
390
+ row_name = row_name.to_s if row_name.is_a?(Numeric)
391
+ Zaxcel::References::Cell.new(
392
+ document: @document,
393
+ sheet_name: sheet_name || @name,
394
+ row_name: row_name.to_sym,
395
+ col_name: col_name.to_sym,
396
+ )
397
+ end
398
+
399
+ sig { params(col_name: T.any(Symbol, String), sheet_name: T.nilable(String)).returns(Zaxcel::References::Column) }
400
+ def column_ref(col_name, sheet_name: nil)
401
+ Zaxcel::References::Column.new(document: @document, sheet_name: sheet_name || @name, col_name: col_name.to_sym)
402
+ end
403
+
404
+ private
405
+
406
+ sig { params(cell: T.nilable(Zaxcel::Cell)).returns(T.nilable(T.any(Numeric, Money, String))) }
407
+ def format_cell_contents(cell)
408
+ value = cell&.value
409
+ base_format = Zaxcel::Cell.format(value, on_sheet: @name, quote_strings: false)
410
+ # always pass along numerics + money + nil as is for caxlsx to print directly
411
+ return base_format if !base_format.is_a?(String)
412
+
413
+ # prefix the evaluation operator if the result is a cell formula
414
+ if value.is_a?(Time) || value.is_a?(Zaxcel::CellFormula) || value.is_a?(Zaxcel::Reference) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
415
+ base_format = "=#{base_format}"
416
+
417
+ # array formulas need to be wrapped in curly braces for CAXLSX. See
418
+ # https://github.com/caxlsx/caxlsx/blob/master/lib/axlsx/workbook/worksheet/cell.rb#L422 and
419
+ # https://github.com/caxlsx/caxlsx/blob/master/lib/axlsx/workbook/worksheet/cell_serializer.rb#L101
420
+ base_format = "{#{base_format}}" if value.respond_to?(:array_formula?) && T.unsafe(value).array_formula?
421
+ end
422
+
423
+ base_format
424
+ end
425
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require 'active_support/concern'
5
+
6
+ # Include this in a sorbet typed enum (T::Enum) so that it can be used easily with the `enumerize` gem:
7
+ # ```ruby
8
+ # class Category < T::Enum
9
+ # enums do
10
+ # One = new(:one)
11
+ # Two = new(:two)
12
+ # end
13
+ # end
14
+ #
15
+ # enumerize(
16
+ # :category,
17
+ # in: Category.enumerize_values,
18
+ # value_class: Category,
19
+ # )
20
+ # ```
21
+ class Sorbet
22
+ module EnumerizableEnum
23
+ extend ActiveSupport::Concern
24
+ extend T::Sig
25
+ extend T::Generic
26
+
27
+ # Enumerize requires that `value` on an instance of the `value_class` returns the enum's underlying value.
28
+ sig { returns(Symbol) }
29
+ def value = T.bind(self, T::Enum).serialize
30
+
31
+ class_methods do
32
+ extend T::Sig
33
+
34
+ # Monkey patch new to call `new` on `T::Enum` if a single value is passed in, otherwise call `deserialize` on the
35
+ # on the enum class. Enumerize calls `new` on the value class with two args; the second of which is the underlying
36
+ # value.
37
+ sig { params(args: T.untyped).returns(T.untyped) }
38
+ def new(*args)
39
+ return super if args.length == 1
40
+
41
+ T.bind(self, T.class_of(T::Enum)).deserialize(args[1].to_sym)
42
+ end
43
+
44
+ sig { returns(T::Array[Symbol]) }
45
+ def enumerize_values
46
+ T.bind(self, T.class_of(T::Enum)).values.map(&:serialize)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Zaxcel
5
+ VERSION = '0.1.1'
6
+ end
data/lib/zaxcel.rb ADDED
@@ -0,0 +1,73 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+ require 'caxlsx'
6
+ require 'active_support/core_ext/object/blank'
7
+ require 'active_support/core_ext/object/try'
8
+ require 'active_support/core_ext/string/filters'
9
+ require 'active_support/core_ext/string/access'
10
+ require 'active_support/core_ext/string/inflections'
11
+ require 'money'
12
+
13
+ require_relative 'zaxcel/version'
14
+ require_relative 'zaxcel/sorbet/enumerizable_enum'
15
+ require_relative 'enumerable'
16
+
17
+ # Core classes
18
+ require_relative 'zaxcel/arithmetic'
19
+ require_relative 'zaxcel/roundable'
20
+ require_relative 'zaxcel/reference'
21
+ require_relative 'zaxcel/cell_formula'
22
+ require_relative 'zaxcel/binary_expression'
23
+ require_relative 'zaxcel/function'
24
+ require_relative 'zaxcel/if_builder'
25
+ require_relative 'zaxcel/lang'
26
+
27
+ # Binary expressions
28
+ require_relative 'zaxcel/binary_expressions'
29
+ require_relative 'zaxcel/binary_expressions/addition'
30
+ require_relative 'zaxcel/binary_expressions/subtraction'
31
+ require_relative 'zaxcel/binary_expressions/multiplication'
32
+ require_relative 'zaxcel/binary_expressions/division'
33
+
34
+ # References
35
+ require_relative 'zaxcel/references'
36
+ require_relative 'zaxcel/references/cell'
37
+ require_relative 'zaxcel/references/column'
38
+ require_relative 'zaxcel/references/row'
39
+ require_relative 'zaxcel/references/range'
40
+
41
+ # Functions
42
+ require_relative 'zaxcel/functions'
43
+ require_relative 'zaxcel/functions/abs'
44
+ require_relative 'zaxcel/functions/and'
45
+ require_relative 'zaxcel/functions/average'
46
+ require_relative 'zaxcel/functions/choose'
47
+ require_relative 'zaxcel/functions/concatenate'
48
+ require_relative 'zaxcel/functions/if'
49
+ require_relative 'zaxcel/functions/if_error'
50
+ require_relative 'zaxcel/functions/index'
51
+ require_relative 'zaxcel/functions/len'
52
+ require_relative 'zaxcel/functions/match'
53
+ require_relative 'zaxcel/functions/match/match_type'
54
+ require_relative 'zaxcel/functions/max'
55
+ require_relative 'zaxcel/functions/min'
56
+ require_relative 'zaxcel/functions/negate'
57
+ require_relative 'zaxcel/functions/or'
58
+ require_relative 'zaxcel/functions/round'
59
+ require_relative 'zaxcel/functions/sum'
60
+ require_relative 'zaxcel/functions/sum_if'
61
+ require_relative 'zaxcel/functions/sum_ifs'
62
+ require_relative 'zaxcel/functions/sum_product'
63
+ require_relative 'zaxcel/functions/text'
64
+ require_relative 'zaxcel/functions/unique'
65
+ require_relative 'zaxcel/functions/x_lookup'
66
+ require_relative 'zaxcel/functions/xirr'
67
+
68
+ # Main classes
69
+ require_relative 'zaxcel/cell'
70
+ require_relative 'zaxcel/column'
71
+ require_relative 'zaxcel/row'
72
+ require_relative 'zaxcel/sheet'
73
+ require_relative 'zaxcel/document'
data/zaxcel.gemspec ADDED
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/zaxcel/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'zaxcel'
7
+ spec.version = Zaxcel::VERSION
8
+ spec.authors = ['AngelList Engineering']
9
+ spec.email = ['engineering@angellist.com']
10
+
11
+ spec.summary = 'A Ruby DSL for building Excel spreadsheets programmatically'
12
+ spec.description = <<~DESC
13
+ Zaxcel is a Ruby library built on top of caxlsx that adds an abstraction layer#{' '}
14
+ to building Excel documents. It provides simple methods for building formulas#{' '}
15
+ and references to other cells, even across many worksheets, using clean Ruby idioms#{' '}
16
+ without having to think about the underlying Excel implementation.
17
+ DESC
18
+ spec.homepage = 'https://github.com/angellist/zaxcel'
19
+ spec.license = 'MIT'
20
+ spec.required_ruby_version = '>= 3.0.0'
21
+
22
+ spec.metadata = {
23
+ 'bug_tracker_uri' => 'https://github.com/angellist/zaxcel/issues',
24
+ 'changelog_uri' => 'https://github.com/angellist/zaxcel/blob/main/CHANGELOG.md',
25
+ 'documentation_uri' => 'https://github.com/angellist/zaxcel',
26
+ 'homepage_uri' => 'https://github.com/angellist/zaxcel',
27
+ 'source_code_uri' => 'https://github.com/angellist/zaxcel',
28
+ 'rubygems_mfa_required' => 'true',
29
+ }
30
+
31
+ spec.files = Dir.chdir(__dir__) do
32
+ %x(git ls-files -z).split("\x0").reject do |f|
33
+ (File.expand_path(f) == __FILE__) ||
34
+ f.start_with?(*%w[
35
+ bin/
36
+ test/
37
+ spec/
38
+ features/
39
+ .git
40
+ .circleci
41
+ .github/
42
+ appveyor
43
+ Gemfile
44
+ vendor/
45
+ vendor/bundle/
46
+ coverage/
47
+ tmp/
48
+ docs/
49
+ script/
50
+ sorbet/
51
+ examples/
52
+ ])
53
+ end
54
+ end
55
+ spec.bindir = 'exe'
56
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
57
+ spec.require_paths = ['lib']
58
+
59
+ # Runtime dependencies
60
+ spec.add_dependency 'activesupport', '>= 6.0'
61
+ spec.add_dependency 'caxlsx', '~> 4.0'
62
+ spec.add_dependency 'money', '~> 6.0'
63
+ spec.add_dependency 'sorbet-runtime', '~> 0.5'
64
+
65
+ # Development dependencies
66
+ spec.add_development_dependency 'bundler', '~> 2.0'
67
+ spec.add_development_dependency 'rake', '~> 13.0'
68
+ spec.add_development_dependency 'rspec', '~> 3.0'
69
+ spec.add_development_dependency 'rubocop', '~> 1.72'
70
+ spec.add_development_dependency 'rubocop-rspec', '~> 3.5'
71
+ spec.add_development_dependency 'sorbet', '~> 0.5'
72
+ spec.add_development_dependency 'tapioca', '~> 0.11'
73
+ end