webri 1.0.4 → 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 +281 -362
- metadata +8 -3
- data/bin/webri +0 -65
data/lib/webri.rb
CHANGED
|
@@ -1,45 +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/'
|
|
50
|
+
|
|
51
|
+
PROMPT = "('?' for help) webri> "
|
|
33
52
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
CLASS = 'class/module'
|
|
54
|
+
SINGLETON = 'singleton method'
|
|
55
|
+
INSTANCE = 'instance method'
|
|
56
|
+
FILE = 'file'
|
|
57
|
+
|
|
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)
|
|
37
66
|
capture_options(options)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
41
72
|
print_info if @info
|
|
42
|
-
print @noreline
|
|
43
73
|
if os_type == :linux && !@noreline
|
|
44
74
|
repl_reline
|
|
45
75
|
else
|
|
@@ -47,13 +77,36 @@ class WebRI
|
|
|
47
77
|
end
|
|
48
78
|
end
|
|
49
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
|
+
|
|
50
99
|
def repl_plain # Read-evaluate-print loop, without Reline.
|
|
51
100
|
while true
|
|
52
|
-
|
|
101
|
+
$stdout.write(PROMPT)
|
|
53
102
|
$stdout.flush
|
|
54
103
|
response = $stdin.gets.chomp
|
|
55
104
|
exit if response == 'exit'
|
|
56
105
|
next if response.empty?
|
|
106
|
+
if response.start_with?('?')
|
|
107
|
+
help(response)
|
|
108
|
+
next
|
|
109
|
+
end
|
|
57
110
|
if response.split(' ').size > 1
|
|
58
111
|
puts "One name at a time, please."
|
|
59
112
|
next
|
|
@@ -69,18 +122,11 @@ class WebRI
|
|
|
69
122
|
end
|
|
70
123
|
|
|
71
124
|
begin
|
|
72
|
-
completion_words= []
|
|
73
|
-
@index_for_type.each_pair do |type, index|
|
|
74
|
-
if type == :page
|
|
75
|
-
completion_words += index.keys.map {|name| 'ruby:' + name }
|
|
76
|
-
else
|
|
77
|
-
completion_words += index.keys
|
|
78
|
-
end
|
|
79
|
-
end
|
|
125
|
+
completion_words= @data['hrefs_for_name'].keys.sort
|
|
80
126
|
Reline.completion_proc = proc { |word|
|
|
81
127
|
completion_words
|
|
82
128
|
}
|
|
83
|
-
while line = Reline.readline(
|
|
129
|
+
while line = Reline.readline(PROMPT, true)
|
|
84
130
|
case line.chomp
|
|
85
131
|
when 'exit'
|
|
86
132
|
exit 0
|
|
@@ -102,147 +148,49 @@ class WebRI
|
|
|
102
148
|
puts
|
|
103
149
|
end
|
|
104
150
|
|
|
105
|
-
def set_doc_release
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
supported_releases.push(release)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
all_releases = [master_release] + supported_releases + unsupported_releases
|
|
125
|
-
if @doc_release
|
|
126
|
-
unless all_releases.include?(@doc_release)
|
|
127
|
-
puts "Unknown documentation release: #{@doc_release}"
|
|
128
|
-
puts "Available releases: #{all_releases.join(' ')}"
|
|
129
|
-
exit
|
|
130
|
-
end
|
|
131
|
-
else
|
|
132
|
-
a = RUBY_VERSION.split('.')
|
|
133
|
-
@doc_release ||= a[0..1].join('.')
|
|
134
|
-
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
|
|
135
167
|
end
|
|
136
168
|
|
|
137
169
|
def print_info
|
|
138
|
-
puts "Ruby documentation
|
|
139
|
-
puts "
|
|
140
|
-
puts "
|
|
170
|
+
puts "Ruby documentation:"
|
|
171
|
+
puts " Release: #{release_name}"
|
|
172
|
+
puts " Site: #{DOC_SITE}"
|
|
173
|
+
puts " Snapshot: #{@data['timestamp']}"
|
|
141
174
|
puts "Names:"
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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')
|
|
145
187
|
exit
|
|
146
188
|
end
|
|
147
189
|
|
|
148
|
-
def build_indexes
|
|
149
|
-
# Index for each type of entry.
|
|
150
|
-
# Each index has a hash; key is name, value is array of URIs.
|
|
151
|
-
@index_for_type = {
|
|
152
|
-
class: {}, # Has both classes and modules.
|
|
153
|
-
singleton_method: {},
|
|
154
|
-
instance_method: {},
|
|
155
|
-
page: {},
|
|
156
|
-
}
|
|
157
|
-
# Iterate over the lines of the TOC page.
|
|
158
|
-
lines = @toc_html.split("\n")
|
|
159
|
-
i = 0
|
|
160
|
-
while i < lines.count
|
|
161
|
-
item_line = lines[i]
|
|
162
|
-
i += 1
|
|
163
|
-
next unless item_line.match('<li class="(\w+)"')
|
|
164
|
-
class_attr_value = $1 # Save for later.
|
|
165
|
-
# We have a pair of lines such as:
|
|
166
|
-
# <li class="file">
|
|
167
|
-
# <a href="COPYING.html">COPYING</a>
|
|
168
|
-
anchor_line = lines[i] # Second line of pair.
|
|
169
|
-
# Consume anchor_line.
|
|
170
|
-
i += 1
|
|
171
|
-
# We capture variables thus:
|
|
172
|
-
# - +type+ is the value of attribute 'class'.
|
|
173
|
-
# - +path+ is the value of attribute 'href'.
|
|
174
|
-
# - +full_name+ is the HTML text.
|
|
175
|
-
type = class_attr_value
|
|
176
|
-
_, path, rest = anchor_line.split('"')
|
|
177
|
-
full_name = rest.split(/<|>/)[1]
|
|
178
|
-
full_name = CGI.unescapeHTML(full_name)
|
|
179
|
-
case type
|
|
180
|
-
when 'class', 'module'
|
|
181
|
-
index = @index_for_type[:class]
|
|
182
|
-
if index.include?(full_name)
|
|
183
|
-
entry = index[full_name]
|
|
184
|
-
else
|
|
185
|
-
entry = ClassEntry.new(full_name)
|
|
186
|
-
index[full_name] = entry
|
|
187
|
-
end
|
|
188
|
-
entry.paths.push(path) unless entry.paths.include?(path)
|
|
189
|
-
when 'file'
|
|
190
|
-
index = @index_for_type[:page]
|
|
191
|
-
if index.include?(full_name)
|
|
192
|
-
entry = index[full_name]
|
|
193
|
-
else
|
|
194
|
-
entry = FileEntry.new(full_name)
|
|
195
|
-
index[full_name] = entry
|
|
196
|
-
end
|
|
197
|
-
entry.paths.push(path) unless entry.paths.include?(path)
|
|
198
|
-
when 'method'
|
|
199
|
-
case anchor_line
|
|
200
|
-
when /method-c-/
|
|
201
|
-
index = @index_for_type[:singleton_method]
|
|
202
|
-
if index.include?(full_name)
|
|
203
|
-
entry = index[full_name]
|
|
204
|
-
else
|
|
205
|
-
entry = SingletonMethodEntry.new(full_name)
|
|
206
|
-
index[full_name] = entry
|
|
207
|
-
end
|
|
208
|
-
entry.paths.push(path) unless entry.paths.include?(path)
|
|
209
|
-
when /method-i-/
|
|
210
|
-
index = @index_for_type[:instance_method]
|
|
211
|
-
if index.include?(full_name)
|
|
212
|
-
entry = index[full_name]
|
|
213
|
-
else
|
|
214
|
-
entry = InstanceMethodEntry.new(full_name)
|
|
215
|
-
index[full_name] = entry
|
|
216
|
-
end
|
|
217
|
-
entry.paths.push(path) unless entry.paths.include?(path)
|
|
218
|
-
else
|
|
219
|
-
fail anchor_line
|
|
220
|
-
end
|
|
221
|
-
else
|
|
222
|
-
fail class_attr_val
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
|
|
227
190
|
def capture_options(options)
|
|
228
191
|
@noop = options[:noop]
|
|
229
192
|
@info = options[:info]
|
|
230
193
|
@noreline = options[:noreline]
|
|
231
|
-
@doc_release = options[:release]
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def get_toc_html
|
|
235
|
-
# Construct the doc release; e.g., '3.4'.
|
|
236
|
-
# Get the doc table of contents as a temp file.
|
|
237
|
-
@toc_url = DocSite + @doc_release + '/table_of_contents.html'
|
|
238
|
-
begin
|
|
239
|
-
toc_file = URI.open(@toc_url)
|
|
240
|
-
@toc_html = toc_file.read
|
|
241
|
-
rescue Socket::ResolutionError => x
|
|
242
|
-
message = "#{x.class}: #{x.message}\nPossibly not connected to internet."
|
|
243
|
-
$stderr.puts(message)
|
|
244
|
-
exit
|
|
245
|
-
end
|
|
246
194
|
end
|
|
247
195
|
|
|
248
196
|
class Entry
|
|
@@ -322,15 +270,15 @@ class WebRI
|
|
|
322
270
|
# Figure out what's asked for.
|
|
323
271
|
case
|
|
324
272
|
when name.match(/^[A-Z]/)
|
|
325
|
-
show_class(name
|
|
273
|
+
show_class(name)
|
|
326
274
|
when %w[fatal fata fat fa f].include?(name)
|
|
327
|
-
show_class(name
|
|
275
|
+
show_class(name)
|
|
328
276
|
when name.start_with?('ruby:')
|
|
329
|
-
show_file(name
|
|
277
|
+
show_file(name)
|
|
330
278
|
when name.start_with?('::')
|
|
331
|
-
show_singleton_method(name
|
|
279
|
+
show_singleton_method(name)
|
|
332
280
|
when name.start_with?('#')
|
|
333
|
-
show_instance_method(name
|
|
281
|
+
show_instance_method(name)
|
|
334
282
|
when name == '@help'
|
|
335
283
|
show_help
|
|
336
284
|
when name == '@readme'
|
|
@@ -340,207 +288,123 @@ class WebRI
|
|
|
340
288
|
# when name.match(/^[a-z]/)
|
|
341
289
|
# show_method(name, @index_for_type[:singleton_method], @index_for_type[:instance_method])
|
|
342
290
|
else
|
|
291
|
+
|
|
343
292
|
puts "No documentation available for name '#{name}'."
|
|
344
293
|
end
|
|
345
294
|
end
|
|
346
295
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
key.start_with?(name)
|
|
354
|
-
end
|
|
355
|
-
# Find paths for selected_choices
|
|
356
|
-
selected_paths = []
|
|
357
|
-
selected_entries.each_pair do |name, entry|
|
|
358
|
-
entry.paths.each do |path|
|
|
359
|
-
selected_paths.push(path)
|
|
360
|
-
end
|
|
361
|
-
end
|
|
362
|
-
case selected_paths.size
|
|
363
|
-
when 1
|
|
364
|
-
selected_choices = ClassEntry.choices(selected_entries)
|
|
365
|
-
choice = selected_choices.keys.first
|
|
366
|
-
path = selected_choices.values.first
|
|
367
|
-
puts "Found one class/module name starting with '#{name}'\n #{choice}"
|
|
368
|
-
full_name = ClassEntry.full_name_for_choice(choice)
|
|
369
|
-
if name != full_name
|
|
370
|
-
message = "Open page #{path}?"
|
|
371
|
-
return unless get_boolean_answer(message)
|
|
372
|
-
end
|
|
373
|
-
path
|
|
374
|
-
when 0
|
|
375
|
-
puts "Found no class/module name starting with '#{name}'."
|
|
376
|
-
message = "Show #{all_choices.size} class/module names?"
|
|
377
|
-
return unless get_boolean_answer(message)
|
|
378
|
-
choice_index = get_choice(all_choices.keys)
|
|
379
|
-
return if choice_index.nil?
|
|
380
|
-
path = all_choices[choice_index]
|
|
381
|
-
else
|
|
382
|
-
selected_choices = ClassEntry.choices(selected_entries)
|
|
383
|
-
puts "Found #{selected_choices.size} class/module names starting with '#{name}'."
|
|
384
|
-
message = "Show #{selected_choices.size} class/module names?'"
|
|
385
|
-
return unless get_boolean_answer(message)
|
|
386
|
-
key = get_choice(selected_choices.keys)
|
|
387
|
-
return if key.nil?
|
|
388
|
-
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)
|
|
389
302
|
end
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return unless get_boolean_answer(message)
|
|
421
|
-
end
|
|
422
|
-
path
|
|
423
|
-
when 0
|
|
424
|
-
puts "Found no page name starting with '#{name}'."
|
|
425
|
-
message = "Show names of all #{all_choices.size} pages?"
|
|
426
|
-
return unless get_boolean_answer(message)
|
|
427
|
-
key = get_choice(all_choices.keys)
|
|
428
|
-
return if key.nil?
|
|
429
|
-
path = all_choices[key]
|
|
430
|
-
else
|
|
431
|
-
selected_choices = FileEntry.choices(selected_entries)
|
|
432
|
-
puts "Found #{selected_choices.size} page names starting with '#{name}'."
|
|
433
|
-
message = "Show #{selected_choices.size} names?'"
|
|
434
|
-
return unless get_boolean_answer(message)
|
|
435
|
-
key = get_choice(selected_choices.keys)
|
|
436
|
-
return if key.nil?
|
|
437
|
-
path = selected_choices[key]
|
|
438
|
-
end
|
|
439
|
-
uri = Entry.uri(path)
|
|
440
|
-
open_page(name, uri)
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
# Show singleton method.
|
|
444
|
-
def show_singleton_method(name, singleton_method_index)
|
|
445
|
-
# Target page is a singleton method such as ::new.
|
|
446
|
-
all_entries = @index_for_type[:singleton_method]
|
|
447
|
-
all_choices = SingletonMethodEntry.choices(all_entries)
|
|
448
|
-
# Find entries whose names that start with name.
|
|
449
|
-
selected_entries = all_entries.select do |key, value|
|
|
450
|
-
key.start_with?(name)
|
|
451
|
-
end
|
|
452
|
-
# Find paths for selected_choices
|
|
453
|
-
selected_paths = []
|
|
454
|
-
selected_entries.each_pair do |name, entry|
|
|
455
|
-
entry.paths.each do |path|
|
|
456
|
-
selected_paths.push(path)
|
|
457
|
-
end
|
|
458
|
-
end
|
|
459
|
-
selected_choices = SingletonMethodEntry.choices(selected_entries)
|
|
460
|
-
case selected_paths.size
|
|
461
|
-
when 1
|
|
462
|
-
full_name = selected_entries.keys.first
|
|
463
|
-
path = selected_choices.values.first
|
|
464
|
-
puts "Found one singleton method name starting with '#{name}'\n #{full_name}"
|
|
465
|
-
if name != full_name
|
|
466
|
-
uri = URI.parse(path)
|
|
467
|
-
message = "Open page #{uri.path} at method #{full_name}?"
|
|
468
|
-
return unless get_boolean_answer(message)
|
|
469
|
-
end
|
|
470
|
-
path
|
|
471
|
-
when 0
|
|
472
|
-
puts "Found no singleton method name starting with '#{name}'."
|
|
473
|
-
message = "Show names of all #{all_choices.size} singleton methods?"
|
|
474
|
-
return unless get_boolean_answer(message)
|
|
475
|
-
choice = get_choice(all_choices.keys)
|
|
476
|
-
return if choice.nil?
|
|
477
|
-
full_name = SingletonMethodEntry.full_name_for_choice(choice)
|
|
478
|
-
path = all_choices[choice]
|
|
479
|
-
else
|
|
480
|
-
puts "Found #{selected_paths.size} singleton method names starting with '#{name}'."
|
|
481
|
-
message = "Show #{selected_paths.size} names?'"
|
|
482
|
-
return unless get_boolean_answer(message)
|
|
483
|
-
choice = get_choice(selected_choices.keys)
|
|
484
|
-
return if choice.nil?
|
|
485
|
-
full_name = SingletonMethodEntry.full_name_for_choice(choice)
|
|
486
|
-
path = all_choices[choice]
|
|
487
|
-
end
|
|
488
|
-
uri = Entry.uri(path)
|
|
489
|
-
open_page(full_name, uri)
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
# Show instance method.
|
|
493
|
-
def show_instance_method(name, instance_method_index)
|
|
494
|
-
# Target page is an instance method such as #to_s.
|
|
495
|
-
all_entries = @index_for_type[:instance_method]
|
|
496
|
-
all_choices = InstanceMethodEntry.choices(all_entries)
|
|
497
|
-
# Find entries whose names that start with name.
|
|
498
|
-
selected_entries = all_entries.select do |key, value|
|
|
499
|
-
key.start_with?(name)
|
|
500
|
-
end
|
|
501
|
-
# Find paths for selected_choices
|
|
502
|
-
selected_paths = []
|
|
503
|
-
selected_entries.each_pair do |name, entry|
|
|
504
|
-
entry.paths.each do |path|
|
|
505
|
-
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
|
|
506
333
|
end
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
|
518
375
|
end
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
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
|
|
528
384
|
else
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
return
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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)
|
|
539
403
|
end
|
|
540
404
|
|
|
541
405
|
def show_help
|
|
542
406
|
puts 'Showing help.'
|
|
543
|
-
puts `ruby
|
|
407
|
+
puts `ruby exe/webri --help`
|
|
544
408
|
end
|
|
545
409
|
|
|
546
410
|
def show_readme
|
|
@@ -548,7 +412,7 @@ class WebRI
|
|
|
548
412
|
end
|
|
549
413
|
|
|
550
414
|
# Present choices; return choice.
|
|
551
|
-
def
|
|
415
|
+
def get_choice_(choices, required: false)
|
|
552
416
|
index = nil
|
|
553
417
|
range = (0..choices.size - 1)
|
|
554
418
|
until range.include?(index)
|
|
@@ -557,14 +421,20 @@ class WebRI
|
|
|
557
421
|
puts " #{s}: #{choice}"
|
|
558
422
|
end
|
|
559
423
|
while true
|
|
560
|
-
|
|
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
|
|
561
430
|
$stdout.flush
|
|
562
431
|
response = $stdin.gets
|
|
563
432
|
case response
|
|
564
433
|
when /(\d+)/
|
|
565
|
-
|
|
434
|
+
index = $1.to_i
|
|
435
|
+
return choices[index] if index < choices.size
|
|
566
436
|
when "\n"
|
|
567
|
-
return nil
|
|
437
|
+
return nil unless required
|
|
568
438
|
else
|
|
569
439
|
|
|
570
440
|
end
|
|
@@ -586,8 +456,9 @@ class WebRI
|
|
|
586
456
|
end
|
|
587
457
|
|
|
588
458
|
# Open URL in browser.
|
|
589
|
-
def
|
|
590
|
-
|
|
459
|
+
def show_web_page(name, href)
|
|
460
|
+
href.gsub!('::', '/')
|
|
461
|
+
uri = URI.parse(File.join(DOC_SITE, release_name, href))
|
|
591
462
|
open_uri(name, uri)
|
|
592
463
|
end
|
|
593
464
|
|
|
@@ -629,10 +500,58 @@ class WebRI
|
|
|
629
500
|
puts message
|
|
630
501
|
command = "#{opener_name} #{full_url}"
|
|
631
502
|
if @noop
|
|
632
|
-
puts "Command: '#{command}'"
|
|
503
|
+
# puts "Command: '#{command}'"
|
|
633
504
|
else
|
|
634
|
-
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
|
|
635
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
|
+
|
|
636
555
|
end
|
|
637
556
|
|
|
638
557
|
end
|