abide_dev_utils 0.10.1 → 0.11.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +7 -1
  4. data/Gemfile.lock +25 -19
  5. data/Rakefile +28 -0
  6. data/abide_dev_utils.gemspec +1 -0
  7. data/lib/abide_dev_utils/cem/benchmark.rb +490 -0
  8. data/lib/abide_dev_utils/cem/generate/coverage_report.rb +380 -0
  9. data/lib/abide_dev_utils/cem/generate/reference.rb +319 -0
  10. data/lib/abide_dev_utils/cem/generate.rb +11 -0
  11. data/lib/abide_dev_utils/cem/hiera_data/mapping_data/map_data.rb +110 -0
  12. data/lib/abide_dev_utils/cem/hiera_data/mapping_data/mixins.rb +46 -0
  13. data/lib/abide_dev_utils/cem/hiera_data/mapping_data.rb +146 -0
  14. data/lib/abide_dev_utils/cem/hiera_data/resource_data/control.rb +127 -0
  15. data/lib/abide_dev_utils/cem/hiera_data/resource_data/parameters.rb +90 -0
  16. data/lib/abide_dev_utils/cem/hiera_data/resource_data/resource.rb +102 -0
  17. data/lib/abide_dev_utils/cem/hiera_data/resource_data.rb +310 -0
  18. data/lib/abide_dev_utils/cem/hiera_data.rb +7 -0
  19. data/lib/abide_dev_utils/cem/mapping/mapper.rb +282 -0
  20. data/lib/abide_dev_utils/cem/validate/resource_data.rb +33 -0
  21. data/lib/abide_dev_utils/cem/validate.rb +10 -0
  22. data/lib/abide_dev_utils/cem.rb +1 -0
  23. data/lib/abide_dev_utils/cli/cem.rb +98 -0
  24. data/lib/abide_dev_utils/dot_number_comparable.rb +75 -0
  25. data/lib/abide_dev_utils/errors/cem.rb +32 -0
  26. data/lib/abide_dev_utils/errors/general.rb +8 -2
  27. data/lib/abide_dev_utils/errors/ppt.rb +4 -0
  28. data/lib/abide_dev_utils/errors.rb +6 -0
  29. data/lib/abide_dev_utils/markdown.rb +104 -0
  30. data/lib/abide_dev_utils/ppt/class_utils.rb +1 -1
  31. data/lib/abide_dev_utils/ppt/code_gen/data_types.rb +64 -0
  32. data/lib/abide_dev_utils/ppt/code_gen/generate.rb +15 -0
  33. data/lib/abide_dev_utils/ppt/code_gen/resource.rb +59 -0
  34. data/lib/abide_dev_utils/ppt/code_gen/resource_types/base.rb +93 -0
  35. data/lib/abide_dev_utils/ppt/code_gen/resource_types/class.rb +17 -0
  36. data/lib/abide_dev_utils/ppt/code_gen/resource_types/manifest.rb +16 -0
  37. data/lib/abide_dev_utils/ppt/code_gen/resource_types/parameter.rb +16 -0
  38. data/lib/abide_dev_utils/ppt/code_gen/resource_types/strings.rb +13 -0
  39. data/lib/abide_dev_utils/ppt/code_gen/resource_types.rb +6 -0
  40. data/lib/abide_dev_utils/ppt/code_gen.rb +15 -0
  41. data/lib/abide_dev_utils/ppt/code_introspection.rb +102 -0
  42. data/lib/abide_dev_utils/ppt/facter_utils.rb +140 -0
  43. data/lib/abide_dev_utils/ppt/hiera.rb +300 -0
  44. data/lib/abide_dev_utils/ppt/puppet_module.rb +75 -0
  45. data/lib/abide_dev_utils/ppt.rb +6 -5
  46. data/lib/abide_dev_utils/validate.rb +14 -0
  47. data/lib/abide_dev_utils/version.rb +1 -1
  48. data/lib/abide_dev_utils/xccdf/parser/helpers.rb +146 -0
  49. data/lib/abide_dev_utils/xccdf/parser/objects.rb +87 -144
  50. data/lib/abide_dev_utils/xccdf/parser.rb +5 -0
  51. data/lib/abide_dev_utils/xccdf/utils.rb +89 -0
  52. data/lib/abide_dev_utils/xccdf.rb +3 -0
  53. metadata +50 -3
  54. data/lib/abide_dev_utils/ppt/coverage.rb +0 -86
