reportinator 0.2.0 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -7
  3. data/Gemfile.lock +10 -0
  4. data/README.md +113 -22
  5. data/app/reports/example.report.json +7 -3
  6. data/app/reports/multiplication.report.json +1 -1
  7. data/app/reports/multiplication_v2.report.json +15 -0
  8. data/data/schema/report_schema.json +76 -0
  9. data/docs/0_first_report.md +8 -8
  10. data/lib/reportinator/base.rb +2 -1
  11. data/lib/reportinator/config.rb +10 -0
  12. data/lib/reportinator/function.rb +5 -5
  13. data/lib/reportinator/functions/array/flatten.rb +12 -0
  14. data/lib/reportinator/functions/array/helper.rb +7 -1
  15. data/lib/reportinator/functions/array/join.rb +1 -1
  16. data/lib/reportinator/functions/array/range.rb +11 -0
  17. data/lib/reportinator/functions/array/snippet.rb +30 -0
  18. data/lib/reportinator/functions/array/string.rb +10 -0
  19. data/lib/reportinator/functions/string/number.rb +26 -4
  20. data/lib/reportinator/functions/string/variable.rb +4 -2
  21. data/lib/reportinator/helpers.rb +29 -0
  22. data/lib/reportinator/parsers/method.rb +5 -2
  23. data/lib/reportinator/parsers/value.rb +14 -13
  24. data/lib/reportinator/report/column.rb +25 -0
  25. data/lib/reportinator/report/loader.rb +71 -0
  26. data/lib/reportinator/report/report.rb +33 -0
  27. data/lib/reportinator/report/row.rb +42 -0
  28. data/lib/reportinator/report/template.rb +108 -0
  29. data/lib/reportinator/{report.rb → report_type.rb} +4 -1
  30. data/lib/reportinator/types/model.rb +10 -5
  31. data/lib/reportinator/types/preset.rb +23 -2
  32. data/lib/reportinator/version.rb +1 -1
  33. data/lib/reportinator.rb +21 -11
  34. metadata +30 -5
  35. data/lib/reportinator/loader.rb +0 -120
@@ -0,0 +1,10 @@
1
+ module Reportinator
2
+ class StringArrayFunction < ArrayFunction
3
+ PREFIXES = [">string"]
4
+
5
+ def output
6
+ values.prepend target
7
+ values.map { |value| parse_value(value).to_s }.join
8
+ end
9
+ end
10
+ end
@@ -1,11 +1,33 @@
1
1
  module Reportinator
2
2
  class NumberStringFunction < StringFunction
3
- PREFIXES = ["!n"]
3
+ PREFIXES = ["!n", "!nf", "!ni"]
4
+
5
+ attr_writer :parsed_body
4
6
 
5
7
  def output
6
- float = (body =~ /\d\.\d/)
7
- return body.to_f if float.present?
8
- body.to_i
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
9
31
  end
10
32
  end
11
33
  end
@@ -3,8 +3,10 @@ module Reportinator
3
3
  PREFIXES = ["$"]
4
4
 
5
5
  def output
6
- return element unless variables.include? body.to_sym
7
- variables[body.to_sym]
6
+ variables = metadata[:variables]
7
+ variable = body.to_sym
8
+ return element unless variables.present? && variables.include?(variable)
9
+ variables[variable]
8
10
  end
9
11
  end
10
12
  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
@@ -5,6 +5,9 @@ module Reportinator
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
@@ -45,15 +48,15 @@ module Reportinator
45
48
  private
46
49
 
47
50
  def send_value(target, method, value = nil)
48
- method = method.to_s
49
51
  return nil unless target_has_method?(target, method)
52
+ method = method.to_s
50
53
  return target.send(method) unless value.present?
51
54
  return target.send(method, *value) if value.instance_of?(Array)
52
55
  target.send(method, value)
53
56
  end
54
57
 
55
58
  def target_has_method?(target, method)
56
- target.methods.include?(method.to_sym)
59
+ target.respond_to?(method)
57
60
  end
58
61
 
59
62
  def parse_method(target, method)
@@ -1,25 +1,26 @@
1
1
  module Reportinator
2
- class ValueParser < Parser
2
+ class ValueParser < Parser
3
3
  attribute :element
4
- attribute :variables, default: {}
4
+ attribute :metadata, default: {}
5
5
 
