k_builder 0.0.60 → 0.0.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ /Users/davidcruwys/dev/slides-addons/highlight_css
@@ -0,0 +1,7 @@
1
+ <html>
2
+ <head><title>404 Not Found</title></head>
3
+ <body bgcolor="white">
4
+ <center><h1>404 Not Found</h1></center>
5
+ <hr><center>nginx</center>
6
+ </body>
7
+ </html>
@@ -1,21 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KBuilder
4
+ # TODO: Why is this called BaseBuilder, why not just builder
5
+ # TODO: Is this really the builder pattern, it could be the class used by a director
6
+ # but it is not really storing information for builder purposes.
7
+ #
4
8
  # Base builder defines builder methods, build method and configuration
5
9
  #
6
10
  # Convention: Setter methods (are Fluent) and use the prefix set_
7
11
  # Getter methods (are NOT fluent) and return the stored value
8
12
  # Setter methods (are NOT fluent) can be created as needed
9
13
  # these methods would not be prefixed with the set_
14
+
15
+ # process_any_content(content: 'abc')
16
+ # process_any_content(content_file: 'abc.txt')
17
+ # process_any_content(template: 'abc {{name}}', name: 'sean')
18
+ # process_any_content(template_file: 'abc.txt', name: 'sean')
19
+
20
+ # process_any_content(content_gist: 'https://gist.github.com/klueless-io/8d4b6d199dbe4a5d40807a47fff8ed1c')
21
+ # process_any_content(template_gist: 'https://gist.github.com/klueless-io/8d4b6d199dbe4a5d40807a47fff8ed1c', name: 'sean')
22
+
10
23
  class BaseBuilder
11
24
  include KLog::Logging
12
25
 
13
26
  attr_reader :configuration
14
27
 
15
28
  attr_accessor :target_folders
16
-
17
29
  attr_accessor :template_folders
18
30
 
31
+ attr_accessor :last_output_file
32
+ attr_accessor :last_output_folder
33
+ # attr_accessor :last_template
34
+ attr_accessor :last_template_file
35
+
19
36
  # Factory method that provides a builder for a specified structure
20
37
  # runs through a configuration block and then builds the final structure
21
38
  #
@@ -59,6 +76,7 @@ module KBuilder
59
76
  }
60
77
  end
61
78
 
79
+ # rubocop:disable Metrics/AbcSize
62
80
  def debug
63
81
  log.subheading 'kbuilder'
64
82
 
@@ -69,8 +87,16 @@ module KBuilder
69
87
  log.info ''
70
88
 
71
89
  template_folders.debug(title: 'template folders (search order)')
90
+
91
+ log.info ''
92
+ log.kv 'last output file' , last_output_file
93
+ log.kv 'last output folder' , last_output_folder
94
+ # log.kv 'last template' , last_template
95
+ log.kv 'last template file' , last_template_file
96
+
72
97
  ''
73
98
  end
99
+ # rubocop:enable Metrics/AbcSize
74
100
 
75
101
  # ----------------------------------------------------------------------
76
102
  # Fluent interface
@@ -95,31 +121,92 @@ module KBuilder
95
121
  # Extra options will be used as data for templates, e.g
96
122
  # @option opts [String] :to Recipient email
97
123
  # @option opts [String] :body The email's body
124
+ def add_file_command(file, **opts)
125
+ {
126
+ action: :add_file,
127
+ file: file,
128
+ opts: opts
129
+ }
130
+ end
131
+
132
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
98
133
  def add_file(file, **opts)
99
134
  # move to command
100
- full_file = opts.key?(:folder_key) ? target_file(file, folder: opts[:folder_key]) : target_file(file)
135
+ full_file = target_file(file, **opts) # opts.key?(:folder_key) || opts.key?(:folder) ? target_file(file, folder: opts[:folder], folder_key: opts[:folder_key]) : target_file(file)
101
136
 
102
137
  # Need logging options that can log these internal details