@@ -15,10 +15,108 @@ module Abide
15
15
  CMD_LONG = 'Namespace for commands related to Puppet CEM'
16
16
  def initialize
17
17
  super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
18
+ add_command(CemGenerate.new)
18
19
  add_command(CemUpdateConfig.new)
19
20
  end
20
21
  end
21
22
 
23
+ class CemGenerate < AbideCommand
24
+ CMD_NAME = 'generate'
25
+ CMD_SHORT = 'Holds subcommands for generating objects / files'
26
+ CMD_LONG = 'Holds subcommands for generating objects / files'
27
+ def initialize
28
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
29
+ add_command(CemGenerateCoverageReport.new)
30
+ add_command(CemGenerateReference.new)
31
+ end
32
+ end
33
+
34
+ class CemGenerateCoverageReport < AbideCommand
35
+ CMD_NAME = 'coverage-report'
36
+ CMD_SHORT = 'Generates control coverage report'
37
+ CMD_LONG = <<-EOLC.chomp
38
+ Generates report of resources that are associated with controls in mapping data. This command must
39
+ be run from a module directory.
40
+ EOLC
41
+ def initialize
42
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
43
+ options.on('-o [FILE]', '--out-file [FILE]', 'Path to save the coverage report') { |f| @data[:file] = f }
44
+ options.on('-f [FORMAT]', '--format [FORMAT]', 'The format to output the report in (hash, json, yaml)') do |f|
45
+ @data[:format] = f
46
+ end
47
+ options.on('-B [BENCHMARK]', '--benchmark [BENCHMARK]', 'Specify the benchmark to show coverage for') do |x|
48
+ @data[:benchmark] = x
49
+ end
50
+ options.on('-P [PROFILE]', '--profile [PROFILE]', 'Specifiy the profile to show coverage for') do |x|
51
+ @data[:profile] = x
52
+ end
53
+ options.on('-L [LEVEL]', '--level [LEVEL]', 'Specify the level to show coverage for') do |l|
54
+ @data[:profile] = l
55
+ end
56
+ options.on('-I', '--ignore-benchmark-errors', 'Ignores errors while generating benchmark reports') do
57
+ @data[:ignore_all] = true
58
+ end
59
+ options.on('-X [XCCDF_DIR]', '--xccdf-dir [XCCDF_DIR]', 'If specified, the coverage report will be correlated with info from the benchmark XCCDF files') do |d|
60
+ @data[:xccdf_dir] = d
61
+ end
62
+ options.on('-v', '--verbose', 'Will output the report to the console') { @data[:verbose] = true }
63
+ options.on('-q', '--quiet', 'Will not output anything to the console') { @data[:quiet] = true }
64
+ end
65
+
66
+ def execute
67
+ file_name = @data.fetch(:file, 'coverage_report')
68
+ out_format = @data.fetch(:format, 'yaml')
69
+ quiet = @data.fetch(:quiet, false)
70
+ console = @data.fetch(:verbose, false) && !quiet
71
+ generate_opts = {
72
+ benchmark: @data.fetch(:benchmark),
73
+ profile: @data.fetch(:profile),
74
+ level: @data.fetch(:level),
75
+ ignore_benchmark_errors: @data.fetch(:ignore_all, false),
76
+ xccdf_dir: @data.fetch(:xccdf_dir),
77
+ }
78
+ AbideDevUtils::Output.simple('Generating coverage report...') unless quiet
79
+ coverage = AbideDevUtils::CEM::Generate::CoverageReport.generate(format_func: :to_h, opts: generate_opts)
80
+ AbideDevUtils::Output.simple("Saving coverage report to #{file_name}...")
81
+ case out_format
82
+ when /yaml/i
83
+ AbideDevUtils::Output.yaml(coverage, console: console, file: file_name)
84
+ when /json/i
85
+ AbideDevUtils::Output.json(coverage, console: console, file: file_name)
86
+ else
87
+ File.open(file_name, 'w') do |f|
88
+ AbideDevUtils::Output.simple(coverage.to_s, stream: f)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ class CemGenerateReference < AbideCommand
95
+ CMD_NAME = 'reference'
96
+ CMD_SHORT = 'Generates a reference doc for the module'
97
+ CMD_LONG = 'Generates a reference doc for the module'
98
+ def initialize
99
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
100
+ options.on('-o [FILE]', '--out-file [FILE]', 'Path to save the updated config file') do |o|
101
+ @data[:out_file] = o
102
+ end
103
+ options.on('-f [FORMAT]', '--format [FORMAT]', 'Format to save reference as') do |f|
104
+ @data[:format] = f
105
+ end
106
+ options.on('-v', '--verbose', 'Verbose output') do
107
+ @data[:verbose] = true
108
+ end
109
+ options.on('-q', '--quiet', 'Quiet output') do
110
+ @data[:quiet] = true
111
+ end
112
+ end
113
+
114
+ def execute
115
+ AbideDevUtils::Validate.puppet_module_directory
116
+ AbideDevUtils::CEM::Generate::Reference.generate(@data)
117
+ end
118
+ end
119
+
22
120
  class CemUpdateConfig < AbideCommand
