squared 0.6.0 → 0.6.2

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.
@@ -20,7 +20,8 @@ module Squared
20
20
  inspect multiline no-pager noautocomplete nocolorize noecho noecho-on-assignment noinspect
21
21
  nomultiline noprompt noscript nosingleline noverbose regexp-completor sample-book-mode script
22
22
  simple-prompt single-irb singleline tracer truncate-echo-on-assignment type-completor verbose
23
- back-trace-limit=i context-mode=i prompt=b prompt-mode=b].freeze
23
+ back-trace-limit=i context-mode=i prompt=b prompt-mode=b].freeze,
24
+ rbs: %w[I=pm r=bm no-stdlib no-collection collection=p log-level=b log-output=p repo=p].freeze
24
25
  }.freeze
25
26
  OPT_BUNDLE = {
26
27
  common: %w[no-color V|verbose r|retry=i].freeze,
@@ -124,6 +125,7 @@ module Squared
124
125
  ruby: %w[I disable enable dump r s].freeze,
125
126
  rake: %w[I libdir r require].freeze,
126
127
  irb: %w[I r].freeze,
128
+ rbs: %w[I r repo].freeze,
127
129
  gem: {
128
130
  contents: %w[s spec-dir].freeze,
129
131
  dependency: %w[s source].freeze,
@@ -159,11 +161,12 @@ module Squared
159
161
  'ruby' => %i[file script version].freeze,
160
162
  'gem' => %i[install uninstall outdated update pristine build push exec command].freeze,
161
163
  'bundle' => %i[install update cache exec config reinstall command].freeze,
164
+ 'rbs' => nil,
162
165
  'rake' => nil,
163
166
  'irb' => nil
164
167
  })
165
168
 
166
- def initialize(*, autodetect: false, gemspec: nil, asdf: 'ruby', **kwargs)
169
+ def initialize(*, autodetect: false, gemspec: nil, steep: 'Steepfile', asdf: 'ruby', **kwargs)
167
170
  super
168
171
  if @pass.include?(Ruby.ref)
169
172
  initialize_ref Ruby.ref
@@ -179,6 +182,7 @@ module Squared
179
182
  elsif gemspec
180
183
  basepath(gemspec.include?('.') ? gemspec : "#{gemspec}.gemspec")
181
184
  end
185
+ @steepfile = basepath(steep).yield_self { |file| file if file.exist? }
182
186
  return unless rakefile && @output[0].nil? && @copy.nil? && !version && !@autodetect
183
187
 
184
188
  begin
@@ -213,33 +217,41 @@ module Squared
213
217
  when 'rake'
214
218
  next unless rakefile
215
219
 
216
- format_desc action, nil, "task+,opts*|#{indexchar}index+|#,pattern*"
220
+ format_desc action, nil, "task+|#{indexchar}index+,opts*|#,pattern*"
217
221
  task action, [:command] do |_, args|
218
222
  if args.command == '#'
219
223
  format_list(raketasks, "rake[#{indexchar}N]", 'tasks', grep: args.extras, from: rakefile, &:join)
220
224
  else
221
- items, opts = args.to_a.partition { |val| indexitem(val) }
222
- if items.empty?
223
- rake(banner: true, opts: opts)
224
- else
225
- tasks = raketasks
226
- while (n, pre = indexitem(items.shift))
225
+ tasks = raketasks
226
+ cmd = nil
227
+ opts = []
228
+ queue = lambda do
229
+ if cmd
230
+ cmd += "[#{opts.join(',')}]" unless opts.empty?
231
+ rake(cmd, banner: true)
232
+ cmd = nil
233
+ elsif !opts.empty?
234
+ rake(banner: true, opts: opts)
235
+ end
236
+ opts.clear
237
+ end
238
+ args.to_a.each do |item|
239
+ if (n, pre = indexitem(item))
240
+ queue.call
227
241
  if (item = tasks[n.pred])
228
- cmd = pre ? "#{pre} #{item.first}" : item.first
242
+ cmd = [pre, item.first].compact.join(' ')
229
243
  elsif exception
230
244
  indexerror n, tasks
231
245
  else
232
246
  log.warn "rake task #{n} of #{tasks.size} (out of range)"
