squared 0.0.5 → 0.0.6

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,306 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Squared
4
+ module Workspace
5
+ class Application
6
+ include Common
7
+ include Format
8
+ include ::Rake::DSL
9
+
10
+ TASK_BASE = {
11
+ build: [],
12
+ refresh: [],
13
+ depend: [],
14
+ outdated: [],
15
+ doc: [],
16
+ test: [],
17
+ copy: [],
18
+ clean: []
19
+ }
20
+ TASK_EXTEND = {}
21
+ WORKSPACE_KEYS = TASK_BASE.keys.freeze
22
+ private_constant :TASK_BASE, :TASK_EXTEND
23
+
24
+ class << self
25
+ def implement(*objs)
26
+ objs.each do |obj|
27
+ next unless obj < Project::Base
28
+
29
+ project_kind.unshift(obj)
30
+ obj.tasks&.each do |val|
31
+ TASK_BASE[val] ||= []
32
+ (TASK_EXTEND[val] ||= []).push(obj)
33
+ end
34
+ end
35
+ end
36
+
37
+ def find(path: nil, ref: nil)
38
+ if ref.is_a?(::Symbol) || ref.is_a?(::String)
39
+ ret = project_kind.find { |proj| proj.to_sym == ref.to_sym }
40
+ return ret if ret
41
+ end
42
+ project_kind.find { |proj| proj.is_a?(path) } if path
43
+ end
44
+
45
+ def to_s
46
+ super.to_s.match(/[^:]+$/)[0]
47
+ end
48
+
49
+ attr_reader :project_kind
50
+ end
51
+
52
+ @project_kind = []
53
+
54
+ attr_reader :main, :root, :home, :series, :theme, :warning
55
+ attr_accessor :exception, :pipe, :verbose
56
+
57
+ def initialize(main, *, common: true, exception: false, pipe: false, verbose: nil, **)
58
+ @main = main.to_s
59
+ @home = Pathname.pwd
60
+ @root = @home.parent
61
+ @series = Series.new(TASK_BASE, self)
62
+ @project = {}
63
+ @extensions = []
64
+ @script = {
65
+ group: {},
66
+ ref: {},
67
+ build: nil,
68
+ dev: nil,
69
+ prod: nil
70
+ }
71
+ @theme = common && @verbose ? __get__(:theme)[:workspace] : {}
72
+ @exception = exception.is_a?(String) ? !env(exception, ignore: '0').nil? : exception
73
+ @pipe = pipe.is_a?(String) ? !env(pipe, ignore: '0').nil? : pipe
74
+ @verbose = verbose.nil? ? !@pipe : verbose
75
+ @warning = true
76
+ end
77
+
78
+ def build(**kwargs)
79
+ return unless enabled?
80
+
81
+ @project.each_value do |proj|
82
+ next unless proj.enabled?
83
+
84
+ series.populate(proj)
85
+ proj.populate(**kwargs)
86
+ end
87
+
88
+ Application.project_kind.each { |obj| obj.populate(self, **kwargs) }
89
+ @extensions.each { |ext| __send__(ext, **kwargs) }
90
+
91
+ series[:refresh].clear if series[:refresh].all? { |val| val.end_with?(':build') }
92
+
93
+ task default: kwargs[:default] if series.has?(kwargs[:default]) && !task_defined?('default')
94
+
95
+ if series.has?('build')
96
+ init = ['depend', dev? && series.has?('refresh') ? 'refresh' : 'build']
97
+
98
+ task default: init[1] unless task_defined?('default')
99
+
100
+ if series.has?(init[0]) && !task_defined?('init')
101
+ desc 'init'
102
+ task init: init
103
+ end
104
+ end
105
+ series.finalize!(kwargs.fetch(:parallel, []))
106
+
107
+ yield self if block_given?
108
+ end
109
+
110
+ def run(script, group: nil, ref: nil)
111
+ script_command :run, script, group, ref
112
+ end
113
+
114
+ def depend(script, group: nil, ref: nil)
115
+ script_command :depend, script, group, ref
116
+ end
117
+
118
+ def clean(script, group: nil, ref: nil)
119
+ script_command :clean, script, group, ref
120
+ end
121
+
122
+ def doc(script, group: nil, ref: nil)
123
+ script_command :doc, script, group, ref
124
+ end
125
+
126
+ def test(script, group: nil, ref: nil)
127
+ script_command :test, script, group, ref
128
+ end
129
+
130
+ def add(name, path = nil, ref: nil, **kwargs)
131
+ path = root_path((path || name).to_s)
132
+ project = if !ref.is_a?(::Class)
133
+ Application.find(path: path, ref: ref)
134
+ elsif ref < Project::Base
135
+ ref
136
+ end
137
+ instance = (project || Project::Base).new(name, path, self, **kwargs)
138
+ @project[n = name.to_sym] = instance
139
+ __get__(:project)[n] = instance unless kwargs[:private]
140
+ self
141
+ end
142
+
143
+ def group(name, path, **kwargs)
144
+ root_path(path).children.map do |ent|
145
+ next unless (dir = Pathname.new(ent)).directory?
146
+
147
+ index = 0
148
+ basename = dir.basename.to_s.to_sym
149
+ override = kwargs.delete(basename)
150
+ while @project[basename]
151
+ index += 1
152
+ basename = :"#{basename}-#{index}"
153
+ end
154
+ [basename, dir, override]
155
+ end
156
+ .each do |basename, dir, override|
157
+ opts = kwargs.dup
158
+ opts.merge!(override) if override
159
+ add(basename, dir, group: name, **opts)
160
+ end
161
+ self
162
+ end
163
+
164
+ def compose(name, &blk)
165
+ namespace(name, &blk)
166
+ self
167
+ end
168
+
169
+ def apply(&blk)
170
+ instance_eval(&blk)
171
+ self
172
+ end
173
+
174
+ def style(name, *args, target: nil, empty: false)
175
+ data = nil
176
+ if target
177
+ as_a(target).each_with_index do |val, i|
178
+ if i == 0
179
+ break unless (data = __get__(:theme)[val.to_sym])
180
+ else
181
+ data = data[val.to_sym] ||= {}
182
+ end
183
+ end
184
+ return unless data
185
+ end
186
+ apply_style(data || theme, name, *args, empty: empty)
187
+ self
188
+ end
189
+
190
+ def script(group: nil, ref: nil)
191
+ if group || ref
192
+ (group && @script[:group][group.to_sym]) || (ref && @script[:ref][ref.to_sym])
193
+ else
194
+ @script[:build]
195
+ end
196
+ end
197
+
198
+ def env(key, equals: nil, ignore: nil, default: nil)
199
+ ret = ENV.fetch("#{key}_#{main.gsub(/[^\w]/, '_').upcase}", ENV[key]).to_s
200
+ return ret == equals.to_s unless equals.nil?
201
+
202
+ ret.empty? || (ignore && as_a(ignore).any? { |val| ret == val.to_s }) ? default : ret
203
+ end
204
+
205
+ def root_path(*args)
206
+ root.join(*args)
207
+ end
208
+
209
+ def home_path(*args)
210
+ home.join(*args)
211
+ end
212
+
213
+ def find_base(obj)
214
+ Application.project_kind.find { |proj| obj.instance_of?(proj) }
215
+ end
216
+
217
+ def to_s
218
+ root.to_s
219
+ end
220
+
221
+ def inspect
222
+ "#<#{self.class}: #{main} => #{self}>"
223
+ end
224
+
225
+ def enabled?
226
+ !@extensions.empty? || @project.any? { |_, proj| proj.enabled? }
227
+ end
228
+
229
+ def task_base?(key, val)
230
+ return val if task_defined?(key)
231
+
232
+ val if key == :refresh && series[:build].include?(val = "#{val.split(':').first}:build")
233
+ end
234
+
235
+ def task_include?(obj, key)
236
+ TASK_EXTEND.fetch(key, []).any? { |item| obj.is_a?(item) }
237
+ end
238
+
239
+ def task_defined?(key)
240
+ ::Rake::Task.task_defined?(key)
241
+ end
242
+
243
+ def dev?(script: nil, pat: nil, global: nil)
244
+ with?(:dev, script: script, pat: pat, global: global || (pat.nil? && global.nil?))
245
+ end
246
+
247
+ def prod?(script: nil, pat: nil, global: false)
248
+ return false if global && (@script[:dev] == true || !@script[:prod])
249
+
250
+ with?(:prod, script: script, pat: pat, global: global)
251
+ end
252
+
253
+ protected
254
+
255
+ def confirm(msg, agree: 'Y', cancel: 'N', default: nil, attempts: 5, timeout: 15)
256
+ require 'readline'
257
+ require 'timeout'
258
+ agree = /^#{agree}$/i if agree.is_a?(String)
259
+ cancel = /^#{cancel}$/i if cancel.is_a?(String)
260
+ Timeout.timeout(timeout) do
261
+ begin
262
+ while (ch = Readline.readline(msg, true))
263
+ ch = ch.chomp
264
+ ch = default if ch.empty?
265
+ case ch
266
+ when agree
267
+ return true
268
+ when cancel
269
+ return false
270
+ end
271
+ attempts -= 1
272
+ exit 1 unless attempts >= 0
273
+ end
274
+ rescue Interrupt
275
+ puts
276
+ exit 0
277
+ else
278
+ false
279
+ end
280
+ end
281
+ end
282
+
283
+ private
284
+
285
+ def script_command(task, val, group, ref)
286
+ if group
287
+ label = :group
288
+ items = as_a(group)
289
+ else
290
+ label = :ref
291
+ items = as_a(ref)
292
+ end
293
+ items.each { |name| (@script[label][name.to_sym] ||= {})[task] = val }
294
+ self
295
+ end
296
+
297
+ def with?(state, script: nil, pat: nil, global: false)
298
+ if global
299
+ pat = @script[state] if pat.nil?
300
+ script ||= @script[:build]
301
+ end
302
+ pat.is_a?(::Regexp) ? pat.match?(script) : pat == true
303
+ end
304
+ end
305
+ end
306
+ end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'date'
4
+ require 'logger'
4
5
 