23
121
  CMD_NAME = 'update-config'
24
122
  CMD_SHORT = 'Updates the Puppet CEM config'
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbideDevUtils
4
+ # Module provides comparison methods for "dot numbers", numbers that
5
+ # take the form of "1.1.1" as found in CIS benchmarks. Classes that
6
+ # include this module must implement a method "number" that returns
7
+ # their dot number representation.
8
+ module DotNumberComparable
9
+ include ::Comparable
10
+
11
+ def <=>(other)
12
+ return 0 if number_eq(number, other.number)
13
+ return 1 if number_gt(number, other.number)
14
+ return -1 if number_lt(number, other.number)
15
+ end
16
+
17
+ def number_eq(this_num, other_num)
18
+ this_num == other_num
19
+ end
20
+
21
+ def number_parent_of?(this_num, other_num)
22
+ return false if number_eq(this_num, other_num)
23
+
24
+ # We split the numbers into parts and compare the resulting arrays
25
+ num1_parts = this_num.to_s.split('.')
26
+ num2_parts = other_num.to_s.split('.')
27
+ # For this_num to be a parent of other_num, the number of parts in
28
+ # this_num must be less than the number of parts in other_num.
29
+ # Additionally, each part of this_num must be equal to the parts of
30
+ # other_num at the same index.
31
+ # Example: this_num = '1.2.3' and other_num = '1.2.3.4'
32
+ # In this case, num1_parts = ['1', '2', '3'] and num2_parts = ['1', '2', '3', '4']
33
+ # So, this_num is a parent of other_num because at indexes 0, 1, and 2
34
+ # of num1_parts and num2_parts, the parts are equal.
35
+ num1_parts.length < num2_parts.length &&
36
+ num2_parts[0..(num1_parts.length - 1)] == num1_parts
37
+ end
38
+
39
+ def number_child_of?(this_num, other_num)
40
+ number_parent_of?(other_num, this_num)
41
+ end
42
+
43
+ def number_gt(this_num, other_num)
44
+ return false if number_eq(this_num, other_num)
45
+ return true if number_parent_of?(this_num, other_num)
46
+
47
+ num1_parts = this_num.to_s.split('.')
48
+ num2_parts = other_num.to_s.split('.')
49
+ num1_parts.zip(num2_parts).each do |num1_part, num2_part|
50
+ next if num1_part == num2_part # we skip past equal parts
51
+
52
+ # If num1_part is nil that means that we've had equal numbers so far.
53
+ # Therfore, this_num is greater than other num because of the
54
+ # hierarchical nature of the numbers.
55
+ # Example: this_num = '1.2' and other_num = '1.2.3'
56
+ # In this case, num1_part is nil and num2_part is '3'
57
+ # So, this_num is greater than other_num
58
+ return true if num1_part.nil?
59
+ # If num2_part is nil that means that we've had equal numbers so far.
60
+ # Therfore, this_num is less than other num because of the
61
+ # hierarchical nature of the numbers.
62
+ # Example: this_num = '1.2.3' and other_num = '1.2'
63
+ # In this case, num1_part is '3' and num2_part is nil
64
+ # So, this_num is less than other_num
65
+ return false if num2_part.nil?
66
+
67
+ return num1_part.to_i > num2_part.to_i
68
+ end
69
+ end
70
+
71
+ def number_lt(this_num, other_num)
72
+ number_gt(other_num, this_num)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/errors/base'
4
+
5
+ module AbideDevUtils
6
+ module Errors
7
+ # Raised by Benchmark when mapping data cannot be loaded
8
+ class MappingFilesNotFoundError < GenericError
9
+ @default = 'Mapping files not found using facts:'
10
+ end
11
+
12
+ # Raised by Benchmark when mapping files are not found for the specified framework
13
+ class MappingDataFrameworkMismatchError < GenericError
14
+ @default = 'Mapping data could not be found for the specified framework:'
15
+ end
16
+
17
+ # Raised by Benchmark when resource data cannot be loaded
18
+ class ResourceDataNotFoundError < GenericError
19
+ @default = 'Resource data not found using facts:'
20
+ end
21
+
22
+ # Raised by Control when it can't find mapping data for itself
23
+ class NoMappingDataForControlError < GenericError
24
+ @default = 'No mapping data found for control:'
25
+ end
26
+
27
+ # Raised by a control when it's given ID and framework are incompatible
28
+ class ControlIdFrameworkMismatchError < GenericError
29
+ @default = 'Control ID is invalid with the given framework:'
30
+ end
31
+ end
32
+ end
@@ -9,7 +9,12 @@ module AbideDevUtils
9
9
  @default = 'Object is empty and should not be:'
