toys-core 0.11.5 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +62 -0
- data/LICENSE.md +1 -1
- data/README.md +5 -2
- data/docs/guide.md +1 -1
- data/lib/toys/acceptor.rb +13 -4
- data/lib/toys/arg_parser.rb +7 -7
- data/lib/toys/cli.rb +170 -120
- data/lib/toys/compat.rb +71 -23
- data/lib/toys/completion.rb +18 -6
- data/lib/toys/context.rb +24 -15
- data/lib/toys/core.rb +6 -2
- data/lib/toys/dsl/base.rb +87 -0
- data/lib/toys/dsl/flag.rb +26 -20
- data/lib/toys/dsl/flag_group.rb +18 -14
- data/lib/toys/dsl/internal.rb +206 -0
- data/lib/toys/dsl/positional_arg.rb +26 -16
- data/lib/toys/dsl/tool.rb +180 -218
- data/lib/toys/errors.rb +64 -8
- data/lib/toys/flag.rb +662 -656
- data/lib/toys/flag_group.rb +24 -10
- data/lib/toys/input_file.rb +13 -7
- data/lib/toys/loader.rb +293 -140
- data/lib/toys/middleware.rb +46 -22
- data/lib/toys/mixin.rb +10 -8
- data/lib/toys/positional_arg.rb +21 -20
- data/lib/toys/settings.rb +914 -0
- data/lib/toys/source_info.rb +147 -35
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +2 -0
- data/lib/toys/standard_middleware/apply_config.rb +6 -4
- data/lib/toys/standard_middleware/handle_usage_errors.rb +1 -0
- data/lib/toys/standard_middleware/set_default_descriptions.rb +19 -18
- data/lib/toys/standard_middleware/show_help.rb +19 -5
- data/lib/toys/standard_middleware/show_root_version.rb +2 -0
- data/lib/toys/standard_mixins/bundler.rb +24 -15
- data/lib/toys/standard_mixins/exec.rb +43 -34
- data/lib/toys/standard_mixins/fileutils.rb +3 -1
- data/lib/toys/standard_mixins/gems.rb +21 -17
- data/lib/toys/standard_mixins/git_cache.rb +46 -0
- data/lib/toys/standard_mixins/highline.rb +8 -8
- data/lib/toys/standard_mixins/terminal.rb +5 -5
- data/lib/toys/standard_mixins/xdg.rb +56 -0
- data/lib/toys/template.rb +11 -9
- data/lib/toys/{tool.rb → tool_definition.rb} +292 -226
- data/lib/toys/utils/completion_engine.rb +7 -2
- data/lib/toys/utils/exec.rb +162 -132
- data/lib/toys/utils/gems.rb +85 -60
- data/lib/toys/utils/git_cache.rb +813 -0
- data/lib/toys/utils/help_text.rb +117 -37
- data/lib/toys/utils/terminal.rb +11 -3
- data/lib/toys/utils/xdg.rb +293 -0
- data/lib/toys/wrappable_string.rb +9 -2
- data/lib/toys-core.rb +18 -6
- metadata +14 -7
@@ -54,6 +54,7 @@ module Toys
|
|
54
54
|
|
55
55
|
##
|
56
56
|
# Internal completion method designed for testing.
|
57
|
+
#
|
57
58
|
# @private
|
58
59
|
#
|
59
60
|
def run_internal(line)
|
@@ -78,7 +79,9 @@ module Toys
|
|
78
79
|
end
|
79
80
|
|
80
81
|
class << self
|
81
|
-
##
|
82
|
+
##
|
83
|
+
# @private
|
84
|
+
#
|
82
85
|
def split(line)
|
83
86
|
words = []
|
84
87
|
field = ::String.new
|
@@ -97,7 +100,9 @@ module Toys
|
|
97
100
|
words
|
98
101
|
end
|
99
102
|
|
100
|
-
##
|
103
|
+
##
|
104
|
+
# @private
|
105
|
+
#
|
101
106
|
def format_candidate(candidate, quote_type)
|
102
107
|
str = candidate.to_s
|
103
108
|
partial = candidate.is_a?(Completion::Candidate) ? candidate.partial? : false
|
data/lib/toys/utils/exec.rb
CHANGED
@@ -16,8 +16,6 @@ module Toys
|
|
16
16
|
# This class is not loaded by default. Before using it directly, you should
|
17
17
|
# `require "toys/utils/exec"`
|
18
18
|
#
|
19
|
-
# ## Features
|
20
|
-
#
|
21
19
|
# ### Controlling processes
|
22
20
|
#
|
23
21
|
# A process can be started in the *foreground* or the *background*. If you
|
@@ -138,7 +136,7 @@ module Toys
|
|
138
136
|
# end
|
139
137
|
# exec_service.exec(["git", "init"], result_callback: my_callback)
|
140
138
|
#
|
141
|
-
#
|
139
|
+
# ### Configuration options
|
142
140
|
#
|
143
141
|
# A variety of options can be used to control subprocesses. These can be
|
144
142
|
# provided to any method that starts a subprocess. Youc an also set
|
@@ -282,7 +280,7 @@ module Toys
|
|
282
280
|
#
|
283
281
|
def exec_ruby(args, **opts, &block)
|
284
282
|
cmd = args.is_a?(::Array) ? [::RbConfig.ruby] + args : "#{::RbConfig.ruby} #{args}"
|
285
|
-
log_cmd =
|
283
|
+
log_cmd = "exec ruby: #{args.inspect}"
|
286
284
|
opts = {argv0: "ruby", log_cmd: log_cmd}.merge(opts)
|
287
285
|
exec(cmd, **opts, &block)
|
288
286
|
end
|
@@ -398,92 +396,6 @@ module Toys
|
|
398
396
|
exec(cmd, **opts, &block).exit_code
|
399
397
|
end
|
400
398
|
|
401
|
-
##
|
402
|
-
# An internal helper class storing the configuration of a subprocess invocation
|
403
|
-
# @private
|
404
|
-
#
|
405
|
-
class Opts
|
406
|
-
##
|
407
|
-
# Option keys that belong to exec configuration
|
408
|
-
# @private
|
409
|
-
#
|
410
|
-
CONFIG_KEYS = [
|
411
|
-
:argv0,
|
412
|
-
:background,
|
413
|
-
:cli,
|
414
|
-
:env,
|
415
|
-
:err,
|
416
|
-
:in,
|
417
|
-
:logger,
|
418
|
-
:log_cmd,
|
419
|
-
:log_level,
|
420
|
-
:name,
|
421
|
-
:out,
|
422
|
-
:result_callback,
|
423
|
-
].freeze
|
424
|
-
|
425
|
-
##
|
426
|
-
# Option keys that belong to spawn configuration
|
427
|
-
# @private
|
428
|
-
#
|
429
|
-
SPAWN_KEYS = [
|
430
|
-
:chdir,
|
431
|
-
:close_others,
|
432
|
-
:new_pgroup,
|
433
|
-
:pgroup,
|
434
|
-
:umask,
|
435
|
-
:unsetenv_others,
|
436
|
-
].freeze
|
437
|
-
|
438
|
-
## @private
|
439
|
-
def initialize(parent = nil)
|
440
|
-
if parent
|
441
|
-
@config_opts = ::Hash.new { |_h, k| parent.config_opts[k] }
|
442
|
-
@spawn_opts = ::Hash.new { |_h, k| parent.spawn_opts[k] }
|
443
|
-
elsif block_given?
|
444
|
-
@config_opts = ::Hash.new { |_h, k| yield k }
|
445
|
-
@spawn_opts = ::Hash.new { |_h, k| yield k }
|
446
|
-
else
|
447
|
-
@config_opts = {}
|
448
|
-
@spawn_opts = {}
|
449
|
-
end
|
450
|
-
end
|
451
|
-
|
452
|
-
## @private
|
453
|
-
def add(config)
|
454
|
-
config.each do |k, v|
|
455
|
-
if CONFIG_KEYS.include?(k)
|
456
|
-
@config_opts[k] = v
|
457
|
-
elsif SPAWN_KEYS.include?(k) || k.to_s.start_with?("rlimit_")
|
458
|
-
@spawn_opts[k] = v
|
459
|
-
else
|
460
|
-
raise ::ArgumentError, "Unknown key: #{k.inspect}"
|
461
|
-
end
|
462
|
-
end
|
463
|
-
self
|
464
|
-
end
|
465
|
-
|
466
|
-
## @private
|
467
|
-
def delete(*keys)
|
468
|
-
keys.each do |k|
|
469
|
-
if CONFIG_KEYS.include?(k)
|
470
|
-
@config_opts.delete(k)
|
471
|
-
elsif SPAWN_KEYS.include?(k) || k.to_s.start_with?("rlimit_")
|
472
|
-
@spawn_opts.delete(k)
|
473
|
-
else
|
474
|
-
raise ::ArgumentError, "Unknown key: #{k.inspect}"
|
475
|
-
end
|
476
|
-
end
|
477
|
-
self
|
478
|
-
end
|
479
|
-
|
480
|
-
## @private
|
481
|
-
attr_reader :config_opts
|
482
|
-
|
483
|
-
## @private
|
484
|
-
attr_reader :spawn_opts
|
485
|
-
end
|
486
|
-
|
487
399
|
##
|
488
400
|
# An object that controls a subprocess. This object is returned from an
|
489
401
|
# execution running in the background, or is yielded to a control block
|
@@ -492,28 +404,6 @@ module Toys
|
|
492
404
|
# send signals to the process, and get its result.
|
493
405
|
#
|
494
406
|
class Controller
|
495
|
-
## @private
|
496
|
-
def initialize(name, controller_streams, captures, pid, join_threads,
|
497
|
-
result_callback, mutex)
|
498
|
-
@name = name
|
499
|
-
@in = controller_streams[:in]
|
500
|
-
@out = controller_streams[:out]
|
501
|
-
@err = controller_streams[:err]
|
502
|
-
@captures = captures
|
503
|
-
@pid = @exception = @wait_thread = nil
|
504
|
-
case pid
|
505
|
-
when ::Integer
|
506
|
-
@pid = pid
|
507
|
-
@wait_thread = ::Process.detach(pid)
|
508
|
-
when ::Exception
|
509
|
-
@exception = pid
|
510
|
-
end
|
511
|
-
@join_threads = join_threads
|
512
|
-
@result_callback = result_callback
|
513
|
-
@mutex = mutex
|
514
|
-
@result = nil
|
515
|
-
end
|
516
|
-
|
517
407
|
##
|
518
408
|
# The subcommand's name.
|
519
409
|
# @return [Object]
|
@@ -748,8 +638,33 @@ module Toys
|
|
748
638
|
end
|
749
639
|
end
|
750
640
|
|
641
|
+
##
|
642
|
+
# @private
|
643
|
+
#
|
644
|
+
def initialize(name, controller_streams, captures, pid, join_threads,
|
645
|
+
result_callback, mutex)
|
646
|
+
@name = name
|
647
|
+
@in = controller_streams[:in]
|
648
|
+
@out = controller_streams[:out]
|
649
|
+
@err = controller_streams[:err]
|
650
|
+
@captures = captures
|
651
|
+
@pid = @exception = @wait_thread = nil
|
652
|
+
case pid
|
653
|
+
when ::Integer
|
654
|
+
@pid = pid
|
655
|
+
@wait_thread = ::Process.detach(pid)
|
656
|
+
when ::Exception
|
657
|
+
@exception = pid
|
658
|
+
end
|
659
|
+
@join_threads = join_threads
|
660
|
+
@result_callback = result_callback
|
661
|
+
@mutex = mutex
|
662
|
+
@result = nil
|
663
|
+
end
|
664
|
+
|
751
665
|
##
|
752
666
|
# Close the controller's streams.
|
667
|
+
#
|
753
668
|
# @private
|
754
669
|
#
|
755
670
|
def close_streams(which)
|
@@ -801,15 +716,6 @@ module Toys
|
|
801
716
|
# return the numeric signal code.
|
802
717
|
#
|
803
718
|
class Result
|
804
|
-
## @private
|
805
|
-
def initialize(name, out, err, status, exception)
|
806
|
-
@name = name
|
807
|
-
@captured_out = out
|
808
|
-
@captured_err = err
|
809
|
-
@status = status
|
810
|
-
@exception = exception
|
811
|
-
end
|
812
|
-
|
813
719
|
##
|
814
720
|
# The subcommand's name.
|
815
721
|
#
|
@@ -930,13 +836,129 @@ module Toys
|
|
930
836
|
code = exit_code
|
931
837
|
!code.nil? && !code.zero?
|
932
838
|
end
|
839
|
+
|
840
|
+
##
|
841
|
+
# @private
|
842
|
+
#
|
843
|
+
def initialize(name, out, err, status, exception)
|
844
|
+
@name = name
|
845
|
+
@captured_out = out
|
846
|
+
@captured_err = err
|
847
|
+
@status = status
|
848
|
+
@exception = exception
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
private
|
853
|
+
|
854
|
+
##
|
855
|
+
# An internal helper class storing the configuration of a subprocess invocation
|
856
|
+
#
|
857
|
+
# @private
|
858
|
+
#
|
859
|
+
class Opts
|
860
|
+
##
|
861
|
+
# Option keys that belong to exec configuration
|
862
|
+
#
|
863
|
+
# @private
|
864
|
+
#
|
865
|
+
CONFIG_KEYS = [
|
866
|
+
:argv0,
|
867
|
+
:background,
|
868
|
+
:cli,
|
869
|
+
:env,
|
870
|
+
:err,
|
871
|
+
:in,
|
872
|
+
:logger,
|
873
|
+
:log_cmd,
|
874
|
+
:log_level,
|
875
|
+
:name,
|
876
|
+
:out,
|
877
|
+
:result_callback,
|
878
|
+
].freeze
|
879
|
+
|
880
|
+
##
|
881
|
+
# Option keys that belong to spawn configuration
|
882
|
+
#
|
883
|
+
# @private
|
884
|
+
#
|
885
|
+
SPAWN_KEYS = [
|
886
|
+
:chdir,
|
887
|
+
:close_others,
|
888
|
+
:new_pgroup,
|
889
|
+
:pgroup,
|
890
|
+
:umask,
|
891
|
+
:unsetenv_others,
|
892
|
+
].freeze
|
893
|
+
|
894
|
+
##
|
895
|
+
# @private
|
896
|
+
#
|
897
|
+
def initialize(parent = nil)
|
898
|
+
if parent
|
899
|
+
@config_opts = ::Hash.new { |_h, k| parent.config_opts[k] }
|
900
|
+
@spawn_opts = ::Hash.new { |_h, k| parent.spawn_opts[k] }
|
901
|
+
elsif block_given?
|
902
|
+
@config_opts = ::Hash.new { |_h, k| yield k }
|
903
|
+
@spawn_opts = ::Hash.new { |_h, k| yield k }
|
904
|
+
else
|
905
|
+
@config_opts = {}
|
906
|
+
@spawn_opts = {}
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
##
|
911
|
+
# @private
|
912
|
+
#
|
913
|
+
def add(config)
|
914
|
+
config.each do |k, v|
|
915
|
+
if CONFIG_KEYS.include?(k)
|
916
|
+
@config_opts[k] = v
|
917
|
+
elsif SPAWN_KEYS.include?(k) || k.to_s.start_with?("rlimit_")
|
918
|
+
@spawn_opts[k] = v
|
919
|
+
else
|
920
|
+
raise ::ArgumentError, "Unknown key: #{k.inspect}"
|
921
|
+
end
|
922
|
+
end
|
923
|
+
self
|
924
|
+
end
|
925
|
+
|
926
|
+
##
|
927
|
+
# @private
|
928
|
+
#
|
929
|
+
def delete(*keys)
|
930
|
+
keys.each do |k|
|
931
|
+
if CONFIG_KEYS.include?(k)
|
932
|
+
@config_opts.delete(k)
|
933
|
+
elsif SPAWN_KEYS.include?(k) || k.to_s.start_with?("rlimit_")
|
934
|
+
@spawn_opts.delete(k)
|
935
|
+
else
|
936
|
+
raise ::ArgumentError, "Unknown key: #{k.inspect}"
|
937
|
+
end
|
938
|
+
end
|
939
|
+
self
|
940
|
+
end
|
941
|
+
|
942
|
+
##
|
943
|
+
# @private
|
944
|
+
#
|
945
|
+
attr_reader :config_opts
|
946
|
+
|
947
|
+
##
|
948
|
+
# @private
|
949
|
+
#
|
950
|
+
attr_reader :spawn_opts
|
933
951
|
end
|
934
952
|
|
935
953
|
##
|
936
954
|
# An object that manages the execution of a subcommand
|
955
|
+
#
|
937
956
|
# @private
|
938
957
|
#
|
939
958
|
class Executor
|
959
|
+
##
|
960
|
+
# @private
|
961
|
+
#
|
940
962
|
def initialize(exec_opts, spawn_cmd, block)
|
941
963
|
@fork_func = spawn_cmd.respond_to?(:call) ? spawn_cmd : nil
|
942
964
|
@spawn_cmd = spawn_cmd.respond_to?(:call) ? nil : spawn_cmd
|
@@ -952,6 +974,9 @@ module Toys
|
|
952
974
|
@mutex = ::Mutex.new
|
953
975
|
end
|
954
976
|
|
977
|
+
##
|
978
|
+
# @private
|
979
|
+
#
|
955
980
|
def execute
|
956
981
|
setup_in_stream
|
957
982
|
setup_out_stream(:out)
|
@@ -972,17 +997,23 @@ module Toys
|
|
972
997
|
def log_command
|
973
998
|
logger = @config_opts[:logger]
|
974
999
|
if logger && @config_opts[:log_level] != false
|
975
|
-
cmd_str = @config_opts[:log_cmd] || default_log_str
|
1000
|
+
cmd_str = @config_opts[:log_cmd] || default_log_str
|
976
1001
|
logger.add(@config_opts[:log_level] || ::Logger::INFO, cmd_str) if cmd_str
|
977
1002
|
end
|
978
1003
|
end
|
979
1004
|
|
980
|
-
def default_log_str
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
1005
|
+
def default_log_str
|
1006
|
+
if @fork_func
|
1007
|
+
"exec proc: #{@fork_func.inspect}"
|
1008
|
+
elsif @spawn_cmd
|
1009
|
+
if @spawn_cmd.size == 1 && @spawn_cmd.first.is_a?(::String)
|
1010
|
+
"exec sh: #{@spawn_cmd.first.inspect}"
|
1011
|
+
else
|
1012
|
+
cmd_binary = @spawn_cmd.first
|
1013
|
+
cmd_binary = cmd_binary.first if cmd_binary.is_a?(::Array)
|
1014
|
+
"exec: #{([cmd_binary] + @spawn_cmd[1..-1]).inspect}"
|
1015
|
+
end
|
1016
|
+
end
|
986
1017
|
end
|
987
1018
|
|
988
1019
|
def start_with_controller
|
@@ -1093,9 +1124,10 @@ module Toys
|
|
1093
1124
|
|
1094
1125
|
def interpret_out_array_within_fork(stream)
|
1095
1126
|
if stream.first == :child
|
1096
|
-
|
1127
|
+
case stream[1]
|
1128
|
+
when :err
|
1097
1129
|
$stderr
|
1098
|
-
|
1130
|
+
when :out
|
1099
1131
|
$stdout
|
1100
1132
|
end
|
1101
1133
|
else
|
@@ -1312,8 +1344,6 @@ module Toys
|
|
1312
1344
|
end
|
1313
1345
|
end
|
1314
1346
|
|
1315
|
-
private
|
1316
|
-
|
1317
1347
|
def canonical_binary_spec(cmd, exec_opts)
|
1318
1348
|
config_argv0 = exec_opts.config_opts[:argv0]
|
1319
1349
|
return cmd.to_s if !config_argv0 && !cmd.is_a?(::Array)
|
data/lib/toys/utils/gems.rb
CHANGED
@@ -132,8 +132,8 @@ module Toys
|
|
132
132
|
end
|
133
133
|
@on_conflict = on_conflict || :error
|
134
134
|
@terminal = terminal
|
135
|
-
@input = input ||
|
136
|
-
@output = output ||
|
135
|
+
@input = input || $stdin
|
136
|
+
@output = output || $stdout
|
137
137
|
end
|
138
138
|
|
139
139
|
##
|
@@ -185,17 +185,19 @@ module Toys
|
|
185
185
|
gemfile_path = Gems.find_gemfile(dir, gemfile_names: gemfile_names)
|
186
186
|
end
|
187
187
|
raise GemfileNotFoundError, "Gemfile not found" unless gemfile_path
|
188
|
+
gemfile_path = ::File.absolute_path(gemfile_path)
|
188
189
|
Gems.synchronize do
|
189
190
|
if configure_gemfile(gemfile_path)
|
190
|
-
activate("bundler", "~> 2.
|
191
|
+
activate("bundler", "~> 2.2")
|
191
192
|
require "bundler"
|
192
|
-
|
193
|
-
setup_bundle(gemfile_path, lockfile_path, groups: groups, retries: retries)
|
193
|
+
setup_bundle(gemfile_path, groups: groups, retries: retries)
|
194
194
|
end
|
195
195
|
end
|
196
196
|
end
|
197
197
|
|
198
|
+
##
|
198
199
|
# @private
|
200
|
+
#
|
199
201
|
def self.find_gemfile(search_dir, gemfile_names: nil)
|
200
202
|
gemfile_names ||= DEFAULT_GEMFILE_NAMES
|
201
203
|
Array(gemfile_names).each do |file|
|
@@ -207,7 +209,9 @@ module Toys
|
|
207
209
|
|
208
210
|
@global_mutex = ::Monitor.new
|
209
211
|
|
212
|
+
##
|
210
213
|
# @private
|
214
|
+
#
|
211
215
|
def self.synchronize(&block)
|
212
216
|
@global_mutex.synchronize(&block)
|
213
217
|
end
|
@@ -254,8 +258,7 @@ module Toys
|
|
254
258
|
def confirm_and_install_gem(name, requirements)
|
255
259
|
if @on_missing == :confirm
|
256
260
|
requirements_text = gem_requirements_text(name, requirements)
|
257
|
-
response = terminal.confirm("Gem needed: #{requirements_text}. Install? ",
|
258
|
-
default: @default_confirm)
|
261
|
+
response = terminal.confirm("Gem needed: #{requirements_text}. Install? ", default: @default_confirm)
|
259
262
|
unless response
|
260
263
|
raise InstallFailedError, "Canceled installation of needed gem: #{requirements_text}"
|
261
264
|
end
|
@@ -296,75 +299,96 @@ module Toys
|
|
296
299
|
if ::File.basename(gemfile_path) == "gems.rb"
|
297
300
|
::File.join(::File.dirname(gemfile_path), "gems.locked")
|
298
301
|
else
|
299
|
-
gemfile_path
|
302
|
+
"#{gemfile_path}.lock"
|
300
303
|
end
|
301
304
|
end
|
302
305
|
|
303
|
-
def setup_bundle(gemfile_path,
|
306
|
+
def setup_bundle(gemfile_path, groups: nil, retries: nil)
|
307
|
+
check_gemfile_compatibility(gemfile_path)
|
304
308
|
groups = Array(groups)
|
305
|
-
|
309
|
+
modified_gemfile_path = create_modified_gemfile(gemfile_path)
|
306
310
|
begin
|
307
|
-
|
308
|
-
::Bundler.ui.silence { ::Bundler.setup(*groups) }
|
311
|
+
attempt_setup_bundle(modified_gemfile_path, groups)
|
309
312
|
rescue ::Bundler::GemNotFound, ::Bundler::VersionConflict
|
310
|
-
restore_toys_libs
|
311
|
-
install_bundle(gemfile_path, retries: retries)
|
312
|
-
old_lockfile_contents = save_old_lockfile(lockfile_path)
|
313
313
|
::Bundler.reset!
|
314
|
-
|
315
|
-
|
314
|
+
restore_toys_libs
|
315
|
+
install_bundle(modified_gemfile_path, retries: retries)
|
316
|
+
attempt_setup_bundle(modified_gemfile_path, groups)
|
317
|
+
ensure
|
318
|
+
delete_modified_gemfile(modified_gemfile_path)
|
319
|
+
::ENV["BUNDLE_GEMFILE"] = gemfile_path
|
316
320
|
end
|
317
321
|
restore_toys_libs
|
318
|
-
ensure
|
319
|
-
restore_old_lockfile(lockfile_path, old_lockfile_contents)
|
320
|
-
end
|
321
|
-
|
322
|
-
def save_old_lockfile(lockfile_path)
|
323
|
-
return nil unless ::File.readable?(lockfile_path) && ::File.writable?(lockfile_path)
|
324
|
-
::File.read(lockfile_path)
|
325
322
|
end
|
326
323
|
|
327
|
-
def
|
328
|
-
|
329
|
-
|
330
|
-
|
324
|
+
def attempt_setup_bundle(modified_gemfile_path, groups)
|
325
|
+
::ENV["BUNDLE_GEMFILE"] = modified_gemfile_path
|
326
|
+
::Bundler.configure
|
327
|
+
::Bundler.settings.temporary({gemfile: modified_gemfile_path}) do
|
328
|
+
::Bundler.ui.silence do
|
329
|
+
::Bundler.setup(*groups)
|
331
330
|
end
|
332
331
|
end
|
333
332
|
end
|
334
333
|
|
335
|
-
def
|
334
|
+
def check_gemfile_compatibility(gemfile_path)
|
335
|
+
::Bundler.configure
|
336
336
|
builder = ::Bundler::Dsl.new
|
337
337
|
builder.eval_gemfile(gemfile_path)
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
add_gem_to_definition(builder, "toys-core")
|
342
|
-
if removed_toys || ::Toys.const_defined?(:VERSION)
|
343
|
-
add_gem_to_definition(builder, "toys")
|
344
|
-
toys_gems << "toys"
|
345
|
-
end
|
346
|
-
definition = builder.to_definition(lockfile_path, { gems: toys_gems })
|
347
|
-
::Bundler.instance_variable_set(:@definition, definition)
|
338
|
+
check_gemfile_gem_compatibility(builder, "toys-core")
|
339
|
+
check_gemfile_gem_compatibility(builder, "toys")
|
340
|
+
::Bundler.reset!
|
348
341
|
end
|
349
342
|
|
350
|
-
def
|
343
|
+
def check_gemfile_gem_compatibility(builder, name)
|
351
344
|
existing_dep = builder.dependencies.find { |dep| dep.name == name }
|
352
|
-
|
353
|
-
unless existing_dep.requirement.satisfied_by?(::Gem::Version.new(::Toys::Core::VERSION))
|
345
|
+
if existing_dep && !existing_dep.requirement.satisfied_by?(::Gem::Version.new(::Toys::Core::VERSION))
|
354
346
|
raise IncompatibleToysError,
|
355
347
|
"The bundle lists #{name} #{existing_dep.requirement} as a dependency, which is" \
|
356
|
-
" incompatible with the current version #{::Toys::Core::VERSION}."
|
348
|
+
" incompatible with the current toys version #{::Toys::Core::VERSION}."
|
357
349
|
end
|
358
|
-
builder.dependencies.delete(existing_dep)
|
359
|
-
true
|
360
350
|
end
|
361
351
|
|
362
|
-
def
|
363
|
-
|
364
|
-
|
352
|
+
def create_modified_gemfile(gemfile_path)
|
353
|
+
dir = ::File.dirname(gemfile_path)
|
354
|
+
modified_gemfile_path = loop do
|
355
|
+
timestamp = ::Time.now.strftime("%Y%m%d%H%M%S")
|
356
|
+
uniquifier = rand(3_656_158_440_062_976).to_s(36) # 10 digits in base 36
|
357
|
+
path = ::File.join(dir, ".toys-tmp-gemfile-#{timestamp}-#{uniquifier}")
|
358
|
+
break path unless ::File.exist?(path)
|
365
359
|
end
|
366
|
-
|
367
|
-
|
360
|
+
::File.open(modified_gemfile_path, "w") do |file|
|
361
|
+
modified_gemfile_content(gemfile_path).each do |line|
|
362
|
+
file.puts(line)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
lockfile_path = find_lockfile_path(gemfile_path)
|
366
|
+
modified_lockfile_path = find_lockfile_path(modified_gemfile_path)
|
367
|
+
if ::File.readable?(lockfile_path)
|
368
|
+
lockfile_content = ::File.read(lockfile_path)
|
369
|
+
::File.open(modified_lockfile_path, "w") { |file| file.write(lockfile_content) }
|
370
|
+
end
|
371
|
+
modified_gemfile_path
|
372
|
+
end
|
373
|
+
|
374
|
+
def modified_gemfile_content(gemfile_path)
|
375
|
+
is_running_toys = ::Toys.const_defined?(:VERSION)
|
376
|
+
content = [::File.read(gemfile_path)]
|
377
|
+
content << "has_toys_dep = dependencies.any? { |dep| dep.name == 'toys' }" unless is_running_toys
|
378
|
+
content << "dependencies.delete_if { |dep| dep.name == 'toys-core' || dep.name == 'toys' }"
|
379
|
+
repo_root = ::File.dirname(::File.dirname(::Toys::CORE_LIB_PATH)) if ::ENV["TOYS_DEV"]
|
380
|
+
path = repo_root ? ::File.join(repo_root, "toys-core") : nil
|
381
|
+
content << "gem 'toys-core', #{::Toys::Core::VERSION.inspect}, path: #{path.inspect}"
|
382
|
+
path = repo_root ? ::File.join(repo_root, "toys") : nil
|
383
|
+
guard = is_running_toys ? "" : " if has_toys_dep"
|
384
|
+
content << "gem 'toys', #{::Toys::Core::VERSION.inspect}, path: #{path.inspect}#{guard}"
|
385
|
+
content
|
386
|
+
end
|
387
|
+
|
388
|
+
def delete_modified_gemfile(modified_gemfile_path)
|
389
|
+
::File.delete(modified_gemfile_path) if ::File.exist?(modified_gemfile_path)
|
390
|
+
modified_lockfile_path = find_lockfile_path(modified_gemfile_path)
|
391
|
+
::File.delete(modified_lockfile_path) if ::File.exist?(modified_lockfile_path)
|
368
392
|
end
|
369
393
|
|
370
394
|
def restore_toys_libs
|
@@ -383,8 +407,7 @@ module Toys
|
|
383
407
|
when :error
|
384
408
|
false
|
385
409
|
else
|
386
|
-
terminal.confirm("Your bundle requires additional gems. Install? ",
|
387
|
-
default: @default_confirm)
|
410
|
+
terminal.confirm("Your bundle requires additional gems. Install? ", default: @default_confirm)
|
388
411
|
end
|
389
412
|
end
|
390
413
|
|
@@ -392,17 +415,19 @@ module Toys
|
|
392
415
|
gemfile_dir = ::File.dirname(gemfile_path)
|
393
416
|
unless permission_to_bundle?
|
394
417
|
raise BundleNotInstalledError,
|
395
|
-
"Your bundle is not installed. Consider running"
|
396
|
-
" `cd #{gemfile_dir} && bundle install`"
|
418
|
+
"Your bundle is not installed. Consider running `cd #{gemfile_dir} && bundle install`"
|
397
419
|
end
|
398
420
|
retries = retries.to_i
|
399
|
-
args =
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
421
|
+
args = ["--gemfile=#{gemfile_path}"]
|
422
|
+
args << "--retry=#{retries}" if retries.positive?
|
423
|
+
bundler_bin = ::Gem.bin_path("bundler", "bundle", ::Bundler::VERSION)
|
424
|
+
result = exec_util.exec_ruby([bundler_bin, "install"] + args)
|
425
|
+
if result.error?
|
404
426
|
terminal.puts("Failed to install. Trying update...")
|
405
|
-
|
427
|
+
result = exec_util.exec_ruby([bundler_bin, "update"] + args)
|
428
|
+
unless result.success?
|
429
|
+
raise ::Bundler::InstallError, "Failed to install or update bundle: #{gemfile_path}"
|
430
|
+
end
|
406
431
|
end
|
407
432
|
end
|
408
433
|
end
|