├── .gitignore
├── CHANGELOG.md
├── MIT-LICENSE.txt
├── README.md
├── Rakefile
├── code.gemspec
└── lib
├── code.rb
└── code
└── version.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | Gemfile.lock
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## CHANGELOG
2 |
3 | ### 0.9.3
4 |
5 | * Loosen dependencies to Ruby and method_source
6 |
7 | ### 0.9.2
8 |
9 | * Update method_source dependency
10 |
11 | ### 0.9.1
12 |
13 | * Show GitHub-URLs for C-Sources
14 |
15 | ### 0.9.0
16 |
17 | * Initial release
18 |
19 |
--------------------------------------------------------------------------------
/MIT-LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, 2017 Jan Lelis, https://janlelis.com
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # code [![[version]](https://badge.fury.io/rb/code.svg)](https://badge.fury.io/rb/code)
2 |
3 | Shows a method's code with syntax highlighting. Tries to find a Ruby definition of the method first, then falls back to the C version (if the **core_docs** gem is available).
4 |
5 | ## Setup
6 |
7 | ```
8 | gem install code core_docs
9 | ```
10 |
11 |
12 | ## Usage
13 |
14 | ```ruby
15 | >> Code.for :require
16 | # in /home/jan/.rvm/rubies/ruby-2.2.1/lib/ruby/site_ruby/2.2.0/rubygems/core_ext/kernel_require.rb:38
17 | ##
18 | # When RubyGems is required, Kernel#require is replaced with our own which
19 | # is capable of loading gems on demand.
20 | #
21 | # When you call require 'x', this is what happens:
22 | # * If the file can be loaded from the existing Ruby loadpath, it
23 | # is.
24 | # * Otherwise, installed gems are searched for a file that matches.
25 | # If it's found in gem 'y', that gem is activated (added to the
26 | # loadpath).
27 | #
28 | # The normal require functionality of returning false if
29 | # that file has already been loaded is preserved.
30 | def require path
31 | RUBYGEMS_ACTIVATION_MONITOR.enter
32 |
33 | path = path.to_path if path.respond_to? :to_path
34 |
35 | spec = Gem.find_unresolved_default_spec(path)
36 | if spec
37 | Gem.remove_unresolved_default_spec(spec)
38 | gem(spec.name)
39 | end
40 |
41 | # If there are no unresolved deps, then we can use just try
42 | # normal require handle loading a gem from the rescue below.
43 |
44 | if Gem::Specification.unresolved_deps.empty? then
45 | RUBYGEMS_ACTIVATION_MONITOR.exit
46 | return gem_original_require(path)
47 | end
48 |
49 | # If +path+ is for a gem that has already been loaded, don't
50 | # bother trying to find it in an unresolved gem, just go straight
51 | # to normal require.
52 | #--
53 | # TODO request access to the C implementation of this to speed up RubyGems
54 |
55 | spec = Gem::Specification.stubs.find { |s|
56 | s.activated? and s.contains_requirable_file? path
57 | }
58 |
59 | begin
60 | RUBYGEMS_ACTIVATION_MONITOR.exit
61 | return gem_original_require(spec.to_fullpath(path) || path)
62 | end if spec
63 |
64 | # Attempt to find +path+ in any unresolved gems...
65 |
66 | found_specs = Gem::Specification.find_in_unresolved path
67 |
68 | # If there are no directly unresolved gems, then try and find +path+
69 | # in any gems that are available via the currently unresolved gems.
70 | # For example, given:
71 | #
72 | # a => b => c => d
73 | #
74 | # If a and b are currently active with c being unresolved and d.rb is
75 | # requested, then find_in_unresolved_tree will find d.rb in d because
76 | # it's a dependency of c.
77 | #
78 | if found_specs.empty? then
79 | found_specs = Gem::Specification.find_in_unresolved_tree path
80 |
81 | found_specs.each do |found_spec|
82 | found_spec.activate
83 | end
84 |
85 | # We found +path+ directly in an unresolved gem. Now we figure out, of
86 | # the possible found specs, which one we should activate.
87 | else
88 |
89 | # Check that all the found specs are just different
90 | # versions of the same gem
91 | names = found_specs.map(&:name).uniq
92 |
93 | if names.size > 1 then
94 | RUBYGEMS_ACTIVATION_MONITOR.exit
95 | raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}"
96 | end
97 |
98 | # Ok, now find a gem that has no conflicts, starting
99 | # at the highest version.
100 | valid = found_specs.select { |s| s.conflicts.empty? }.last
101 |
102 | unless valid then
103 | le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate"
104 | le.name = names.first
105 | RUBYGEMS_ACTIVATION_MONITOR.exit
106 | raise le
107 | end
108 |
109 | valid.activate
110 | end
111 |
112 | RUBYGEMS_ACTIVATION_MONITOR.exit
113 | return gem_original_require(path)
114 | rescue LoadError => load_error
115 | RUBYGEMS_ACTIVATION_MONITOR.enter
116 |
117 | if load_error.message.start_with?("Could not find") or
118 | (load_error.message.end_with?(path) and Gem.try_activate(path)) then
119 | RUBYGEMS_ACTIVATION_MONITOR.exit
120 | return gem_original_require(path)
121 | else
122 | RUBYGEMS_ACTIVATION_MONITOR.exit
123 | end
124 |
125 | raise load_error
126 | end
127 | ```
128 |
129 | ```c
130 | >> Code.for File, :open #=> nil
131 | // in io.c:6219
132 | // call-seq:
133 | // IO.open(fd, mode="r" [, opt]) -> io
134 | // IO.open(fd, mode="r" [, opt]) { |io| block } -> obj
135 | //
136 | // With no associated block, IO.open
is a synonym for IO.new. If
137 | // the optional code block is given, it will be passed +io+ as an argument,
138 | // and the IO object will automatically be closed when the block terminates.
139 | // In this instance, IO.open returns the value of the block.
140 | //
141 | // See IO.new for a description of the +fd+, +mode+ and +opt+ parameters.
142 | static VALUE
143 | rb_io_s_open(int argc, VALUE *argv, VALUE klass)
144 | {
145 | VALUE io = rb_class_new_instance(argc, argv, klass);
146 |
147 | if (rb_block_given_p()) {
148 | return rb_ensure(rb_yield, io, io_close, io);
149 | }
150 |
151 | return io;
152 | }
153 |
154 | ```
155 |
156 | ## Goal
157 |
158 | Be as powerful as pry's source browsing: https://github.com/pry/pry/wiki/Source-browsing
159 |
160 |
161 | ## MIT License
162 |
163 | Copyright (C) 2015, 2017 Jan Lelis . Released under the MIT license.
164 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # # #
2 | # Get gemspec info
3 |
4 | gemspec_file = Dir['*.gemspec'].first
5 | gemspec = eval File.read(gemspec_file), binding, gemspec_file
6 | info = "#{gemspec.name} | #{gemspec.version} | " \
7 | "#{gemspec.runtime_dependencies.size} dependencies | " \
8 | "#{gemspec.files.size} files"
9 |
10 |
11 | # # #
12 | # Gem build and install task
13 |
14 | desc info
15 | task :gem do
16 | puts info + "\n\n"
17 | print " "; sh "gem build #{gemspec_file}"
18 | FileUtils.mkdir_p 'pkg'
19 | FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg'
20 | puts; sh %{gem install --no-document pkg/#{gemspec.name}-#{gemspec.version}.gem}
21 | end
22 |
23 |
24 | # # #
25 | # Start an IRB session with the gem loaded
26 |
27 | desc "#{gemspec.name} | IRB"
28 | task :irb do
29 | sh "irb -I ./lib -r #{gemspec.name.gsub '-','/'}"
30 | end
31 |
--------------------------------------------------------------------------------
/code.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 |
3 | require File.dirname(__FILE__) + "/lib/code/version"
4 |
5 | Gem::Specification.new do |gem|
6 | gem.name = "code"
7 | gem.version = Code::VERSION
8 | gem.summary = "Displays a method's code."
9 | gem.description = "Displays a method's code (from source or docs). Supports native C source when core_docs gem is available"
10 | gem.authors = ["Jan Lelis"]
11 | gem.email = ["hi@ruby.consulting"]
12 | gem.homepage = "https://github.com/janlelis/code"
13 | gem.license = "MIT"
14 |
15 | gem.files = Dir['{**/}{.*,*}'].select{ |path| File.file?(path) && path !~ /^pkg/ }
16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18 | gem.require_paths = ['lib']
19 |
20 | gem.required_ruby_version = ">= 2.0", "< 4.0"
21 | gem.add_dependency "method_source", ">= 0.9", "< 2.0"
22 | gem.add_dependency "coderay", "~> 1.1"
23 | end
24 |
--------------------------------------------------------------------------------
/lib/code.rb:
--------------------------------------------------------------------------------
1 | require_relative "code/version"
2 |
3 | require 'method_source'
4 | require 'coderay'
5 |
6 | begin
7 | require 'core_docs'
8 | rescue LoadError
9 | end
10 |
11 |
12 | module Code
13 | class NotFound < StandardError
14 | end
15 |
16 | # API for end user
17 | def self.for(object = self, method_name)
18 | m = object.method(method_name)
19 | begin
20 | from_ruby(m)
21 | rescue MethodSource::SourceNotFoundError
22 | from_docs(m)
23 | end
24 | rescue NameError, NotFound
25 | warn $!.message
26 | end
27 |
28 | # Syntax highlight code string
29 | def self.display(string, language = :ruby)
30 | puts CodeRay.scan(string, language).term
31 | end
32 |
33 | # Find Ruby definition of code
34 | def self.from_ruby(m)
35 | source = m.source || ""
36 | indent = source.match(/\A +/)
37 | source = source.gsub(/^#{indent}/,"")
38 | comment = m.comment && !m.comment.empty? ? "#{ m.comment }" : ""
39 | location = m.source_location ? "#\n# #{ m.source_location*':' }\n#\n" : ""
40 |
41 | display location + comment + source
42 | end
43 |
44 | # Find C definition of Code
45 | def self.from_docs(m)
46 | if RUBY_ENGINE != "ruby"
47 | raise Code::NotFound, "Method source not found for non-CRuby."
48 | elsif !defined?(CoreDocs)
49 | raise Code::NotFound, 'Method source not found. Might be possible with core_docs gem'
50 | elsif !(method_info = CoreDocs::MethodInfo.info_for(m))
51 | raise Code::NotFound, 'Method source not found.'
52 | else
53 | source = method_info.source
54 | location = "//\n// #{cruby_on_github(method_info.file, method_info.line)}\n//\n"
55 | comment = method_info.docstring ? method_info.docstring.gsub(/^/, '// ') + "\n" : ""
56 |
57 | display location + comment + source, :c
58 | end
59 | end
60 |
61 | def self.cruby_on_github(filename, line)
62 | "https://github.com/ruby/ruby/blob/ruby_#{RUBY_VERSION[0]}_#{RUBY_VERSION[2]}/#{filename}#L#{line}"
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/lib/code/version.rb:
--------------------------------------------------------------------------------
1 | module Code
2 | VERSION = "0.9.3".freeze
3 | end
4 |
5 |
6 |
--------------------------------------------------------------------------------