5
6
  module Squared
6
- module Repo
7
+ module Workspace
7
8
  module Project
8
9
  class Base
9
10
  include Common
@@ -12,6 +13,8 @@ module Squared
12
13
  include Task
13
14
  include ::Rake::DSL
14
15
 
16
+ SEM_VER = /^(\d+)(\.)(\d+)(?:(\.)(\d+))?$/.freeze
17
+
15
18
  class << self
16
19
  include Common::Task
17
20
  include ::Rake::DSL
@@ -23,9 +26,12 @@ module Squared
23
26
  end
24
27
 
25
28
  def as_path(val)
26
- return val if val.is_a?(::Pathname)
27
-
28
- val.is_a?(::String) ? Pathname.new(val) : nil
29
+ case val
30
+ when ::Pathname
31
+ val
32
+ when ::String
33
+ Pathname.new(val)
34
+ end
29
35
  end
30
36
 
31
37
  def to_s
@@ -117,7 +123,7 @@ module Squared
117
123
  def populate(*)
118
124
  namespace name do
119
125
  workspace.series.each_key do |key|
120
- next unless Workspace::TASK_KEYS.include?(key) ? has?(key) : workspace.task_defined?(self, key)
126
+ next unless Application::WORKSPACE_KEYS.include?(key) ? has?(key) : workspace.task_include?(self, key)
121
127
 
