k_builder 0.0.28 → 0.0.53

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2dcd63316f0cc08692e83ef120cd5a9b940a4e3b73439cda7e6a298be420a05f
4
- data.tar.gz: '08e1adbd07386448c9c60bbdacbc069257660e216d8fd0aea6417bae94ee5342'
3
+ metadata.gz: 122c7daee609c40dbd918d5bb98640c3850d3cb0c54eeabee10e4d4082c036f3
4
+ data.tar.gz: 3c2041cd23f31b624860a141858f0c54ac995cf49b277bd591acb94a37cb1456
5
5
  SHA512:
6
- metadata.gz: fa441f9ed2e140c89cad9bf3e0246b82489572e3f3843e053e681310dfb86a7b92648bec69f2578b3cc37b4c74fd258e9b6f97829e727d12f80a49284c2a8e92
7
- data.tar.gz: 20d753e90550f3d28ab66f82c756e8f22fca1056c4b7af0bdc2ecc5105e89c28b33392baf083648d6dbffdacd800bad0ef9c07f95112fa67713e3a16d61c74e8
6
+ metadata.gz: ae5aa592ffe9845c5438a80761a2ee8dccf358d4d32cfa6f84926038d64433c9e041911ba464c29811617b871a3c174baadb23d2dadb57bf76bcd9de44487ff9
7
+ data.tar.gz: c009caabf877b46d424e529bce376477835153338eef1b6bd2fd833fdbfb3b7cca157238823efd86aa44513c0ba5bd7e7fab5571a9799666ba9ab81624e4cb7f
data/.rubocop.yml CHANGED
@@ -73,6 +73,9 @@ Layout/EmptyLineBetweenDefs:
73
73
  Exclude:
74
74
  - "**/spec/**/*"
75
75
 
76
+ Style/Documentation:
77
+ Enabled: false
78
+
76
79
  Lint/AmbiguousBlockAssociation:
77
80
  Exclude:
78
81
  - "**/spec/**/*"
data/Rakefile CHANGED
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ GEM_NAME = 'k_builder'
4
+
3
5
  require 'bundler/gem_tasks'
4
6
  require 'rspec/core/rake_task'
7
+ require 'k_builder/version'
5
8
 
6
9
  RSpec::Core::RakeTask.new(:spec)
7
10
 
@@ -14,4 +17,15 @@ Rake::ExtensionTask.new('k_builder') do |ext|
14
17
  ext.lib_dir = 'lib/k_builder'
15
18
  end
16
19
 
20
+ desc 'Publish the gem to RubyGems.org'
21
+ task :publish do
22
+ system 'gem build'
23
+ system "gem push #{GEM_NAME}-#{KBuilder::VERSION}.gem"
24
+ end
25
+
26
+ desc 'Remove old *.gem files'
27
+ task :clean do
28
+ system 'rm *.gem'
29
+ end
30
+
17
31
  task default: %i[clobber compile spec]
data/STORIES.md CHANGED
@@ -10,14 +10,34 @@ As a Polyglot Developer, I want to be up and running in any development language
10
10
 
11
11
  As a Polyglot Developer, I want to be up and running in any development language with consistency, so I am productive and using best practices [EPIC]
12
12
 
13
- ### Tasks next on list
13
+ As a Developer, I need builders to be easier to use, so I am more efficient
14
14
 
