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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5501aee18aa242031b41934001bef19e73a034545aaebaadf199328eee8518a
4
- data.tar.gz: 351c8efbdf423ad30ebd642988a31cb69f6eaa6512e1338f574e46aab8310347
3
+ metadata.gz: b8672fc0d9fd37e7c71b08e3cadd36472b2fc4a2cad4f41f563aa9f96e4affbb
4
+ data.tar.gz: abd342aedbbea609a30fb62402b8b211dfdd0da636549994566723ac995ead80
5
5
  SHA512:
6
- metadata.gz: 7a9e35b78e352e767aa970ed40ef09be8c7e9548b59b931abfcbeec309d8a83a462cdffa809f40d9f61a2c675fcc02aef0d63235eab2700b37397973a04106a8
7
- data.tar.gz: 93c352deb35426ca0528cc5ca7b0c67ec23c75945ee6672efc86d7f3d5326ba140cd69da007c7ee254ecb40713142ddb919b08b1ee1eb8ee361aaed7abab6a11
6
+ metadata.gz: c9c002c93d8416bd0f1577f9b7c6250972d3fc2d825ae2740bf3e3d2fa5b67f1f5e42e2e6afe2d260d24439724df3cdc28d57bd7f8ffef93a61bca12718843d4
7
+ data.tar.gz: f5d89f9babc8a0e914372507d20b03d4cf3bb512d4751b11a6efbc44ca5ce5aae9175974f8984f478e281f622656dc6c972bab752ea4271aaba2eddc05826c9b
data/.gitignore CHANGED
@@ -4,3 +4,4 @@
4
4
  pkg
5
5
  tmp
6
6
  .ruby-version
7
+ bin/
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in event_emitter.gemspec
4
4
  gemspec
5
+
6
+ gem 'mutex_m'
data/exe/gyazo ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
3
+ require 'gyazo/cli'
4
+ Gyazo::CLI.start(ARGV)
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.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
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
- raise Gyazo::Error, res.body unless res.status == 200
43
- return ::JSON.parse res.body, symbolize_names: true
42
+
43
+ parse_body(res)[:json]
44
44
  end
45
45
 
46
46
  def list(page: 1, per_page: 20)
47
- path = '/api/images'
48
- res = @conn.get path do |req|
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 = "/api/images/#{image_id}"
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 = "/api/images/#{image_id}"
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 = '/api/users/me'
82
- send_get_without_param(path:)
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 send_get_without_param(path:)
99
- res = @conn.get path do |req|
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
- return ::JSON.parse res.body, symbolize_names: true
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
@@ -1,3 +1,3 @@
1
1
  module Gyazo
2
- VERSION = '3.2.0'
2
+ VERSION = '3.3.0'
3
3
  end
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.2.0
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: bin
10
+ bindir: exe
11
11
  cert_chain: []
12
- date: 2025-01-05 00:00:00.000000000 Z
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