122
128
  desc message(name, key)
123
129
  task key do
@@ -127,7 +133,7 @@ module Squared
127
133
  end
128
134
  end
129
135
 
130
- def build(*args)
136
+ def build(*args, sync: true)
131
137
  if args.empty?
132
138
  cmd, opts = @output
133
139
  opts &&= shell_escape(opts)
@@ -148,13 +154,13 @@ module Squared
148
154
  cmd = compose(opts)
149
155
  banner = env('REPO_BUILD') == 'verbose'
150
156
  end
151
- run(cmd, exception: workspace.exception, banner: banner)
157
+ run(cmd, exception: workspace.exception, banner: banner, sync: sync)
152
158
  end
153
159
 
154
160
  def refresh(*)
155
- build
161
+ build(sync: invoked_sync?('depend'))
156
162
  key = "#{name}:copy"
157
- if ::Rake::Task.task_defined?(key)
163
+ if workspace.task_defined?(key)
158
164
  invoke key
159
165
  else
160
166
  copy
@@ -162,29 +168,30 @@ module Squared
162
168
  end
163
169
 
164
170
  def depend(*)
165
- run(@depend, exception: workspace.exception) if @depend
171
+ run(@depend, exception: workspace.exception, sync: invoked_sync?('depend')) if @depend
166
172
  end
