dakrone-fastri 0.3.1
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.
- data/CHANGES +61 -0
- data/COPYING +340 -0
- data/LEGAL +4 -0
- data/LICENSE +56 -0
- data/README.en +102 -0
- data/Rakefile +26 -0
- data/THANKS +36 -0
- data/bin/fastri-server +251 -0
- data/bin/fri +353 -0
- data/bin/ri-emacs +202 -0
- data/fastri.gemspec +64 -0
- data/indexer.rb +135 -0
- data/lib/fastri/full_text_index.rb +245 -0
- data/lib/fastri/full_text_indexer.rb +100 -0
- data/lib/fastri/name_descriptor.rb +71 -0
- data/lib/fastri/ri_index.rb +601 -0
- data/lib/fastri/ri_service.rb +430 -0
- data/lib/fastri/util.rb +183 -0
- data/lib/fastri/version.rb +13 -0
- data/lookup.rb +197 -0
- data/pre-install.rb +11 -0
- data/setup.rb +1585 -0
- data/test/test_full_text_index.rb +182 -0
- data/test/test_full_text_indexer.rb +84 -0
- data/test/test_functional_ri_service.rb +60 -0
- data/test/test_integration_full_text_index.rb +43 -0
- data/test/test_name_descriptor.rb +35 -0
- data/test/test_ri_index.rb +389 -0
- data/test/test_util.rb +91 -0
- metadata +84 -0
data/README.en
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
FastRI Copyright (C) 2006 Mauricio Fernandez <mfp@acm.org>
|
2
|
+
Inspired by ri-emacs.rb by Kristof Bastiaensen <kristof@vleeuwen.org>
|
3
|
+
|
4
|
+
Overview
|
5
|
+
========
|
6
|
+
FastRI is an alternative to the ri command-line tool. It is *much* faster, and
|
7
|
+
also allows you to offer RI lookup services over DRb. FastRI is a bit smarter
|
8
|
+
than ri, and can find classes anywhere in the hierarchy without specifying the
|
9
|
+
"full path". It also knows about gems, and can tell you e.g. which extensions
|
10
|
+
to a core class were added by a specific gem.
|
11
|
+
|
12
|
+
Install
|
13
|
+
=======
|
14
|
+
Just run
|
15
|
+
setup.rb
|
16
|
+
|
17
|
+
Usage
|
18
|
+
=====
|
19
|
+
FastRI can be used either standalone or with a DRb server, for extra speed.
|
20
|
+
|
21
|
+
The standalone client is qri and is used the same way as ri; run
|
22
|
+
$ qri -h
|
23
|
+
to list the options.
|
24
|
+
|
25
|
+
There are two parts to FastRI over DRb:
|
26
|
+
* the server: fastri-server
|
27
|
+
* the client: fri
|
28
|
+
|
29
|
+
FastRI uses a Rinda Ring to allow servers to be discovered automatically
|
30
|
+
without needing to indicate the DRb URIs manually. It can work across
|
31
|
+
machines if you make sure the ring server is bound to the correct interface,
|
32
|
+
and the ACL permissions are correct.
|
33
|
+
|
34
|
+
qri and fri are nearly identical, the only difference being that fri tries to
|
35
|
+
use a FastRI service over DRb by default.
|
36
|
+
|
37
|
+
fri Quickstart
|
38
|
+
--------------
|
39
|
+
$ fastri-server (blocks)
|
40
|
+
Later,
|
41
|
+
$ fri String
|
42
|
+
---------------------------------------------------------- Class: String
|
43
|
+
A String object holds and manipulates an arbitrary sequence of
|
44
|
+
bytes, typically representing characters. String objects may be
|
45
|
+
created using String::new or as literals.
|
46
|
+
...
|
47
|
+
|
48
|
+
Read on for more information, including how to make FastRI work across
|
49
|
+
machines.
|
50
|
+
|
51
|
+
Launching the server
|
52
|
+
--------------------
|
53
|
+
For local usage, just
|
54
|
+
$ fastri-server
|
55
|
+
will do. The DRb service will bind to 127.0.0.1, and only connections from
|
56
|
+
127.0.0.1 will be allowed. If you want to allow fri to be used from other
|
57
|
+
machines, you have to specify which interface to bind to, and allow incoming
|
58
|
+
connections from the desired hosts. For example, if your network is
|
59
|
+
192.168.1.0, and your IP is 192.168.1.2, you can do
|
60
|
+
$ fastri-server -a 192.168.1.0/24 -s 192.168.1.2
|
61
|
+
FastRI 0.0.1 listening on druby://192.168.1.2:41217
|
62
|
+
ACL:
|
63
|
+
deny all
|
64
|
+
allow localhost
|
65
|
+
allow 192.168.1.0/24
|
66
|
+
|
67
|
+
Further options are documented in
|
68
|
+
$ fastri-server -h
|
69
|
+
|
70
|
+
Using fri
|
71
|
+
---------
|
72
|
+
Running fri with no options (or -h/--help) will explain the command-line options.
|
73
|
+
If you are using fri locally (i.e. on the same server as fastri-server), just use
|
74
|
+
it as follows:
|
75
|
+
$ fri Array
|
76
|
+
------------------------------------------------------- Class: Array
|
77
|
+
Arrays are ordered, integer-indexed collections of any object.
|
78
|
+
...
|
79
|
+
|
80
|
+
If you're on a different machine, you'll probably have to specify which
|
81
|
+
address the DRb service should be attached to. You can either specify it with
|
82
|
+
-s ADDRESS (or --bind ADDRESS), or set the FASTRI_ADDR environment variable.
|
83
|
+
For example, if you're on 192.168.1.101, both
|
84
|
+
export FASTRI_ADDR="192.168.1.101"
|
85
|
+
fri Array
|
86
|
+
and
|
87
|
+
fri -s 192.168.1.101 Array
|
88
|
+
will work. Note that FASTRI_ADDR is the *local* address: the server will be
|
89
|
+
discovered automatically. (The reason why the local address must be specified
|
90
|
+
is that it will be given to the ring server, which must be able to establish
|
91
|
+
a reverse connection through the local DRb service.)
|
92
|
+
|
93
|
+
Feedback
|
94
|
+
========
|
95
|
+
Bug reports, patches, comments... are appreciated.
|
96
|
+
You can contact the author via <mfp@acm.org>. Please add "fastri" to the
|
97
|
+
subject in order to bypass the spam filters.
|
98
|
+
|
99
|
+
License
|
100
|
+
=======
|
101
|
+
FastRI is licensed under the same terms as Ruby. See LICENSE.
|
102
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$:.unshift "lib" if File.directory? "lib"
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
desc "Run the functional and unit tests."
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
6
|
+
t.test_files = FileList['test/test*.rb']
|
7
|
+
t.verbose = true
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'rcov/rcovtask'
|
11
|
+
desc "Run rcov."
|
12
|
+
Rcov::RcovTask.new do |t|
|
13
|
+
t.test_files = FileList['test/test_*.rb'].to_a.reject{|x| /test_functional/ =~ x}
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Save current coverage state for later comparisons."
|
18
|
+
Rcov::RcovTask.new(:rcovsave) do |t|
|
19
|
+
t.rcov_opts << "--save"
|
20
|
+
t.test_files = FileList['test/test_*.rb'].to_a.reject{|x| /test_functional/ =~ x}
|
21
|
+
t.verbose = true
|
22
|
+
end
|
23
|
+
|
24
|
+
task :default => :test
|
25
|
+
|
26
|
+
# vim: set sw=2 ft=ruby:
|
data/THANKS
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
Tomasz Wegrzanowski
|
3
|
+
===================
|
4
|
+
* contributed the code that allows fri to find a method in the ancestors
|
5
|
+
for core classes
|
6
|
+
|
7
|
+
Dee Zsombor
|
8
|
+
===========
|
9
|
+
* reported problem with ri-emacs setting $stdout.sync to true
|
10
|
+
|
11
|
+
Greg Weber
|
12
|
+
==========
|
13
|
+
* came up with the idea to load optparse only when needed,
|
14
|
+
saving some 10-30ms per run.
|
15
|
+
|
16
|
+
Steven Lumos
|
17
|
+
============
|
18
|
+
* found bug triggered by hyphens in Gem.path
|
19
|
+
|
20
|
+
Lee Marlow
|
21
|
+
==========
|
22
|
+
* helped fix FastRI on Leopard
|
23
|
+
|
24
|
+
Steve Madsen
|
25
|
+
============
|
26
|
+
* reported the problems on Leopard and tested the patches
|
27
|
+
|
28
|
+
Nikolai Lugovoi
|
29
|
+
===============
|
30
|
+
* implemented -a (--all)
|
31
|
+
|
32
|
+
rubikitch
|
33
|
+
=========
|
34
|
+
* ri-emacs work
|
35
|
+
* -1 (--exact)
|
36
|
+
* fixes
|
data/bin/fastri-server
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# fastri-server: serve RI documentation over DRb
|
3
|
+
# Copyright (C) 2006 Mauricio Fernandez <mfp@acm.org>
|
4
|
+
|
5
|
+
require 'fastri/version'
|
6
|
+
require 'fastri/ri_index'
|
7
|
+
require 'fastri/ri_service'
|
8
|
+
require 'fastri/util'
|
9
|
+
require 'fastri/full_text_indexer'
|
10
|
+
require 'enumerator'
|
11
|
+
|
12
|
+
FASTRI_SERVER_VERSION = "0.0.1"
|
13
|
+
|
14
|
+
def make_index(index_file)
|
15
|
+
# The local environment is trusted --- what we don't trust is what would come
|
16
|
+
# from the DRb connection. This way the RiService will be untainted, and we
|
17
|
+
# will be able to use it with $SAFE = 1.
|
18
|
+
ObjectSpace.each_object{|obj| obj.untaint unless obj.frozen? }
|
19
|
+
|
20
|
+
paths = [ ::RDoc::RI::Paths::SYSDIR, ::RDoc::RI::Paths::SITEDIR, ::RDoc::RI::Paths::HOMEDIR ].find_all do |p|
|
21
|
+
p && File.directory?(p)
|
22
|
+
end
|
23
|
+
FastRI::Util.gem_directories_unique.each do |name, version, path|
|
24
|
+
paths << path
|
25
|
+
puts "Indexing RI docs for #{name} version #{version || "unknown"}."
|
26
|
+
end
|
27
|
+
|
28
|
+
puts "Building index."
|
29
|
+
t0 = Time.new
|
30
|
+
#ri_reader = RI::RiReader.new(RI::RiCache.new(paths))
|
31
|
+
ri_reader = FastRI::RiIndex.new_from_paths(paths)
|
32
|
+
open(index_file, "wb"){|io| Marshal.dump ri_reader, io}
|
33
|
+
puts <<EOF
|
34
|
+
Indexed:
|
35
|
+
* #{ri_reader.num_methods} methods
|
36
|
+
* #{ri_reader.num_namespaces} classes/modules
|
37
|
+
Needed #{Time.new - t0} seconds
|
38
|
+
EOF
|
39
|
+
ri_reader
|
40
|
+
end
|
41
|
+
|
42
|
+
def linearize(comment)
|
43
|
+
case s = comment["body"]
|
44
|
+
when String; s
|
45
|
+
else
|
46
|
+
if Array === (y = comment["contents"])
|
47
|
+
y.map{|z| linearize(z)}.join("\n")
|
48
|
+
elsif s = comment["text"]
|
49
|
+
s
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def make_full_text_index(dir)
|
57
|
+
paths = [ RI::Paths::SYSDIR, RI::Paths::SITEDIR, RI::Paths::HOMEDIR ].find_all do |p|
|
58
|
+
p && File.directory?(p)
|
59
|
+
end
|
60
|
+
FastRI::Util.gem_directories_unique.each do |name, version, path|
|
61
|
+
paths << path
|
62
|
+
puts "Indexing RI docs for #{name} version #{version || "unknown"}."
|
63
|
+
end
|
64
|
+
unless File.exist?(dir)
|
65
|
+
Dir.mkdir(dir)
|
66
|
+
end
|
67
|
+
indexer = FastRI::FullTextIndexer.new(40)
|
68
|
+
bad = 0
|
69
|
+
paths.each do |path|
|
70
|
+
Dir["#{path}/**/*.yaml"].each do |yamlfile|
|
71
|
+
yaml = File.read(yamlfile)
|
72
|
+
begin
|
73
|
+
data = YAML.load(yaml.gsub(/ \!.*/, ''))
|
74
|
+
rescue Exception
|
75
|
+
bad += 1
|
76
|
+
#puts "Couldn't load #{yamlfile}"
|
77
|
+
next
|
78
|
+
end
|
79
|
+
|
80
|
+
desc = (data['comment']||[]).map{|x| linearize(x)}.join("\n")
|
81
|
+
desc.gsub!(/<\/?(em|b|tt|ul|ol|table)>/, "")
|
82
|
+
desc.gsub!(/"/, "'")
|
83
|
+
desc.gsub!(/</, "<")
|
84
|
+
desc.gsub!(/>/, ">")
|
85
|
+
desc.gsub!(/&/, "&")
|
86
|
+
unless desc.empty?
|
87
|
+
indexer.add_document(yamlfile, desc)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
File.open(File.join(dir, "full_text.dat"), "wb") do |fulltextIO|
|
93
|
+
File.open(File.join(dir, "suffixes.dat"), "wb") do |suffixesIO|
|
94
|
+
indexer.build_index(fulltextIO, suffixesIO)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
#{{{ Main program
|
100
|
+
|
101
|
+
require 'optparse'
|
102
|
+
|
103
|
+
home = FastRI::Util.find_home
|
104
|
+
options = {:allowed_hosts => ["127.0.0.1"], :addr => "127.0.0.1",
|
105
|
+
:index_file => File.join(home, ".fastri-index"),
|
106
|
+
:do_full_text => false,
|
107
|
+
:full_text_dir => File.join(home, ".fastri-fulltext"),
|
108
|
+
}
|
109
|
+
OptionParser.new do |opts|
|
110
|
+
opts.version = FastRI::FASTRI_VERSION
|
111
|
+
opts.release = FastRI::FASTRI_RELEASE_DATE
|
112
|
+
opts.banner = "Usage: fastri-server.rb [options]"
|
113
|
+
|
114
|
+
opts.on("-a", "--allow HOST", "Allow connections from HOST.",
|
115
|
+
"(default: 127.0.0.1)") do |host|
|
116
|
+
options[:allowed_hosts] << host
|
117
|
+
end
|
118
|
+
|
119
|
+
opts.on("-s", "--bind ADDR", "Listen to connections on ADDR.",
|
120
|
+
"(default: 127.0.0.1)") do |addr|
|
121
|
+
options[:addr] = addr
|
122
|
+
end
|
123
|
+
|
124
|
+
opts.on("--index-file=FILE", "Use index file.",
|
125
|
+
"(default: #{options[:index_file]})") do |file|
|
126
|
+
options[:index_file] = file
|
127
|
+
end
|
128
|
+
|
129
|
+
opts.on("-b", "--rebuild-index", "Only rebuild index.") do
|
130
|
+
make_index(options[:index_file])
|
131
|
+
exit 0
|
132
|
+
end
|
133
|
+
|
134
|
+
opts.on("-F", "--full-text-dir DIR", "Place full-text index in DIR",
|
135
|
+
"(default: #{options[:full_text_dir]})") do |dir|
|
136
|
+
options[:full_text_dir] = dir if dir
|
137
|
+
options[:do_full_text] = true
|
138
|
+
end
|
139
|
+
|
140
|
+
opts.on("-B", "--rebuild-full-text", "Rebuild full-text index.") do
|
141
|
+
make_full_text_index(options[:full_text_dir])
|
142
|
+
exit 0
|
143
|
+
end
|
144
|
+
|
145
|
+
opts.on("-h", "--help", "Show this help message") do
|
146
|
+
puts opts
|
147
|
+
exit
|
148
|
+
end
|
149
|
+
end.parse!
|
150
|
+
|
151
|
+
if File.exist?(options[:index_file])
|
152
|
+
ri_reader = open(options[:index_file], "rb"){|io| Marshal.load io } rescue nil
|
153
|
+
end
|
154
|
+
|
155
|
+
ri_reader ||= make_index(options[:index_file])
|
156
|
+
|
157
|
+
service = FastRI::RiService.new(ri_reader)
|
158
|
+
GC.start
|
159
|
+
|
160
|
+
require 'rinda/ring'
|
161
|
+
require 'rinda/tuplespace'
|
162
|
+
require 'drb/acl'
|
163
|
+
|
164
|
+
class FastRI::RiService
|
165
|
+
include DRbUndumped
|
166
|
+
end
|
167
|
+
|
168
|
+
#{{{ start DRb service
|
169
|
+
acl_opt = ["deny", "all"]
|
170
|
+
options[:allowed_hosts].each{|host| acl_opt.concat ["allow", host.strip]}
|
171
|
+
acl = ACL.new(acl_opt)
|
172
|
+
DRb.install_acl(acl)
|
173
|
+
|
174
|
+
ip = options[:addr][/^[^:]+/] || "127.0.0.1"
|
175
|
+
port = options[:addr][/:(\d+)/, 1] || 0
|
176
|
+
drb_addr = "druby://#{ip}:#{port}"
|
177
|
+
DRb.start_service(drb_addr)
|
178
|
+
|
179
|
+
$SAFE = 1
|
180
|
+
|
181
|
+
$stdout.sync = true
|
182
|
+
begin
|
183
|
+
puts "Looking for Ring server..."
|
184
|
+
finder = Rinda::RingFinger.new
|
185
|
+
service_ts = finder.lookup_ring_any(2)
|
186
|
+
puts "Located Ring server at #{service_ts.__drburi}"
|
187
|
+
rescue Exception
|
188
|
+
puts "No Ring server found, starting my own."
|
189
|
+
service_ts = Rinda::TupleSpace.new
|
190
|
+
ring_server = Rinda::RingServer.new(service_ts)
|
191
|
+
end
|
192
|
+
|
193
|
+
begin
|
194
|
+
service_ts.write([:name, :FastRI, service, 'FastRI documentation'], Rinda::SimpleRenewer.new)
|
195
|
+
rescue Exception
|
196
|
+
puts <<EOF
|
197
|
+
The FastRI service could not be registered in the Ring.
|
198
|
+
This is probably due to the Ring being bound to an unreachable address.
|
199
|
+
You can specify which address the Ring should be bound to with
|
200
|
+
fastri-server --bind ADDRESS --allow ADDRESS
|
201
|
+
EOF
|
202
|
+
exit
|
203
|
+
end
|
204
|
+
|
205
|
+
# {{{ Init message
|
206
|
+
puts "fastri-server #{FASTRI_SERVER_VERSION} (FastRI #{FastRI::FASTRI_VERSION}) listening on #{DRb.uri}"
|
207
|
+
puts "ACL:"
|
208
|
+
acl_opt.each_slice(2){|action, host| puts "%-5s %s" % [action, host]}
|
209
|
+
|
210
|
+
# {{{ GC periodically
|
211
|
+
# keeps the process hot too :)
|
212
|
+
Thread.new do
|
213
|
+
loop do
|
214
|
+
GC.start
|
215
|
+
sleep 300
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
if $DEBUG
|
220
|
+
# trying to see where our memory is going
|
221
|
+
population = Hash.new{|h,k| h[k] = [0,0]}
|
222
|
+
array_sizes = Hash.new{|h,k| h[k] = 0}
|
223
|
+
ObjectSpace.each_object do |object|
|
224
|
+
size = case object # rough estimates
|
225
|
+
when Array
|
226
|
+
array_sizes[object.size / 10] += 1
|
227
|
+
case object.size
|
228
|
+
when 0..16
|
229
|
+
20 + 64
|
230
|
+
else
|
231
|
+
20 + 4 * object.size * 1.5
|
232
|
+
end
|
233
|
+
when Hash; 40 + 4 * [object.size / 5, 11].max + 16 * object.size
|
234
|
+
when String; 30 + object.size
|
235
|
+
else 120 # the iv_tbl, etc
|
236
|
+
end
|
237
|
+
count, tsize = population[object.class]
|
238
|
+
population[object.class] = [count + 1, tsize + size]
|
239
|
+
end
|
240
|
+
|
241
|
+
population.sort_by{|k,(c,s)| s}.reverse[0..10].each do |klass, (count, bytes)|
|
242
|
+
puts "%-20s %7d %9d" % [klass, count, bytes]
|
243
|
+
end
|
244
|
+
|
245
|
+
puts "Array sizes:"
|
246
|
+
array_sizes.sort.each{|k,v| puts "%5d %6d" % [k * 10, v]}
|
247
|
+
end
|
248
|
+
|
249
|
+
DRb.thread.join
|
250
|
+
|
251
|
+
# vi: set sw=2 expandtab:
|
data/bin/fri
ADDED
@@ -0,0 +1,353 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# fri: access RI documentation through DRb
|
3
|
+
# Copyright (C) 2006 Mauricio Fernandez <mfp@acm.org>
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'fastri/util'
|
7
|
+
require 'fastri/full_text_index'
|
8
|
+
|
9
|
+
default_local_mode = File.basename($0)[/^qri/] ? true : false
|
10
|
+
|
11
|
+
# we bind to 127.0.0.1 by default, because otherwise Ruby will try with
|
12
|
+
# 0.0.0.0, which results in a DNS request, adding way too much latency
|
13
|
+
options = {
|
14
|
+
:addr => "127.0.0.1",
|
15
|
+
:format =>
|
16
|
+
case RUBY_PLATFORM
|
17
|
+
when /win/
|
18
|
+
if /darwin|cygwin/ =~ RUBY_PLATFORM
|
19
|
+
"ansi"
|
20
|
+
else
|
21
|
+
"plain"
|
22
|
+
end
|
23
|
+
else
|
24
|
+
"ansi"
|
25
|
+
end,
|
26
|
+
:width => 72,
|
27
|
+
:lookup_order => [
|
28
|
+
:exact, :exact_ci, :nested, :nested_ci, :partial, :partial_ci,
|
29
|
+
:nested_partial, :nested_partial_ci,
|
30
|
+
],
|
31
|
+
:show_matches => false,
|
32
|
+
:do_full_text => false,
|
33
|
+
:full_text_dir => File.join(FastRI::Util.find_home, ".fastri-fulltext"),
|
34
|
+
:use_pager => nil,
|
35
|
+
:pager => nil,
|
36
|
+
:list_classes => nil,
|
37
|
+
:list_methods => nil,
|
38
|
+
:extended => false,
|
39
|
+
:index_file => File.join(FastRI::Util.find_home, ".fastri-index"),
|
40
|
+
:local_mode => default_local_mode,
|
41
|
+
:do_second_guess => true,
|
42
|
+
:expand_choices => false,
|
43
|
+
}
|
44
|
+
|
45
|
+
override_addr_env = false
|
46
|
+
|
47
|
+
|
48
|
+
# only load optparse if actually needed
|
49
|
+
# saves ~0.01-0.04s.
|
50
|
+
if (arg = ARGV[0]) && arg[0, 1] == "-" or ARGV.empty?
|
51
|
+
require 'optparse'
|
52
|
+
optparser = OptionParser.new do |opts|
|
53
|
+
opts.version = FastRI::FASTRI_VERSION
|
54
|
+
opts.release = FastRI::FASTRI_RELEASE_DATE
|
55
|
+
opts.banner = "Usage: #{File.basename($0)} [options] <query>"
|
56
|
+
|
57
|
+
opts.on("-L", "--local", "Try to use local index instead of DRb service.",
|
58
|
+
*[("(default)" if default_local_mode)].compact) do
|
59
|
+
options[:local_mode] = true
|
60
|
+
end
|
61
|
+
opts.on("--index-file=FILE", "Use index file (forces --local mode).",
|
62
|
+
"(default: #{options[:index_file]})") do |file|
|
63
|
+
options[:index_file] = file
|
64
|
+
options[:local_mode] = true
|
65
|
+
end
|
66
|
+
opts.on("-R", "--remote", "Use DRb service. #{'(default)' unless default_local_mode}") do
|
67
|
+
options[:local_mode] = false
|
68
|
+
end
|
69
|
+
opts.on("-s", "--bind ADDR", "Bind to ADDR for incoming DRb connections.",
|
70
|
+
"(default: 127.0.0.1)") do |addr|
|
71
|
+
options[:addr] = addr
|
72
|
+
override_addr_env = true
|
73
|
+
end
|
74
|
+
|
75
|
+
order_mapping = {
|
76
|
+
'e' => :exact, 'E' => :exact_ci, 'n' => :nested, 'N' => :nested_ci,
|
77
|
+
'p' => :partial, 'P' => :partial_ci, 'x' => :nested_partial,
|
78
|
+
'X' => :nested_partial_ci, 'a' => :anywhere, 'A' => :anywhere_ci,
|
79
|
+
'm' => :namespace_partial, 'M' => :namespace_partial_ci,
|
80
|
+
'f' => :full_partial, 'F' => :full_partial_ci,
|
81
|
+
}
|
82
|
+
opts.on("-O", "--order ORDER", "Specify lookup order.",
|
83
|
+
"(default: eEnNpPxX)", "Uppercase: case-indep.",
|
84
|
+
"e:exact n:nested p:partial (completion)",
|
85
|
+
"x:nested and partial m:complete namespace",
|
86
|
+
"f:complete both class and method",
|
87
|
+
"a:match method name anywhere") do |order|
|
88
|
+
options[:lookup_order] = order.split(//).map{|x| order_mapping[x]}.compact
|
89
|
+
end
|
90
|
+
|
91
|
+
opts.on("-1", "--exact", "Does not do second guess(exact query).") do
|
92
|
+
options[:do_second_guess] = false
|
93
|
+
options[:lookup_order] = [ :exact ]
|
94
|
+
end
|
95
|
+
opts.on("-a", "--all", "Show info for all methods in Multiple choices",
|
96
|
+
"(default: don't)") do
|
97
|
+
options[:expand_choices] = true
|
98
|
+
end
|
99
|
+
|
100
|
+
opts.on("-e", "--extended", "Show all methods for given namespace.") do
|
101
|
+
options[:extended] = true
|
102
|
+
options[:use_pager] = true
|
103
|
+
options[:format] = "plain"
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.on("--show-matches", "Only show matching entries."){ options[:show_matches] = true }
|
107
|
+
|
108
|
+
opts.on("--classes", "List all known classes/modules.") do
|
109
|
+
options[:use_pager] = true
|
110
|
+
options[:list_classes] = true
|
111
|
+
end
|
112
|
+
opts.on("--methods", "List all known methods.") do
|
113
|
+
options[:use_pager] = true
|
114
|
+
options[:list_methods] = true
|
115
|
+
end
|
116
|
+
opts.on("-l", "--list-names", "List all known namespaces/methods.") do
|
117
|
+
options[:use_pager] = true
|
118
|
+
options[:list_classes] = true
|
119
|
+
options[:list_methods] = true
|
120
|
+
end
|
121
|
+
|
122
|
+
opts.on("-S", "--full-text", "Perform full-text search.") do
|
123
|
+
options[:do_full_text] = true
|
124
|
+
options[:use_pager] = true if options[:use_pager].nil?
|
125
|
+
options[:format] = "plain"
|
126
|
+
end
|
127
|
+
|
128
|
+
opts.on("-F", "--full-text-dir DIR", "Use full-text index in DIR",
|
129
|
+
"(default: #{options[:full_text_dir]})") do |dir|
|
130
|
+
options[:full_text_dir] = dir if dir
|
131
|
+
options[:do_full_text] = true
|
132
|
+
options[:use_pager] = true
|
133
|
+
options[:format] = "plain"
|
134
|
+
end
|
135
|
+
|
136
|
+
opts.on("-f", "--format FMT", "Format to use when displaying output:",
|
137
|
+
" ansi, plain (default: #{options[:format]})") do |format|
|
138
|
+
options[:format] = format
|
139
|
+
end
|
140
|
+
|
141
|
+
opts.on("-P", "--[no-]pager", "Use pager.", "(default: don't)") do |usepager|
|
142
|
+
options[:use_pager] = usepager
|
143
|
+
options[:format] = "plain" if usepager
|
144
|
+
end
|
145
|
+
|
146
|
+
opts.on("-T", "Don't use a pager."){ options[:use_pager] = false }
|
147
|
+
|
148
|
+
opts.on("--pager-cmd PAGER", "Use pager PAGER.", "(default: don't)") do |pager|
|
149
|
+
options[:pager] = pager
|
150
|
+
options[:use_pager] = true
|
151
|
+
options[:format] = "plain"
|
152
|
+
end
|
153
|
+
|
154
|
+
opts.on("-w", "--width WIDTH", "Set the width of the output.") do |width|
|
155
|
+
w = width.to_i
|
156
|
+
options[:width] = w > 0 ? w : options[:width]
|
157
|
+
end
|
158
|
+
|
159
|
+
opts.on("-h", "--help", "Show this help message") do
|
160
|
+
puts opts
|
161
|
+
exit
|
162
|
+
end
|
163
|
+
end
|
164
|
+
optparser.parse!
|
165
|
+
|
166
|
+
if !options[:list_classes] && !options[:list_methods] && ARGV.empty?
|
167
|
+
puts optparser
|
168
|
+
exit
|
169
|
+
end
|
170
|
+
end # lazy optparse loading
|
171
|
+
|
172
|
+
# {{{ try to find where the method comes from exactly
|
173
|
+
include FastRI::Util::MagicHelp
|
174
|
+
|
175
|
+
MAX_CONTEXT_LINES = 20
|
176
|
+
def context_wrap(text, width)
|
177
|
+
"... " +
|
178
|
+
text.gsub(/(.{1,#{width-4}})( +|$\n?)|(.{1,#{width-4}})/, "\\1\\3\n").chomp
|
179
|
+
end
|
180
|
+
|
181
|
+
def display_fulltext_search_results(results, gem_dir_info = FastRI::Util.gem_directories_unique,
|
182
|
+
width = 78)
|
183
|
+
return if results.empty?
|
184
|
+
path = File.expand_path(results[0].path)
|
185
|
+
gem_name, version, gem_path = FastRI::Util.gem_info_for_path(path, gem_dir_info)
|
186
|
+
if gem_name
|
187
|
+
rel_path = path[/#{Regexp.escape(gem_path)}\/(.*)/, 1]
|
188
|
+
if rel_path
|
189
|
+
entry_name = FastRI::Util.gem_relpath_to_full_name(rel_path)
|
190
|
+
end
|
191
|
+
puts "Found in #{gem_name} #{version} #{entry_name}"
|
192
|
+
else
|
193
|
+
rdoc_system_path = File.expand_path(RI::Paths::SYSDIR)
|
194
|
+
if path.index(rdoc_system_path)
|
195
|
+
rel_path = path[/#{Regexp.escape(rdoc_system_path)}\/(.*)/, 1]
|
196
|
+
puts "Found in system #{FastRI::Util.gem_relpath_to_full_name(rel_path)}"
|
197
|
+
else
|
198
|
+
puts "Found in #{path}:"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
text = results.map do |result|
|
202
|
+
context = result.context(120)
|
203
|
+
from = (context.rindex("\n", context.index(result.query)) || -1) + 1
|
204
|
+
to = (context.index("\n", context.index(result.query)) || 0) - 1
|
205
|
+
context_wrap(context[from..to], width)
|
206
|
+
end
|
207
|
+
puts
|
208
|
+
puts text.uniq[0...MAX_CONTEXT_LINES]
|
209
|
+
puts
|
210
|
+
end
|
211
|
+
|
212
|
+
def perform_fulltext_search(options)
|
213
|
+
fulltext = File.join(options[:full_text_dir], "full_text.dat")
|
214
|
+
suffixes = File.join(options[:full_text_dir], "suffixes.dat")
|
215
|
+
begin
|
216
|
+
index = FastRI::FullTextIndex.new_from_filenames(fulltext, suffixes)
|
217
|
+
rescue Exception
|
218
|
+
puts <<EOF
|
219
|
+
Couldn't open the full-text index:
|
220
|
+
#{fulltext}
|
221
|
+
#{suffixes}
|
222
|
+
|
223
|
+
The index needs to be rebuilt with
|
224
|
+
fastri-server -B
|
225
|
+
EOF
|
226
|
+
exit(-1)
|
227
|
+
end
|
228
|
+
gem_dir_info = FastRI::Util.gem_directories_unique
|
229
|
+
match_sets = ARGV.map do |query|
|
230
|
+
result = index.lookup(query)
|
231
|
+
if result
|
232
|
+
index.next_matches(result) + [result]
|
233
|
+
else
|
234
|
+
[]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
path_map = Hash.new{|h,k| h[k] = []}
|
238
|
+
match_sets.each{|matches| matches.each{|m| path_map[m.path] << m} }
|
239
|
+
paths = match_sets[1..-1].inject(match_sets[0].map{|x| x.path}.uniq) do |s,x|
|
240
|
+
s & x.map{|y| y.path}.uniq
|
241
|
+
end
|
242
|
+
if paths.empty?
|
243
|
+
puts "nil"
|
244
|
+
else
|
245
|
+
puts "#{paths.size} hits"
|
246
|
+
paths.sort_by{|path| 1.0 * -path_map[path].size / path_map[path].first.metadata[:size] ** 0.5}.map do |path|
|
247
|
+
puts "=" * options[:width]
|
248
|
+
puts 1.0 * path_map[path].size / path_map[path].first.metadata[:size] ** 0.5
|
249
|
+
display_fulltext_search_results(path_map[path], gem_dir_info, options[:width])
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
exit 0
|
254
|
+
end
|
255
|
+
|
256
|
+
#{{{ set up pager
|
257
|
+
if options[:use_pager]
|
258
|
+
[options[:pager], ENV["PAGER"], "less", "more", "pager"].compact.uniq.each do |pager|
|
259
|
+
begin
|
260
|
+
$stdout = IO.popen(pager, "w")
|
261
|
+
at_exit{ $stdout.close }
|
262
|
+
break
|
263
|
+
rescue Exception
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
#{{{ perform full text search if asked to
|
269
|
+
perform_fulltext_search(options) if options[:do_full_text]
|
270
|
+
|
271
|
+
#{{{ normal query
|
272
|
+
if options[:local_mode]
|
273
|
+
require 'fastri/ri_service'
|
274
|
+
ri_reader = open(options[:index_file], "rb"){|io| Marshal.load io } rescue nil
|
275
|
+
unless ri_reader
|
276
|
+
puts <<EOF
|
277
|
+
Couldn't open the index:
|
278
|
+
#{options[:index_file]}
|
279
|
+
|
280
|
+
The index needs to be rebuilt with
|
281
|
+
fastri-server -b
|
282
|
+
EOF
|
283
|
+
exit(-1)
|
284
|
+
end
|
285
|
+
service = FastRI::RiService.new(ri_reader)
|
286
|
+
else # remote
|
287
|
+
require 'rinda/ring'
|
288
|
+
|
289
|
+
#{{{ determine the address to bind to
|
290
|
+
if override_addr_env
|
291
|
+
addr_spec = options[:addr]
|
292
|
+
else
|
293
|
+
addr_spec = ENV["FASTRI_ADDR"] || options[:addr]
|
294
|
+
end
|
295
|
+
|
296
|
+
ip = addr_spec[/^[^:]+/] || "127.0.0.1"
|
297
|
+
port = addr_spec[/:(\d+)/, 1] || 0
|
298
|
+
addr = "druby://#{ip}:#{port}"
|
299
|
+
|
300
|
+
#{{{ start DRb and perform request
|
301
|
+
begin
|
302
|
+
DRb.start_service(addr)
|
303
|
+
ring_server = Rinda::RingFinger.primary
|
304
|
+
rescue Exception
|
305
|
+
$stderr.puts <<EOF
|
306
|
+
Couldn't initialize DRb and locate the Ring server.
|
307
|
+
|
308
|
+
Please make sure that:
|
309
|
+
* the fastri-server is running, the server is bound to the correct interface,
|
310
|
+
and the ACL setup allows connections from this host
|
311
|
+
* fri is using the correct interface for incoming DRb requests:
|
312
|
+
either set the FASTRI_ADDR environment variable, or use --bind ADDR, e.g
|
313
|
+
export FASTRI_ADDR="192.168.1.12"
|
314
|
+
fri Array
|
315
|
+
EOF
|
316
|
+
exit(-1)
|
317
|
+
end
|
318
|
+
service = ring_server.read([:name, :FastRI, nil, nil])[2]
|
319
|
+
end
|
320
|
+
|
321
|
+
|
322
|
+
info_options = {
|
323
|
+
:formatter => options[:format],
|
324
|
+
:width => options[:width],
|
325
|
+
:lookup_order => options[:lookup_order],
|
326
|
+
:extended => options[:extended],
|
327
|
+
:expand_choices => options[:expand_choices],
|
328
|
+
}
|
329
|
+
|
330
|
+
# {{{ list classes or methods
|
331
|
+
puts service.all_classes if options[:list_classes]
|
332
|
+
puts service.all_methods if options[:list_methods]
|
333
|
+
exit if options[:list_classes] || options[:list_methods]
|
334
|
+
|
335
|
+
# {{{ normal query
|
336
|
+
|
337
|
+
ARGV.each do |term|
|
338
|
+
help_query = magic_help(term)
|
339
|
+
if options[:show_matches]
|
340
|
+
puts service.matches(help_query, info_options).sort
|
341
|
+
else
|
342
|
+
result = service.info(help_query, info_options)
|
343
|
+
# second-guess the correct method type only as the last resort.
|
344
|
+
if result
|
345
|
+
puts result
|
346
|
+
elsif options[:do_second_guess] and (new_query = FastRI::Util.change_query_method_type(help_query)) != help_query
|
347
|
+
puts service.info(new_query)
|
348
|
+
else
|
349
|
+
puts nil
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
# vi: set sw=2 expandtab:
|