magic-report 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/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
|
+
[![Build Status](https://github.com/thefaded/magic-report/workflows/test/badge.svg?branch=master)](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: []
|