reportinator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +11 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +79 -0
- data/LICENSE.txt +21 -0
- data/README.md +397 -0
- data/Rakefile +10 -0
- data/app/reports/example.report.json +20 -0
- data/app/reports/multiplication.report.json +15 -0
- data/lib/reportinator/config.rb +28 -0
- data/lib/reportinator/loader.rb +112 -0
- data/lib/reportinator/parsers/method.rb +61 -0
- data/lib/reportinator/parsers/value.rb +144 -0
- data/lib/reportinator/report.rb +51 -0
- data/lib/reportinator/types/model.rb +30 -0
- data/lib/reportinator/types/preset.rb +5 -0
- data/lib/reportinator/version.rb +5 -0
- data/lib/reportinator.rb +69 -0
- data/sig/reportinator.rbs +4 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bb46422febcfaac711a20fbb21418ed224bc22e59468e9295dfccdd0c7ca309e
|
4
|
+
data.tar.gz: afba11ce1ecc80668797392b5c24eeffc715d349b78f93fa7d6e7ea8dfdc096a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8c604e8d6b30dff8df130dd6af08fcbc7a6ee0a4bb4a9e3c39478e97f2d3a2bb073c4543dcd2f366c4a39b5b15c97925d64bcba4060ed344c4c95de616507d91
|
7
|
+
data.tar.gz: 678d1ed69042d72e8f0c146abcc0a55becb31a7961f034bb58d63a1f2999100443f13e91fd71129bcaab33616a1bc0361bbac7dd62ae5003c21159574c2c4ef0
|
data/.rspec
ADDED
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -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 dev@moxvallix.com. 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
data/Gemfile.lock
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
reportinator (0.1.0)
|
5
|
+
activemodel (~> 7.0)
|
6
|
+
require_all (~> 3.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activemodel (7.0.4)
|
12
|
+
activesupport (= 7.0.4)
|
13
|
+
activesupport (7.0.4)
|
14
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
15
|
+
i18n (>= 1.6, < 2)
|
16
|
+
minitest (>= 5.1)
|
17
|
+
tzinfo (~> 2.0)
|
18
|
+
ast (2.4.2)
|
19
|
+
concurrent-ruby (1.1.10)
|
20
|
+
diff-lcs (1.5.0)
|
21
|
+
i18n (1.12.0)
|
22
|
+
concurrent-ruby (~> 1.0)
|
23
|
+
json (2.6.2)
|
24
|
+
minitest (5.16.3)
|
25
|
+
parallel (1.22.1)
|
26
|
+
parser (3.1.2.1)
|
27
|
+
ast (~> 2.4.1)
|
28
|
+
rainbow (3.1.1)
|
29
|
+
rake (13.0.6)
|
30
|
+
regexp_parser (2.6.0)
|
31
|
+
require_all (3.0.0)
|
32
|
+
rexml (3.2.5)
|
33
|
+
rspec (3.11.0)
|
34
|
+
rspec-core (~> 3.11.0)
|
35
|
+
rspec-expectations (~> 3.11.0)
|
36
|
+
rspec-mocks (~> 3.11.0)
|
37
|
+
rspec-core (3.11.0)
|
38
|
+
rspec-support (~> 3.11.0)
|
39
|
+
rspec-expectations (3.11.1)
|
40
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
41
|
+
rspec-support (~> 3.11.0)
|
42
|
+
rspec-mocks (3.11.1)
|
43
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
44
|
+
rspec-support (~> 3.11.0)
|
45
|
+
rspec-support (3.11.1)
|
46
|
+
rubocop (1.35.1)
|
47
|
+
json (~> 2.3)
|
48
|
+
parallel (~> 1.10)
|
49
|
+
parser (>= 3.1.2.1)
|
50
|
+
rainbow (>= 2.2.2, < 4.0)
|
51
|
+
regexp_parser (>= 1.8, < 3.0)
|
52
|
+
rexml (>= 3.2.5, < 4.0)
|
53
|
+
rubocop-ast (>= 1.20.1, < 2.0)
|
54
|
+
ruby-progressbar (~> 1.7)
|
55
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
56
|
+
rubocop-ast (1.21.0)
|
57
|
+
parser (>= 3.1.1.0)
|
58
|
+
rubocop-performance (1.14.3)
|
59
|
+
rubocop (>= 1.7.0, < 2.0)
|
60
|
+
rubocop-ast (>= 0.4.0)
|
61
|
+
ruby-progressbar (1.11.0)
|
62
|
+
standard (1.16.1)
|
63
|
+
rubocop (= 1.35.1)
|
64
|
+
rubocop-performance (= 1.14.3)
|
65
|
+
tzinfo (2.0.5)
|
66
|
+
concurrent-ruby (~> 1.0)
|
67
|
+
unicode-display_width (2.3.0)
|
68
|
+
|
69
|
+
PLATFORMS
|
70
|
+
x86_64-linux
|
71
|
+
|
72
|
+
DEPENDENCIES
|
73
|
+
rake (~> 13.0)
|
74
|
+
reportinator!
|
75
|
+
rspec (~> 3.0)
|
76
|
+
standard (~> 1.3)
|
77
|
+
|
78
|
+
BUNDLED WITH
|
79
|
+
2.3.11
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Moxvallix
|
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,397 @@
|
|
1
|
+
# Reportinator
|
2
|
+
## Behold! My Report-inator!
|
3
|
+
|
4
|
+
**Warning: this gem can execute methods based on strings defined in a JSON file. Use at your own caution.**
|
5
|
+
**This gem has not been security audited, and should not be used in a production environment!**
|
6
|
+
|
7
|
+
Reportinator is a gem that allows you to easily define a report using a JSON file.
|
8
|
+
Report templates can reference other report templates, allowing for report "partials" to easily be re-used.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Install the gem and add to the application's Gemfile by executing:
|
13
|
+
|
14
|
+
$ bundle add reportinator
|
15
|
+
|
16
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
17
|
+
|
18
|
+
$ gem install reportinator
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
### Creating my first Report
|
22
|
+
Let's start by considering what we want our output to be.
|
23
|
+
Say we want a multiplication table, like such:
|
24
|
+
| nx1 | nx2 | nx3 | nx4 | nx5 |
|
25
|
+
|-----|-----|-----|-----|-----|
|
26
|
+
| 1 | 2 | 3 | 4 | 5 |
|
27
|
+
| 2 | 4 | 6 | 8 | 10 |
|
28
|
+
| 3 | 6 | 9 | 12 | 15 |
|
29
|
+
| 4 | 8 | 12 | 16 | 20 |
|
30
|
+
| 5 | 10 | 15 | 20 | 25 |
|
31
|
+
|
32
|
+
Make a new file in your `app/reports` directory.
|
33
|
+
Name it `multiplication.report.json`
|
34
|
+
|
35
|
+
Set it's type to ":preset". The ":preset" type takes one parameter, "data",
|
36
|
+
and returns any values passed inside it, but with their values parsed.
|
37
|
+
We put a colon in front of the word "preset", such that the value parser
|
38
|
+
knows to turn it into a symbol.
|
39
|
+
|
40
|
+
```
|
41
|
+
{
|
42
|
+
"type": ":preset"
|
43
|
+
}
|
44
|
+
```
|
45
|
+
|
46
|
+
Next we need to give it parameters to be passed into the report.
|
47
|
+
":preset" only accepts the "data" parameter.
|
48
|
+
Add "data" to a "params" object, and set it to be an empty array.
|
49
|
+
|
50
|
+
```
|
51
|
+
{
|
52
|
+
"type": ":preset",
|
53
|
+
"params": {
|
54
|
+
"data": []
|
55
|
+
}
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
You can now try running this report:
|
60
|
+
|
61
|
+
```
|
62
|
+
> Reportinator.report("multiplication")
|
63
|
+
=> []
|
64
|
+
```
|
65
|
+
|
66
|
+
If all went to plan, you should have gotten an empty array.
|
67
|
+
Let's now add some data to this bad boy.
|
68
|
+
|
69
|
+
```
|
70
|
+
{
|
71
|
+
"type": ":preset",
|
72
|
+
"params": {
|
73
|
+
"data": ["nx1","nx2","nx3","nx4","nx5"]
|
74
|
+
}
|
75
|
+
}
|
76
|
+
```
|
77
|
+
```
|
78
|
+
> Reportinator.report("multiplication")
|
79
|
+
=> [["nx1", "nx2", "nx3", "nx4", "nx5"]]
|
80
|
+
```
|
81
|
+
|
82
|
+
Now we could add the other rows ourselves, by adding more rows to "data":
|
83
|
+
|
84
|
+
```
|
85
|
+
{
|
86
|
+
"type": ":preset",
|
87
|
+
"params": {
|
88
|
+
"data": [
|
89
|
+
["nx1","nx2","nx3","nx4","nx5"],
|
90
|
+
[1, 2, 3, 4, 5],
|
91
|
+
[2, 4, 6, 8, 10],
|
92
|
+
[3, 6, 9, 12, 15],
|
93
|
+
[4, 8, 12, 16, 20],
|
94
|
+
[5, 10, 15, 20, 25]
|
95
|
+
]
|
96
|
+
}
|
97
|
+
}
|
98
|
+
```
|
99
|
+
```
|
100
|
+
> Reportinator.report("multiplication")
|
101
|
+
=>
|
102
|
+
[
|
103
|
+
["nx1", "nx2", "nx3", "nx4", "nx5"],
|
104
|
+
[1, 2, 3, 4, 5],
|
105
|
+
[2, 4, 6, 8, 10],
|
106
|
+
[3, 6, 9, 12, 15],
|
107
|
+
[4, 8, 12, 16, 20],
|
108
|
+
[5, 10, 15, 20, 25]
|
109
|
+
]
|
110
|
+
```
|
111
|
+
|
112
|
+
However, there is a cleaner way of doing this.
|
113
|
+
Move your entire report object inside of an array.
|
114
|
+
This allows us to string reports together in the same template.
|
115
|
+
|
116
|
+
```
|
117
|
+
[
|
118
|
+
{
|
119
|
+
"type": ":preset",
|
120
|
+
"params": {
|
121
|
+
"data": ["nx1","nx2","nx3","nx4","nx5"]
|
122
|
+
}
|
123
|
+
}
|
124
|
+
]
|
125
|
+
```
|
126
|
+
|
127
|
+
Add a new report object underneath the first.
|
128
|
+
This time, the type will be ":model".
|
129
|
+
|
130
|
+
":model" reports take two parameters:
|
131
|
+
1. "target"
|
132
|
+
2. "method_list"
|
133
|
+
|
134
|
+
Add both these keys to the "params" of the second report object.
|
135
|
+
Set both to be an empty array.
|
136
|
+
|
137
|
+
```
|
138
|
+
[
|
139
|
+
{
|
140
|
+
"type": ":preset",
|
141
|
+
"params": {
|
142
|
+
"data": ["nx1","nx2","nx3","nx4","nx5"]
|
143
|
+
}
|
144
|
+
},
|
145
|
+
{
|
146
|
+
"type": ":model",
|
147
|
+
"params": {
|
148
|
+
"target": [],
|
149
|
+
"method_list": []
|
150
|
+
}
|
151
|
+
}
|
152
|
+
]
|
153
|
+
```
|
154
|
+
|
155
|
+
Model reports take a target, as specified in "target", and run methods against it,
|
156
|
+
specified in "method_list", saving the outputs of each to the row.
|
157
|
+
|
158
|
+
If the target is enumerable, said methods will run on each enumeration of the target,
|
159
|
+
each enumeration adding a new row to the report.
|
160
|
+
|
161
|
+
A method is specified by either a symbol, array or hash.
|
162
|
+
Lets take the string "100" as our target.
|
163
|
+
|
164
|
+
If our method was to be `":reverse"`, it would be the same as running"
|
165
|
+
|
166
|
+
```
|
167
|
+
> "100".reverse
|
168
|
+
=> "001"
|
169
|
+
```
|
170
|
+
|
171
|
+
We can chain methods using an array. For example: `[":reverse", ":to_i"]`
|
172
|
+
|
173
|
+
```
|
174
|
+
> "100".reverse.to_i
|
175
|
+
=> 1
|
176
|
+
```
|
177
|
+
|
178
|
+
Methods inside a hash allow for parameters to be passed to the method.
|
179
|
+
The value of the hash are passed as the parameters, and an array is passed
|
180
|
+
as multiple parameters.
|
181
|
+
|
182
|
+
Eg. `{"gsub": ["0", "1"]}`
|
183
|
+
|
184
|
+
```
|
185
|
+
> "100".gsub("0", "1")
|
186
|
+
=> "111"
|
187
|
+
```
|
188
|
+
|
189
|
+
In Ruby, it turns out the multiplication "*" sign is a method.
|
190
|
+
Using this, we can write a much smarter report.
|
191
|
+
|
192
|
+
```
|
193
|
+
[
|
194
|
+
{
|
195
|
+
"type": ":preset",
|
196
|
+
"params": {
|
197
|
+
"data": ["nx1","nx2","nx3","nx4","nx5"]
|
198
|
+
}
|
199
|
+
},
|
200
|
+
{
|
201
|
+
"type": ":model",
|
202
|
+
"params": {
|
203
|
+
"target": [1, 2, 3, 4, 5],
|
204
|
+
"method_list": [{"*": 1},{"*": 2},{"*": 3},{"*": 4},{"*": 5}]
|
205
|
+
}
|
206
|
+
}
|
207
|
+
]
|
208
|
+
```
|
209
|
+
|
210
|
+
The "*" is behaving exactly the same way as our "gsub" example earlier.
|
211
|
+
|
212
|
+
If we run our report again:
|
213
|
+
|
214
|
+
```
|
215
|
+
> Reportinator.report("multiplication")
|
216
|
+
=>
|
217
|
+
[
|
218
|
+
["nx1", "nx2", "nx3", "nx4", "nx5"],
|
219
|
+
[1, 2, 3, 4, 5],
|
220
|
+
[2, 4, 6, 8, 10],
|
221
|
+
[3, 6, 9, 12, 15],
|
222
|
+
[4, 8, 12, 16, 20],
|
223
|
+
[5, 10, 15, 20, 25]
|
224
|
+
]
|
225
|
+
```
|
226
|
+
|
227
|
+
The result should be exactly the same.
|
228
|
+
|
229
|
+
This is pretty good, but we can do better!
|
230
|
+
Notice how the "target" was an array? As it is enumerable,
|
231
|
+
we could run our methods against each element within it.
|
232
|
+
|
233
|
+
But what if we wanted to have 10 rows? Or 50? Soon our array is going to get pretty long.
|
234
|
+
|
235
|
+
This is where a range would be perfect. Set the start value to 1, the end to whatever number we need,
|
236
|
+
and then we go from there.
|
237
|
+
|
238
|
+
Unfortunately, we can't use a range in JSON.
|
239
|
+
|
240
|
+
... or can we?
|
241
|
+
|
242
|
+
Reportinator has a bunch of handy built in functions, for converting strings.
|
243
|
+
We have already seen ":symbol" to make a string into a symbol.
|
244
|
+
|
245
|
+
We won't explore all the functions now, but we will explore "!r".
|
246
|
+
Or more specifically, "!rn", which auto converts strings into numbers as well.
|
247
|
+
|
248
|
+
We can make a range simply by writing "!rn 1,5". It takes the number before the comma,
|
249
|
+
as the start of the range, and the one after as the end.
|
250
|
+
|
251
|
+
We can test this with the actual parse method:
|
252
|
+
|
253
|
+
```
|
254
|
+
> Reportinator::ValueParser.parse("!rn 1, 5")
|
255
|
+
=> (1..5)
|
256
|
+
```
|
257
|
+
|
258
|
+
Let's add this now as the target of our report:
|
259
|
+
|
260
|
+
```
|
261
|
+
[
|
262
|
+
{
|
263
|
+
"type": ":preset",
|
264
|
+
"params": {
|
265
|
+
"data": ["nx1","nx2","nx3","nx4","nx5"]
|
266
|
+
}
|
267
|
+
},
|
268
|
+
{
|
269
|
+
"type": ":model",
|
270
|
+
"params": {
|
271
|
+
"target": "!rn 1,5",
|
272
|
+
"method_list": [{"*": 1},{"*": 2},{"*": 3},{"*": 4},{"*": 5}]
|
273
|
+
}
|
274
|
+
}
|
275
|
+
]
|
276
|
+
```
|
277
|
+
|
278
|
+
Finally, rather than peering at the console to see if it worked,
|
279
|
+
lets put it into a csv.
|
280
|
+
|
281
|
+
```
|
282
|
+
> Reportinator.output("multiplication.csv","multiplication")
|
283
|
+
=> "multiplication.csv"
|
284
|
+
```
|
285
|
+
|
286
|
+
Open the csv up in your spreadsheet viewer of choice, and revel
|
287
|
+
in your brand new report!
|
288
|
+
### Reports in more detail
|
289
|
+
#### The Report Template Object
|
290
|
+
A Report template has four attributes:
|
291
|
+
|
292
|
+
| key | type | description |
|
293
|
+
|-----------|--------|----------------------------------------------------|
|
294
|
+
| type | symbol | specifies the report type to use |
|
295
|
+
| variables | hash | defines variables to be used with the `$` function |
|
296
|
+
| template | string | references another template to load and merge with |
|
297
|
+
| params | hash | report specific parameters |
|
298
|
+
|
299
|
+
#### Reportinator String Parse Cheatsheet
|
300
|
+
| prefix | example | output |
|
301
|
+
|--------|-----------------------------|--------------------------------------------|
|
302
|
+
| `:` | ":symbol" | :symbol |
|
303
|
+
| `&` | "&Constant" | Constant |
|
304
|
+
| `$` | "$variable" | Value of key `variable` in variables hash. |
|
305
|
+
| `!a` | "!a 1,2,3" | 6 |
|
306
|
+
| `!d` | "!d 1970-01-01" | 1970-01-01 00:00:00 |
|
307
|
+
| `!n` | "!n 100" | 100 |
|
308
|
+
| `!r` | "!r a,z" | ("a".."z") |
|
309
|
+
| `!rd` | "!rd 1970-01-01,1979-01-01" | (1970-01-01 00:00:00..1979-01-01 00:00:00) |
|
310
|
+
| `!rn` | "!rn 1,100" | (1..100) |
|
311
|
+
|
312
|
+
#### Reportinator Method Parse Cheatsheet
|
313
|
+
When an array has a string as it's first value, and that string has the `#` prefix,
|
314
|
+
that string is parsed, and the result becomes the target of the following methods.
|
315
|
+
|
316
|
+
Hashes within the array take the first key in the hash as the method,
|
317
|
+
and the first value as parameters for that method. If the first value
|
318
|
+
is an array, each item in the array is sent as a seperate parameter.
|
319
|
+
|
320
|
+
Subsequent symbols in the array are sent as methods to the target.
|
321
|
+
| method array | ruby equivalent |
|
322
|
+
|------------------------------------------------|-------------------------------|
|
323
|
+
| `["#&Date", ":today"]` | Date.today |
|
324
|
+
| `["#&Date", ":today", ":to_s"]` | Date.today.to_s |
|
325
|
+
| `["#&Date", ":today", {"strftime": "%b, %Y"}]` | Date.today.strftime("%b, %Y") |
|
326
|
+
| `["#&Range", {"new": [1,100]}]` | Range.new(1, 100) |
|
327
|
+
|
328
|
+
### Where to put my Reports?
|
329
|
+
By default, Reportinator checks `app/reports` for reports.
|
330
|
+
It checks for files named `*.json` and `*.report.json`
|
331
|
+
More locations and suffixes can be added in the config.
|
332
|
+
|
333
|
+
### Getting your Report's Output
|
334
|
+
`Reportinator.report(template, params)` will output a two dimensional array.
|
335
|
+
If you picture this as a table, each sub array is a row.
|
336
|
+
`params` is optional.
|
337
|
+
|
338
|
+
`Reportinator.output(template, params, filename)` will output the report to a csv,
|
339
|
+
in the configured output directory.
|
340
|
+
`params` and `filename` are optional.
|
341
|
+
|
342
|
+
Template is the name of the template file, minus the ".json" suffix.
|
343
|
+
Here is how templates are resolved:
|
344
|
+
- "profit" => "app/reports/profit.json"
|
345
|
+
- "users/joined" => "app/reports/users/joined.json"
|
346
|
+
|
347
|
+
Params is a hash, accepting the same keys as a template would.
|
348
|
+
Params are merged with those provided by the template, overriding any conflicts.
|
349
|
+
|
350
|
+
### Configuring Reportinator
|
351
|
+
```
|
352
|
+
Reportinator.configuration do |config|
|
353
|
+
config.output_directory = "my/report/dir"
|
354
|
+
config.report_directories = ["first/directory","other/directory"]
|
355
|
+
config.report_suffixes = ["custom.json", "txt"]
|
356
|
+
config.report_types = {
|
357
|
+
my_type: "MyModule::MyReportType"
|
358
|
+
}
|
359
|
+
end
|
360
|
+
```
|
361
|
+
Configuration set will not override the default configuration.
|
362
|
+
The keys used in report types, eg. `my_type`, will be the same used in the "type" field
|
363
|
+
of the reports.
|
364
|
+
|
365
|
+
### Making a Custom Report Type
|
366
|
+
The requirements to make a Report are very simple.
|
367
|
+
1. The report must inherit from `Reportinator::Report`
|
368
|
+
2. The report must provide a `data` method, which returns a one or two dimensional array.
|
369
|
+
|
370
|
+
For example, this is the entire code for the Preset Report:
|
371
|
+
```
|
372
|
+
module Reportinator
|
373
|
+
class PresetReport < Report
|
374
|
+
attribute :data, default: []
|
375
|
+
end
|
376
|
+
end
|
377
|
+
```
|
378
|
+
Once a report has been written, it must be registed as a report type.
|
379
|
+
See the configuration section for more details.
|
380
|
+
|
381
|
+
## Development
|
382
|
+
|
383
|
+
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.
|
384
|
+
|
385
|
+
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).
|
386
|
+
|
387
|
+
## Contributing
|
388
|
+
|
389
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/reportinator. 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/[USERNAME]/reportinator/blob/master/CODE_OF_CONDUCT.md).
|
390
|
+
|
391
|
+
## License
|
392
|
+
|
393
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
394
|
+
|
395
|
+
## Code of Conduct
|
396
|
+
|
397
|
+
Everyone interacting in the Reportinator project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/reportinator/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"type": ":preset",
|
4
|
+
"variables": {"variable": "i am a variable"},
|
5
|
+
"params": {
|
6
|
+
"data": [
|
7
|
+
"string", ":symbol", "&Reportinator",
|
8
|
+
"$variable", "!i 1234", "!a !i 2, 2",
|
9
|
+
["#!d 1970-01-01", {"strftime": "%b, %Y"}], "!r !i 1, !i 5"
|
10
|
+
]
|
11
|
+
}
|
12
|
+
},
|
13
|
+
{
|
14
|
+
"type": ":model",
|
15
|
+
"params": {
|
16
|
+
"target": ["#&Reportinator", ":config"],
|
17
|
+
"method_list": [":configured_directories", ":configured_suffixes", ":configured_types"]
|
18
|
+
}
|
19
|
+
}
|
20
|
+
]
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class Config < Base
|
3
|
+
DEFAULT_TYPES = {
|
4
|
+
model: "Reportinator::ModelReport",
|
5
|
+
preset: "Reportinator::PresetReport"
|
6
|
+
}
|
7
|
+
DEFAULT_REPORT_DIRS = ["reports", "app/reports"]
|
8
|
+
DEFAULT_REPORT_SUFFIXES = ["report.json", "json"]
|
9
|
+
|
10
|
+
attribute :report_directories, default: []
|
11
|
+
attribute :report_suffixes, default: []
|
12
|
+
attribute :report_types, default: {}
|
13
|
+
attribute :output_directory, default: "reports"
|
14
|
+
|
15
|
+
def configured_directories
|
16
|
+
DEFAULT_REPORT_DIRS + report_directories
|
17
|
+
end
|
18
|
+
|
19
|
+
def configured_suffixes
|
20
|
+
DEFAULT_REPORT_SUFFIXES + report_suffixes
|
21
|
+
end
|
22
|
+
|
23
|
+
def configured_types
|
24
|
+
types = DEFAULT_TYPES
|
25
|
+
types.merge(report_types)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class Loader < Base
|
3
|
+
attribute :type
|
4
|
+
attribute :template
|
5
|
+
attribute :variables
|
6
|
+
attribute :params
|
7
|
+
|
8
|
+
def self.data_from_template(template, additional_params = {})
|
9
|
+
template_data = load_template(template, additional_params)
|
10
|
+
return split_rows(template_data.data) unless template_data.instance_of?(Array)
|
11
|
+
output = []
|
12
|
+
template_data.each { |report| output += report.data }
|
13
|
+
split_rows(output)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.load_template(template, additional_params = {})
|
17
|
+
data = parse_template(template)
|
18
|
+
return load_multiple(data, additional_params) if data.instance_of?(Array)
|
19
|
+
load_singular(data, additional_params)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.load_multiple(data, additional_params)
|
23
|
+
data.map { |report| load_singular(report, additional_params) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.load_singular(data, additional_params)
|
27
|
+
data.merge!(additional_params) { |key, old_value, new_value| merge_values(new_value, old_value) }
|
28
|
+
filtered_data = filter_params(data, attribute_names)
|
29
|
+
variables = filtered_data[:variables]
|
30
|
+
parsed_data = ValueParser.parse(filtered_data, variables)
|
31
|
+
new(parsed_data).report
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.find_template(template)
|
35
|
+
suffixes = config.configured_suffixes
|
36
|
+
directories = config.configured_directories
|
37
|
+
template_files = suffixes.map { |suffix| (suffix.present? ? "#{template}.#{suffix}" : template) }
|
38
|
+
template_paths = directories.map { |dir| template_files.map { |file| "#{dir}/#{file}" } }
|
39
|
+
template_paths.flatten!
|
40
|
+
template_paths.each do |path|
|
41
|
+
return path if File.exist? path
|
42
|
+
end
|
43
|
+
raise "Missing template: #{template}. Searched: #{template_paths}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.parse_template(template)
|
47
|
+
file = find_template(template)
|
48
|
+
begin
|
49
|
+
json = File.read(file)
|
50
|
+
JSON.parse(json, symbolize_names: true)
|
51
|
+
rescue
|
52
|
+
raise "Error parsing template file: #{file}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.split_rows(data)
|
57
|
+
data = data.instance_of?(Array) ? data : [data]
|
58
|
+
rows = []
|
59
|
+
temp = []
|
60
|
+
data.each do |col|
|
61
|
+
if col.instance_of?(Array)
|
62
|
+
rows << temp unless temp.empty?
|
63
|
+
temp = []
|
64
|
+
rows << col
|
65
|
+
else
|
66
|
+
temp << col
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rows << temp unless temp.empty?
|
70
|
+
rows
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.filter_params(params, allowed_params)
|
74
|
+
filtered_params = params.select { |param| allowed_params.include? param.to_s }
|
75
|
+
if params.size > filtered_params.size
|
76
|
+
invalid_params = (params.keys - filtered_params.keys).map { |key| key.to_s }
|
77
|
+
logger.warn "Invalid attributes found: #{invalid_params} Valid attributes are: #{allowed_params}"
|
78
|
+
end
|
79
|
+
filtered_params
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.merge_values(new_value, old_value)
|
83
|
+
return old_value.merge(new_value) if old_value.is_a?(Hash) && new_value.is_a?(Hash)
|
84
|
+
new_value
|
85
|
+
end
|
86
|
+
|
87
|
+
def report
|
88
|
+
if template.present?
|
89
|
+
additional_params = {type: type, variables: variables, params: params}
|
90
|
+
self.class.load_template(template, additional_params.compact)
|
91
|
+
else
|
92
|
+
attribute_list = report_class.attribute_names
|
93
|
+
filtered_params = self.class.filter_params(params, attribute_list)
|
94
|
+
report_class.new(filtered_params)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def report_class
|
99
|
+
types = config.configured_types
|
100
|
+
raise "Invalid type: #{type}" unless types.include? type
|
101
|
+
types[type].constantize
|
102
|
+
end
|
103
|
+
|
104
|
+
def template_file
|
105
|
+
self.class.find_template(template)
|
106
|
+
end
|
107
|
+
|
108
|
+
def template_data
|
109
|
+
self.class.parse_template(template)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class MethodParser < Base
|
3
|
+
attribute :target
|
4
|
+
attribute :method
|
5
|
+
|
6
|
+
def self.parse(target, method)
|
7
|
+
new(target: target, method: method).output
|
8
|
+
end
|
9
|
+
|
10
|
+
def output
|
11
|
+
return send_value(target, method) if method_class == Symbol
|
12
|
+
return parse_array_method if method_class == Array
|
13
|
+
return parse_hash_method if method_class == Hash
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_class
|
18
|
+
method.class
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_array_method
|
22
|
+
raise "Not an array" unless method_class == Array
|
23
|
+
valid = false
|
24
|
+
output = target
|
25
|
+
method.each do |m|
|
26
|
+
value = parse_method(output, m)
|
27
|
+
next unless value.present?
|
28
|
+
valid = true
|
29
|
+
output = value
|
30
|
+
end
|
31
|
+
return output if valid
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_hash_method
|
36
|
+
raise "Not a hash" unless method_class == Hash
|
37
|
+
data = method.first
|
38
|
+
method = data[0]
|
39
|
+
value = data[1]
|
40
|
+
send_value(target, method, value)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def send_value(target, method, value = nil)
|
46
|
+
method = method.to_s
|
47
|
+
return nil unless target_has_method?(target, method)
|
48
|
+
return target.send(method) unless value.present?
|
49
|
+
return target.send(method, *value) if value.instance_of?(Array)
|
50
|
+
target.send(method, value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def target_has_method?(target, method)
|
54
|
+
target.methods.include?(method.to_sym)
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_method(target, method)
|
58
|
+
self.class.parse(target, method)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class ValueParser < Base
|
3
|
+
VALUE_FUNCTIONS = %i[a d n rn rd r]
|
4
|
+
|
5
|
+
attribute :element
|
6
|
+
attribute :variables, default: {}
|
7
|
+
|
8
|
+
def self.parse(element, variables = {})
|
9
|
+
variables = variables.present? ? variables : {}
|
10
|
+
new(element: element, variables: variables).output
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.parse_and_execute(target, values, variables = {})
|
14
|
+
parsed_target = target
|
15
|
+
if target.instance_of?(String)
|
16
|
+
parsed_target = new(element: target, variables: variables).parse_string
|
17
|
+
end
|
18
|
+
parsed_values = parse(values, variables)
|
19
|
+
MethodParser.parse(parsed_target, parsed_values)
|
20
|
+
end
|
21
|
+
|
22
|
+
def output
|
23
|
+
return parse_array if element_class == Array
|
24
|
+
return parse_hash if element_class == Hash
|
25
|
+
return parse_string if element_class == String
|
26
|
+
element
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_array
|
30
|
+
raise "Not an array" unless element_class == Array
|
31
|
+
front = element[0]
|
32
|
+
return parse_executed_array if front.instance_of?(String) && front.start_with?("#")
|
33
|
+
element.map { |value| parse_value(value) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_executed_array
|
37
|
+
raise "Not an executable array" unless element[0].start_with?("#")
|
38
|
+
values = element
|
39
|
+
target = values.delete_at(0).sub("#", "")
|
40
|
+
parse_and_execute_value(target, values)
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_hash
|
44
|
+
raise "Not a hash" unless element_class == Hash
|
45
|
+
element.transform_values { |value| parse_value(value) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_string
|
49
|
+
raise "Not a string" unless element_class == String
|
50
|
+
return element.sub(":", "").to_sym if element.start_with?(":")
|
51
|
+
return element.sub("&", "").constantize if element.start_with?("&")
|
52
|
+
return parse_variable(element) if element.start_with?("$")
|
53
|
+
return parse_function(element) if element.start_with?("!")
|
54
|
+
element
|
55
|
+
end
|
56
|
+
|
57
|
+
def element_class
|
58
|
+
element.class
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def parse_variable(value)
|
64
|
+
key = value.sub("$", "").to_sym
|
65
|
+
variables[key]
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_function(value)
|
69
|
+
input = value.strip
|
70
|
+
function = function_type(input)
|
71
|
+
return value unless function.present?
|
72
|
+
input.sub!(function_prefix(function), "")
|
73
|
+
output = run_function(function, input)
|
74
|
+
output.nil? ? value : output
|
75
|
+
end
|
76
|
+
|
77
|
+
def run_function(function, input)
|
78
|
+
case function
|
79
|
+
when :a then addition_function(input)
|
80
|
+
when :d then date_function(input)
|
81
|
+
when :n then number_function(input)
|
82
|
+
when :r then range_function(input)
|
83
|
+
when :rn then range_function(input, :number)
|
84
|
+
when :rd then range_function(input, :date)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def function_type(value)
|
89
|
+
VALUE_FUNCTIONS.each do |function|
|
90
|
+
return function if value.start_with?(function_prefix(function))
|
91
|
+
end
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
def function_prefix(function)
|
96
|
+
"!#{function}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def addition_function(value)
|
100
|
+
values = parse_function_array(value)
|
101
|
+
values.map! { |value| number_function(value) }
|
102
|
+
values.sum(0)
|
103
|
+
rescue
|
104
|
+
0
|
105
|
+
end
|
106
|
+
|
107
|
+
def date_function(value)
|
108
|
+
Time.parse(value)
|
109
|
+
rescue
|
110
|
+
Time.now
|
111
|
+
end
|
112
|
+
|
113
|
+
def number_function(value)
|
114
|
+
float = (value =~ /\d\.\d/)
|
115
|
+
return value.to_f if float.present?
|
116
|
+
value.to_i
|
117
|
+
rescue
|
118
|
+
0
|
119
|
+
end
|
120
|
+
|
121
|
+
def range_function(value, type = :any)
|
122
|
+
values = parse_function_array(value)
|
123
|
+
case type
|
124
|
+
when :number then values.map! { |subvalue| number_function(subvalue) }
|
125
|
+
when :date then values.map! { |subvalue| date_function(subvalue) }
|
126
|
+
end
|
127
|
+
Range.new(*values)
|
128
|
+
rescue
|
129
|
+
Range(0..1)
|
130
|
+
end
|
131
|
+
|
132
|
+
def parse_function_array(value)
|
133
|
+
value.split(",").map { |value| parse_value(value.strip) }
|
134
|
+
end
|
135
|
+
|
136
|
+
def parse_value(value)
|
137
|
+
self.class.parse(value, variables)
|
138
|
+
end
|
139
|
+
|
140
|
+
def parse_and_execute_value(target, value)
|
141
|
+
self.class.parse_and_execute(target, value, variables)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class Report < Base
|
3
|
+
attribute :title
|
4
|
+
|
5
|
+
def self.generate(params)
|
6
|
+
output = {}
|
7
|
+
params.each do |key, value|
|
8
|
+
raise "Missing hash!" unless value.instance_of?(Hash)
|
9
|
+
value[:name] ||= key
|
10
|
+
output[key] = new(value).data
|
11
|
+
end
|
12
|
+
output
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.generate_to_csv(params)
|
16
|
+
params.each do |key, value|
|
17
|
+
raise "Missing hash!" unless value.instance_of?(Hash)
|
18
|
+
value[:name] ||= key
|
19
|
+
report = new(value)
|
20
|
+
csv = report.generate_csv
|
21
|
+
puts "Report generated to #{csv}"
|
22
|
+
end
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def report_title
|
27
|
+
return nil unless title.present?
|
28
|
+
parsed_title = ValueParser.parse(title)
|
29
|
+
parsed_title = parsed_title.join if parsed_title.instance_of?(Array)
|
30
|
+
parsed_title
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_csv(path)
|
34
|
+
write_to_csv(path, data)
|
35
|
+
end
|
36
|
+
|
37
|
+
def error_message
|
38
|
+
errors.full_messages.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def write_to_csv(path, data)
|
44
|
+
return path if File.exist?(path)
|
45
|
+
CSV.open(path, "wb") do |csv|
|
46
|
+
data.each { |row| csv << row }
|
47
|
+
end
|
48
|
+
path
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class ModelReport < Report
|
3
|
+
attribute :target
|
4
|
+
attribute :method_list, default: []
|
5
|
+
|
6
|
+
validates :target, presence: true
|
7
|
+
|
8
|
+
def data
|
9
|
+
return get_model_data(target) unless target.methods.include? :first
|
10
|
+
records_data
|
11
|
+
end
|
12
|
+
|
13
|
+
def records_data
|
14
|
+
records = target
|
15
|
+
records = target.all if target.methods.include? :all
|
16
|
+
output = []
|
17
|
+
records.each do |model|
|
18
|
+
value = get_model_data(model)
|
19
|
+
output << value
|
20
|
+
end
|
21
|
+
output
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_model_data(target)
|
25
|
+
method_list.map do |method|
|
26
|
+
ValueParser.parse_and_execute(target, method)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/reportinator.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "reportinator/version"
|
4
|
+
require "csv"
|
5
|
+
require "json"
|
6
|
+
require "fileutils"
|
7
|
+
require "active_support"
|
8
|
+
require "active_model"
|
9
|
+
require "require_all"
|
10
|
+
|
11
|
+
module Reportinator
|
12
|
+
class Error < StandardError; end
|
13
|
+
class << self
|
14
|
+
attr_writer :config
|
15
|
+
attr_writer :logger
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.config
|
19
|
+
@config ||= Config.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.configure
|
23
|
+
yield(config)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.logger
|
27
|
+
@logger || ActiveSupport::Logger.new($stdout)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.report(template, additional_params = {})
|
31
|
+
Loader.data_from_template(template, additional_params)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.output(template, additional_params = {}, filename = "")
|
35
|
+
filename = (filename.present? ? filename : "#{template}.csv")
|
36
|
+
path = "#{config.output_directory}/#{filename}"
|
37
|
+
FileUtils.mkdir_p(File.dirname(path))
|
38
|
+
data = Loader.data_from_template(template, additional_params)
|
39
|
+
CSV.open(path, "wb") do |csv|
|
40
|
+
data.each do |row|
|
41
|
+
csv << row
|
42
|
+
end
|
43
|
+
end
|
44
|
+
path
|
45
|
+
end
|
46
|
+
|
47
|
+
class Base
|
48
|
+
include ActiveModel::API
|
49
|
+
include ActiveModel::Attributes
|
50
|
+
|
51
|
+
require_all "lib/reportinator"
|
52
|
+
|
53
|
+
def self.config
|
54
|
+
Reportinator.config
|
55
|
+
end
|
56
|
+
|
57
|
+
def config
|
58
|
+
self.class.config
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.logger
|
62
|
+
Reportinator.logger
|
63
|
+
end
|
64
|
+
|
65
|
+
def logger
|
66
|
+
self.class.logger
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reportinator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Moxvallix
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-10-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: require_all
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
description: Behold! My Reportinator! Reportinator makes reports from JSON files.
|
42
|
+
email:
|
43
|
+
- dev@moxvallix.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".rspec"
|
49
|
+
- ".standard.yml"
|
50
|
+
- CHANGELOG.md
|
51
|
+
- CODE_OF_CONDUCT.md
|
52
|
+
- Gemfile
|
53
|
+
- Gemfile.lock
|
54
|
+
- LICENSE.txt
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- app/reports/example.report.json
|
58
|
+
- app/reports/multiplication.report.json
|
59
|
+
- lib/reportinator.rb
|
60
|
+
- lib/reportinator/config.rb
|
61
|
+
- lib/reportinator/loader.rb
|
62
|
+
- lib/reportinator/parsers/method.rb
|
63
|
+
- lib/reportinator/parsers/value.rb
|
64
|
+
- lib/reportinator/report.rb
|
65
|
+
- lib/reportinator/types/model.rb
|
66
|
+
- lib/reportinator/types/preset.rb
|
67
|
+
- lib/reportinator/version.rb
|
68
|
+
- sig/reportinator.rbs
|
69
|
+
homepage: https://github.com/moxvallix/reportinator
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata:
|
73
|
+
rubygems_mfa_required: 'true'
|
74
|
+
homepage_uri: https://github.com/moxvallix/reportinator
|
75
|
+
source_code_uri: https://github.com/moxvallix/reportinator
|
76
|
+
changelog_uri: https://github.com/moxvallix/reportinator
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.6.0
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.3.3
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Easily generate reports using a JSON template.
|
96
|
+
test_files: []
|