cli-kit 3.0.0 → 3.0.1

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
  SHA1:
3
- metadata.gz: 88690eeba1179ef8cb31ebd13b68d978e71f5c8a
4
- data.tar.gz: f63da3dc52f120f759241232fb64953deb0e0b3f
3
+ metadata.gz: c6c63b562d9e105af0a13a110ad973bbb56d40a4
4
+ data.tar.gz: f89728ea2fd0f05ed1c9b6f16dbad900c4bc5595
5
5
  SHA512:
6
- metadata.gz: abd4591db97ad44a9ebdf3645181ad90b443ad70d3849b3f247cf577cc394b04638646d14ab1cc18273d05a3a55cb1cdb3022ce9052792b025a7498c6d94a309
7
- data.tar.gz: 6c8571d319162adfeb0dd66c294fb225565c10e50fdcb70ab60dcd4bbd705131ec43916691c8ce160939348491ec453d5b1054a2504bc13594e76aecc20cdf01
6
+ metadata.gz: 7088f3a7a238311cb8aaad7e83f74611ef30f2cbe0db5bbdc393d6c7da7ca51e68d54884c73707ea9751644c9ebdec6156829f939f4cf65044b5a690b053450a
7
+ data.tar.gz: 19fbec7a9eaee07f188c69f1d776404227f554dc38e44ff08bd2160026a45aac0e843526703ba0a1312bbcb853d2496bd5b5bf2b41d232f1ac033353b05b83c3
@@ -5,6 +5,10 @@ AllCops:
5
5
  Exclude: [ 'gen/template/**/*' ]
6
6
  TargetRubyVersion: 2.3
7
7
 
8
+ Style/ClassAndModuleChildren:
9
+ Exclude:
10
+ - lib/cli/kit/support/test_helper.rb
11
+
8
12
  Style/FrozenStringLiteralComment:
9
13
  Enabled: false
10
14
 
@@ -1,5 +1,14 @@
1
1
  sudo: false
2
2
  language: ruby
3
+ cache: bundler
3
4
  rvm:
4
5
  - 2.3.3
5
- before_install: gem install bundler -v 1.15.0
6
+ - 2.5.0
7
+ os:
8
+ - linux
9
+ - osx
10
+ script:
11
+ - bin/testunit
12
+ - git clone https://github.com/Shopify/cli-ui.git
13
+ - DEPS=vendor bin/test_gen
14
+ - DEPS=bundler bin/test_gen
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cli-kit (3.0.0)
4
+ cli-kit (3.0.1)
5
5
  cli-ui (>= 1.1.0)
6
6
 
7
7
  GEM
@@ -11,7 +11,7 @@ GEM
11
11
  ast (2.3.0)
12
12
  builder (3.2.3)
13
13
  byebug (9.0.6)
14
- cli-ui (1.1.0)
14
+ cli-ui (1.1.1)
15
15
  metaclass (0.0.4)
16
16
  method_source (0.8.2)
17
17
  minitest (5.10.2)
@@ -54,4 +54,4 @@ DEPENDENCIES
54
54
  rubocop
55
55
 
56
56
  BUNDLED WITH
57
- 1.16.0
57
+ 1.16.1
data/README.md CHANGED
@@ -11,8 +11,106 @@
11
11
  to build a number of internal developer tools, along with
