k_builder 0.0.27 → 0.0.51

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad123a63da5538fbf3c598f39f6af371760f01dcd90320f7d4c43cfa081c2a04
4
- data.tar.gz: b851793f9e4716209fc5a49183d4bb49257e71648515a0dfb4c185581adbd604
3
+ metadata.gz: 528f2587aacba6aa1d13d32737aa081b3ebb5364d3bc7e4628a2935a3746ff15
4
+ data.tar.gz: bb780d8e64e7c814e0b51c8561537a2cbdc31a1ab81f5ee4c78e8d3bb3fa1e89
5
5
  SHA512:
6
- metadata.gz: 1f528ed1fecbc4776e7a5315d844207bb819e9cda4a4b43ff508ccbaf56e4b1e844f53824f50857a57051b8d5b5c8f7b3aeea0528767cb2dd809e1b55c2077cf
7
- data.tar.gz: 38f600a8f7016f6f8aed25b5745122efd8b1e7e3f10a77d47a56cee3b54b2885ac7f563f8e1bba1d3ee17ef00fda870b98764ff858e256b47835dfc55e034dbb
6
+ metadata.gz: 2d1dfa3e5875b691576390c970d8005036244566277fee71bbaebe5fc8e63e43588b87780c7616d8e5e07df838dcc3f2ccbf2d4edad8689d2308b8ce41e780e0
7
+ data.tar.gz: 2eda2bb2b6caa396b0a7ffa0052d22e1bd98fc2931fbb3eb1352ff0b2e0c4fe86880c70e947f456d862d7279bf862f948ffb1c8eea9f9581b29257eb75ded468
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/lib/k_builder.rb CHANGED
@@ -3,9 +3,11 @@
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
7
  require 'k_builder/data_helper'
8
+ require 'k_builder/file_segments'
9
+ require 'k_builder/named_folders'
10
+ require 'k_builder/layered_folders'
9
11
 
10
12
  require 'handlebars/helpers/template'
11
13
 
@@ -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 = KBuilder.data.struct_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,41 @@ 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
45
  puts '-' * 120
37
46
  puts 'kbuilder base configuration'
38
- kv 'target_folder' , target_folder
39
- kv 'template_folder' , template_folder
40
- kv 'global_template_folder', global_template_folder
47
+
48
+ puts 'target_folders'
49
+ target_folders.folders.each_key do |key|
50
+ folder = target_folders.folders[key]
51
+ kv key.to_s, folder
52
+ end
53
+
54
+ puts 'template folders (search order)'
55
+ template_folders.ordered_folders.each do |folder|
56
+ puts folder.to_s
57
+ end
41
58
  end
59
+ # rubocop:enable Metrics/AbcSize
42
60
  end
43
61
  end
@@ -31,13 +31,15 @@ module KBuilder
31
31
  end
32
32
  end
33
33
 
34
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
34
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
35
35
  def struct_to_hash(data)
36
36
  # No test yet
37
37
  if data.is_a?(Array)
38
38
  return data.map { |v| v.is_a?(OpenStruct) ? struct_to_hash(v) : v }
39
39
  end
40
40
 
41
+ return struct_to_hash(data.to_h) if !data.is_a?(Hash) && data.respond_to?(:to_h)
42
+
41
43
  data.each_pair.with_object({}) do |(key, value), hash|
42
44
  case value
43
45
  when OpenStruct, Struct, Hash
@@ -53,7 +55,7 @@ module KBuilder
53
55
  end
54
56
  end
55
57
  end
56
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
58
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
57
59
 
58
60
  def clean_symbol(value)
59
61
  return value if value.nil?
@@ -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.27'
4
+ VERSION = '0.0.51'
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.27
4
+ version: 0.0.51
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-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: handlebars-helpers
@@ -55,9 +55,11 @@ files:
55
55
  - lib/k_builder.rb
56
56
  - lib/k_builder/base_builder.rb
57
57
  - lib/k_builder/base_configuration.rb
58
- - lib/k_builder/builder.rb
59
58
  - lib/k_builder/configuration.rb
60
59
  - lib/k_builder/data_helper.rb
60
+ - lib/k_builder/file_segments.rb
61
+ - lib/k_builder/layered_folders.rb
62
+ - lib/k_builder/named_folders.rb
61
63
  - lib/k_builder/version.rb
62
64
  - usage/_out1.png
63
65
  - 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