proforma 1.0.0.pre.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +8 -0
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +11 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +20 -0
  7. data/CHANGELOG.md +7 -0
  8. data/Gemfile +5 -0
  9. data/Gemfile.lock +105 -0
  10. data/Guardfile +16 -0
  11. data/LICENSE +7 -0
  12. data/README.md +328 -0
  13. data/bin/console +11 -0
  14. data/bin/render +68 -0
  15. data/lib/proforma.rb +38 -0
  16. data/lib/proforma/compiling.rb +12 -0
  17. data/lib/proforma/compiling/aggregation.rb +62 -0
  18. data/lib/proforma/compiling/compilable.rb +21 -0
  19. data/lib/proforma/compiling/counter.rb +35 -0
  20. data/lib/proforma/core_ext/hash.rb +21 -0
  21. data/lib/proforma/document.rb +38 -0
  22. data/lib/proforma/hash_evaluator.rb +40 -0
  23. data/lib/proforma/model_factory.rb +38 -0
  24. data/lib/proforma/modeling.rb +21 -0
  25. data/lib/proforma/modeling/banner.rb +64 -0
  26. data/lib/proforma/modeling/collection.rb +34 -0
  27. data/lib/proforma/modeling/data_table.rb +117 -0
  28. data/lib/proforma/modeling/data_table/aggregator.rb +43 -0
  29. data/lib/proforma/modeling/data_table/column.rb +94 -0
  30. data/lib/proforma/modeling/generic_container.rb +57 -0
  31. data/lib/proforma/modeling/grouping.rb +40 -0
  32. data/lib/proforma/modeling/header.rb +18 -0
  33. data/lib/proforma/modeling/pane.rb +40 -0
  34. data/lib/proforma/modeling/pane/column.rb +68 -0
  35. data/lib/proforma/modeling/pane/line.rb +42 -0
  36. data/lib/proforma/modeling/separator.rb +19 -0
  37. data/lib/proforma/modeling/spacer.rb +19 -0
  38. data/lib/proforma/modeling/table.rb +52 -0
  39. data/lib/proforma/modeling/table/cell.rb +49 -0
  40. data/lib/proforma/modeling/table/row.rb +24 -0
  41. data/lib/proforma/modeling/table/section.rb +23 -0
  42. data/lib/proforma/modeling/text.rb +37 -0
  43. data/lib/proforma/modeling/types.rb +10 -0
  44. data/lib/proforma/modeling/types/align.rb +22 -0
  45. data/lib/proforma/plain_text_renderer.rb +106 -0
  46. data/lib/proforma/prototype.rb +28 -0
  47. data/lib/proforma/template.rb +65 -0
  48. data/lib/proforma/type_factory.rb +44 -0
  49. data/lib/proforma/version.rb +12 -0
  50. data/proforma.gemspec +34 -0
  51. data/spec/fixtures/snapshots/custom_table.yml +55 -0
  52. data/spec/fixtures/snapshots/user_details.yml +101 -0
  53. data/spec/fixtures/snapshots/user_list.yml +143 -0
  54. data/spec/proforma/hash_evaluator_spec.rb +26 -0
  55. data/spec/proforma/modeling/table/cell_spec.rb +26 -0
  56. data/spec/proforma/modeling/table/row_spec.rb +42 -0
  57. data/spec/proforma_spec.rb +230 -0
  58. data/spec/spec_helper.rb +25 -0
  59. metadata +211 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 99a81eae5ad4326d4a525793b02615f3bcd5b7ac9bf3dd2e77d06b39f6fafe62