103
- FileUtils.mkdir_p(File.dirname(full_file))
138
+ mkdir_p(File.dirname(full_file))
104
139
 
105
140
  content = process_any_content(**opts)
106
141
 
107
- File.write(full_file, content)
142
+ file_write(full_file, content, on_exist: opts[:on_exist])
108
143
 
109
144
  # Prettier needs to work with the original file name
110
- run_prettier file if opts.key?(:pretty)
145
+ run_prettier file if opts.key?(:pretty)
146
+ # TODO: Add test
147
+ run_cop(full_file, fix_safe: true) if opts.key?(:cop) || opts.key?(:ruby_cop)
148
+
111
149
  # Need support for rubocop -a
150
+ open_file(last_output_file) if opts.key?(:open)
151
+ open_file(last_template_file) if opts.key?(:open_template)
152
+ browse_file(last_output_file) if opts.key?(:browse)
153
+ pause(opts[:pause]) if opts[:pause]
112
154
 
113
155
  self
114
156
  end
157
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
158
+
159
+ def replay_commands(commands)
160
+ commands.each do |command|
161
+ case command[:action]
162
+ when :add_file
163
+ add_file(command[:file], command[:opts])
164
+ when :delete_file
165
+ delete_file(command[:file], command[:opts])
166
+ else
167
+ log.error "Unknown command: #{command[:action]}"
168
+ end
169
+ end
170
+ end
171
+
115
172
  alias touch add_file # it is expected that you would not supply any options, just a file name
116
173
 
174
+ def delete_file_command(file, **opts)
175
+ {
176
+ action: :delete_file,
177
+ file: file,
178
+ opts: opts
179
+ }
180
+ end
181
+
182
+ def delete_file(file, **opts)
183
+ full_file = target_file(file, **opts) # = opts.key?(:folder_key) ? target_file(file, folder: opts[:folder_key]) : target_file(file)
184
+
185
+ File.delete(full_file) if File.exist?(full_file)
186
+
187
+ self
188
+ end
189
+
190
+ def file_exist?(file, **opts)
191
+ # full_file = opts.key?(:folder_key) ? target_file(file, folder_key: opts[:folder_key]) : target_file(file)
192
+ full_file = target_file(file, **opts)
193
+
194
+ File.exist?(full_file)
195
+ end
196
+
197
+ # ToDo
198
+ # def delete_folder(file)
199
+ # FileUtils.remove_dir(path_to_directory) if File.directory?(path_to_directory)
200
+
201
+ # self
202
+ # end
203
+
117
204
  def make_folder(folder_key = nil, sub_path: nil)
118
205
  folder_key = current_folder_key if folder_key.nil?
119
206
  folder = target_folder(folder_key)
120
207
  folder = File.join(folder, sub_path) unless sub_path.nil?
121
208
 
122
- FileUtils.mkdir_p(folder)
209
+ mkdir_p(folder)
123
210
 
124
211
  self
125
212
  end
@@ -140,6 +227,8 @@ module KBuilder
140
227
 
141
228
  begin
142
229
  IO.popen('pbcopy', 'w') { |f| f << content }
230
+
231
+ open_file(last_template_file) if opts.key?(:open_template)
143
232
  rescue Errno::ENOENT => e
144
233
  if e.message == 'No such file or directory - pbcopy'
145
234
  # May want to use this GEM in the future
@@ -152,15 +241,82 @@ module KBuilder
152
241
  end
153
242
  alias clipboard_copy add_clipboard
154
243
 
155
- def vscode(*file_parts, folder: current_folder_key)
244
+ def vscode_command(*file_parts, folder_key: current_folder_key, file: nil)
245
+ {
246
+ action: :vscode,
247
+ file_parts: file_parts,
248
+ opts: { folder_key: folder_key, file: file }
249
+ }
250
+ end
251
+
252
+ def vscode(*file_parts, folder_key: current_folder_key, file: nil)
156
253
  # move to command
157
- file = target_file(*file_parts, folder: folder)
254
+ file = target_file(*file_parts, folder_key: folder_key) if file.nil?
158
255
 
