cli-kit 3.0.0 → 3.0.1

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 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