12
12
  [cli-ui](https://github.com/shopify/cli-ui).
13
13
 
14
- ## Example Usage
14
+ ## Getting Started
15
15
 
16
- You can see example usage [here](https://github.com/Shopify/cli-kit-example). We may one day build
17
- an application generator, as this framework paradigm requires a small amount of boilerplate. For
18
- now, the best way to get going is to use this example application as a starting point.
16
+ To begin creating your first `cli-kit` application, run:
17
+ ```bash
18
+ gem install cli-kit
19
+ cli-kit new myproject
20
+ ```
21
+
22
+ Where `myproject` is the name of the application you wish to create. Then, you will be prompted to
23
+ select how the project consumes `cli-kit` and `cli-ui`. The available options are:
24
+ - Vendor (faster execution, more difficult to update dependencies)
25
+ - Bundler (slower execution, easier dependency management)
26
+
27
+ You're now ready to write your very first `cli-kit` application!
28
+
29
+ ## How do `cli-kit` Applications Work?
30
+
31
+ The executable for your `cli-kit` app is stored in the "exe" directory. To execute the app, simply
32
+ run:
33
+ ```bash
34
+ ./exe/myproject
35
+ ```
36
+
37
+ ### Folder Structure
38
+ * `/exe/` - Location of the executables for your application.
39
+ * `/lib/` - Location of the resources for your app (modules, classes, helpers, etc).
40
+ * `myproject.rb` - This file is the starting point for where to look for all other files. It
41
+ configures autoload and autocall for the app.
42
+ * `myproject/` - Stores the various commands/entry points.
43
+ * `entry_point.rb` - This is the file that is first called from the executable. It handles
44
+ loading and commands.
45
+ * `commands.rb` - Registers the various commands that your application is able to handle.
46
+ * `commands/` - Stores Ruby files for each command (help, new, add, etc).
47
+
48
+ ## Adding a New Command to your App
49
+
50
+ ### Registering the Command
51
+
52
+ Let's say that you'd like your program to be able to handle a specific task, and you'd like to
53
+ _register_ a new handler for the command for that task, like `myproject add` to add 2 numbers, like
54
+ in a calculator app.
55
+ To do this, open `/lib/myproject/commands.rb`. Then, add a new line into the module, like this:
56
+ ```ruby
57
+ register :Add, 'add', 'myproject/commands/add'
58
+ ```
59
+
60
+ The format for this is `register :<CommandClass>, '<command-at-cli>', '<path/to/command.rb>'`
61
+
62
+ ### Creating the Command Action
63
+
64
+ The action for a specific command is stored in its own Ruby file, in the `/lib/myproject/commands/`
65
+ directory. Here is an example of the `add` command in our previous to-do app example:
66
+ ```ruby
67
+ require 'myproject'
68
+
69
+ module Myproject
70
+ module Commands
71
+ class Add < Myproject::Command
72
+ def call(args, _name)
73
+ # command action goes here
74
+ end
75
+
76
+ def self.help
77
+ # help or instructions go here
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ ```
84
+
85
+ The `call(args, _name)` method is what actually runs when the `myproject add` command is executed.
86
+
87
+ - **Note:** The `args` parameter represents all the arguments the user has specified.
88
+
89
+ Let's say that you are trying to compute the sum of 2 numbers that the user has specified as
90
+ arguments. For example:
91
+ ```ruby
92
+ def call(args, _name)
93
+ sum = args.map(&:to_i).inject(&:+)
94
+ puts sum
95
+ end
96
+ ```
97
+
98
+ ### Getting Help
99
+
100
+ Above, you'll notice that we also have a `self.help` method. This method is what runs when the user
101
+ has incorrectly used the command, or has requested help. For example:
102
+ ```ruby
103
+ def self.help
104
+ "Print the sum of 2 numbers.\nUsage: {{command:#{Myproject::TOOL_NAME} add}} 5 7"
105
+ end
106
+ ```
107
+
108
+ ## User Interfaces
109
+
110
+ `cli-kit` also features `cli-ui`, another gem from us here at Shopify, which allows for the use of
111
+ powerful command-line user interface elements. For more details on how to use `cli-ui`, visit the
112
+ [`cli-ui`](https://github.com/Shopify/cli-ui) repo.
113
+
114
+ ## Examples
115
+
116
+ - [A Simple To-Do App](https://github.com/Shopify/cli-kit-example)
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Make sure we're in the cli kit directory
4
+ CURR_DIR=$(dirname "$0")
5
+ cd $CURR_DIR
6
+ cd ../
7
+
8
+ # Clean up
9
+ function finish {
10
+ cd $CURR_DIR
11
+ cd ../../
12
+ rm -rf myapp
13
+ }
14
+ trap finish EXIT
15
+
16
+ # Generate app and move up a level to be at the same level as cli-kit
17
+ bundle exec ruby exe/cli-kit new myapp
18
+ mv myapp ../
19
+ cd ../myapp
20
+
21
+ # Test
22
+ bundle install
23
+ bin/testunit
24
+
25
+ if [[ $DEPS == 'vendor' ]]; then
26
+ git clone https://github.com/Shopify/cli-ui.git ../cli-ui
27
+ bin/update-deps
28
+ fi
@@ -9,7 +9,7 @@ module Gen
9
9
 
10
10
  Gen::Commands::Registry.resolved_commands.each do |name, klass|
11
11
  puts CLI::UI.fmt("{{command:#{Gen::TOOL_NAME} #{name}}}")
12
- if help = klass.help
12
+ if klass.respond_to?(:help) && help = klass.help
13
13
  puts CLI::UI.fmt(help)
14
14
  end
15
15
  puts ""
@@ -27,7 +27,6 @@ module Gen
27
27
  private_constant :VENDOR_TRANSLATIONS
28
28
 
29
29
  BUNDLER_TRANSLATIONS = {
30
- 'bin' => false,
31
30
  'bin/update-deps' => false,
32
31
  'exe/__app__-gems' => 'exe/__app__',
33
32
  'exe/__app__-vendor' => false,
@@ -59,6 +58,9 @@ module Gen
59
58
  private
60
59
 
61
60
  def ask_vendor?
61
+ return 'vendor' if ENV['DEPS'] == 'vendor'
62
+ return 'bundler' if ENV['DEPS'] == 'bundler'
63
+
62
64
  vendor = nil
63
65
  CLI::UI::Frame.open('Configuration') do
64
66
  q = 'How would you like the application to consume {{command:cli-kit}} and {{command:cli-ui}}?'
@@ -2,3 +2,9 @@ source 'https://rubygems.org'
2
2
 
3
3
  gem 'cli-kit', '~> __cli-kit-version__'
4
4
  gem 'cli-ui', '~> __cli-ui-version__'
5
+
6
+ group :test do
7
+ gem 'mocha', require: false
8
+ gem 'minitest', '>= 5.0.0', require: false
9
+ gem 'minitest-reporters', require: false
10
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+
6
+ root = File.expand_path('../..', __FILE__)
7
+ TEST_ROOT = root + '/test'
8
+
9
+ $LOAD_PATH.unshift(TEST_ROOT)
10
+
11
+ def test_files
12
+ Dir.glob(TEST_ROOT + "/**/*_test.rb")
13
+ end
14
+
15
+ if ARGV.empty?
16
+ test_files.each { |f| require(f) }
17
+ exit 0
18
+ end
19
+
20
+ # A list of files is presumed to be specified
21
+ ARGV.each do |a|
22
+ require a.sub(%r{^test/}, '')
23
+ end
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby --disable-gems
1
+ #!/usr/bin/env ruby
2
2
 
3
3
  $LOAD_PATH.unshift(File.expand_path("../../vendor/deps/cli-ui/lib", __FILE__))
4
4
  require 'open3'
@@ -39,6 +39,7 @@ deps.each do |dep|
39
39
  bail(
40
40
  "dependency is not checked out: {{yellow:#{dep}}}.\n" \
41
41
  " This repo {{bold_blue:(github.com/shopify/#{dep})}} must be cloned at {{bold_blue:#{path}}} for this script to succeed.\n" \
42
+ " Currently, SOURCE_ROOT is set to {{bold_blue:#{source_path}}}.\n" \
42
43
  " Alternatively, you can set {{bold_blue:SOURCE_ROOT}} to a directory containing {{yellow:#{dep}}}.\n" \
43
44
  " {{bold_blue:SOURCE_ROOT}} defaults to {{bold_blue:../}}."
44
45
  )
@@ -0,0 +1,15 @@
1
+ require 'test_helper'
2
+
3
+ module __App__
4
+ class ExampleTest < MiniTest::Test
5
+ include CLI::Kit::Support::TestHelper
6
+
7
+ def test_example
8
+ CLI::Kit::System.fake("ls -al", stdout: "a\nb", success: true)
9
+ assert_all_commands_run do
10
+ out, = CLI::Kit::System.capture2('ls', '-al')
11
+ assert_equal ['a', 'b'], out.split("\n")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ begin
2
+ addpath = lambda do |p|
3
+ path = File.expand_path("../../#{p}", __FILE__)
4
+ $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
5
+ end
6
+ addpath.call("lib")
7
+ end
8
+
9
+ require 'cli/kit'
10
+
11
+ require 'fileutils'
12
+ require 'tmpdir'
13
+ require 'tempfile'
14
+
15
+ require 'rubygems'
16
+ require 'bundler/setup'
17
+
18
+ CLI::UI::StdoutRouter.enable
19
+
20
+ require 'minitest/autorun'
21
+ require 'mocha/mini_test'
@@ -12,6 +12,7 @@ module CLI
12
12
  autoload :Ini, 'cli/kit/ini'
13
13
  autoload :Levenshtein, 'cli/kit/levenshtein'
14
14
  autoload :Resolver, 'cli/kit/resolver'
15
+ autoload :Support, 'cli/kit/support'
15
16
  autoload :System, 'cli/kit/system'
16
17
 
17
18
  EXIT_FAILURE_BUT_NOT_BUG = 30
@@ -68,7 +68,7 @@ module CLI
68
68
 
69
69
  def resolve_global_command(name)
70
70
  klass = resolve_class(commands.fetch(name, nil))
71
- return nil unless klass
71
+ return nil unless klass && klass.defined?
72
72
  [klass, name]
73
73
  rescue NameError
74
74
  nil
@@ -43,6 +43,27 @@ module CLI
43
43
  write_config
44
44
  end
45
45
 
46
+ # Unsets a config value in the config file
47
+ #
48
+ # #### Parameters
49
+ # `section` : the section of the config you are deleting
50
+ # `name` : the name of the config you are deleting
51
+ #
52
+ # #### Example Usage
53
+ # `config.unset('section', 'name.of.config')`
54
+ #
55
+ def unset(section, name)
56
+ set(section, name, nil)
57
+ end
58
+
59
+ # Gets the hash for the entire section
60
+ #
61
+ # #### Parameters
62
+ # `section` : the section of the config you are getting
63
+ #
64
+ # #### Example Usage
65
+ # `config.get_section('section')`
66
+ #
46
67
  def get_section(section)
47
68
  (all_configs["[#{section}]"] || {}).dup
48
69
  end
@@ -20,43 +20,37 @@ module CLI
20
20
  handle_abort(&block)
21
21
  end
22
22
 
23
- private
24
-
25
- def install!
26
- at_exit { handle_final_exception(@exception || $ERROR_INFO) }
23
+ def handle_exception(error)
24
+ if notify_with = exception_for_submission(error)
25
+ logs = begin
26
+ File.read(@log_file)
27
+ rescue => e
28
+ "(#{e.class}: #{e.message})"
29
+ end
30
+ exception_reporter.report(notify_with, logs)
31
+ end
27
32
  end
28
33
 
29
- def handle_abort
30
- yield
31
- CLI::Kit::EXIT_SUCCESS
32
- rescue CLI::Kit::GenericAbort => e
33
- is_bug = e.is_a?(CLI::Kit::Bug) || e.is_a?(CLI::Kit::BugSilent)
34
- is_silent = e.is_a?(CLI::Kit::AbortSilent) || e.is_a?(CLI::Kit::BugSilent)
35
-
36
- print_error_message(e) unless is_silent
37
- (@exception = e) if is_bug
38
-
39
- CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
40
- rescue Interrupt
41
- $stderr.puts(format_error_message("Interrupt"))
42
- return CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
43
- end
34
+ # maybe we can get rid of this.
35
+ attr_writer :exception
44
36
 
45
- def handle_final_exception(error)
46
- notify_with = nil
37
+ private
47
38
 
39
+ def exception_for_submission(error)
48
40
  case error
49
41
  when nil # normal, non-error termination
42
+ nil
50
43
  when Interrupt # ctrl-c
44
+ nil
51
45
  when CLI::Kit::Abort, CLI::Kit::AbortSilent # Not a bug
46
+ nil
52
47
  when SignalException
53
48
  skip = %w(SIGTERM SIGHUP SIGINT)
54
- unless skip.include?(error.message)
55
- notify_with = error
56
- end
49
+ skip.include?(error.message) ? nil : error
57
50
  when SystemExit # "exit N" called
58
51
  case error.status
59
52
  when CLI::Kit::EXIT_SUCCESS # submit nothing if it was `exit 0`
53
+ nil
60
54
  when CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
61
55
  # if it was `exit 30`, translate the exit code to 1, and submit nothing.
62
56
  # 30 is used to signal normal failures that are not indicative of bugs.
@@ -65,20 +59,31 @@ module CLI
65
59
  else
66
60
  # A weird termination status happened. `error.exception "message"` will maintain backtrace
67
61
  # but allow us to set a message
68
- notify_with = error.exception "abnormal termination status: #{error.status}"
62
+ error.exception("abnormal termination status: #{error.status}")
69
63
  end
70
64
  else
71
- notify_with = error
65
+ error
72
66
  end
67
+ end
73
68
 
74
- if notify_with
75
- logs = begin
76
- File.read(@log_file)
77
- rescue => e
78
- "(#{e.class}: #{e.message})"
79
- end
80
- exception_reporter.report(notify_with, logs)
81
- end
69
+ def install!
70
+ at_exit { handle_exception(@exception || $ERROR_INFO) }
71
+ end
72
+
73
+ def handle_abort
74
+ yield
75
+ CLI::Kit::EXIT_SUCCESS
76
+ rescue CLI::Kit::GenericAbort => e
77
+ is_bug = e.is_a?(CLI::Kit::Bug) || e.is_a?(CLI::Kit::BugSilent)
78
+ is_silent = e.is_a?(CLI::Kit::AbortSilent) || e.is_a?(CLI::Kit::BugSilent)
79
+
80
+ print_error_message(e) unless is_silent
81
+ (@exception = e) if is_bug
82
+
83
+ CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
84
+ rescue Interrupt
85
+ $stderr.puts(format_error_message("Interrupt"))
86
+ return CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
82
87
  end
83
88
 
84
89
  def exception_reporter
@@ -28,10 +28,14 @@ module CLI
28
28
  end
29
29
 
30
30
  def twrap(signal, handler)
31
- prev_handler = trap(signal, handler)
32
- yield
33
- ensure
34
- trap(signal, prev_handler)
31
+ return yield unless Signal.list.key?(signal)
32
+
33
+ begin
34
+ prev_handler = trap(signal, handler)
35
+ yield
36
+ ensure
37
+ trap(signal, prev_handler)
38
+ end
35
39
  end
36
40
 
37
41
  def quit_handler(_sig)
@@ -46,21 +46,26 @@ module CLI
46
46
  @ini
47
47
  end
48
48
 
49
+ def git_format
50
+ to_ini(@ini, git_format: true).flatten.join("\n")
51
+ end
52
+
49
53
  def to_s
50
54
  to_ini(@ini).flatten.join("\n")
51
55
  end
52
56
 
53
57
  private
54
58
 
55
- def to_ini(h)
59
+ def to_ini(h, git_format: false)
60
+ optional_tab = git_format ? "\t" : ""
56
61
  str = []
57
62
  h.each do |k, v|
58
63
  if section_designator?(k)
59
- str << "" unless str.empty?
64
+ str << "" unless str.empty? || git_format
60
65
  str << k
61
- str << to_ini(v)
66
+ str << to_ini(v, git_format: git_format)
62
67
  else
63
- str << "#{k} = #{v}"
68
+ str << "#{optional_tab}#{k} = #{v}"
64
69
  end
65
70
  end
66
71
  str
@@ -0,0 +1,9 @@
1
+ require 'cli/kit'
2
+
3
+ module CLI
4
+ module Kit
5
+ module Support
6
+ autoload :TestHelper, 'cli/kit/support/test_helper'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,226 @@
1
+ module CLI
2
+ module Kit
3
+ module Support
4
+ module TestHelper
5
+ def setup
6
+ super
7
+ CLI::Kit::System.reset!
8
+ end
9
+
10
+ def assert_all_commands_run(should_raise: true)
11
+ errors = CLI::Kit::System.error_message
12
+ CLI::Kit::System.reset!
13
+ assert false, errors if should_raise && !errors.nil?
14
+ errors
15
+ end
16
+
17
+ def teardown
18
+ super
19
+ assert_all_commands_run
20
+ end
21
+
22
+ class FakeSuccess
23
+ def initialize(success)
24
+ @success = success
25
+ end
26
+
27
+ def success?
28
+ @success
29
+ end
30
+ end
31
+
32
+ module ::CLI
33
+ module Kit
34
+ module System
35
+ class << self
36
+ alias_method :original_system, :system
37
+ def system(*a, sudo: false, env: {}, **kwargs)
38
+ expected_command = expected_command(*a, sudo: sudo, env: env)
39
+
40
+ # In the case of an unexpected command, expected_command will be nil
41
+ return FakeSuccess.new(false) if expected_command.nil?
42
+
43
+ # Otherwise handle the command
44
+ if expected_command[:allow]
45
+ original_system(*a, sudo: sudo, env: env, **kwargs)
46
+ else
47
+ FakeSuccess.new(expected_command[:success])
48
+ end
49
+ end
50
+
51
+ alias_method :original_capture2, :capture2
52
+ def capture2(*a, sudo: false, env: {}, **kwargs)
53
+ expected_command = expected_command(*a, sudo: sudo, env: env)
54
+
55
+ # In the case of an unexpected command, expected_command will be nil
56
+ return [nil, FakeSuccess.new(false)] if expected_command.nil?
57
+
58
+ # Otherwise handle the command
59
+ if expected_command[:allow]
60
+ original_capture2(*a, sudo: sudo, env: env, **kwargs)
61
+ else
62
+ [
63
+ expected_command[:stdout],
64
+ FakeSuccess.new(expected_command[:success]),
65
+ ]
66
+ end
67
+ end
68
+
69
+ alias_method :original_capture2e, :capture2e
70
+ def capture2e(*a, sudo: false, env: {}, **kwargs)
71
+ expected_command = expected_command(*a, sudo: sudo, env: env)
72
+
73
+ # In the case of an unexpected command, expected_command will be nil
74
+ return [nil, FakeSuccess.new(false)] if expected_command.nil?
75
+
76
+ # Otherwise handle the command
77
+ if expected_command[:allow]
78
+ original_capture2ecapture2e(*a, sudo: sudo, env: env, **kwargs)
79
+ else
80
+ [
81
+ expected_command[:stdout],
82
+ FakeSuccess.new(expected_command[:success]),
83
+ ]
84
+ end
85
+ end
86
+
87
+ alias_method :original_capture3, :capture3
88
+ def capture3(*a, sudo: false, env: {}, **kwargs)
89
+ expected_command = expected_command(*a, sudo: sudo, env: env)
90
+
91
+ # In the case of an unexpected command, expected_command will be nil
92
+ return [nil, nil, FakeSuccess.new(false)] if expected_command.nil?
93
+
94
+ # Otherwise handle the command
95
+ if expected_command[:allow]
96
+ original_capture3(*a, sudo: sudo, env: env, **kwargs)
97
+ else
98
+ [
99
+ expected_command[:stdout],
100
+ expected_command[:stderr],
101
+ FakeSuccess.new(expected_command[:success]),
102
+ ]
103
+ end
104
+ end
105
+
106
+ # Sets up an expectation for a command and stubs out the call (unless allow is true)
107
+ #
108
+ # #### Parameters
109
+ # `*a` : the command, represented as a splat
110
+ # `stdout` : stdout to stub the command with (defaults to empty string)
111
+ # `stderr` : stderr to stub the command with (defaults to empty string)
112
+ # `allow` : allow determines if the command will be actually run, or stubbed. Defaults to nil (stub)
113
+ # `success` : success status to stub the command with (Defaults to nil)
114
+ # `sudo` : expectation of sudo being set or not (defaults to false)
115
+ # `env` : expectation of env being set or not (defaults to {})
116
+ #
117
+ # Note: Must set allow or success
118
+ #
119
+ def fake(*a, stdout: "", stderr: "", allow: nil, success: nil, sudo: false, env: {})
120
+ raise ArgumentError, "success or allow must be set" if success.nil? && allow.nil?
121
+
122
+ @delegate_open3 ||= {}
123
+ @delegate_open3[a.join(' ')] = {
124
+ expected: {
125
+ sudo: sudo,
126
+ env: env,
127
+ },
128
+ actual: {
129
+ sudo: nil,
130
+ env: nil,
131
+ },
132
+ stdout: stdout,
133
+ stderr: stderr,
134
+ allow: allow,
135
+ success: success,
136
+ run: false,
137
+ }
138
+ end
139
+
140
+ # Resets the faked commands
141
+ #
142
+ def reset!
143
+ @delegate_open3 = {}
144
+ end
145
+
146
+ # Returns the errors associated to a test run
147
+ #
148
+ # #### Returns
149
+ # `errors` (String) a string representing errors found on this run, nil if none
150
+ def error_message
151
+ errors = {
152
+ unexpected: [],
153
+ not_run: [],
154
+ other: {},
155
+ }
156
+
157
+ @delegate_open3.each do |cmd, opts|
158
+ if opts[:unexpected]
159
+ errors[:unexpected] << cmd
160
+ elsif opts[:run]
161
+ error = []
162
+
163
+ if opts[:expected][:sudo] != opts[:actual][:sudo]
164
+ error << "- sudo was supposed to be #{opts[:expected][:sudo]} but was #{opts[:actual][:sudo]}"
165
+ end
166
+
167
+ if opts[:expected][:env] != opts[:actual][:env]
168
+ error << "- env was supposed to be #{opts[:expected][:env]} but was #{opts[:actual][:env]}"
169
+ end
170
+
171
+ errors[:other][cmd] = error.join("\n") unless error.empty?
172
+ else
173
+ errors[:not_run] << cmd
174
+ end
175
+ end
176
+
177
+ final_error = []
178
+
179
+ unless errors[:unexpected].empty?
180
+ final_error << CLI::UI.fmt(<<~EOF)
181
+ {{bold:Unexpected command invocations:}}
182
+ {{command:#{errors[:unexpected].join("\n")}}}
183
+ EOF
184
+ end
185
+
186
+ unless errors[:not_run].empty?
187
+ final_error << CLI::UI.fmt(<<~EOF)
188
+ {{bold:Expected commands were not run:}}
189
+ {{command:#{errors[:not_run].join("\n")}}}
190
+ EOF
191
+ end
192
+
193
+ unless errors[:other].empty?
194
+ final_error << CLI::UI.fmt(<<~EOF)
195
+ {{bold:Commands were not run as expected:}}
196
+ #{errors[:other].map { |cmd, msg| "{{command:#{cmd}}}\n#{msg}" }.join("\n\n")}
197
+ EOF
198
+ end
199
+
200
+ return nil if final_error.empty?
201
+ "\n" + final_error.join("\n") # Initial new line for formatting reasons
202
+ end
203
+
204
+ private
205
+
206
+ def expected_command(*a, sudo: raise, env: raise)
207
+ expected_cmd = @delegate_open3[a.join(' ')]
208
+
209
+ if expected_cmd.nil?
210
+ @delegate_open3[a.join(' ')] = { unexpected: true }
211
+ return nil
212
+ end
213
+
214
+ expected_cmd[:run] = true
215
+ expected_cmd[:actual][:sudo] = sudo
216
+ expected_cmd[:actual][:env] = env
217
+ expected_cmd
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -1,5 +1,5 @@
1
1
  module CLI
2
2
  module Kit
3
- VERSION = "3.0.0"
3
+ VERSION = "3.0.1"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cli-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2018-02-27 00:00:00.000000000 Z
13
+ date: 2018-05-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: cli-ui
@@ -85,7 +85,9 @@ files:
85
85
  - Gemfile.lock
86
86
  - LICENSE.txt
87
87
  - README.md
88
+ - Rakefile
88
89
  - bin/console
90
+ - bin/test_gen
89
91
  - bin/testunit
90
92
  - cli-kit.gemspec
91
93
  - dev.yml
@@ -99,6 +101,7 @@ files:
99
101
  - gen/template/.gitignore
100
102
  - gen/template/Gemfile
101
103
  - gen/template/README.md
104
+ - gen/template/bin/testunit
102
105
  - gen/template/bin/update-deps
103
106
  - gen/template/dev-gems.yml
104
107
  - gen/template/dev-vendor.yml
@@ -109,6 +112,8 @@ files:
109
112
  - gen/template/lib/__app__/commands/example.rb
110
113
  - gen/template/lib/__app__/commands/help.rb
111
114
  - gen/template/lib/__app__/entry_point.rb
115
+ - gen/template/test/example_test.rb
116
+ - gen/template/test/test_helper.rb
112
117
  - lib/cli/kit.rb
113
118
  - lib/cli/kit/autocall.rb
114
119
  - lib/cli/kit/base_command.rb
@@ -120,6 +125,8 @@ files:
120
125
  - lib/cli/kit/levenshtein.rb
121
126
  - lib/cli/kit/resolver.rb
122
127
  - lib/cli/kit/ruby_backports/enumerable.rb
128
+ - lib/cli/kit/support.rb
129
+ - lib/cli/kit/support/test_helper.rb
123
130
  - lib/cli/kit/system.rb
124
131
  - lib/cli/kit/version.rb
125
132
  homepage: https://github.com/shopify/cli-kit