159
256
  rc "code #{file}"
160
257
 
161
258
  self
162
259
  end
163
260
 
261
+ def browse_command(*file_parts, folder_key: current_folder_key, file: nil)
262
+ {
263
+ action: :browse,
264
+ file_parts: file_parts,
265
+ opts: { folder_key: folder_key, file: file }
266
+ }
267
+ end
268
+
269
+ def browse(*file_parts, folder_key: current_folder_key, file: nil)
270
+ # move to command
271
+ file = target_file(*file_parts, folder_key: folder_key) if file.nil?
272
+
273
+ rc "open -a \"Google Chrome\" #{file}"
274
+
275
+ self
276
+ end
277
+
278
+ def open
279
+ open_file(last_output_file)
280
+
281
+ self
282
+ end
283
+ alias o open
284
+
285
+ def open_template
286
+ open_file(last_template_file)
287
+
288
+ self
289
+ end
290
+ alias ot open_template
291
+
292
+ def open_file(file)
293
+ if file.nil?
294
+ log.warn('open_file will not open when file is nil')
295
+ return self
296
+ end
297
+
298
+ vscode(file: file)
299
+
300
+ self
301
+ end
302
+
303
+ def browse_file(file)
304
+ if file.nil?
305
+ log.warn('browse_file will not browse when file is nil')
306
+ return self
307
+ end
308
+
309
+ browse(file: file)
310
+
311
+ self
312
+ end
313
+
314
+ def pause(seconds = 1)
315
+ sleep(seconds)
316
+
317
+ self
318
+ end
319
+
164
320
  # ----------------------------------------------------------------------
165
321
  # Attributes: Think getter/setter
166
322
  #
@@ -216,12 +372,18 @@ module KBuilder
216
372
  # target_file('/abc.txt')
217
373
  # target_file('/xyz/abc.txt')
218
374
  # target_file('/xyz', 'abc.txt')
219
- def target_file(*file_parts, folder: current_folder_key)
375
+ def target_file(*file_parts, folder_key: current_folder_key, folder: nil, **)
376
+ # TODO: Mismatch (sometimes called folder, sometimes called folder_key:)
377
+ if folder
378
+ log.error("Change folder: to folder_key: for #{folder} - #{file_parts}")
379
+ return
380
+ end
381
+
220
382
  # Absolute path
221
383
  return File.join(*file_parts) if Pathname.new(file_parts.first).absolute?
222
384
 
223
- # Relative to :folder
224
- File.join(target_folder(folder), *file_parts)
385
+ # Relative to :folder_key
386
+ File.join(target_folder(folder_key), *file_parts)
225
387
  end
226
388
 
227
389
  # Template folder & Files
@@ -242,7 +404,8 @@ module KBuilder
242
404
  # Gets a template_file relative to the template folder, looks first in
243
405
  # local template folder and if not found, looks in global template folder
244
406
  def find_template_file(file_parts)
245
- template_folders.find_file(file_parts)
407
+ self.last_template_file = template_folders.find_file(file_parts)
408
+ last_template_file
246
409
  end
247
410
 
248
411
  # Building content from templates
@@ -268,15 +431,6 @@ module KBuilder
268
431
 
269
432
  return "content not found: #{opts[:content_file]}" if cf.nil?
270
433
 
271
- # cf = opts[:content_file]
272
-
273
- # unless File.exist?(cf)
274
- # cf_from_template_folders = find_template_file(cf)
275
- # return "Content not found: #{File.expand_path(cf)}" unless File.exist?(cf_from_template_folders)
276
-
277
- # cf = cf_from_template_folders
278
- # end
279
-
280
434
  File.read(cf)
281
435
  end
282
436
 
@@ -320,6 +474,15 @@ module KBuilder
320
474
  Handlebars::Helpers::Template.render(template_content, opts) unless template_content.nil?
321
475
  end
322
476
 