15
- BaseBuilder
15
+ - Logging needs to be more informative
16
+ - Template errors need to log the template and the filename
17
+ - add_file with template_file: needs to support optional filename that is the same as the template_file (or use a token, eg. $TF_PATH$, $TF_NAME$, $TF_FILE$
18
+ - add_file, the files being generated are not being logged
16
19
 
17
20
  ## Stories and tasks
18
21
 
22
+ ### Stories - completed
23
+
24
+ As a Developer, I want have multiple template, so I can group my templates by area of specialty
25
+
26
+ - Refactor global and app templates to a layered folder array using (First In, Last Out) priority
27
+ - Support subfolders of any template folder (maybe)
28
+
29
+ As a Developer, I want have multiple output folders, so I can write to multiple locations
30
+
31
+ - Refactor output folder so that there are multiple named output folders, with :default working the same way as the existing system
32
+ - Support subfolders of any output folder
33
+ - Support output folder change of focus
34
+
19
35
  ### Tasks - completed
20
36
 
37
+ WatchBuilder - Build Watcher (as a builder) - [k_builder-watch](https://github.com/klueless-io/k_builder-watch)
38
+
39
+ Refactor BaseBuilder
40
+
21
41
  Setup RubyGems and RubyDoc
22
42
 
23
43
  - Build and deploy gem to [rubygems.org](https://rubygems.org/gems/k_builder)
data/USAGE.md CHANGED
@@ -55,26 +55,26 @@ TEXT
55
55
 
56
56
  builder = KBuilder::Builder.init
57
57
 
58
- builder
59
- .add_file('main.rb', template_file: 'class.rb', name: 'main')
60
- .add_file('person.rb',
61
- template_file: 'model.rb',
62
- name: 'person',
63
- fields: %i[first_name last_name])
64
- .add_file('address.rb',
65
- template_file: 'model.rb',
66
- name: 'address',
67
- fields: %i[street1 street2 post_code state])
68
- .add_file('configuration.log.txt',
69
- template: template,
70
- a: builder.template_folder,
71
- b: builder.global_template_folder,
72
- c: builder.target_folder)
73
- .add_file('css/index.css',
74
- template: '{{#each colors}} .{{.}} { color: {{.}} } {{/each}}',
75
- colors: ['red', 'blue', 'green'],
76
- pretty: true)
77
-
58
+ builder.add_file('main.rb', template_file: 'class.rb', name: 'main').add_file(
59
+ 'person.rb',
60
+ template_file: 'model.rb', name: 'person', fields: %i[first_name last_name]
61
+ ).add_file(
62
+ 'address.rb',
63
+ template_file: 'model.rb',
64
+ name: 'address',
65
+ fields: %i[street1 street2 post_code state]
66
+ ).add_file(
67
+ 'configuration.log.txt',
68
+ template: template,
69
+ a: builder.template_folder,
70
+ b: builder.global_template_folder,
71
+ c: builder.target_folder
72
+ ).add_file(
73
+ 'css/index.css',
74
+ template: '{{#each colors}} .{{.}} { color: {{.}} } {{/each}}',
75
+ colors: %w[red blue green],
76
+ pretty: true
77
+ )
78
78
  ```
79
79
 
80
80
  #### Folder Structure (after)
@@ -85,7 +85,7 @@ Folder structure after running the builder
85
85
 
86
86
  #### main.rb
87
87
 
88
- ![main.rb](usage/_out1.png)
88
+ ![](usage/_out1.png)
89
89
 
90
90
  #### person.rb
91
91
 
@@ -93,12 +93,12 @@ Folder structure after running the builder
93
93
 
94
94
  #### address.rb
95
95
 
96
- ![person.rb](usage/_out3.png)
96
+ ![](usage/_out3.png)
97
97
 
98
98
  #### configuration.log.txt
99
99
 
100
- ![configuration.log.txt](usage/_out4.png)
100
+ ![](usage/_out4.png)
101
101
 
102
102
  #### css/index.css
103
103
 
104
- ![css/index.css](usage/_out5.png)
104
+ ![](usage/_out5.png)
data/k_builder.gemspec CHANGED
@@ -39,6 +39,8 @@ Gem::Specification.new do |spec|
39
39
  # spec.extensions = ['ext/k_builder/extconf.rb']
40
40
 
41
41
  spec.add_dependency 'handlebars-helpers', '~> 0'
42
+ spec.add_dependency 'k_log', '~> 0'
43
+ spec.add_dependency 'k_util', '~> 0'
42
44
  # spec.add_dependency "anyway_config" , ">= 2.0.0"
43
45
  # spec.add_dependency "config" , ">= 3.0.0"
44
46
  end
data/lib/k_builder.rb CHANGED
@@ -3,9 +3,12 @@
3
3
  require 'k_builder/version'
4
4
  require 'k_builder/base_builder'
5
5
  require 'k_builder/base_configuration'
6
- require 'k_builder/builder'
7
6
  require 'k_builder/configuration'
8
- require 'k_builder/data_helper'
7
+ require 'k_builder/file_segments'
8
+ require 'k_builder/named_folders'
9
+ require 'k_builder/layered_folders'
10
+ require 'k_log'
11
+ require 'k_util'
9
12
 
10
13
  require 'handlebars/helpers/template'
11
14
 
@@ -13,5 +16,15 @@ module KBuilder
13
16
  # raise KBuilder::Error, 'Sample message'
14
17
  class Error < StandardError; end
15
18
 
16
- # Your code goes here...
19
+ # Need to move this into a KLog factory
20
+ def self.configure_logger
21
+ logger = Logger.new($stdout)
22
+ logger.level = Logger::DEBUG
23
+ logger.formatter = KLog::LogFormatter.new
24
+ KLog::LogUtil.new(logger)
25
+ end
17
26
  end
27
+
28
+ L = KBuilder.configure_logger
29
+
30
+ puts "KBuilder::Version: #{KBuilder::VERSION}" if ENV['KLUE_DEBUG']&.to_s&.downcase == 'true'
@@ -8,7 +8,11 @@ module KBuilder
8
8
  # Setter methods (are NOT fluent) can be created as needed
9
9
  # these methods would not be prefixed with the set_
10
10
  class BaseBuilder
11
- attr_reader :hash
11
+ attr_reader :configuration
12
+
13
+ attr_accessor :target_folders
14
+
15
+ attr_accessor :template_folders
12
16
 
13
17
  # Factory method that provides a builder for a specified structure
14
18
  # runs through a configuration block and then builds the final structure
@@ -20,112 +24,282 @@ module KBuilder
20
24
 
21
25
  # Create and initialize the builder.
22
26
  #
23
- # Initialization can be done via any of these three sequential steps.
24
- # - Configuration hash
25
- # - After new event
26
- # - Configuration block (lambda)
27
- #
28
27
  # @return [Builder] Returns the builder via fluent interface
29
28
  def self.init(configuration = nil)
30
29
  builder = new(configuration)
31
30
 
32
- builder.after_new
33
-
34
31
  yield(builder) if block_given?
35
32
 
36
33
  builder
37
34
  end
38
35
 
39
- # Use after_new to massage hash values that come in via
40
- # configuration into more complex values
41
- #
42
- # Abstract method
43
- def after_new; end
44
-
45
36
  # assigns a builder hash and defines builder methods
46
37
  def initialize(configuration = nil)
47
- @hash = {}
48
-
49
- unless configuration.nil?
50
- raise KBuilder::StandardError, 'Unknown configuration object' unless configuration.is_a?(Hash)
38
+ configuration = KBuilder.configuration if configuration.nil?
51
39
 
52
- hash.merge!(configuration)
53
- end
40
+ @configuration = configuration
54
41
 
55
- define_builder_setter_methods
56
- end
57
-
58
- # Return an array of symbols to represent the fluent
59
- # setter methods that you want on your builder.
60
- #
61
- # Abstract method
62
- def builder_setter_methods
63
- raise NotImplementedError
42
+ @target_folders = configuration.target_folders.clone
43
+ @template_folders = configuration.template_folders.clone
64
44
  end
65
45
 
66
46
  # @return [Hash/StrongType] Returns data object, can be a hash
67
47
  # or strong typed object that you
68
48
  # have wrapped around the hash
69
49
  def build
70
- hash
71
- end
72
-
73
- # TODO
74
- # Support Nesting
75
- # Support Generation fo the following
76
- # - fluent set_
77
- # - Support setter (non-fluent)
78
- # - Support getter (non-fluent)
79
-
80
- # # builds a nested structure by either builder block or hash
81
- # # @param data_structure [type=DataStructure]
82
- # # @param builder [type=Builder]
83
- # # @param attributes [type=Hash|DataStructure instance]
84
- # # @param &block
85
- # #
86
- # # @return [type=Hash]
87
- # def build_nested(data_structure, builder, attributes = {}, &block)
88
- # if block_given?
89
- # builder.build(&block).to_h
90
- # else
91
- # build_hash(data_structure, attributes)
92
- # end
93
- # end
94
-
95
- private
96
-
97
- # #
98
- # # @param data_structure [type=DataStructure]
99
- # # @param attributes [type=Hash, DataStructure]
100
- # #
101
- # # @return [type=Hash]
102
- # def build_hash(data_structure, attributes)
103
- # if attributes.is_a?(data_structure)
104
- # attributes.to_h
105
- # else
106
- # data_structure.new(attributes).to_h
107
- # end
108
- # end
109
-
110
- # Defines all of the necessary builder setter methods
50
+ raise NotImplementedError
51
+ end
52
+
53
+ def to_h
54
+ {
55
+ target_folders: target_folders.to_h,
56
+ template_folders: template_folders.to_h
57
+ }
58
+ end
59
+
60
+ # ----------------------------------------------------------------------
61
+ # Fluent interface
62
+ # ----------------------------------------------------------------------
63
+
64
+ # Internal Actions are considered helpers for the builder, they do
65
+ # something useful, but they do not tend to implement fluent interfaces.
111
66
  #
112
- # @return [Builder] Returns the builder via fluent interface
113
- def define_builder_setter_methods
114
- builder_setter_methods.each { |method| define_builder_method(method) }
67
+ # They some times do actions, they sometimes return information.
68
+ #
69
+ # NOTE: [SRP] - These methods should probably be converted into objects
70
+ # ----------------------------------------------------------------------
71
+
72
+ # Add a file to the target location
73
+ #
74
+ # @param [String] file The file name with or without relative path, eg. my_file.json or src/my_file.json
75
+ # @option opts [String] :content Supply the content that you want to write to the file
76
+ # @option opts [String] :template Supply the template that you want to write to the file, template will be processed ('nobody') From address
77
+ # @option opts [String] :content_file File with content, file location is based on where the program is running
78
+ # @option opts [String] :template_file File with handlebars templated content that will be transformed, file location is based on the configured template_path
79
+ #
80
+ # Extra options will be used as data for templates, e.g
81
+ # @option opts [String] :to Recipient email
82
+ # @option opts [String] :body The email's body
83
+ def add_file(file, **opts)
84
+ full_file = opts.key?(:folder_key) ? target_file(file, folder: opts[:folder_key]) : target_file(file)
85
+
86
+ # Need logging options that can log these internal details
87
+ FileUtils.mkdir_p(File.dirname(full_file))
88
+
89
+ content = process_any_content(**opts)
90
+
91
+ File.write(full_file, content)
92
+
93
+ # Prettier needs to work with the original file name
94
+ run_prettier file if opts.key?(:pretty)
95
+
115
96
  self
116
97
  end
98
+ alias touch add_file # it is expected that you would not supply any options, just a file name
117
99
 
118
- # Defines a method using the convention set_[method_name]
100
+ # Add content to the clipboard
119
101
  #
120
- # Convention: Setter methods (are Fluent) and use the prefix set_
121
- # Getter methods (are NOT fluent) and return the stored value
102
+ # @option opts [String] :content Supply the content that you want to write to the file
103
+ # @option opts [String] :template Supply the template that you want to write to the file, template will be processed ('nobody') From address
104
+ # @option opts [String] :content_file File with content, file location is based on where the program is running
105
+ # @option opts [String] :template_file File with handlebars templated content that will be transformed, file location is based on the configured template_path
122
106
  #
123
- # @return [Builder] Returns the builder via fluent interface
124
- def define_builder_method(method_name)
125
- self.class.send(:define_method, "set_#{method_name}") do |value|
126
- @hash[method_name.to_s] = value
127
- self
107
+ # Extra options will be used as data for templates, e.g
108
+ # @option opts [String] :to Recipient email
109
+ # @option opts [String] :body The email's body
110
+ def add_clipboard(**opts)
111
+ content = process_any_content(**opts)
112
+
113
+ begin
114
+ IO.popen('pbcopy', 'w') { |f| f << content }
115
+ rescue Errno::ENOENT => e
116
+ if e.message == 'No such file or directory - pbcopy'
117
+ # May want to use this GEM in the future
118
+ # https://github.com/janlelis/clipboard
119
+ puts 'Clipboard paste is currently only supported on MAC'
120
+ end
128
121
  end
122
+
123
+ self
124
+ end
125
+ alias clipboard_copy add_clipboard
126
+
127
+ def vscode(*file_parts, folder: current_folder_key)
128
+ file = target_file(*file_parts, folder: folder)
129
+
130
+ rc "code #{file}"
131
+
132
+ self
133
+ end
134
+
135
+ # ----------------------------------------------------------------------
136
+ # Attributes: Think getter/setter
137
+ #
138
+ # The following getter/setters can be referenced both inside and outside
139
+ # of the fluent builder fluent API. They do not implement the fluent
140
+ # interface unless prefixed by set_.
141
+ #
142
+ # set_: Only setters with the prefix _set are considered fluent.
143
+ # ----------------------------------------------------------------------
144
+
145
+ # Target folders and files
146
+ # ----------------------------------------------------------------------
147
+
148
+ def set_current_folder(folder_key)
149
+ target_folders.current = folder_key
150
+
151
+ self
152
+ end
153
+ alias cd set_current_folder
154
+
155
+ # Fluent adder for target folder (KBuilder::NamedFolders)
156
+ def add_target_folder(folder_key, value)
157
+ target_folders.add(folder_key, value)
158
+
159
+ self
160
+ end
161
+
162
+ def current_folder_key
163
+ target_folders.current
164
+ end
165
+
166
+ # Get target folder by folder_key
167
+ #
168
+ # If folder_key not supplied then get the current target folder
169
+ def target_folder(folder_key = current_folder_key)
170
+ target_folders.get(folder_key)
171
+ end
172
+
173
+ # Get target file
174
+ #
175
+ # If you provide a relative folder, then it will be relative to the :folder parameter
176
+ #
177
+ # If the :folder is not set, then it will be relative to the current folder
178
+ #
179
+ # @examples
180
+ # target_file('abc.txt')
181
+ # target_file('xyz/abc.txt')
182
+ # target_file('xyz', 'abc.txt')
183
+ #
184
+ # If you provide an absolute folder, then it will ignore the :folder parameter
185
+ #
186
+ # @examples
187
+ # target_file('/abc.txt')
188
+ # target_file('/xyz/abc.txt')
189
+ # target_file('/xyz', 'abc.txt')
190
+ def target_file(*file_parts, folder: current_folder_key)
191
+ # Absolute path
192
+ return File.join(*file_parts) if Pathname.new(file_parts.first).absolute?
193
+
194
+ # Relative to :folder
195
+ File.join(target_folder(folder), *file_parts)
196
+ end
197
+
198
+ # Template folder & Files
199
+ # ----------------------------------------------------------------------
200
+
201
+ # Fluent adder for template folder (KBuilder::LayeredFolders)
202
+ def add_template_folder(folder_key, value)
203
+ template_folders.add(folder_key, value)
204
+
205
+ self
206
+ end
207
+
208
+ # Get for template folder
209
+ def get_template_folder(folder_key)
210
+ template_folders.get(folder_key)
211
+ end
212
+
213
+ # Gets a template_file relative to the template folder, looks first in
214
+ # local template folder and if not found, looks in global template folder
215
+ def find_template_file(file_parts)
216
+ template_folders.find_file(file_parts)
217
+ end
218
+
219
+ # Building content from templates
220
+ # ----------------------------------------------------------------------
221
+
222
+ # Use content from a a selection of content sources
223
+ #
224
+ # @option opts [String] :content Just pass through the :content as is.
225
+ # @option opts [String] :content_file Read content from the :content_file
226
+ #
227
+ # Future options
228
+ # @option opts [String] :content_loren [TODO]Create Loren Ipsum text as a :content_loren count of words
229
+ # @option opts [String] :content_url Read content from the :content_url
230
+ #
231
+ # @return Returns some content
232
+ def use_content(**opts)
233
+ return opts[:content] unless opts[:content].nil?
234
+
235
+ return unless opts[:content_file]
236
+
237
+ cf = opts[:content_file]
238
+
239
+ return "Content not found: #{File.expand_path(cf)}" unless File.exist?(cf)
240
+
241
+ File.read(cf)
242
+ end
243
+
244
+ # Use template from a a selection of template sources
245
+ #
246
+ # @option opts [String] :template Just pass through the :template as is.
247
+ # @option opts [String] :template_file Read template from the :template_file
248
+ #
249
+ # @return Returns some template
250
+ def use_template(**opts)
251
+ return opts[:template] unless opts[:template].nil?
252
+
253
+ return unless opts[:template_file]
254
+
255
+ tf = find_template_file(opts[:template_file])
256
+
257
+ return "template not found: #{opts[:template_file]}" if tf.nil?
258
+
259
+ File.read(tf)
260
+ end
261
+
262
+ # Process content will take any one of the following
263
+ # - Raw content
264
+ # - File based content
265
+ # - Raw template (translated via handlebars)
266
+ # - File base template (translated via handlebars)
267
+ #
268
+ # Process any of the above inputs to create final content output
269
+ #
270
+ # @option opts [String] :content Supply the content that you want to write to the file
271
+ # @option opts [String] :template Supply the template that you want to write to the file, template will be transformed using handlebars
272
+ # @option opts [String] :content_file File with content, file location is based on where the program is running
273
+ # @option opts [String] :template_file File with handlebars templated content that will be transformed, file location is based on the configured template_path
274
+ def process_any_content(**opts)
275
+ raw_content = use_content(**opts)
276
+
277
+ return raw_content if raw_content
278
+
279
+ template_content = use_template(**opts)
280
+
281
+ Handlebars::Helpers::Template.render(template_content, opts) unless template_content.nil?
282
+ end
283
+
284
+ def run_prettier(file, log_level: :log)
285
+ # command = "prettier --check #{file} --write #{file}"
286
+ command = "npx prettier --loglevel #{log_level} --write #{file}"
287
+
288
+ run_command command
289
+ end
290
+
291
+ def run_command(command)
292
+ # Deep path create if needed
293
+ tf = target_folder
294
+
295
+ FileUtils.mkdir_p(tf)
296
+
297
+ build_command = "cd #{tf} && #{command}"
298
+
299
+ puts build_command
300
+
301
+ system(build_command)
129
302
  end
303
+ alias rc run_command
130
304
  end
131
305
  end
@@ -16,18 +16,38 @@ module KBuilder
16
16
  end
17
17
  end
18
18
 
19
- def to_hash
19
+ # move out into module
20
+ def to_h
20
21
  hash = {}
21
22
  instance_variables.each do |var|
22
23
  value = instance_variable_get(var)
23
24
 
24
- value = value.to_hash if value.is_a?(KBuilder::BaseConfiguration)
25
+ value = KUtil.data.to_hash(value) if complex_type?(value)
25
26
 
26
27
  hash[var.to_s.delete('@')] = value
27
28
  end
28
29
  hash
29
30
  end
30
31
 
32
+ # Any basic (aka primitive) type
33
+ def basic_type?(value)
34
+ value.is_a?(String) ||
35
+ value.is_a?(Symbol) ||
36
+ value.is_a?(FalseClass) ||
37
+ value.is_a?(TrueClass) ||
38
+ value.is_a?(Integer) ||
39
+ value.is_a?(Float)
40
+ end
41
+
42
+ # Anything container that is not a regular class
43
+ def complex_type?(value)
44
+ value.is_a?(Array) ||
45
+ value.is_a?(Hash) ||
46
+ value.is_a?(Struct) ||
47
+ value.is_a?(OpenStruct) ||
48
+ value.respond_to?(:to_h)
49
+ end
50
+
31
51
  def kv(name, value)
32
52
  puts "#{name.rjust(30)} : #{value}"
33
53
  end
@@ -21,23 +21,44 @@ module KBuilder
21
21
 
22
22
  # Configuration class
23
23
  class Configuration < BaseConfiguration
24
- attr_accessor :target_folder
25
- attr_accessor :template_folder
26
- attr_accessor :global_template_folder
24
+ attr_accessor :target_folders
25
+ attr_accessor :template_folders
27
26
 
28
27
  def initialize
29
28
  super
30
- @target_folder = Dir.getwd
31
- @template_folder = File.join(Dir.getwd, '.templates')
32
- @global_template_folder = nil
29
+ # @target_folder = Dir.getwd
30
+ # @template_folder = File.join(Dir.getwd, '.templates')
31
+ # @global_template_folder = nil
32
+ @target_folders = NamedFolders.new
33
+ @template_folders = LayeredFolders.new
33
34
  end
34
35
 
36
+ def initialize_copy(orig)
37
+ super(orig)
38
+
39
+ @target_folders = orig.target_folders.clone
40
+ @template_folders = orig.template_folders.clone
41
+ end
42
+
43
+ # rubocop:disable Metrics/AbcSize
35
44
  def debug
36
- puts '-' * 120
37
- puts 'kbuilder base configuration'
38
- kv 'target_folder' , target_folder
39
- kv 'template_folder' , template_folder
40
- kv 'global_template_folder', global_template_folder
45
+ L.subheading 'kbuilder base configuration'
46
+
47
+ L.section_heading 'target_folders'
48
+ target_folders.folders.each_key do |key|
49
+ folder = target_folders.folders[key]
50
+ L.kv key.to_s, folder
51
+ end
52
+ L.info ''
53
+
54
+ L.section_heading 'template folders (search order)'
55
+
56
+ template_folders.ordered_keys.each do |key|
57
+ folder = template_folders.folders[key]
58
+ L.kv key.to_s, folder
59
+ end
60
+ ''
41
61
  end
62
+ # rubocop:enable Metrics/AbcSize
42
63
  end
43
64
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KBuilder
4
+ # Splits a file into its base parts (file, path, file_name, extension and file_name_only)
5
+ #
6
+ # Provides the interpolate helper to rebuild a different filename using those segments
7
+ class FileSegments
8
+ attr_reader :file
9
+ attr_reader :path
10
+ attr_reader :file_name
11
+ attr_reader :ext
12
+ attr_reader :file_name_only
13
+
14
+ def initialize(file)
15
+ @file = file
16
+ @path = File.dirname(file)
17
+ @file_name = File.basename(file)
18
+ @ext = File.extname(file)
19
+ @file_name_only = File.basename(file, @ext)
20
+ end
21
+
22
+ def interpolate(target_file)
23
+ # p str.gsub( /#{var}/, 'foo' ) # => "a test foo"
24
+ target_file
25
+ .gsub(/\$T_FILE\$/i, file)
26
+ .gsub(/\$T_PATH\$/i, path)
27
+ .gsub(/\$T_FILE_NAME\$/i, file_name)
28
+ .gsub(/\$T_EXT\$/i, ext)
29
+ .gsub(/\$T_FILE_NAME_ONLY\$/i, file_name_only)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KBuilder
4
+ #
5
+ # Named folders makes sense for generated/output folders because you may want
6
+ # more than one type of location to generate output.
7
+ #
8
+ # Don't confuse multiple named output folders with sub-paths, when you want to
9
+ # build up a file name in a child folder, you can do that as part of building
10
+ # the filename.
11
+ #
12
+ # The idea behind named folders is for when you have two or more totally different
13
+ # outputs that (may live in the a similar location) or live in different locations.
14
+
15
+ # Layered folders allow files to be found in any of the searchable folders
16
+ #
17
+ # They derive from and thus work just like named folders in that they allow folders
18
+ # to be stored with easy to remember names/alias's.
19
+ #
20
+ # Where they differ is that they are retrieved in preferential search order that is
21
+ # by default (First In, Last Out) priority aka a Stack (Last In, First Out) or
22
+ # optionally over ridden via the search_order method
23
+ #
24
+ # Layered folders makes sense for use with template files and source data/model
25
+ # where you can have specific usage files available and if they are not found then
26
+ # you can use fall-back files in other folders.
27
+ #
28
+ # example:
29
+ # folders = LayeredFolders.new
30
+ # folders.add(:global , '~/global_templates')
31
+ # folders.add(:domain , '/my-project/domain_templates')
32
+ # folders.add(:app , '/my-project/my-app/.templates')
33
+ #
34
+ # # Find a file and folder will in folders in this order
35
+ # # app_templates, then domain_templates and then finally global templates
36
+ # # ['/my-project/my-app/.templates', '/my-project/domain_templates', '~/global_templates']
37
+ # #
38
+ # # Find a file called template1.txt and return its fully-qualified path
39
+ # folders.find_file('template1.txt')
40
+ #
41
+ # # As above, but returns the folder only, file name and sub-paths are ignored
42
+ # folders.find_file_folder('template1.txt')
43
+ # folders.find_file_folder('abc/xyz/deep-template.txt')
44
+ #
45
+ # # If an additional folder is added, say in child configuration that is designed
46
+ # # to override some of the global templates, then you can run a search_order
47
+ # # method to re-order the templates
48
+ #
49
+ # folders.add(:global_shim , '~/global_templates_shim')
50
+ # folders.search_order(:app, :domain, :global_shim, :global)
51
+ #
52
+ # class Builder < KBuilder::BaseBuilder
53
+ class LayeredFolders < KBuilder::NamedFolders
54
+ attr_reader :ordered_keys
55
+ attr_reader :ordered_folders
56
+
57
+ def initialize
58
+ super()
59
+
60
+ @ordered_keys = []
61
+ @ordered_folders = []
62
+ end
63
+
64
+ def initialize_copy(orig)
65
+ super(orig)
66
+
67
+ @ordered_keys = orig.ordered_keys.clone
68
+ @ordered_folders = orig.ordered_folders.clone
69
+ end
70
+
71
+ def add(folder_key, *folder_parts)
72
+ folder = super(folder_key, *folder_parts)
73
+
74
+ ordered_keys.prepend(folder_key)
75
+ ordered_folders.prepend(folder)
76
+
77
+ folder
78
+ end
79
+
80
+ # File name or array of sub-paths plus file
81
+ #
82
+ # Return the folder that a file is found in
83
+ def find_file(file_parts)
84
+ folder = find_file_folder(file_parts)
85
+ folder.nil? ? nil : File.join(folder, file_parts)
86
+ end
87
+
88
+ # File name or array of sub-paths plus file
89
+ #
90
+ # Return the folder that a file is found in
91
+ def find_file_folder(file_parts)
92
+ ordered_folders.find { |folder| File.exist?(File.join(folder, file_parts)) }
93
+ end
94
+
95
+ def to_h
96
+ {
97
+ ordered: {
98
+ keys: ordered_keys,
99
+ folders: ordered_folders
100
+ }
101
+ }.merge(@folders)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KBuilder
4
+ # Named folders allow folders to be stored with easy to remember names/alias's
5
+ # Secondarily, you can also build up file names based on these named folders.
6
+ #
7
+ # Named folders makes sense for generated/output folders because you may want
8
+ # more than one type of location to generate output.
9
+ #
10
+ # Don't confuse multiple named output folders with sub-paths, when you want to
11
+ # build up a file name in a child folder, you can do that as part of building
12
+ # the filename.
13
+ #
14
+ # The idea behind named folders is for when you have two or more totally different
15
+ # outputs that (may live in the a similar location) or live in different locations.
16
+ # Samples:
17
+ # name: :code - generating source code into a project
18
+ # name: :slide - generating slide deck into a documentation folder
19
+ # name: :webpack - folder where you might generate webpack files, e.g. webpack.config.*.json
20
+ #
21
+ # example:
22
+ # folders = NamedFolders.new
23
+ # folders.add(:csharp , '~/dev/csharp/cool-project')
24
+ # folders.add(:package_json , :csharp)
25
+ # folders.add(:webpack , folders.join(:csharp, 'config'))
26
+ # folders.add(:builder , folders.join(:csharp, 'builder'))
27
+ # folders.add(:slides , '~/doc/csharp/cool-project')
28
+ #
29
+ # puts folders.get(:builder)
30
+ #
31
+ # puts folders.get_filename(:csharp, 'Program.cs')
32
+ # puts folders.get_filename(:csharp, 'Models/Order.cs')
33
+ # puts folders.get_filename(:csharp, 'Models', 'Order.cs')
34
+ #
35
+ # Do I need to support :default?
36
+ class NamedFolders
37
+ attr_reader :folders
38
+
39
+ attr_reader :current
40
+
41
+ def initialize
42
+ @folders = {}
43
+ @current = nil
44
+ end
45
+
46
+ def initialize_copy(orig)
47
+ super(orig)
48
+
49
+ @folders = orig.folders.clone
50
+ end
51
+
52
+ def current=(folder_key)
53
+ guard_folder_key(folder_key)
54
+ @current = folder_key
55
+ end
56
+
57
+ # Add support for file_parts
58
+ def add(folder_key, *folder_parts)
59
+ # get a predefined folder by symbol
60
+ folder = join_folder_parts(folder_parts)
61
+ if folder.is_a?(Symbol)
62
+ folder = get(folder)
63
+ elsif folder.start_with?('~')
64
+ folder = File.expand_path(folder)
65
+ end
66
+
67
+ @current = folder_key if @current.nil?
68
+ folders[folder_key] = folder
69
+ end
70
+
71
+ # Get a folder
72
+ def get(folder_key)
73
+ guard_folder_key(folder_key)
74
+ folders[folder_key]
75
+ end
76
+
77
+ # Join the lookup folder key with the subpath folder parts (optionally + filename) and return the folder or filename
78
+ #
79
+ # Return fully qualified filename
80
+ def join(folder_key, *file_folder_parts)
81
+ folder = get(folder_key)
82
+
83
+ File.join(folder, file_folder_parts)
84
+ end
85
+ # Get a file name using the lookup folder key and the file name or array of sub-paths plus filename
86
+ alias get_filename join
87
+
88
+ def folder_keys
89
+ @folders.keys
90
+ end
91
+
92
+ def to_h
93
+ @folders
94
+ end
95
+
96
+ private
97
+
98
+ def join_folder_parts(folder_parts)
99
+ raise KBuilder::Error, 'No folder part provided' if folder_parts.nil? || folder_parts.length.zero?
100
+
101
+ # If only one part, and that can be a folder or :folder_key, then just return it
102
+ return folder_parts.first if folder_parts.length == 1
103
+
104
+ folder_parts = folder_parts.map.with_index do |folder_part, index|
105
+ folder_part = get(folder_part) if index.zero? && folder_part.is_a?(Symbol)
106
+ folder_part
107
+ end
108
+
109
+ File.join(folder_parts)
110
+ end
111
+
112
+ def guard_folder_key(folder_key)
113
+ raise KBuilder::Error, "Folder not found, this folder key not found: #{folder_key}" unless folders.key?(folder_key)
114
+ end
115
+ end
116
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KBuilder
4
- VERSION = '0.0.28'
4
+ VERSION = '0.0.53'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: k_builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.28
4
+ version: 0.0.53
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-15 00:00:00.000000000 Z
11
+ date: 2021-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: handlebars-helpers
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: k_log
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: k_util
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  description: " K Builder provides various fluent builders for initializing applications
28
56
  with different language requirements\n"
29
57
  email:
@@ -55,9 +83,10 @@ files:
55
83
  - lib/k_builder.rb
56
84
  - lib/k_builder/base_builder.rb
57
85
  - lib/k_builder/base_configuration.rb
58
- - lib/k_builder/builder.rb
59
86
  - lib/k_builder/configuration.rb
60
- - lib/k_builder/data_helper.rb
87
+ - lib/k_builder/file_segments.rb
88
+ - lib/k_builder/layered_folders.rb
89
+ - lib/k_builder/named_folders.rb
61
90
  - lib/k_builder/version.rb
62
91
  - usage/_out1.png
63
92
  - usage/_out2.png
@@ -1,258 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module KBuilder
4
- # Base builder defines builder methods, build method and configuration
5
- class Builder < KBuilder::BaseBuilder
6
- # builder_setter_methods = %w[].freeze
7
- # target_folder
8
- # template_folder
9
- # global_template_folder
10
-
11
- def initialize(configuration = nil)
12
- configuration = KBuilder.configuration.to_hash if configuration.nil?
13
-
14
- super(configuration)
15
- end
16
-
17
- # rubocop:disable Metrics/AbcSize
18
- def after_new
19
- # ensure that any configured folders are expanded
20
- self.target_folder = hash['target_folder'] unless hash['target_folder'].nil?
21
- self.template_folder = hash['template_folder'] unless hash['template_folder'].nil?
22
- self.global_template_folder = hash['global_template_folder'] unless hash['global_template_folder'].nil?
23
- end
24
- # rubocop:enable Metrics/AbcSize
25
-
26
- # Return an array of symbols to represent the fluent setter methods in this builder.
27
- def builder_setter_methods
28
- # Currently I have manually created my settings because I needed custom
29
- # logic in the settings to handle path expansion
30
- []
31
- end
32
-
33
- # def build
34
- # # SomeDryStruct.new(hash)
35
- # end
36
-
37
- # ----------------------------------------------------------------------
38
- # Fluent interface
39
- # ----------------------------------------------------------------------
40
-
41
- # Add a file to the target location
42
- #
43
- # @param [String] file The file name with or without relative path, eg. my_file.json or src/my_file.json
44
- # @option opts [String] :content Supply the content that you want to write to the file
45
- # @option opts [String] :template Supply the template that you want to write to the file, template will be processed ('nobody') From address
46
- # @option opts [String] :content_file File with content, file location is based on where the program is running
47
- # @option opts [String] :template_file File with handlebars templated content that will be transformed, file location is based on the configured template_path
48
- #
49
- # Extra options will be used as data for templates, e.g
50
- # @option opts [String] :to Recipient email
51
- # @option opts [String] :body The email's body
52
- def add_file(file, **opts)
53
- full_file = target_file(file)
54
-
55
- FileUtils.mkdir_p(File.dirname(full_file))
56
-
57
- content = process_any_content(**opts)
58
-
59
- File.write(full_file, content)
60
-
61
- # Prettier needs to work with the original file name
62
- run_prettier file if opts.key?(:pretty)
63
-
64
- self
65
- end
66
-
67
- # ----------------------------------------------------------------------
68
- # Attributes: Think getter/setter
69
- #
70
- # The following getter/setters can be referenced both inside and outside
71
- # of the fluent builder fluent API. They do not implement the fluent
72
- # interface unless prefixed by set_.
73
- #
74
- # set_: Only setters with the prefix _set are considered fluent.
75
- # ----------------------------------------------------------------------
76
-
77
- # Target folder
78
- # ----------------------------------------------------------------------
79
-
80
- # Fluent setter for target folder
81
- def set_target_folder(value)
82
- self.target_folder = value
83
-
84
- self
85
- end
86
-
87
- # Setter for target folder
88
- def target_folder=(value)
89
- hash['target_folder'] = File.expand_path(value)
90
- end
91
-
92
- # Getter for target folder
93
- def target_folder
94
- hash['target_folder']
95
- end
96
-
97
- # Template folder
98
- # ----------------------------------------------------------------------
99
-
100
- # Fluent setter for template folder
101
- def set_template_folder(value)
102
- self.template_folder = value
103
-
104
- self
105
- end
106
-
107
- # Setter for template folder
108
- # Refactor: Make Private
109
- def template_folder=(value)
110
- hash['template_folder'] = File.expand_path(value)
111
- end
112
-
113
- # Getter for template folder
114
- # Refactor: generate
115
- def template_folder
116
- hash['template_folder']
117
- end
118
-
119
- # Global Target folder
120
- # ----------------------------------------------------------------------
121
-
122
- # Fluent setter for global template folder
123
- def set_global_template_folder(value)
124
- self.global_template_folder = value
125
-
126
- self
127
- end
128
-
129
- # Setter for global template folder
130
- # Refactor: Make Private
131
- def global_template_folder=(value)
132
- hash['global_template_folder'] = File.expand_path(value)
133
- end
134
-
135
- # Setter for global template folder
136
- # Refactor: generate
137
- def global_template_folder
138
- hash['global_template_folder']
139
- end
140
-
141
- # Internal Actions are considered helpers for the builder, they do
142
- # something useful, but they do not tend to implement fluent interfaces.
143
- #
144
- # They some times do actions, they sometimes return information.
145
- #
146
- # NOTE: [SRP] - These methods should probably be converted into objects
147
- # ----------------------------------------------------------------------
148
-
149
- # Gets a target_file relative to target folder
150
- def target_file(file)
151
- File.join(target_folder, file)
152
- end
153
-
154
- # Gets a template_file relative to the template folder
155
- def template_file(file)
156
- File.join(template_folder, file)
157
- end
158
-
159
- # Gets a global_template_file relative to the global template folder
160
- def global_template_file(file)
161
- File.join(global_template_folder, file)
162
- end
163
-
164
- # Gets a template_file relative to the template folder, looks first in
165
- # local template folder and if not found, looks in global template folder
166
- def find_template_file(file)
167
- full_file = template_file(file)
168
- return full_file if File.exist?(full_file)
169
-
170
- full_file = global_template_file(file)
171
- return full_file if File.exist?(full_file)
172
-
173
- # File not found
174
- nil
175
- end
176
-
177
- # Use content from a a selection of content sources
178
- #
179
- # @option opts [String] :content Just pass through the :content as is.
180
- # @option opts [String] :content_file Read content from the :content_file
181
- #
182
- # Future options
183
- # @option opts [String] :content_loren [TODO]Create Loren Ipsum text as a :content_loren count of words
184
- # @option opts [String] :content_url Read content from the :content_url
185
- #
186
- # @return Returns some content
187
- def use_content(**opts)
188
- return opts[:content] unless opts[:content].nil?
189
-
190
- return unless opts[:content_file]
191
-
192
- cf = opts[:content_file]
193
-
194
- return "Content not found: #{File.expand_path(cf)}" unless File.exist?(cf)
195
-
196
- File.read(cf)
197
- end
198
-
199
- # Use template from a a selection of template sources
200
- #
201
- # @option opts [String] :template Just pass through the :template as is.
202
- # @option opts [String] :template_file Read template from the :template_file
203
- #
204
- # @return Returns some template
205
- def use_template(**opts)
206
- return opts[:template] unless opts[:template].nil?
207
-
208
- return unless opts[:template_file]
209
-
210
- tf = find_template_file(opts[:template_file])
211
-
212
- return "template not found: #{opts[:template_file]}" if tf.nil?
213
-
214
- File.read(tf)
215
- end
216
-
217
- # Process content will take any one of the following
218
- # - Raw content
219
- # - File based content
220
- # - Raw template (translated via handlebars)
221
- # - File base template (translated via handlebars)
222
- #
223
- # Process any of the above inputs to create final content output
224
- #
225
- # @option opts [String] :content Supply the content that you want to write to the file
226
- # @option opts [String] :template Supply the template that you want to write to the file, template will be transformed using handlebars
227
- # @option opts [String] :content_file File with content, file location is based on where the program is running
228
- # @option opts [String] :template_file File with handlebars templated content that will be transformed, file location is based on the configured template_path
229
- def process_any_content(**opts)
230
- raw_content = use_content(**opts)
231
-
232
- return raw_content if raw_content
233
-
234
- template_content = use_template(**opts)
235
-
236
- Handlebars::Helpers::Template.render(template_content, opts) unless template_content.nil?
237
- end
238
-
239
- def run_prettier(file, log_level: :log)
240
- # command = "prettier --check #{file} --write #{file}"
241
- command = "npx prettier --loglevel #{log_level} --write #{file}"
242
-
243
- run_command command
244
- end
245
-
246
- def run_command(command)
247
- # Deep path create if needed
248
- FileUtils.mkdir_p(target_folder)
249
-
250
- build_command = "cd #{target_folder} && #{command}"
251
-
252
- puts build_command
253
-
254
- system(build_command)
255
- end
256
- alias rc run_command
257
- end
258
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Attach data helper to the KBuilder module
4
- module KBuilder
5
- # Data helpers/utils for Kbuilder
6
- class << self
7
- attr_writer :data
8
- end
9
-
10
- def self.data
11
- @data ||= DataHelper.new
12
- end
13
-
14
- # Helper methods attached to the namespace for working with Data
15
- #
16
- # Usage: KBuilder.data.to_struct(data)
17
- class DataHelper
18
- # Convert a hash into a deep OpenStruct or array an array
19
- # of objects into an array of OpenStruct
20
- def to_struct(data)
21
- case data
22
- when Hash
23
- OpenStruct.new(data.transform_values { |v| to_struct(v) })
24
-
25
- when Array
26
- data.map { |o| to_struct(o) }
27
-
28
- else
29
- # Some primitave type: String, True/False, Symbol or an ObjectStruct
30
- data
31
- end
32
- end
33
-
34
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
35
- def struct_to_hash(data)
36
- # No test yet
37
- if data.is_a?(Array)
38
- return data.map { |v| v.is_a?(OpenStruct) ? struct_to_hash(v) : v }
39
- end
40
-
41
- data.each_pair.with_object({}) do |(key, value), hash|
42
- case value
43
- when OpenStruct, Struct, Hash
44
- hash[key] = struct_to_hash(value)
45
- when Array
46
- # No test yet
47
- values = value.map do |v|
48
- v.is_a?(OpenStruct) || v.is_a?(Struct) || v.is_a?(Hash) ? struct_to_hash(v) : v
49
- end
50
- hash[key] = values
51
- else
52
- hash[key] = value
53
- end
54
- end
55
- end
56
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
57
-
58
- def clean_symbol(value)
59
- return value if value.nil?
60
-
61
- value.is_a?(Symbol) ? value.to_s : value
62
- end
63
- end
64
- end