├── VERSION.yml
├── spec
├── site
│ ├── spec_helper.rb
│ ├── NOTES
│ └── library_spec.rb
├── colorize_spec.rb
├── spec_helper.rb
├── auto_config_spec.rb
├── request_spec.rb
├── inspector_spec.rb
├── tools_spec.rb
├── list_method_spec.rb
└── internals_spec.rb
├── lib
├── ori
│ ├── extensions.rb
│ ├── config.rb
│ ├── library.rb
│ ├── auto_config.rb
│ ├── colorize.rb
│ ├── extensions
│ │ └── object
│ │ │ └── ri.rb
│ ├── request.rb
│ ├── tools.rb
│ ├── list_method.rb
│ └── internals.rb
├── misc
│ └── method_aliases.rb
└── ori.rb
├── .gitignore
├── samples
├── NOTES
├── basic_extension.rb
├── singleton_class_includes_module.rb
├── self_singletons.rb
└── basic_inheritance.rb
├── MIT-LICENSE
├── Rakefile
├── ori.gemspec
├── README_RI_CORE.md
└── README.md
/VERSION.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :major: 0
3 | :minor: 1
4 | :patch: 0
5 | #:build: pre3
6 |
--------------------------------------------------------------------------------
/spec/site/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), "../spec_helper")
2 |
--------------------------------------------------------------------------------
/lib/ori/extensions.rb:
--------------------------------------------------------------------------------
1 | module ORI
2 | module Extensions #:nodoc:
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # General Ruby.
2 | .old*
3 | *-old*
4 | .ref-*
5 | /.ruby-*
6 |
7 | # Project-specific.
8 | /*.rb
9 | /doc/
10 | /pkg/
11 |
--------------------------------------------------------------------------------
/spec/site/NOTES:
--------------------------------------------------------------------------------
1 | These specs depend on a particular OS, environment and setup.
2 |
3 | They SHOULD pass, but if the setup is incorrect, they may fail.
4 |
--------------------------------------------------------------------------------
/samples/NOTES:
--------------------------------------------------------------------------------
1 | These are the "official" samples used in tests, references etc.
2 |
3 | There's no single naming scheme. Top-level namespace may describe the general situation (`BasicInheritance`) or the particular case (`ModuleExtendsClass`).
4 |
--------------------------------------------------------------------------------
/lib/misc/method_aliases.rb:
--------------------------------------------------------------------------------
1 | # NOTE: RDoc looks ugly. Just remove it, that's internal stuff anyway.
2 |
3 | # Retain access to instance_method by providing a prefixed alias to it.
4 | class Module #:nodoc:
5 | alias_method :_ori_instance_method, :instance_method
6 | end
7 |
8 | # Retain access to method by providing a prefixed alias to it.
9 | class Object #:nodoc:
10 | alias_method :_ori_method, :method
11 | end
12 |
--------------------------------------------------------------------------------
/samples/basic_extension.rb:
--------------------------------------------------------------------------------
1 | module Sample
2 | module BasicExtension
3 | module Mo
4 | def public_meth
5 | true
6 | end
7 |
8 | protected
9 | def protected_meth
10 | true
11 | end
12 |
13 | private
14 | def private_meth
15 | true
16 | end
17 | end
18 |
19 | class Klass
20 | extend Mo
21 | end
22 |
23 | module OtherMo
24 | extend Mo
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/ori/config.rb:
--------------------------------------------------------------------------------
1 | module ORI
2 | # Configuration object.
3 | class Config
4 | # Enable color. Example:
5 | #
6 | # true
7 | attr_accessor :color
8 |
9 | # RI frontend command to use. %s is replaced with sought topic. Example:
10 | #
11 | # ri -T -f ansi %s
12 | attr_accessor :frontend
13 |
14 | # Paging program to use. Examples:
15 | #
16 | # less -R
17 | # more
18 | attr_accessor :pager
19 |
20 | # Shell escape mode. :unix or :windows.
21 | attr_accessor :shell_escape
22 |
23 | def initialize(attrs = {})
24 | attrs.each {|k, v| send("#{k}=", v)}
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/samples/singleton_class_includes_module.rb:
--------------------------------------------------------------------------------
1 | module Sample
2 | module SingletonClassIncludesModule
3 | module Mo
4 | # NOTE: Method names are in context of `Mo`. `Mo` doesn't "know" that they are to become singleton.
5 |
6 | def public_meth
7 | true
8 | end
9 |
10 | protected
11 | def protected_meth
12 | true
13 | end
14 |
15 | private
16 | def private_meth
17 | true
18 | end
19 | end
20 |
21 | class Klass
22 | class << self
23 | include Mo
24 | end
25 | end
26 |
27 | module OtherMo
28 | class << self
29 | include Mo
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/samples/self_singletons.rb:
--------------------------------------------------------------------------------
1 | module Sample
2 | # Singletons declared via self. are in fact always public.
3 | module SelfSingletons
4 | module Mo
5 | def self.public_meth
6 | true
7 | end
8 |
9 | protected
10 | def self.protected_meth
11 | true
12 | end
13 |
14 | private
15 | def self.private_meth
16 | true
17 | end
18 | end
19 |
20 | class Klass
21 | def self.public_meth
22 | true
23 | end
24 |
25 | protected
26 | def self.protected_meth
27 | true
28 | end
29 |
30 | private
31 | def self.private_meth
32 | true
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/spec/colorize_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), "spec_helper")
2 |
3 | describe "::ORI::Tools" do
4 | mod = ::ORI::Colorize
5 |
6 | describe ".colorize" do
7 | meth = :colorize
8 |
9 | it "generally works" do
10 | mod.send(meth, "the quick ", "brown fox").should == "the quick brown fox"
11 | mod.send(meth, [:message, :error], "the message", [:reset]).should(match(/the message/) && match(/\e\[0m/)) # NOTE: () and && are to satisfy Ruby 1.8.
12 | end
13 | end
14 |
15 | describe ".seq" do
16 | meth = :seq
17 |
18 | it "generally works" do
19 | mod.send(meth, :reset).should == "\e[0m"
20 |
21 | proc do
22 | mod.send(meth, :kaka)
23 | end.should raise_error ArgumentError
24 | end
25 | end # .seq
26 | end
27 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # Library files.
2 | require File.join(File.dirname(__FILE__), "../lib/ori")
3 |
4 | # Samples.
5 | Dir[File.join(File.dirname(__FILE__), "../samples/**/*.rb")].each {|fn| require fn}
6 |
7 | module HelperMethods
8 | # Quickly fetch ONE named ListMethod of obj.
9 | #
10 | # glm(Hash, "::[]")
11 | # glm(Hash, "#[]")
12 | def glm(obj, method_name)
13 | req = ::ORI::Request.parse(method_name)
14 | raise ArgumentError, "glm: Not a method request" if not req.method?
15 |
16 | req.glm_options[:objs].unshift(obj)
17 | ar = ::ORI::Internals.get_list_methods(req.glm_options)
18 | if ar.size < 1
19 | raise "glm: No methods found"
20 | elsif ar.size > 1
21 | raise "glm: #{ar.size} methods found, please be more specific"
22 | end
23 |
24 | ar[0]
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/spec/site/library_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), "spec_helper")
2 |
3 | require "rbconfig"
4 |
5 | describe "ORI::Library" do
6 | klass = ::ORI::Library
7 |
8 | # NOTES:
9 | # * We recreate object every time if we are not specifically testing caching.
10 | # * We rely upon AutoConfig to detect OS settings.
11 |
12 | autoconf = ::ORI::AutoConfig.new((k = "host_os") => RbConfig::CONFIG[k])
13 | fresh = klass.new({
14 | (k = :frontend) => autoconf.send(k),
15 | (k = :shell_escape) => autoconf.send(k),
16 | })
17 | ##p "fresh", fresh
18 |
19 | describe "#lookup" do
20 | meth = :lookup
21 |
22 | it "generally works" do
23 | r = fresh.dup
24 | r.lookup("Object#is_a?").should_not be_nil
25 |
26 | r = fresh.dup
27 | r.lookup("Object#is_kk?").should be_nil
28 |
29 | r = fresh.dup
30 | r.lookup("Object#kakamakaka").should be_nil
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/spec/auto_config_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), "spec_helper")
2 |
3 | describe "::ORI::AutoConfig" do
4 | klass = ::ORI::AutoConfig
5 |
6 | it "requires host_os most of the time" do
7 | r = klass.new
8 | proc {r.has_less?}.should raise_error
9 | proc {r.unix?}.should raise_error
10 | proc {r.windows?}.should raise_error
11 |
12 | proc {r.color}.should raise_error
13 | proc {r.frontend}.should raise_error
14 | proc {r.pager}.should raise_error
15 | proc {r.shell_escape}.should raise_error
16 | end
17 |
18 | it "generally works" do
19 | r = klass.new(:host_os => "mswin32")
20 | r.has_less?.should == false
21 | r.unix?.should == false
22 | r.windows?.should == true
23 |
24 | r = klass.new(:host_os => "cygwin")
25 | r.has_less?.should == true
26 | r.color.should == true
27 |
28 | r = klass.new(:host_os => "linux-gnu")
29 | r.has_less?.should == true
30 | r.color.should == true
31 | r.frontend.should match /ansi/
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2012 Alex Fortuna
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/lib/ori.rb:
--------------------------------------------------------------------------------
1 | require "rbconfig"
2 |
3 | # TODO: Remove `File.dirname`, use `expand_path`.
4 | Dir[File.join(File.dirname(__FILE__), "**/*.rb")].each do |fn|
5 | require File.expand_path(fn)
6 | end
7 |
8 | # == Object-Oriented RI for IRB Console
9 | #
10 | # ORI brings RI documentation right to your IRB console in a simple, consistent and truly object-oriented way.
11 | #
12 | # To enable ORI add to your ~/.irbrc:
13 | #
14 | # require "rubygems"
15 | # require "ori"
16 | #
17 | # Quick test:
18 | #
19 | # $ irb
20 | # irb> Array.ri
21 | #
22 | # You should see RI page on Array.
23 | #
24 | # See also:
25 | #
26 | # * ORI::Extensions::Object#ri
27 | # * ORI::conf
28 | module ORI
29 | # Get configuration object to query or set its values.
30 | # Note that default values are set automatically based on your OS and environment.
31 | #
32 | # ORI.conf.color = true
33 | # ORI.conf.frontend = "ri -T -f ansi %s"
34 | # ORI.conf.pager = "less -R"
35 | # ORI.conf.shell_escape = :unix
36 | #
37 | # See also: ORI::Config.
38 | def self.conf
39 | @conf ||= begin
40 | autoconf = AutoConfig.new((k = "host_os") => RbConfig::CONFIG[k])
41 | Config.new({
42 | (k = :color) => autoconf.send(k),
43 | (k = :frontend) => autoconf.send(k),
44 | (k = :pager) => autoconf.send(k),
45 | (k = :shell_escape) => autoconf.send(k),
46 | })
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "rake/rdoctask"
2 | require "yaml"
3 |
4 | GEM_NAME = "ori"
5 |
6 | begin
7 | require "jeweler"
8 | Jeweler::Tasks.new do |gem|
9 | gem.name = GEM_NAME
10 | gem.summary = "Object-Oriented RI for IRB Console"
11 | gem.description = "Object-Oriented RI for IRB Console"
12 | gem.email = "alex.r@askit.org"
13 | gem.homepage = "http://github.com/dadooda/ori"
14 | gem.authors = ["Alex Fortuna"]
15 | gem.files = FileList[
16 | "[A-Z]*",
17 | "*.gemspec",
18 | "lib/**/*.rb",
19 | "samples/**/*",
20 | "spec/**/*",
21 | ]
22 | end
23 | rescue LoadError
24 | STDERR.puts "This gem requires Jeweler to be built"
25 | end
26 |
27 | desc "Rebuild gemspec and package"
28 | task :rebuild => [:gemspec, :build, :readme]
29 |
30 | desc "Push (publish) gem to RubyGems.org"
31 | task :push do
32 | # Yet found no way to ask Jeweler forge a complete version string for us.
33 | vh = YAML.load(File.read("VERSION.yml"))
34 | version = [vh[:major], vh[:minor], vh[:patch], (if (v = vh[:build]); v; end)].compact.join(".")
35 | pkgfile = File.join("pkg", [GEM_NAME, "-", version, ".gem"].join)
36 | Kernel.system("gem", "push", pkgfile)
37 | end
38 |
39 | desc "Generate rdoc documentation"
40 | Rake::RDocTask.new(:rdoc) do |rdoc|
41 | rdoc.rdoc_dir = "doc"
42 | #rdoc.title = "ORI"
43 | #rdoc.options << "--line-numbers" # No longer supported.
44 | rdoc.rdoc_files.include("lib/**/*.rb")
45 | end
46 |
--------------------------------------------------------------------------------
/lib/ori/library.rb:
--------------------------------------------------------------------------------
1 | require "set"
2 |
3 | module ORI
4 | # ri lookup library.
5 | class Library #:nodoc:
6 | # Mask of ri command to fetch content. Example:
7 | #
8 | # ri -T -f ansi %s
9 | attr_accessor :frontend
10 |
11 | # Shell escape mode. :unix or :windows.
12 | attr_accessor :shell_escape
13 |
14 | def initialize(attrs = {})
15 | @cache = {}
16 | attrs.each {|k, v| send("#{k}=", v)}
17 | end
18 |
19 | # Lookup an article.
20 | #
21 | # lookup("Kernel#puts") # => content or nil.
22 | def lookup(topic)
23 | if @cache.has_key? topic
24 | @cache[topic]
25 | else
26 | require_frontend
27 |
28 | etopic = case @shell_escape
29 | when :unix
30 | Tools.shell_escape(topic)
31 | when :windows
32 | Tools.win_shell_escape(topic)
33 | else
34 | topic
35 | end
36 |
37 | cmd = @frontend % etopic
38 | ##p "cmd", cmd
39 | content = `#{cmd} 2>&1`
40 | ##p "content", content
41 |
42 | # NOTES:
43 | # * Windows' ri always returns 0 even if article is not found. Work around it with a hack.
44 | # * Unix's ri sometimes returns 0 when it offers suggestions. Try `ri Object#is_ax?`.
45 | @cache[topic] = if $?.exitstatus != 0 or content.lines.count < 4
46 | nil
47 | else
48 | content
49 | end
50 | end
51 | end
52 |
53 | protected
54 |
55 | def require_frontend
56 | raise "`frontend` is not set" if not @frontend
57 | end
58 | end # Library
59 | end # ORI
60 |
--------------------------------------------------------------------------------
/lib/ori/auto_config.rb:
--------------------------------------------------------------------------------
1 | module ORI
2 | # Propose config defaults based on OS and environment.
3 | class AutoConfig #:nodoc:
4 | # Value of RbConfig::Config["host_os"].
5 | #
6 | # linux-gnu
7 | # mswin32
8 | # cygwin
9 | attr_reader :host_os
10 |
11 | def initialize(attrs = {})
12 | attrs.each {|k, v| send("#{k}=", v)}
13 | clear_cache
14 | end
15 |
16 | #--------------------------------------- Accessors and pseudo-accessors
17 |
18 | def has_less?
19 | @cache[:has_less] ||= begin
20 | require_host_os
21 | !!@host_os.match(/cygwin|darwin|freebsd|gnu|linux/i)
22 | end
23 | end
24 |
25 | def host_os=(s)
26 | @host_os = s
27 | clear_cache
28 | end
29 |
30 | def unix?
31 | @cache[:is_unix] ||= begin
32 | require_host_os
33 | !!@host_os.match(/cygwin|darwin|freebsd|gnu|linux|sunos|solaris/i)
34 | end
35 | end
36 |
37 | def windows?
38 | @cache[:is_windows] ||= begin
39 | require_host_os
40 | !!@host_os.match(/mswin|windows/i)
41 | end
42 | end
43 |
44 | #--------------------------------------- Defaults
45 |
46 | def color
47 | @cache[:color] ||= unix?? true : false
48 | end
49 |
50 | def frontend
51 | @cache[:frontend] ||= unix?? "ri -T -f ansi %s" : "ri -T %s"
52 | end
53 |
54 | def pager
55 | @cache[:pager] ||= has_less?? "less -R" : "more"
56 | end
57 |
58 | def shell_escape
59 | @cache[:shell_escape] ||= if unix?
60 | :unix
61 | elsif windows?
62 | :windows
63 | else
64 | nil
65 | end
66 | end
67 |
68 | private
69 |
70 | def clear_cache
71 | @cache = {}
72 | end
73 |
74 | def require_host_os
75 | raise "`host_os` is not set" if not @host_os
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/lib/ori/colorize.rb:
--------------------------------------------------------------------------------
1 | module ORI
2 | # Simplistic ANSI colorizer.
3 | module Colorize #:nodoc:
4 | # Issue an ANSI color sequence.
5 | #
6 | # puts [Colorize.seq(:message, :error), "Error!", Colorize.seq(:reset)].join
7 | def self.seq(*spec)
8 | Tools.ansi(*case spec
9 | when [:choice, :title]
10 | [:green]
11 | when [:choice, :index]
12 | [:yellow, :bold]
13 | when [:choice, :label]
14 | [:cyan]
15 | when [:choice, :prompt]
16 | [:yellow, :bold]
17 |
18 | # These go in sequence, each knows who's before. Thus we minimize ANSI.
19 | when [:list_method, :own_marker]
20 | [:reset, :bold]
21 | when [:list_method, :not_own_marker]
22 | [:reset]
23 | when [:list_method, :obj_module_name]
24 | [:cyan, :bold]
25 | when [:list_method, :owner_name]
26 | [:reset]
27 | when [:list_method, :access]
28 | [:reset, :cyan]
29 | when [:list_method, :name]
30 | [:reset, :bold]
31 | when [:list_method, :visibility]
32 | [:reset, :yellow]
33 |
34 | # These go in sequence.
35 | when [:mam, :module_name]
36 | [:cyan, :bold]
37 | when [:mam, :access]
38 | [:reset, :cyan]
39 | when [:mam, :method_name]
40 | [:reset, :bold]
41 |
42 | when [:message, :action]
43 | [:green]
44 | when [:message, :error]
45 | [:red, :bold]
46 | when [:message, :info]
47 | [:green]
48 |
49 | when [:reset]
50 | [:reset]
51 |
52 | else
53 | raise ArgumentError, "Unknown spec: #{spec.inspect}"
54 | end
55 | ) # Tools.ansi
56 | end
57 |
58 | def self.colorize(*args)
59 | args.map {|v| v.is_a?(Array) ? seq(*v) : v}.join
60 | end
61 | end # Colorize
62 | end
63 |
--------------------------------------------------------------------------------
/samples/basic_inheritance.rb:
--------------------------------------------------------------------------------
1 | module Sample
2 | module BasicInheritance
3 | class Grandpa
4 | def grandpa_public
5 | true
6 | end
7 |
8 | protected
9 | def grandpa_protected
10 | true
11 | end
12 |
13 | private
14 | def grandpa_private
15 | true
16 | end
17 |
18 | class << self
19 | def grandpa_public_singleton
20 | true
21 | end
22 |
23 | protected
24 | def grandpa_protected_singleton
25 | true
26 | end
27 |
28 | private
29 | def grandpa_private_singleton
30 | true
31 | end
32 | end
33 | end # Grandpa
34 |
35 | class Papa < Grandpa
36 | def papa_public
37 | true
38 | end
39 |
40 | protected
41 | def papa_protected
42 | true
43 | end
44 |
45 | private
46 | def papa_private
47 | true
48 | end
49 |
50 | class << self
51 | def papa_public_singleton
52 | true
53 | end
54 |
55 | protected
56 | def papa_protected_singleton
57 | true
58 | end
59 |
60 | private
61 | def papa_private_singleton
62 | true
63 | end
64 | end
65 | end # Papa
66 |
67 | class Son < Papa
68 | def son_public
69 | true
70 | end
71 |
72 | protected
73 | def son_protected
74 | true
75 | end
76 |
77 | private
78 | def son_private
79 | true
80 | end
81 |
82 | class << self
83 | def son_public_singleton
84 | true
85 | end
86 |
87 | protected
88 | def son_protected_singleton
89 | true
90 | end
91 |
92 | private
93 | def son_private_singleton
94 | true
95 | end
96 | end
97 | end # Son
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/spec/request_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), "spec_helper")
2 |
3 | describe "::ORI::Request" do
4 | klass = ::ORI::Request
5 |
6 | it "generally works" do
7 | # NOTE: Order is: self, method, list, error. Thinnest first, fattest last. Then errors.
8 |
9 | r = klass.parse()
10 | r.self?.should == true
11 | r.glm_options[:objs].should == []
12 |
13 | ###
14 |
15 | r = klass.parse(:puts)
16 | r.method?.should == true
17 | r.glm_options[:all].should == true
18 | "puts".should match r.glm_options[:re]
19 | ##p "r.glm_options", r.glm_options
20 |
21 | r = klass.parse("#puts")
22 | r.method?.should == true
23 | r.glm_options[:all].should == true
24 | "puts".should match r.glm_options[:re]
25 | r.glm_options[:access].should == "#"
26 |
27 | r = klass.parse("::puts")
28 | r.method?.should == true
29 | r.glm_options[:all].should == true
30 | "puts".should match r.glm_options[:re]
31 | r.glm_options[:access].should == "::"
32 |
33 | ###
34 |
35 | r = klass.parse(/kk/)
36 | r.list?.should == true
37 | r.glm_options[:objs].should == []
38 | r.glm_options[:re].should == /kk/
39 |
40 | r = klass.parse(/kk/, :all, :own)
41 | r.glm_options[:all].should == true
42 | r.glm_options[:own].should == true
43 | r.glm_options[:objs].should == []
44 | r.glm_options[:re].should == /kk/
45 |
46 | ###
47 |
48 | r = klass.parse(//, :join => 1)
49 | r.list?.should == true
50 | r.glm_options.should_not have_key(:join)
51 | r.glm_options[:objs].should == [1]
52 | r.glm_options[:re].should == //
53 |
54 | r = klass.parse(//, :join => [1])
55 | r.list?.should == true
56 | r.glm_options.should_not have_key(:join)
57 | r.glm_options[:objs].should == [1]
58 | r.glm_options[:re].should == //
59 |
60 | r = klass.parse(//, :join => [1, [2]])
61 | r.list?.should == true
62 | r.glm_options.should_not have_key(:join)
63 | r.glm_options[:objs].should == [1, [2]]
64 | r.glm_options[:re].should == //
65 |
66 | ###
67 |
68 | r = klass.parse(5678)
69 | r.error?.should == true
70 | r.message.should match /5678/
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/spec/inspector_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), "spec_helper")
2 |
3 | # Rules of method inspection, formulated as spec samples.
4 | #
5 | # NOTES:
6 | # * DO NOT sort arrays to compare them with each other. Instead use array arithmetic.
7 | # * Try NOT to touch method `methods`. ORI doesn't actually use it.
8 | # * Test concrete inspectors thoroughly: `public_methods`, `protected_methods`, etc.
9 | # * Sort from more public to less public: public -- protected -- private.
10 |
11 | obj_subjects = [
12 | nil,
13 | false,
14 | 5,
15 | "kaka",
16 | Time.now,
17 | ]
18 |
19 | class_subjects = [
20 | Array,
21 | Fixnum,
22 | String,
23 | ]
24 |
25 | module_subjects = [
26 | Enumerable,
27 | Kernel,
28 | ]
29 |
30 | cm_subjects = class_subjects + module_subjects
31 | all_subjects = obj_subjects + cm_subjects
32 |
33 | describe "{Class|Module}#instance_methods" do
34 | it "does not intersect #private_instance_methods" do
35 | (cm_subjects).each do |subj|
36 | (subj.instance_methods & subj.private_instance_methods).should == []
37 | end
38 | end
39 |
40 | it "is fully contained in (#public_instance_methods + #protected_instance_methods)" do
41 | (cm_subjects).each do |subj|
42 | (subj.instance_methods - (subj.public_instance_methods + subj.protected_instance_methods)).should == []
43 | end
44 | end
45 | end
46 |
47 | describe "anything#methods" do
48 | it "is fully contained in (#public_methods + #protected_methods)" do
49 | all_subjects.each do |subj|
50 | (subj.methods - (subj.public_methods + subj.protected_methods)).should == []
51 | end
52 | end
53 | end
54 |
55 | describe "{Class|Module}#singleton_methods" do
56 | it "is fully contained in (#public_methods + #protected_methods + #private_methods)" do
57 | cm_subjects.each do |subj|
58 | (subj.singleton_methods - (subj.public_methods + subj.protected_methods + subj.private_methods)).should == []
59 | end
60 | end
61 | end
62 |
63 | describe "Module#singleton_methods" do
64 | it "is fully containted in #public_methods" do
65 | module_subjects.each do |subj|
66 | (subj.singleton_methods - subj.public_methods).should == []
67 | end
68 | end
69 |
70 | it "does not intersect with #protected_methods or #private_methods)" do
71 | module_subjects.each do |subj|
72 | (subj.singleton_methods & (subj.protected_methods + subj.private_methods)).should == []
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/ori.gemspec:
--------------------------------------------------------------------------------
1 | # Generated by jeweler
2 | # DO NOT EDIT THIS FILE DIRECTLY
3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4 | # -*- encoding: utf-8 -*-
5 |
6 | Gem::Specification.new do |s|
7 | s.name = %q{ori}
8 | s.version = "0.1.0"
9 |
10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11 | s.authors = ["Alex Fortuna"]
12 | s.date = %q{2011-01-02}
13 | s.description = %q{Object-Oriented RI for IRB Console}
14 | s.email = %q{alex.r@askit.org}
15 | s.extra_rdoc_files = [
16 | "README.html",
17 | "README.md"
18 | ]
19 | s.files = [
20 | "MIT-LICENSE",
21 | "README.html",
22 | "README.md",
23 | "Rakefile",
24 | "VERSION.yml",
25 | "lib/misc/method_aliases.rb",
26 | "lib/ori.rb",
27 | "lib/ori/auto_config.rb",
28 | "lib/ori/colorize.rb",
29 | "lib/ori/config.rb",
30 | "lib/ori/extensions.rb",
31 | "lib/ori/extensions/object/ri.rb",
32 | "lib/ori/internals.rb",
33 | "lib/ori/library.rb",
34 | "lib/ori/list_method.rb",
35 | "lib/ori/request.rb",
36 | "lib/ori/tools.rb",
37 | "ori.gemspec",
38 | "samples/NOTES",
39 | "samples/basic_extension.rb",
40 | "samples/basic_inheritance.rb",
41 | "samples/self_singletons.rb",
42 | "samples/singleton_class_includes_module.rb",
43 | "spec/auto_config_spec.rb",
44 | "spec/colorize_spec.rb",
45 | "spec/inspector_spec.rb",
46 | "spec/internals_spec.rb",
47 | "spec/list_method_spec.rb",
48 | "spec/request_spec.rb",
49 | "spec/site/NOTES",
50 | "spec/site/library_spec.rb",
51 | "spec/site/spec_helper.rb",
52 | "spec/spec_helper.rb",
53 | "spec/tools_spec.rb"
54 | ]
55 | s.homepage = %q{http://github.com/dadooda/ori}
56 | s.require_paths = ["lib"]
57 | s.rubygems_version = %q{1.3.7}
58 | s.summary = %q{Object-Oriented RI for IRB Console}
59 | s.test_files = [
60 | "spec/auto_config_spec.rb",
61 | "spec/colorize_spec.rb",
62 | "spec/inspector_spec.rb",
63 | "spec/internals_spec.rb",
64 | "spec/list_method_spec.rb",
65 | "spec/request_spec.rb",
66 | "spec/site/library_spec.rb",
67 | "spec/site/spec_helper.rb",
68 | "spec/spec_helper.rb",
69 | "spec/tools_spec.rb"
70 | ]
71 |
72 | if s.respond_to? :specification_version then
73 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
74 | s.specification_version = 3
75 |
76 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
77 | else
78 | end
79 | else
80 | end
81 | end
82 |
83 |
--------------------------------------------------------------------------------
/lib/ori/extensions/object/ri.rb:
--------------------------------------------------------------------------------
1 | module ORI
2 | module Extensions
3 | module Object
4 | # View RI pages on module, class, method. Interactively list receiver's methods.
5 | #
6 | # == Request RI on a Class
7 | #
8 | # Array.ri
9 | # String.ri
10 | # [].ri
11 | # "".ri
12 | # 5.ri
13 | #
14 | # So that's fairly straightforward -- grab a class or class instance and call ri on it:
15 | #
16 | # obj = SomeKlass.new
17 | # obj.ri
18 | #
19 | # == Request RI on a Method
20 | #
21 | # String.ri :upcase
22 | # "".ri :upcase
23 | # [].ri :sort
24 | # Hash.ri :[]
25 | # Hash.ri "::[]"
26 | # Hash.ri "#[]"
27 | #
28 | # == Request Interactive Method List
29 | #
30 | # # Regular expression argument denotes list request.
31 | # String.ri //
32 | # "".ri //
33 | #
34 | # # Show method names matching a regular expression.
35 | # "".ri /case/
36 | # "".ri /^to_/
37 | # [].ri /sort/
38 | # {}.ri /each/
39 | #
40 | # # Show ALL methods, including those private of Kernel.
41 | # Hash.ri //, :all => true
42 | # Hash.ri //, :all
43 | #
44 | # # Show class methods or instance methods only.
45 | # Module.ri //, :access => "::"
46 | # Module.ri //, :access => "#"
47 | #
48 | # # Show own methods only.
49 | # Time.ri //, :own => true
50 | # Time.ri //, :own
51 | #
52 | # # Specify visibility: public, protected or private.
53 | # Module.ri //, :visibility => :private
54 | # Module.ri //, :visibility => [:public, :protected]
55 | #
56 | # # Filter fully formatted name by given regexp.
57 | # Module.ri //, :fullre => /\(Object\)::/
58 | #
59 | # # Combine options.
60 | # Module.ri //, :fullre => /\(Object\)::/, :access => "::", :visibility => :private
61 | #
62 | # == Request Interactive Method List for More Than 1 Object at Once
63 | #
64 | # By using the :join option it's possible to fetch methods for more
65 | # than 1 object at once. Value of :join (which can be an object or an array)
66 | # is joined with the original receiver, and then a combined set is queried.
67 | #
68 | # # List all division-related methods from numeric classes.
69 | # Fixnum.ri /div/, :join => [Float, Rational]
70 | # 5.ri /div/, :join => [5.0, 5.to_r]
71 | #
72 | # # List all ActiveSupport extensions to numeric classes.
73 | # 5.ri //, :join => [5.0, 5.to_r], :fullre => /ActiveSupport/
74 | #
75 | # # Query entire Rails family for methods having the word "javascript".
76 | # rails_modules = ObjectSpace.each_object(Module).select {|mod| mod.to_s.match /Active|Action/}
77 | # "".ri /javascript/, :join => rails_modules
78 | def ri(*args)
79 | ::ORI::Internals.do_history
80 | ::ORI::Internals.ri(self, *args)
81 | end
82 | end
83 | end
84 | end
85 |
86 | class Object #:nodoc:
87 | include ::ORI::Extensions::Object
88 | end
89 |
--------------------------------------------------------------------------------
/README_RI_CORE.md:
--------------------------------------------------------------------------------
1 |
2 | Setting up core RI documentation for your version of Ruby
3 | ===========================================================
4 |
5 |
6 | All versions, general procedure
7 | -------------------------------
8 |
9 | * Compile and install Ruby without ri/rdoc documentation.
10 | * Install the latest `rdoc` gem before you install any other gems.
11 | * Generate RI documentation using the latest `rdoc` gem.
12 | * Test if RI documentation is installed correctly.
13 |
14 |
15 | RVM
16 | ---
17 |
18 | ### 2.1.0 ###
19 |
20 | Install and select the new Ruby:
21 |
22 | ~~~
23 | $ rvm install 2.1.0
24 | $ rvm 2.1.0
25 | ~~~
26 |
27 | Generate RI documentation (takes a while):
28 |
29 | ~~~
30 | $ cd ~/.rvm/src
31 | $ rvm docs generate-ri
32 | ~~~
33 |
34 | Test:
35 |
36 | ~~~
37 | $ ri Array.each
38 | ~~~
39 |
40 |
41 | ### 2.0.x ###
42 |
43 | Never had a chance to try this one. :)
44 | Contributor information is welcome.
45 |
46 |
47 | ### 1.9.x ###
48 |
49 | Install and select the new Ruby:
50 |
51 | ~~~
52 | $ rvm install 1.9.3-p125
53 | $ rvm 1.9.3-p125
54 | ~~~
55 |
56 | Install the most recent `rdoc` gem:
57 |
58 | ~~~
59 | $ rvm gemset use global
60 | $ gem install rdoc
61 | ~~~
62 |
63 | Test:
64 |
65 | ~~~
66 | $ ri Array.each
67 | ~~~
68 |
69 |
70 | ### 1.8.x ###
71 |
72 | Install and select the new Ruby:
73 |
74 | ~~~
75 | $ rvm install 1.8.7-p358
76 | $ rvm 1.8.7-p358
77 | ~~~
78 |
79 | Install the most recent `rdoc` gem (for 1.8 you **must** do it):
80 |
81 | ~~~
82 | $ rvm gemset use global
83 | $ gem install rdoc
84 | ~~~
85 |
86 | Generate RI documentation (takes a while):
87 |
88 | ~~~
89 | $ cd ~/.rvm/src
90 | $ rvm docs generate-ri
91 | ~~~
92 |
93 | Test:
94 |
95 | ~~~
96 | $ ri Array.each
97 | ~~~
98 |
99 |
100 | Non-RVM, from source
101 | --------------------
102 |
103 | ### 2.0.x, 2.1.x ###
104 |
105 | Contributor information is highly welcome.
106 |
107 |
108 | ### 1.9.x ###
109 |
110 | `rdoc` 2.5 shipped with Ruby 1.9 seems to do the job. No tweaking is required, just build and install:
111 |
112 | ~~~
113 | $ make
114 | $ make install
115 | ~~~
116 |
117 | Test:
118 |
119 | ~~~
120 | $ ri Array.each
121 | ~~~
122 |
123 |
124 | ### 1.8.x ###
125 |
126 | If installed with `make install` by default, RI documentation will be generated in an outdated format, which is no longer supported. Please follow these steps to generate the documentation correctly.
127 |
128 | Unpack source:
129 |
130 | ~~~
131 | $ tar xjvf ruby-1.8.7-p352.tar.bz2
132 | ~~~
133 |
134 | Build:
135 |
136 | ~~~
137 | $ cd ruby-1.8.7-p352
138 | $ ./configure
139 | $ make
140 | ~~~
141 |
142 | Install without doc:
143 |
144 | ~~~
145 | $ make install-nodoc
146 | ~~~
147 |
148 | Install the latest rdoc gem:
149 |
150 | ~~~
151 | $ gem install rdoc
152 | ~~~
153 |
154 | Now fix `Makefile` to use the new rdoc generator instead of the shipped one. Replace the line:
155 |
156 | ~~~
157 | $(RUNRUBY) "$(srcdir)/bin/rdoc" --all --ri --op "$(RDOCOUT)" "$(srcdir)"
158 | ~~~
159 |
160 | with:
161 |
162 | ~~~
163 | rdoc --all --ri --op "$(RDOCOUT)" "$(srcdir)"
164 | ~~~
165 |
166 | Generate and install the docs:
167 |
168 | ~~~
169 | $ make install-doc
170 | ~~~
171 |
172 | Test:
173 |
174 | ~~~
175 | $ ri Array.each
176 | ~~~
177 |
--------------------------------------------------------------------------------
/lib/ori/request.rb:
--------------------------------------------------------------------------------
1 | module ORI
2 | # something.ri [something] request logic.
3 | #
4 | # NOTE: This class DOES NOT validate particular options to be passed to get_list_methods.
5 | class Request #:nodoc:
6 | class ParseError < Exception #:nodoc:
7 | end
8 |
9 | # Options for Internals::get_list_methods.
10 | attr_accessor :glm_options
11 |
12 | # :self, :list, :method or :error.
13 | attr_accessor :kind
14 |
15 | # Message. E.g. for :error kind this is the message for the user.
16 | attr_accessor :message
17 |
18 | def initialize(attrs = {})
19 | @glm_options = {}
20 | attrs.each {|k, v| send("#{k}=", v)}
21 | end
22 |
23 | def error?
24 | @kind == :error
25 | end
26 |
27 | def list?
28 | @kind == :list
29 | end
30 |
31 | def method?
32 | @kind == :method
33 | end
34 |
35 | def self?
36 | @kind == :self
37 | end
38 |
39 | #---------------------------------------
40 |
41 | # Parse arguments into a new Request object.
42 | #
43 | # parse()
44 | # parse(//)
45 | # parse(//, :all)
46 | # parse(//, :all => true, :access => "#")
47 | # parse(:puts)
48 | # parse("#puts")
49 | # parse("::puts")
50 | def self.parse(*args)
51 | r = new(:glm_options => {:objs => []})
52 |
53 | begin
54 | if args.size < 1
55 | # Fixnum.ri
56 | # 5.ri
57 | r.kind = :self
58 | else
59 | # At least 1 argument is present.
60 | arg1 = args.shift
61 |
62 | case arg1
63 | when Symbol, String
64 | # ri :meth
65 | # ri "#meth"
66 | # ri "::meth"
67 | r.kind = :method
68 | if args.size > 0
69 | raise ParseError, "Unexpected arguments after #{arg1.inspect}"
70 | end
71 |
72 | # This is important -- look through all available methods.
73 | r.glm_options[:all] = true
74 |
75 | method_name = if arg1.to_s.match /\A(::|#)(.+)\z/
76 | r.glm_options[:access] = $1
77 | $2
78 | else
79 | arg1.to_s
80 | end
81 |
82 | r.glm_options[:re] = /\A#{Regexp.escape(method_name)}\z/
83 |
84 | when Regexp
85 | # ri //
86 | # ri //, :all
87 | # ri /kk/, :option => value etc.
88 | r.kind = :list
89 | r.glm_options[:re] = arg1
90 | args.each do |arg|
91 | if arg.is_a? Hash
92 | if arg.has_key?(k = :join)
93 | r.glm_options[:objs] += [arg.delete(k)].flatten(1)
94 | end
95 |
96 | r.glm_options.merge! arg
97 | elsif [String, Symbol].include? arg.class
98 | r.glm_options.merge! arg.to_sym => true
99 | else
100 | raise ParseError, "Unsupported argument #{arg.inspect}"
101 | end
102 | end
103 |
104 | # Don't bother making `objs` unique, we're just the request parser.
105 |
106 | else
107 | raise ParseError, "Unsupported argument #{arg1.inspect}"
108 | end # case arg1
109 | end # if args.size < 1
110 | rescue ParseError => e
111 | r.kind = :error
112 | r.message = e.message
113 | end
114 |
115 | r
116 | end
117 | end
118 | end
119 |
--------------------------------------------------------------------------------
/spec/tools_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), "spec_helper")
2 |
3 | describe "::ORI::Tools" do
4 | mod = ::ORI::Tools
5 |
6 | describe ".ansi" do
7 | meth = :ansi
8 |
9 | it "returns empty string if no attrs are given" do
10 | mod.send(meth).should == ""
11 | end
12 |
13 | it "refuses to take unknown attributes" do
14 | proc do
15 | mod.send(meth, :kaka)
16 | end.should raise_error(ArgumentError)
17 | end
18 |
19 | it "generally works" do
20 | mod.send(meth, :red).should == "\e[31m"
21 | mod.send(meth, :red, :on_green).should == "\e[31;42m"
22 | end
23 | end # .ansi
24 |
25 | describe ".get_methods" do
26 | meth = :get_methods
27 |
28 | it "allows to fetch own methods only" do
29 | ar = mod.send(meth, ::Sample::BasicInheritance::Son, :inspector_arg => false)
30 | ##p "ar", ar
31 | h = Hash[*ar.flatten(1)]
32 | ##p "h", h
33 |
34 | h["public_instance_methods"].should == ["son_public"]
35 | h["protected_instance_methods"].should == ["son_protected"]
36 | h["private_instance_methods"].should == ["son_private"]
37 | h["public_methods"].should include("son_public_singleton", "papa_public_singleton", "grandpa_public_singleton")
38 | h["protected_methods"].should include("son_protected_singleton", "papa_protected_singleton", "grandpa_protected_singleton")
39 | h["private_methods"].should include("son_private_singleton", "papa_private_singleton", "grandpa_private_singleton")
40 | end
41 |
42 | it "supports MAV mode" do
43 | ar = mod.send(meth, ::Sample::BasicInheritance::Son, :to_mav => true)
44 | ##p "ar", ar
45 | ar.should include(["son_public", "#", :public], ["son_protected", "#", :protected], ["son_private", "#", :private])
46 | ar.should include(["son_public_singleton", "::", :public], ["son_protected_singleton", "::", :protected], ["son_private_singleton", "::", :private])
47 | ar.should include(["papa_public", "#", :public], ["papa_protected", "#", :protected], ["papa_private", "#", :private])
48 | ar.should include(["papa_public_singleton", "::", :public], ["papa_protected_singleton", "::", :protected], ["papa_private_singleton", "::", :private])
49 |
50 | ar = mod.send(meth, ::Sample::BasicExtension::OtherMo, :to_mav => true)
51 | ar.should include(["public_meth", "::", :public], ["protected_meth", "::", :protected], ["private_meth", "::", :private])
52 |
53 | ar = mod.send(meth, ::Sample::BasicExtension::Klass, :to_mav => true)
54 | ar.should include(["public_meth", "::", :public], ["protected_meth", "::", :protected], ["private_meth", "::", :private])
55 | end
56 | end # .get_methods
57 |
58 | describe ".get_module_name" do
59 | meth = :get_module_name
60 |
61 | it "works for normal classes and modules" do
62 | mod.send(meth, Kernel).should == "Kernel"
63 | mod.send(meth, String).should == "String"
64 | end
65 |
66 | it "works for class singletons" do
67 | klass = class << String; self; end
68 | mod.send(meth, klass).should == "String"
69 | end
70 |
71 | it "works for module singletons" do
72 | klass = class << Enumerable; self; end
73 | mod.send(meth, klass).should == "Enumerable"
74 | end
75 |
76 | it "works for instance singletons" do
77 | klass = class << "kk"; self; end
78 | mod.send(meth, klass).should == "String"
79 |
80 | klass = class << []; self; end
81 | mod.send(meth, klass).should == "Array"
82 |
83 | klass = class << (class << []; self; end); self; end
84 | mod.send(meth, klass).should == "Class"
85 | end
86 |
87 | it "works for namespaced names" do
88 | klass = class << ::Sample::BasicExtension::Mo; self; end
89 | mod.send(meth, klass).should == "Sample::BasicExtension::Mo"
90 |
91 | klass = class << ::Sample::BasicExtension::Klass; self; end
92 | mod.send(meth, klass).should == "Sample::BasicExtension::Klass"
93 |
94 | klass = class << ::Sample::BasicExtension::Klass.new; self; end
95 | mod.send(meth, klass).should == "Sample::BasicExtension::Klass"
96 | end
97 | end # .get_module_name
98 |
99 | describe ".shell_escape" do
100 | meth = :shell_escape
101 |
102 | it "generally works" do
103 | mod.send(meth, "").should == "''"
104 | mod.send(meth, "one two").should == "one\\ two"
105 | mod.send(meth, "one\ntwo").should == "one'\n'two"
106 | mod.send(meth, "Kernel#`").should == "Kernel\\#\\`"
107 | end
108 | end # .shell_escape
109 | end # ::ORI::Tools
110 |
--------------------------------------------------------------------------------
/lib/ori/tools.rb:
--------------------------------------------------------------------------------
1 | module ORI
2 | # Generic tools.
3 | module Tools #:nodoc:
4 | ANSI_ATTRS = {
5 | :reset => 0,
6 | :bold => 1,
7 | :underscore => 4,
8 | :underline => 4,
9 | :blink => 5,
10 | :reverse => 7,
11 | :concealed => 8,
12 | :black => 30,
13 | :red => 31,
14 | :green => 32,
15 | :yellow => 33,
16 | :blue => 34,
17 | :magenta => 35,
18 | :cyan => 36,
19 | :white => 37,
20 | :on_black => 40,
21 | :on_red => 41,
22 | :on_green => 42,
23 | :on_yellow => 43,
24 | :on_blue => 44,
25 | :on_magenta => 45,
26 | :on_cyan => 46,
27 | :on_white => 47,
28 | }
29 |
30 | # Default inspectors for get_methods.
31 | GET_METHODS_INSPECTORS = [
32 | :private_instance_methods,
33 | :protected_instance_methods,
34 | :public_instance_methods,
35 | :private_methods,
36 | :protected_methods,
37 | :public_methods,
38 | ]
39 |
40 | # Build an ANSI sequence.
41 | #
42 | # ansi() # => ""
43 | # ansi(:red) # => "\e[31m"
44 | # ansi(:red, :bold) # => "\e[31;1m"
45 | # puts "Hello, #{ansi(:bold)}user#{ansi(:reset)}"
46 | def self.ansi(*attrs)
47 | codes = attrs.map {|attr| ANSI_ATTRS[attr.to_sym] or raise ArgumentError, "Unknown attribute #{attr.inspect}"}
48 | codes.empty?? "" : "\e[#{codes.join(';')}m"
49 | end
50 |
51 | # Inspect an object with various inspectors.
52 | #
53 | # Options:
54 | #
55 | # :inspectors => [] # Array of inspectors, e.g. [:public_instance_methods].
56 | # :inspector_arg => T|F # Arg to pass to inspector. Default is true
57 | # :to_mav => T|F # Post-transform list into [method_name, access, visibility] ("MAV"). Default is false.
58 | #
59 | # Examples:
60 | #
61 | # get_methods(obj)
62 | # # => [[inspector, [methods]], [inspector, [methods]], ...]
63 | # get_methods(obj, :to_mav => true)
64 | # # => [[method_name, access, visibility], [method_name, access, visibility], ...]
65 | def self.get_methods(obj, options = {})
66 | options = options.dup
67 | o = {}
68 | o[k = :inspectors] = (v = options.delete(k)).nil?? GET_METHODS_INSPECTORS : v
69 | o[k = :inspector_arg] = (v = options.delete(k)).nil?? true : v
70 | o[k = :to_mav] = (v = options.delete(k)).nil?? false : v
71 | raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty?
72 |
73 | out = []
74 |
75 | o[:inspectors].each do |inspector|
76 | next if not obj.respond_to? inspector
77 | out << [inspector.to_s, obj.send(inspector, o[:inspector_arg]).sort.map(&:to_s)]
78 | end
79 |
80 | if o[:to_mav]
81 | mav = []
82 |
83 | is_module = obj.is_a? Module
84 |
85 | out.each do |inspector, methods|
86 | ##puts "-- inspector-#{inspector.inspect}"
87 | access = (is_module and not inspector.match /instance/) ? "::" : "#"
88 |
89 | visibility = if inspector.match /private/
90 | :private
91 | elsif inspector.match /protected/
92 | :protected
93 | else
94 | :public
95 | end
96 |
97 | methods.each do |method_name|
98 | mav << [method_name, access, visibility]
99 | end
100 | end
101 |
102 | out = mav.uniq # NOTE: Dupes are possible, e.g. when custom inspectors are given.
103 | end
104 |
105 | out
106 | end
107 |
108 | # Return name of a module, even a "nameless" one.
109 | def self.get_module_name(mod)
110 | if mod.name.to_s.empty?
111 | if mat = mod.inspect.match(/#|:[0#])/)
112 | mat[1]
113 | end
114 | else
115 | mod.name
116 | end
117 | end
118 |
119 | # Escape string for use in Unix shell command.
120 | # Credits http://stackoverflow.com/questions/1306680/shellwords-shellescape-implementation-for-ruby-1-8.
121 | def self.shell_escape(s)
122 | # An empty argument will be skipped, so return empty quotes.
123 | return "''" if s.empty?
124 |
125 | s = s.dup
126 |
127 | # Process as a single byte sequence because not all shell
128 | # implementations are multibyte aware.
129 | s.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
130 |
131 | # A LF cannot be escaped with a backslash because a backslash + LF
132 | # combo is regarded as line continuation and simply ignored.
133 | s.gsub!(/\n/, "'\n'")
134 |
135 | s
136 | end
137 |
138 | # Escape string for use in Windows command. Word "shell" is used for similarity.
139 | def self.win_shell_escape(s)
140 | s
141 | end
142 | end # Tools
143 | end # ORI
144 |
--------------------------------------------------------------------------------
/spec/list_method_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), "spec_helper")
2 |
3 | describe "::ORI::ListMethod" do
4 | klass = ::ORI::ListMethod
5 |
6 | describe "#access" do
7 | meth = :access
8 |
9 | it "generally works" do
10 | inputs = []
11 | inputs << [{:obj => ::Sample::BasicInheritance::Son, :method_name => "grandpa_protected", :inspector => "protected_instance_methods"}, "#"]
12 | inputs << [{:obj => ::Sample::BasicInheritance::Son, :method_name => "grandpa_protected_singleton", :inspector => "protected_methods"}, "::"]
13 | inputs.each do |attrs, expected|
14 | r = klass.new(attrs)
15 | ##p "r", r
16 | r.send(meth).should == expected
17 | end
18 | end
19 | end
20 |
21 | describe "#format" do
22 | meth = :format
23 |
24 | it "generally works" do
25 | inputs = []
26 | inputs << [{:obj => 5, :method_name => "%", :inspector => "public_methods"}, [/\bFixnum#%/]]
27 | inputs << [{:obj => 5, :method_name => "between?", :inspector => "public_methods"}, [/\bFixnum\b/, /\bComparable\b/, /#between\?/]]
28 | inputs << [{:obj => 5, :method_name => "puts", :inspector => "private_methods"}, [/\bFixnum\b/, /\bKernel\b/, /private/]]
29 | inputs << [{:obj => nil, :method_name => "to_s", :inspector => "public_methods"}, [/\bNilClass#to_s/]]
30 | inputs << [{:obj => Hash, :method_name => "<", :inspector => "public_methods"}, [/\bHash\b/, /\bModule\b/, /]]
31 | inputs << [{:obj => [], :method_name => "&", :inspector => "public_methods"}, [/\bArray#&/]]
32 | inputs << [{:obj => Kernel, :method_name => "puts", :inspector => "public_methods"}, [/\bKernel::puts/]]
33 | inputs << [{:obj => ::Sample::BasicExtension::OtherMo, :method_name => "public_meth", :inspector => "public_methods"}, [/\bSample::BasicExtension::OtherMo\b/, /\bSample::BasicExtension::Mo\b/, /::public_meth/]]
34 | inputs << [{:obj => ::Sample::BasicExtension::OtherMo, :method_name => "protected_meth", :inspector => "protected_methods"}, [/\bSample::BasicExtension::OtherMo\b/, /\bSample::BasicExtension::Mo\b/, /::protected_meth/]]
35 | inputs << [{:obj => ::Sample::BasicExtension::OtherMo, :method_name => "private_meth", :inspector => "private_methods"}, [/\bSample::BasicExtension::OtherMo\b/, /\bSample::BasicExtension::Mo\b/, /::private_meth/]]
36 |
37 | inputs.each do |attrs, checks|
38 | r = klass.new(attrs)
39 | ##p "r", r
40 | checks.each do |re|
41 | ##p "re", re
42 | r.send(meth, :color => false).should match re
43 | end
44 | end
45 | end
46 |
47 | it "supports colored and plain output" do
48 | inputs = []
49 | inputs << {:obj => 5, :method_name => "%", :inspector => "public_methods"}
50 | inputs << {:obj => 5, :method_name => "between?", :inspector => "public_methods"}
51 | inputs << {:obj => 5, :method_name => "puts", :inspector => "private_methods"}
52 | inputs << {:obj => nil, :method_name => "to_s", :inspector => "public_methods"}
53 | inputs << {:obj => Hash, :method_name => "<", :inspector => "public_methods"}
54 | inputs << {:obj => [], :method_name => "&", :inspector => "public_methods"}
55 | inputs << {:obj => Kernel, :method_name => "puts", :inspector => "public_methods"}
56 |
57 | inputs.each do |attrs|
58 | r = klass.new(attrs)
59 | ##p "r", r
60 | r.send(meth, :color => true).should match Regexp.new(Regexp.escape(attrs[:method_name]))
61 | r.send(meth, :color => true).should match Regexp.new(Regexp.escape("\e"))
62 | r.send(meth, :color => false).should match Regexp.new(Regexp.escape(attrs[:method_name]))
63 | r.send(meth, :color => false).should_not match Regexp.new(Regexp.escape("\e"))
64 | end
65 | end
66 | end # #format
67 |
68 | describe "#own?" do
69 | meth = :own?
70 |
71 | it "generally works" do
72 | inputs = []
73 | inputs << [{:obj => 5, :method_name => "%", :inspector => "public_methods"}, true]
74 | inputs << [{:obj => 5, :method_name => "between?", :inspector => "public_methods"}, false]
75 | inputs << [{:obj => Kernel, :method_name => "puts", :inspector => "public_methods"}, true]
76 | inputs << [{:obj => Kernel, :method_name => "dup", :inspector => "public_instance_methods"}, true]
77 |
78 | inputs.each do |attrs, expected|
79 | r = klass.new(attrs)
80 | ##p "r", r
81 | r.send(meth).should == expected
82 | end
83 | end
84 | end # #own?
85 |
86 | describe "#ri_topics" do
87 | meth = :ri_topics
88 |
89 | include HelperMethods
90 |
91 | it "generally works" do
92 | r = glm(5, "#is_a?")
93 | r.ri_topics.should include ["Fixnum", "#", "is_a?"]
94 | r.ri_topics.should include ["Object", "#", "is_a?"]
95 |
96 | r = glm(Hash, "#[]")
97 | r.ri_topics.should include ["Hash", "#", "[]"]
98 |
99 | r = glm(Sample::BasicInheritance::Son, "grandpa_public")
100 | r.ri_topics.should include ["Sample::BasicInheritance::Son", "#", "grandpa_public"]
101 | r.ri_topics.should include ["Sample::BasicInheritance::Grandpa", "#", "grandpa_public"]
102 |
103 | r = glm(Sample::BasicInheritance::Son, "grandpa_public_singleton")
104 | r.ri_topics.should include ["Sample::BasicInheritance::Son", "::", "grandpa_public_singleton"]
105 | r.ri_topics.should include ["Sample::BasicInheritance::Papa", "::", "grandpa_public_singleton"]
106 | r.ri_topics.should include ["Sample::BasicInheritance::Grandpa", "::", "grandpa_public_singleton"]
107 |
108 | r = glm(Sample::BasicExtension::Klass, "public_meth")
109 | r.ri_topics.should include ["Sample::BasicExtension::Klass", "::", "public_meth"]
110 | r.ri_topics.should include ["Sample::BasicExtension::Mo", "#", "public_meth"]
111 | end
112 |
113 | it "does not contain duplicates" do
114 | r = glm(Module, "#public")
115 | r.ri_topics.should == r.ri_topics.uniq
116 | end
117 | end # #ri_topics
118 | end
119 |
--------------------------------------------------------------------------------
/lib/ori/list_method.rb:
--------------------------------------------------------------------------------
1 | module ORI
2 | # Our method representation suitable for listing.
3 | class ListMethod #:nodoc:
4 | OWN_MARKER = ["~", " "]
5 |
6 | # Object. Can be anything, including nil.
7 | attr_reader :obj
8 |
9 | attr_reader :method_name
10 | attr_reader :inspector
11 |
12 | def initialize(attrs = {})
13 | attrs.each {|k, v| send("#{k}=", v)}
14 | clear_cache
15 | end
16 |
17 | #--------------------------------------- Accessors and pseudo accessors
18 |
19 | # Return method access substring: "::" or "#".
20 | def access
21 | # NOTE: It is *WRONG* to rely on Ruby's `inspect` to handle things because
22 | # it doesn't work for cases when singleton methods are included from modules.
23 | @cache[:access] ||= (module? and not inspector.match /instance/) ? "::" : "#"
24 | end
25 |
26 | def inspector=(s)
27 | @inspector = s.to_s
28 | clear_cache
29 | end
30 |
31 | def instance?
32 | access == "#"
33 | end
34 |
35 | def method_name=(s)
36 | @method_name = s.to_s
37 | clear_cache
38 | end
39 |
40 | # Fetch method object.
41 | def method_object
42 | require_valid
43 |
44 | @cache[:method_object] ||= if @inspector.match /instance/
45 | @obj._ori_instance_method(@method_name)
46 | else
47 | @obj._ori_method(@method_name)
48 | end
49 | end
50 |
51 | def module?
52 | @cache[:is_module] ||= begin
53 | require_obj
54 | @obj.is_a? Module
55 | end
56 | end
57 |
58 | def obj=(obj)
59 | @obj = obj
60 | @obj_present = true
61 | clear_cache
62 | end
63 |
64 | def obj_module
65 | @cache[:obj_module] ||= obj.is_a?(Module) ? obj : obj.class
66 | end
67 |
68 | def obj_module_name
69 | @cache[:obj_module_name] ||= Tools.get_module_name(obj_module)
70 | end
71 |
72 | def owner
73 | @cache[:owner] ||= method_object.owner
74 | end
75 |
76 | # Get, if possible, obj singleton class.
77 | # Some objects, e.g. Fixnum instances, don't have a singleton class.
78 | def obj_singleton_class
79 | @cache[:obj_singleton] ||= begin
80 | class << obj #:nodoc:
81 | self
82 | end
83 | rescue
84 | nil
85 | end
86 | end
87 |
88 | # Return true if method is natively owned by obj class.
89 | def own?
90 | @cache[:is_own] ||= begin
91 | require_valid
92 | owner == obj_module || owner == obj_singleton_class
93 | end
94 | end
95 |
96 | def owner_name
97 | @cache[:owner_name] ||= Tools.get_module_name(owner)
98 | end
99 |
100 | def private?
101 | visibility == :private
102 | end
103 |
104 | def protected?
105 | visibility == :protected
106 | end
107 |
108 | def public?
109 | visibility == :public
110 | end
111 |
112 | def singleton?
113 | access == "::"
114 | end
115 |
116 | # Return visibility: :public, :protected, :private.
117 | def visibility
118 | @cache[:visibility] ||= begin
119 | require_valid
120 |
121 | if @inspector.match /private/
122 | :private
123 | elsif @inspector.match /protected/
124 | :protected
125 | else
126 | :public
127 | end
128 | end
129 | end
130 |
131 | #---------------------------------------
132 |
133 | # Format self into a string.
134 | #
135 | # Options:
136 | #
137 | # :color => T|F # Default is true.
138 | def format(options = {})
139 | options = options.dup
140 | o = {}
141 | o[k = :color] = (v = options.delete(k)).nil?? true : v
142 | raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty?
143 |
144 | require_valid
145 |
146 | Colorize.colorize *[
147 | (own?? [[:list_method, :own_marker], OWN_MARKER[0]] : [[:list_method, :not_own_marker], OWN_MARKER[1]]),
148 | [[:list_method, :obj_module_name], obj_module_name],
149 | ([[:list_method, :owner_name], "(#{owner_name})"] if not own?),
150 | [[:list_method, :access], access],
151 | [[:list_method, :name], method_name],
152 | ([[:list_method, :visibility], " [#{visibility}]"] if not public?),
153 | [[:reset]],
154 | ].compact.flatten(1).reject {|v| v.is_a? Array and not o[:color]}
155 | end
156 |
157 | # Match entire formatted record against RE.
158 | def fullmatch(re)
159 | format(:color => false).match(re)
160 | end
161 |
162 | # Match method name against RE.
163 | def match(re)
164 | @method_name.match(re)
165 | end
166 |
167 | # Quick format. No options, no hashes, no checks.
168 | def qformat
169 | #"#{owner_name}#{access}#{@method_name} [#{visibility}]" # Before multi-obj support.
170 | "#{obj_module_name}#{access}#{@method_name} [#{visibility}]"
171 | end
172 |
173 | def ri_topics
174 | @cache[:ri_topics] ||= begin
175 | require_valid
176 |
177 | # Build "hierarchy methods". Single record is:
178 | #
179 | # ["Kernel", "#", "dup"]
180 | hmethods = []
181 |
182 | # Always stuff self in front of the line regardless of if we have method or not.
183 | hmethods << [obj_module_name, access, method_name]
184 |
185 | ancestors = []
186 | ancestors += obj_module.ancestors
187 | ancestors += obj_singleton_class.ancestors if obj_singleton_class # E.g. when module extends class.
188 |
189 | ancestors.each do |mod|
190 | mav = Tools.get_methods(mod, :inspector_arg => false, :to_mav => true)
191 | ##p "mav", mav
192 | found = mav.select {|method_name,| method_name == self.method_name}
193 | ##p "found", found
194 | found.each do |method_name, access|
195 | hmethods << [Tools.get_module_name(mod), access, method_name]
196 | end
197 | end
198 |
199 | # Misdoc hack -- stuff Object#meth lookup if Kernel#meth is present. For methods like Kernel#is_a?.
200 | if (found = hmethods.find {|mod, access| [mod, access] == ["Kernel", "#"]}) and not hmethods.find {|mod,| mod == "Object"}
201 | hmethods << ["Object", "#", found.last]
202 | end
203 |
204 | hmethods.uniq
205 | end
206 | end
207 |
208 | def valid?
209 | [
210 | @obj_present,
211 | @method_name,
212 | @inspector,
213 | ].all?
214 | end
215 |
216 | #---------------------------------------
217 |
218 | # Support Enumerable#sort.
219 | def <=>(other)
220 | [@method_name, access, obj_module_name] <=> [other.method_name, other.access, obj_module_name]
221 | end
222 |
223 | # Support Array#uniq.
224 | def hash
225 | @cache[:hash] ||= qformat.hash
226 | end
227 |
228 | # Support Array#uniq.
229 | def eql?(other)
230 | hash == other.hash
231 | end
232 |
233 | #---------------------------------------
234 | private
235 |
236 | def clear_cache
237 | @cache = {}
238 | end
239 |
240 | def require_obj
241 | raise "`obj` is not set" if not @obj_present
242 | end
243 |
244 | def require_valid
245 | raise "Object is not valid" if not valid?
246 | end
247 | end # ListMethod
248 | end
249 |
--------------------------------------------------------------------------------
/spec/internals_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), "spec_helper")
2 |
3 | # NOTE: When checking collection against the rule, it's wiser to use `all?`, `any?` and `none?` and ONE `should` at the end. It's about 2 times faster
4 | # than calling a `should` at every pass of an `each` loop.
5 |
6 | describe "::ORI::Internals" do
7 | mod = ::ORI::Internals
8 |
9 | describe ".get_list_methods" do
10 | meth = :get_list_methods
11 |
12 | it "maintains uniqueness of output" do
13 | list1 = mod.send(meth, :objs => [1])
14 | list2 = mod.send(meth, :objs => [1, 1])
15 | (list2 - list1).should be_empty
16 |
17 | list1 = mod.send(meth, :objs => [Array])
18 | list2 = mod.send(meth, :objs => [Array, Array])
19 | (list2 - list1).should be_empty
20 |
21 | list1 = mod.send(meth, :objs => [1])
22 | list2 = mod.send(meth, :objs => [1, 2])
23 | (list2 - list1).should be_empty
24 | end
25 |
26 | it "requires :objs option to be an Array" do
27 | proc do
28 | mod.send(meth)
29 | end.should raise_error ArgumentError
30 |
31 | proc do
32 | mod.send(meth, :objs => 5)
33 | end.should raise_error ArgumentError
34 | end
35 |
36 | it "supports :access option" do
37 | proc do
38 | mod.send(meth, :objs => [5], :access => 1)
39 | mod.send(meth, :objs => [5], :access => "kk")
40 | end.should raise_error ArgumentError
41 |
42 | proc do
43 | mod.send(meth, :objs => [5], :access => "#")
44 | mod.send(meth, :objs => [5], :access => "::")
45 | mod.send(meth, :objs => [5], :access => :"#")
46 | mod.send(meth, :objs => [5], :access => :"::")
47 | end.should_not raise_error
48 |
49 | list_methods = mod.send(meth, :objs => [Time], :access => "::")
50 | list_methods.all? {|r| r.access == "::"}.should == true
51 |
52 | list_methods = mod.send(meth, :objs => [Time], :access => "#")
53 | list_methods.all? {|r| r.access == "#"}.should == true
54 | end
55 |
56 | it "supports `:fullre` option" do
57 | list_methods = mod.send(meth, :objs => [Module], :fullre => (re = /Object.*::/))
58 | ##re = /kk/ #DEBUG: Fail by hand.
59 | list_methods.all? do |r|
60 | ##p "r", r
61 | r.format(:color => false).match re
62 | end.should == true
63 | end
64 |
65 | it "supports `:own` option" do
66 | inputs = []
67 | inputs << 5
68 | inputs << []
69 | inputs << String
70 | inputs << Enumerable
71 | inputs << Array
72 | inputs << Class
73 | inputs << Module
74 | inputs << Object
75 |
76 | inputs.each do |obj|
77 | list_methods = mod.send(meth, :objs => [obj], :own => true)
78 | list_methods.all? do |r|
79 | ##p "r", r # Uncomment if fails.
80 | r.own?
81 | end.should == true
82 | end
83 |
84 | list_methods = mod.send(meth, :objs => inputs, :own => true)
85 | list_methods.all? do |r|
86 | ##p "r", r # Uncomment if fails.
87 | r.own?
88 | end.should == true
89 | end
90 |
91 | it "supports `:re` option" do
92 | list_methods = mod.send(meth, :objs => [Module], :re => (re = /pub/))
93 | ##re = /kk/ #DEBUG: Fail by hand.
94 | list_methods.all? do |r|
95 | ##p "r", r
96 | r.method_name.match re
97 | end.should == true
98 | end
99 |
100 | it "supports `:visibility` option" do
101 | list_methods = mod.send(meth, :objs => [::Sample::BasicInheritance::Son], :all => true, :visibility => :public)
102 | list_methods.all? do |r|
103 | ##p "r", r
104 | r.public?
105 | end.should == true
106 |
107 | list_methods = mod.send(meth, :objs => [::Sample::BasicInheritance::Son], :all => true, :visibility => :protected)
108 | list_methods.all? do |r|
109 | ##p "r", r
110 | r.protected?
111 | end.should == true
112 |
113 | list_methods = mod.send(meth, :objs => [::Sample::BasicInheritance::Son], :all => true, :visibility => :private)
114 | list_methods.all? do |r|
115 | ##p "r", r
116 | r.private?
117 | end.should == true
118 | end
119 |
120 | describe "filtering" do
121 | it "chops off all methods starting with '_ori_'" do
122 | inputs = [5, "", [], String, Enumerable, Array, Class, Module, Object]
123 | inputs.each do |obj|
124 | list_methods = mod.send(meth, :objs => [obj])
125 | list_methods.none? do |r|
126 | ##p "r", r # Uncomment if fails.
127 | r.method_name.match /\A_ori_/
128 | end.should == true
129 | end
130 |
131 | list_methods = mod.send(meth, :objs => inputs)
132 | list_methods.none? do |r|
133 | ##p "r", r # Uncomment if fails.
134 | r.method_name.match /\A_ori_/
135 | end.should == true
136 | end
137 |
138 | it "chops off non-public Kernel methods if `obj` is not Kernel" do
139 | inputs = [5, "", [], String, Enumerable, Array, Class, Module, Object]
140 | inputs.each do |obj|
141 | list_methods = mod.send(meth, :objs => [obj])
142 | list_methods.none? do |r|
143 | ##p "r", r # Uncomment if fails.
144 | r.owner == Kernel and not r.public?
145 | end.should == true
146 |
147 | list_methods = mod.send(meth, :objs => inputs)
148 | list_methods.none? do |r|
149 | ##p "r", r # Uncomment if fails.
150 | r.owner == Kernel and not r.public?
151 | end.should == true
152 | end
153 | end
154 |
155 | it "chops off non-public methods if `obj` is an object" do
156 | inputs = [5, "", []]
157 | inputs.each do |obj|
158 | list_methods = mod.send(meth, :objs => [obj])
159 | list_methods.none? do |r|
160 | ##p "r", r # Uncomment if fails.
161 | not r.public?
162 | end.should == true
163 | end
164 | end
165 |
166 | it "chops off others' private instance methods if `obj` is a module or a class" do
167 | inputs = [String, Enumerable, Array, Class, Module, Object, ::Sample::BasicInheritance::Son]
168 | inputs.each do |obj|
169 | list_methods = mod.send(meth, :objs => [obj])
170 | list_methods.none? do |r|
171 | ##p "r", r # Uncomment if fails.
172 | not r.own? and r.private? and r.instance?
173 | end.should == true
174 | end
175 |
176 | list_methods = mod.send(meth, :objs => inputs)
177 | list_methods.none? do |r|
178 | ##p "r", r # Uncomment if fails.
179 | not r.own? and r.private? and r.instance?
180 | end.should == true
181 | end
182 |
183 | it "generally works for modules and classes" do
184 | list_methods = mod.send(meth, :objs => [::Sample::BasicInheritance::Son])
185 | list_methods.find {|r| r.method_name == "son_public"}.should_not be_nil
186 | list_methods.find {|r| r.method_name == "son_protected"}.should_not be_nil
187 | list_methods.find {|r| r.method_name == "son_private"}.should_not be_nil
188 | list_methods.find {|r| r.method_name == "papa_public"}.should_not be_nil
189 | list_methods.find {|r| r.method_name == "papa_protected"}.should_not be_nil
190 | list_methods.find {|r| r.method_name == "grandpa_public"}.should_not be_nil
191 | list_methods.find {|r| r.method_name == "grandpa_protected"}.should_not be_nil
192 | list_methods.find {|r| r.method_name == "papa_private"}.should be_nil
193 | list_methods.find {|r| r.method_name == "grandpa_private"}.should be_nil
194 |
195 | list_methods = mod.send(meth, :objs => [::Sample::BasicExtension::Klass])
196 | list_methods.find {|r| r.method_name == "public_meth"}.should_not be_nil
197 | list_methods.find {|r| r.method_name == "protected_meth"}.should_not be_nil
198 | list_methods.find {|r| r.method_name == "private_meth"}.should_not be_nil
199 |
200 | list_methods = mod.send(meth, :objs => [::Sample::BasicExtension::OtherMo])
201 | list_methods.find {|r| r.method_name == "public_meth"}.should_not be_nil
202 | list_methods.find {|r| r.method_name == "protected_meth"}.should_not be_nil
203 | list_methods.find {|r| r.method_name == "private_meth"}.should_not be_nil
204 | end
205 | end # filtering
206 | end # .get_list_methods
207 |
208 | describe ".get_ri_arg_prefix" do
209 | meth = :get_ri_arg_prefix
210 |
211 | it "generally works" do
212 | mod.send(meth, "kaka").should == nil
213 | mod.send(meth, "Object.ri").should == nil
214 | mod.send(meth, " Object.ri ").should == nil
215 | mod.send(meth, "Object.ri //").should == "Object.ri"
216 | mod.send(meth, " Object.ri :x").should == " Object.ri"
217 | end
218 | end
219 | end
220 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Object-oriented RI for IRB console
3 | ====================================
4 |
5 |
6 | * [Introduction](#introduction)
7 | * [Setup](#setup)
8 | * [Pre-setup (test your environment)](#pre_setup)
9 | * [Regular setup](#regular_setup)
10 | * [RVM setup](#rvm_setup)
11 | * [Rails 3.x+Bundler setup](#rails_3_bundler_setup)
12 | * [Local project doc setup](#local_project_doc_setup)
13 | * [Usage](#usage)
14 | * [Configuration](#configuration)
15 | * [Compatibility](#compatibility)
16 | * [Copyright](#copyright)
17 | * [Feedback](#feedback)
18 |
19 |
20 | Introduction
21 | --------------------------------------
22 |
23 | Finding documentation for Ruby gems and libraries is often time-consuming.
24 | ORI addresses this issue by bringing RI documentation right to your IRB console in a **simple**, **consistent** and truly **object-oriented** way.
25 |
26 | If you're too lazy to read this README, [watch this screencast](http://www.screencast-o-matic.com/watch/cXVVYuXpH) instead.
27 |
28 |
29 | Setup
30 | ------------------------
31 |
32 | [Click here](#usage) to skip the boring setup part and see live examples right away.
33 |
34 | ### Pre-setup (test your environment) ###
35 |
36 | 1. Check your Ruby version. Should be at least **1.8.7**.
37 |
38 | ~~~
39 | $ ruby -v
40 | ruby 1.9.3p327 (2012-11-10 revision 37606) [i686-linux]
41 | ~~~
42 |
43 | 2. Check your RI version. Should be at least version **2.5**:
44 |
45 | ~~~
46 | $ ri --version
47 | ri 3.9.4
48 | ~~~
49 |
50 | 3. Check if core RI documentation is available:
51 |
52 | ~~~
53 | $ ri Array.each
54 | ~~~
55 |
56 | You should see the doc article.
57 |
58 | **If you see `Nothing known about Array`, you are missing the core RI documentation. To set it up, please follow the steps from [README_RI_CORE](README_RI_CORE.md).**
59 |
60 |
61 | ### Regular setup ###
62 |
63 | Install the gem:
64 |
65 | ~~~
66 | $ gem sources --add http://rubygems.org
67 | $ gem install ori
68 | ~~~
69 |
70 | Add to your `~/.irbrc`:
71 |
72 | ~~~
73 | require "rubygems"
74 | require "ori"
75 | ~~~
76 |
77 | Test:
78 |
79 | ~~~
80 | $ irb
81 | irb> Array.ri //
82 | irb> Array.ri :each
83 | ~~~
84 |
85 |
86 | ### RVM setup ###
87 |
88 | Under Ruby Version Manager ([RVM](http://rvm.beginrescueend.com/)), install the gem into `global` gemset of Ruby versions you're using:
89 |
90 | ~~~
91 | $ rvm 1.9.3
92 | $ rvm gemset use global
93 | $ gem install ori
94 | $ gem install rdoc
95 | ~~~
96 |
97 | Add to your `~/.irbrc`:
98 |
99 | ~~~
100 | require "rubygems"
101 | require "ori"
102 | ~~~
103 |
104 | Test:
105 |
106 | ~~~
107 | $ irb
108 | irb> Array.ri //
109 | irb> Array.ri :each
110 | ~~~
111 |
112 |
113 | ### Rails 3.x+Bundler setup ###
114 |
115 | First, complete steps described in [RVM setup](#rvm_setup).
116 |
117 | Then, step into your Rails project directory:
118 |
119 | ~~~
120 | $ cd myrailsproject
121 | ~~~
122 |
123 | Add to your `Gemfile`:
124 |
125 | ~~~
126 | group :development do
127 | gem "ori"
128 | gem "rdoc"
129 | end
130 | ~~~
131 |
132 | Test:
133 |
134 | ~~~
135 | $ ri Array.each
136 | $ bundle exec ri Array.each
137 | ~~~
138 |
139 | You should see the doc article in **both** cases.
140 |
141 | And finally:
142 |
143 | ~~~
144 | $ rails console
145 | >> Array.ri :each
146 | ~~~
147 |
148 | #### Further important Bundler information ####
149 |
150 | At the moment of this writing (2012-12-14) `bundle install` installs gems without RI documentation and it's not possible to change this behavior via options of any kind.
151 |
152 | It means that you need to **manually re-install** the gems for which you need RI documentation. Example:
153 |
154 | ~~~
155 | $ rails console
156 | >> ActiveRecord::Base.ri :validate
157 | No articles found
158 | ~~~
159 |
160 | The above means that Rails components have been installed via `bundle install` and have no RI documentation. Let's fix it:
161 |
162 | ~~~
163 | $ grep rails Gemfile
164 | gem "rails", "3.2.8"
165 | $ gem install rails -v 3.2.8
166 | ~~~
167 |
168 | Rails gems are now re-installed, let's try again:
169 |
170 | ~~~
171 | $ rails console
172 | >> ActiveRecord::Base.ri :validate
173 | ActiveModel::Validations::ClassMethods#validate
174 |
175 | (from gem activemodel-3.2.8)
176 | ------------------------------------------------------------------------------
177 | validate(*args, &block)
178 |
179 | ------------------------------------------------------------------------------
180 |
181 | Adds a validation method or block to the class. This is useful when overriding
182 | the validate instance method becomes too unwieldy and you're looking
183 | for more descriptive declaration of your validations.
184 | ...
185 | ~~~
186 |
187 | Seems to work now.
188 |
189 |
190 | Local project doc setup
191 | ------------------------------------------------------------
192 |
193 | With a small hack it is possible to generate **your own local project's** RI documentation and make it instantly available in your IRB/Rails console.
194 |
195 | To do it, add to your `~/.irbrc`:
196 |
197 | ~~~
198 | # Local doc hack.
199 | if true
200 | path = "doc/ri"
201 |
202 | eval %{
203 | module Kernel
204 | private
205 |
206 | def localdoc
207 | system("rdoc --ri --all -O -o #{path}")
208 | end
209 | end
210 |
211 | # Add local lookup path if it exists. Otherwise `ri` will refuse to find anything at all.
212 | if File.directory? "#{path}"
213 | ORI.conf.frontend.gsub!("%s", "-d #{path} %s")
214 | end
215 | } # eval
216 |
217 | puts "Local doc hack available. Use `localdoc` to update project's RI doc"
218 | end # if true
219 | ~~~
220 |
221 | Now in your project's IRB or Rails console you can do a:
222 |
223 | ~~~
224 | >> localdoc
225 | ~~~
226 |
227 | After the doc has been rebuilt, do a:
228 |
229 | ~~~
230 | >> MyKlass.ri
231 | >> MyKlass.ri :some_method
232 | ~~~
233 |
234 | , and enjoy an up-to-date doc.
235 |
236 | My own experience shows that the habit of using `localdoc` has at least two good consequences:
237 |
238 | * Now there **is** an incentive to document methods **now**, not "when time permits".
239 | * We can keep an eye on RDoc issues which occur during the doc generation.
240 | RDoc's parser is far from being perfect, but in many cases we can fix or work around certain quirks.
241 | It's much easier to do it in small portions as we go, rather than all at once.
242 |
243 | If your experience is different from mine, feel free to share it!
244 |
245 |
246 | Usage
247 | ------------------------
248 |
249 | All commands listed below are assumed to be typed in IRB. Example:
250 |
251 | ~~~
252 | $ irb
253 | irb> Array.ri
254 | ~~~
255 |
256 | ### Request RI on a class ###
257 |
258 | It's fairly straightforward -- grab a class or class instance and call `ri` on it:
259 |
260 | ~~~
261 | Array.ri
262 | String.ri
263 | [].ri
264 | "".ri
265 | ar = Array.new
266 | ar.ri
267 | ~~~
268 |
269 | ### Request RI on a method ###
270 |
271 | ~~~
272 | String.ri :upcase
273 | "".ri :upcase
274 | [].ri :each
275 | Hash.ri :[]
276 | Hash.ri "::[]"
277 | Hash.ri "#[]"
278 | ~~~
279 |
280 | ### Request interactive method list ###
281 |
282 | Interactive method list lets you explore the particular class or object by listing the methods it actually has. This powerful feature is my personal favorite. Try it once and you'll like it, too.
283 |
284 | ~~~
285 | # Regular expression argument denotes list request.
286 | String.ri //
287 | "".ri //
288 |
289 | # Show method names matching a regular expression.
290 | "".ri /case/
291 | "".ri /^to_/
292 | [].ri /sort/
293 | {}.ri /each/
294 |
295 | # Show own methods only.
296 | Time.ri //, :own => true
297 | Time.ri //, :own
298 |
299 | # Show ALL methods, including those private of Kernel.
300 | Hash.ri //, :all => true
301 | Hash.ri //, :all
302 |
303 | # Show class methods or instance methods only.
304 | Module.ri //, :access => "::"
305 | Module.ri //, :access => "#"
306 |
307 | # Specify visibility: public, protected or private.
308 | Module.ri //, :visibility => :private
309 | Module.ri //, :visibility => [:public, :protected]
310 |
311 | # Filter fully formatted name by given regexp.
312 | Module.ri //, :fullre => /\(Object\)::/
313 |
314 | # Combine options.
315 | Module.ri //, :fullre => /\(Object\)::/, :access => "::", :visibility => :private
316 | ~~~
317 |
318 | ### Request interactive method list for more than 1 object at once ###
319 |
320 | By using the `:join` option it's possible to fetch methods for more
321 | than 1 object at once. Value of `:join` (which can be an object or an array)
322 | is joined with the original receiver, and then a combined set is queried.
323 |
324 | ~~~
325 | # List all division-related methods from numeric classes.
326 | Fixnum.ri /div/, :join => [Float, Rational]
327 | 5.ri /div/, :join => [5.0, 5.to_r]
328 |
329 | # List all ActiveSupport extensions to numeric classes.
330 | 5.ri //, :join => [5.0, 5.to_r], :fullre => /ActiveSupport/
331 |
332 | # Query entire Rails family for methods having the word "javascript".
333 | rails_modules = ObjectSpace.each_object(Module).select {|mod| mod.to_s.match /Active|Action/}
334 | "".ri /javascript/, :join => rails_modules
335 | ~~~
336 |
337 |
338 | Configuration
339 | ----------------------------------------
340 |
341 | You can configure ORI via `ORI.conf` object. By default it's autoconfigured based on your OS and environment.
342 |
343 | # Enable color.
344 | ORI.conf.color = true
345 |
346 | # RI frontend command to use. `%s` is replaced with sought topic.
347 | ORI.conf.frontend = "ri -T -f ansi %s"
348 |
349 | # Paging program to use.
350 | ORI.conf.pager = "less -R"
351 |
352 |
353 | Compatibility
354 | ----------------------------------------
355 |
356 | Tested to run on:
357 |
358 | * Ruby 1.9.3-p0, Linux, RVM
359 | * Ruby 1.9.2-p290, Linux, RVM
360 | * Ruby 1.9.2-p0, Linux, RVM
361 | * Ruby 1.8.7-p352, Linux, RVM
362 | * Ruby 1.8.7-p302, Linux, RVM
363 | * Ruby 1.8.7-p72, Windows, Cygwin
364 | * Ruby 1.8.7-p72, Windows
365 |
366 | Compatibility issue reports will be greatly appreciated.
367 |
368 |
369 | Copyright
370 | --------------------------------
371 |
372 | Copyright © 2011-2012 Alex Fortuna.
373 |
374 | Licensed under the MIT License.
375 |
376 |
377 | Feedback
378 | ------------------------------
379 |
380 | Send bug reports, suggestions and criticisms through [project's page on GitHub](http://github.com/dadooda/ori).
381 |
--------------------------------------------------------------------------------
/lib/ori/internals.rb:
--------------------------------------------------------------------------------
1 | module ORI
2 | # Tools used internally by ORI.
3 | module Internals #:nodoc:
4 | GLM_ALL_ACCESSES = ["::", "#"]
5 | GLM_ALL_VISIBILITIES = [:public, :protected, :private]
6 |
7 | # Error message for the user. Sometimes it's CAUSED by the user, sometimes it's influenced by him.
8 | class UserError < Exception #:nodoc:
9 | end
10 |
11 | # Non-destructive break request.
12 | class Break < Exception #:nodoc:
13 | end
14 |
15 | # Apply smart filters on array of ListMethod. Return filtered array.
16 | def self.apply_smart_filters(obj, list_methods)
17 | # Filters.
18 | #
19 | # * Filters return false if record is "bad". Any other return result means that record is "good".
20 | filters = []
21 |
22 | # Hide all methods starting with "_ori_".
23 | filters << proc do |r|
24 | if r.method_name.match /\A_ori_/
25 | false
26 | end
27 | end
28 |
29 | # Obj is not Kernel.
30 | if (obj != Kernel rescue false)
31 | filters << proc do |r|
32 | # Chop off Kernel's non-public methods.
33 | if r.owner == Kernel and not r.public?
34 | false
35 | end
36 | end
37 | end
38 |
39 | # Obj is an object.
40 | if not obj.is_a? Module
41 | filters << proc do |r|
42 | # Chop off non-public methods.
43 | if not r.public?
44 | false
45 | end
46 | end
47 | end
48 |
49 | # Obj is a module or a class.
50 | if obj.is_a? Module
51 | filters << proc do |r|
52 | # Chop off others' private instance methods.
53 | # NOTE: We shouldn't chop private singleton methods since they are callable from the context of our class. See Sample::BasicExtension::Klass.
54 | if not r.own? and r.private? and r.instance?
55 | false
56 | end
57 | end
58 | end
59 |
60 | # Go! If any filter rejects the record, it's rejected.
61 | list_methods.reject do |r|
62 | filters.any? {|f| f.call(r) == false}
63 | end
64 | end
65 |
66 | # Process interactive choice.
67 | # Return chosen item or nil.
68 | #
69 | # choice [
70 | # ["wan", 1.0],
71 | # ["tew", 2.0],
72 | # ["free", 3.0],
73 | # ]
74 | #
75 | # Options:
76 | #
77 | # :colorize_labels => T|F # Default is true.
78 | # :item_indent => " " # Default is " ".
79 | # :prompt => ">" # Default is ">>".
80 | # :title => "my title" # Dialog title. Default is nil (no title).
81 | #
82 | # :on_abort => obj # Result to return on abort (Ctrl-C). Default is nil.
83 | # :on_skip => obj # Treat empty input as skip action. Default is nil.
84 | def self.choice(items, options = {})
85 | raise ArgumentError, "At least 1 item required" if items.size < 1
86 |
87 | options = options.dup
88 | o = {}
89 |
90 | o[k = :colorize_labels] = (v = options.delete(k)).nil?? true : v
91 | o[k = :item_indent] = (v = options.delete(k)).nil?? " " : v
92 | o[k = :prompt] = (v = options.delete(k)).nil?? ">>" : v
93 | o[k = :title] = options.delete(k)
94 |
95 | o[k = :on_abort] = options.delete(k)
96 | o[k = :on_skip] = options.delete(k)
97 |
98 | raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty?
99 |
100 | # Convert `items` into an informative hash.
101 | hitems = []
102 | items.each_with_index do |item, i|
103 | hitems << {
104 | :index => (i + 1).to_s, # Convert to string here, which eliminates the need to do String -> Integer after input.
105 | :label => item[0],
106 | :value => item[1],
107 | }
108 | end
109 |
110 | ### Begin dialog. ###
111 |
112 | if not (s = o[:title].to_s).empty?
113 | puts colorize([:choice, :title], s, [:reset])
114 | puts
115 | end
116 |
117 | # Print items.
118 | index_nchars = hitems.size.to_s.size
119 | hitems.each do |h|
120 | puts colorize(*[
121 | [
122 | o[:item_indent],
123 | [:choice, :index], "%*d" % [index_nchars, h[:index]],
124 | " ",
125 | ],
126 | (o[:colorize_labels] ? [[:choice, :label], h[:label]] : [h[:label]]),
127 | [[:reset]],
128 | ].flatten(1))
129 | end
130 | puts
131 |
132 | # Read input.
133 |
134 | # Catch INT for a while.
135 | old_sigint = trap("INT") do
136 | puts "\nAborted"
137 | return o[:on_abort]
138 | end
139 |
140 | # WARNING: Return result of `while` is return result of method.
141 | while true
142 | print colorize([:choice, :prompt], o[:prompt], " ", [:reset])
143 |
144 | input = gets
145 | if input.nil?
146 | return o[:on_abort]
147 | end
148 |
149 | input.strip!
150 | if input.empty?
151 | if o[:on_skip]
152 | break o[:on_skip]
153 | else
154 | next
155 | end
156 | end
157 |
158 | # Something has been input.
159 | found = hitems.find {|h| h[:index] == input}
160 | break found[:value] if found
161 |
162 | puts colorize([:message, :error], "Invalid input", [:reset])
163 | end # while true
164 | ensure
165 | # NOTE: `old_sigint` is literally declared above, so it always exists here no matter when we gain control.
166 | if not old_sigint.nil?
167 | trap("INT", &old_sigint)
168 | end
169 | end # choice
170 |
171 | # Same as ORI::Colorize.colorize, but this one produces
172 | # plain output if color is turned off in ORI.conf.
173 | def self.colorize(*args)
174 | Colorize.colorize *args.reject {|v| v.is_a? Array and not ::ORI.conf.color}
175 | end
176 |
177 | # Colorize a MAM (module-access-method) array.
178 | #
179 | # colorize_mam(["Kernel", "#", "dup"])
180 | def self.colorize_mam(mam)
181 | colorize(*[
182 | [:mam, :module_name], mam[0],
183 | [:mam, :access], mam[1],
184 | [:mam, :method_name], mam[2],
185 | [:reset],
186 | ])
187 | end
188 |
189 | # Stuff a ready-made ".ri " command into Readline history if last request had an argument.
190 | def self.do_history
191 | # `cmd` is actually THIS command being executed.
192 | cmd = Readline::HISTORY.to_a.last
193 | if prefix = get_ri_arg_prefix(cmd)
194 | Readline::HISTORY.pop
195 | Readline::HISTORY.push "#{prefix} "
196 | Readline::HISTORY.push cmd
197 | end
198 | end
199 |
200 | # Fetch ListMethods from one or more objects (:obj => ...) and optionally filter them.
201 | # Options:
202 | #
203 | # :access => "#" # "#" or "::".
204 | # :all => true|false # Show all methods. Default is `false`.
205 | # :fullre => Regexp # Full record filter.
206 | # :objs => Array # Array of objects to fetch methods of. Must be specified.
207 | # :own => true|false # Show own methods only.
208 | # :re => Regexp # Method name filter.
209 | # :visibility => :protected # Symbol or [Symbol, Symbol, ...].
210 | def self.get_list_methods(options = {})
211 | options = options.dup
212 | o = {}
213 |
214 | o[k = :access] = if v = options.delete(k); v.to_s; end
215 | o[k = :all] = (v = options.delete(k)).nil?? false : v
216 | o[k = :fullre] = options.delete(k)
217 | o[k = :objs] = options.delete(k)
218 | o[k = :own] = (v = options.delete(k)).nil?? false : v
219 | o[k = :re] = options.delete(k)
220 | o[k = :visibility] = options.delete(k)
221 | raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty?
222 |
223 | k = :access; raise ArgumentError, "options[#{k.inspect}] must be in #{GLM_ALL_ACCESSES.inspect}, #{o[k].inspect} given" if o[k] and not GLM_ALL_ACCESSES.include? o[k]
224 | k = :fullre; raise ArgumentError, "options[#{k.inspect}] must be Regexp, #{o[k].class} given" if o[k] and not o[k].is_a? Regexp
225 |
226 | k = :objs
227 | raise ArgumentError, "options[#{k.inspect}] must be set" if not o[k]
228 | raise ArgumentError, "options[#{k.inspect}] must be Array, #{o[k].class} given" if o[k] and not o[k].is_a? Array
229 |
230 | k = :re; raise ArgumentError, "options[#{k.inspect}] must be Regexp, #{o[k].class} given" if o[k] and not o[k].is_a? Regexp
231 |
232 | if o[k = :visibility]
233 | o[k] = [o[k]].flatten
234 | o[k].each do |v|
235 | raise ArgumentError, "options[#{k.inspect}] must be in #{GLM_ALL_VISIBILITIES.inspect}, #{v.inspect} given" if not GLM_ALL_VISIBILITIES.include? v
236 | end
237 | end
238 |
239 | # NOTE: `:all` and `:own` are NOT mutually exclusive. They are mutually confusive. :)
240 |
241 | # Build per-obj lists.
242 | per_obj = o[:objs].uniq.map do |obj|
243 | ar = []
244 |
245 | Tools.get_methods(obj).each do |inspector, methods|
246 | ar += methods.map {|method_name| ListMethod.new(:obj => obj, :inspector => inspector, :method_name => method_name)}
247 | end
248 |
249 | # Filter by access if requested.
250 | ar.reject! {|r| r.access != o[:access]} if o[:access]
251 |
252 | # Filter by visibility if requested.
253 | ar.reject! {|r| o[:visibility].none? {|vis| r.visibility == vis}} if o[:visibility]
254 |
255 | # Leave only own methods if requested.
256 | ar.reject! {|r| not r.own?} if o[:own]
257 |
258 | # Apply RE if requested.
259 | ar.reject! {|r| not r.match(o[:re])} if o[:re]
260 |
261 | # Apply full RE if requested
262 | ar.reject! {|r| not r.fullmatch(o[:fullre])} if o[:fullre]
263 |
264 | # Apply smart filters if requested.
265 | ar = apply_smart_filters(obj, ar) if not o[:all]
266 |
267 | # Important, return `ar` from block.
268 | ar
269 | end # o[:objs].each
270 |
271 | out = per_obj.flatten(1)
272 | ##p "out.size", out.size
273 |
274 | # Chop off duplicates.
275 | out.uniq!
276 |
277 | # DO NOT sort by default. If required for visual listing, that's caller's responsibility!
278 | #out.sort!
279 |
280 | out
281 | end
282 |
283 | # Used in do_history.
284 | # Get prefix of the last "subject.ri args" command.
285 | # Return everything before " args" or nil if command didn't have arguments.
286 | def self.get_ri_arg_prefix(cmd)
287 | if (mat = cmd.match /\A(\s*.+?\.ri)\s+\S/)
288 | mat[1]
289 | end
290 | end
291 |
292 | # Return local library instance.
293 | def self.library
294 | @lib ||= Library.new
295 |
296 | # Update sensitive attrs on every call.
297 | @lib.frontend = ::ORI.conf.frontend
298 | @lib.shell_escape = ::ORI.conf.shell_escape
299 |
300 | @lib
301 | end
302 |
303 | # Show content in a configured pager.
304 | #
305 | # pager do |f|
306 | # f.puts "Hello, world!"
307 | # end
308 | def self.pager(&block)
309 | IO.popen(::ORI.conf.pager, "w", &block)
310 | end
311 |
312 | # Do main job.
313 | def self.ri(obj, *args)
314 | # Most of the time return nil, for list modes return number of items. Could be useful. Don't return `false` on error, that's confusing.
315 | out = nil
316 |
317 | begin
318 | # Build request.
319 | req = ::ORI::Request.parse(*args)
320 | raise UserError, "Bad request: #{req.message}" if req.error?
321 | ##IrbHacks.break req
322 |
323 | # List request.
324 | #
325 | # Klass.ri //
326 | if req.list?
327 | begin
328 | req.glm_options[:objs].unshift(obj)
329 | list_methods = get_list_methods(req.glm_options).sort
330 | rescue ArgumentError => e
331 | raise UserError, "Bad request: #{e.message}"
332 | end
333 | raise UserError, "No methods found" if list_methods.size < 1
334 |
335 | # Display.
336 | pager do |f|
337 | f.puts list_methods.map {|r| r.format(:color => ::ORI.conf.color)}
338 | end
339 |
340 | out = list_methods.size
341 | raise Break
342 | end # if req.list?
343 |
344 | # Class or method request. Particular ri article should be displayed.
345 | #
346 | # Klass.ri
347 | # Klass.ri :meth
348 | mam_topics = if req.self?
349 | [[Tools.get_module_name(obj.is_a?(Module) ? obj : obj.class)]]
350 | elsif req.method?
351 | begin
352 | req.glm_options[:objs].unshift(obj)
353 | list_methods = get_list_methods(req.glm_options)
354 | rescue ArgumentError => e
355 | raise UserError, "Bad request: #{e.message}"
356 | end
357 | raise UserError, "No methods found" if list_methods.size < 1
358 |
359 | # Collect topics.
360 | # NOTE: `uniq` is important. Take `Module#public` as an example.
361 | list_methods.map {|r| r.ri_topics}.flatten(1).uniq
362 | else
363 | raise "Unrecognized request kind #{req.kind.inspect}, SE"
364 | end # mam_topics =
365 |
366 | # Lookup topics. Display progress -- 1 character per lookup.
367 | print colorize([:message, :action], "Looking up topics [", [:reset], mam_topics.map {|ar| colorize_mam(ar)}.join(", "), [:message, :action], "] ", [:reset])
368 |
369 | found = []
370 | mam_topics.each do |mam|
371 | topic = mam.join
372 | content = library.lookup(topic)
373 | if content
374 | print "o"
375 | found << {
376 | :topic => colorize_mam(mam),
377 | :content => content,
378 | }
379 | else
380 | print "."
381 | end
382 | end
383 | puts
384 |
385 | raise UserError, "No articles found" if found.size < 1
386 |
387 | # Decide which article to show.
388 | content = if found.size == 1
389 | found.first[:content]
390 | else
391 | items = found.map {|h| ["#{h[:topic]} (#{h[:content].size}b)", h[:content]]}
392 | choice(items, {
393 | :colorize_labels => false,
394 | :title => "More than 1 article found",
395 | :on_skip => items.first[1],
396 | })
397 | end
398 |
399 | # Handle abort.
400 | raise Break if not content
401 |
402 | # Display.
403 | pager do |f|
404 | f.puts content
405 | end
406 | rescue UserError => e
407 | puts colorize([:message, :error], e.message, [:reset])
408 |
409 | out = nil
410 | rescue Break
411 | end
412 |
413 | out
414 | end
415 | end # Internals
416 | end # ORI
417 |
--------------------------------------------------------------------------------