477
+ def run_cop(file, **opts)
478
+ command = Commands::RuboCopCommand.new(file, builder: self, **opts)
479
+ command.execute
480
+
481
+ self
482
+ end
483
+
484
+ # Need to handle absolute files, see
485
+ # /Users/davidcruwys/dev/printspeak/reference_application/printspeak-domain/.builders/presentation/presentation_builder/commands/copy_ruby_resource_command.rb
323
486
  def run_prettier(file, log_level: :log)
324
487
  # command = "prettier --check #{file} --write #{file}"
325
488
  command = "npx prettier --loglevel #{log_level} --write #{file}"
@@ -331,7 +494,7 @@ module KBuilder
331
494
  # Deep path create if needed
332
495
  tf = target_folder
333
496
 
334
- FileUtils.mkdir_p(tf)
497
+ mkdir_p(tf)
335
498
 
336
499
  build_command = "cd #{tf} && #{command}"
337
500
 
@@ -342,5 +505,46 @@ module KBuilder
342
505
  system(build_command)
343
506
  end
344
507
  alias rc run_command
508
+
509
+ def file_write(file, content, on_exist: :skip)
510
+ self.last_output_file = file # if file not found, we still want to record this as the last_output_file
511
+
512
+ not_found = !File.exist?(file)
513
+
514
+ if not_found
515
+ File.write(file, content)
516
+ return
517
+ end
518
+
519
+ return if %i[skip ignore].include?(on_exist)
520
+
521
+ if %i[overwrite write].include?(on_exist)
522
+ File.write(file, content)
523
+ return
524
+ end
525
+
526
+ return unless on_exist == :compare
527
+
528
+ vscompare(file, content)
529
+ end
530
+
531
+ def vscompare(file, content)
532
+ # need to use some sort of caching folder for this
533
+ ext = File.extname(file)
534
+ fn = File.basename(file, ext)
535
+ temp_file = Tempfile.new([fn, ext])
536
+
537
+ temp_file.write(content)
538
+ temp_file.close
539
+
540
+ return if File.read(file) == content
541
+
542
+ system("code -d #{file} #{temp_file.path}")
543
+ sleep 2
544
+ end
545
+
546
+ def mkdir_p(folder)
547
+ @last_output_folder = FileUtils.mkdir_p(folder).first
548
+ end
345
549
  end