10
10
  end
11
11
 
12
- # Raised when a an object is initialized with a nil param
12
+ # Raised when something is not a string, or is an empty string
13
+ class NotPopulatedStringError < GenericError
14
+ @default = 'Object is either not a String or is empty:'
15
+ end
16
+
17
+ # Raised when an object is initialized with a nil param
13
18
  class NewObjectParamNilError < GenericError
14
19
  @default = 'Object init parameter is nil and should not be:'
15
20
  end
@@ -54,8 +59,9 @@ module AbideDevUtils
54
59
  @default = 'Object does not respond to #to_hash or #to_h:'
55
60
  end
56
61
 
62
+ # Raised when conflicting CLI options are specified for a command
57
63
  class CliOptionsConflict < GenericError
58
- @default = "Console options conflict."
64
+ @default = 'Console options conflict:'
59
65
  end
60
66
  end
61
67
  end
@@ -5,6 +5,10 @@ require 'abide_dev_utils/errors/base'
5
5
  module AbideDevUtils
6
6
  module Errors
7
7
  module Ppt
8
+ class NotModuleDirError < GenericError
9
+ @default = 'Path is not a Puppet module directory:'
10
+ end
11
+
8
12
  class ObjClassPathError < GenericError
9
13
  @default = 'Invalid path for class:'
10
14
  end
@@ -1,9 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'abide_dev_utils/errors/base'
4
+ require 'abide_dev_utils/errors/cem'
4
5
  require 'abide_dev_utils/errors/comply'
5
6
  require 'abide_dev_utils/errors/gcloud'
