fastri 0.1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
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
+ # {{{ Package tasks
27
+
28
+ require 'fastri/version'
29
+
30
+ PKG_REVISION = ".0"
31
+ PKG_FILES = FileList[
32
+ "bin/fri", "bin/fastri-server", "bin/ri-emacs",
33
+ "lib/**/*.rb",
34
+ "COPYING", "LEGAL", "LICENSE", "Rakefile", "README.*", "test/*.rb",
35
+ "setup.rb",
36
+ ]
37
+
38
+ require 'rake/gempackagetask'
39
+ Spec = Gem::Specification.new do |s|
40
+ s.name = "fastri"
41
+ s.version = FastRI::FASTRI_VERSION + PKG_REVISION
42
+ s.summary = "RI docs across machines, faster and smarter than ri."
43
+ s.description = <<EOF
44
+ FastRI is an alternative to the ri command-line tool. It is *much* faster, and
45
+ also allows you to offer RI lookup services over DRb. FastRI is a bit smarter
46
+ than ri, and can find classes anywhere in the hierarchy without specifying the
47
+ "full path". It also knows about gems, and can tell you e.g. which extensions
48
+ to a core class were added by a specific gem.
49
+ EOF
50
+ s.files = PKG_FILES.to_a
51
+ s.require_path = 'lib'
52
+ s.author = "Mauricio Fernandez"
53
+ s.email = "mfp@acm.org"
54
+ s.homepage = "http://eigenclass.org/hiki.rb?fastri"
55
+ s.bindir = "bin"
56
+ s.executables = %w[fri fastri-server ri-emacs]
57
+ s.has_rdoc = true
58
+ #s.extra_rdoc_files = %w[]
59
+ s.rdoc_options << "--title" << 'FastRI: better, faster ri'
60
+ s.test_files = Dir["test/test_*.rb"]
61
+ s.post_install_message = <<EOF
62
+
63
+ A small note about the RubyGems + FastRI.
64
+ =========================================
65
+ RubyGems adds a noticable overhead to fri, making it run slower than if you
66
+ installed it directly from the tarball with setup.rb.
67
+
68
+ Compare the execution time when installed with RubyGems:
69
+ $ time fri -f plain String > /dev/null
70
+
71
+ real 0m0.385s
72
+ user 0m0.244s
73
+ sys 0m0.036s
74
+
75
+ to the time fri actually takes to run, without the overhead introduced by
76
+ RubyGems:
77
+ $ time ruby bin/fri -f plain String > /dev/null
78
+
79
+ real 0m0.088s
80
+ user 0m0.040s
81
+ sys 0m0.008s
82
+
83
+ If you care about those extra 300ms (and there are situations where they will
84
+ matter, e.g. when using fri for method completion), get FastRI from the
85
+ tarballs.
86
+
87
+ EOF
88
+ end
89
+
90
+ task :gem => [:test]
91
+ Rake::GemPackageTask.new(Spec) do |p|
92
+ p.need_tar = true
93
+ end
94
+
95
+ # vim: set sw=2 ft=ruby:
@@ -0,0 +1,188 @@
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
+
9
+ FASTRI_SERVER_VERSION = "0.0.1"
10
+
11
+ def make_index(index_file)
12
+ # The local environment is trusted --- what we don't trust is what would come
13
+ # from the DRb connection. This way the RiService will be untainted, and we
14
+ # will be able to use it with $SAFE = 1.
15
+ ObjectSpace.each_object{|obj| obj.untaint unless obj.frozen? }
16
+
17
+ paths = [ RI::Paths::SYSDIR, RI::Paths::SITEDIR, RI::Paths::HOMEDIR ].find_all do |p|
18
+ p && File.directory?(p)
19
+ end
20
+ begin
21
+ require 'rubygems'
22
+ gemdirs = Dir["#{Gem.path}/doc/*/ri"]
23
+ gems = Hash.new{|h,k| h[k] = []}
24
+ gemdirs.each do |path|
25
+ gemname, version = %r{/([^/]+)-(.*)/ri$}.match(path).captures
26
+ if gemname.nil? # doesn't follow any conventions :(
27
+ gems[path[%r{/([^/]+)/ri$}, 1]] << ["unknown", path]
28
+ else
29
+ gems[gemname] << [version, path]
30
+ end
31
+ end
32
+ gems.sort_by{|name, _| name}.each do |name, versions|
33
+ version, path = versions.sort.last
34
+ puts "Indexing RI docs for #{name} version #{version}."
35
+ paths << path
36
+ end
37
+ rescue LoadError
38
+ end
39
+
40
+ puts "Building index."
41
+ t0 = Time.new
42
+ #ri_reader = RI::RiReader.new(RI::RiCache.new(paths))
43
+ ri_reader = FastRI::RiIndex.new_from_paths(paths)
44
+ open(index_file, "wb"){|io| Marshal.dump ri_reader, io}
45
+ puts <<EOF
46
+ Indexed:
47
+ * #{ri_reader.num_methods} methods
48
+ * #{ri_reader.num_namespaces} classes/modules
49
+ Needed #{Time.new - t0} seconds
50
+ EOF
51
+ ri_reader
52
+ end
53
+
54
+ #{{{ Main program
55
+
56
+ require 'optparse'
57
+
58
+ options = {:allowed_hosts => ["127.0.0.1"], :addr => "127.0.0.1",
59
+ :index_file => File.expand_path("~/.fastri-index")}
60
+ OptionParser.new do |opts|
61
+ opts.banner = "Usage: fastri-server.rb [options]"
62
+
63
+ opts.on("-a", "--allow HOST", "Allow connections from HOST.",
64
+ "(default: 127.0.0.1)") do |host|
65
+ options[:allowed_hosts] << host
66
+ end
67
+
68
+ opts.on("-s", "--bind ADDR", "Listen to connections on ADDR.",
69
+ "(default: 127.0.0.1)") do |addr|
70
+ options[:addr] = addr
71
+ end
72
+
73
+ opts.on("--index-file=FILE", "Use index file.",
74
+ "(default: #{options[:index_file]})") do |file|
75
+ options[:index_file] = file
76
+ end
77
+
78
+ opts.on("-b", "--rebuild-index", "Only rebuild index.") do
79
+ make_index(options[:index_file])
80
+ exit 0
81
+ end
82
+
83
+ opts.on("-h", "--help", "Show this help message") do
84
+ puts opts
85
+ exit
86
+ end
87
+ end.parse!
88
+
89
+ if File.exist?(options[:index_file])
90
+ ri_reader = open(options[:index_file], "rb"){|io| Marshal.load io } rescue nil
91
+ end
92
+
93
+ ri_reader ||= make_index(options[:index_file])
94
+
95
+ service = FastRI::RiService.new(ri_reader)
96
+ GC.start
97
+
98
+ require 'rinda/ring'
99
+ require 'rinda/tuplespace'
100
+ require 'drb/acl'
101
+
102
+ class FastRI::RiService
103
+ include DRbUndumped
104
+ end
105
+
106
+ #{{{ start DRb service
107
+ acl_opt = ["deny", "all"]
108
+ options[:allowed_hosts].each{|host| acl_opt.concat ["allow", host.strip]}
109
+ acl = ACL.new(acl_opt)
110
+ DRb.install_acl(acl)
111
+
112
+ drb_addr = "druby://#{options[:addr]}:0"
113
+ DRb.start_service(drb_addr)
114
+
115
+ $SAFE = 1
116
+
117
+ $stdout.sync = true
118
+ begin
119
+ puts "Looking for Ring server..."
120
+ finder = Rinda::RingFinger.new
121
+ service_ts = finder.lookup_ring_any(2)
122
+ puts "Located Ring server at #{service_ts.__drburi}"
123
+ rescue Exception
124
+ puts "No Ring server found, starting my own."
125
+ service_ts = Rinda::TupleSpace.new
126
+ ring_server = Rinda::RingServer.new(service_ts)
127
+ end
128
+
129
+ begin
130
+ service_ts.write([:name, :FastRI, service, 'FastRI documentation'], Rinda::SimpleRenewer.new)
131
+ rescue Exception
132
+ puts <<EOF
133
+ The FastRI service could not be registered in the Ring.
134
+ This is probably due to the Ring being bound to an unreachable address.
135
+ You can specify which address the Ring should be bound to with
136
+ fastri-server --bind ADDRESS --allow ADDRESS
137
+ EOF
138
+ exit
139
+ end
140
+
141
+ # {{{ Init message
142
+ require 'enumerator'
143
+ puts "fastri-server #{FASTRI_SERVER_VERSION} (FastRI #{FastRI::FASTRI_VERSION}) listening on #{DRb.uri}"
144
+ puts "ACL:"
145
+ acl_opt.each_slice(2){|action, host| puts "%-5s %s" % [action, host]}
146
+
147
+ # {{{ GC periodically
148
+ # keeps the process hot too :)
149
+ Thread.new do
150
+ loop do
151
+ GC.start
152
+ sleep 300
153
+ end
154
+ end
155
+
156
+ if $DEBUG
157
+ # trying to see where our memory is going
158
+ population = Hash.new{|h,k| h[k] = [0,0]}
159
+ array_sizes = Hash.new{|h,k| h[k] = 0}
160
+ ObjectSpace.each_object do |object|
161
+ size = case object # rough estimates
162
+ when Array
163
+ array_sizes[object.size / 10] += 1
164
+ case object.size
165
+ when 0..16
166
+ 20 + 64
167
+ else
168
+ 20 + 4 * object.size * 1.5
169
+ end
170
+ when Hash; 40 + 4 * [object.size / 5, 11].max + 16 * object.size
171
+ when String; 30 + object.size
172
+ else 120 # the iv_tbl, etc
173
+ end
174
+ count, tsize = population[object.class]
175
+ population[object.class] = [count + 1, tsize + size]
176
+ end
177
+
178
+ population.sort_by{|k,(c,s)| s}.reverse[0..10].each do |klass, (count, bytes)|
179
+ puts "%-20s %7d %9d" % [klass, count, bytes]
180
+ end
181
+
182
+ puts "Array sizes:"
183
+ array_sizes.sort.each{|k,v| puts "%5d %6d" % [k * 10, v]}
184
+ end
185
+
186
+ DRb.thread.join
187
+
188
+ # vi: set sw=2 expandtab:
data/bin/fri ADDED
@@ -0,0 +1,67 @@
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 'rinda/ring'
7
+ require 'optparse'
8
+
9
+ # we bind to 127.0.0.1 by default, because otherwise Ruby will try with
10
+ # 0.0.0.0, which results in a DNS request, adding way too much latency
11
+ options = {
12
+ :addr => "127.0.0.1",
13
+ :format => "ansi"
14
+ }
15
+ override_addr_env = false
16
+ optparser = OptionParser.new do |opts|
17
+ opts.banner = "Usage: fri [options] <query>"
18
+
19
+ opts.on("-s", "--bind ADDR", "Bind to ADDR for incoming DRb connections.",
20
+ "(default: 127.0.0.1)") do |addr|
21
+ options[:addr] = addr
22
+ override_addr_env = true
23
+ end
24
+
25
+ opts.on("-f", "--format FMT", "Format to use when displaying output:",
26
+ " ansi, plain (default: ansi)") do |format|
27
+ options[:format] = format
28
+ end
29
+
30
+ opts.on("-h", "--help", "Show this help message") do
31
+ puts opts
32
+ exit
33
+ end
34
+ end
35
+ optparser.parse!
36
+
37
+ if ARGV.empty?
38
+ puts optparser
39
+ exit
40
+ end
41
+
42
+ if override_addr_env
43
+ addr = "druby://#{options[:addr]}:0"
44
+ else
45
+ addr = "druby://#{ENV["FASTRI_ADDR"]||options[:addr]}:0"
46
+ end
47
+
48
+ begin
49
+ DRb.start_service(addr)
50
+ ring_server = Rinda::RingFinger.primary
51
+ rescue Exception
52
+ puts <<EOF
53
+ Couldn't initialize DRb and locate the Ring server.
54
+
55
+ Please make sure that:
56
+ * the fastri-server is running, the server is bound to the correct interface,
57
+ and the ACL setup allows connections from this host
58
+ * fri is using the correct interface for incoming DRb requests:
59
+ either set the FASTRI_ADDR environment variable, or use --bind ADDR, e.g
60
+ export FASTRI_ADDR="192.168.1.12"
61
+ fri Array
62
+ EOF
63
+ exit(-1)
64
+ end
65
+ service = ring_server.read([:name, :FastRI, nil, nil])[2]
66
+ puts service.info(ARGV[0], options[:format])
67
+ # vi: set sw=2 expandtab:
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env ruby
2
+ ## drop-in replacement for the ri-emacs helper script for use
3
+ # with ri-ruby.el, using the FastRI service via DRb
4
+ #
5
+ # Based on ri-emacs.rb by Kristof Bastiaensen <kristof@vleeuwen.org>
6
+ #
7
+ # Copyright (C) 2004,2006 Kristof Bastiaensen
8
+ # 2006 Mauricio Fernandez <mfp@acm.org>
9
+ #
10
+ # This program is free software; you can redistribute it and/or modify
11
+ # it under the terms of the GNU General Public License as published by
12
+ # the Free Software Foundation; either version 2 of the License, or
13
+ # (at your option) any later version.
14
+ #
15
+ # This program is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program; if not, write to the Free Software
22
+ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23
+ #----------------------------------------------------------------------
24
+
25
+ require 'rinda/ring'
26
+ require 'optparse'
27
+
28
+ # {{{ cmdline parsing and service discovery
29
+ # we bind to 127.0.0.1 by default, because otherwise Ruby will try with
30
+ # 0.0.0.0, which results in a DNS request, adding way too much latency
31
+ options = {:addr => "127.0.0.1"}
32
+ override_addr_env = false
33
+ optparser = OptionParser.new do |opts|
34
+ opts.banner = "Usage: ri-emacs [options] <query>"
35
+
36
+ opts.on("-s", "--bind [ADDR]", "Bind to ADDR for incoming DRb connections.",
37
+ "(default: 127.0.0.1)") do |addr|
38
+ options[:addr] = addr
39
+ override_addr_env = true
40
+ end
41
+
42
+ opts.on("-h", "--help", "Show this help message") do
43
+ puts opts
44
+ exit
45
+ end
46
+ end
47
+ optparser.parse!
48
+
49
+ if override_addr_env
50
+ addr = "druby://#{options[:addr]}:0"
51
+ else
52
+ addr = "druby://#{ENV["FASTRI_ADDR"]||options[:addr]}:0"
53
+ end
54
+
55
+ begin
56
+ DRb.start_service(addr)
57
+ ring_server = Rinda::RingFinger.primary
58
+ rescue Exception
59
+ puts <<EOF
60
+ Couldn't initialize DRb and locate the Ring server.
61
+
62
+ Please make sure that:
63
+ * the fastri-server is running, the server is bound to the correct interface,
64
+ and the ACL setup allows connections from this host
65
+ * fri is using the correct interface for incoming DRb requests:
66
+ either set the FASTRI_ADDR environment variable, or use --bind ADDR, e.g
67
+ export FASTRI_ADDR="192.168.1.12"
68
+ fri Array
69
+ EOF
70
+ exit(-1)
71
+ end
72
+ service = ring_server.read([:name, :FastRI, nil, nil])[2]
73
+
74
+ class EventLoop
75
+ def initialize(ri)
76
+ @ri = ri
77
+ end
78
+
79
+ def run
80
+ puts "READY"
81
+ loop do
82
+ line = $stdin.gets
83
+ cmd, p = /(\w+)(.*)$/.match(line)[1..2]
84
+ p.strip!
85
+ case cmd
86
+ when "TRY_COMPLETION"; puts complete_try(p)
87
+ when "COMPLETE_ALL"; puts complete_all(p)
88
+ when "LAMBDA"; puts complete_lambda(p)
89
+ when "CLASS_LIST"; puts class_list(p)
90
+ when "CLASS_LIST_WITH_FLAG"; puts class_list_with_flag(p)
91
+ when "DISPLAY_ARGS"; display_args(p)
92
+ when "DISPLAY_INFO"; display_info(p)
93
+ end
94
+ end
95
+ end
96
+
97
+ def complete_try(keyw)
98
+ list = @ri.completion_list(keyw)
99
+ if list.nil?
100
+ return "nil"
101
+ elsif list.size == 1 and
102
+ list[0].split(/(::)|#|\./) == keyw.split(/(::)|#|\./)
103
+ return "t"
104
+ end
105
+
106
+ first = list.shift;
107
+ if first =~ /(.*)((?:::)|(?:#))(.*)/
108
+ other = $1 + ($2 == "::" ? "#" : "::") + $3
109
+ end
110
+
111
+ len = first.size
112
+ match_both = false
113
+ list.each do |w|
114
+ while w[0, len] != first[0, len]
115
+ if other and w[0, len] == other[0, len]
116
+ match_both = true
117
+ break
118
+ end
119
+ len -= 1
120
+ end
121
+ end
122
+
123
+ if match_both
124
+ return other.sub(/(.*)((?:::)|(?:#))/) { $1 + "." }[0, len].inspect
125
+ else
126
+ return first[0, len].inspect
127
+ end
128
+ end
129
+
130
+ def complete_all(keyw)
131
+ list = @ri.completion_list(keyw)
132
+ if list.nil?
133
+ "nil"
134
+ else
135
+ "(" + list.map { |w| w.inspect }.join(" ") + ")"
136
+ end
137
+ end
138
+
139
+ def complete_lambda(keyw)
140
+ list = @ri.completion_list(keyw)
141
+ if list.nil?
142
+ "nil"
143
+ else
144
+ if list.find { |n| n.split(/(::)|#|\./) == keyw.split(/(::)|#|\./) }
145
+ "t"
146
+ else
147
+ "nil"
148
+ end
149
+ end
150
+ end
151
+
152
+ def class_list(keyw)
153
+ list = @ri.class_list(keyw)
154
+ if list
155
+ "(" + list.map{|x| "(#{x.inspect})"}.join(" ") + ")"
156
+ else
157
+ "nil"
158
+ end
159
+ end
160
+
161
+ def class_list_with_flag(keyw)
162
+ list = @ri.class_list_with_flag(keyw)
163
+ if list
164
+ "(" + list.map{|x| "(#{x.inspect})"}.join(" ") + ")"
165
+ else
166
+ "nil"
167
+ end
168
+ end
169
+
170
+ def display_args(keyw)
171
+ data = @ri.args(keyw)
172
+ puts data if data
173
+ puts "RI_EMACS_END_OF_INFO"
174
+ end
175
+
176
+ def display_info(keyw)
177
+ data = @ri.info(keyw)
178
+ puts data if data
179
+ puts "RI_EMACS_END_OF_INFO"
180
+ end
181
+ end
182
+
183
+
184
+ #{{{ event loop
185
+ $stdout.sync = true
186
+ EventLoop.new(service).run
187
+
188
+ # vi: set sw=2 expandtab: