bauk-gen 0.0.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.
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bauk/utils/log'
4
+ require 'bauk/gen/configs/files'
5
+ require 'fileutils'
6
+
7
+ module Bauk
8
+ module Gen
9
+ # Class purely used for init
10
+ module Init
11
+ include Bauk::Utils::Log
12
+ def init
13
+ FileUtils.mkdir_p '.bauk/generator'
14
+ init_templates.each do |file,content|
15
+ file = ".bauk/generator/templates/#{name}/#{file}"
16
+ FileUtils.mkdir_p File.dirname(file.to_s)
17
+ File.write(file.to_s, content)
18
+ end
19
+ init_files.each do |file,content|
20
+ FileUtils.mkdir_p File.dirname(file.to_s)
21
+ File.write(file.to_s, content)
22
+ end
23
+ init_config_file
24
+ end
25
+
26
+ def init_files
27
+ {}
28
+ end
29
+
30
+ def init_templates
31
+ {
32
+ 'exampleGenerated/example..erb': <<~FILE
33
+ <%= name %>
34
+ FILE,
35
+ 'example_generated..erb': <<~FILE
36
+ Name: <%= name %>
37
+ Description: <%= description %>
38
+ FILE
39
+ }
40
+ end
41
+
42
+ def init_config_file
43
+ file = Bauk::Gen::Configs::Files.new.default_files[0]
44
+ if File.exist? file
45
+ log.error "File already exists: #{file}"
46
+ else
47
+ init_config = default_config
48
+ unless init_config[:generators]
49
+ init_config[:generators] = {}
50
+ init_config[:generators][name.to_sym] = {}
51
+ end
52
+ File.write(file, YAML.dump(init_config))
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bauk/utils/log'
4
+
5
+ module Bauk
6
+ module Gen
7
+ module Inputs
8
+ # Base class for inputs
9
+ # Each input needs to overwrite the following methods:
10
+ # - input_items(items)
11
+ # -> Takes a hash of items and adds
12
+ class Base
13
+ def input_items(_items)
14
+ raise NotImplementedError
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bauk
4
+ module Gen
5
+ module Inputs
6
+ class Error < StandardError
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bauk/utils/log'
4
+ require 'bauk/gen/inputs/base'
5
+ require 'bauk/gen/contents/erb_content'
6
+ require 'bauk/gen/contents/file_content'
7
+ require 'ostruct'
8
+ require 'deep_merge'
9
+
10
+ module Bauk
11
+ module Gen
12
+ module Inputs
13
+ class Templates < Base
14
+ require 'bauk/utils/array_utils'
15
+ include Bauk::Utils::Log
16
+
17
+ Contents = Bauk::Gen::Contents
18
+ TEMPLATE_KEYS = %i[name dirs extra_dirs].freeze
19
+ ATTRIBUTE_VALUE_MAPPINGS = {
20
+ /^(yes|true)$/ => true,
21
+ /^(no|false|)$/ => false # Empty string is also false
22
+ }.freeze
23
+
24
+ def initialize(generator, config = {})
25
+ @generator = generator
26
+ config[:inputs] ||= {}
27
+ config[:inputs][:templates] ||= {}
28
+ template_config = config[:inputs][:templates]
29
+ @template_name = template_config[:template_name] || generator.name
30
+ setup_dirs template_config
31
+ invalid_keys = template_config.keys.reject { |key| TEMPLATE_KEYS.include? key }
32
+ unless invalid_keys.empty?
33
+ raise "Invalid keys passed to Bauk::Gen::Inputs::Templates: #{invalid_keys.join ', '}"
34
+ end
35
+ end
36
+
37
+ def setup_dirs(template_config)
38
+ template_dirs = template_config[:dirs] || default_template_dirs
39
+ template_dirs.unshift(*template_config[:extra_dirs]) if template_config.include?(:extra_dirs)
40
+ log.debug "Checking template dirs: #{template_dirs.join(', ')} (#{@template_name})"
41
+ @template_dirs = sanitise_dirs template_dirs
42
+ if @template_dirs.empty?
43
+ log.error "No template directories found for generator: '#{@generator.name}'. Searched dirs: #{template_dirs.join_custom}"
44
+ else
45
+ log.info "Using template dirs: #{@template_dirs}"
46
+ end
47
+ end
48
+
49
+ def default_template_dirs
50
+ [
51
+ '.bauk/generator/templates',
52
+ '~/.bauk/generator/templates',
53
+ File.expand_path('../../../../templates', __dir__)
54
+ ]
55
+ end
56
+
57
+ # This function returns the absolute paths of only the provided dirs that exist
58
+ def sanitise_dirs(dirs)
59
+ dirs.map do |dir|
60
+ File.expand_path("#{dir}/#{@template_name}")
61
+ end.select do |dir|
62
+ File.directory? dir
63
+ end
64
+ end
65
+
66
+ def input_items(items)
67
+ @template_dirs.each do |dir|
68
+ input_items_from_dir(items, dir)
69
+ end
70
+ end
71
+
72
+ def input_items_from_dir(items, base_dir, sub_dir = '', dir_attributes = {})
73
+ dir = "#{base_dir}/#{sub_dir}"
74
+ log.debug "Adding items from dir: #{dir}"
75
+ Dir.entries(dir).reject { |d| %w[. ..].include?(d) || d =~ /\.\.config$/ }.each do |filename|
76
+ path = sub_dir.empty? ? filename.clone : "#{sub_dir}/#{filename}"
77
+ full_path = "#{base_dir}/#{path}"
78
+ name = path.clone.dup
79
+ attributes = dir_attributes.merge(strip_attributes_from_name!(name))
80
+ # Check if there is a config file to read in
81
+ config_file = "#{base_dir}/#{name}..config"
82
+ if File.exist? config_file
83
+ log.debug "Loading in config for #{name} from file: #{config_file}"
84
+ eval File.read(config_file)
85
+ end
86
+ if File.directory? full_path
87
+ next if attributes.key?(:if) and not check_if(attributes[:if])
88
+ input_items_from_dir(items, base_dir, path, attributes)
89
+ else
90
+ if attributes[:foreach]
91
+ foreach = @generator.config.dig(*attributes[:foreach].split(':').map{|i| i.to_sym})
92
+ log.debug "Looping through foreach: #{foreach}"
93
+ if foreach.is_a? Hash
94
+ foreach.each do |key,value|
95
+ add_file_to_items items: items, name: name, attributes: attributes, full_path: full_path, config: {key: key, value: value}
96
+ end
97
+ elsif foreach.is_a? ::Array
98
+ foreach.each do |value|
99
+ add_file_to_items items: items, name: name, attributes: attributes, full_path: full_path, config: {value: value}
100
+ end
101
+ else
102
+ log.debug "Foreach: #{foreach}"
103
+ raise "Cannot itterate though foreach(#{attributes[:foreach]})"
104
+ end
105
+ else
106
+ add_file_to_items items: items, name: name, attributes: attributes, full_path: full_path
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ # Config variable extra complex (extra_if_config) to ensure it does notclash with a key in @generator.config as this causes issues
113
+ def check_if(if_value, extra_if_config = {})
114
+ # TODO: Use Openstruct to remove this self-binding config issue
115
+ b = binding
116
+ @generator.config.each do |key,value|
117
+ b.local_variable_set(key.to_sym, value)
118
+ end
119
+ extra_if_config.each do |key,value|
120
+ b.local_variable_set(key.to_sym, value)
121
+ end
122
+ if if_value.is_a? String
123
+ if eval(if_value, b)
124
+ return true
125
+ end
126
+ elsif if_value
127
+ return true
128
+ end
129
+ false
130
+ end
131
+
132
+ def add_file_to_items(items:, name:, attributes:, full_path:, config: {})
133
+ return if attributes.key?(:if) and not check_if(attributes[:if], config)
134
+ merged_config = @generator.config.clone
135
+ merged_config.deep_merge!(config)
136
+
137
+ # Substitute any variables in the name
138
+ dst_name = name.clone
139
+ name.scan(/%=([^%]*)%/).flatten.each do |path_item|
140
+ path_value = merged_config.dig(*path_item.split('.').map{|i| i.to_sym})
141
+ raise "Could not file '#{path_item}' config for '#{name}'" unless path_value
142
+ dst_name.sub!("%=#{path_item}%", path_value.to_s)
143
+ end
144
+
145
+ log.debug "Adding item: '#{dst_name}'. Attributes: #{attributes}"
146
+ if items[dst_name]
147
+ log.warn "Overwriting #{dst_name} with template: #{full_path}"
148
+ end
149
+ items[dst_name] = {
150
+ name: dst_name,
151
+ attributes: attributes,
152
+ content: if attributes[:erb]
153
+ Contents::ErbContent.new(file: full_path, config: merged_config, attributes: attributes)
154
+ else
155
+ Contents::FileContent.new(file: full_path, config: merged_config, attributes: attributes)
156
+ end
157
+ }
158
+ end
159
+
160
+ def strip_attributes_from_name!(name)
161
+ return {} unless name =~ /\.\./
162
+ log.debug("strip_attributes_from_name!(#{name})")
163
+
164
+ # First strip any attributes from parent folders as those are merged in by default
165
+ # If a folder started with .. then that whole folder should be removed
166
+ name.gsub!(%r{\.\.([^/\.]+\.?)+/}, '/')
167
+ name.gsub!(%r{^/*}, '')
168
+ name.gsub!(%r{//*}, '/')
169
+
170
+ parts = name.split('/').last().split('..')
171
+ attr_string = parts.pop
172
+ name.sub!("..#{attr_string}", '')
173
+ log.debug("strip_attributes_from_name!.attr_string = #{attr_string}")
174
+
175
+ attributes = {}
176
+ attr_string.split('.').each do |attribute|
177
+ if attribute =~ /=/
178
+ vals = attribute.split('=')
179
+ key = vals.shift.to_sym
180
+ value = vals.join('=')
181
+ ATTRIBUTE_VALUE_MAPPINGS.each do |mapping, val|
182
+ next unless value =~ mapping
183
+
184
+ value = val
185
+ break
186
+ end
187
+ attributes[key] = value
188
+ else
189
+ attributes[attribute.to_sym] = true
190
+ end
191
+ end
192
+ attributes
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bauk/utils/log'
4
+ require 'bauk/gen/inputs/templates'
5
+ require 'bauk/gen/outputs/filesystem'
6
+ require 'bauk/gen/inputs/error'
7
+ require 'bauk/gen/configs/files'
8
+ require 'bauk/gen/config_utils'
9
+ require 'deep_merge'
10
+
11
+ module Bauk
12
+ module Gen
13
+ # This class is the base module that all others should extend from.
14
+ # It contains methods for generating items and creating default config.
15
+ # It itself is not a valid module as modules need names
16
+ # All config for a module needs to be in the config modules->#name
17
+ # It takes 3 arguments:
18
+ # - Calling generator
19
+ # - Config generated by the generator
20
+ # - Map of options
21
+ # This map contains the possible keys:
22
+ # - disable_by_default: Whether to not enable this generator unless the
23
+ # config explicity sets this value to true or a hash.
24
+ # e.g {modules:{moduleA:true}
25
+ class Module
26
+ include Bauk::Utils::Log
27
+ include ConfigUtils
28
+
29
+ def initialize(generator, config, map = {})
30
+ @generator_config = config
31
+ @generator = generator
32
+ @map = map
33
+ end
34
+
35
+ def config
36
+ return @config if @config
37
+ @config = default_config.deep_merge! @generator_config
38
+ @config = @generator_config
39
+ @config[:modules] ||= {}
40
+ @config[:modules][name] ||= {}
41
+ @config[:modules][name] = {} if config[:modules][name].eql? true
42
+ # Overwrite values with this module values
43
+ @config = @config.deep_merge! @config[:modules][name]
44
+ config
45
+ end
46
+
47
+ # Function to tell the calling generator whether this module is active andshould be used
48
+ # to generate items. It depends on whether the config privided
49
+ def active?
50
+ if @map[:disable_by_default] then @config
51
+ else !@config.eql?(false)
52
+ end
53
+ end
54
+
55
+ # Method to check validate the config being provided to the generator.
56
+ # This overrride also takes an argument of the generator config in case
57
+ # default values want to be derived.
58
+ def validate_config(_generator_config); end
59
+
60
+ # This function contains the default list of inputs
61
+ # It defaults to using the inputs from the root generator
62
+ def input_generators
63
+ @generator.input_generators
64
+ end
65
+
66
+ # This function gets the items from the inputs.
67
+ # It does so by itterating though each input and passing it
68
+ # the global list of items to add to.
69
+ def input_items(items)
70
+ log.info "Inputting items from module: #{name} (#{self.class.name})"
71
+ input_generators.each do |i_gen|
72
+ i_gen.new(self, config).input_items(items)
73
+ end
74
+ end
75
+
76
+ # Method to get generator name from class
77
+ def name
78
+ return @name if @name
79
+
80
+ @name = underscore(self.class.name.split('::').join('_').sub(/_*module$/i, '')).to_sym
81
+ raise "Invalid nameless module provided: #{self.class.name}" if @name.empty?
82
+
83
+ @name
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bauk/utils/log'
4
+ require 'fileutils'
5
+
6
+ module Bauk
7
+ module Gen
8
+ module Outputs
9
+ # Base class for all outputters
10
+ # Each outputter needs to implement the following methods:
11
+ # - output_items(items)
12
+ class Base
13
+ include Bauk::Utils::Log
14
+
15
+ def initialize(_generator, config = {})
16
+ config[:outputs] ||= {}
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bauk
4
+ module Gen
5
+ module Outputs
6
+ class Error < StandardError
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bauk/utils/log'
4
+ require 'fileutils'
5
+ require 'bauk/gen/contents/file_content'
6
+ require 'bauk/gen/outputs/base'
7
+
8
+ module Bauk
9
+ module Gen
10
+ module Outputs
11
+ class Filesystem < Base
12
+ include Bauk::Utils::Log
13
+ FILESYSTEM_KEYS = %s(output_base)
14
+ Contents = Bauk::Gen::Contents
15
+
16
+ def initialize(generator, config = {})
17
+ super generator, config
18
+ config[:outputs][:filesystem] ||= {}
19
+ filesystem_config = config[:outputs][:filesystem]
20
+ @output_base = File.expand_path(filesystem_config[:output_base] || '.')
21
+ invalid_keys = filesystem_config.keys.reject { |key| FILESYSTEM_KEYS.include? key }
22
+ unless invalid_keys.empty?
23
+ raise "Invalid keys passed to Bauk::Gen::Outputs::Filesystem: #{invalid_keys.join ', '}"
24
+ end
25
+ end
26
+
27
+ def default_template_dirs
28
+ [
29
+ '.bauk/generator/templates',
30
+ '~/.bauk/generator/templates',
31
+ File.expand_path('../../../../templates', __dir__)
32
+ ]
33
+ end
34
+
35
+ # This function returns the absoluts path of only the provided dirs that exist
36
+ def sanitise_dirs(dirs)
37
+ dirs.map do |dir|
38
+ File.expand_path("#{dir}/#{@template_name}")
39
+ end.select do |dir|
40
+ File.directory? dir
41
+ end
42
+ end
43
+
44
+ def output_items(items)
45
+ Dir.chdir(@output_base) do
46
+ log.info "Outputting items to dir: #{@output_base}"
47
+ items.each do |name, item|
48
+ log.debug "Outputting item: #{name}(#{item})"
49
+ if name =~ %r{/}
50
+ parent_dir = name.sub(%r{/[^/]*$}, '')
51
+ FileUtils.mkdir_p parent_dir
52
+ end
53
+ if item[:attributes][:merge] and File.exist?(name)
54
+ merge_item name, item
55
+ elsif item[:attributes][:overwrite] == false and File.exist?(name)
56
+ log.info "Not overwriting file: '#{name}'"
57
+ else
58
+ output_item name, item
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def merge_item(name, item)
65
+ if item[:content].respond_to? :merge
66
+ File.write(name, item[:content].merge(File.read(name)))
67
+ else
68
+ log.warn "Content type for '#{name}' does not respond to merge request: #{item[:content]} (Outputting without merge)"
69
+ output_item name, item
70
+ end
71
+ end
72
+
73
+ def output_item(name, item)
74
+ if item[:content].is_a? Contents::FileContent
75
+ FileUtils.cp item[:content].file.path, name
76
+ elsif item[:content].respond_to? :content
77
+ File.write(name, item[:content].content)
78
+ else
79
+ raise "Invalid content found for #{name}: #{item[:content]}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end