reportinator 0.2.0 → 0.3.3

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.
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: