gistribute 0.2 → 0.3
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/.github/workflows/blocking-issues.yml +14 -0
- data/.github/workflows/lint.yml +17 -0
- data/.github/workflows/test.yml +21 -0
- data/.gitignore +21 -0
- data/.rubocop.yml +94 -0
- data/Gemfile +4 -0
- data/README.md +32 -23
- data/Rakefile +2 -0
- data/VERSION +1 -1
- data/bin/gistribute +3 -83
- data/gistribute.gemspec +14 -7
- data/lib/cli/auth.rb +64 -0
- data/lib/cli/install.rb +86 -0
- data/lib/cli/upload.rb +54 -0
- data/lib/cli.rb +117 -0
- data/lib/gistribute.rb +33 -0
- data/spec/.rubocop.yml +46 -0
- data/spec/gistribute/cli_auth_spec.rb +133 -0
- data/spec/gistribute/cli_install_spec.rb +193 -0
- data/spec/gistribute/cli_spec.rb +31 -0
- data/spec/gistribute/cli_upload_spec.rb +161 -0
- data/spec/gistribute_spec.rb +15 -27
- data/spec/spec_helper.rb +40 -0
- data/spec/support/cli.rb +27 -0
- data/spec/support/constants.rb +4 -0
- metadata +123 -6
data/lib/cli.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cli/auth"
|
4
|
+
require "cli/install"
|
5
|
+
require "cli/upload"
|
6
|
+
|
7
|
+
module Gistribute
|
8
|
+
class CLI
|
9
|
+
def initialize
|
10
|
+
@options = OptimistXL.options do
|
11
|
+
version "gistribute #{File.read(File.expand_path('../VERSION', __dir__)).strip}"
|
12
|
+
|
13
|
+
banner <<~BANNER
|
14
|
+
#{version}
|
15
|
+
|
16
|
+
Usage:
|
17
|
+
gistribute SUBCOMMAND [OPTION]... INPUT
|
18
|
+
|
19
|
+
Try `gistribute SUBCOMMAND -h` for more info. Available subcommands:
|
20
|
+
* login: log into your GitHub account
|
21
|
+
* logout: log out of your GitHub account
|
22
|
+
* install: download and install files from a gistribution
|
23
|
+
* upload: upload a new gistribution
|
24
|
+
|
25
|
+
Options:
|
26
|
+
BANNER
|
27
|
+
|
28
|
+
opt :version, "display version number"
|
29
|
+
opt :help, "display a help message"
|
30
|
+
|
31
|
+
# Sub-commands can't access the version from this scope for whatever reason
|
32
|
+
v = version
|
33
|
+
|
34
|
+
subcmd :login, "log into your GitHub account"
|
35
|
+
subcmd :logout, "log out of your GitHub account"
|
36
|
+
|
37
|
+
subcmd :install, "install from a gistribution" do
|
38
|
+
banner <<~BANNER
|
39
|
+
#{v}
|
40
|
+
|
41
|
+
Usage:
|
42
|
+
gistribute install [OPTION]... URL_OR_ID
|
43
|
+
|
44
|
+
Options:
|
45
|
+
BANNER
|
46
|
+
|
47
|
+
opt :yes, "install files without prompting"
|
48
|
+
opt :force, "overwrite existing files without prompting"
|
49
|
+
end
|
50
|
+
|
51
|
+
subcmd :upload, "upload a gistribution" do
|
52
|
+
banner <<~BANNER
|
53
|
+
#{v}
|
54
|
+
|
55
|
+
Usage:
|
56
|
+
gistribute upload [OPTION]... FILE...
|
57
|
+
gistribute upload [OPTION]... DIRECTORY
|
58
|
+
|
59
|
+
Options:
|
60
|
+
BANNER
|
61
|
+
|
62
|
+
opt :description, "description for the Gist", type: :string
|
63
|
+
opt :private, "use a private Gist"
|
64
|
+
opt :yes, "upload files without prompting"
|
65
|
+
end
|
66
|
+
|
67
|
+
educate_on_error
|
68
|
+
end
|
69
|
+
|
70
|
+
@subcommand, @global_options, @subcommand_options =
|
71
|
+
@options.subcommand, @options.global_options, @options.subcommand_options
|
72
|
+
|
73
|
+
authenticate unless @subcommand == "logout"
|
74
|
+
|
75
|
+
case @subcommand
|
76
|
+
when "install"
|
77
|
+
@gist_input = ARGV.first
|
78
|
+
when "upload"
|
79
|
+
@files = ARGV.dup
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def run
|
84
|
+
case @subcommand
|
85
|
+
when "login"
|
86
|
+
# Do nothing, #authenticate is run from the constructor
|
87
|
+
when "logout"
|
88
|
+
FileUtils.rm_rf CONFIG_FILE
|
89
|
+
puts "Logged out.".green
|
90
|
+
else
|
91
|
+
if ARGV.empty?
|
92
|
+
OptimistXL.educate
|
93
|
+
end
|
94
|
+
|
95
|
+
eval @subcommand
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def confirm?(prompt)
|
100
|
+
print prompt
|
101
|
+
input = $stdin.gets.strip.downcase
|
102
|
+
|
103
|
+
input.start_with?("y") || input.empty?
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_input(prompt)
|
107
|
+
print prompt
|
108
|
+
$stdin.gets.strip
|
109
|
+
end
|
110
|
+
|
111
|
+
# Prints an error message and exits the program.
|
112
|
+
def panic!(message)
|
113
|
+
$stderr.puts "#{'Error'.red}: #{message}"
|
114
|
+
exit 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/gistribute.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "colorize"
|
4
|
+
require "fileutils"
|
5
|
+
require "json"
|
6
|
+
require "launchy"
|
7
|
+
require "octokit"
|
8
|
+
require "optimist_xl"
|
9
|
+
|
10
|
+
require "cli"
|
11
|
+
|
12
|
+
CLIENT_ID = "3f37dc8255e5ab891c3d"
|
13
|
+
CONFIG_FILE = "#{Dir.home}/.config/gistribute".freeze
|
14
|
+
|
15
|
+
module Gistribute
|
16
|
+
class << self
|
17
|
+
# The user may enter either the full URL or just the ID, this function
|
18
|
+
# will parse it out of the input.
|
19
|
+
def parse_id(str)
|
20
|
+
str[%r{(^|/)([[:xdigit:]]+)}, 2]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Encodes a file path for use in the Gist filename
|
24
|
+
def encode(filename)
|
25
|
+
filename.sub(/^#{Dir.home}/, "~").gsub("/", "|")
|
26
|
+
end
|
27
|
+
|
28
|
+
# Decodes the filename from the Gist into a usable path
|
29
|
+
def decode(filename)
|
30
|
+
filename.gsub(/[~|]/, "|" => "/", "~" => Dir.home)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/.rubocop.yml
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
inherit_from: ../.rubocop.yml
|
2
|
+
|
3
|
+
RSpec/Focus:
|
4
|
+
Severity: warning
|
5
|
+
|
6
|
+
Style/SignalException:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
# Lots of spacing for alignment in the tests
|
10
|
+
Layout/SpaceInsideArrayLiteralBrackets:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Lint/AmbiguousBlockAssociation:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
################################################################################
|
17
|
+
|
18
|
+
RSpec/ContextWording:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
RSpec/EmptyExampleGroup:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
RSpec/ExampleLength:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
RSpec/InstanceVariable:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
RSpec/MultipleExpectations:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
RSpec/NestedGroups:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
RSpec/NoExpectationExample:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
RSpec/ScatteredLet:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
RSpec/SortMetadata:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
RSpec/VerifiedDoubles:
|
46
|
+
Enabled: false
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
DEVICE_RES = {
|
6
|
+
"device_code" => "testdevicecode",
|
7
|
+
"expires_in" => "899",
|
8
|
+
"interval" => "1",
|
9
|
+
"user_code" => "1337-6969",
|
10
|
+
"verification_uri" => "https://github.com/login/device"
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
PENDING_RES = {
|
14
|
+
"error" => "authorization_pending",
|
15
|
+
"error_description" => "The authorization request is still pending.",
|
16
|
+
"error_uri" => "https://docs.github.com/developers/apps/authorizing-oauth-apps#error-codes-for-the-device-flow"
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
EXPIRED_RES = {
|
20
|
+
"error" => "expired_token",
|
21
|
+
"error_description" => "The `device code` has expired.",
|
22
|
+
"error_uri" => "https://docs.github.com/developers/apps/authorizing-oauth-apps#error-codes-for-the-device-flow"
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
SUCCESS_RES = {
|
26
|
+
"access_token" => OAUTH_TOKEN,
|
27
|
+
"scope" => "gist",
|
28
|
+
"token_type" => "bearer"
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
def mock_oauth_response(res)
|
32
|
+
allow(Net::HTTP).to receive(:post_form)
|
33
|
+
.with(URI("https://github.com/login/oauth/access_token"), anything)
|
34
|
+
.and_return(:res2)
|
35
|
+
allow(URI).to receive(:decode_www_form)
|
36
|
+
.with(:res2)
|
37
|
+
.and_return(res)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Hack to get the `:resX` symbols to fall through into #decode_www_form
|
41
|
+
class Symbol
|
42
|
+
def body
|
43
|
+
itself
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe Gistribute::CLI do
|
48
|
+
before do
|
49
|
+
allow(Launchy).to receive(:open)
|
50
|
+
allow(File).to receive(:write)
|
51
|
+
suppress_stdout
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:http_ok) { Net::HTTPOK.new("1.1", "200", "OK") }
|
55
|
+
|
56
|
+
describe "#authenticate" do
|
57
|
+
context "when there is no access key saved" do
|
58
|
+
before do
|
59
|
+
allow(File).to receive(:exist?).and_return(false)
|
60
|
+
|
61
|
+
allow(Net::HTTP).to receive(:post_form)
|
62
|
+
.with(URI("https://github.com/login/device/code"), anything)
|
63
|
+
.and_return(:res1)
|
64
|
+
allow(URI).to receive(:decode_www_form)
|
65
|
+
.with(:res1)
|
66
|
+
.and_return(DEVICE_RES.to_a)
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "the initial output" do
|
70
|
+
before do
|
71
|
+
mock_oauth_response SUCCESS_RES
|
72
|
+
run "login"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "prints the user code" do
|
76
|
+
expect($stdout).to have_received(:write)
|
77
|
+
.with(a_string_matching DEVICE_RES["user_code"])
|
78
|
+
end
|
79
|
+
|
80
|
+
it "prints the verification URI" do
|
81
|
+
expect($stdout).to have_received(:write)
|
82
|
+
.with(a_string_matching DEVICE_RES["verification_uri"])
|
83
|
+
end
|
84
|
+
|
85
|
+
it "opens the verification URI in a browser" do
|
86
|
+
expect(Launchy).to have_received(:open).with(DEVICE_RES["verification_uri"])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "when the response is a success" do
|
91
|
+
before do
|
92
|
+
mock_oauth_response SUCCESS_RES
|
93
|
+
allow(Octokit::Client).to receive(:new).and_call_original
|
94
|
+
run "login"
|
95
|
+
end
|
96
|
+
|
97
|
+
it "writes the token to the config file" do
|
98
|
+
expect(File).to have_received(:write).with(CONFIG_FILE, OAUTH_TOKEN)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "logs in with Octokit" do
|
102
|
+
expect(Octokit::Client).to have_received(:new)
|
103
|
+
.with(a_hash_including access_token: OAUTH_TOKEN)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when the response is timed out" do
|
108
|
+
before do
|
109
|
+
mock_oauth_response EXPIRED_RES
|
110
|
+
%i[puts print].each { |p| allow($stderr).to receive p }
|
111
|
+
run "login", fail_on_exit: false
|
112
|
+
end
|
113
|
+
|
114
|
+
let(:error) { "#{'Error'.red}: Token expired! Please try again." }
|
115
|
+
|
116
|
+
it "displays an error" do
|
117
|
+
expect($stderr).to have_received(:puts).with(error)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "the `logout` subcommand" do
|
124
|
+
before do
|
125
|
+
allow(FileUtils).to receive(:rm_rf)
|
126
|
+
run "logout"
|
127
|
+
end
|
128
|
+
|
129
|
+
it "deletes the auth token" do
|
130
|
+
expect(FileUtils).to have_received(:rm_rf).with(CONFIG_FILE)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
NEW_DIR_FILENAME = "nested/test.file"
|
6
|
+
MULTI_FILENAMES = ["file1", "dir/file2"].freeze
|
7
|
+
|
8
|
+
SINGLE_FILE_CONTENTS = "Line 1\nLine 2\n"
|
9
|
+
|
10
|
+
# Test Gists
|
11
|
+
PUB_SINGLE_FILE_ID = "4346763"
|
12
|
+
SEC_SINGLE_FILE_ID = "5fa3c6bab88036e95d62cadf15128ec3"
|
13
|
+
NO_TITLE_ID = "5865a130f9cf40acd9f0e85d15e601a7"
|
14
|
+
NO_PIPE_SPACING_ID = "5677679b0db25054521753e5d59bed3d"
|
15
|
+
NON_EXISTENT_DIR_ID = "8c86bf9cda921ebe7ad1bf0c46afb108"
|
16
|
+
MULTI_FILE_ID = "8d4a2a4c8fe0b1427fed39c939857a40"
|
17
|
+
CWD_ID = "3c7006e629fcc54262ef02a5b2204735"
|
18
|
+
HOME_ID = "acb6caa80886101a68c5c85e4c100ddb"
|
19
|
+
|
20
|
+
def test_single_file(id, path)
|
21
|
+
before { run "install", id }
|
22
|
+
|
23
|
+
# TEMP already gets `rm -rf`ed in the `cli_spec.rb` #after
|
24
|
+
unless path == TEMP
|
25
|
+
after { FileUtils.rm "#{path}/#{FILENAME}" }
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:file_contents) { File.read "#{path}/#{FILENAME}" }
|
29
|
+
|
30
|
+
it "downloads the file into #{path}" do
|
31
|
+
expect(file_contents).to eq SINGLE_FILE_CONTENTS
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe Gistribute::CLI do
|
36
|
+
before do
|
37
|
+
FileUtils.rm_rf TEMP
|
38
|
+
FileUtils.mkdir_p TEMP
|
39
|
+
suppress_stdout
|
40
|
+
end
|
41
|
+
|
42
|
+
after { FileUtils.rm_rf TEMP }
|
43
|
+
|
44
|
+
describe "#install" do
|
45
|
+
context "when user inputs `y` at the installation prompt" do
|
46
|
+
before { simulate_user_input "y\n" }
|
47
|
+
|
48
|
+
{
|
49
|
+
"public single file": PUB_SINGLE_FILE_ID,
|
50
|
+
"secret single file": SEC_SINGLE_FILE_ID,
|
51
|
+
"no title": NO_TITLE_ID,
|
52
|
+
"no || spacing": NO_PIPE_SPACING_ID
|
53
|
+
}.each do |description, id|
|
54
|
+
context "when run with a #{description} Gist" do
|
55
|
+
test_single_file id, TEMP
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when given a directory that doesn't exist" do
|
60
|
+
before { run "install", NON_EXISTENT_DIR_ID }
|
61
|
+
|
62
|
+
let(:file_contents) { File.read "#{TEMP}/#{NEW_DIR_FILENAME}" }
|
63
|
+
|
64
|
+
it "creates the directory" do
|
65
|
+
expect(file_contents).to eq SINGLE_FILE_CONTENTS
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when run with a multi-file Gist" do
|
70
|
+
before { run "install", MULTI_FILE_ID }
|
71
|
+
|
72
|
+
let(:file1_contents) { File.read "#{TEMP}/#{MULTI_FILENAMES[0]}" }
|
73
|
+
let(:file2_contents) { File.read "#{TEMP}/#{MULTI_FILENAMES[1]}" }
|
74
|
+
|
75
|
+
it "downloads the files into the correct locations" do
|
76
|
+
[file1_contents, file2_contents].each_with_index do |result, i|
|
77
|
+
file_number = i + 1
|
78
|
+
expect(result).to eq "F#{file_number}L1\nF#{file_number}L2\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when given a file for the current working directory" do
|
84
|
+
test_single_file CWD_ID, Dir.pwd
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when given a file for the home directory" do
|
88
|
+
test_single_file HOME_ID, Dir.home
|
89
|
+
end
|
90
|
+
|
91
|
+
context "when given a bad ID (404)" do
|
92
|
+
before do
|
93
|
+
%i[puts print].each { |p| allow($stderr).to receive p }
|
94
|
+
run "install", "bad", fail_on_exit: false
|
95
|
+
end
|
96
|
+
|
97
|
+
it "prints the error to STDERR" do
|
98
|
+
expect($stderr).to have_received(:print).with <<~EOS.chop.red
|
99
|
+
\rThere was an error downloading the requested Gist.
|
100
|
+
The error is as follows:
|
101
|
+
EOS
|
102
|
+
|
103
|
+
expect($stderr).to have_received(:puts).with(" 404 Not Found")
|
104
|
+
expect($stderr).to have_received(:print).with("The ID that was queried is: ".red)
|
105
|
+
expect($stderr).to have_received(:puts).with("bad")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "when user inputs nothing at the installation prompt" do
|
111
|
+
before do
|
112
|
+
simulate_user_input "\n"
|
113
|
+
run "install", PUB_SINGLE_FILE_ID
|
114
|
+
end
|
115
|
+
|
116
|
+
it "saves the file" do
|
117
|
+
expect(File).to exist("#{TEMP}/#{FILENAME}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
%w[n m].each do |ch|
|
122
|
+
context "when user inputs `#{ch}` at the installation prompt" do
|
123
|
+
before do
|
124
|
+
simulate_user_input "#{ch}\n"
|
125
|
+
run "install", PUB_SINGLE_FILE_ID
|
126
|
+
end
|
127
|
+
|
128
|
+
it "doesn't save the file" do
|
129
|
+
expect(File).not_to exist("#{TEMP}/#{FILENAME}")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "when a file would be overwritten" do
|
135
|
+
let(:orig_content) { "original" }
|
136
|
+
|
137
|
+
before do
|
138
|
+
FileUtils.mkdir_p "#{TEMP}/dir"
|
139
|
+
MULTI_FILENAMES.each do |filename|
|
140
|
+
File.write("#{TEMP}/#{filename}", orig_content)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
let(:file1_contents) { File.read "#{TEMP}/#{MULTI_FILENAMES[0]}" }
|
145
|
+
let(:file2_contents) { File.read "#{TEMP}/#{MULTI_FILENAMES[1]}" }
|
146
|
+
|
147
|
+
context "when the user inputs `y` at the file overwrite prompts" do
|
148
|
+
before do
|
149
|
+
simulate_user_input "y\n", "y\n", "y\n"
|
150
|
+
run "install", MULTI_FILE_ID
|
151
|
+
end
|
152
|
+
|
153
|
+
it "downloads the files into the correct locations" do
|
154
|
+
expect([file1_contents, file2_contents]).not_to include orig_content
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "when the user inputs `n` at the file overwrite prompts" do
|
159
|
+
before do
|
160
|
+
simulate_user_input "y\n", "n\n", "n\n"
|
161
|
+
run "install", MULTI_FILE_ID
|
162
|
+
end
|
163
|
+
|
164
|
+
it "doesn't download the files" do
|
165
|
+
expect([file1_contents, file2_contents]).to all eq orig_content
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "with the `--force` flag" do
|
170
|
+
before do
|
171
|
+
simulate_user_input "y\n"
|
172
|
+
run "install", "--force", MULTI_FILE_ID
|
173
|
+
end
|
174
|
+
|
175
|
+
it "overwrites the files without prompting" do
|
176
|
+
[file1_contents, file2_contents].each do |result|
|
177
|
+
expect(result).not_to eq orig_content
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "when ran with the --yes flag" do
|
184
|
+
before { run "install", "--yes", PUB_SINGLE_FILE_ID }
|
185
|
+
|
186
|
+
let(:file_contents) { File.read "#{TEMP}/#{FILENAME}" }
|
187
|
+
|
188
|
+
it "downloads the file without prompting" do
|
189
|
+
expect(file_contents).to eq SINGLE_FILE_CONTENTS
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Gistribute::CLI do
|
6
|
+
before { suppress_stdout }
|
7
|
+
|
8
|
+
%w[install upload].each do |subcmd|
|
9
|
+
describe "the `#{subcmd}` subcommand" do
|
10
|
+
context "when no argument is provided" do
|
11
|
+
it "shows the help screen" do
|
12
|
+
allow(OptimistXL).to receive(:educate).and_call_original
|
13
|
+
run subcmd, fail_on_exit: false
|
14
|
+
|
15
|
+
expect(OptimistXL).to have_received :educate
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when run with the `--version` flag" do
|
22
|
+
let :version do
|
23
|
+
File.read(File.expand_path("../../VERSION", __dir__)).strip
|
24
|
+
end
|
25
|
+
|
26
|
+
it "outputs the version number" do
|
27
|
+
expect { run "--version", fail_on_exit: false }
|
28
|
+
.to output(a_string_matching(version)).to_stdout
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|