6
- def self.parse(element, variables = {})
7
- variables = variables.present? ? variables : {}
8
- new(element: element, variables: variables).output
6
+ def self.parse(element, metadata = {}, dup = true)
7
+ metadata = metadata.present? ? metadata : {}
8
+ input_element = (dup ? element.dup : element)
9
+ new(element: input_element, metadata: metadata).output
10
+ rescue => e
11
+ logger.error "[ERROR] #{e.class}: #{e}"
12
+ "Parsing Error"
9
13
  end
10
14
 
11
- def self.parse_and_execute(target, values, variables = {})
12
- parsed_target = target
13
- if target.instance_of?(String)
14
- parsed_target = parse(target, variables)
15
- end
16
- parsed_values = parse(values, variables)
15
+ def self.parse_and_execute(target, values, metadata = {})
16
+ parsed_target = parse(target, metadata, false)
17
+ parsed_values = parse(values, metadata)
17
18
  MethodParser.parse(parsed_target, parsed_values)
18
19
  end
19
20
 
20
21
  def output
21
22
  config.configured_functions.each do |function|
22
- return function.parse(element, variables) if function.accepts? element
23
+ return function.parse(element, metadata) if function.accepts? element
23
24
  end
24
25
  return parse_array if element_class == Array
25
26
  return parse_hash if element_class == Hash
@@ -41,7 +42,7 @@ module Reportinator
41
42
  end
42
43
 
43
44
  def parse_value(value)
44
- self.class.parse(value, variables)
45
+ self.class.parse(value, metadata)
45
46
  end
46
47
  end
47
48
  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
@@ -1,5 +1,8 @@
1
1
  module Reportinator
2
- class Report < Base
2
+ class ReportType < Base
3
+ PARSE_PARAMS = true
4
+
5
+ attr_accessor :metadata
3
6
  attribute :title
4
7
 
5
8
  def self.generate(params)
@@ -1,13 +1,16 @@
1
1
  module Reportinator
2
- class ModelReport < Report
2
+ class ModelReport < ReportType
3
+ include Helpers
4
+ PARSE_PARAMS = false
3
5
  attribute :target
4
6
  attribute :method_list, default: []
5
7
 
6
8
  validates :target, presence: true
7
9
 
8
10
  def data
9
- return get_model_data(target) unless target.respond_to? :to_ary
10
- records_data
11
+ self.target = ValueParser.parse(target, metadata, false)
12
+ return Row.create(get_model_data(target)) unless target.respond_to? :each
13
+ records_data.map { |data| Row.create(data) }
11
14
  end
12
15
 
13
16
  def records_data
@@ -26,8 +29,10 @@ module Reportinator
26
29
  end
27
30
 
28
31
  def parse_method(target, method)
29
- return method if method.instance_of?(String)
30
- MethodParser.parse(target, method)
32
+ parser_metadata = merge_hash(metadata, {variables: {target: target}})
33
+ parsed_method = ValueParser.parse(method, parser_metadata)
34
+ return parsed_method if parsed_method.instance_of? String
35
+ MethodParser.parse(target, parsed_method)
31
36
  end
32
37
  end
33
38
  end
@@ -1,5 +1,26 @@
1
1
  module Reportinator
2
- class PresetReport < Report
3
- attribute :data, default: []
2
+ class PresetReport < ReportType
3
+ attribute :values
4
+
5
+ def data
6
+ split_values.map { |row| Row.create(row) }
7
+ end
8
+
9
+ def split_values
10
+ data = values.instance_of?(Array) ? values : [values]
11
+ rows = []
12
+ temp = []
13
+ data.each do |col|
14
+ if col.instance_of?(Array)
15
+ rows << temp unless temp.empty?
16
+ temp = []
17
+ rows << col
18
+ else
19
+ temp << col
20
+ end
21
+ end
22
+ rows << temp unless temp.empty?
23
+ rows
24
+ end
4
25
  end
5
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reportinator
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.3"
5
5
  end
data/lib/reportinator.rb CHANGED
@@ -6,14 +6,17 @@ require "fileutils"
6
6
  require "active_support"
7
7
  require "active_model"
8
8
  require "require_all"
9
+ require "json_schemer"
9
10
  require_relative "reportinator/version"
10
11
  require_relative "reportinator/base"
11
12
 
12
13
  module Reportinator
14
+ SCHEMA = "#{__dir__}/../data/schema/report_schema.json"
13
15
  class Error < StandardError; end
14
16
  class << self
15
17
  attr_writer :config
16
18
  attr_writer :logger
19
+ attr_writer :schema
17
20
  end
18
21
 
19
22
  def self.config
@@ -24,28 +27,35 @@ module Reportinator
24
27
  yield(config)
25
28
  end
26
29
 