6
7
  require 'abide_dev_utils/errors/general'
7
8
  require 'abide_dev_utils/errors/jira'
8
9
  require 'abide_dev_utils/errors/xccdf'
9
10
  require 'abide_dev_utils/errors/ppt'
11
+
12
+ module AbideDevUtils
13
+ # Namespace for Error objects
14
+ module Errors; end
15
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbideDevUtils
4
+ # Formats text for output in markdown
5
+ class Markdown
6
+ def initialize(file, with_toc: true)
7
+ @file = file
8
+ @with_toc = with_toc
9
+ @toc = ["## Table of Contents\n"]
10
+ @body = []
11
+ @title = nil
12
+ end
13
+
14
+ def to_markdown
15
+ toc = @toc.join("\n")
16
+ body = @body.join("\n")
17
+ "#{@title}\n#{toc}\n\n#{body}"
18
+ end
19
+
20
+ def to_file
21
+ File.write(@file, to_markdown)
22
+ end
23
+
24
+ def method_missing(name, *args, &block)
25
+ if name.to_s.start_with?('add_')
26
+ add(name.to_s.sub('add_', '').to_sym, *args, &block)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def respond_to_missing?(name, include_private = false)
33
+ name.to_s.start_with?('add_') || super
34
+ end
35
+
36
+ def title(text)
37
+ "# #{text}\n"
38
+ end
39
+
40
+ def h1(text)
41
+ "## #{text}\n"
42
+ end
43
+
44
+ def h2(text)
45
+ "### #{text}\n"
46
+ end
47
+
48
+ def h3(text)
49
+ "#### #{text}\n"
50
+ end
51
+
52
+ def ul(text, indent: 0)
53
+ indented_text = []
54
+ indent.times { indented_text << ' ' } if indent.positive?
55
+
56
+ indented_text << "* #{text}"
57
+ indented_text.join
58
+ end
59
+
60
+ def bold(text)
61
+ "**#{text}**"
62
+ end
63
+
64
+ def italic(text)
65
+ "*#{text}*"
66
+ end
67
+
68
+ def link(text, url, anchor: false)
69
+ url = anchor(url) if anchor
70
+ "[#{text}](#{url.downcase})"
71
+ end
72
+
73
+ def code(text)
74
+ "\`#{text}\`"
75
+ end
76
+
77
+ def code_block(text, language: nil)
78
+ language.nil? ? "```\n#{text}\n```" : "```#{language}\n#{text}\n```"
79
+ end
80
+
81
+ def anchor(text)
82
+ "##{text.downcase.gsub(%r{\s|_}, '-').tr('.,\'"()', '')}"
83
+ end
84
+
85
+ private
86
+
87
+ def add(type, text, *args, **kwargs)
88
+ @toc << ul(link(text, text, anchor: true), indent: 0) if @with_toc && type == :h1
89
+
90
+ case type.to_sym
91
+ when :title
92
+ @title = title(text)
93
+ when :ul
94
+ @body << ul(text, indent: kwargs.fetch(:indent, 0))
95
+ when :link
96
+ @body << link(text, args.first, anchor: kwargs.fetch(:anchor, false))
97
+ when :code_block
98
+ @body << code_block(text, language: kwargs.fetch(:language, nil))
99
+ else
100
+ @body << send(type, text)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -27,7 +27,7 @@ module AbideDevUtils
27
27
  def self.path_from_class_name(class_name)
28
28
  parts = class_name.split('::')
29
29
  parts[-1] = "#{parts[-1]}.pp"
30
- File.expand_path(File.join('manifests', parts[1..-1]))
30
+ File.expand_path(File.join('manifests', parts[1..]))
31
31
  end
32
32
 