233
- next
234
- end
235
- if opts.empty?
236
- rake(cmd, banner: true)
237
- else
238
- rake(cmd + "[#{opts.join(',')}]", banner: true)
239
247
  opts.clear
248
+ next
240
249
  end
250
+ else
251
+ opts << item
241
252
  end
242
253
  end
254
+ queue.call
243
255
  end
244
256
  end
245
257
  when 'irb'
@@ -250,6 +262,72 @@ module Squared
250
262
  name = gemname if gemlib.any? { |file| exist?(file, "#{gemname}.rb") }
251
263
  irb(*args, opts: opts, name: name, verbose: false)
252
264
  end
265
+ when 'rbs'
266
+ next unless @steepfile
267
+
268
+ data = {}
269
+ target = nil
270
+ File.foreach(@steepfile) do |line|
271
+ if line =~ /^\s*target(?:\s+|\(\s*)(?::(\S+)|(["'])(.+)\2)/
272
+ target = [[], []]
273
+ data[$1 || $3.gsub(/[: ]/, '-')] = target
274
+ next
275
+ end
276
+ next unless target && line =~ /^\s*(check|signature)\s+(["'])(.+)\2/
277
+
278
+ target[$1 == 'check' ? 1 : 0] << $3
279
+ end
280
+ next if data.empty?
281
+
282
+ namespace 'rbs' do
283
+ data.each do |key, item|
284
+ sig, lib = item
285
+ next if sig.empty? || lib.empty?
286
+
287
+ format_desc action, key, 'sig?,path*'
288
+ task key do |_, args|
289
+ args = args.to_a
290
+ list = []
291
+ lib.each do |val|
292
+ val = File.join(val, '**/*.rb') unless val.include?('*') || val.end_with?('.rb')
293
+ list.concat(Dir.glob(val, base: path))
294
+ end
295
+ if args.empty?
296
+ files = choice_index('Select files', list, multiple: true, force: true, series: true,
297
+ accept: [accept_y('Generate?')])
298
+ else
299
+ files = []
300
+ args.each do |val|
301
+ if val.include?('*')
302
+ files.concat(Dir.glob(val, base: path))
303
+ elsif !(file = basepath(val)).exist?
304
+ print_error(val, hint: 'not found')
305
+ elsif file.directory?
306
+ files.concat(file.glob('**/*.rb'))
307
+ else
308
+ files << val
309
+ end
310
+ end
311
+ list.map! { |val| basepath(val).to_s }
312
+ files = files.select { |val| list.include?(basepath(val).to_s) }
313
+ if files.empty?
314
+ print_error('steep', 'no files matched', hint: "#{key}:check")
315
+ exit 1
316
+ end
317
+ files.uniq!
318
+ end
319
+ sig = if (n = sig.index(args.first))
320
+ args.shift
321
+ sig[n]
322
+ elsif sig.size > 1
323
+ choice_index('Select a sig', sig, force: true, series: true)
324
+ else
325
+ sig.first
326
+ end
327
+ rbs(:prototype, sig, *files)
328
+ end
329
+ end
330
+ end
253
331
  end
254
332
  else
255
333
  namespace action do
@@ -274,9 +352,9 @@ module Squared
274
352
  end
275
353
  gem(flag, opts: opts, banner: true, filter: {
276
354
  semver: semver,
277
- update: opts.delete('u') || opts.delete('update'),
278
- interactive: opts.delete('i') || opts.delete('interactive'),
279
- select: opts.delete('s') || opts.delete('select')
355
+ update: has_value!(opts, 'u', 'update'),
356
+ interactive: has_value!(opts, 'i', 'interactive'),
357
+ select: has_value!(opts, 's', 'select')
280
358
  })
281
359
  end
282
360
  when :build, :push, :exec, :update
@@ -320,7 +398,7 @@ module Squared
320
398
  format_desc action, flag, 'f/orce?,opts*'
321
399
  task flag do |_, args|
322
400
  opts = args.to_a
323
- opts << 'redownload' if opts.delete('f') || opts.delete('force')
401
+ opts << 'redownload' if has_value!(opts, 'f', 'force')
324
402
  if (lock = basepath('Gemfile.lock')).exist?
325
403
  config = basepath '.bundle', 'config'
326
404
  if config.exist? && config.read.match?(/\bBUNDLE_FROZEN:\s+"true"/)
@@ -406,17 +484,17 @@ module Squared
406
484
  end
407
485
 
408
486
  def copy(from: gemlib, into: @gemdir, override: false, **kwargs)
487
+ return if @copy == false
488
+
409
489
  glob = kwargs[:include]
410
490
  pass = kwargs[:exclude]
411
491
  if @copy && !override
412
492
  return super unless @copy.is_a?(Hash)
413
493
 
414
494
  from = @copy[:from] if @copy.key?(:from)
495
+ into = @copy[:into] if @copy.key?(:into)
415
496
  glob = @copy[:include] if @copy.key?(:include)
416
497
  pass = @copy[:exclude] if @copy.key?(:exclude)
417
- into = @copy[:into] if @copy.key?(:into)
418
- elsif @copy == false
419
- return
420
498
  end
421
499
  return unless into
422
500
 
@@ -439,8 +517,8 @@ module Squared
439
517
  def outdated(flag = nil, opts = [], sync: invoked_sync?('outdated', flag))
440
518
  cmd = bundle_output 'outdated'
441
519
  if flag
442
- se = (opts.delete('s') || opts.delete('select')) && !stdin?
443
- up = opts.delete('u') || opts.delete('update')
520
+ se = has_value!(opts, 's', 'select') && !stdin?
521
+ up = has_value!(opts, 'u', 'update')
444
522
  cmd << "--#{flag}"
445
523
  OptionPartition.new(opts, bundleopts(:outdated), cmd, project: self)
446
524
  .clear
@@ -673,6 +751,7 @@ module Squared
673
751
  end
674
752
  out.map!(&:split)
675
753
  pad = out.map(&:first).map!(&:size).max
754
+ print_item
676
755
  puts(out.map! { |line| '%*s %s' % [pad, line.first, line[1..-1].join(' ')] })
677
756
  end
678
757
  return
@@ -1064,9 +1143,7 @@ module Squared
1064
1143
  output = val.match?(/(?:un)?set/)
1065
1144
  else
1066
1145
  a = op.dup
1067
- b = op.shift
1068
- c = op.shift
1069
- d = op.shift
1146
+ b, c, d = op.slice!(0, 3)
1070
1147
  e = op.arg?('global', 'local')
1071
1148
  op << b
1072
1149
  getname = -> { op << (c || readline('Enter name', force: true)) }
@@ -1161,6 +1238,56 @@ module Squared
1161
1238
  run(banner: false, from: :irb)
1162
1239
  end
1163
1240
 
1241
+ def rbs(flag, *args, banner: verbose?, with: nil, pass: nil, **kwargs)
1242
+ case pass
1243
+ when NilClass
1244
+ pass = PASS_RUBY[:rbs]
1245
+ when Array
1246
+ pass += PASS_RUBY[:rbs]
1247
+ end
1248
+ opts = session_opts(with, args: args, kwargs: kwargs, pass: pass)
1249
+ cmd, opts = rbs_session(opts: opts)
1250
+ op = OptionPartition.new(opts, [], cmd << flag, project: self)
1251
+ case flag
1252
+ when :prototype
1253
+ sig = args.shift
1254
+ y = option('y', ignore: false)
1255
+ i = 1
1256
+ args.map { |val| basepath(val).relative_path_from(path) }.each do |file|
1257
+ dir = basepath sig, file.dirname
1258
+ dir.mkpath unless dir.exist?
1259
+ base = file.basename.to_s
1260
+ rbs = dir.join(base.stripext + '.rbs')
1261
+ status = if rbs.exist?
1262
+ case y
1263
+ when '0', 'false'
1264
+ 'ignored'
1265
+ else
1266
+ next unless y || confirm_basic('Overwrite?', rbs, 'N')
1267
+
1268
+ 'overwrite'
1269
+ end
1270
+ end
1271
+ unless status == 'ignored'
1272
+ ret = run(op.target.temp(File.extname(base) == '.rbi' ? 'rbi' : 'rb', file, '>', rbs), banner: false,
1273
+ series: true)
1274
+ if !ret
1275
+ status = 'FAIL'
1276
+ elsif File.empty?(rbs)
1277
+ status = 'empty'
1278
+ end
1279
+ end
1280
+ puts "#{i.to_s.rjust(2)}. #{rbs.relative_path_from(path)}".subhint(status)
1281
+ i += 1
1282
+ end
1283
+ else
1284
+ op.clear
1285
+ .append(*args)
1286
+ print_run(op, banner, **kwargs)
1287
+ run(banner: false, from: :"rbs:#{flag}")
1288
+ end
1289
+ end
1290
+
1164
1291
  def gemspec
1165
1292
  return @gemspec || nil unless @gemspec.nil?
1166
1293
 
@@ -1285,6 +1412,13 @@ module Squared
1285
1412
  session('rake', *preopts, *cmd, **kwargs)
1286
1413
  end
1287
1414
 
1415
+ def rbs_session(*cmd, opts: nil)
1416
+ return session('rbs', *cmd) unless opts
1417
+
1418
+ op = OptionPartition.new(opts, OPT_RUBY[:rbs], project: self)
1419
+ [session('rbs', *op.to_a, *cmd), op.extras]
1420
+ end
1421
+
1288
1422
  def gem_output(*cmd, **kwargs)
1289
1423
  session_output('gem', *cmd, **kwargs)
1290
1424
  end
@@ -1320,7 +1454,7 @@ module Squared
1320
1454
  end
1321
1455
 
1322
1456
  def config_set(key, *val)
1323
- run(bundle_output('config set', key, *val), banner: false, series: false)
1457
+ run(bundle_output('config set', key, *val), banner: false, series: true)
1324
1458
  end
1325
1459
 
1326
1460
  def preopts
@@ -10,9 +10,9 @@ module Squared
10
10
  include Common::Shell
11
11
  extend Forwardable
12
12
 
13
- OPT_NAME = /\A(?:(--)|-)((?(1)[A-Za-z\d]+|[A-Za-z\d]))\z/
14
- OPT_VALUE = /\A-{0,2}([^= ]+)(?: *= *| +)(.+)\z/
15
- OPT_SINGLE = /\A-([A-Za-z\d])(.+)\z/
13
+ OPT_NAME = /\A(?:(--)|-)((?(1)[^\[\]=\s-][^\[\]=\s]*|[^\[\]=\s-]))\z/
14
+ OPT_VALUE = /\A-{0,2}([^\[\]=\s-][^\[\]=\s]*)(?:=|\s+)(\S.*)\z/
15
+ OPT_SINGLE = /\A-([^\[\]=\s-])(.+)\z/
16
16
  private_constant :OPT_NAME, :OPT_VALUE, :OPT_SINGLE
17
17
 
18
18
  class << self
@@ -27,9 +27,9 @@ module Squared
27
27
  target << '--' if delim && !target.include?('--')
28
28
  if strip
29
29
  pat, s = Array(strip)
30
- ret.map! { |val| val.gsub(pat, s || '') }
30
+ ret.map! { |val| val.is_a?(String) ? val.gsub(pat, s || '') : val }
31
31
  end
32
- ret, err = ret.partition { |val| filter.match?(val) } if filter
32
+ ret, err = ret.partition { |val| filter.match?(val.to_s) } if filter
33
33
  if block_given?
34
34
  out = []
35
35
  err ||= []
@@ -47,7 +47,9 @@ module Squared
47
47
  end
48
48
  if escape || quote
49
49
  ret.map! do |val|
50
- if escape
50
+ if opt?(val)
51
+ val
52
+ elsif escape
51
53
  shell_escape(val, quote: quote, double: double)
52
54
  else
53
55
  shell_quote(val, force: force, double: double)
@@ -117,8 +119,17 @@ module Squared
117
119
  def arg?(target, *args, value: false, **)
118
120
  r, s = args.partition { |val| val.is_a?(Regexp) }
119
121
  r << matchopts(s, value) unless s.empty?
120
- s = target.to_a.compact
121
- r.any? { |pat| s.any?(pat) }
122
+ a = target.to_a
123
+ if (n = a.index('--'))
124
+ a = a[0..n]
125
+ end
126
+ r.any? { |pat| a.any?(pat) }
127
+ end
128
+
129
+ def opt?(val)
130
+ return false unless val.is_a?(String)
131
+
132
+ val.start_with?('-') && (OPT_NAME.match?(val) || OPT_VALUE.match?(val) || OPT_SINGLE.match?(val))
122
133
  end
123
134
 
124
135
  def pattern?(val)
@@ -140,13 +151,13 @@ module Squared
140
151
  end
141
152
 
142
153
  def shortopt(*group)
143
- group.map! { |s| s.delete_prefix('-') }
144
- "-(?:#{Regexp.escape(group.join('|'))})(?:\\z|[^ =]| +[^ -])"
154
+ group.map! { |s| Regexp.escape(s.delete_prefix('-')) }
155
+ "-(?:#{group.join('|')})(?:\\z|[^ =]| +[^ -])"
145
156
  end
146
157
 
147
158
  def longopt(*group, value)
148
- group.map! { |s| s.delete_prefix('--') }
149
- "--(?:#{Regexp.escape(group.join('|'))})(?:#{value ? '=[^ ]| +[^ -]' : '[= ]|\z'})"
159
+ group.map! { |s| Regexp.escape(s.delete_prefix('--')) }
160
+ "--(?:#{group.join('|')})(?:#{value ? '=[^ ]| +[^ -]' : '[= ]|\z'})"
150
161
  end
151
162
  end
152
163
 
@@ -155,7 +166,8 @@ module Squared
155
166
  def_delegators :@target, :+, :-, :<<, :any?, :none?, :include?, :add, :add?, :find, :find_all, :find_index,
156
167
  :merge, :delete, :delete?, :delete_if, :grep, :grep_v, :inspect, :to_a, :to_s
157
168
  def_delegators :@extras, :empty?, :each, :each_with_index, :partition, :dup, :first, :shift, :unshift,
158
- :pop, :push, :concat, :index, :join, :map, :map!, :detect, :select, :select!, :reject, :size
169
+ :pop, :push, :concat, :index, :join, :detect, :map, :map!, :select, :select!, :slice, :slice!,
170
+ :reject, :size
159
171
 
160
172
  def_delegator :@extras, :delete, :remove
161
173
  def_delegator :@extras, :delete_at, :remove_at
@@ -187,6 +199,7 @@ module Squared
187
199
  f = []
188
200
  si = []
189
201
  bl = []
202
+ ml = []
190
203
  list.flat_map do |val|
191
204
  x, y = val.split('|', 2)
192
205
  if y
@@ -223,6 +236,9 @@ module Squared
223
236
  @values << Regexp.escape(flag)
224
237
  when '!'
225
238
  bl << flag
239
+ when '+'
240
+ ml << flag
241
+ bare << flag
226
242
  else
227
243
  next
228
244
  end
@@ -248,8 +264,10 @@ module Squared
248
264
  f.concat(tr.call(f))
249
265
  si.concat(tr.call(si))
250
266
  bl.concat(tr.call(bl))
267
+ ml.concat(tr.call(ml))
251
268
  no.concat(tr.call(no))
252
269
  end
270
+ target.multiple.concat(ml.map { |val| val.size == 1 ? "-#{val}" : "--#{val}" }) if target.is_a?(JoinSet)
253
271
  numtype = [
254
272
  [i, /\A\d+\z/],
255
273
  [f, /\A\d*(?:\.\d+)?\z/],
@@ -442,7 +460,7 @@ module Squared
442
460
 
443
461
  def add_path(*args, force: true, double: false, **kwargs)
444
462
  if args.empty?
445
- args = select { |s| s.is_a?(String) }
463
+ args = select { |val| val.is_a?(String) }
446
464
  args.map! { |val| path + val } if path
447
465
  append(args, force: force, **kwargs)
448
466
  else
@@ -452,7 +470,8 @@ module Squared
452
470
  end
453
471
 
454
472
  def add_quote(*args, **kwargs)
455
- merge(args.compact.map! { |val| val == '--' ? val : shell_quote(val, **kwargs) })
473
+ merge(args.compact
474
+ .map! { |val| val == '--' || OptionPartition.opt?(val) ? val : shell_quote(val, **kwargs) })
456
475
  self
457
476
  end
458
477
 
@@ -621,11 +640,22 @@ module Squared
621
640
  super[/[^:]+\z/, 0]
622
641
  end
623
642
 
624
- attr_reader :delim
643
+ alias to_ary to_a
625
644
 
626
- def initialize(data = [], delim: ' ')
627
- super(data.compact)
645
+ attr_reader :delim, :extras
646
+ attr_accessor :multiple
647
+
648
+ def initialize(data = [], delim: ' ', partition: '--', uniq: /\A--?[^\[\]=\s-]/, multiple: [])
628
649
  @delim = delim
650
+ @partition = partition
651
+ @uniq = uniq
652
+ @multiple = multiple
653
+ @extras = []
654
+ super(data.compact)
655
+ end
656
+
657
+ def insert(*args)
658
+ replace Set.new(to_ary.reject { |val| val.nil? || (val.is_a?(String) && val.empty?) }.insert(*args))
629
659
  end
630
660
 
631
661
  def last(val, pat)
@@ -634,7 +664,7 @@ module Squared
634
664
  end
635
665
 
636
666
  def pass(&blk)
637
- ret = to_a.map!(&:to_s).reject(&:empty?)
667
+ ret = to_ary.map!(&:to_s).reject(&:empty?)
638
668
  @last&.each do |val, pat, key|
639
669
  items = []
640
670
  index = nil
@@ -655,6 +685,7 @@ module Squared
655
685
  end
656
686
  ret[items.last] = val
657
687
  end
688
+ ret.concat(extras.map(&:to_s).reject(&:empty?)) unless extras.empty?
658
689
  return ret unless block_given?
659
690
 
660
691
  ret.reject(&blk)
@@ -676,18 +707,82 @@ module Squared
676
707
 
677
708
  def temp(*args, &blk)
678
709
  args.compact!
679
- ret = pass(&blk)
680
- ret = Set.new(ret.concat(args)).to_a unless args.empty?
681
- ret.join(@delim)
710
+ pass(&blk)
711
+ .concat(args)
712
+ .join(@delim)
682
713
  end
683
714
 
684
715
  def done
685
716
  to_s.tap { clear }
686
717
  end
687
718
 
719
+ def merge(enum)
720
+ if !extras.empty?
721
+ extras.concat(enum.to_a)
722
+ self
723
+ elsif (n = enum.find_index { |val| extras?(val) })
724
+ data = enum.to_a
725
+ @extras = if n == 0
726
+ data
727
+ else
728
+ super(data[0...n])
729
+ data[n..-1]
730
+ end
731
+ self
732
+ else
733
+ super
734
+ end
735
+ end
736
+
737
+ def <<(obj)
738
+ extras!(obj) || super
739
+ end
740
+
741
+ def add(obj)
742
+ extras!(obj) || super
743
+ end
744
+
745
+ def add?(obj)
746
+ extras!(obj) || super
747
+ end
748
+
749
+ def to_a
750
+ pass
751
+ end
752
+
688
753
  def to_s
689
754
  pass.join(@delim)
690
755
  end
756
+
757
+ def to_enum(*args)
758
+ pass.to_enum(*args)
759
+ end
760
+
761
+ def to_json(*args)
762
+ pass.to_json(*args)
763
+ end
764
+
765
+ def to_yaml(*args)
766
+ pass.to_yaml(*args)
767
+ end
768
+
769
+ alias push :<<
770
+ alias concat merge
771
+
772
+ private
773
+
774
+ def extras!(obj)
775
+ return if extras.empty? && !extras?(obj)
776
+
777
+ unless !extras.include?(@partition) && include?(obj) && @uniq.match?(s = obj.to_s) && !multiple.include?(s)
778
+ extras << obj
779
+ end
780
+ self
781
+ end
782
+
783
+ def extras?(obj)
784
+ obj == @partition || (include?(obj) && (!@uniq.match?(s = obj.to_s) || multiple.include?(s)))
785
+ end
691
786
  end
692
787
  end
693
788
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: squared
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - An Pham
@@ -124,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
126
  requirements: []
127
- rubygems_version: 3.7.2
127
+ rubygems_version: 3.6.9
128
128
  specification_version: 4
129
129
  summary: Rake task generator for managing multi-language workspaces.
130
130
  test_files: []