30
+ def self.schema
31
+ return @schema if @schema.instance_of? JSONSchemer
32
+ schema = JSON.parse(File.read(SCHEMA))
33
+ @schema = JSONSchemer.schema(schema)
34
+ end
35
+
27
36
  def self.logger
28
37
  @logger || ActiveSupport::Logger.new($stdout)
29
38
  end
30
39
 
31
- def self.parse(input, variables = {})
32
- ValueParser.parse(input, variables)
40
+ def self.parse(input, metadata = {})
41
+ ValueParser.parse(input, metadata)
33
42
  end
34
43
 
35
- def self.report(template, additional_params = {})
36
- Loader.data_from_template(template, additional_params)
44
+ def self.report(template, metadata = {})
45
+ report = ReportLoader.load(template, metadata).report
46
+ report.output
37
47
  end
38
48
 
39
- def self.output(template, additional_params = {}, filename = "")
49
+ def self.output(template, metadata = {}, filename = "")
40
50
  filename = (filename.present? ? filename : "#{template}.csv")
41
51
  path = "#{config.output_directory}/#{filename}"
42
52
  FileUtils.mkdir_p(File.dirname(path))
43
- data = Loader.data_from_template(template, additional_params)
44
- CSV.open(path, "wb") do |csv|
45
- data.each do |row|
46
- csv << row
47
- end
53
+ report = ReportLoader.load(template, metadata).report
54
+ data = report.to_csv
55
+ if File.write(path, data)
56
+ path
57
+ else
58
+ raise "Error writing to: #{path}"
48
59
  end
49
- path
50
60
  end
51
61
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reportinator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Moxvallix
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-14 00:00:00.000000000 Z
11
+ date: 2022-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json_schemer
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
41
55
  description: Behold! My Reportinator! Reportinator makes reports from JSON files.
42
56
  email:
43
57
  - dev@moxvallix.com
@@ -56,15 +70,21 @@ files:
56
70
  - Rakefile
57
71
  - app/reports/example.report.json
58
72
  - app/reports/multiplication.report.json
73
+ - app/reports/multiplication_v2.report.json
74
+ - data/schema/report_schema.json
59
75
  - docs/0_first_report.md
60
76
  - lib/reportinator.rb
61
77
  - lib/reportinator/base.rb
62
78
  - lib/reportinator/config.rb
63
79
  - lib/reportinator/function.rb
64
80
  - lib/reportinator/functions/array.rb
81
+ - lib/reportinator/functions/array/flatten.rb
65
82
  - lib/reportinator/functions/array/helper.rb
66
83
  - lib/reportinator/functions/array/join.rb
67
84
  - lib/reportinator/functions/array/method.rb
85
+ - lib/reportinator/functions/array/range.rb
86
+ - lib/reportinator/functions/array/snippet.rb
87
+ - lib/reportinator/functions/array/string.rb
68
88
  - lib/reportinator/functions/string.rb
69
89
  - lib/reportinator/functions/string/addition.rb
70
90
  - lib/reportinator/functions/string/constant.rb
@@ -75,12 +95,17 @@ files:
75
95
  - lib/reportinator/functions/string/range.rb
76
96
  - lib/reportinator/functions/string/symbol.rb
77
97
  - lib/reportinator/functions/string/variable.rb
78
- - lib/reportinator/loader.rb
98
+ - lib/reportinator/helpers.rb
79
99
  - lib/reportinator/parser.rb
80
100
  - lib/reportinator/parsers/method.rb
81
101
  - lib/reportinator/parsers/report.rb
82
102
  - lib/reportinator/parsers/value.rb
83
- - lib/reportinator/report.rb
103
+ - lib/reportinator/report/column.rb
104
+ - lib/reportinator/report/loader.rb
105
+ - lib/reportinator/report/report.rb
106
+ - lib/reportinator/report/row.rb
107
+ - lib/reportinator/report/template.rb
108
+ - lib/reportinator/report_type.rb
84
109
  - lib/reportinator/types/model.rb
85
110
  - lib/reportinator/types/preset.rb
86
111
  - lib/reportinator/version.rb
@@ -92,7 +117,7 @@ metadata:
92
117
  rubygems_mfa_required: 'true'
93
118
  homepage_uri: https://github.com/moxvallix/reportinator
94
119
  source_code_uri: https://github.com/moxvallix/reportinator
95
- changelog_uri: https://github.com/moxvallix/reportinator
120
+ changelog_uri: https://github.com/moxvallix/reportinator/blob/main/CHANGELOG.md
96
121
  post_install_message:
97
122
  rdoc_options: []
98
123
  require_paths: