gyazo 3.2.0 → 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 +37 -26
- data/lib/gyazo/version.rb +1 -1
- data/test/test_cli.rb +169 -0
- metadata +9 -4
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
|
-
send_get_without_param(path:)
|
|
59
|
+
send_get(path: "/api/images/#{image_id}")[:json]
|
|
68
60
|
end
|
|
69
61
|
|
|
70
62
|
def delete(image_id:)
|
|
71
|
-
path
|
|
72
|
-
res = @conn.delete path do |req|
|
|
73
|
-
req.params[:access_token] = @access_token
|
|
74
|
-
req.headers['User-Agent'] = @user_agent
|
|
75
|
-
end
|
|
76
|
-
raise Gyazo::Error, res.body unless res.status == 200
|
|
77
|
-
return ::JSON.parse res.body, symbolize_names: true
|
|
63
|
+
send_delete(path: "/api/images/#{image_id}")[:json]
|
|
78
64
|
end
|
|
79
65
|
|
|
80
66
|
def user_info
|
|
81
|
-
path
|
|
82
|
-
|
|
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
|
|
@@ -95,13 +84,35 @@ module Gyazo
|
|
|
95
84
|
raise ArgumentError, "cannot find file #{file}"
|
|
96
85
|
end
|
|
97
86
|
|
|
98
|
-
def
|
|
99
|
-
res = @conn.
|
|
87
|
+
def send_req(method:, path:, params: {})
|
|
88
|
+
res = @conn.send(method) do |req|
|
|
89
|
+
req.url path
|
|
100
90
|
req.params[:access_token] = @access_token
|
|
91
|
+
params.each do |k, v|
|
|
92
|
+
req.params[k] = v
|
|
93
|
+
end
|
|
101
94
|
req.headers['User-Agent'] = @user_agent
|
|
102
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)
|
|
103
109
|
raise Gyazo::Error, res.body unless res.status == 200
|
|
104
|
-
|
|
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 }
|
|
105
116
|
end
|
|
106
117
|
end
|
|
107
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,15 +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
|
-
bindir:
|
|
10
|
+
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2026-04-08 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: bundler
|
|
@@ -98,7 +98,8 @@ dependencies:
|
|
|
98
98
|
description: Gyazo.com API Wrapper
|
|
99
99
|
email:
|
|
100
100
|
- masui@pitecan.com
|
|
101
|
-
executables:
|
|
101
|
+
executables:
|
|
102
|
+
- gyazo
|
|
102
103
|
extensions: []
|
|
103
104
|
extra_rdoc_files: []
|
|
104
105
|
files:
|
|
@@ -109,14 +110,17 @@ files:
|
|
|
109
110
|
- Makefile
|
|
110
111
|
- README.md
|
|
111
112
|
- Rakefile
|
|
113
|
+
- exe/gyazo
|
|
112
114
|
- gyazo.gemspec
|
|
113
115
|
- lib/gyazo.rb
|
|
116
|
+
- lib/gyazo/cli.rb
|
|
114
117
|
- lib/gyazo/client.rb
|
|
115
118
|
- lib/gyazo/error.rb
|
|
116
119
|
- lib/gyazo/version.rb
|
|
117
120
|
- samples/list.rb
|
|
118
121
|
- samples/upload.rb
|
|
119
122
|
- test/test.png
|
|
123
|
+
- test/test_cli.rb
|
|
120
124
|
- test/test_gyazo.rb
|
|
121
125
|
- test/test_helper.rb
|
|
122
126
|
homepage: http://github.com/gyazo/gyazo-ruby
|
|
@@ -142,5 +146,6 @@ specification_version: 4
|
|
|
142
146
|
summary: Gyazo.com API Wrapper
|
|
143
147
|
test_files:
|
|
144
148
|
- test/test.png
|
|
149
|
+
- test/test_cli.rb
|
|
145
150
|
- test/test_gyazo.rb
|
|
146
151
|
- test/test_helper.rb
|