k_builder 0.0.27 → 0.0.51

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: 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