togows 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3aa356b44bbb2bf65b5f57312ed7134c221480b1b0f21727310756f5f8f1aa05
4
+ data.tar.gz: 222d0166a0d845fae0dca15a94a178b9ff6f2f3ef0329331390f692f32e85c49
5
+ SHA512:
6
+ metadata.gz: 986782292f69d0fe7db251001108570a20e4502ad4e0bc0e4865443f233ea536c4f72d23f1c64176d04eee3b12f78a322748d153867ff0c6dd96a616ee9a8b86
7
+ data.tar.gz: 38199b730174c357333a5ae268529add28e289888ade3d384286c69f60d9eaec718661addb336e4a2f6c5cabad9df4f87b8257e830a24e0b18932bde4d1252af
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kojix2
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # togows
2
+
3
+ A small dependency-free Ruby command line client for the TogoWS REST API.
4
+
5
+ ```sh
6
+ gem install togows
7
+ ```
8
+
9
+ ```sh
10
+ togows databases
11
+ togows entry pubmed 20472643 authors --format json
12
+ togows search uniprot "FGF19" --limit 10
13
+ ```
14
+
15
+ See the [user guide](docs/user-guide.md) for installation from source, command examples, exit status, and testing notes.
16
+
17
+ ## License
18
+
19
+ MIT
@@ -0,0 +1,171 @@
1
+ # User Guide
2
+
3
+ ## Installation
4
+
5
+ Install the released gem:
6
+
7
+ ```sh
8
+ gem install togows
9
+ ```
10
+
11
+ Or build it from this repository:
12
+
13
+ ```sh
14
+ gem build togows.gemspec
15
+ gem install ./togows-*.gem
16
+ ```
17
+
18
+ ## Commands
19
+
20
+ ```text
21
+ togows entry DATABASE ID[,ID...] [FIELD] [options]
22
+ togows search DATABASE QUERY [options]
23
+ togows convert SOURCE.FORMAT [FILE] [options]
24
+ togows databases [entry|search] [options]
25
+ togows ucsc [DATABASE] [TABLE] [QUERY] [options]
26
+ ```
27
+
28
+ Run `togows help COMMAND` for command-specific examples:
29
+
30
+ ```sh
31
+ togows help entry
32
+ togows help search
33
+ togows help convert
34
+ ```
35
+
36
+ ## Discover Databases
37
+
38
+ TogoWS database names must be exact. Start here when you are unsure:
39
+
40
+ ```sh
41
+ togows databases
42
+ togows databases entry
43
+ togows databases search
44
+ ```
45
+
46
+ ## Entry
47
+
48
+ Fetch one entry:
49
+
50
+ ```sh
51
+ togows entry nucleotide NC_001138
52
+ ```
53
+
54
+ Fetch a field:
55
+
56
+ ```sh
57
+ togows entry pubmed 20472643 authors --format json
58
+ togows entry uniprot ACT_YEAST seq
59
+ ```
60
+
61
+ Use comma-separated IDs for multiple entries:
62
+
63
+ ```sh
64
+ togows entry pubmed 20472643,20472644 title
65
+ ```
66
+
67
+ ## Search
68
+
69
+ Count search results:
70
+
71
+ ```sh
72
+ togows search pubmed "lung cancer" --count
73
+ ```
74
+
75
+ Fetch the first page of search results:
76
+
77
+ ```sh
78
+ togows search uniprot "FGF19" --limit 10
79
+ ```
80
+
81
+ Specify both offset and limit when you need a later page:
82
+
83
+ ```sh
84
+ togows search uniprot "lung cancer" --offset 11 --limit 10
85
+ ```
86
+
87
+ ## Convert
88
+
89
+ Convert a local file:
90
+
91
+ ```sh
92
+ {
93
+ printf 'LOCUS TEST 12 bp DNA linear UNA 01-JAN-2000\n'
94
+ printf 'FEATURES Location/Qualifiers\n'
95
+ printf 'ORIGIN\n'
96
+ printf ' 1 acgtacgtacgt\n'
97
+ printf '//\n'
98
+ } > input.gb
99
+
100
+ togows convert genbank.fasta input.gb
101
+ ```
102
+
103
+ Convert from stdin:
104
+
105
+ ```sh
106
+ {
107
+ printf 'LOCUS TEST 12 bp DNA linear UNA 01-JAN-2000\n'
108
+ printf 'FEATURES Location/Qualifiers\n'
109
+ printf 'ORIGIN\n'
110
+ printf ' 1 acgtacgtacgt\n'
111
+ printf '//\n'
112
+ } | togows convert genbank.fasta
113
+ ```
114
+
115
+ ## UCSC
116
+
117
+ List UCSC genome databases or tables:
118
+
119
+ ```sh
120
+ togows ucsc
121
+ togows ucsc hg38
122
+ ```
123
+
124
+ Query a table:
125
+
126
+ ```sh
127
+ togows ucsc hg38 refGene name2=UVSSA
128
+ ```
129
+
130
+ ## Useful Options
131
+
132
+ Print the constructed URL without making a request:
133
+
134
+ ```sh
135
+ togows entry pubmed 20472643 authors --format json --raw-url
136
+ ```
137
+
138
+ Page long output explicitly:
139
+
140
+ ```sh
141
+ togows ucsc hg38 --pager
142
+ ```
143
+
144
+ ## Exit Status
145
+
146
+ ```text
147
+ 0 success
148
+ 2 usage or option error
149
+ 4 HTTP client error from TogoWS
150
+ 5 HTTP server error from TogoWS
151
+ 6 network, socket, or timeout error
152
+ ```
153
+
154
+ ## Testing
155
+
156
+ ```sh
157
+ bundle exec rake test
158
+ ```
159
+
160
+ Live tests are skipped by default. To make real requests to TogoWS:
161
+
162
+ ```sh
163
+ TOGOWS_LIVE_TEST=1 bundle exec rake test
164
+ ```
165
+
166
+ ## Release
167
+
168
+ ```sh
169
+ gem build togows.gemspec
170
+ gem push togows-*.gem
171
+ ```
data/exe/togows ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/togows"
5
+
6
+ exit TogoWS::CLI.new(ARGV, $stdin, $stdout, $stderr).run
data/lib/togows/cli.rb ADDED
@@ -0,0 +1,343 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "optparse"
5
+ require "shellwords"
6
+ require "timeout"
7
+
8
+ module TogoWS
9
+ class CLI
10
+ DEFAULT_BASE_URL = "https://togows.org"
11
+ DEFAULT_TIMEOUT = 30
12
+
13
+ def initialize(argv, stdin, stdout, stderr)
14
+ @argv = argv.dup
15
+ @stdin = stdin
16
+ @stdout = stdout
17
+ @stderr = stderr
18
+ @out_color = Color.new(Color.enabled_for?(stdout))
19
+ @err_color = Color.new(Color.enabled_for?(stderr))
20
+ end
21
+
22
+ def run
23
+ command = @argv.shift
24
+ return help_for(@argv.shift, 0) if ["help", "-h", "--help"].include?(command)
25
+ return help(nil, 0) if command.nil?
26
+ return version if ["version", "--version", "-v"].include?(command)
27
+
28
+ case command
29
+ when "entry" then run_entry
30
+ when "search" then run_search
31
+ when "convert" then run_convert
32
+ when "databases", "dbs" then run_databases
33
+ when "ucsc" then run_ucsc
34
+ else
35
+ error("unknown command: #{command}")
36
+ help(nil, 2)
37
+ end
38
+ rescue HTTPError => e
39
+ error("#{e.code} #{e.message_text}")
40
+ @stderr.puts e.body unless e.body.empty?
41
+ e.code >= 500 ? 5 : 4
42
+ rescue OptionParser::ParseError, Error => e
43
+ error(e.message)
44
+ 2
45
+ rescue SystemCallError, SocketError, Timeout::Error => e
46
+ error("#{e.class}: #{e.message}")
47
+ 6
48
+ end
49
+
50
+ private
51
+
52
+ def common_options
53
+ {
54
+ base_url: DEFAULT_BASE_URL,
55
+ timeout: DEFAULT_TIMEOUT,
56
+ format: nil,
57
+ raw_url: false,
58
+ pager: false,
59
+ pretty: false
60
+ }
61
+ end
62
+
63
+ def parser(options, banner)
64
+ OptionParser.new do |opts|
65
+ opts.banner = banner
66
+ opts.on("-f", "--format FORMAT", "Output format") { |v| options[:format] = v }
67
+ opts.on("--base-url URL", "Base URL") { |v| options[:base_url] = v }
68
+ opts.on("--timeout SEC", Integer, "Timeout seconds") { |v| options[:timeout] = v }
69
+ opts.on("--raw-url", "Print URL and exit") { options[:raw_url] = true }
70
+ opts.on("--pager", "Page output with PAGER when stdout is a terminal") { options[:pager] = true }
71
+ opts.on("--pretty", "Pretty-print JSON output") { options[:pretty] = true }
72
+ opts.on("-h", "--help", "Show help") do
73
+ @stdout.puts opts
74
+ throw :help
75
+ end
76
+ end
77
+ end
78
+
79
+ def run_entry
80
+ options = common_options
81
+ catch(:help) do
82
+ parser(options, "Usage: togows entry DATABASE ID[,ID...] [FIELD] [options]").parse!(@argv)
83
+ database, *rest = @argv
84
+ return command_help(:entry, "entry requires DATABASE and ID", 2) if database.nil? || rest.empty?
85
+
86
+ ids, field = parse_entry_arguments(rest)
87
+ path = URLBuilder.entry(database, ids, field, options[:format])
88
+ output_request(path, options, :get)
89
+ end || 0
90
+ end
91
+
92
+ def parse_entry_arguments(args)
93
+ raise Error, "entry accepts ID[,ID...] and optional FIELD; use commas for multiple IDs" if args.length > 2
94
+
95
+ ids = args.first.to_s.split(",")
96
+ field = args[1]
97
+ raise Error, "entry accepts comma-separated IDs, not space-separated IDs" if field && entry_id_like?(field)
98
+
99
+ [ids, field]
100
+ end
101
+
102
+ def entry_id_like?(value)
103
+ value.include?(",") || value.match?(/\A[A-Z0-9_]+(?:\.\d+)?\z/)
104
+ end
105
+
106
+ def run_search
107
+ options = common_options.merge(offset: nil, limit: nil, count: false)
108
+ catch(:help) do
109
+ p = parser(options, "Usage: togows search DATABASE QUERY [options]")
110
+ p.on("-o", "--offset N", Integer, "Result offset") { |v| options[:offset] = v }
111
+ p.on("-n", "--limit N", Integer, "Result limit") { |v| options[:limit] = v }
112
+ p.on("--count", "Count results") { options[:count] = true }
113
+ p.parse!(@argv)
114
+ database, query = @argv
115
+ return command_help(:search, "search requires DATABASE and QUERY", 2) if database.nil?
116
+ return command_help(:search, "search requires QUERY after DATABASE", 2) if query.nil?
117
+
118
+ validate_search_options(options)
119
+ options[:offset] = 1 if options[:limit] && !options[:offset]
120
+
121
+ path = URLBuilder.search(database, query, options[:offset], options[:limit], options[:count], options[:format])
122
+ output_request(path, options, :get)
123
+ end || 0
124
+ end
125
+
126
+ def validate_search_options(options)
127
+ if options[:count] && (options[:offset] || options[:limit])
128
+ raise Error, "--count cannot be used with --offset or --limit"
129
+ end
130
+
131
+ raise Error, "--offset and --limit must be used together" if options[:offset] && !options[:limit]
132
+ end
133
+
134
+ def run_convert
135
+ options = common_options.merge(to: nil)
136
+ catch(:help) do
137
+ p = parser(options, "Usage: togows convert SOURCE.FORMAT [FILE] [options]")
138
+ p.on("--to FORMAT", "Target format") { |v| options[:to] = v }
139
+ p.parse!(@argv)
140
+ source, file = @argv
141
+ return command_help(:convert, "convert requires SOURCE.FORMAT or SOURCE --to FORMAT", 2) if source.nil?
142
+
143
+ path = URLBuilder.convert(source, options[:to])
144
+ body = file ? File.read(file) : read_convert_stdin
145
+ output_request(path, options, :post, body)
146
+ end || 0
147
+ end
148
+
149
+ def read_convert_stdin
150
+ raise Error, "convert requires FILE or piped stdin; stdin is a terminal" if @stdin.tty?
151
+
152
+ @stdin.read
153
+ end
154
+
155
+ def run_ucsc
156
+ options = common_options.merge(offset: nil, limit: nil)
157
+ catch(:help) do
158
+ p = parser(options, "Usage: togows ucsc [DATABASE] [TABLE] [QUERY] [options]")
159
+ p.on("-o", "--offset N", Integer, "Result offset") { |v| options[:offset] = v }
160
+ p.on("-n", "--limit N", Integer, "Result limit") { |v| options[:limit] = v }
161
+ p.parse!(@argv)
162
+ raise Error, "--offset and --limit must be used together" if options[:offset] && !options[:limit]
163
+ raise Error, "--offset and --limit must be used together" if options[:limit] && !options[:offset]
164
+
165
+ database, table, *query_parts = @argv
166
+ query = query_parts.empty? ? nil : query_parts.join("/")
167
+ path = URLBuilder.ucsc(database, table, query, options[:offset], options[:limit], options[:format])
168
+ output_request(path, options, :get)
169
+ end || 0
170
+ end
171
+
172
+ def run_databases
173
+ options = common_options
174
+ catch(:help) do
175
+ parser(options, "Usage: togows databases [entry|search] [options]").parse!(@argv)
176
+ kind = @argv.shift
177
+ raise Error, "databases accepts only one argument: entry or search" unless @argv.empty?
178
+
179
+ kinds = if kind.nil?
180
+ %w[entry search]
181
+ elsif %w[entry search].include?(kind)
182
+ [kind]
183
+ else
184
+ return command_help(:databases, "database list must be entry or search", 2)
185
+ end
186
+
187
+ output_database_lists(kinds, options)
188
+ end || 0
189
+ end
190
+
191
+ def output_request(path, options, method, body = nil)
192
+ if options[:raw_url]
193
+ @stdout.puts URLBuilder.full_url(options[:base_url], path)
194
+ return 0
195
+ end
196
+
197
+ client = Client.new(options[:base_url], options[:timeout])
198
+ response = with_status("Fetching...") do
199
+ (method == :post ? client.post(path, body) : client.get(path)).to_s
200
+ end
201
+ write_output(format_response(response, options), options)
202
+ 0
203
+ end
204
+
205
+ def output_database_lists(kinds, options)
206
+ if options[:raw_url]
207
+ kinds.each { |kind| @stdout.puts URLBuilder.full_url(options[:base_url], URLBuilder.databases(kind)) }
208
+ return 0
209
+ end
210
+
211
+ client = Client.new(options[:base_url], options[:timeout])
212
+ responses = with_status("Fetching...") do
213
+ kinds.map { |kind| [kind, client.get(URLBuilder.databases(kind))] }
214
+ end
215
+ responses.each_with_index do |(kind, body), index|
216
+ @stdout.puts if index.positive?
217
+ @stdout.puts "#{kind}:"
218
+ @stdout.write(body)
219
+ end
220
+ @stdout.write("\n")
221
+ 0
222
+ end
223
+
224
+ def format_response(response, options)
225
+ return response unless options[:pretty]
226
+
227
+ JSON.pretty_generate(JSON.parse(response))
228
+ rescue JSON::ParserError
229
+ response
230
+ end
231
+
232
+ def version
233
+ @stdout.puts "togows #{VERSION}"
234
+ 0
235
+ end
236
+
237
+ def help(message, status)
238
+ @stderr.puts message if message
239
+ @stdout.puts color_help(<<~HELP)
240
+ Usage:
241
+ togows entry DATABASE ID[,ID...] [FIELD] [options]
242
+ togows search DATABASE QUERY [options]
243
+ togows convert SOURCE.FORMAT [FILE] [options]
244
+ togows databases [entry|search] [options]
245
+ togows ucsc [DATABASE] [TABLE] [QUERY] [options]
246
+
247
+ Commands:
248
+ entry Retrieve database entries
249
+ search Search database entries
250
+ convert Convert data from stdin or FILE
251
+ databases List databases supported by TogoWS
252
+ ucsc Access TogoWS UCSC API
253
+
254
+ Common options:
255
+ -f, --format FORMAT
256
+ --base-url URL
257
+ --timeout SEC
258
+ --raw-url
259
+ --pager
260
+ --pretty
261
+ -h, --help
262
+ HELP
263
+ status
264
+ end
265
+
266
+ def help_for(command, status)
267
+ return help(nil, status) if command.nil?
268
+
269
+ case command
270
+ when "entry" then command_help(:entry, nil, status)
271
+ when "search" then command_help(:search, nil, status)
272
+ when "convert" then command_help(:convert, nil, status)
273
+ when "databases", "dbs" then command_help(:databases, nil, status)
274
+ when "ucsc" then command_help(:ucsc, nil, status)
275
+ else
276
+ error("unknown command: #{command}")
277
+ help(nil, 2)
278
+ end
279
+ end
280
+
281
+ def command_help(command, message, status)
282
+ error(message) if message
283
+ @stdout.puts color_help(Help::COMMANDS.fetch(command))
284
+ status
285
+ end
286
+
287
+ def error(message)
288
+ @stderr.puts "#{@err_color.bold('togows:')} #{message}"
289
+ end
290
+
291
+ def write_output(response, options)
292
+ output = response.end_with?("\n") ? response : "#{response}\n"
293
+ return @stdout.write(output) unless options[:pager] && @stdout.respond_to?(:tty?) && @stdout.tty?
294
+
295
+ command = pager_command
296
+ return @stdout.write(output) if command.empty?
297
+
298
+ write_pager(command, output)
299
+ end
300
+
301
+ def pager_command
302
+ ENV.fetch("PAGER", "less -R").shellsplit
303
+ end
304
+
305
+ def write_pager(command, output)
306
+ IO.popen(command, "w") { |pager| pager.write(output) }
307
+ rescue Errno::EPIPE
308
+ nil
309
+ end
310
+
311
+ def with_status(message)
312
+ return yield unless @stderr.respond_to?(:tty?) && @stderr.tty?
313
+
314
+ @stderr.print "\r#{message}"
315
+ yield
316
+ ensure
317
+ clear_status(message) if @stderr.respond_to?(:tty?) && @stderr.tty?
318
+ end
319
+
320
+ def clear_status(message)
321
+ @stderr.print "\r#{' ' * message.length}\r"
322
+ end
323
+
324
+ def color_help(text)
325
+ highlighted = text.gsub(/^(Usage:)/) { @out_color.bold(Regexp.last_match(1)) }
326
+ color_help_headings(highlighted)
327
+ end
328
+
329
+ def color_help_headings(text)
330
+ headings = [
331
+ "Next arguments:",
332
+ "Common search databases:",
333
+ "Search options:",
334
+ "Convert options:",
335
+ "Examples:",
336
+ "Commands:",
337
+ "Common options:"
338
+ ]
339
+ pattern = /^(#{Regexp.union(headings)})/
340
+ text.gsub(pattern) { @out_color.bold(Regexp.last_match(1)) }
341
+ end
342
+ end
343
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+
6
+ module TogoWS
7
+ class Client
8
+ MAX_REDIRECTS = 5
9
+ USER_AGENT = "togows/#{VERSION}".freeze
10
+
11
+ def initialize(base_url, timeout)
12
+ @base_url = base_url
13
+ @timeout = timeout
14
+ end
15
+
16
+ def get(path)
17
+ uri = URI.parse(URLBuilder.full_url(@base_url, path))
18
+ request(uri) do |http, request_uri|
19
+ req = Net::HTTP::Get.new(request_uri)
20
+ apply_headers(req)
21
+ http.request(req)
22
+ end
23
+ end
24
+
25
+ def post(path, body)
26
+ uri = URI.parse(URLBuilder.full_url(@base_url, path))
27
+ request(uri) do |http, request_uri|
28
+ req = Net::HTTP::Post.new(request_uri)
29
+ apply_headers(req)
30
+ req["Content-Type"] = "text/plain"
31
+ req.body = body
32
+ http.request(req)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def request(uri, redirects = 0, &)
39
+ http = Net::HTTP.new(uri.host, uri.port)
40
+ http.use_ssl = uri.scheme == "https"
41
+ http.open_timeout = @timeout
42
+ http.read_timeout = @timeout
43
+
44
+ response = http.start { |h| yield h, uri.request_uri }
45
+ if response.is_a?(Net::HTTPRedirection) && redirects < MAX_REDIRECTS
46
+ return request(redirect_uri(uri, response), redirects + 1, &)
47
+ end
48
+
49
+ raise HTTPError.new(response.code, response.message, response.body) unless response.is_a?(Net::HTTPSuccess)
50
+
51
+ response.body
52
+ end
53
+
54
+ def redirect_uri(uri, response)
55
+ location = response["location"]
56
+ raise HTTPError.new(response.code, response.message, response.body) if location.nil? || location.empty?
57
+
58
+ uri + location
59
+ end
60
+
61
+ def apply_headers(request)
62
+ request["User-Agent"] = USER_AGENT
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TogoWS
4
+ class Color
5
+ STYLES = {
6
+ bold: 1
7
+ }.freeze
8
+
9
+ def initialize(enabled)
10
+ @enabled = enabled
11
+ end
12
+
13
+ def self.enabled_for?(io)
14
+ io.respond_to?(:tty?) && io.tty? && !ENV.key?("NO_COLOR")
15
+ end
16
+
17
+ def apply(text, *styles)
18
+ return text unless @enabled
19
+
20
+ codes = styles.map { |style| STYLES.fetch(style) }
21
+ "\e[#{codes.join(';')}m#{text}\e[0m"
22
+ end
23
+
24
+ def bold(text)
25
+ apply(text, :bold)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TogoWS
4
+ class Error < StandardError; end
5
+
6
+ class HTTPError < Error
7
+ attr_reader :code, :message_text, :body
8
+
9
+ def initialize(code, message_text, body)
10
+ @code = code.to_i
11
+ @message_text = message_text
12
+ @body = body.to_s
13
+ super("#{code} #{message_text}")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TogoWS
4
+ module Help
5
+ COMMANDS = {
6
+ entry: <<~HELP,
7
+ Usage: togows entry DATABASE ID[,ID...] [FIELD] [options]
8
+
9
+ Next arguments:
10
+ DATABASE Database name such as pubmed, nucleotide, uniprot
11
+ ID[,ID...] One ID or comma-separated IDs
12
+ FIELD Optional field such as authors, title, seq, definition
13
+
14
+ Examples:
15
+ togows entry pubmed 20472643 authors --format json
16
+ togows entry uniprot ACT_YEAST seq
17
+ togows entry nucleotide NC_001138
18
+ HELP
19
+ search: <<~HELP,
20
+ Usage: togows search DATABASE QUERY [options]
21
+
22
+ Next arguments:
23
+ DATABASE Search target. Run `togows databases search` to list choices.
24
+ QUERY Search text or database query
25
+
26
+ Common search databases:
27
+ pubmed Literature
28
+ uniprot Protein entries
29
+ nucleotide Nucleotide entries
30
+ protein Protein sequences
31
+ gene NCBI Gene
32
+ pathway KEGG pathways
33
+
34
+ Search options:
35
+ -o, --offset N
36
+ -n, --limit N Defaults to offset 1 when --offset is omitted
37
+ --count
38
+ --pager
39
+
40
+ Examples:
41
+ togows databases search
42
+ togows search pubmed "lung cancer" --count
43
+ togows search uniprot "FGF19" --limit 10
44
+ togows search uniprot "lung cancer" --offset 1 --limit 10
45
+ HELP
46
+ convert: <<~HELP,
47
+ Usage: togows convert SOURCE.FORMAT [FILE] [options]
48
+
49
+ Next arguments:
50
+ SOURCE.FORMAT Conversion pair such as genbank.gff
51
+ FILE Optional input file; stdin is used when omitted
52
+
53
+ Convert options:
54
+ --to FORMAT
55
+
56
+ Examples:
57
+ {
58
+ printf 'LOCUS TEST 12 bp DNA linear UNA 01-JAN-2000\n'
59
+ printf 'FEATURES Location/Qualifiers\n'
60
+ printf 'ORIGIN\n'
61
+ printf ' 1 acgtacgtacgt\n'
62
+ printf '//\n'
63
+ } | togows convert genbank.fasta
64
+ HELP
65
+ databases: <<~HELP,
66
+ Usage: togows databases [entry|search] [options]
67
+
68
+ Next arguments:
69
+ entry Databases usable with togows entry
70
+ search Databases usable with togows search
71
+
72
+ Examples:
73
+ togows databases
74
+ togows databases entry
75
+ togows databases search
76
+ HELP
77
+ ucsc: <<~HELP
78
+ Usage: togows ucsc [DATABASE] [TABLE] [QUERY] [options]
79
+
80
+ Next arguments:
81
+ DATABASE Genome database such as hg38 or hg19
82
+ TABLE UCSC table such as refGene
83
+ QUERY Optional query fragment such as name2=UVSSA
84
+
85
+ Examples:
86
+ togows ucsc
87
+ togows ucsc hg38
88
+ togows ucsc hg38 refGene name2=UVSSA
89
+ HELP
90
+ }.freeze
91
+ end
92
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module TogoWS
6
+ module URLBuilder
7
+ module_function
8
+
9
+ def entry(database, ids, field = nil, format = nil)
10
+ id_list = Array(ids).join(",")
11
+ parts = ["entry", escape_segment(database), escape_segment(id_list)]
12
+ parts << escape_path(field) if field && !field.empty?
13
+ with_format("/#{parts.join('/')}", format)
14
+ end
15
+
16
+ def search(database, query, offset = nil, limit = nil, count = false, format = nil)
17
+ parts = ["search", escape_segment(database), escape_segment(query)]
18
+ if count
19
+ parts << "count"
20
+ elsif offset && limit
21
+ parts << "#{offset},#{limit}"
22
+ end
23
+ with_format("/#{parts.join('/')}", format)
24
+ end
25
+
26
+ def convert(source, format = nil)
27
+ data_source, target_format = split_source_format(source, format)
28
+ "/convert/#{escape_segment(data_source)}.#{escape_segment(target_format)}"
29
+ end
30
+
31
+ def databases(kind)
32
+ case kind
33
+ when "entry" then "/entry"
34
+ when "search" then "/search"
35
+ else
36
+ raise Error, "database list must be entry or search"
37
+ end
38
+ end
39
+
40
+ def ucsc(database = nil, table = nil, query = nil, offset = nil, limit = nil, format = nil)
41
+ parts = %w[api ucsc]
42
+ parts << escape_segment(database) if database && !database.empty?
43
+ parts << escape_segment(table) if table && !table.empty?
44
+ parts << escape_path(query) if query && !query.empty?
45
+ parts << "#{offset},#{limit}" if offset && limit
46
+ with_format("/#{parts.join('/')}", format)
47
+ end
48
+
49
+ def full_url(base_url, path)
50
+ base = base_url.to_s.sub(%r{/*\z}, "")
51
+ base + path
52
+ end
53
+
54
+ def escape_segment(value)
55
+ URI.encode_www_form_component(value.to_s)
56
+ end
57
+
58
+ def escape_path(value)
59
+ value.to_s.split("/", -1).map { |part| escape_segment(part) }.join("/")
60
+ end
61
+
62
+ def with_format(path, format)
63
+ return path if format.nil? || format.empty?
64
+
65
+ "#{path}.#{escape_segment(format)}"
66
+ end
67
+
68
+ def split_source_format(source, format)
69
+ if format && !format.empty?
70
+ [source, format]
71
+ elsif source.to_s.include?(".")
72
+ source.to_s.split(".", 2)
73
+ else
74
+ raise Error, "missing target format; use SOURCE.FORMAT or --to FORMAT"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TogoWS
4
+ VERSION = "0.1.0"
5
+ end
data/lib/togows.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "togows/version"
4
+ require_relative "togows/color"
5
+ require_relative "togows/error"
6
+ require_relative "togows/help"
7
+ require_relative "togows/url_builder"
8
+ require_relative "togows/client"
9
+ require_relative "togows/cli"
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: togows
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kojix2
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: togows provides a small Ruby command line interface for TogoWS entry,
13
+ search, convert, and UCSC API endpoints.
14
+ email:
15
+ - 2xijok@gmail.com
16
+ executables:
17
+ - togows
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE.txt
22
+ - README.md
23
+ - docs/user-guide.md
24
+ - exe/togows
25
+ - lib/togows.rb
26
+ - lib/togows/cli.rb
27
+ - lib/togows/client.rb
28
+ - lib/togows/color.rb
29
+ - lib/togows/error.rb
30
+ - lib/togows/help.rb
31
+ - lib/togows/url_builder.rb
32
+ - lib/togows/version.rb
33
+ homepage: https://github.com/kojix2/togows-cli
34
+ licenses:
35
+ - MIT
36
+ metadata:
37
+ homepage_uri: https://github.com/kojix2/togows-cli
38
+ source_code_uri: https://github.com/kojix2/togows-cli/tree/main
39
+ bug_tracker_uri: https://github.com/kojix2/togows-cli/issues
40
+ rubygems_mfa_required: 'true'
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '3.1'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 4.0.10
56
+ specification_version: 4
57
+ summary: Dependency-free command line client for the TogoWS REST API
58
+ test_files: []