gyazo 3.1.1 → 3.3.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/.gitignore +1 -0
- data/Gemfile +2 -0
- data/exe/gyazo +4 -0
- data/gyazo.gemspec +2 -1
- data/lib/gyazo/cli.rb +237 -0
- data/lib/gyazo/client.rb +46 -26
- data/lib/gyazo/version.rb +1 -1
- data/test/test_cli.rb +169 -0
- metadata +10 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8672fc0d9fd37e7c71b08e3cadd36472b2fc4a2cad4f41f563aa9f96e4affbb
|
|
4
|
+
data.tar.gz: abd342aedbbea609a30fb62402b8b211dfdd0da636549994566723ac995ead80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c9c002c93d8416bd0f1577f9b7c6250972d3fc2d825ae2740bf3e3d2fa5b67f1f5e42e2e6afe2d260d24439724df3cdc28d57bd7f8ffef93a61bca12718843d4
|
|
7
|
+
data.tar.gz: f5d89f9babc8a0e914372507d20b03d4cf3bb512d4751b11a6efbc44ca5ce5aae9175974f8984f478e281f622656dc6c972bab752ea4271aaba2eddc05826c9b
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/exe/gyazo
ADDED
data/gyazo.gemspec
CHANGED
|
@@ -14,7 +14,8 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
spec.license = "MIT"
|
|
15
15
|
|
|
16
16
|
spec.files = `git ls-files`.split($/).reject{|i| i=="Gemfile.lock" }
|
|
17
|
-
spec.
|
|
17
|
+
spec.bindir = 'exe'
|
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
20
|
spec.require_paths = ["lib"]
|
|
20
21
|
|
data/lib/gyazo/cli.rb
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
require 'optparse'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'time'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require 'gyazo'
|
|
7
|
+
|
|
8
|
+
module Gyazo
|
|
9
|
+
class CLI
|
|
10
|
+
CONFIG_FILE = File.join(Dir.home, '.config', 'gyazo', 'config')
|
|
11
|
+
|
|
12
|
+
def self.start(argv)
|
|
13
|
+
new.run(argv)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run(argv)
|
|
17
|
+
global_options = {}
|
|
18
|
+
global_parser = OptionParser.new do |opts|
|
|
19
|
+
opts.banner = <<~BANNER
|
|
20
|
+
Usage: gyazo [options] <command> [args]
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
auth login Save access token to config file
|
|
24
|
+
auth logout Remove access token from config file
|
|
25
|
+
|
|
26
|
+
upload <file> Upload an image
|
|
27
|
+
--title TITLE Image title
|
|
28
|
+
--description DESC Image description
|
|
29
|
+
--collection-id ID Collection ID
|
|
30
|
+
--created-at DATETIME Created at (ISO 8601)
|
|
31
|
+
|
|
32
|
+
list List images
|
|
33
|
+
--page N Page number (default: 1)
|
|
34
|
+
--per-page N Per page (default: 20)
|
|
35
|
+
|
|
36
|
+
image <id> Show image details
|
|
37
|
+
delete <id> Delete an image
|
|
38
|
+
|
|
39
|
+
search <query> Search images
|
|
40
|
+
--page N Page number (default: 1)
|
|
41
|
+
--per-page N Per page (default: 20)
|
|
42
|
+
|
|
43
|
+
user Show user info
|
|
44
|
+
version Show version
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
BANNER
|
|
48
|
+
opts.on('-t', '--token TOKEN', 'Access token') { |t| global_options[:token] = t }
|
|
49
|
+
opts.on('-f', '--format FORMAT', 'Output format: text (default) or json') { |f| global_options[:format] = f }
|
|
50
|
+
opts.on_tail('-h', '--help', 'Show help') { puts opts; exit }
|
|
51
|
+
opts.on_tail('-v', '--version', 'Show version') { puts Gyazo::VERSION; exit }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
global_parser.order!(argv)
|
|
55
|
+
command = argv.shift
|
|
56
|
+
|
|
57
|
+
case command
|
|
58
|
+
when 'auth' then run_auth(argv, global_options)
|
|
59
|
+
when 'upload' then run_upload(argv, global_options)
|
|
60
|
+
when 'list' then run_list(argv, global_options)
|
|
61
|
+
when 'image' then run_image(argv, global_options)
|
|
62
|
+
when 'delete' then run_delete(argv, global_options)
|
|
63
|
+
when 'search' then run_search(argv, global_options)
|
|
64
|
+
when 'user' then run_user(argv, global_options)
|
|
65
|
+
when 'version' then puts Gyazo::VERSION
|
|
66
|
+
when nil
|
|
67
|
+
warn global_parser
|
|
68
|
+
exit 1
|
|
69
|
+
else
|
|
70
|
+
warn "Unknown command: #{command}\n\n#{global_parser}"
|
|
71
|
+
exit 1
|
|
72
|
+
end
|
|
73
|
+
rescue Gyazo::Error => e
|
|
74
|
+
warn "Error: #{e.message}"
|
|
75
|
+
exit 1
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def client(options)
|
|
81
|
+
token = resolve_token(options)
|
|
82
|
+
abort "No access token. Set GYAZO_ACCESS_TOKEN or run 'gyazo auth login'" unless token
|
|
83
|
+
Gyazo::Client.new(access_token: token)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def resolve_token(options)
|
|
87
|
+
options[:token] || ENV['GYAZO_ACCESS_TOKEN'] || load_token
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def load_token
|
|
91
|
+
return nil unless File.exist?(CONFIG_FILE)
|
|
92
|
+
File.foreach(CONFIG_FILE) do |line|
|
|
93
|
+
return $1.strip if line =~ /\Aaccess_token=(.+)/
|
|
94
|
+
end
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def save_token(token)
|
|
99
|
+
FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
|
|
100
|
+
File.write(CONFIG_FILE, "access_token=#{token}\n")
|
|
101
|
+
File.chmod(0o600, CONFIG_FILE)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def run_auth(argv, _options)
|
|
105
|
+
subcommand = argv.shift
|
|
106
|
+
case subcommand
|
|
107
|
+
when 'login'
|
|
108
|
+
puts 'Create a token at https://gyazo.com/oauth/applications'
|
|
109
|
+
print 'Enter your Gyazo access token: '
|
|
110
|
+
token = $stdin.gets&.chomp
|
|
111
|
+
abort 'Cancelled' if token.nil? || token.empty?
|
|
112
|
+
save_token(token)
|
|
113
|
+
puts "Token saved to #{CONFIG_FILE}"
|
|
114
|
+
when 'logout'
|
|
115
|
+
if File.exist?(CONFIG_FILE)
|
|
116
|
+
File.delete(CONFIG_FILE)
|
|
117
|
+
puts 'Token removed'
|
|
118
|
+
else
|
|
119
|
+
puts 'No token found'
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
warn 'Usage: gyazo auth <login|logout>'
|
|
123
|
+
exit 1
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def run_upload(argv, options)
|
|
128
|
+
upload_opts = {}
|
|
129
|
+
parser = OptionParser.new do |opts|
|
|
130
|
+
opts.banner = 'Usage: gyazo upload [options] <file>'
|
|
131
|
+
opts.on('--title TITLE', 'Image title') { |v| upload_opts[:title] = v }
|
|
132
|
+
opts.on('--description DESC', 'Image description') { |v| upload_opts[:desc] = v }
|
|
133
|
+
opts.on('--collection-id ID', 'Collection ID') { |v| upload_opts[:collection_id] = v }
|
|
134
|
+
opts.on('--created-at DATETIME', 'Created at (ISO 8601)') { |v| upload_opts[:created_at] = Time.parse(v) }
|
|
135
|
+
end
|
|
136
|
+
parser.parse!(argv)
|
|
137
|
+
|
|
138
|
+
file = argv.shift
|
|
139
|
+
abort parser.to_s unless file
|
|
140
|
+
abort "File not found: #{file}" unless File.file?(file)
|
|
141
|
+
|
|
142
|
+
result = client(options).upload(imagefile: file, **upload_opts)
|
|
143
|
+
output(result, options)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def run_list(argv, options)
|
|
147
|
+
list_opts = {}
|
|
148
|
+
parser = OptionParser.new do |opts|
|
|
149
|
+
opts.banner = 'Usage: gyazo list [options]'
|
|
150
|
+
opts.on('--page N', Integer, 'Page number (default: 1)') { |v| list_opts[:page] = v }
|
|
151
|
+
opts.on('--per-page N', Integer, 'Per page (default: 20)') { |v| list_opts[:per_page] = v }
|
|
152
|
+
end
|
|
153
|
+
parser.parse!(argv)
|
|
154
|
+
|
|
155
|
+
result = client(options).list(**list_opts)
|
|
156
|
+
|
|
157
|
+
if options[:format] == 'json'
|
|
158
|
+
puts JSON.pretty_generate(result)
|
|
159
|
+
else
|
|
160
|
+
puts "Total: #{result[:total_count]} Page: #{result[:current_page]} Per page: #{result[:per_page]}"
|
|
161
|
+
puts
|
|
162
|
+
result[:images].each do |img|
|
|
163
|
+
puts "#{img[:image_id]} #{img[:permalink_url]}"
|
|
164
|
+
title = img.dig(:metadata, :title)
|
|
165
|
+
puts " #{title}" unless title.nil? || title.empty?
|
|
166
|
+
puts " #{img[:created_at]}" if img[:created_at]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def run_image(argv, options)
|
|
172
|
+
id = argv.shift
|
|
173
|
+
abort 'Usage: gyazo image <id>' unless id
|
|
174
|
+
output(client(options).image(image_id: id), options)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def run_delete(argv, options)
|
|
178
|
+
id = argv.shift
|
|
179
|
+
abort 'Usage: gyazo delete <id>' unless id
|
|
180
|
+
result = client(options).delete(image_id: id)
|
|
181
|
+
if options[:format] == 'json'
|
|
182
|
+
puts JSON.pretty_generate(result)
|
|
183
|
+
else
|
|
184
|
+
puts "Deleted: #{result[:image_id]}"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def run_search(argv, options)
|
|
189
|
+
search_opts = {}
|
|
190
|
+
parser = OptionParser.new do |opts|
|
|
191
|
+
opts.banner = 'Usage: gyazo search [options] <query>'
|
|
192
|
+
opts.on('--page N', Integer, 'Page number (default: 1)') { |v| search_opts[:page] = v }
|
|
193
|
+
opts.on('--per-page N', Integer, 'Per page (default: 20)') { |v| search_opts[:per_page] = v }
|
|
194
|
+
end
|
|
195
|
+
parser.parse!(argv)
|
|
196
|
+
|
|
197
|
+
query = argv.shift
|
|
198
|
+
abort parser.to_s unless query
|
|
199
|
+
|
|
200
|
+
result = client(options).search(query:, **search_opts)
|
|
201
|
+
if options[:format] == 'json'
|
|
202
|
+
puts JSON.pretty_generate(result)
|
|
203
|
+
else
|
|
204
|
+
result.each do |img|
|
|
205
|
+
puts "#{img[:image_id]} #{img[:permalink_url]}"
|
|
206
|
+
title = img.dig(:metadata, :title)
|
|
207
|
+
puts " #{title}" unless title.nil? || title.empty?
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def run_user(argv, options)
|
|
213
|
+
output(client(options).user_info, options)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def output(data, options)
|
|
217
|
+
if options[:format] == 'json'
|
|
218
|
+
puts JSON.pretty_generate(data)
|
|
219
|
+
else
|
|
220
|
+
print_hash(data)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def print_hash(hash, indent = 0)
|
|
225
|
+
hash.each do |k, v|
|
|
226
|
+
if v.is_a?(Hash)
|
|
227
|
+
puts "#{' ' * indent}#{k}:"
|
|
228
|
+
print_hash(v, indent + 2)
|
|
229
|
+
elsif v.is_a?(Array)
|
|
230
|
+
puts "#{' ' * indent}#{k}: [#{v.length} items]"
|
|
231
|
+
else
|
|
232
|
+
puts "#{' ' * indent}#{k}: #{v.to_s.gsub(/\r?\n/, '\n')}"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
data/lib/gyazo/client.rb
CHANGED
|
@@ -39,47 +39,36 @@ module Gyazo
|
|
|
39
39
|
}
|
|
40
40
|
req.headers['User-Agent'] = @user_agent
|
|
41
41
|
end
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
|
|
43
|
+
parse_body(res)[:json]
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def list(page: 1, per_page: 20)
|
|
47
|
-
|
|
48
|
-
res =
|
|
49
|
-
req.params[:access_token] = @access_token
|
|
50
|
-
req.params[:page] = page
|
|
51
|
-
req.params[:per_page] = per_page
|
|
52
|
-
req.headers['User-Agent'] = @user_agent
|
|
53
|
-
end
|
|
54
|
-
raise Gyazo::Error, res.body unless res.status == 200
|
|
55
|
-
json = ::JSON.parse res.body, symbolize_names: true
|
|
47
|
+
r = send_get(path: '/api/images', params: { page:, per_page: })
|
|
48
|
+
res = r[:response]
|
|
56
49
|
{
|
|
57
50
|
total_count: res.headers['X-Total-Count'],
|
|
58
51
|
current_page: res.headers['X-Current-Page'],
|
|
59
52
|
per_page: res.headers['X-Per-Page'],
|
|
60
53
|
user_type: res.headers['X-User-Type'],
|
|
61
|
-
images: json
|
|
54
|
+
images: r[:json],
|
|
62
55
|
}
|
|
63
56
|
end
|
|
64
57
|
|
|
65
58
|
def image(image_id:)
|
|
66
|
-
path
|
|
67
|
-
res = @conn.get path do |req|
|
|
68
|
-
req.params[:access_token] = @access_token
|
|
69
|
-
req.headers['User-Agent'] = @user_agent
|
|
70
|
-
end
|
|
71
|
-
raise Gyazo::Error, res.body unless res.status == 200
|
|
72
|
-
return ::JSON.parse res.body, symbolize_names: true
|
|
59
|
+
send_get(path: "/api/images/#{image_id}")[:json]
|
|
73
60
|
end
|
|
74
61
|
|
|
75
62
|
def delete(image_id:)
|
|
76
|
-
path
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
63
|
+
send_delete(path: "/api/images/#{image_id}")[:json]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def user_info
|
|
67
|
+
send_get(path: '/api/users/me')[:json]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def search(query:, page: 1, per_page: 20)
|
|
71
|
+
send_get(path: '/api/search', params: { query:, page:, per_page: })[:json]
|
|
83
72
|
end
|
|
84
73
|
|
|
85
74
|
private
|
|
@@ -94,5 +83,36 @@ module Gyazo
|
|
|
94
83
|
return if ::File.file? file
|
|
95
84
|
raise ArgumentError, "cannot find file #{file}"
|
|
96
85
|
end
|
|
86
|
+
|
|
87
|
+
def send_req(method:, path:, params: {})
|
|
88
|
+
res = @conn.send(method) do |req|
|
|
89
|
+
req.url path
|
|
90
|
+
req.params[:access_token] = @access_token
|
|
91
|
+
params.each do |k, v|
|
|
92
|
+
req.params[k] = v
|
|
93
|
+
end
|
|
94
|
+
req.headers['User-Agent'] = @user_agent
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
parse_body(res)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def send_get(path:, params: {})
|
|
101
|
+
send_req(method: :get, path:, params:)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def send_delete(path:, params: {})
|
|
105
|
+
send_req(method: :delete, path:, params:)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def parse_body(res)
|
|
109
|
+
raise Gyazo::Error, res.body unless res.status == 200
|
|
110
|
+
begin
|
|
111
|
+
json = ::JSON.parse res.body, symbolize_names: true
|
|
112
|
+
rescue ::JSON::ParserError => e
|
|
113
|
+
raise Gyazo::Error, "Gyazo API response is not JSON: #{e.message}, body: #{res.body}"
|
|
114
|
+
end
|
|
115
|
+
{ json:, response: res }
|
|
116
|
+
end
|
|
97
117
|
end
|
|
98
118
|
end
|
data/lib/gyazo/version.rb
CHANGED
data/test/test_cli.rb
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
require File.expand_path 'test_helper', File.dirname(__FILE__)
|
|
2
|
+
require 'gyazo/cli'
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
|
|
5
|
+
class TestCLI < MiniTest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@cli = Gyazo::CLI.new
|
|
8
|
+
@tmpdir = Dir.mktmpdir
|
|
9
|
+
@config_file = File.join(@tmpdir, 'config')
|
|
10
|
+
# Override CONFIG_FILE constant for tests
|
|
11
|
+
Gyazo::CLI.send(:remove_const, :CONFIG_FILE) if Gyazo::CLI.const_defined?(:CONFIG_FILE)
|
|
12
|
+
Gyazo::CLI.const_set(:CONFIG_FILE, @config_file)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def teardown
|
|
16
|
+
FileUtils.rm_rf(@tmpdir)
|
|
17
|
+
# Restore original
|
|
18
|
+
Gyazo::CLI.send(:remove_const, :CONFIG_FILE) if Gyazo::CLI.const_defined?(:CONFIG_FILE)
|
|
19
|
+
Gyazo::CLI.const_set(:CONFIG_FILE, File.join(Dir.home, '.config', 'gyazo', 'config'))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# --- Token resolution ---
|
|
23
|
+
|
|
24
|
+
def test_load_token_returns_nil_when_no_config
|
|
25
|
+
assert_nil @cli.send(:load_token)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_save_and_load_token
|
|
29
|
+
@cli.send(:save_token, 'mytoken123')
|
|
30
|
+
assert_equal 'mytoken123', @cli.send(:load_token)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_save_token_sets_permissions
|
|
34
|
+
@cli.send(:save_token, 'mytoken123')
|
|
35
|
+
mode = File.stat(@config_file).mode & 0o777
|
|
36
|
+
assert_equal 0o600, mode
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_resolve_token_prefers_option_flag
|
|
40
|
+
@cli.send(:save_token, 'fromfile')
|
|
41
|
+
ENV['GYAZO_ACCESS_TOKEN'] = 'fromenv'
|
|
42
|
+
token = @cli.send(:resolve_token, { token: 'fromflag' })
|
|
43
|
+
assert_equal 'fromflag', token
|
|
44
|
+
ensure
|
|
45
|
+
ENV.delete('GYAZO_ACCESS_TOKEN')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_resolve_token_prefers_env_over_file
|
|
49
|
+
@cli.send(:save_token, 'fromfile')
|
|
50
|
+
ENV['GYAZO_ACCESS_TOKEN'] = 'fromenv'
|
|
51
|
+
token = @cli.send(:resolve_token, {})
|
|
52
|
+
assert_equal 'fromenv', token
|
|
53
|
+
ensure
|
|
54
|
+
ENV.delete('GYAZO_ACCESS_TOKEN')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_resolve_token_falls_back_to_file
|
|
58
|
+
ENV.delete('GYAZO_ACCESS_TOKEN')
|
|
59
|
+
@cli.send(:save_token, 'fromfile')
|
|
60
|
+
token = @cli.send(:resolve_token, {})
|
|
61
|
+
assert_equal 'fromfile', token
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_resolve_token_returns_nil_when_nothing
|
|
65
|
+
ENV.delete('GYAZO_ACCESS_TOKEN')
|
|
66
|
+
assert_nil @cli.send(:resolve_token, {})
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# --- Output formatting ---
|
|
70
|
+
|
|
71
|
+
def test_output_text_format
|
|
72
|
+
data = { image_id: 'abc123', permalink_url: 'https://gyazo.com/abc123' }
|
|
73
|
+
out = capture_stdout { @cli.send(:output, data, {}) }
|
|
74
|
+
assert_includes out, 'image_id: abc123'
|
|
75
|
+
assert_includes out, 'permalink_url: https://gyazo.com/abc123'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_output_json_format
|
|
79
|
+
data = { image_id: 'abc123', permalink_url: 'https://gyazo.com/abc123' }
|
|
80
|
+
out = capture_stdout { @cli.send(:output, data, { format: 'json' }) }
|
|
81
|
+
parsed = JSON.parse(out, symbolize_names: true)
|
|
82
|
+
assert_equal 'abc123', parsed[:image_id]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_output_nested_hash
|
|
86
|
+
data = { metadata: { title: 'My Image', desc: 'A test' } }
|
|
87
|
+
out = capture_stdout { @cli.send(:output, data, {}) }
|
|
88
|
+
assert_includes out, 'metadata:'
|
|
89
|
+
assert_includes out, 'title: My Image'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_output_escapes_newlines_in_values
|
|
93
|
+
data = { desc: "line1\nline2\r\nline3" }
|
|
94
|
+
out = capture_stdout { @cli.send(:output, data, {}) }
|
|
95
|
+
# 値の中の改行がエスケープされ、出力が1行になること
|
|
96
|
+
assert_equal 1, out.lines.length
|
|
97
|
+
assert_includes out, 'desc: line1\nline2\nline3'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_output_array_shows_count
|
|
101
|
+
data = { images: [1, 2, 3] }
|
|
102
|
+
out = capture_stdout { @cli.send(:output, data, {}) }
|
|
103
|
+
assert_includes out, 'images: [3 items]'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# --- version command ---
|
|
107
|
+
|
|
108
|
+
def test_version_command
|
|
109
|
+
out = capture_stdout { @cli.run(['version']) }
|
|
110
|
+
assert_equal Gyazo::VERSION, out.chomp
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# --- upload validation ---
|
|
114
|
+
|
|
115
|
+
def test_upload_aborts_without_file_argument
|
|
116
|
+
err = assert_raises(SystemExit) do
|
|
117
|
+
capture_stderr { @cli.run(['--token', 'dummy', 'upload']) }
|
|
118
|
+
end
|
|
119
|
+
assert_equal 1, err.status
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def test_upload_aborts_with_nonexistent_file
|
|
123
|
+
err = assert_raises(SystemExit) do
|
|
124
|
+
capture_stderr { @cli.run(['--token', 'dummy', 'upload', '/nonexistent/file.png']) }
|
|
125
|
+
end
|
|
126
|
+
assert_equal 1, err.status
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# --- auth commands ---
|
|
130
|
+
|
|
131
|
+
def test_auth_logout_when_no_token
|
|
132
|
+
out = capture_stdout { @cli.run(['auth', 'logout']) }
|
|
133
|
+
assert_includes out, 'No token found'
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_auth_logout_removes_config
|
|
137
|
+
@cli.send(:save_token, 'mytoken')
|
|
138
|
+
assert File.exist?(@config_file)
|
|
139
|
+
capture_stdout { @cli.run(['auth', 'logout']) }
|
|
140
|
+
refute File.exist?(@config_file)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_auth_unknown_subcommand
|
|
144
|
+
err = assert_raises(SystemExit) do
|
|
145
|
+
capture_stderr { @cli.run(['auth', 'unknown']) }
|
|
146
|
+
end
|
|
147
|
+
assert_equal 1, err.status
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def capture_stdout
|
|
153
|
+
old = $stdout
|
|
154
|
+
$stdout = StringIO.new
|
|
155
|
+
yield
|
|
156
|
+
$stdout.string
|
|
157
|
+
ensure
|
|
158
|
+
$stdout = old
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def capture_stderr
|
|
162
|
+
old = $stderr
|
|
163
|
+
$stderr = StringIO.new
|
|
164
|
+
yield
|
|
165
|
+
$stderr.string
|
|
166
|
+
ensure
|
|
167
|
+
$stderr = old
|
|
168
|
+
end
|
|
169
|
+
end
|
metadata
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gyazo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Toshiyuki Masui
|
|
8
8
|
- Sho Hashimoto
|
|
9
9
|
- Nana Kugayama
|
|
10
|
-
|
|
11
|
-
bindir: bin
|
|
10
|
+
bindir: exe
|
|
12
11
|
cert_chain: []
|
|
13
|
-
date:
|
|
12
|
+
date: 2026-04-08 00:00:00.000000000 Z
|
|
14
13
|
dependencies:
|
|
15
14
|
- !ruby/object:Gem::Dependency
|
|
16
15
|
name: bundler
|
|
@@ -99,7 +98,8 @@ dependencies:
|
|
|
99
98
|
description: Gyazo.com API Wrapper
|
|
100
99
|
email:
|
|
101
100
|
- masui@pitecan.com
|
|
102
|
-
executables:
|
|
101
|
+
executables:
|
|
102
|
+
- gyazo
|
|
103
103
|
extensions: []
|
|
104
104
|
extra_rdoc_files: []
|
|
105
105
|
files:
|
|
@@ -110,21 +110,23 @@ files:
|
|
|
110
110
|
- Makefile
|
|
111
111
|
- README.md
|
|
112
112
|
- Rakefile
|
|
113
|
+
- exe/gyazo
|
|
113
114
|
- gyazo.gemspec
|
|
114
115
|
- lib/gyazo.rb
|
|
116
|
+
- lib/gyazo/cli.rb
|
|
115
117
|
- lib/gyazo/client.rb
|
|
116
118
|
- lib/gyazo/error.rb
|
|
117
119
|
- lib/gyazo/version.rb
|
|
118
120
|
- samples/list.rb
|
|
119
121
|
- samples/upload.rb
|
|
120
122
|
- test/test.png
|
|
123
|
+
- test/test_cli.rb
|
|
121
124
|
- test/test_gyazo.rb
|
|
122
125
|
- test/test_helper.rb
|
|
123
126
|
homepage: http://github.com/gyazo/gyazo-ruby
|
|
124
127
|
licenses:
|
|
125
128
|
- MIT
|
|
126
129
|
metadata: {}
|
|
127
|
-
post_install_message:
|
|
128
130
|
rdoc_options: []
|
|
129
131
|
require_paths:
|
|
130
132
|
- lib
|
|
@@ -139,11 +141,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
139
141
|
- !ruby/object:Gem::Version
|
|
140
142
|
version: '0'
|
|
141
143
|
requirements: []
|
|
142
|
-
rubygems_version: 3.2
|
|
143
|
-
signing_key:
|
|
144
|
+
rubygems_version: 3.6.2
|
|
144
145
|
specification_version: 4
|
|
145
146
|
summary: Gyazo.com API Wrapper
|
|
146
147
|
test_files:
|
|
147
148
|
- test/test.png
|
|
149
|
+
- test/test_cli.rb
|
|
148
150
|
- test/test_gyazo.rb
|
|
149
151
|
- test/test_helper.rb
|