caxlsx_builder 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 166ae65f2879639670efc80db83d33e769008aff2445b8f17a201ed859e739b0
4
+ data.tar.gz: 22925363722171bdf7141d22e8f964c2f5a28344dff5b18bec7ea7f69942a026
5
+ SHA512:
6
+ metadata.gz: 94cf4c88f0c77fabc79e2e2699d7a1d2898abd1008901771fecb1d197e29692c9b473f3adf9775771c54e110d5075b07dbdfcfe8bcbff7ca6d73ec3f57557aa7
7
+ data.tar.gz: bc70d29e37edc0a7d289e617238ef6785aa94484c969a166426ab47ba7a378f10cb00d0ee17e3534aeb542b7dd7ee8f3cf0d4d69fd5d9d0761289ceda12edcb9
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,226 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+ SuggestExtensions: false
4
+ NewCops: enable
5
+ Exclude:
6
+ - 'bin'
7
+ - '**/Gemfile'
8
+ - '**/Rakefile'
9
+ - 'spec/**/*'
10
+
11
+ # Checks if String literals are using single quotes when no interpolation is required
12
+ Style/StringLiterals:
13
+ Enabled: true
14
+ EnforcedStyle: single_quotes
15
+ ConsistentQuotesInMultiline: false
16
+
17
+ # Checks if the quotes used for quoted symbols are single quotes when no interpolation is required
18
+ Style/QuotedSymbols:
19
+ Enabled: true
20
+ EnforcedStyle: same_as_string_literals
21
+
22
+ # This cop checks for uses of literal strings converted to a symbol where a literal symbol could be used instead.
23
+ Lint/SymbolConversion:
24
+ Enabled: true
25
+ EnforcedStyle: strict
26
+
27
+ # Useless cop. It checks for unnecessary safe navigations.
28
+ # Example:
29
+ # obj&.a && obj.b
30
+ # Triggers rubocop error: it requires to add safe navigation for "obj.b" call => "obj&.b".
31
+ # but it is not necessary. obj&.a will return nil if obj is nil, and it will stop
32
+ # execution of the operation because `&&` right part executes only when left part is truthy.
33
+ Lint/SafeNavigationConsistency:
34
+ Enabled: false
35
+
36
+ # Checks for places where keyword arguments can be used instead of boolean arguments when defining methods.
37
+ # Disabled because moving from default arguments to keywords is not that easy.
38
+ Style/OptionalBooleanParameter:
39
+ Enabled: false
40
+
41
+ # Checks for use of the lambda.(args) syntax.
42
+ # Disabled while the Ruby team has not voted on this.
43
+ Style/LambdaCall:
44
+ Enabled: false
45
+ EnforcedStyle: braces
46
+
47
+ # Checks for presence or absence of braces around hash literal as a last array item depending on configuration.
48
+ # Disabled because it would break a lot of permitted_params definitions
49
+ Style/HashAsLastArrayItem:
50
+ Enabled: false
51
+
52
+ # Checks for grouping of accessors in class and module bodies.
53
+ # Useless.
54
+ Style/AccessorGrouping:
55
+ Enabled: false
56
+
57
+ # Makes our lives happier: we don't need to disable it in each case/when method
58
+ # with more than 5 "when"s.
59
+ Metrics/CyclomaticComplexity:
60
+ Max: 10
61
+
62
+ # Commonly used screens these days easily fit more than 80 characters.
63
+ Layout/LineLength:
64
+ Max: 120
65
+
66
+ # Too short methods lead to extraction of single-use methods, which can make
67
+ # the code easier to read (by naming things), but can also clutter the class
68
+ Metrics/MethodLength:
69
+ Max: 20
70
+
71
+ # The guiding principle of classes is SRP, SRP can't be accurately measured by LoC
72
+ Metrics/ClassLength:
73
+ Max: 1500
74
+
75
+ # No space makes the method definition shorter and differentiates
76
+ # from a regular assignment.
77
+ Layout/SpaceAroundEqualsInParameterDefault:
78
+ EnforcedStyle: no_space
79
+
80
+ # We do not need to support Ruby 1.9, so this is good to use.
81
+ Style/SymbolArray:
82
+ Enabled: true
83
+
84
+ # Most readable form.
85
+ Layout/HashAlignment:
86
+ EnforcedHashRocketStyle: table
87
+ EnforcedColonStyle: table
88
+
89
+ # Mixing the styles looks just silly.
90
+ Style/HashSyntax:
91
+ EnforcedStyle: ruby19_no_mixed_keys
92
+
93
+ # has_key? and has_value? are far more readable than key? and value?
94
+ Style/PreferredHashMethods:
95
+ Enabled: false
96
+
97
+ # String#% is by far the least verbose and only object oriented variant.
98
+ Style/FormatString:
99
+ EnforcedStyle: percent
100
+
101
+ # Annotated or template are too verbose and rarely needed.
102
+ Style/FormatStringToken:
103
+ EnforcedStyle: unannotated
104
+
105
+ Style/CollectionMethods:
106
+ Enabled: true
107
+ PreferredMethods:
108
+ # inject seems more common in the community.
109
+ reduce: "inject"
110
+
111
+ # Either allow this style or don't. Marking it as safe with parenthesis
112
+ # is silly. Let's try to live without them for now.
113
+ Style/ParenthesesAroundCondition:
114
+ AllowSafeAssignment: false
115
+ Lint/AssignmentInCondition:
116
+ AllowSafeAssignment: false
117
+
118
+ # A specialized exception class will take one or more arguments and construct the message from it.
119
+ # So both variants make sense.
120
+ Style/RaiseArgs:
121
+ Enabled: false
122
+
123
+ # Indenting the chained dots beneath each other is not supported by this cop,
124
+ # see https://github.com/bbatsov/rubocop/issues/1633
125
+ Layout/MultilineOperationIndentation:
126
+ Enabled: false
127
+
128
+ # Fail is an alias of raise. Avoid aliases, it's more cognitive load for no gain.
129
+ # The argument that fail should be used to abort the program is wrong too,
130
+ # there's Kernel#abort for that.
131
+ Style/SignalException:
132
+ EnforcedStyle: only_raise
133
+
134
+ # Suppressing exceptions can be perfectly fine, and be it to avoid to
135
+ # explicitly type nil into the rescue since that's what you want to return,
136
+ # or suppressing LoadError for optional dependencies
137
+ Lint/SuppressedException:
138
+ Enabled: false
139
+
140
+ # { ... } for multi-line blocks is okay, follow Weirichs rule instead:
141
+ # https://web.archive.org/web/20140221124509/http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc
142
+ Style/BlockDelimiters:
143
+ Enabled: false
144
+
145
+ # do / end blocks should be used for side effects,
146
+ # methods that run a block for side effects and have
147
+ # a useful return value are rare, assign the return
148
+ # value to a local variable for those cases.
149
+ Style/MethodCalledOnDoEndBlock:
150
+ Enabled: true
151
+
152
+ # Enforcing the names of variables? To single letter ones? Just no.
153
+ Style/SingleLineBlockParams:
154
+ Enabled: false
155
+
156
+ # Shadowing outer local variables with block parameters is often useful
157
+ # to not reinvent a new name for the same thing, it highlights the relation
158
+ # between the outer variable and the parameter. The cases where it's actually
159
+ # confusing are rare, and usually bad for other reasons already, for example
160
+ # because the method is too long.
161
+ Lint/ShadowingOuterLocalVariable:
162
+ Enabled: false
163
+
164
+ # Check with yard instead.
165
+ Style/Documentation:
166
+ Enabled: false
167
+
168
+ # This is just silly. Calling the argument `other` in all cases makes no sense.
169
+ Naming/BinaryOperatorParameterName:
170
+ Enabled: false
171
+
172
+ # Disable frozen string
173
+ Style/FrozenStringLiteralComment:
174
+ Enabled: false
175
+
176
+ # Disable No ASCII char in comments
177
+ Style/AsciiComments:
178
+ Enabled: false
179
+
180
+ # Disable ordered Gems By ascii
181
+ Bundler/OrderedGems:
182
+ Enabled: false
183
+
184
+ # Change ABC max value
185
+ Metrics/AbcSize:
186
+ Max: 35
187
+
188
+ # Disable empty method in one line
189
+ Style/EmptyMethod:
190
+ EnforcedStyle: expanded
191
+
192
+ # Disable max height block
193
+ Metrics/BlockLength:
194
+ Enabled: true
195
+ Exclude:
196
+ - 'app/admin/**/*'
197
+
198
+ # Checks if empty lines around the bodies of classes match the configuration.
199
+ Layout/EmptyLinesAroundClassBody:
200
+ EnforcedStyle: empty_lines
201
+ # Checks if empty lines around the bodies of modules match the configuration.
202
+ Layout/EmptyLinesAroundModuleBody:
203
+ EnforcedStyle: empty_lines
204
+
205
+ # Enforces the consistent usage of %-literal delimiters.
206
+ Style/PercentLiteralDelimiters:
207
+ PreferredDelimiters:
208
+ default: '()'
209
+ '%i': '[]'
210
+ '%I': '[]'
211
+ '%r': '{}'
212
+ '%w': '[]'
213
+ '%W': '[]'
214
+
215
+ # Unnecessary cop. In what universe "A || B && C" or "A && B || C && D" is ambiguous? looks
216
+ # like a cop for those who can't in boolean.
217
+ Lint/AmbiguousOperatorPrecedence:
218
+ Enabled: false
219
+
220
+ # Checks for simple usages of parallel assignment.
221
+ Style/ParallelAssignment:
222
+ Enabled: false
223
+
224
+ # Checks the style of children definitions at classes and modules.
225
+ Style/ClassAndModuleChildren:
226
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## [1.0.0] - 2022-12-26
2
+
3
+ - Initial release
4
+ - Builder with custom styles, dynamic cell styles and types, dynamic footers
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at TODO: Write your email address. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in caxlsx_builder.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.12"
11
+
12
+ gem "rubocop", "~> 1.41"
13
+
14
+ gem 'rubyXL', '~> 3.4', '>= 3.4.25'
data/Gemfile.lock ADDED
@@ -0,0 +1,73 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ caxlsx_builder (0.1.0)
5
+ caxlsx (~> 3.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ caxlsx (3.3.0)
12
+ htmlentities (~> 4.3, >= 4.3.4)
13
+ marcel (~> 1.0)
14
+ nokogiri (~> 1.10, >= 1.10.4)
15
+ rubyzip (>= 1.3.0, < 3)
16
+ diff-lcs (1.5.0)
17
+ htmlentities (4.3.4)
18
+ json (2.6.3)
19
+ marcel (1.0.2)
20
+ nokogiri (1.13.10-aarch64-linux)
21
+ racc (~> 1.4)
22
+ parallel (1.22.1)
23
+ parser (3.1.3.0)
24
+ ast (~> 2.4.1)
25
+ racc (1.6.1)
26
+ rainbow (3.1.1)
27
+ rake (13.0.6)
28
+ regexp_parser (2.6.1)
29
+ rexml (3.2.5)
30
+ rspec (3.12.0)
31
+ rspec-core (~> 3.12.0)
32
+ rspec-expectations (~> 3.12.0)
33
+ rspec-mocks (~> 3.12.0)
34
+ rspec-core (3.12.0)
35
+ rspec-support (~> 3.12.0)
36
+ rspec-expectations (3.12.1)
37
+ diff-lcs (>= 1.2.0, < 2.0)
38
+ rspec-support (~> 3.12.0)
39
+ rspec-mocks (3.12.1)
40
+ diff-lcs (>= 1.2.0, < 2.0)
41
+ rspec-support (~> 3.12.0)
42
+ rspec-support (3.12.0)
43
+ rubocop (1.41.0)
44
+ json (~> 2.3)
45
+ parallel (~> 1.10)
46
+ parser (>= 3.1.2.1)
47
+ rainbow (>= 2.2.2, < 4.0)
48
+ regexp_parser (>= 1.8, < 3.0)
49
+ rexml (>= 3.2.5, < 4.0)
50
+ rubocop-ast (>= 1.23.0, < 2.0)
51
+ ruby-progressbar (~> 1.7)
52
+ unicode-display_width (>= 1.4.0, < 3.0)
53
+ rubocop-ast (1.24.0)
54
+ parser (>= 3.1.1.0)
55
+ ruby-progressbar (1.11.0)
56
+ rubyXL (3.4.25)
57
+ nokogiri (>= 1.10.8)
58
+ rubyzip (>= 1.3.0)
59
+ rubyzip (2.3.2)
60
+ unicode-display_width (2.3.0)
61
+
62
+ PLATFORMS
63
+ aarch64-linux
64
+
65
+ DEPENDENCIES
66
+ caxlsx_builder!
67
+ rake (~> 13.0)
68
+ rspec (~> 3.12)
69
+ rubocop (~> 1.41)
70
+ rubyXL (~> 3.4, >= 3.4.25)
71
+
72
+ BUNDLED WITH
73
+ 2.3.26
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Matthieu CIAPPARA
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,76 @@
1
+ # CaxlsxBuilder
2
+
3
+ Write Excel files with ease using a DSL!
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add caxlsx_builder
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install caxlsx_builder
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+ # Initialize the builder
19
+ builder = CaxlsxBuilder::Builder.new({ 'Test' => [['a', 1.99, 'Yay!'], ['b', 2.015, 'Oh?']] }) do |sheet|
20
+ # Add a custom style
21
+ sheet.add_style(:white_on_brown, { bg_color: '936E00', fg_color: 'FFFFFF' })
22
+
23
+ # Add a first column with a simple footer
24
+ sheet.column('Letters', footer: 'Some letters') { |item| item.first }
25
+
26
+ # Add a second column with some dynamic style, a float type and a dynamic footer
27
+ sheet.column('Numbers',
28
+ style: ->(item) { item[1] > 2 ? :white_on_brown : :default },
29
+ footer: ->(cells) { cells.sum }, type: :float) { |item| item[1] }
30
+
31
+ # Add a last column with default style and no footer
32
+ sheet.column('Interjections') { |item| item.last }
33
+ end
34
+
35
+ # Generate the AXLSX package
36
+ package_axlsx = builder.call
37
+
38
+ # Use Axlsx package's methods to store/write the file
39
+
40
+ # Write on disk
41
+ package_axlsx.serialize('test.xlsx')
42
+
43
+ # Store with ActiveStorage (not a dependency of this gem): https://guides.rubyonrails.org/active_storage_overview.html
44
+ class Export < ApplicationRecord
45
+ has_one_attached :file
46
+ end
47
+
48
+ file = package_axlsx.to_stream.string
49
+ export = Export.new
50
+ export.file.attach(io: StringIO.new(file), filename: 'test.xlsx',
51
+ content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
52
+ ```
53
+
54
+ Result:
55
+
56
+ ![Excel export with 3 columns a header two rows and a footer](/test.xlsx.png)
57
+
58
+ CaxlsxBuilder use the [Caxlsx](https://github.com/caxlsx/caxlsx) gem. The builder directly returns the Caxlsx package to let you do what you want with it.
59
+
60
+ ## Development
61
+
62
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
63
+
64
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
+
66
+ ## Contributing
67
+
68
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Ezveus/caxlsx_builder. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/Ezveus/caxlsx_builder/blob/master/CODE_OF_CONDUCT.md).
69
+
70
+ ## License
71
+
72
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
73
+
74
+ ## Code of Conduct
75
+
76
+ Everyone interacting in the CaxlsxBuilder project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Ezveus/caxlsx_builder/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,172 @@
1
+ module CaxlsxBuilder
2
+
3
+ class Builder
4
+
5
+ # rubocop:disable Naming/VariableNumber
6
+ # I can not change iso_8601 to iso8601 since it is the type expected by Caxlsx.
7
+ CELL_TYPES = %i[date time float integer richtext string boolean iso_8601 text].freeze
8
+ # rubocop:enable Naming/VariableNumber
9
+
10
+ def initialize(sheets, &builder)
11
+ @sheets = sheets
12
+ @builder = builder
13
+ @styles = {}
14
+
15
+ # raise ArgumentError, "`sheets` must be a Hash[String, Array[untyped]]"
16
+ end
17
+
18
+ # @return [Axlsx::Package]
19
+ def call
20
+ # Creating, initializing and returning the +Axlsx+ package
21
+ Axlsx::Package.new do |package|
22
+ package.use_shared_strings = true
23
+
24
+ workbook = package.workbook
25
+
26
+ workbook.styles do |styles|
27
+ define_default_styles(styles)
28
+
29
+ build_sheets(workbook, styles)
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Define some default styles/formats for the spreadsheet:
37
+ # default, header, currency and date
38
+ # @param styles [Axlsx::Styles]
39
+ def define_default_styles(styles)
40
+ # Defines the styles of the spreadsheet
41
+ # default style
42
+ define_style(:default, styles.add_style(sz: 12))
43
+ # for the cells of the header
44
+ define_style(:header, styles.add_style(b: true, sz: 16))
45
+ # for the amounts
46
+ define_style(:currency, styles.add_style(format_code: '#.00 &quot;€&quot;'))
47
+ # for the dates
48
+ define_style(:date, styles.add_style(format_code: 'dd/mm/yyyy'))
49
+ # for the cells of the footer
50
+ define_style(:footer, styles.add_style(sz: 16))
51
+ # for wrapped cells
52
+ define_style(:wrapped, styles.add_style(alignment: { wrap_text: true }))
53
+ end
54
+
55
+ # Register a custom style under the given name
56
+ # @param name [String,Symbol] A name for the style
57
+ # @param style [Integer] An Excel style identifier
58
+ # e.g. the result of `workbook.styles { |styles| styles.add_style() }`
59
+ # @return [String, Symbol] The given name as a Symbol
60
+ def define_style(name, style)
61
+ name = name.to_sym
62
+ @styles[name] = style
63
+
64
+ name
65
+ end
66
+
67
+ # @param workbook [Axlsx::Workbook]
68
+ # @param styles [Axlsx::Styles]
69
+ def build_sheets(workbook, styles)
70
+ @sheets.each do |name, collection|
71
+ sheet = Sheet.new(name)
72
+ @builder.call(sheet)
73
+
74
+ add_custom_styles(sheet, styles)
75
+
76
+ # Add a worksheet
77
+ workbook.add_worksheet(name:) do |worksheet|
78
+ add_header_row(sheet, worksheet)
79
+
80
+ add_data_rows(collection, sheet, worksheet)
81
+
82
+ add_footer_row(sheet, worksheet)
83
+ end
84
+ end
85
+ end
86
+
87
+ # @param sheet [CaxlsxBuilder::Sheet]
88
+ # @param styles [Axlsx::Styles]
89
+ def add_custom_styles(sheet, styles)
90
+ sheet.styles.each do |style_name, style|
91
+ define_style(style_name, styles.add_style(style))
92
+ end
93
+ end
94
+
95
+ # @param sheet [CaxlsxBuilder::Sheet]
96
+ # @param worksheet [Axlsx::Worksheet]
97
+ def add_header_row(sheet, worksheet)
98
+ # First row is the head with the header style applied
99
+ worksheet.add_row(sheet.headers, style: @styles[:header])
100
+ end
101
+
102
+ # @param collection [#each]
103
+ # @param sheet [CaxlsxBuilder::Sheet]
104
+ # @param worksheet [Axlsx::Worksheet]
105
+ def add_data_rows(collection, sheet, worksheet)
106
+ collection.each do |item|
107
+ row = sheet.make_row(item)
108
+
109
+ # Nil row or empty row means no row
110
+ compacted_row = row&.compact
111
+ next if compacted_row.nil? || compacted_row.empty?
112
+
113
+ # Cells options returns an array which is logic: one option per sheet cell
114
+ # But `sheet.add_row` does not handle arrays correctly, so I transform it to a valid
115
+ # Hash using `cast_to_hash`.
116
+ cells = sheet.cell_styles.map { |cell| cell_options(**cell.as_style(item)) }
117
+ options = cast_to_hash(cells)
118
+ worksheet.add_row(row, options)
119
+ end
120
+ end
121
+
122
+ def add_footer_row(sheet, worksheet)
123
+ return if sheet.footers.empty?
124
+
125
+ footer = sheet.footers.map.with_index do |cell, index|
126
+ if cell.respond_to?(:call)
127
+ cell.call(sheet.cells(index))
128
+ else
129
+ cell
130
+ end
131
+ rescue StandardError
132
+ nil
133
+ end
134
+
135
+ # Last row is the footer with the footer style applied
136
+ worksheet.add_row(footer, style: @styles[:footer])
137
+ end
138
+
139
+ # @return [Hash] The options to apply to a cell
140
+ def cell_options(style: nil, type: nil)
141
+ style = get_style(style)
142
+ type = get_type(type)
143
+
144
+ { style:, type: }
145
+ end
146
+
147
+ # Take an array of cell options and map it to a row option
148
+ def cast_to_hash(array)
149
+ return array if array.is_a?(Hash)
150
+
151
+ { style: array.map { |i| i[:style] }, types: array.map { |i| i[:type] } }
152
+ end
153
+
154
+ # @param style [String,Symbol,nil] The name of the style
155
+ # @return [Integer] The Excel style for the given name or the default cell style
156
+ def get_style(style)
157
+ return @styles[:default] if style.nil? || style == ''
158
+
159
+ @styles[style.to_sym] || @styles[:default]
160
+ end
161
+
162
+ # @param type [Symbol,nil]
163
+ # @return [Symbol]
164
+ def get_type(type)
165
+ return :string unless CELL_TYPES.include?(type)
166
+
167
+ type
168
+ end
169
+
170
+ end
171
+
172
+ end
@@ -0,0 +1,19 @@
1
+ module CaxlsxBuilder
2
+
3
+ class Cell
4
+
5
+ def initialize(style: :default, type: :string)
6
+ @style = style
7
+ @type = type
8
+ end
9
+
10
+ def as_style(item)
11
+ style = @style.respond_to?(:call) ? @style.call(item) : @style
12
+ type = @type.respond_to?(:call) ? @type.call(item) : @type
13
+
14
+ { style:, type: }
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,46 @@
1
+ module CaxlsxBuilder
2
+
3
+ class Sheet
4
+
5
+ attr_reader :name, :headers, :cell_styles, :footers, :styles
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ @headers = []
10
+ @cell_styles = []
11
+ @footers = []
12
+ @cell_builders = []
13
+ @row_cells = []
14
+ @styles = {}
15
+ end
16
+
17
+ def column(header, style: :default, type: :string, footer: nil, &cell_builder)
18
+ @headers << header
19
+ @cell_styles << Cell.new(style:, type:)
20
+ @footers << footer
21
+ @cell_builders << cell_builder
22
+ end
23
+
24
+ def add_style(name, style)
25
+ @styles[name] = style
26
+ end
27
+
28
+ def make_row(item)
29
+ # Build the new row
30
+ row = @cell_builders.map do |cell|
31
+ cell.call(item)
32
+ rescue StandardError
33
+ nil
34
+ end
35
+
36
+ # Add the new row to the cache then return it
37
+ (@row_cells << row).last
38
+ end
39
+
40
+ def cells(index)
41
+ @row_cells.flat_map { |row| row[index] }
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaxlsxBuilder
4
+
5
+ VERSION = '1.0.0'
6
+
7
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'caxlsx'
4
+
5
+ require_relative 'caxlsx_builder/version'
6
+ require_relative 'caxlsx_builder/cell'
7
+ require_relative 'caxlsx_builder/sheet'
8
+ require_relative 'caxlsx_builder/builder'
9
+
10
+ module CaxlsxBuilder
11
+ end
@@ -0,0 +1,45 @@
1
+ module Axlsx
2
+ class Package
3
+ end
4
+
5
+ class Styles
6
+ end
7
+
8
+ class Workbook
9
+ end
10
+
11
+ class Worksheet
12
+ end
13
+ end
14
+
15
+ module CaxlsxBuilder
16
+ class Builder
17
+ CELL_TYPES: Array[Cell::type_value]
18
+
19
+ def initialize: (Hash[String, Array[untyped]] sheets) { (Sheet) -> void } -> void
20
+
21
+ def call: () -> Axlsx::Package
22
+
23
+ def define_default_styles: (Axlsx::Styles styles) -> void
24
+
25
+ def define_style: (String | Symbol name, Integer style) -> (String | Symbol)
26
+
27
+ def build_sheets: (Axlsx::Workbook workbook, Axlsx::Styles styles) -> void
28
+
29
+ def add_custom_styles: (Sheet sheet, Axlsx::Styles styles) -> void
30
+
31
+ def add_header_row: (Sheet sheet, Axlsx::Worksheet worksheet) -> void
32
+
33
+ def add_data_rows: (Array[untyped] collection, Sheet sheet, Axlsx::Worksheet worksheet) -> void
34
+
35
+ def add_footer_row: (Sheet sheet, Axlsx::Worksheet worksheet) -> void
36
+
37
+ def cell_options: (style: String? | Symbol?, type: Cell::type_value?) -> { style: Integer, type: Cell::type_value }
38
+
39
+ def cast_to_hash: (Array[{ style: Integer, type: Cell::type_value }] | { style: Array[Integer], types: Array[Cell::type_value] }) -> { style: Array[Integer], types: Array[Cell::type_value] }
40
+
41
+ def get_style: (String? | Symbol? style) -> Integer
42
+
43
+ def get_type: (Cell::type_value?) -> Cell::type_value
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ module CaxlsxBuilder
2
+ class Cell
3
+ type style_proc = ^(untyped) -> Symbol
4
+ type type_value = :date | :time | :float | :integer | :richtext | :string | :boolean | :iso_8601 | :text
5
+ type type_proc = ^(untyped) -> type_value
6
+
7
+ @style: Symbol | style_proc
8
+ @type: type_value | type_proc
9
+
10
+ def initialize: (?style: Symbol | style_proc, ?type: type_value | type_proc) -> void
11
+
12
+ def as_style: (untyped item) -> ({ style: Symbol, type: type_value })
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ module CaxlsxBuilder
2
+ class Sheet
3
+ type cell_value = String? | Numeric?
4
+ type footer_proc = ^(Array[cell_value]) -> cell_value
5
+
6
+ attr_reader name: String
7
+ attr_reader headers: Array[String]
8
+ attr_reader cell_styles: Array[Cell]
9
+ attr_reader footers: Array[cell_value | footer_proc]
10
+ attr_reader styles: Hash[String | Symbol, Hash[Symbol, untyped]]
11
+
12
+ def initialize: (String name) -> void
13
+
14
+ def column: (String header, style: Symbol | Cell::style_proc, type: Cell::type_value | Cell::type_proc,
15
+ footer: cell_value | footer_proc) { (untyped) -> cell_value } -> void
16
+
17
+ def add_style: (String | Symbol name, Hash[Symbol, untyped] style) -> void
18
+
19
+ def make_row: (untyped item) -> Array[cell_value]?
20
+
21
+ def cells: (Integer index) -> Array[cell_value]?
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module CaxlsxBuilder
2
+ VERSION: String
3
+ end
data/test.xlsx.png ADDED
Binary file
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: caxlsx_builder
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthieu CIAPPARA
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-12-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: caxlsx
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ description: Write Excel files with ease using a DSL
28
+ email:
29
+ - matthieu.ciappara@outlook.fr
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rspec"
35
+ - ".rubocop.yml"
36
+ - CHANGELOG.md
37
+ - CODE_OF_CONDUCT.md
38
+ - Gemfile
39
+ - Gemfile.lock
40
+ - LICENSE.txt
41
+ - README.md
42
+ - Rakefile
43
+ - lib/caxlsx_builder.rb
44
+ - lib/caxlsx_builder/builder.rb
45
+ - lib/caxlsx_builder/cell.rb
46
+ - lib/caxlsx_builder/sheet.rb
47
+ - lib/caxlsx_builder/version.rb
48
+ - sig/caxlsx_builder/builder.rbs
49
+ - sig/caxlsx_builder/cell.rbs
50
+ - sig/caxlsx_builder/sheet.rbs
51
+ - sig/caxlsx_builder/version.rbs
52
+ - test.xlsx.png
53
+ homepage: https://github.com/Ezveus/caxlsx_builder
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ homepage_uri: https://github.com/Ezveus/caxlsx_builder
58
+ source_code_uri: https://github.com/Ezveus/caxlsx_builder
59
+ changelog_uri: https://github.com/Ezveus/caxlsx_builder/blob/master/CHANGELOG.md
60
+ rubygems_mfa_required: 'true'
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 3.1.0
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.3.26
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: Write Excel files with ease!
80
+ test_files: []