bauk-gen 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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