346
550
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KBuilder
4
+ module Commands
5
+ # Base command for single responsibility actions that can be fired
6
+ # from methods in the builder.
7
+ #
8
+ # Uses the command pattern
9
+ class BaseCommand
10
+ include KLog::Logging
11
+
12
+ attr_accessor :builder
13
+ attr_accessor :valid
14
+
15
+ def initialize(**opts)
16
+ @builder = opts[:builder]
17
+ @valid = true
18
+ end
19
+
20
+ def guard(message)
21
+ # THIS SHOULD ONLY LOG IF DEBUGGING IS TURNED ON
22
+ log.error(message)
23
+ @valid = false
24
+ end
25
+
26
+ def valid?
27
+ @valid
28
+ end
29
+
30
+ def debug(title: nil)
31
+ log.section_heading(title) if title
32
+ debug_values if respond_to?(:debug_values)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KBuilder
4
+ module Commands
5
+ # Run CodeSyntaxHighlighter against source code and produce a styled HTML representation
6
+ #
7
+ # Alternatives to Highlighter-js could be carbon-now and ray.so
8
+ class CodeSyntaxHighlighterCommand < BaseCommand
9
+ attr_reader :source_code
10
+ attr_reader :formatted_code
11
+
12
+ def initialize(source_code, **opts)
13
+ super(**opts)
14
+
15
+ self.source_code = source_code
16
+ end
17
+
18
+ def execute
19
+ return unless valid?
20
+
21
+ run
22
+ end
23
+
24
+ private
25
+
26
+ def source_code=(value)
27
+ @source_code = value
28
+
29
+ guard('Source code is required for formatting') if value.nil? || value.empty?
30
+ end
31
+
32
+ def run
33
+ # @formatted_code = ExecJS.eval("'red yellow blue'.split(' ')")
34
+
35
+ # # highlight_source = 'lib/k_builder/assets/a.js'
36
+ # highlight_source = 'lib/k_builder/assets/highlight.min.js'
37
+
38
+ # log.error ExecJS.runtime.name
39
+
40
+ # a = File.read(highlight_source)
41
+ # # context = ExecJS.compile(a)
42
+ # context = ExecJS.compile(highlight_source)
43
+ # context.call("html = hljs.highlightAuto('<h1>Hello World!</h1>').value")
44
+
45
+ # get_js_asset('highlight')
46
+ # get_js_asset('ruby')
47
+ end
48
+
49
+ # https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/highlight.min.js
50
+ # https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/languages/ruby.min.js
51
+
52
+ def get_js_asset(name)
53
+ url = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/#{name}.min.js"
54
+ target_folder = 'lib/k_builder/assets'
55
+ file = "#{name}.min.js"
56
+
57
+ get_asset(url, target_folder, file)
58
+ end
59
+
60
+ def get_asset(url, target_folder, file)
61
+ local_asset_file = File.join(target_folder, file)
62
+
63
+ return if File.exist?(local_asset_file)
64
+
65
+ content = Net::HTTP.get(URI.parse(url))
66
+
67
+ File.write(local_asset_file, content)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KBuilder
4
+ module Commands
5
+ # Run RuboCop against a file
6
+ class RuboCopCommand < BaseCommand
7
+ attr_reader :file_pattern
8
+ attr_reader :fix_safe
9
+ attr_reader :fix_unsafe
10
+ attr_reader :rubo_config_file
11
+ attr_reader :show_console
12
+
13
+ attr_reader :cop_options
14
+ attr_reader :cop_opt_values
15
+
16
+ # Initialize RuboCop command
17
+ #
18
+ # @param [String] file_pattern File name or file pattern
19
+ # @param [Hash] **opts The options
20
+ # @option opts [Boolean] :fix_safe RuboCop -a option will fix simple and safe issues
21
+ # @option opts [Boolean] :fix_unsafe RuboCop -A option will fix simple but potentially unsafe issues
22
+ # @option opts [Boolean] :show_console This will show in console, or if false set --out ~/last_cop.txt so that console is redirected to file
23
+ # @option opts [String] :rubo_config_file YAML file with RuboCop configuration options
24
+ #
25
+ # @example Cop for single file with auto fix turned on for simple issues
26
+ #
27
+ # RubCopCommand.new('abc.rb', fix_safe: true)
28
+ #
29
+ # @example Cop for all spec files to auto simple and unsafe issues
30
+ #
31
+ # RubCopCommand.new('spec/**/*.rb', fix_unsafe: true)
32
+ def initialize(file_pattern, **opts)
33
+ super(**opts)
34
+
35
+ @valid = true
36
+
37
+ self.file_pattern = file_pattern
38
+
39
+ self.fix_safe = opts[:fix_safe]
40
+ self.fix_unsafe = opts[:fix_unsafe]
41
+ self.show_console = opts[:show_console]
42
+ self.rubo_config_file = opts[:rubo_config_file]
43
+ end
44
+
45
+ def execute
46
+ return unless valid?
47
+
48
+ cop_run
49
+ end
50
+
51
+ def cli_options
52
+ cli_options = []
53
+ # quite is the same as simple, except you will see nothing if no offenses
54
+ cli_options << '--format' << 'quiet' # 'simple'
55
+ cli_options << '-a' if fix_safe
56
+ cli_options << '-A' if fix_unsafe
57
+ cli_options << '--config' << rubo_config_file if rubo_config_file
58
+ cli_options << '--out' << File.expand_path('~/last_cop.txt') unless show_console
59
+ cli_options << file_pattern
60
+ cli_options
61
+ end
62
+
63
+ def debug_values
64
+ log.kv 'rubocop target', file_pattern
65
+ log.kv '-a', 'automatic fix for safe issues' if fix_safe
66
+ log.kv '-A', 'automatic fix for potentially unsafe issues' if fix_unsafe
67
+ log.kv '-config', rubo_config_file if rubo_config_file
68
+ end
69
+
70
+ private
71
+
72
+ def file_pattern=(value)
73
+ @file_pattern = value
74
+
75
+ if value.nil? || value.empty?
76
+ guard 'file_pattern is required'
77
+ elsif Pathname.glob(value).length.zero?
78
+ guard 'file_pattern does not reference an existing file'
79
+ end
80
+ end
81
+
82
+ def fix_safe=(value)
83
+ @fix_safe = value || false
84
+ end
85
+
86
+ def fix_unsafe=(value)
87
+ @fix_unsafe = value || false
88
+ end
89
+
90
+ def show_console=(value)
91
+ @show_console = value || false
92
+ end
93
+
94
+ def rubo_config_file=(value)
95
+ @rubo_config_file = value
96
+
97
+ return if value.nil? || value.empty?
98
+
99
+ guard("Unknown RuboCop config file: #{value}") unless File.exist?(value)
100
+ end
101
+
102
+ def cop_run
103
+ cli = RuboCop::CLI.new
104
+
105
+ # log.section_heading('CLI OPTIONS')
106
+ # log.block cli_options
107
+
108
+ cli.run(cli_options)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -5,18 +5,24 @@ module KBuilder
5
5
  # Configuration for webpack5/builder