33
33
  # Returns the namespaced class name from a file path
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppet'
4
+
5
+ module AbideDevUtils
6
+ module Ppt
7
+ module CodeGen
8
+ module DataTypes
9
+ def infer_data_type(data)
10
+ Puppet::Pops::Types::TypeCalculator.infer(data).to_s
11
+ end
12
+
13
+ # Displays a Puppet type value as a string
14
+ def display_value(val)
15
+ if val.is_a?(Puppet::Pops::Model::LiteralUndef)
16
+ 'undef'
17
+ elsif val.respond_to?(:value)
18
+ display_value(val.value)
19
+ elsif val.respond_to?(:cased_value)
20
+ display_value(val.cased_value)
21
+ else
22
+ val
23
+ end
24
+ end
25
+
26
+ # Displays a Puppet type expression (type signature) as a string
27
+ # @param param [Puppet::Pops::Model::Parameter] AST Parameter node of a parsed Puppet manifest
28
+ def display_type_expr(param)
29
+ te = param.respond_to?(:type_expr) ? param.type_expr : param
30
+ if te.respond_to? :left_expr
31
+ display_type_expr_with_left_expr(te)
32
+ elsif te.respond_to? :entries
33
+ display_type_expr_with_entries(te)
34
+ elsif te.respond_to? :cased_value
35
+ te.cased_value
36
+ elsif te.respond_to? :value
37
+ te.value
38
+ end
39
+ end
40
+
41
+ # Used by #display_type_expr
42
+ def display_type_expr_with_left_expr(te)
43
+ cased = nil
44
+ keys = nil
45
+ cased = te.left_expr.cased_value if te.left_expr.respond_to? :cased_value
46
+ keys = te.keys.map { |x| display_type_expr(x) }.to_s if te.respond_to? :keys
47
+ keys.tr!('"', '') unless cased == 'Enum'
48
+ "#{cased}#{keys}"
49
+ end
50
+
51
+ # Used by #display_type_expr
52
+ def display_type_expr_with_entries(te)
53
+ te.entries.each_with_object({}) do |x, hsh|
54
+ key = nil
55
+ val = nil
56
+ key = display_value(x.key) if x.respond_to? :key
57
+ val = display_type_expr(x.value) if x.respond_to? :value
58
+ hsh[key] = val if key
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/ppt/code_gen/resource_types'
4
+
5
+ module AbideDevUtils
6
+ module Ppt
7
+ module CodeGen
8
+ module Generate
9
+ def self.a_manifest
10
+ AbideDevUtils::Ppt::CodeGen::Manifest.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbideDevUtils
4
+ module Ppt
5
+ module CodeGen
6
+ class Resource
7
+ attr_reader :type, :title
8
+
9
+ def initialize(type, title, **attributes)
10
+ validate_type_and_title(type, title)
11
+ @type = type
12
+ @title = title
13
+ @attributes = attributes
14
+ end
15
+
16
+ def reference
17
+ "#{title.split('::').map(&:capitalize).join('::')}['#{title}']"
18
+ end
19
+
20
+ def to_s
21
+ return "#{type} { '#{title}': }" if @attributes.empty?
22
+
23
+ str_array = ["#{type} { '#{title}':"]
24
+ @attributes.each do |key, val|
25
+ str_array << " #{pad_attribute(key)} => #{val},"
26
+ end
27
+ str_array << '}'
28
+ str_array.join("\n")
29
+ end
30
+
31
+ private
32
+
33
+ def validate_type_and_title(type, title)
34
+ raise 'Type / title must be String' unless type.is_a?(String) && title.is_a?(String)
35
+ raise 'Type / title must not be empty' if type.empty? || title.empty?
36
+ end
37
+
38
+ def longest_attribute_length
39
+ return @longest_attribute_length if defined?(@longest_attribute_length)
40
+
41
+ longest = ''
42
+ @attributes.each_key do |k|
43
+ longest = k if k.length > longest.length
44
+ end
45
+ @longest_attribute_length = longest.length
46
+ @longest_attribute_length
47
+ end
48
+
49
+ def pad_attribute(attribute)
50
+ return attribute if attribute.length == longest_attribute_length
51
+
52
+ attr_array = [attribute]
53
+ (longest_attribute_length - attribute.length).times { attr_array << ' ' }
54
+ attr_array.join
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module AbideDevUtils
6
+ module Ppt
7
+ module CodeGen
8
+ # Base class for all code gen objects
9
+ class Base
10
+ attr_accessor :title, :id
11
+
12
+ def initialize
13
+ @id = SecureRandom.hex(10)
14
+ @supports_value = false
15
+ @supports_children = false
16
+ end
17
+
18
+ def to_s
19
+ "#{type} : value: #{@value}; children: #{@children}"
20
+ end
21
+
22
+ def reference
23
+ raise NotImplementedError, "#{type} does not support having a reference"
24
+ end
25
+
26
+ def type
27
+ self.class.to_s
28
+ end
29
+
30
+ def value
31
+ raise NotImplementedError, "#{type} does not support having a value" unless @supports_value
32
+
33
+ @value
34
+ end
35
+
36
+ def get_my(t, named: nil)
37
+ if named.nil?
38
+ children.each_with_object([]) do |(k, v), arr|
39
+ arr << v if k.start_with?("#{t.to_s.capitalize}_")
40
+ end
41
+ else
42
+ children["#{t.to_s.capitalize}_#{named}"]
43
+ end
44
+ end
45
+
46
+ # Creates a new object of the given type and adds it to the current objects children
47
+ # if the current object supports children.
48
+ # Returns `self`. If a block is given, the new
49
+ # object will be yielded before adding to children.
50
+ def with_a(t, named: nil)
51
+ obj = Object.const_get("AbideDevUtils::Ppt::CodeGen::#{t.to_s.capitalize}").new
52
+ obj.title = named unless named.nil? || named.empty?
53
+
54
+ yield obj if block_given?
55
+
56
+ children["#{t.to_s.capitalize}_#{obj.id}"] = obj
57
+ self
58
+ end
59
+ alias and_a with_a
60
+
61
+ def has_a(t, named: nil)
62
+ obj = Object.const_get("AbideDevUtils::Ppt::CodeGen::#{t.to_s.capitalize}").new
63
+ obj.title = named unless named.nil? || named.empty?
64
+ children["#{t.to_s.capitalize}_#{obj.id}"] = obj
65
+ obj
66
+ end
67
+ alias and_has_a has_a
68
+ alias that_has_a has_a
69
+
70
+ # Sets the explicit value of the current object if the current object has an explicit value.
71
+ def that_equals(val)
72
+ self.value = val
73
+ self
74
+ end
75
+ alias and_assign_a_value_of that_equals
76
+ alias has_a_value_of that_equals
77
+ alias that_has_a_value_of that_equals
78
+
79
+ private
80
+
81
+ def children
82
+ raise NotImplementedError, "#{type} does not support children" unless @supports_children
83
+
84
+ @children ||= {}
85
+ end
86
+
87
+ def value=(val)
88
+ @value = val if @supports_value
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/ppt/code_gen/resource_types/base'
4
+
5
+ module AbideDevUtils
6
+ module Ppt
7
+ module CodeGen
8
+ class Class < Base
9
+ def initialize
10
+ super
11
+ @supports_children = true
12
+ @supports_value = true
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/ppt/code_gen/resource_types/base'
4
+
5
+ module AbideDevUtils
6
+ module Ppt
7
+ module CodeGen
8
+ class Manifest < Base
9
+ def initialize
10
+ super
11
+ @supports_children = true
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/ppt/code_gen/resource_types/base'
4
+
5
+ module AbideDevUtils
6
+ module Ppt
7
+ module CodeGen
8
+ class Parameter < Base
9
+ def initialize
10
+ @supports_children = true
11
+ @supports_value = true
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/ppt/code_gen/resource_types/base'
4
+
5
+ module AbideDevUtils
6
+ module Ppt
7
+ module CodeGen
8
+ class Strings < Base
9
+ VALID_CHILDREN = %w[See Summary Param Example].freeze
10
+ end
11
+ end
12
+ end
13
+ end