webri 1.0.5 → 2.0.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/README.md +1 -1
- data/bin/scrape +12 -0
- data/data/3.2.json +30958 -0
- data/data/3.4.json +33900 -0
- data/data/4.0.json +28533 -0
- data/exe/webri +46 -0
- data/lib/scraper.rb +285 -0
- data/lib/webri/version.rb +1 -1
- data/lib/webri.rb +279 -371
- metadata +8 -3
- data/bin/webri +0 -65
data/lib/webri.rb
CHANGED
|
@@ -1,56 +1,75 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'rbconfig'
|
|
4
|
-
require 'open-uri'
|
|
5
|
-
require 'rexml'
|
|
6
|
-
require 'cgi'
|
|
7
4
|
require 'reline'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'json/add/core'
|
|
7
|
+
require 'uri'
|
|
8
|
+
require 'open3'
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
# TODO: Use reline.
|
|
11
|
-
#
|
|
12
|
-
# TODO: Subroutinize.
|
|
13
|
-
# TODO: Make initialization faster.
|
|
10
|
+
require_relative 'scraper'
|
|
14
11
|
|
|
15
12
|
# TODO: Choose dynamically the test names (rather than fixed)?
|
|
16
13
|
# TODO: Test on Linux.
|
|
17
|
-
# TODO: Test all releases(?).
|
|
18
|
-
# TODO: Test all pages(?).
|
|
19
|
-
|
|
20
|
-
# TODO: Make it work for
|
|
14
|
+
# TODO: Test all releases(?).
|
|
15
|
+
# TODO: Test all web pages(?).
|
|
16
|
+
|
|
17
|
+
# TODO: Make it work for:
|
|
18
|
+
# - Array.new
|
|
19
|
+
# - Array::new
|
|
20
|
+
# - Array.sort
|
|
21
|
+
# - Array#sort
|
|
22
|
+
# - .new
|
|
23
|
+
# - ::new
|
|
24
|
+
# - .sort
|
|
25
|
+
# - #sort
|
|
21
26
|
|
|
22
27
|
# TODO: Support pager.
|
|
23
28
|
|
|
24
29
|
# TODO: Support .webrirc.
|
|
25
30
|
# TODO: Make it save options to .webrirc.
|
|
26
31
|
# TODO: Make it show .webrirc.
|
|
32
|
+
# TODO: Support ENV.
|
|
27
33
|
|
|
34
|
+
# TODO: Support favorites.
|
|
35
|
+
# TODO: Support recents.
|
|
36
|
+
# TODO: Support direct in REPL; e.g., Array.sort.
|
|
37
|
+
# TODO: Support direct from command-line.
|
|
38
|
+
# TODO: Support partial on command-line, into REPL.
|
|
39
|
+
#
|
|
40
|
+
# TODO: Support alternate character for '\#' on command-line. ('3'?)
|
|
41
|
+
#
|
|
42
|
+
# TODO: Token begins with character, period, colon, or hashmark;
|
|
43
|
+
# anything else could signal a special op.
|
|
44
|
+
#
|
|
28
45
|
# A class to display Ruby online HTML documentation.
|
|
29
46
|
class WebRI
|
|
30
47
|
|
|
31
48
|
# Site of the official documentation.
|
|
32
|
-
|
|
49
|
+
DOC_SITE = 'https://docs.ruby-lang.org/en/'
|
|
33
50
|
|
|
34
|
-
|
|
35
|
-
# and build our @index_for_type.
|
|
36
|
-
def initialize(options = {})
|
|
37
|
-
msg = <<MSG
|
|
38
|
-
The release of Ruby 4.0 broke webri in a couple of ways:
|
|
51
|
+
PROMPT = "('?' for help) webri> "
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
CLASS = 'class/module'
|
|
54
|
+
SINGLETON = 'singleton method'
|
|
55
|
+
INSTANCE = 'instance method'
|
|
56
|
+
FILE = 'file'
|
|
43
57
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
attr_accessor :release_name,
|
|
59
|
+
:href_for_class_name,
|
|
60
|
+
:href_for_file_name,
|
|
61
|
+
:href_for_singleton_method_name,
|
|
62
|
+
:href_for_instance_method_name
|
|
63
|
+
|
|
64
|
+
def initialize(release_name = nil, options = {})
|
|
65
|
+
self.release_name = set_doc_release(release_name)
|
|
48
66
|
capture_options(options)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
# data_file_path = File.join('data', self.release_name + '.json')
|
|
68
|
+
data_file_path = File.expand_path("../data/#{self.release_name}.json", __dir__)
|
|
69
|
+
json = open(data_file_path).read
|
|
70
|
+
@data = JSON.parse(json, create_additions: true)
|
|
71
|
+
make_groups
|
|
52
72
|
print_info if @info
|
|
53
|
-
print @noreline
|
|
54
73
|
if os_type == :linux && !@noreline
|
|
55
74
|
repl_reline
|
|
56
75
|
else
|
|
@@ -58,13 +77,36 @@ MSG
|
|
|
58
77
|
end
|
|
59
78
|
end
|
|
60
79
|
|
|
80
|
+
def make_groups
|
|
81
|
+
self.href_for_class_name = {}
|
|
82
|
+
self.href_for_file_name = {}
|
|
83
|
+
self.href_for_singleton_method_name = {}
|
|
84
|
+
self.href_for_instance_method_name = {}
|
|
85
|
+
@data['hrefs_for_name'].group_by do |name, hrefs|
|
|
86
|
+
case
|
|
87
|
+
when name.start_with?('ruby:')
|
|
88
|
+
self.href_for_file_name[name] = hrefs.first
|
|
89
|
+
when name.start_with?('#')
|
|
90
|
+
self.href_for_instance_method_name[name] = hrefs
|
|
91
|
+
when name.start_with?('::')
|
|
92
|
+
self.href_for_singleton_method_name[name] = hrefs
|
|
93
|
+
else
|
|
94
|
+
self.href_for_class_name[name] = hrefs.first
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
61
99
|
def repl_plain # Read-evaluate-print loop, without Reline.
|
|
62
100
|
while true
|
|
63
|
-
|
|
101
|
+
$stdout.write(PROMPT)
|
|
64
102
|
$stdout.flush
|
|
65
103
|
response = $stdin.gets.chomp
|
|
66
104
|
exit if response == 'exit'
|
|
67
105
|
next if response.empty?
|
|
106
|
+
if response.start_with?('?')
|
|
107
|
+
help(response)
|
|
108
|
+
next
|
|
109
|
+
end
|
|
68
110
|
if response.split(' ').size > 1
|
|
69
111
|
puts "One name at a time, please."
|
|
70
112
|
next
|
|
@@ -80,18 +122,11 @@ MSG
|
|
|
80
122
|
end
|
|
81
123
|
|
|
82
124
|
begin
|
|
83
|
-
completion_words= []
|
|
84
|
-
@index_for_type.each_pair do |type, index|
|
|
85
|
-
if type == :page
|
|
86
|
-
completion_words += index.keys.map {|name| 'ruby:' + name }
|
|
87
|
-
else
|
|
88
|
-
completion_words += index.keys
|
|
89
|
-
end
|
|
90
|
-
end
|
|
125
|
+
completion_words= @data['hrefs_for_name'].keys.sort
|
|
91
126
|
Reline.completion_proc = proc { |word|
|
|
92
127
|
completion_words
|
|
93
128
|
}
|
|
94
|
-
while line = Reline.readline(
|
|
129
|
+
while line = Reline.readline(PROMPT, true)
|
|
95
130
|
case line.chomp
|
|
96
131
|
when 'exit'
|
|
97
132
|
exit 0
|
|
@@ -113,147 +148,49 @@ MSG
|
|
|
113
148
|
puts
|
|
114
149
|
end
|
|
115
150
|
|
|
116
|
-
def set_doc_release
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
supported_releases.push(release)
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
all_releases = [master_release] + supported_releases + unsupported_releases
|
|
136
|
-
if @doc_release
|
|
137
|
-
unless all_releases.include?(@doc_release)
|
|
138
|
-
puts "Unknown documentation release: #{@doc_release}"
|
|
139
|
-
puts "Available releases: #{all_releases.join(' ')}"
|
|
140
|
-
exit
|
|
141
|
-
end
|
|
142
|
-
else
|
|
143
|
-
a = RUBY_VERSION.split('.')
|
|
144
|
-
@doc_release ||= a[0..1].join('.')
|
|
145
|
-
end
|
|
151
|
+
def set_doc_release(release_name)
|
|
152
|
+
# If doc release not specified, get it from the local Ruby version.
|
|
153
|
+
unless release_name
|
|
154
|
+
s = RUBY_VERSION.split('.')
|
|
155
|
+
release_name ||= s[0..1].join('.')
|
|
156
|
+
puts "Documentation release defaulting to #{release_name} (the Ruby version you're running)."
|
|
157
|
+
release_name
|
|
158
|
+
end
|
|
159
|
+
# If the doc release is not available, let them choose.
|
|
160
|
+
release_names = Scraper.release_names
|
|
161
|
+
unless release_names.include?(release_name)
|
|
162
|
+
puts "Found no documentation release #{release_name}."
|
|
163
|
+
puts "Releases:"
|
|
164
|
+
release_name = get_choice_(release_names, required: true)
|
|
165
|
+
end
|
|
166
|
+
release_name
|
|
146
167
|
end
|
|
147
168
|
|
|
148
169
|
def print_info
|
|
149
|
-
puts "Ruby documentation
|
|
150
|
-
puts "
|
|
151
|
-
puts "
|
|
170
|
+
puts "Ruby documentation:"
|
|
171
|
+
puts " Release: #{release_name}"
|
|
172
|
+
puts " Site: #{DOC_SITE}"
|
|
173
|
+
puts " Snapshot: #{@data['timestamp']}"
|
|
152
174
|
puts "Names:"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
175
|
+
puts format(" %5d %s", href_for_file_name.size, 'Files')
|
|
176
|
+
puts format(" %5d %s", href_for_class_name.size, 'Classes and modules')
|
|
177
|
+
count = 0
|
|
178
|
+
href_for_singleton_method_name.each_pair do |name, href_for_name|
|
|
179
|
+
count += href_for_name.size
|
|
180
|
+
end
|
|
181
|
+
puts format(" %5d %s", count, 'Singleton methods')
|
|
182
|
+
count = 0
|
|
183
|
+
href_for_instance_method_name.each_pair do |name, href_for_name|
|
|
184
|
+
count += href_for_name.size
|
|
185
|
+
end
|
|
186
|
+
puts format(" %5d %s", count, 'Instance methods')
|
|
156
187
|
exit
|
|
157
188
|
end
|
|
158
189
|
|
|
159
|
-
def build_indexes
|
|
160
|
-
# Index for each type of entry.
|
|
161
|
-
# Each index has a hash; key is name, value is array of URIs.
|
|
162
|
-
@index_for_type = {
|
|
163
|
-
class: {}, # Has both classes and modules.
|
|
164
|
-
singleton_method: {},
|
|
165
|
-
instance_method: {},
|
|
166
|
-
page: {},
|
|
167
|
-
}
|
|
168
|
-
# Iterate over the lines of the TOC page.
|
|
169
|
-
lines = @toc_html.split("\n")
|
|
170
|
-
i = 0
|
|
171
|
-
while i < lines.count
|
|
172
|
-
item_line = lines[i]
|
|
173
|
-
i += 1
|
|
174
|
-
next unless item_line.match('<li class="(\w+)"')
|
|
175
|
-
class_attr_value = $1 # Save for later.
|
|
176
|
-
# We have a pair of lines such as:
|
|
177
|
-
# <li class="file">
|
|
178
|
-
# <a href="COPYING.html">COPYING</a>
|
|
179
|
-
anchor_line = lines[i] # Second line of pair.
|
|
180
|
-
# Consume anchor_line.
|
|
181
|
-
i += 1
|
|
182
|
-
# We capture variables thus:
|
|
183
|
-
# - +type+ is the value of attribute 'class'.
|
|
184
|
-
# - +path+ is the value of attribute 'href'.
|
|
185
|
-
# - +full_name+ is the HTML text.
|
|
186
|
-
type = class_attr_value
|
|
187
|
-
_, path, rest = anchor_line.split('"')
|
|
188
|
-
full_name = rest.split(/<|>/)[1]
|
|
189
|
-
full_name = CGI.unescapeHTML(full_name)
|
|
190
|
-
case type
|
|
191
|
-
when 'class', 'module'
|
|
192
|
-
index = @index_for_type[:class]
|
|
193
|
-
if index.include?(full_name)
|
|
194
|
-
entry = index[full_name]
|
|
195
|
-
else
|
|
196
|
-
entry = ClassEntry.new(full_name)
|
|
197
|
-
index[full_name] = entry
|
|
198
|
-
end
|
|
199
|
-
entry.paths.push(path) unless entry.paths.include?(path)
|
|
200
|
-
when 'file'
|
|
201
|
-
index = @index_for_type[:page]
|
|
202
|
-
if index.include?(full_name)
|
|
203
|
-
entry = index[full_name]
|
|
204
|
-
else
|
|
205
|
-
entry = FileEntry.new(full_name)
|
|
206
|
-
index[full_name] = entry
|
|
207
|
-
end
|
|
208
|
-
entry.paths.push(path) unless entry.paths.include?(path)
|
|
209
|
-
when 'method'
|
|
210
|
-
case anchor_line
|
|
211
|
-
when /method-c-/
|
|
212
|
-
index = @index_for_type[:singleton_method]
|
|
213
|
-
if index.include?(full_name)
|
|
214
|
-
entry = index[full_name]
|
|
215
|
-
else
|
|
216
|
-
entry = SingletonMethodEntry.new(full_name)
|
|
217
|
-
index[full_name] = entry
|
|
218
|
-
end
|
|
219
|
-
entry.paths.push(path) unless entry.paths.include?(path)
|
|
220
|
-
when /method-i-/
|
|
221
|
-
index = @index_for_type[:instance_method]
|
|
222
|
-
if index.include?(full_name)
|
|
223
|
-
entry = index[full_name]
|
|
224
|
-
else
|
|
225
|
-
entry = InstanceMethodEntry.new(full_name)
|
|
226
|
-
index[full_name] = entry
|
|
227
|
-
end
|
|
228
|
-
entry.paths.push(path) unless entry.paths.include?(path)
|
|
229
|
-
else
|
|
230
|
-
fail anchor_line
|
|
231
|
-
end
|
|
232
|
-
else
|
|
233
|
-
fail class_attr_val
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
190
|
def capture_options(options)
|
|
239
191
|
@noop = options[:noop]
|
|
240
192
|
@info = options[:info]
|
|
241
193
|
@noreline = options[:noreline]
|
|
242
|
-
@doc_release = options[:release]
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def get_toc_html
|
|
246
|
-
# Construct the doc release; e.g., '3.4'.
|
|
247
|
-
# Get the doc table of contents as a temp file.
|
|
248
|
-
@toc_url = DocSite + @doc_release + '/table_of_contents.html'
|
|
249
|
-
begin
|
|
250
|
-
toc_file = URI.open(@toc_url)
|
|
251
|
-
@toc_html = toc_file.read
|
|
252
|
-
rescue Socket::ResolutionError => x
|
|
253
|
-
message = "#{x.class}: #{x.message}\nPossibly not connected to internet."
|
|
254
|
-
$stderr.puts(message)
|
|
255
|
-
exit
|
|
256
|
-
end
|
|
257
194
|
end
|
|
258
195
|
|
|
259
196
|
class Entry
|
|
@@ -333,15 +270,15 @@ MSG
|
|
|
333
270
|
# Figure out what's asked for.
|
|
334
271
|
case
|
|
335
272
|
when name.match(/^[A-Z]/)
|
|
336
|
-
show_class(name
|
|
273
|
+
show_class(name)
|
|
337
274
|
when %w[fatal fata fat fa f].include?(name)
|
|
338
|
-
show_class(name
|
|
275
|
+
show_class(name)
|
|
339
276
|
when name.start_with?('ruby:')
|
|
340
|
-
show_file(name
|
|
277
|
+
show_file(name)
|
|
341
278
|
when name.start_with?('::')
|
|
342
|
-
show_singleton_method(name
|
|
279
|
+
show_singleton_method(name)
|
|
343
280
|
when name.start_with?('#')
|
|
344
|
-
show_instance_method(name
|
|
281
|
+
show_instance_method(name)
|
|
345
282
|
when name == '@help'
|
|
346
283
|
show_help
|
|
347
284
|
when name == '@readme'
|
|
@@ -351,207 +288,123 @@ MSG
|
|
|
351
288
|
# when name.match(/^[a-z]/)
|
|
352
289
|
# show_method(name, @index_for_type[:singleton_method], @index_for_type[:instance_method])
|
|
353
290
|
else
|
|
291
|
+
|
|
354
292
|
puts "No documentation available for name '#{name}'."
|
|
355
293
|
end
|
|
356
294
|
end
|
|
357
295
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
key.start_with?(name)
|
|
365
|
-
end
|
|
366
|
-
# Find paths for selected_choices
|
|
367
|
-
selected_paths = []
|
|
368
|
-
selected_entries.each_pair do |name, entry|
|
|
369
|
-
entry.paths.each do |path|
|
|
370
|
-
selected_paths.push(path)
|
|
371
|
-
end
|
|
372
|
-
end
|
|
373
|
-
case selected_paths.size
|
|
374
|
-
when 1
|
|
375
|
-
selected_choices = ClassEntry.choices(selected_entries)
|
|
376
|
-
choice = selected_choices.keys.first
|
|
377
|
-
path = selected_choices.values.first
|
|
378
|
-
puts "Found one class/module name starting with '#{name}'\n #{choice}"
|
|
379
|
-
full_name = ClassEntry.full_name_for_choice(choice)
|
|
380
|
-
if name != full_name
|
|
381
|
-
message = "Open page #{path}?"
|
|
382
|
-
return unless get_boolean_answer(message)
|
|
383
|
-
end
|
|
384
|
-
path
|
|
385
|
-
when 0
|
|
386
|
-
puts "Found no class/module name starting with '#{name}'."
|
|
387
|
-
message = "Show #{all_choices.size} class/module names?"
|
|
388
|
-
return unless get_boolean_answer(message)
|
|
389
|
-
choice_index = get_choice(all_choices.keys)
|
|
390
|
-
return if choice_index.nil?
|
|
391
|
-
path = all_choices[choice_index]
|
|
392
|
-
else
|
|
393
|
-
selected_choices = ClassEntry.choices(selected_entries)
|
|
394
|
-
puts "Found #{selected_choices.size} class/module names starting with '#{name}'."
|
|
395
|
-
message = "Show #{selected_choices.size} class/module names?'"
|
|
396
|
-
return unless get_boolean_answer(message)
|
|
397
|
-
key = get_choice(selected_choices.keys)
|
|
398
|
-
return if key.nil?
|
|
399
|
-
path = selected_choices[key]
|
|
296
|
+
def get_choice(situation, choices, type)
|
|
297
|
+
puts situation
|
|
298
|
+
count = choices.size
|
|
299
|
+
if count > 20
|
|
300
|
+
message = "Show #{count} #{type} names?"
|
|
301
|
+
return nil unless get_boolean_answer(message)
|
|
400
302
|
end
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
return unless get_boolean_answer(message)
|
|
432
|
-
end
|
|
433
|
-
path
|
|
434
|
-
when 0
|
|
435
|
-
puts "Found no page name starting with '#{name}'."
|
|
436
|
-
message = "Show names of all #{all_choices.size} pages?"
|
|
437
|
-
return unless get_boolean_answer(message)
|
|
438
|
-
key = get_choice(all_choices.keys)
|
|
439
|
-
return if key.nil?
|
|
440
|
-
path = all_choices[key]
|
|
441
|
-
else
|
|
442
|
-
selected_choices = FileEntry.choices(selected_entries)
|
|
443
|
-
puts "Found #{selected_choices.size} page names starting with '#{name}'."
|
|
444
|
-
message = "Show #{selected_choices.size} names?'"
|
|
445
|
-
return unless get_boolean_answer(message)
|
|
446
|
-
key = get_choice(selected_choices.keys)
|
|
447
|
-
return if key.nil?
|
|
448
|
-
path = selected_choices[key]
|
|
449
|
-
end
|
|
450
|
-
uri = Entry.uri(path)
|
|
451
|
-
open_page(name, uri)
|
|
452
|
-
end
|
|
453
|
-
|
|
454
|
-
# Show singleton method.
|
|
455
|
-
def show_singleton_method(name, singleton_method_index)
|
|
456
|
-
# Target page is a singleton method such as ::new.
|
|
457
|
-
all_entries = @index_for_type[:singleton_method]
|
|
458
|
-
all_choices = SingletonMethodEntry.choices(all_entries)
|
|
459
|
-
# Find entries whose names that start with name.
|
|
460
|
-
selected_entries = all_entries.select do |key, value|
|
|
461
|
-
key.start_with?(name)
|
|
462
|
-
end
|
|
463
|
-
# Find paths for selected_choices
|
|
464
|
-
selected_paths = []
|
|
465
|
-
selected_entries.each_pair do |name, entry|
|
|
466
|
-
entry.paths.each do |path|
|
|
467
|
-
selected_paths.push(path)
|
|
468
|
-
end
|
|
469
|
-
end
|
|
470
|
-
selected_choices = SingletonMethodEntry.choices(selected_entries)
|
|
471
|
-
case selected_paths.size
|
|
472
|
-
when 1
|
|
473
|
-
full_name = selected_entries.keys.first
|
|
474
|
-
path = selected_choices.values.first
|
|
475
|
-
puts "Found one singleton method name starting with '#{name}'\n #{full_name}"
|
|
476
|
-
if name != full_name
|
|
477
|
-
uri = URI.parse(path)
|
|
478
|
-
message = "Open page #{uri.path} at method #{full_name}?"
|
|
479
|
-
return unless get_boolean_answer(message)
|
|
480
|
-
end
|
|
481
|
-
path
|
|
482
|
-
when 0
|
|
483
|
-
puts "Found no singleton method name starting with '#{name}'."
|
|
484
|
-
message = "Show names of all #{all_choices.size} singleton methods?"
|
|
485
|
-
return unless get_boolean_answer(message)
|
|
486
|
-
choice = get_choice(all_choices.keys)
|
|
487
|
-
return if choice.nil?
|
|
488
|
-
full_name = SingletonMethodEntry.full_name_for_choice(choice)
|
|
489
|
-
path = all_choices[choice]
|
|
490
|
-
else
|
|
491
|
-
puts "Found #{selected_paths.size} singleton method names starting with '#{name}'."
|
|
492
|
-
message = "Show #{selected_paths.size} names?'"
|
|
493
|
-
return unless get_boolean_answer(message)
|
|
494
|
-
choice = get_choice(selected_choices.keys)
|
|
495
|
-
return if choice.nil?
|
|
496
|
-
full_name = SingletonMethodEntry.full_name_for_choice(choice)
|
|
497
|
-
path = all_choices[choice]
|
|
498
|
-
end
|
|
499
|
-
uri = Entry.uri(path)
|
|
500
|
-
open_page(full_name, uri)
|
|
501
|
-
end
|
|
502
|
-
|
|
503
|
-
# Show instance method.
|
|
504
|
-
def show_instance_method(name, instance_method_index)
|
|
505
|
-
# Target page is an instance method such as #to_s.
|
|
506
|
-
all_entries = @index_for_type[:instance_method]
|
|
507
|
-
all_choices = InstanceMethodEntry.choices(all_entries)
|
|
508
|
-
# Find entries whose names that start with name.
|
|
509
|
-
selected_entries = all_entries.select do |key, value|
|
|
510
|
-
key.start_with?(name)
|
|
511
|
-
end
|
|
512
|
-
# Find paths for selected_choices
|
|
513
|
-
selected_paths = []
|
|
514
|
-
selected_entries.each_pair do |name, entry|
|
|
515
|
-
entry.paths.each do |path|
|
|
516
|
-
selected_paths.push(path)
|
|
303
|
+
get_choice_(choices)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Show web page for selected file or class name.
|
|
307
|
+
def show_web_page_for_file_or_class(partial_name, href_for_name, type)
|
|
308
|
+
# Find names that start with partial name (which may in fact be the full name).
|
|
309
|
+
selected_names = href_for_name.keys.select do |name|
|
|
310
|
+
name.start_with?(partial_name)
|
|
311
|
+
end
|
|
312
|
+
count = selected_names.size
|
|
313
|
+
selected_name =
|
|
314
|
+
case count
|
|
315
|
+
when 0
|
|
316
|
+
situation = "Found no #{type} name starting with '#{partial_name}'."
|
|
317
|
+
selected_name = get_choice(situation, href_for_name.keys, type)
|
|
318
|
+
return if selected_name.nil?
|
|
319
|
+
selected_name
|
|
320
|
+
when 1
|
|
321
|
+
full_name = selected_names.first
|
|
322
|
+
puts "Found one #{type} name starting with '#{partial_name}': #{full_name}"
|
|
323
|
+
if partial_name != full_name
|
|
324
|
+
message = "Open web page #{full_name}?"
|
|
325
|
+
return unless get_boolean_answer(message)
|
|
326
|
+
end
|
|
327
|
+
full_name
|
|
328
|
+
else
|
|
329
|
+
situation = "Found #{count} #{type} names starting with '#{partial_name}'."
|
|
330
|
+
selected_name = get_choice(situation, selected_names, type)
|
|
331
|
+
return if selected_name.nil?
|
|
332
|
+
selected_name
|
|
517
333
|
end
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
334
|
+
href = href_for_name[selected_name]
|
|
335
|
+
show_web_page(selected_name, href)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Show web page for selected class name.
|
|
339
|
+
def show_class(partial_name)
|
|
340
|
+
show_web_page_for_file_or_class(partial_name, href_for_class_name, CLASS)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Show web page for selected file name.
|
|
344
|
+
def show_file(partial_name)
|
|
345
|
+
show_web_page_for_file_or_class(partial_name, href_for_file_name, FILE)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Show web page for selected method name.
|
|
349
|
+
def show_web_page_for_method(partial_name, href_for_name, type)
|
|
350
|
+
# Find names that start with partial name (which may in fact be the full name).
|
|
351
|
+
selected_names = href_for_name.keys.select do |name|
|
|
352
|
+
name.start_with?(partial_name)
|
|
353
|
+
end
|
|
354
|
+
count = selected_names.size
|
|
355
|
+
selected_name =
|
|
356
|
+
case count
|
|
357
|
+
when 0
|
|
358
|
+
situation = "Found no #{type} name starting with '#{partial_name}'."
|
|
359
|
+
selected_name = get_choice(situation, href_for_name, type)
|
|
360
|
+
return if selected_name.nil?
|
|
361
|
+
selected_name
|
|
362
|
+
when 1
|
|
363
|
+
full_name = selected_names.first
|
|
364
|
+
puts "Found one #{type} name starting with '#{partial_name}': #{full_name}"
|
|
365
|
+
if partial_name != full_name
|
|
366
|
+
message = "Open web page #{full_name}?"
|
|
367
|
+
return unless get_boolean_answer(message)
|
|
368
|
+
end
|
|
369
|
+
full_name
|
|
370
|
+
else
|
|
371
|
+
situation = "Found #{count} #{type} names starting with '#{partial_name}'."
|
|
372
|
+
selected_name = get_choice(situation, selected_names, type)
|
|
373
|
+
return if selected_name.nil?
|
|
374
|
+
selected_name
|
|
529
375
|
end
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
full_name = InstanceMethodEntry.full_name_for_choice(choice)
|
|
376
|
+
qualified_names = []
|
|
377
|
+
@data['classes_for_method'][selected_name].each do |class_name|
|
|
378
|
+
qualified_names << "#{class_name}#{selected_name}"
|
|
379
|
+
end
|
|
380
|
+
count = qualified_names.size
|
|
381
|
+
if count == 1
|
|
382
|
+
puts "Found one #{CLASS} that has method '#{selected_name}'."
|
|
383
|
+
qualified_name = qualified_names.first
|
|
539
384
|
else
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
return
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
385
|
+
situation = "Found #{count} #{type} names that have method '#{selected_name}'."
|
|
386
|
+
qualified_name = get_choice(situation, qualified_names, type)
|
|
387
|
+
return if qualified_name.nil?
|
|
388
|
+
end
|
|
389
|
+
method_href = href_for_name[selected_name]
|
|
390
|
+
class_name = qualified_name.sub(selected_name, '')
|
|
391
|
+
href = "#{class_name}.html#{method_href}"
|
|
392
|
+
show_web_page(selected_name, href)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Show web page for singleton method name.
|
|
396
|
+
def show_singleton_method(partial_name)
|
|
397
|
+
show_web_page_for_method(partial_name, href_for_singleton_method_name, SINGLETON)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Show web page for instance method name.
|
|
401
|
+
def show_instance_method(partial_name)
|
|
402
|
+
show_web_page_for_method(partial_name, href_for_instance_method_name, INSTANCE)
|
|
550
403
|
end
|
|
551
404
|
|
|
552
405
|
def show_help
|
|
553
406
|
puts 'Showing help.'
|
|
554
|
-
puts `ruby
|
|
407
|
+
puts `ruby exe/webri --help`
|
|
555
408
|
end
|
|
556
409
|
|
|
557
410
|
def show_readme
|
|
@@ -559,7 +412,7 @@ MSG
|
|
|
559
412
|
end
|
|
560
413
|
|
|
561
414
|
# Present choices; return choice.
|
|
562
|
-
def
|
|
415
|
+
def get_choice_(choices, required: false)
|
|
563
416
|
index = nil
|
|
564
417
|
range = (0..choices.size - 1)
|
|
565
418
|
until range.include?(index)
|
|
@@ -568,14 +421,20 @@ MSG
|
|
|
568
421
|
puts " #{s}: #{choice}"
|
|
569
422
|
end
|
|
570
423
|
while true
|
|
571
|
-
|
|
424
|
+
message = if required
|
|
425
|
+
'Type a number to choose: '
|
|
426
|
+
else
|
|
427
|
+
'Type a number to choose, or Return to skip: '
|
|
428
|
+
end
|
|
429
|
+
print message
|
|
572
430
|
$stdout.flush
|
|
573
431
|
response = $stdin.gets
|
|
574
432
|
case response
|
|
575
433
|
when /(\d+)/
|
|
576
|
-
|
|
434
|
+
index = $1.to_i
|
|
435
|
+
return choices[index] if index < choices.size
|
|
577
436
|
when "\n"
|
|
578
|
-
return nil
|
|
437
|
+
return nil unless required
|
|
579
438
|
else
|
|
580
439
|
|
|
581
440
|
end
|
|
@@ -597,8 +456,9 @@ MSG
|
|
|
597
456
|
end
|
|
598
457
|
|
|
599
458
|
# Open URL in browser.
|
|
600
|
-
def
|
|
601
|
-
|
|
459
|
+
def show_web_page(name, href)
|
|
460
|
+
href.gsub!('::', '/')
|
|
461
|
+
uri = URI.parse(File.join(DOC_SITE, release_name, href))
|
|
602
462
|
open_uri(name, uri)
|
|
603
463
|
end
|
|
604
464
|
|
|
@@ -640,10 +500,58 @@ MSG
|
|
|
640
500
|
puts message
|
|
641
501
|
command = "#{opener_name} #{full_url}"
|
|
642
502
|
if @noop
|
|
643
|
-
puts "Command: '#{command}'"
|
|
503
|
+
# puts "Command: '#{command}'"
|
|
644
504
|
else
|
|
645
|
-
system(command)
|
|
505
|
+
# system(command)
|
|
506
|
+
Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def self.get_webri_root_dir
|
|
512
|
+
webri_root_dir = `git rev-parse --show-toplevel`.chomp
|
|
513
|
+
if $?.success? && File.basename(webri_root_dir) == 'webri'
|
|
514
|
+
return webri_root_dir
|
|
646
515
|
end
|
|
516
|
+
message = "Current working directory must be in a webri project, not #{Dir.pwd}."
|
|
517
|
+
puts message
|
|
518
|
+
exit
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def self.check_release_name(release_name)
|
|
522
|
+
release_names = Scraper.release_names
|
|
523
|
+
unless release_names.include?(release_name)
|
|
524
|
+
message = "Release must be one of #{release_names.inspect}, not #{release_name.inspect}."
|
|
525
|
+
puts message
|
|
526
|
+
exit 1
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def help(response)
|
|
531
|
+
if response == '?'
|
|
532
|
+
puts <<HELP
|
|
533
|
+
Type:
|
|
534
|
+
- 'exit' to exit webri.
|
|
535
|
+
- #{CLASS.capitalize} name (full or partial) to see #{CLASS} names:
|
|
536
|
+
- 'Array' (full name, not the start of other names).
|
|
537
|
+
- 'Ar' (partial name).
|
|
538
|
+
- #{SINGLETON.capitalize} name (full or partial) to see #{SINGLETON} names:
|
|
539
|
+
- '::tanh' (full name, not the start of other names).
|
|
540
|
+
- '::ta' (partial name).
|
|
541
|
+
- #{INSTANCE.capitalize} name (full or partial) to see #{INSTANCE} names:
|
|
542
|
+
- '#query=' (full name, not the start of other names).
|
|
543
|
+
- '#qu' (partial name).
|
|
544
|
+
- #{FILE.capitalize}name (full or partial) to see #{FILE} names:
|
|
545
|
+
- 'ruby:syntax_rdoc' (full name, not the start of other names).
|
|
546
|
+
- 'ruby:syntax' (partial name).
|
|
547
|
+
HELP
|
|
548
|
+
else
|
|
549
|
+
p response
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def help_main
|
|
554
|
+
|
|
647
555
|
end
|
|
648
556
|
|
|
649
557
|
end
|