reportinator 0.1.1 → 0.3.2
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 +4 -4
- data/CHANGELOG.md +62 -7
- data/Gemfile.lock +11 -1
- data/README.md +201 -291
- data/app/reports/example.report.json +7 -3
- data/app/reports/multiplication.report.json +1 -1
- data/app/reports/multiplication_v2.report.json +15 -0
- data/data/schema/report_schema.json +76 -0
- data/docs/0_first_report.md +267 -0
- data/lib/reportinator/base.rb +2 -1
- data/lib/reportinator/config.rb +30 -0
- data/lib/reportinator/function.rb +33 -0
- data/lib/reportinator/functions/array/flatten.rb +12 -0
- data/lib/reportinator/functions/array/helper.rb +77 -0
- data/lib/reportinator/functions/array/join.rb +11 -0
- data/lib/reportinator/functions/array/method.rb +9 -0
- data/lib/reportinator/functions/array/range.rb +11 -0
- data/lib/reportinator/functions/array/snippet.rb +30 -0
- data/lib/reportinator/functions/array/string.rb +10 -0
- data/lib/reportinator/functions/array.rb +43 -0
- data/lib/reportinator/functions/string/addition.rb +11 -0
- data/lib/reportinator/functions/string/constant.rb +9 -0
- data/lib/reportinator/functions/string/date.rb +9 -0
- data/lib/reportinator/functions/string/join.rb +10 -0
- data/lib/reportinator/functions/string/logical.rb +14 -0
- data/lib/reportinator/functions/string/number.rb +33 -0
- data/lib/reportinator/functions/string/range.rb +14 -0
- data/lib/reportinator/functions/string/symbol.rb +9 -0
- data/lib/reportinator/functions/string/variable.rb +12 -0
- data/lib/reportinator/functions/string.rb +29 -0
- data/lib/reportinator/helpers.rb +29 -0
- data/lib/reportinator/parser.rb +25 -0
- data/lib/reportinator/parsers/method.rb +8 -3
- data/lib/reportinator/parsers/report.rb +47 -0
- data/lib/reportinator/parsers/value.rb +15 -112
- data/lib/reportinator/report/column.rb +25 -0
- data/lib/reportinator/report/loader.rb +71 -0
- data/lib/reportinator/report/report.rb +33 -0
- data/lib/reportinator/report/row.rb +42 -0
- data/lib/reportinator/report/template.rb +108 -0
- data/lib/reportinator/{report.rb → report_type.rb} +4 -1
- data/lib/reportinator/types/model.rb +15 -7
- data/lib/reportinator/types/preset.rb +23 -2
- data/lib/reportinator/version.rb +1 -1
- data/lib/reportinator.rb +23 -9
- metadata +48 -5
- data/lib/reportinator/loader.rb +0 -112
@@ -0,0 +1,14 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class LogicalStringFunction < StringFunction
|
3
|
+
PREFIXES = ["@true", "@false", "@nil", "@null"]
|
4
|
+
|
5
|
+
def output
|
6
|
+
case prefix
|
7
|
+
when "@true" then true
|
8
|
+
when "@false" then false
|
9
|
+
when "@nil", "@null" then nil
|
10
|
+
else element
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class NumberStringFunction < StringFunction
|
3
|
+
PREFIXES = ["!n", "!nf", "!ni"]
|
4
|
+
|
5
|
+
attr_writer :parsed_body
|
6
|
+
|
7
|
+
def output
|
8
|
+
return parse_float if prefix == "!nf"
|
9
|
+
return parse_integer if prefix == "!ni"
|
10
|
+
parse_number
|
11
|
+
end
|
12
|
+
|
13
|
+
def parsed_body
|
14
|
+
to_parse = body
|
15
|
+
to_parse.strip! if to_parse.instance_of? String
|
16
|
+
@parsed_body ||= parse_value(body).to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_float
|
20
|
+
parsed_body.to_f
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_integer
|
24
|
+
parsed_body.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_number
|
28
|
+
float = (parsed_body =~ /\d\.\d/)
|
29
|
+
return parse_float if float.present?
|
30
|
+
parse_integer
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class RangeStringFunction < StringFunction
|
3
|
+
PREFIXES = ["!r", "!rd", "!rn"]
|
4
|
+
|
5
|
+
def output
|
6
|
+
values = body.split(",").map { |value| parse_value(value.strip) }
|
7
|
+
case prefix
|
8
|
+
when "!rn" then values.map! { |subvalue| NumberStringFunction.parse("!n #{subvalue}") }
|
9
|
+
when "!rd" then values.map! { |subvalue| DateStringFunction.parse("!d #{subvalue}") }
|
10
|
+
end
|
11
|
+
Range.new(*values)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class VariableStringFunction < StringFunction
|
3
|
+
PREFIXES = ["$"]
|
4
|
+
|
5
|
+
def output
|
6
|
+
variables = metadata[:variables]
|
7
|
+
variable = body.to_sym
|
8
|
+
return element unless variables.present? && variables.include?(variable)
|
9
|
+
variables[variable]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class StringFunction < Function
|
3
|
+
PREFIXES = []
|
4
|
+
|
5
|
+
attribute :prefix
|
6
|
+
attribute :body
|
7
|
+
|
8
|
+
def self.accepts? input
|
9
|
+
return false unless input.instance_of? String
|
10
|
+
return false if self::PREFIXES.empty?
|
11
|
+
self::PREFIXES.each do |prefix|
|
12
|
+
return true if input.start_with? prefix
|
13
|
+
end
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def get
|
18
|
+
raise "Function missing output!" unless respond_to? :output
|
19
|
+
set_attributes
|
20
|
+
output
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_attributes
|
24
|
+
prefix = get_prefix(element)
|
25
|
+
self.prefix = prefix
|
26
|
+
self.body = element.sub(prefix, "")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Reportinator
|
2
|
+
module Helpers
|
3
|
+
def merge_hash(target, source)
|
4
|
+
target = target.present? ? target : {}
|
5
|
+
source = source.present? ? source : {}
|
6
|
+
merge_hash!(target, source)
|
7
|
+
end
|
8
|
+
|
9
|
+
def merge_hash!(target, source)
|
10
|
+
raise "Target: #{target} is not a hash" unless target.instance_of?(Hash)
|
11
|
+
raise "Source: #{source} is not a hash" unless source.instance_of?(Hash)
|
12
|
+
target.merge(source) do |key, old_value, new_value|
|
13
|
+
if old_value.instance_of?(Hash) && new_value.instance_of?(Hash)
|
14
|
+
merge_hash!(old_value, new_value)
|
15
|
+
elsif new_value.present?
|
16
|
+
new_value
|
17
|
+
else
|
18
|
+
old_value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def symbolize_attributes(target)
|
24
|
+
raise "Missing attributes" unless target.respond_to? :attributes
|
25
|
+
raise "Invalid attributes" unless target.attributes.instance_of? Hash
|
26
|
+
target.attributes.transform_keys { |key| key.to_sym }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class Parser < Base
|
3
|
+
cattr_writer :prefix_list
|
4
|
+
|
5
|
+
def self.get_prefix_list
|
6
|
+
config.configured_functions.map { |function| function::PREFIXES }.flatten
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.prefix_list
|
10
|
+
@prefix_list ||= get_prefix_list
|
11
|
+
end
|
12
|
+
|
13
|
+
def prefix_list
|
14
|
+
self.class.prefix_list
|
15
|
+
end
|
16
|
+
|
17
|
+
def escape_value(value)
|
18
|
+
return value unless value.is_a? String
|
19
|
+
prefix_list.each do |escape|
|
20
|
+
return value.prepend("?/") if value.strip.start_with?(escape)
|
21
|
+
end
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,14 +1,20 @@
|
|
1
1
|
module Reportinator
|
2
|
-
class MethodParser <
|
2
|
+
class MethodParser < Parser
|
3
3
|
attribute :target
|
4
4
|
attribute :method
|
5
5
|
|
6
6
|
def self.parse(target, method)
|
7
7
|
new(target: target, method: method).output
|
8
|
+
rescue => e
|
9
|
+
logger.error "[ERROR] #{e.class}: #{e}"
|
10
|
+
"Method Error"
|
8
11
|
end
|
9
12
|
|
10
13
|
def output
|
11
|
-
|
14
|
+
if method_class == Symbol
|
15
|
+
value = send_value(target, method)
|
16
|
+
return escape_value(value)
|
17
|
+
end
|
12
18
|
return parse_array_method if method_class == Array
|
13
19
|
return parse_hash_method if method_class == Hash
|
14
20
|
nil
|
@@ -24,7 +30,6 @@ module Reportinator
|
|
24
30
|
output = target
|
25
31
|
method.each do |m|
|
26
32
|
value = parse_method(output, m)
|
27
|
-
next unless value.present?
|
28
33
|
valid = true
|
29
34
|
output = value
|
30
35
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class ReportParser < Parser
|
3
|
+
attribute :element
|
4
|
+
attribute :data
|
5
|
+
|
6
|
+
def self.parse(element, data = nil)
|
7
|
+
set_data = (data.present? ? data : element)
|
8
|
+
new(element: element, data: set_data).output
|
9
|
+
end
|
10
|
+
|
11
|
+
def output
|
12
|
+
return parse_array if element_class == Array
|
13
|
+
return parse_hash if element_class == Hash
|
14
|
+
return parse_string if element_class == String
|
15
|
+
element
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_array
|
19
|
+
raise "Not an array" unless element_class == Array
|
20
|
+
element.map { |value| parse_value(value) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_hash
|
24
|
+
raise "Not a hash" unless element_class == Hash
|
25
|
+
element.transform_values { |value| parse_value(value) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_string
|
29
|
+
raise "Not a string" unless element_class == String
|
30
|
+
return element unless element.strip.start_with?("?")
|
31
|
+
return element.sub("?/", "") if element.strip.start_with?("?/")
|
32
|
+
# return parse_row_total if element.start_with?("?tr")
|
33
|
+
# return parse_column_total if element.start_with?("?tc")
|
34
|
+
element
|
35
|
+
end
|
36
|
+
|
37
|
+
def element_class
|
38
|
+
element.class
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def parse_value(value)
|
44
|
+
self.class.parse(value, data)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,144 +1,47 @@
|
|
1
1
|
module Reportinator
|
2
|
-
class ValueParser <
|
3
|
-
VALUE_FUNCTIONS = %i[a d n rn rd r]
|
4
|
-
|
2
|
+
class ValueParser < Parser
|
5
3
|
attribute :element
|
6
|
-
attribute :
|
4
|
+
attribute :metadata, default: {}
|
7
5
|
|
8
|
-
def self.parse(element,
|
9
|
-
|
10
|
-
new(element: element,
|
6
|
+
def self.parse(element, metadata = {})
|
7
|
+
metadata = metadata.present? ? metadata : {}
|
8
|
+
new(element: element.dup, metadata: metadata).output
|
9
|
+
rescue => e
|
10
|
+
logger.error "[ERROR] #{e.class}: #{e}"
|
11
|
+
"Parsing Error"
|
11
12
|
end
|
12
13
|
|
13
|
-
def self.parse_and_execute(target, values,
|
14
|
-
parsed_target = target
|
15
|
-
|
16
|
-
parsed_target = new(element: target, variables: variables).parse_string
|
17
|
-
end
|
18
|
-
parsed_values = parse(values, variables)
|
14
|
+
def self.parse_and_execute(target, values, metadata = {})
|
15
|
+
parsed_target = parse(target, metadata)
|
16
|
+
parsed_values = parse(values, metadata)
|
19
17
|
MethodParser.parse(parsed_target, parsed_values)
|
20
18
|
end
|
21
19
|
|
22
20
|
def output
|
21
|
+
config.configured_functions.each do |function|
|
22
|
+
return function.parse(element, metadata) if function.accepts? element
|
23
|
+
end
|
23
24
|
return parse_array if element_class == Array
|
24
25
|
return parse_hash if element_class == Hash
|
25
|
-
return parse_string if element_class == String
|
26
26
|
element
|
27
27
|
end
|
28
28
|
|
29
29
|
def parse_array
|
30
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
31
|
element.map { |value| parse_value(value) }
|
34
32
|
end
|
35
33
|
|
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
34
|
def parse_hash
|
44
35
|
raise "Not a hash" unless element_class == Hash
|
45
36
|
element.transform_values { |value| parse_value(value) }
|
46
37
|
end
|
47
38
|
|
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
39
|
def element_class
|
58
40
|
element.class
|
59
41
|
end
|
60
42
|
|
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
43
|
def parse_value(value)
|
137
|
-
self.class.parse(value,
|
138
|
-
end
|
139
|
-
|
140
|
-
def parse_and_execute_value(target, value)
|
141
|
-
self.class.parse_and_execute(target, value, variables)
|
44
|
+
self.class.parse(value, metadata)
|
142
45
|
end
|
143
46
|
end
|
144
47
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class Column < Base
|
3
|
+
OUTPUT_TYPES = {
|
4
|
+
numeric: [Numeric],
|
5
|
+
date: [Date, Time],
|
6
|
+
string: [String],
|
7
|
+
hash: [Hash],
|
8
|
+
array: [Array]
|
9
|
+
}
|
10
|
+
|
11
|
+
attribute :data
|
12
|
+
attr_writer :output
|
13
|
+
|
14
|
+
def output
|
15
|
+
@output ||= ReportParser.parse(data)
|
16
|
+
end
|
17
|
+
|
18
|
+
OUTPUT_TYPES.each do |type, classes|
|
19
|
+
define_method(:"#{type}?") {
|
20
|
+
classes.each { |c| return true if output.is_a? c }
|
21
|
+
false
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class ReportLoader < Base
|
3
|
+
attribute :template
|
4
|
+
attribute :metadata
|
5
|
+
|
6
|
+
def self.load(template, metadata = {})
|
7
|
+
loader = new(metadata: metadata)
|
8
|
+
loader.template = Template.load(template: template, metadata: metadata)
|
9
|
+
loader
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_metadata
|
13
|
+
report_metadata = {}
|
14
|
+
template.parse(metadata) do |data, old_meta, new_meta|
|
15
|
+
meta = ValueParser.parse(new_meta, metadata)
|
16
|
+
report_metadata = merge_hash(meta, report_metadata) if meta.present?
|
17
|
+
end
|
18
|
+
report_metadata
|
19
|
+
end
|
20
|
+
|
21
|
+
def report
|
22
|
+
report = Report.new
|
23
|
+
reports = template.parse(metadata) do |data, old_meta, new_meta|
|
24
|
+
parse_metadata(data, old_meta, new_meta)
|
25
|
+
end
|
26
|
+
reports.compact.each do |report_template|
|
27
|
+
output = report_template.data
|
28
|
+
report.insert(output)
|
29
|
+
end
|
30
|
+
report
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_metadata(data, old_meta, new_meta)
|
34
|
+
meta = ValueParser.parse(old_meta, metadata)
|
35
|
+
if new_meta.instance_of? Hash
|
36
|
+
unparsed_meta = new_meta.select { |key| config.configured_metadata.include? key }
|
37
|
+
meta_to_parse = new_meta.reject { |key| config.configured_metadata.include? key }
|
38
|
+
parsing_meta = merge_hash(meta, unparsed_meta)
|
39
|
+
parsed_meta = ValueParser.parse(meta_to_parse, parsing_meta)
|
40
|
+
remerged_meta = merge_hash(parsed_meta, unparsed_meta)
|
41
|
+
else
|
42
|
+
remerged_meta = {}
|
43
|
+
end
|
44
|
+
report_meta = merge_hash(remerged_meta, meta)
|
45
|
+
report_from_data(data, report_meta)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def report_from_data(data, meta)
|
51
|
+
report_type = report_class_from_data(data, meta)
|
52
|
+
return nil unless report_type.present?
|
53
|
+
return report_type.new(ValueParser.parse(data[:params], meta)) if report_type::PARSE_PARAMS
|
54
|
+
report = report_type.new(data[:params])
|
55
|
+
report.metadata = meta
|
56
|
+
report
|
57
|
+
end
|
58
|
+
|
59
|
+
def report_class_from_data(data, meta)
|
60
|
+
type = ValueParser.parse(data[:type], meta)
|
61
|
+
return false unless type.present?
|
62
|
+
report_class_from_type(type)
|
63
|
+
end
|
64
|
+
|
65
|
+
def report_class_from_type(type)
|
66
|
+
types = config.configured_types
|
67
|
+
raise "Invalid type: #{type}" unless types.include? type
|
68
|
+
types[type].constantize
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class Report < Base
|
3
|
+
attr_writer :rows
|
4
|
+
|
5
|
+
def rows
|
6
|
+
@rows ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def insert(row, position = :last)
|
10
|
+
return insert_row(row, position) if row.instance_of? Row
|
11
|
+
raise "Invalid row data: #{row}" unless row.instance_of? Array
|
12
|
+
row.each { |r| insert_row(r) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def insert_row(row, position = :last)
|
16
|
+
raise "Not a row" unless row.instance_of? Row
|
17
|
+
return rows.append(row) if position == :last
|
18
|
+
return rows.prepend(row) if position == :first
|
19
|
+
return rows.insert(position, row) if position.is_a? Numeric
|
20
|
+
raise "Invalid Position!"
|
21
|
+
end
|
22
|
+
|
23
|
+
def output
|
24
|
+
rows.map { |r| r.output }
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_csv
|
28
|
+
CSV.generate do |csv|
|
29
|
+
output.each { |row| csv << row }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class Row < Base
|
3
|
+
attr_writer :columns
|
4
|
+
|
5
|
+
def columns
|
6
|
+
@columns ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.create(input)
|
10
|
+
row = new
|
11
|
+
if input.instance_of? Array
|
12
|
+
input.each { |value| row.insert value }
|
13
|
+
else
|
14
|
+
row.insert(input)
|
15
|
+
end
|
16
|
+
row
|
17
|
+
end
|
18
|
+
|
19
|
+
def insert(data, position = :last)
|
20
|
+
column = create_column(data)
|
21
|
+
return columns.prepend(column) if position == :first
|
22
|
+
return columns.insert(position, column) if position.is_a? Numeric
|
23
|
+
return columns.append(column) if position == :last
|
24
|
+
raise "Invalid Position!"
|
25
|
+
end
|
26
|
+
|
27
|
+
def total
|
28
|
+
numeric_columns = columns.select { |c| c.numeric? }
|
29
|
+
numeric_columns.sum { |c| c.output }
|
30
|
+
end
|
31
|
+
|
32
|
+
def output
|
33
|
+
columns.map { |c| c.output }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def create_column(data)
|
39
|
+
Column.new(data: data)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Reportinator
|
2
|
+
class Template < Base
|
3
|
+
attr_accessor :children
|
4
|
+
attribute :type
|
5
|
+
attribute :template
|
6
|
+
attribute :params
|
7
|
+
attribute :metadata
|
8
|
+
|
9
|
+
def self.load(params = {})
|
10
|
+
template = new(params)
|
11
|
+
template.register
|
12
|
+
end
|
13
|
+
|
14
|
+
def register
|
15
|
+
return load_template if template.present?
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse(meta = {}, data = {})
|
20
|
+
output = []
|
21
|
+
new_meta = metadata
|
22
|
+
combine_meta = merge_hash(meta, new_meta)
|
23
|
+
new_data = attributes.transform_keys { |key| key.to_sym }
|
24
|
+
combine_data = merge_hash(new_data, data)
|
25
|
+
if children.present? && children.respond_to?(:to_ary)
|
26
|
+
children.each do |child|
|
27
|
+
output += child.parse(combine_meta, combine_data) do |combine_data, meta, new_meta|
|
28
|
+
yield(combine_data, meta, new_meta)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
else
|
32
|
+
output << yield(combine_data, meta, new_meta)
|
33
|
+
end
|
34
|
+
output
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def load_template
|
40
|
+
template_data = filter_template
|
41
|
+
data = if template_data.respond_to? :to_ary
|
42
|
+
template_data.map { |template| self.class.load(template) }
|
43
|
+
else
|
44
|
+
self.class.load(template_data)
|
45
|
+
end
|
46
|
+
self.children ||= []
|
47
|
+
if data.respond_to? :to_ary
|
48
|
+
self.children += data
|
49
|
+
else
|
50
|
+
self.children << data
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def filter_template
|
56
|
+
template_data = parse_template
|
57
|
+
if template_data.respond_to? :to_ary
|
58
|
+
template_data.map { |template| filter_params(template) }
|
59
|
+
else
|
60
|
+
filter_params(template_data)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_template
|
65
|
+
raise "Template isn't a string" unless template.instance_of? String
|
66
|
+
suffixes = config.configured_suffixes
|
67
|
+
directories = config.configured_directories
|
68
|
+
template_files = suffixes.map { |suffix| (suffix.present? ? "#{template}.#{suffix}" : template) }
|
69
|
+
template_paths = directories.map { |dir| template_files.map { |file| "#{dir}/#{file}" } }
|
70
|
+
template_paths.flatten!
|
71
|
+
template_paths.each do |path|
|
72
|
+
return path if File.exist? path
|
73
|
+
end
|
74
|
+
raise "Missing template: #{template}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate_template(json)
|
78
|
+
return true if Reportinator.schema.valid?(json)
|
79
|
+
raise "Template doesn't match schema: #{Reportinator.schema.validate(json).to_a}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse_template
|
83
|
+
file = read_template
|
84
|
+
begin
|
85
|
+
plain_json = JSON.parse(file)
|
86
|
+
symbolised_json = JSON.parse(file, symbolize_names: true)
|
87
|
+
rescue
|
88
|
+
raise "Error parsing template file: #{file}"
|
89
|
+
end
|
90
|
+
validate_template(plain_json)
|
91
|
+
symbolised_json
|
92
|
+
end
|
93
|
+
|
94
|
+
def read_template
|
95
|
+
file = find_template
|
96
|
+
File.read(file)
|
97
|
+
end
|
98
|
+
|
99
|
+
def filter_params(params)
|
100
|
+
filtered_params = params.select { |param| attribute_names.include? param.to_s }
|
101
|
+
if params.size > filtered_params.size
|
102
|
+
invalid_params = (params.keys - filtered_params.keys).map { |key| key.to_s }
|
103
|
+
logger.warn "Invalid attributes found: #{invalid_params} Valid attributes are: #{attribute_names}"
|
104
|
+
end
|
105
|
+
filtered_params
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|