167
173
 
168
174
  def copy(*)
169
- run_s @copy
175
+ run_s(@copy, sync: invoked_sync?('copy'))
170
176
  end
171
177
 
172
178
  def doc
173
- build @doc if @doc
179
+ build(@doc, sync: invoked_sync?('doc')) if @doc
174
180
  end
175
181
 
176
182
  def test
177
- build @test if @test
183
+ build(@test, sync: invoked_sync?('test')) if @test
178
184
  end
179
185
 
180
186
  def clean
181
187
  return unless @clean
182
188
 
183
189
  if @clean.is_a?(::String)
184
- run_s @clean
190
+ run_s(@clean, sync: invoked_sync?('clean'))
185
191
  else
186
192
  @clean.each do |val|
187
- if (val = val.to_s).match?(%r{[\\/]$})
193
+ val = val.to_s
194
+ if val =~ %r{[\\/]$}
188
195
  dir = Pathname.new(val)
189
196
  dir = base_path(dir) unless dir.absolute?
190
197
  next unless dir.directory?
@@ -234,7 +241,7 @@ module Squared
234
241
  end
235
242
 
236
243
  def refresh?
237
- build? && (copy? || ::Rake::Task.task_defined?("#{name}:copy"))
244
+ build? && (copy? || workspace.task_defined?("#{name}:copy"))
238
245
  end
239
246
 
240
247
  def depend?
@@ -259,12 +266,12 @@ module Squared
259
266
 
260
267
  protected
261
268
 
262
- def run(cmd = @session, exception: false, banner: true, req: nil)
269
+ def run(cmd = @session, exception: false, banner: true, sync: true, req: nil)
263
270
  return if req && !base_path(req).exist?
264
271
 
265
272
  cmd = close_session(cmd)
266
273
  log.info cmd
267
- print_item format_banner(cmd, banner: banner)
274
+ print_item format_banner(cmd, banner: banner) if sync
268
275
  begin
269
276
  shell(cmd, chdir: path, exception: exception)
270
277
  rescue StandardError => e
@@ -273,22 +280,22 @@ module Squared
273
280
  end
274
281
  end
275
282
 
276
- def run_s(cmd)
277
- run(cmd, exception: workspace.exception, banner: verbose?) if cmd.is_a?(::String)
283
+ def run_s(cmd, **kwargs)
284
+ run(cmd, exception: workspace.exception, banner: verbose?, **kwargs) if cmd.is_a?(::String)
278
285
  end
279
286
 
