├── 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/, / [], :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 | --------------------------------------------------------------------------------