magic-report 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +138 -0
- data/lib/magic_report/report/class_helpers.rb +23 -0
- data/lib/magic_report/report/configuration.rb +57 -0
- data/lib/magic_report/report/csv.rb +37 -0
- data/lib/magic_report/report/process.rb +59 -0
- data/lib/magic_report/report/row.rb +64 -0
- data/lib/magic_report/report.rb +97 -0
- data/lib/magic_report/utils.rb +35 -0
- data/lib/magic_report/version.rb +5 -0
- data/lib/magic_report.rb +25 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b11733b1128011acfba29bf24cbf2846ce42e226aeccadfcdfc6ce713c2b1b3e
|
4
|
+
data.tar.gz: 0dbec29ea7c05acf3744fc1a62d4b200455a88926d1e938826243df683d9ce93
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ad63138a1fe01308e5ddfef0f50d3feb04ae5dd49e9fe8c2e81dcac6ed5f376daf32de12baf1912695c798ade584d96652d7c1c0a0fc6f801992fd41c0a09bce
|
7
|
+
data.tar.gz: 2fff4d4f90864fded9a18846e718fbc82671907f106ce16c5240df94d57d2e92c27c2f7e423d02da1ee88a93b4f49a21db201636ffc04bb1b9c020c04a74d562
|
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# MagicReport
|
2
|
+
|
3
|
+
An easy way to export data to CSV
|
4
|
+
|
5
|
+
[](https://github.com/thefaded/magic-report/actions)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application’s Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem "magic-operation"
|
13
|
+
```
|
14
|
+
|
15
|
+
## Getting Started
|
16
|
+
|
17
|
+
This gem provides an ActiveRecord-like DSL to create CSV reports. One of the common use cases is when you have nested data.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
class User < MagicReport::Report
|
21
|
+
field :id
|
22
|
+
field :is_admin, ->(user) { user.is_admin ? "Yes" : "No" }
|
23
|
+
end
|
24
|
+
|
25
|
+
report = User.new
|
26
|
+
report.process(UserModel.first)
|
27
|
+
|
28
|
+
# You can also `process` on a collection
|
29
|
+
# report.process(User.all.limit(50))
|
30
|
+
|
31
|
+
report.as_csv
|
32
|
+
```
|
33
|
+
|
34
|
+
The example above is basic - you have a User model and you want to export the two fields `id` and `is_admin`.
|
35
|
+
The users of your application may not particularly like the naming `true` or `false` since these are more technical terms, that's why you can pass an additional block to the field.
|
36
|
+
|
37
|
+
Also, for each report you must provide locales file:
|
38
|
+
|
39
|
+
```yaml
|
40
|
+
en:
|
41
|
+
magic_report:
|
42
|
+
headings:
|
43
|
+
user:
|
44
|
+
id: ID
|
45
|
+
is_admin: Admin?
|
46
|
+
```
|
47
|
+
|
48
|
+
CSV will be
|
49
|
+
| ID | Admin? |
|
50
|
+
| ---- | -------- |
|
51
|
+
| 123 | Yes |
|
52
|
+
| 222 | No |
|
53
|
+
|
54
|
+
## Nested
|
55
|
+
|
56
|
+
Let's look at a more complex example. Now we have a user model with an address and several cars.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class User < MagicReport::Report
|
60
|
+
field :id
|
61
|
+
field :is_admin, ->(user) { user.is_admin ? "Yes" : "No" }
|
62
|
+
|
63
|
+
has_one :shipping_address, class: Address, prefix: -> { t("shipping_address") }
|
64
|
+
has_one :billing_address, class: Address, prefix: -> { t("billing_address") }
|
65
|
+
|
66
|
+
has_many :cars, class: Car, prefix: -> { t("car") }
|
67
|
+
end
|
68
|
+
|
69
|
+
class Address < MagicReport::Report
|
70
|
+
fields :address_line_1, :city
|
71
|
+
end
|
72
|
+
|
73
|
+
class Car < MagicReport::Report
|
74
|
+
field :name
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
Because we have explicitly said that the user `has_many :cars`, the number of lines in the CSV will be equal to the number of cars.
|
79
|
+
|
80
|
+
```yaml
|
81
|
+
en:
|
82
|
+
magic_report:
|
83
|
+
headings:
|
84
|
+
user:
|
85
|
+
id: ID
|
86
|
+
is_admin: Admin?
|
87
|
+
address: User address
|
88
|
+
cars: Car
|
89
|
+
shipping_address: Shipping address
|
90
|
+
billing_address: Billing address
|
91
|
+
car:
|
92
|
+
name: Name
|
93
|
+
address:
|
94
|
+
address_line_1: Line 1
|
95
|
+
city: City
|
96
|
+
```
|
97
|
+
|
98
|
+
CSV will be
|
99
|
+
| ID | Admin? | Shipping address Line 1 | Shipping address City | Billing address Line 1 | Billing address City | Car Name |
|
100
|
+
| ---- | -------- | ------------------- | ----------------- | -------- | -------- | ------- |
|
101
|
+
| 123 | Yes | 5th Ave | NY | Chester st | San Francisco | Lexus |
|
102
|
+
| 123 | Yes | 5th Ave | NY | Chester st | San Francisco | BMW |
|
103
|
+
|
104
|
+
### Using with blocks
|
105
|
+
|
106
|
+
The above example can be rewritten using blocks instead of class
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class User < MagicReport::Report
|
110
|
+
field :id
|
111
|
+
field :is_admin, ->(user) { user.is_admin ? "Yes" : "No" }
|
112
|
+
|
113
|
+
# You should always provide `name` option if you're using block instead of class
|
114
|
+
# From this option will be used locales for `address_line_1` and `city`
|
115
|
+
has_one :shipping_address, name: :address, prefix: -> { t("shipping_address") } do
|
116
|
+
fields :address_line_1, :city
|
117
|
+
end
|
118
|
+
# Prefix locale is taken from `user.billing_address`
|
119
|
+
has_one :billing_address, name: :address, prefix: -> { t("billing_address") } do
|
120
|
+
fields :address_line_1, :city
|
121
|
+
end
|
122
|
+
|
123
|
+
has_many :cars, name: :car, prefix: -> { t("car") } do
|
124
|
+
field :name
|
125
|
+
end
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
## Contributing
|
130
|
+
|
131
|
+
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
132
|
+
|
133
|
+
- [Report bugs](https://github.com/thefaded/magic-report/issues)
|
134
|
+
- Fix bugs and [submit pull requests](https://github.com/thefaded/magic-report/pulls)
|
135
|
+
- Write, clarify, or fix documentation
|
136
|
+
- Suggest or add new features
|
137
|
+
|
138
|
+
To get started with development and testing, check out the [Contributing Guide](CONTRIBUTING.md).
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicReport
|
4
|
+
class Report
|
5
|
+
module ClassHelpers
|
6
|
+
def name_from_class
|
7
|
+
::MagicReport::Utils.underscore(self.class.name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def fields_from_class
|
11
|
+
self.class.instance_variable_get(:@fields) || []
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_one_from_class
|
15
|
+
self.class.instance_variable_get(:@has_one) || []
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_many_from_class
|
19
|
+
self.class.instance_variable_get(:@has_many) || []
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicReport
|
4
|
+
class Report
|
5
|
+
module Configuration
|
6
|
+
class Field
|
7
|
+
attr_reader :key, :processor
|
8
|
+
|
9
|
+
def initialize(key:, processor: nil)
|
10
|
+
@key = key
|
11
|
+
@processor = processor
|
12
|
+
end
|
13
|
+
|
14
|
+
def process(entity)
|
15
|
+
processor ? processor.call(entity) : entity.send(key)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class HasOne
|
20
|
+
attr_reader :klass, :opts, :prefix, :key
|
21
|
+
|
22
|
+
def initialize(klass:, opts:, key:)
|
23
|
+
@klass = klass
|
24
|
+
@prefix = opts[:prefix]
|
25
|
+
@key = key
|
26
|
+
@opts = opts
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_entity(entity)
|
30
|
+
report.process(entity)
|
31
|
+
end
|
32
|
+
|
33
|
+
def report
|
34
|
+
@report ||= init_report
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# { class: Exports::Supplier, prefix: lambda }
|
40
|
+
|
41
|
+
def init_report
|
42
|
+
klass.new(report_params)
|
43
|
+
end
|
44
|
+
|
45
|
+
def report_opts
|
46
|
+
opts
|
47
|
+
end
|
48
|
+
|
49
|
+
def report_params
|
50
|
+
opts.reject { |k, v| %i[class].include? k }.merge(nested_field: key)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class HasMany < HasOne; end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "csv"
|
4
|
+
|
5
|
+
module MagicReport
|
6
|
+
class Report
|
7
|
+
class Csv
|
8
|
+
attr_reader :report, :file, :csv
|
9
|
+
|
10
|
+
def initialize(report)
|
11
|
+
@report = report
|
12
|
+
@file = Tempfile.new
|
13
|
+
@csv = ::CSV.new(@file, write_headers: true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate
|
17
|
+
write_headers
|
18
|
+
|
19
|
+
report.result.each do |row|
|
20
|
+
row.to_h.each { |nested_row| csv << nested_row.values }
|
21
|
+
end
|
22
|
+
# ensure
|
23
|
+
# file.close
|
24
|
+
end
|
25
|
+
|
26
|
+
def unlink
|
27
|
+
file.unlink
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def write_headers
|
33
|
+
csv << report.headings
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicReport
|
4
|
+
class Report
|
5
|
+
class Process
|
6
|
+
attr_reader :report
|
7
|
+
|
8
|
+
def initialize(report)
|
9
|
+
@report = report
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(input)
|
13
|
+
if input.is_a? Enumerable
|
14
|
+
input.map do |entity|
|
15
|
+
process_entity(entity)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
process_entity(input)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def process_entity(entity)
|
25
|
+
row = Row.new
|
26
|
+
|
27
|
+
process_fields(entity, row)
|
28
|
+
process_has_one(entity, row)
|
29
|
+
process_has_many(entity, row)
|
30
|
+
|
31
|
+
row
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_fields(entity, row)
|
35
|
+
report.fields.each do |field|
|
36
|
+
row.add(field: report.resolve_path(field.key), value: field.process(entity))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_has_one(entity, row)
|
41
|
+
report.has_one.each do |association|
|
42
|
+
inner_row = association.process_entity(entity.send(association.key))
|
43
|
+
|
44
|
+
row.add_inner_row(field: report.resolve_path(association.key), row: inner_row)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def process_has_many(entity, row)
|
49
|
+
report.has_many.each do |association|
|
50
|
+
entity.send(association.key).each do |entity|
|
51
|
+
nested_row = association.process_entity(entity)
|
52
|
+
|
53
|
+
row.add_nested_row(field: report.resolve_path(association.key), row: nested_row)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicReport
|
4
|
+
class Report
|
5
|
+
class Row
|
6
|
+
attr_accessor :data, :inner_rows, :nested_rows
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@data = {}
|
10
|
+
@inner_rows = {}
|
11
|
+
@nested_rows = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(field:, value:)
|
15
|
+
data[field] = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_inner_row(field:, row:)
|
19
|
+
inner_rows[field] = row
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_nested_row(field:, row:)
|
23
|
+
nested_rows[field] ||= []
|
24
|
+
nested_rows[field].push(row)
|
25
|
+
end
|
26
|
+
|
27
|
+
def values
|
28
|
+
data.values
|
29
|
+
end
|
30
|
+
|
31
|
+
def complex?
|
32
|
+
nested_rows.any?
|
33
|
+
end
|
34
|
+
|
35
|
+
def each_nested_row(&block)
|
36
|
+
nested_rows.keys.flat_map do |key|
|
37
|
+
nested_rows[key].map do |nested_row|
|
38
|
+
block.call(nested_row)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def each_inner_row(&block)
|
44
|
+
inner_rows.values.map do |inner_row|
|
45
|
+
block.call(inner_row)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_h
|
50
|
+
@to_h ||= begin
|
51
|
+
original_hash = inner_rows.any? ? data.merge(each_inner_row { |inner_row| inner_row.to_h }.reduce({}, :merge)) : data
|
52
|
+
|
53
|
+
if complex?
|
54
|
+
each_nested_row do |nested_row|
|
55
|
+
original_hash.merge(nested_row.to_h)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
original_hash
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicReport
|
4
|
+
class Report
|
5
|
+
include ClassHelpers
|
6
|
+
|
7
|
+
attr_reader :fields, :has_one, :has_many, :name, :nested_field, :prefix, :result
|
8
|
+
|
9
|
+
def initialize(fields: nil, has_one: nil, has_many: nil, name: nil, prefix: nil, nested_field: nil)
|
10
|
+
@fields = fields || fields_from_class
|
11
|
+
@has_one = has_one || has_one_from_class
|
12
|
+
@has_many = has_many || has_many_from_class
|
13
|
+
@name = name || name_from_class
|
14
|
+
|
15
|
+
@prefix = prefix
|
16
|
+
@nested_field = nested_field
|
17
|
+
end
|
18
|
+
|
19
|
+
def process(input)
|
20
|
+
@result = ::MagicReport::Report::Process.new(self).call(input)
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_csv
|
24
|
+
@as_csv ||= begin
|
25
|
+
csv = ::MagicReport::Report::Csv.new(self)
|
26
|
+
csv.generate
|
27
|
+
|
28
|
+
csv
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def headings
|
33
|
+
@headings ||= (fields.map { |field| t(field.key) } + has_one.map { |association| association.report.headings } + has_many.map { |association| association.report.headings }).flatten
|
34
|
+
end
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def t(key)
|
38
|
+
::MagicReport::Utils.t(name: name, key: key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def fields(*attrs)
|
42
|
+
@fields ||= []
|
43
|
+
|
44
|
+
Types::SymbolArray[attrs].each do |key|
|
45
|
+
@fields << Configuration::Field.new(key: key)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def field(*attrs)
|
50
|
+
key, processor = attrs
|
51
|
+
|
52
|
+
@fields ||= []
|
53
|
+
@fields << Configuration::Field.new(key: key, processor: processor)
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_one(attribute, opts = {}, &block)
|
57
|
+
@has_one ||= []
|
58
|
+
|
59
|
+
coerced_attribute = Types::Coercible::Symbol[attribute]
|
60
|
+
|
61
|
+
klass = ::MagicReport::Utils.derive_class(opts, &block)
|
62
|
+
|
63
|
+
if (prefix = opts[:prefix])
|
64
|
+
opts[:prefix] = new.instance_exec(&prefix)
|
65
|
+
end
|
66
|
+
|
67
|
+
@has_one << Configuration::HasOne.new(klass: klass, opts: opts, key: coerced_attribute)
|
68
|
+
end
|
69
|
+
|
70
|
+
def has_many(attribute, opts = {}, &block)
|
71
|
+
@has_many ||= []
|
72
|
+
|
73
|
+
coerced_attribute = Types::Coercible::Symbol[attribute]
|
74
|
+
|
75
|
+
klass = ::MagicReport::Utils.derive_class(opts, &block)
|
76
|
+
|
77
|
+
if (prefix = opts[:prefix])
|
78
|
+
opts[:prefix] = new.instance_exec(&prefix)
|
79
|
+
end
|
80
|
+
|
81
|
+
@has_many << Configuration::HasMany.new(klass: klass, opts: opts, key: coerced_attribute)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def resolve_path(key)
|
86
|
+
nested_field ? "#{nested_field}.#{key}".to_sym : key
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def t(key)
|
92
|
+
translated = ::MagicReport::Utils.t(name: name, key: key)
|
93
|
+
|
94
|
+
prefix ? "#{prefix} #{translated}" : translated
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicReport
|
4
|
+
module Utils
|
5
|
+
def underscore(klass)
|
6
|
+
klass.gsub(/::/, "/")
|
7
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
8
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
9
|
+
.tr("-", "_")
|
10
|
+
.downcase
|
11
|
+
end
|
12
|
+
|
13
|
+
def derive_class(opts, &block)
|
14
|
+
if block
|
15
|
+
raise "name option must be provided" unless opts[:name]
|
16
|
+
|
17
|
+
cloned_klass = ::MagicReport::Report.clone
|
18
|
+
cloned_klass.class_eval(&block)
|
19
|
+
cloned_klass
|
20
|
+
else
|
21
|
+
opts[:class]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param name is the report name
|
26
|
+
# @key is a field
|
27
|
+
def t(name:, key:)
|
28
|
+
I18n.translate!("magic_report.headings.#{name}.#{key}")
|
29
|
+
end
|
30
|
+
|
31
|
+
module_function :underscore
|
32
|
+
module_function :derive_class
|
33
|
+
module_function :t
|
34
|
+
end
|
35
|
+
end
|
data/lib/magic_report.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-types"
|
4
|
+
require "i18n"
|
5
|
+
|
6
|
+
require "magic_report/version"
|
7
|
+
|
8
|
+
module MagicReport
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
module Types
|
12
|
+
include ::Dry.Types()
|
13
|
+
|
14
|
+
SymbolArray = Array.of(Types::Coercible::Symbol)
|
15
|
+
end
|
16
|
+
|
17
|
+
require "magic_report/utils"
|
18
|
+
|
19
|
+
require "magic_report/report/class_helpers"
|
20
|
+
require "magic_report/report/configuration"
|
21
|
+
require "magic_report/report/process"
|
22
|
+
require "magic_report/report/row"
|
23
|
+
require "magic_report/report/csv"
|
24
|
+
require "magic_report/report"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: magic-report
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Pankratev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-10-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-types
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: i18n
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description:
|
42
|
+
email: thepoddubstep@gmail.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- README.md
|
48
|
+
- lib/magic_report.rb
|
49
|
+
- lib/magic_report/report.rb
|
50
|
+
- lib/magic_report/report/class_helpers.rb
|
51
|
+
- lib/magic_report/report/configuration.rb
|
52
|
+
- lib/magic_report/report/csv.rb
|
53
|
+
- lib/magic_report/report/process.rb
|
54
|
+
- lib/magic_report/report/row.rb
|
55
|
+
- lib/magic_report/utils.rb
|
56
|
+
- lib/magic_report/version.rb
|
57
|
+
homepage: https://github.com/thefaded/magic-report
|
58
|
+
licenses:
|
59
|
+
- MIT
|
60
|
+
metadata: {}
|
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: '2.6'
|
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.1.6
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: An easy way to export data to CSV
|
80
|
+
test_files: []
|