280
- def env(key, suffix: nil, equals: nil, strict: false)
281
- alt = "#{key}_#{name.upcase}"
282
- ret = if suffix
283
- ENV.fetch(([alt] + as_a(suffix)).join('_'), '')
284
- else
285
- ENV.fetch(alt, strict ? '' : ENV[key]).to_s
286
- end
287
- if !equals.nil?
288
- ret == equals.to_s
289
- elsif !ret.empty? && ret != '0'
290
- ret
287
+ def env(key, equals: nil, ignore: ['0'].freeze, default: nil, suffix: nil, strict: false)
288
+ a = "#{key}_#{name.upcase}"
289
+ b = ''
290
+ if suffix
291
+ a = [a, suffix].flatten.join('_')
292
+ elsif !strict
293
+ b = ENV.fetch(key, '')
291
294
  end
295
+ ret = ENV.fetch(a, b)
296
+ return ret == equals.to_s unless equals.nil?
297
+
298
+ ret.empty? || as_a(ignore).any? { |val| ret == val.to_s } ? default : ret
292
299
  end
293
300
 
294
301
  def session(*cmd, options: nil)
@@ -300,8 +307,8 @@ module Squared
300
307
 
301
308
  def close_session(cmd)
302
309
  return cmd unless cmd.respond_to?(:done)
303
- raise ArgumentError, message('none were provided', hint: name) if cmd.empty?
304
310
 
311
+ raise_error('none were provided', hint: name) if cmd.empty?
305
312
  @session = nil if cmd == @session
306
313
  cmd.done
307
314
  end
@@ -362,7 +369,7 @@ module Squared
362
369
 
363
370
  if verbose?
364
371
  print_banner(cmd.sub(/^\S+/, &:upcase), path.to_s)
365
- elsif multiple && workspace.multiple?
372
+ elsif multiple && workspace.series.multiple?
366
373
  "## #{path} ##"
367
374
  end
368
375
  end
@@ -380,17 +387,17 @@ module Squared
380
387
  return unless (val = args[key]).nil? || (pat && !val.match?(pat))
381
388
 
382
389
  @session = nil
383
- raise ArgumentError, message(action, "#{flag}[#{key}]", hint: val.nil? ? 'missing' : 'invalid')
390
+ raise_error(action, "#{flag}[#{key}]", hint: val.nil? ? 'missing' : 'invalid')
384
391
  elsif args.is_a?(::Array)
385
392
  return unless args.empty?
386
393
 
387
394
  @session = nil
388
- raise ArgumentError, message(action, "#{flag}[]", hint: 'empty')
395
+ raise_error(action, "#{flag}[]", hint: 'empty')
389
396
  end
390
397
  return unless action.to_s.empty?
391
398
 
392
399
  @session = nil
393
- raise ArgumentError, message('parameter', flag, hint: 'missing')
400
+ raise_error('parameter', flag, hint: 'missing')
394
401
  end
395
402
 
396
403
  def verbose?
@@ -402,13 +409,12 @@ module Squared
402
409
  end
403
410
 
404
411
  def invoked_sync?(action)
405
- return true if workspace.sync?("#{action}:sync")
412
+ return true if workspace.series.sync?("#{action}:sync")
406
413
 
407
- missing = ->(val) { ::Rake::Task.tasks.none? { |item| item.name == val } }
408
414
  check = lambda do |val|
409
415
  if invoked?(val)
410
- missing.("#{val}:sync")
411
- elsif workspace.sync?("#{val}:sync")
416
+ !workspace.task_defined?("#{val}:sync")
417
+ elsif workspace.series.sync?("#{val}:sync")
412
418
  true
413
419
  end
414
420
  end
@@ -420,7 +426,7 @@ module Squared
420
426
  ret = check.("#{action}:#{base.to_sym}")
421
427
  return ret unless ret.nil?
422
428
  end
423
- invoked?("#{name}:#{action}") && (!invoked?(action) || missing.("#{action}:sync"))
429
+ invoked?("#{name}:#{action}") && (!invoked?(action) || !workspace.task_defined?("#{action}:sync"))
424
430
  end
425
431
 
426
432
  private