4
+ data.tar.gz: 4c3a71da8fa80f4a93117153ffb2a72ff35bbb624233f814d1669f821e3656d5
5
+ SHA512:
6
+ metadata.gz: 1c77a46115ad3804aa9752ef9d118e2e068a7a4d446b59c93eb6184d8f6101dadedeae52e2764957443533549d275cdea8647c1c49cb5c5fc4dd802cba3b13c7
7
+ data.tar.gz: 3ef60250c4a60b014fe28ae9dc1917f01ef5cab6217ba1310c0662ac920172fa14c4599e9a502dcf6380b6aa70ed63d14e7f8cc38038417ba9aa15c4d5d0d9be
data/.editorconfig ADDED
@@ -0,0 +1,8 @@
1
+ # See http://editorconfig.org/
2
+
3
+ [*]
4
+ trim_trailing_whitespace = true
5
+ indent_style = space
6
+ indent_size = 2
7
+ insert_final_newline = true
8
+ end_of_line = lf
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ *.gem
3
+ /tmp
4
+ /coverage
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+
4
+ Metrics/BlockLength:
5
+ ExcludedMethods: ['let', 'it', 'describe', 'context', 'specify']
6
+
7
+ Metrics/MethodLength:
8
+ Max: 25
9
+
10
+ AllCops:
11
+ TargetRubyVersion: 2.3
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.6.0
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ env:
2
+ global:
3
+ - CC_TEST_REPORTER_ID=01997b4f8e96c9ea5d16ccfce52dad36705571483859d8f0e3cb8fdd8825a758
4
+ language: ruby
5
+ rvm:
6
+ # Build on the latest stable of all supported Rubies (https://www.ruby-lang.org/en/downloads/):
7
+ - 2.3.8
8
+ - 2.4.5
9
+ - 2.5.3
10
+ - 2.6.0
11
+ cache: bundler
12
+ before_script:
13
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
14
+ - chmod +x ./cc-test-reporter
15
+ - ./cc-test-reporter before-build
16
+ script:
17
+ - bundle exec rubocop
18
+ - bundle exec rspec spec --format documentation
19
+ after_script:
20
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # 1.0.0 (April 20th, 2019)
2
+
3
+ Initial implementation.
4
+
5
+ # 0.0.1 (March 14th, 2019)
6
+
7
+ Library Shell.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,105 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ proforma (1.0.0.pre.alpha)
5
+ acts_as_hashable (~> 1)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ acts_as_hashable (1.0.5)
11
+ ansi (1.5.0)
12
+ ast (2.4.0)
13
+ coderay (1.1.2)
14
+ diff-lcs (1.3)
15
+ docile (1.3.1)
16
+ ffi (1.9.25)
17
+ formatador (0.2.5)
18
+ guard (2.15.0)
19
+ formatador (>= 0.2.4)
20
+ listen (>= 2.7, < 4.0)
21
+ lumberjack (>= 1.0.12, < 2.0)
22
+ nenv (~> 0.1)
23
+ notiffany (~> 0.0)
24
+ pry (>= 0.9.12)
25
+ shellany (~> 0.0)
26
+ thor (>= 0.18.1)
27
+ guard-compat (1.2.1)
28
+ guard-rspec (4.7.3)
29
+ guard (~> 2.1)
30
+ guard-compat (~> 1.1)
31
+ rspec (>= 2.99.0, < 4.0)
32
+ hirb (0.7.3)
33
+ jaro_winkler (1.5.2)
34
+ json (2.1.0)
35
+ listen (3.1.5)
36
+ rb-fsevent (~> 0.9, >= 0.9.4)
37
+ rb-inotify (~> 0.9, >= 0.9.7)
38
+ ruby_dep (~> 1.2)
39
+ lumberjack (1.0.13)
40
+ method_source (0.9.2)
41
+ nenv (0.3.0)
42
+ notiffany (0.1.1)
43
+ nenv (~> 0.1)
44
+ shellany (~> 0.0)
45
+ parallel (1.13.0)
46
+ parser (2.6.0.0)
47
+ ast (~> 2.4.0)
48
+ powerpack (0.1.2)
49
+ pry (0.12.2)
50
+ coderay (~> 1.1.0)
51
+ method_source (~> 0.9.0)
52
+ rainbow (3.0.0)
53
+ rb-fsevent (0.10.3)
54
+ rb-inotify (0.9.10)
55
+ ffi (>= 0.5.0, < 2)
56
+ rspec (3.8.0)
57
+ rspec-core (~> 3.8.0)
58
+ rspec-expectations (~> 3.8.0)
59
+ rspec-mocks (~> 3.8.0)
60
+ rspec-core (3.8.0)
61
+ rspec-support (~> 3.8.0)
62
+ rspec-expectations (3.8.2)
63
+ diff-lcs (>= 1.2.0, < 2.0)
64
+ rspec-support (~> 3.8.0)
65
+ rspec-mocks (3.8.0)
66
+ diff-lcs (>= 1.2.0, < 2.0)
67
+ rspec-support (~> 3.8.0)
68
+ rspec-support (3.8.0)
69
+ rubocop (0.63.1)
70
+ jaro_winkler (~> 1.5.1)
71
+ parallel (~> 1.10)
72
+ parser (>= 2.5, != 2.5.1.1)
73
+ powerpack (~> 0.1)
74
+ rainbow (>= 2.2.2, < 4.0)
75
+ ruby-progressbar (~> 1.7)
76
+ unicode-display_width (~> 1.4.0)
77
+ ruby-progressbar (1.10.0)
78
+ ruby_dep (1.5.0)
79
+ shellany (0.0.1)
80
+ simplecov (0.16.1)
81
+ docile (~> 1.1)
82
+ json (>= 1.8, < 3)
83
+ simplecov-html (~> 0.10.0)
84
+ simplecov-console (0.4.2)
85
+ ansi
86
+ hirb
87
+ simplecov
88
+ simplecov-html (0.10.2)
89
+ thor (0.20.3)
90
+ unicode-display_width (1.4.1)
91
+
92
+ PLATFORMS
93
+ ruby
94
+
95
+ DEPENDENCIES
96
+ guard-rspec (~> 4.7)
97
+ proforma!
98
+ pry (~> 0)
99
+ rspec (~> 3.8)
100
+ rubocop (~> 0.63.1)
101
+ simplecov (~> 0.16.1)
102
+ simplecov-console (~> 0.4.2)
103
+
104
+ BUNDLED WITH
105
+ 1.17.3
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ guard :rspec, cmd: 'bundle exec rspec' do
4
+ require 'guard/rspec/dsl'
5
+ dsl = Guard::RSpec::Dsl.new(self)
6
+
7
+ # RSpec files
8
+ rspec = dsl.rspec
9
+ watch(rspec.spec_helper) { rspec.spec_dir }
10
+ watch(rspec.spec_support) { rspec.spec_dir }
11
+ watch(rspec.spec_files)
12
+
13
+ # Ruby files
14
+ ruby = dsl.ruby
15
+ dsl.watch_spec_files_for(ruby.lib_files)
16
+ end
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2019 Blue Marble Payroll, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,328 @@
1
+ # Proforma
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/proforma.svg)](https://badge.fury.io/rb/proforma) [![Build Status](https://travis-ci.org/bluemarblepayroll/proforma.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/proforma) [![Maintainability](https://api.codeclimate.com/v1/badges/71ab6d000e617989b3e1/maintainability)](https://codeclimate.com/github/bluemarblepayroll/proforma/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/71ab6d000e617989b3e1/test_coverage)](https://codeclimate.com/github/bluemarblepayroll/proforma/test_coverage) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+
5
+ Proforma is a Ruby-based document rendering framework with a focus on being:
6
+
7
+ 1. Configurable: the virtual document model allows templates to be defined as configuration
8
+ 2. Simple: basic set of controls that limit flexibility but increase simplicity and standardization
9
+ 3. Extendable: different value resolvers and document rendering engines can be plugged in
10
+
11
+ The basic premise is you input a dataset and a template and Proforma:
12
+
13
+ 1. Evaluate and data-bind each component
14
+ 2. Render as a file
15
+
16
+ When a component is in the process of being data-bound it will be evaluated against the current data being read. The evaluator serves two purposes:
17
+
18
+ 1. Given a record and an expression, find me a raw value (resolving)
19
+ 2. Given a record and an expression, return me a formatted string-based value (text templating)
20
+
21
+ The basic evaluator that comes with this library is Proforma::HashEvaluator. It is limited to:
22
+
23
+ 1. only resolving values for hash objects and cannot handle nesting / recursion / dot notation.
24
+ 2. only text templating simple expressions: if the expression starts with a ```$:``` then it is noted as being a data-bound value. The value proceeding the ```$:``` will be resolved and returned. If it does not begin with this special string then it is deemed just a basic string and it will be returned verbatim.
25
+
26
+ Other libraries that can be plugged in, such as:
27
+
28
+ 1. [![proforma-extended-evaluator](https://github.com/bluemarblepayroll/proforma-extended-evaluator): adds nested dot-notation object value resolution and rich text templating.
29
+ 2. [![proforma-prawn-renderer](https://github.com/bluemarblepayroll/proforma-prawn-renderer)]: adds PDF rendering.
30
+
31
+ See the libraries respective README files for more information.
32
+
33
+ ## Installation
34
+
35
+ To install through Rubygems:
36
+
37
+ ````
38
+ gem install install proforma
39
+ ````
40
+
41
+ You can also add this to your Gemfile:
42
+
43
+ ````
44
+ bundle add proforma
45
+ ````
46
+
47
+ ## Examples
48
+
49
+ *The examples in this library will be based on the default plugins that are contained within package and thus will be as bare bones as they can be. See other plugin library documentation to see how the input and output can be greatly enhanced.*
50
+
51
+ ## Virtual Document Object Model Introduction
52
+
53
+ The following is a list of components that can be used for modeling:
54
+
55
+ * Banner: define image, title, and details
56
+ * DataTable: define columns with header, body, and footer contents
57
+ * Grouping: one-to-many dataset traversal
58
+ * Header: large, bold text
59
+ * Pane: define columns and lines (akin to a details section)
60
+ * Separator: draw a line
61
+ * Spacer: blank space below component
62
+ * Text: basic text
63
+
64
+ Most aspects of the components will be data-bound.
65
+
66
+ ### Getting Started: Rendering a List
67
+
68
+ Let's say we have a list of users:
69
+
70
+ ````ruby
71
+ data = [
72
+ { id: 1, first: 'Matt', last: 'Smith' },
73
+ { id: 2, first: 'Katie', last: 'Rizzo' },
74
+ { id: 3, first: 'Nathan', last: 'Nathanson' }
75
+ ]
76
+
77
+ template = {
78
+ title: 'User List',
79
+ children: [
80
+ {
81
+ type: 'DataTable',
82
+ columns: [
83
+ { header: 'ID Number', body: '$:id' },
84
+ { header: 'First Name', body: '$:first' },
85
+ { header: 'Last Name', body: '$:last' }
86
+ ]
87
+ }
88
+ ]
89
+ }
90
+
91
+ documents = Proforma.render(data, template)
92
+ ````
93
+
94
+ The `documents` variable will now be an array with only one document object:
95
+
96
+ ````ruby
97
+ documents = [
98
+ {
99
+ contents: "ID Number, First Name, Last Name\n1, Matt, Smith\n...", # condensed
100
+ extension: ".txt",
101
+ title: "User List"
102
+ )
103
+ ]
104
+ ````
105
+
106
+ The `contents` attribute will be the rendered text-based table.
107
+
108
+ ### Rendering Records
109
+
110
+ Let's now say instead of rendering a table we want to render one unique document per record:
111
+
112
+ ````ruby
113
+ data = [
114
+ { id: 1, first: 'Matt', last: 'Smith' },
115
+ { id: 2, first: 'Katie', last: 'Rizzo' },
116
+ { id: 3, first: 'Nathan', last: 'Nathanson' }
117
+ ]
118
+
119
+ template = {
120
+ title: 'User Details',
121
+ split: true, # notice the split directive here.
122
+ children: [
123
+ {
124
+ type: 'Pane',
125
+ columns: [
126
+ {
127
+ lines: [
128
+ { label: 'ID Number', value: '$:id' },
129
+ { label: 'First Name', value: '$:first' }
130
+ ]
131
+ },
132
+ {
133
+ lines: [
134
+ { label: 'Last Name', value: '$:last' }
135
+ ]
136
+ }
137
+ ]
138
+ }
139
+ ]
140
+ }
141
+
142
+ documents = Proforma.render(data, template)
143
+ ````
144
+
145
+ The `documents` variable will now be an array with three document objects:
146
+
147
+ ````ruby
148
+ documents = [
149
+ {
150
+ contents: "ID Number: 1\nFirst Name: Matt\nLast Name: Smith\n",
151
+ extension: ".txt",
152
+ title: "User Details"
153
+ },
154
+ {
155
+ contents: "ID Number: 2\nFirst Name: Katie\nLast Name: Rizzo\n",
156
+ extension: ".txt",
157
+ title: "User Details"
158
+ },
159
+ {
160
+ contents: "ID Number: 3\nFirst Name: Nathan\nLast Name: Nathanson\n",
161
+ extension: ".txt",
162
+ title: "User Details"
163
+ }
164
+ ]
165
+ ````
166
+
167
+ Each document will have a `contents` attribute that contains its respective rendered text-based pane.
168
+
169
+ ### Bringing It All Together
170
+
171
+ Let's build on our previous user list example and add more data. With this additional data, we will add sub-data tables using grouping:
172
+
173
+ ````ruby
174
+ data = [
175
+ {
176
+ id: 1,
177
+ first: 'Matt',
178
+ last: 'Smith',
179
+ phone_numbers: [
180
+ { type: 'Mobile', number: '444-333-2222' },
181
+ { type: 'Home', number: '444-333-2222' }
182
+ ]
183
+ },
184
+ {
185
+ id: 2,
186
+ first: 'Katie',
187
+ last: 'Rizzo',
188
+ phone_numbers: [
189
+ { type: 'Fax', number: '888-777-6666' }
190
+ ]
191
+ },
192
+ {
193
+ id: 3,
194
+ first: 'Nathan',
195
+ last: 'Nathanson',
196
+ phone_numbers: []
197
+ }
198
+ ]
199
+
200
+ template = {
201
+ title: 'User Report',
202
+ children: [
203
+ {
204
+ type: 'Banner',
205
+ title: 'System A',
206
+ details: "555 N. Michigan Ave.\nChicago, IL 55555\n555-555-5555 ext. 5132"
207
+ },
208
+ { type: 'Header', value: 'User List' },
209
+ { type: 'Separator' },
210
+ { type: 'Spacer' },
211
+ {
212
+ type: 'DataTable',
213
+ columns: [
214
+ { header: 'ID Number', body: '$:id' },
215
+ { header: 'First Name', body: '$:first' },
216
+ { header: 'Last Name', body: '$:last' }
217
+ ]
218
+ },
219
+ { type: 'Spacer' },
220
+ {
221
+ type: 'Grouping',
222
+ children: [
223
+ { type: 'Header', value: 'User Details' },
224
+ { type: 'Separator' },
225
+ { type: 'Spacer' },
226
+ {
227
+ type: 'Pane',
228
+ columns: [
229
+ {
230
+ lines: [
231
+ { label: 'ID Number', value: '$:id' },
232
+ { label: 'First Name', value: '$:first' }
233
+ ]
234
+ },
235
+ {
236
+ lines: [
237
+ { label: 'Last Name', value: '$:last' }
238
+ ]
239
+ }
240
+ ]
241
+ },
242
+ {
243
+ type: 'DataTable',
244
+ property: 'phone_numbers',
245
+ columns: [
246
+ { header: 'Type', body: '$:type' },
247
+ { header: 'Number', body: '$:number' }
248
+ ]
249
+ },
250
+ { type: 'Spacer' }
251
+ ]
252
+ }
253
+ ]
254
+ }
255
+
256
+ documents = Proforma.render(data, template)
257
+ ````
258
+
259
+ The `documents` variable will now be an array with only one document object:
260
+
261
+ ````ruby
262
+ documents = [
263
+ {
264
+ contents: '========================================\nSystem A\n=======Nathan...', # ...
265
+ extension: '.txt',
266
+ title: 'User Report'
267
+ }
268
+ ]
269
+ ````
270
+
271
+ The `contents` attribute will now contain:
272
+
273
+ * banner
274
+ * user summary table
275
+ * user details pane (one per user)
276
+ * user phone numbers table (one per user)
277
+
278
+ ## Contributing
279
+
280
+ ### Development Environment Configuration
281
+
282
+ Basic steps to take to get this repository compiling:
283
+
284
+ 1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check proforma.gemspec for versions supported)
285
+ 2. Install bundler (gem install bundler)
286
+ 3. Clone the repository (git clone git@github.com:bluemarblepayroll/proforma.git)
287
+ 4. Navigate to the root folder (cd proforma)
288
+ 5. Install dependencies (bundle)
289
+
290
+ ### Running Tests
291
+
292
+ To execute the test suite run:
293
+
294
+ ````
295
+ bundle exec rspec spec --format documentation
296
+ ````
297
+
298
+ Alternatively, you can have Guard watch for changes:
299
+
300
+ ````
301
+ bundle exec guard
302
+ ````
303
+
304
+ Also, do not forget to run Rubocop:
305
+
306
+ ````
307
+ bundle exec rubocop
308
+ ````
309
+
310
+ ### Publishing
311
+
312
+ Note: ensure you have proper authorization before trying to publish new versions.
313
+
314
+ After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
315
+
316
+ 1. Merge Pull Request into master
317
+ 2. Update ```lib/proforma/version.rb``` using [semantic versioning](https://semver.org/)
318
+ 3. Install dependencies: ```bundle```
319
+ 4. Update ```CHANGELOG.md``` with release notes
320
+ 5. Commit & push master to remote and ensure CI builds master successfully
321
+ 6. Build the project locally: `gem build proforma`
322
+ 7. Publish package to RubyGems: `gem push proforma-X.gem` where X is the version to push
323
+ 8. Tag master with new version: `git tag <version>`
324
+ 9. Push tags remotely: `git push origin --tags`
325
+
326
+ ## License
327
+
328
+ This project is MIT Licensed.