6
6
  class << self
7
7
  attr_writer :configuration
8
- end
9
8
 
10
- def self.configuration
11
- @configuration ||= Configuration.new
12
- end
9
+ def configuration(name = :default)
10
+ @configuration ||= Hash.new do |h, key|
11
+ h[key] = KBuilder::Configuration.new
12
+ end
13
+ @configuration[name]
14
+ end
13
15
 
14
- def self.reset
15
- @configuration = Configuration.new
16
- end
16
+ def reset(name = :default)
17
+ @configuration ||= Hash.new do |h, key|
18
+ h[key] = KBuilder::Configuration.new
19
+ end
20
+ @configuration[name] = KBuilder::Configuration.new
21
+ end
17
22
 
18
- def self.configure
19
- yield(configuration)
23
+ def configure(name = :default)
24
+ yield(configuration(name))
25
+ end
20
26
  end
21
27
 
22
28
  # Does this class need to move out into k_types?
@@ -48,12 +54,16 @@ module KBuilder
48
54
  @template_folders = orig.template_folders.clone
49
55
  end
50
56
 
51
- def debug
52
- log.subheading 'kbuilder base configuration'
57
+ def debug(heading: 'kbuilder base configuration')
58
+ log.section_heading 'kbuilder base configuration' if heading
59
+
60
+ # TODO: Add name to configuration object
61
+ # Don't have support for name on the configuration object yet
62
+ # log.kv 'config name', name
53
63
 
54
64
  target_folders.debug(title: 'target_folders')
55
65
 
56
- log.info ''
66
+ # log.info ''
57
67
 
58
68
  template_folders.debug(title: 'template folders (search order)')
59
69
  ''
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KBuilder
4
- VERSION = '0.0.60'
4
+ VERSION = '0.0.68'
5
5
  end
data/lib/k_builder.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rubocop'
4
+
3
5
  require 'k_log'
4
6
  require 'k_util'
5
7
  require 'k_type'
@@ -9,6 +11,11 @@ require 'k_builder/base_configuration'
9
11
  require 'k_builder/configuration'
10
12
  require 'k_builder/file_segments'
11
13
 
14
+ # should commands be in their own gem?
15
+ require 'k_builder/commands/base_command'
16
+ require 'k_builder/commands/rubo_cop_command'
17
+ require 'k_builder/commands/code_syntax_highlighter_command'
18
+
12
19
  require 'handlebars/helpers/template'
13
20
 
14
21
  module KBuilder