├── VERSION
├── Gemfile
├── .travis.yml
├── test
├── fixtures
│ ├── sample_module.rb
│ └── sample_class.rb
├── helper.rb
└── test_fast_method_source
│ ├── test_fast_method_source_comment_for.rb
│ └── test_fast_method_source_source_for.rb
├── ext
└── fast_method_source
│ ├── extconf.rb
│ ├── fast_method_source.c
│ └── node.h
├── .gitignore
├── lib
├── fast_method_source
│ └── core_ext.rb
└── fast_method_source.rb
├── benchmarks
├── comment_and_source_for.rb
├── fms_vs_ms_comment_for.rb
├── fms_vs_ms_source_for.rb
└── bench_helper.rb
├── Rakefile
├── LICENCE.txt
├── CHANGELOG.md
├── fast_method_source.gemspec
├── docs
└── API.md
└── README.md
/VERSION:
--------------------------------------------------------------------------------
1 | 0.4.0
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gemspec
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.1
4 | - 2.2
5 | - ruby-head
6 |
--------------------------------------------------------------------------------
/test/fixtures/sample_module.rb:
--------------------------------------------------------------------------------
1 | module SampleModule
2 | # Sample method
3 | def sample_method
4 | :sample_method
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/test/helper.rb:
--------------------------------------------------------------------------------
1 | require 'minitest/autorun'
2 | require 'fast_method_source'
3 |
4 | require_relative 'fixtures/sample_class'
5 | require_relative 'fixtures/sample_module'
6 |
--------------------------------------------------------------------------------
/ext/fast_method_source/extconf.rb:
--------------------------------------------------------------------------------
1 | require 'mkmf'
2 |
3 | $CFLAGS << ' -std=c99 -Wno-declaration-after-statement'
4 |
5 | have_func('rb_sym2str', 'ruby.h')
6 |
7 | create_makefile('fast_method_source/fast_method_source')
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | lib/bundler/man
11 | pkg
12 | rdoc
13 | spec/reports
14 | test/tmp
15 | test/version_tmp
16 | tmp
17 | *.bundle
18 | *.so
19 | *.o
20 | *.a
21 | mkmf.log
22 | *~
23 | .#*
24 | doc/
25 | Makefile
26 |
--------------------------------------------------------------------------------
/lib/fast_method_source/core_ext.rb:
--------------------------------------------------------------------------------
1 | class Proc
2 | unless self.method_defined?(:name)
3 | protected
4 |
5 | def name
6 | self.inspect
7 | end
8 | end
9 | end
10 |
11 | [Method, UnboundMethod, Proc].each do |klass|
12 | klass.include FastMethodSource::MethodExtensions
13 |
14 | klass.class_eval do
15 | def comment_and_source
16 | self.comment + self.source
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/benchmarks/comment_and_source_for.rb:
--------------------------------------------------------------------------------
1 | require_relative 'bench_helper'
2 |
3 | method_list = SystemNavigation.new.all_rb_methods.select do |method|
4 | begin
5 | FastMethodSource.comment_and_source_for(method)
6 | rescue FastMethodSource::SourceNotFoundError, IOError
7 | end
8 | end
9 |
10 | puts "Sample methods: #{method_list.count}"
11 |
12 | Benchmark.bm do |bm|
13 | bm.report('FastMethodSource#comment_and_source_for') do
14 | method_list.each do |method|
15 | FastMethodSource.comment_and_source_for(method)
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/benchmarks/fms_vs_ms_comment_for.rb:
--------------------------------------------------------------------------------
1 | require_relative 'bench_helper'
2 |
3 | method_list = SystemNavigation.new.all_rb_methods.select do |method|
4 | begin
5 | FastMethodSource.comment_for(method)
6 | rescue FastMethodSource::SourceNotFoundError, IOError
7 | end
8 | end
9 |
10 | puts "Sample methods: #{method_list.count}"
11 |
12 | Benchmark.bmbm do |bm|
13 | bm.report('FastMethodSource#comment') do
14 | method_list.each do |method|
15 | FastMethodSource.comment_for(method)
16 | end
17 | end
18 |
19 | bm.report('MethodSource#comment') do
20 | method_list.each do |method|
21 | method.comment
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake/testtask'
2 | require 'rake/extensiontask'
3 | require 'rake/clean'
4 |
5 | Rake::ExtensionTask.new do |ext|
6 | ext.name = 'fast_method_source'
7 | ext.ext_dir = 'ext/fast_method_source'
8 | ext.lib_dir = 'lib/fast_method_source'
9 | end
10 |
11 | task :cleanup do
12 | system 'rm -f lib/fast_method_source/fast_method_source.so'
13 | end
14 |
15 | task :release do
16 | puts "So you want to release this thing, right?"
17 | puts "Here are your instructions:"
18 | puts " 0. Make sure this guide is up to date"
19 | puts " 1. Update the benchmark in README.md"
20 | puts " 2. Push to RubyGems!"
21 | end
22 |
23 | task :test => [:cleanup, :clobber, :compile] do
24 | Rake::TestTask.new do |t|
25 | t.test_files = Dir.glob('test/**/test_*.rb')
26 | end
27 | end
28 |
29 | task default: :test
30 |
--------------------------------------------------------------------------------
/LICENCE.txt:
--------------------------------------------------------------------------------
1 | Copyright (C) 2015 Kyrylo Silin
2 |
3 | This software is provided 'as-is', without any express or implied
4 | warranty. In no event will the authors be held liable for any damages
5 | arising from the use of this software.
6 |
7 | Permission is granted to anyone to use this software for any purpose,
8 | including commercial applications, and to alter it and redistribute it
9 | freely, subject to the following restrictions:
10 |
11 | 1. The origin of this software must not be misrepresented; you must not
12 | claim that you wrote the original software. If you use this software
13 | in a product, an acknowledgment in the product documentation would be
14 | appreciated but is not required.
15 |
16 | 2. Altered source versions must be plainly marked as such, and must not be
17 | misrepresented as being the original software.
18 |
19 | 3. This notice may not be removed or altered from any source distribution.
20 |
--------------------------------------------------------------------------------
/benchmarks/fms_vs_ms_source_for.rb:
--------------------------------------------------------------------------------
1 | require_relative 'bench_helper'
2 |
3 | method_list = SystemNavigation.new.all_rb_methods.select do |method|
4 | # Make sure that both libraries can parse all given methods. Sometimes
5 | # method_source raises an error when Fast Method Soruce doesn't.
6 | begin
7 | FastMethodSource.source_for(method)
8 | rescue FastMethodSource::SourceNotFoundError, IOError
9 | end
10 |
11 | begin
12 | method.source
13 | rescue MethodSource::SourceNotFoundError, Errno::ENOTDIR, NoMethodError
14 | end
15 | end
16 |
17 | puts "Sample methods: #{method_list.count}"
18 |
19 | Benchmark.bmbm do |bm|
20 | bm.report('FastMethodSource#source') do
21 | method_list.each do |method|
22 | FastMethodSource.source_for(method)
23 | end
24 | end
25 |
26 | bm.report('MethodSource#source') do
27 | method_list.each do |method|
28 | method.source
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Fast Method Source changelog
2 | ============================
3 |
4 | ### v0.4.0 (June 18, 2015)
5 |
6 | * _Significantly_ improve speed of both `#soruce` and `#comment`. The trade-off
7 | is that you must indent methods properly.
8 | * Decrease memeory consumption
9 |
10 | ### v0.3.1 (June 11, 2015)
11 |
12 | * Considerable speed improvements
13 |
14 | ### v0.3.0 (June 11, 2015)
15 |
16 | * _Significantly_ reduced memory consumption (`#comment_and_source` for 19K
17 | methods uses about 100-150 MB of RES RAM)
18 | * _Significantly_ decreased time of calculations
19 |
20 | ### v0.2.0 (June 11, 2015)
21 |
22 | * Significantly reduced memory consumption
23 | * The time of calculations has increased by 6%
24 | * Fixed memory not being freed on exceptions
25 | * Fixed exit code value for shells [→](https://github.com/kyrylo/fast_method_source/pull/2)
26 |
27 | ### v0.1.1 (June 9, 2015)
28 |
29 | * Fixed segfault on any C method query
30 |
31 | ### v0.1.0 (June 9, 2015)
32 |
33 | * Initial release
34 |
--------------------------------------------------------------------------------
/lib/fast_method_source.rb:
--------------------------------------------------------------------------------
1 | require_relative 'fast_method_source/fast_method_source'
2 |
3 | module FastMethodSource
4 | # The VERSION file must be in the root directory of the library.
5 | VERSION_FILE = File.expand_path('../../VERSION', __FILE__)
6 |
7 | VERSION = File.exist?(VERSION_FILE) ?
8 | File.read(VERSION_FILE).chomp : '(could not find VERSION file)'
9 |
10 | # The root path of Pry Theme source code.
11 | ROOT = File.expand_path(File.dirname(__FILE__))
12 |
13 | class Method
14 | include MethodExtensions
15 |
16 | def initialize(method)
17 | @method = method
18 | end
19 |
20 | def source_location
21 | @method.source_location
22 | end
23 |
24 | def name
25 | if @method.respond_to?(:name)
26 | @method.name
27 | else
28 | @method.inspect
29 | end
30 | end
31 | end
32 |
33 | def self.source_for(method)
34 | FastMethodSource::Method.new(method).source
35 | end
36 |
37 | def self.comment_for(method)
38 | FastMethodSource::Method.new(method).comment
39 | end
40 |
41 | def self.comment_and_source_for(method)
42 | self.comment_for(method) + self.source_for(method)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/fast_method_source.gemspec:
--------------------------------------------------------------------------------
1 | Gem::Specification.new do |s|
2 | s.name = 'fast_method_source'
3 | s.version = File.read('VERSION')
4 | s.date = Time.now.strftime('%Y-%m-%d')
5 | s.summary = 'Retrieves the source code for a method (and its comment)'
6 | s.description = 'Retrieves the source code for a method (and its comment) and does it faster than any other existing solution'
7 | s.author = 'Kyrylo Silin'
8 | s.email = 'silin@kyrylo.org'
9 | s.homepage = 'https://github.com/kyrylo/fast_method_source'
10 | s.licenses = 'Zlib'
11 |
12 | s.require_path = 'lib'
13 | s.files = %w[
14 | ext/fast_method_source/extconf.rb
15 | ext/fast_method_source/fast_method_source.c
16 | ext/fast_method_source/node.h
17 | lib/fast_method_source.rb
18 | lib/fast_method_source/core_ext.rb
19 | VERSION
20 | README.md
21 | CHANGELOG.md
22 | LICENCE.txt
23 | ]
24 | s.extensions = ['ext/fast_method_source/extconf.rb']
25 | s.platform = Gem::Platform::RUBY
26 |
27 | s.add_development_dependency 'bundler', '~> 1.0'
28 | s.add_development_dependency 'rake', '~> 10.4'
29 | s.add_development_dependency 'rake-compiler', '~> 0.9'
30 | s.add_development_dependency 'minitest', '~> 5.7'
31 | end
32 |
--------------------------------------------------------------------------------
/benchmarks/bench_helper.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | require 'benchmark'
3 | require 'rbconfig'
4 |
5 | def require_dep(dep_name)
6 | require dep_name
7 | rescue LoadError => e
8 | name = e.message.scan(/(?<=-- ).+/).first
9 | raise e unless name
10 | name.gsub!('/', '-')
11 | puts "'#{name}' is not installed. To fix: gem install #{name}"
12 | exit
13 | end
14 |
15 | module Kernel
16 | def suppress_warnings
17 | original_verbosity = $VERBOSE
18 | $VERBOSE = nil
19 | result = yield
20 | $VERBOSE = original_verbosity
21 | return result
22 | end
23 | end
24 |
25 | require_dep 'require_all'
26 | require_dep 'method_source'
27 | require_dep 'system_navigation'
28 | require_dep 'sys/cpu'
29 |
30 | stdlib_files = File.join(RbConfig::CONFIG['rubylibdir'], "**/*.rb")
31 | IGNORE_LIST = [
32 | /tkmacpkg.rb/,
33 | /macpkg.rb/,
34 | /debug.rb/,
35 | /multi-tk.rb/,
36 | /tk.rb/,
37 | /tkwinpkg.rb/,
38 | /winpkg.rb/,
39 | /ICONS.rb/i,
40 | /tkextlib/,
41 | /profile.rb/,
42 | /formatter_test_case.rb/,
43 | /test_case.rb/,
44 | /xmlparser/,
45 | /xmlscanner/,
46 | /cgi_runner.rb/,
47 | /multi-irb.rb/,
48 | /subirb.rb/,
49 | /ws-for-case-2.rb/,
50 | /test_utilities.rb/,
51 | /psych\/parser.rb/,
52 | /rake\/contrib\/sys.rb/,
53 | /rake\/gempackagetask.rb/,
54 | /rake\/rdoctask.rb/,
55 | /ruby182_test_unit_fix.rb/,
56 | /rake\/runtest.rb/,
57 | /tcl/,
58 | /minitest\/spec.rb/,
59 | /rubygems\/exceptions.rb/,
60 | /\/irb\//,
61 | /\/rake\//,
62 | /package\/old.rb/,
63 | /rubygems\/rdoc.rb/
64 | ]
65 |
66 | files_to_require = Dir.glob(stdlib_files).reject do |p|
67 | IGNORE_LIST.any? { |pattern| pattern =~ p }
68 | end
69 |
70 | if $DEBUG
71 | print "Ignoring these files: "
72 | p Dir.glob(stdlib_files) - files_to_require
73 |
74 | puts "Requiring the rest of stdlib..."
75 |
76 | require_all files_to_require
77 |
78 | puts "Requiring fast_method_source from source..."
79 | require_relative '../lib/fast_method_source'
80 | else
81 | suppress_warnings { require_all files_to_require }
82 | end
83 |
84 | puts "Processor: #{Sys::CPU.processors.first.model_name}"
85 | puts "Platform: #{RUBY_ENGINE} #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]"
86 | puts "Counting the number of sample methods..."
87 |
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | API
2 | ===
3 |
4 | Overview
5 | --
6 |
7 | The library provides the following methods:
8 |
9 | * `#comment`
10 | * `#source`
11 | * `#comment_and_source`
12 |
13 | There are two ways to use Fast Method Source. One way is to monkey-patch
14 | relevant core Ruby classes and use the methods directly.
15 |
16 | ```ruby
17 | require 'fast_method_source'
18 | require 'fast_method_source/core_ext' # <= monkey-patches
19 |
20 | # Monkey-patch of UnboundMethod
21 | Set.instance_method(:merge).source #=> " def merge(enum)\n..."
22 |
23 | # Monkey-patch of Method
24 | Set.method(:[]).source #=> " def self.[](*ary)\n..."
25 |
26 | # Monkey-patch of Proc (or lambda)
27 | myproc = proc { |arg|
28 | arg + 1
29 | }
30 | myproc.source #=> "myproc = proc { |arg|\n..."
31 | ```
32 |
33 | The other way is by using these methods defined on the library's class directly.
34 |
35 | ```ruby
36 | require 'fast_method_source'
37 |
38 | # With UnboundMethod
39 | FastMethodSource.source_for(Set.instance_method(:merge)) #=> " def merge(enum)\n..."
40 |
41 | # With Method
42 | FastMethodSource.source_for(Set.method(:[])) #=> " def self.[](*ary)\n..."
43 |
44 | # With Proc (or lambda)
45 | myproc = proc { |arg|
46 | arg + 1
47 | }
48 | FastMethodSource.source_for(myproc) #=> "myproc = proc { |arg|\n..."
49 | ```
50 |
51 | Method Information
52 | --
53 |
54 | #### FastMethodSource#source_for(method)
55 |
56 | Returns the source code of the given _method_ as a String.
57 | Raises `FastMethodSource::SourceNotFoundError` if:
58 |
59 | * _method_ is defined outside of a file (for example, in a REPL)
60 | * _method_ doesn't have a source location (typically C methods)
61 |
62 | Raises `IOError` if:
63 |
64 | * the file location of _method_ is inaccessible
65 |
66 | ```ruby
67 | FastMethodSource.source_for(Set.instance_method(:merge))
68 | #=> " def merge(enum)\n..."
69 | FastMethodSource.source_for(Array.instance_method(:pop))
70 | #=> FastMethodSource::SourceNotFoundError
71 | ```
72 |
73 | #### FastMethodSource#comment_for(method)
74 |
75 | Returns the comment of the given _method_ as a String. The rest is identical to
76 | `FastMethodSource#source_for(method)`.
77 |
78 | #### FastMethodSource#comment_and_source_for(method)
79 |
80 | Returns the comment and the source code of the given _method_ as a String (the
81 | order is the same as the method's name). The rest is identical to
82 | `FastMethodSource#source_for(method)`.
83 |
--------------------------------------------------------------------------------
/test/test_fast_method_source/test_fast_method_source_comment_for.rb:
--------------------------------------------------------------------------------
1 | require_relative '../helper'
2 |
3 | class TestFastMethodSource < Minitest::Test
4 | def test_comment_for_class
5 | method = SampleClass.instance_method(:sample_method)
6 | assert_match(/# Sample method/, FastMethodSource.comment_for(method))
7 | end
8 |
9 | def test_comment_for_module
10 | method = SampleModule.instance_method(:sample_method)
11 | assert_match(/# Sample method/, FastMethodSource.comment_for(method))
12 | end
13 |
14 | def test_comment_for_proc
15 | # Heavy rain
16 | method = proc { |*args|
17 | args.first + args.last
18 | }
19 |
20 | assert_match(/# Heavy rain/, FastMethodSource.comment_for(method))
21 | end
22 |
23 | def test_comment_for_proc_instance
24 | # Heavy rain
25 | method = Proc.new { |*args|
26 | args.first + args.last
27 | }
28 |
29 | assert_match(/# Heavy rain/, FastMethodSource.comment_for(method))
30 | end
31 |
32 | def test_comment_for_lambda
33 | # Life is strange
34 | method = lambda { |*args|
35 | args.first + args.last
36 | }
37 |
38 | assert_match(/# Life is strange/, FastMethodSource.comment_for(method))
39 | end
40 |
41 | def test_comment_for_lambda_arrow
42 | # Life is strange
43 | method = -> *args {
44 | args.first + args.last
45 | }
46 |
47 | assert_match(/# Life is strange/, FastMethodSource.comment_for(method))
48 | end
49 |
50 | def test_comment_for_set_merge
51 | require 'set'
52 |
53 | method = Set.instance_method(:merge)
54 | expected = %r{
55 | \s*
56 | # Merges the elements of the given enumerable object to the set and\n
57 | \s*
58 | # returns self.\n
59 | }x
60 | assert_match(expected, FastMethodSource.comment_for(method))
61 | end
62 |
63 | def test_comment_for_padding
64 | # Life is strange
65 |
66 |
67 | method = -> *args {
68 | args.first + args.last
69 | }
70 |
71 | assert_equal "", FastMethodSource.comment_for(method)
72 | end
73 |
74 | def test_comment_for_no_comment
75 | method = proc {
76 | args.first + args.last
77 | }
78 |
79 | assert_equal '', FastMethodSource.comment_for(method)
80 | end
81 |
82 | def test_comment_for_kernel_require
83 | method = SampleClass.instance_method(:kernel_require)
84 | assert_match(/##\n # When RubyGems is required, Kernel#require is replaced/,
85 | FastMethodSource.comment_for(method))
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/test/fixtures/sample_class.rb:
--------------------------------------------------------------------------------
1 | class SampleClass
2 | attr_accessor :accessor
3 |
4 | attr_reader :reader1,
5 | :reader2
6 |
7 | # Sample method
8 | def sample_method
9 | :sample_method
10 | end
11 |
12 | def one_line_method; 1; end
13 |
14 | ##
15 | # When RubyGems is required, Kernel#require is replaced with our own which
16 | # is capable of loading gems on demand.
17 | #
18 | # When you call require 'x', this is what happens:
19 | # * If the file can be loaded from the existing Ruby loadpath, it
20 | # is.
21 | # * Otherwise, installed gems are searched for a file that matches.
22 | # If it's found in gem 'y', that gem is activated (added to the
23 | # loadpath).
24 | #
25 | # The normal require functionality of returning false if
26 | # that file has already been loaded is preserved.
27 |
28 | def kernel_require path
29 | RUBYGEMS_ACTIVATION_MONITOR.enter
30 |
31 | path = path.to_path if path.respond_to? :to_path
32 |
33 | spec = Gem.find_unresolved_default_spec(path)
34 | if spec
35 | Gem.remove_unresolved_default_spec(spec)
36 | gem(spec.name)
37 | end
38 |
39 | # If there are no unresolved deps, then we can use just try
40 | # normal require handle loading a gem from the rescue below.
41 |
42 | if Gem::Specification.unresolved_deps.empty? then
43 | RUBYGEMS_ACTIVATION_MONITOR.exit
44 | return gem_original_require(path)
45 | end
46 |
47 | # If +path+ is for a gem that has already been loaded, don't
48 | # bother trying to find it in an unresolved gem, just go straight
49 | # to normal require.
50 | #--
51 | # TODO request access to the C implementation of this to speed up RubyGems
52 |
53 | spec = Gem::Specification.stubs.find { |s|
54 | s.activated? and s.contains_requirable_file? path
55 | }
56 |
57 | begin
58 | RUBYGEMS_ACTIVATION_MONITOR.exit
59 | return gem_original_require(spec.to_fullpath(path) || path)
60 | end if spec
61 |
62 | # Attempt to find +path+ in any unresolved gems...
63 |
64 | found_specs = Gem::Specification.find_in_unresolved path
65 |
66 | # If there are no directly unresolved gems, then try and find +path+
67 | # in any gems that are available via the currently unresolved gems.
68 | # For example, given:
69 | #
70 | # a => b => c => d
71 | #
72 | # If a and b are currently active with c being unresolved and d.rb is
73 | # requested, then find_in_unresolved_tree will find d.rb in d because
74 | # it's a dependency of c.
75 | #
76 | if found_specs.empty? then
77 | found_specs = Gem::Specification.find_in_unresolved_tree path
78 |
79 | found_specs.each do |found_spec|
80 | found_spec.activate
81 | end
82 |
83 | # We found +path+ directly in an unresolved gem. Now we figure out, of
84 | # the possible found specs, which one we should activate.
85 | else
86 |
87 | # Check that all the found specs are just different
88 | # versions of the same gem
89 | names = found_specs.map(&:name).uniq
90 |
91 | if names.size > 1 then
92 | RUBYGEMS_ACTIVATION_MONITOR.exit
93 | raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}"
94 | end
95 |
96 | # Ok, now find a gem that has no conflicts, starting
97 | # at the highest version.
98 | valid = found_specs.select { |s| s.conflicts.empty? }.last
99 |
100 | unless valid then
101 | le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate"
102 | le.name = names.first
103 | RUBYGEMS_ACTIVATION_MONITOR.exit
104 | raise le
105 | end
106 |
107 | valid.activate
108 | end
109 |
110 | RUBYGEMS_ACTIVATION_MONITOR.exit
111 | return gem_original_require(path)
112 | rescue LoadError => load_error
113 | RUBYGEMS_ACTIVATION_MONITOR.enter
114 |
115 | if load_error.message.start_with?("Could not find") or
116 | (load_error.message.end_with?(path) and Gem.try_activate(path)) then
117 | RUBYGEMS_ACTIVATION_MONITOR.exit
118 | return gem_original_require(path)
119 | else
120 | RUBYGEMS_ACTIVATION_MONITOR.exit
121 | end
122 |
123 | raise load_error
124 | end
125 |
126 | def rdoc_escape string
127 | string.gsub(/["\\\t\n]/) do |special_character|
128 | case special_character
129 | when "\t"
130 | "\\t"
131 | when "\n"
132 | "\\n"
133 | else
134 | "\\#{special_character}"
135 | end
136 | end
137 | end
138 | end
139 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fast Method Source
2 |
3 | [](https://travis-ci.org/kyrylo/fast_method_source)
4 |
5 | * Repository: [https://github.com/kyrylo/fast_method_source/][repo]
6 | * API description: [https://github.com/kyrylo/fast_method_source/blob/master/docs/API.md][apimd]
7 |
8 | Description
9 | -----------
10 |
11 | Fast Method Source is a Ruby library for querying methods, procs and lambdas for
12 | their source code and comments.
13 |
14 | ```ruby
15 | require 'fast_method_source'
16 | require 'fast_method_source/core_ext' # Adds #source to UnboundMethod
17 | require 'set'
18 |
19 | puts Set.instance_method(:merge).source
20 | ```
21 |
22 | Output.
23 |
24 | ```
25 | def merge(enum)
26 | if enum.instance_of?(self.class)
27 | @hash.update(enum.instance_variable_get(:@hash))
28 | else
29 | do_with_enum(enum) { |o| add(o) }
30 | end
31 |
32 | self
33 | end
34 | ```
35 |
36 | Fast Method Source provides functionality similar to [method_source][ms].
37 | However, it is significantly faster. It can show the source for 19K methods in
38 | roughly a second, while method_source takes about 30 seconds. The benchmarks
39 | below show the difference expressed in numbers.
40 |
41 | #### #source
42 | ##### ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
43 |
44 | The speed boost of method_source after the rehearsal that can be observed in
45 | this benchmark comes from the fact that it uses memoization. Fast Method Source
46 | does not support it at the moment.
47 |
48 | ```
49 | Processor: Intel(R) Core(TM) i5-2410M CPU @ 2.30GHz
50 | Platform: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
51 | Counting the number of sample methods...
52 | Sample methods: 19280
53 | Rehearsal -----------------------------------------------------------
54 | FastMethodSource#source 0.760000 0.240000 1.000000 ( 1.104455)
55 | MethodSource#source 53.240000 0.250000 53.490000 ( 59.304424)
56 | ------------------------------------------------- total: 54.490000sec
57 |
58 | user system total real
59 | FastMethodSource#source 0.610000 0.190000 0.800000 ( 0.890971)
60 | MethodSource#source 51.770000 0.230000 52.000000 ( 57.640986)
61 | ```
62 |
63 | #### #comment
64 | ##### ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
65 |
66 | ```
67 | Processor: Intel(R) Core(TM) i5-2410M CPU @ 2.30GHz
68 | Platform: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
69 | Counting the number of sample methods...
70 | Sample methods: 19437
71 | Rehearsal ------------------------------------------------------------
72 | FastMethodSource#comment 0.180000 0.170000 0.350000 ( 0.395569)
73 | MethodSource#comment 19.860000 0.230000 20.090000 ( 36.373289)
74 | -------------------------------------------------- total: 20.440000sec
75 |
76 | user system total real
77 | FastMethodSource#comment 0.270000 0.190000 0.460000 ( 0.520414)
78 | MethodSource#comment 19.360000 0.020000 19.380000 ( 21.726528)
79 | ```
80 |
81 | #### #comment_and_source
82 | ##### ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
83 |
84 | This is a convenience method, and method_source doesn't have it.
85 |
86 | ```
87 | Processor: Intel(R) Core(TM) i5-2410M CPU @ 2.30GHz
88 | Platform: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
89 | Counting the number of sample methods...
90 | Sample methods: 19435
91 | user system total real
92 | FastMethodSource#comment_and_source_for 0.810000 0.420000 1.230000 ( 1.374435)
93 | ```
94 |
95 | API
96 | ---
97 |
98 | Read here: [https://github.com/kyrylo/fast_method_source/blob/master/docs/API.md][apimd]
99 |
100 | Installation
101 | ------------
102 |
103 | All you need is to install the gem.
104 |
105 | gem install fast_method_source
106 |
107 |
108 | Limitations
109 | -----------
110 |
111 | ### Rubies
112 |
113 | * CRuby 2.1.x
114 | * CRuby 2.2.x
115 | * CRuby 2.3.0dev and higher
116 |
117 | Rubies below 2.2.2 were not tested, so in theory if it quacks like Ruby 2, it
118 | may work.
119 |
120 | ### Operation Systems
121 |
122 | * GNU/Linux
123 | * Mac OS (hopefully)
124 |
125 | Licence
126 | -------
127 |
128 | The project uses Zlib License. See the LICENCE.txt file for more information.
129 |
130 | [repo]: https://github.com/kyrylo/fast_method_source/ "Home page"
131 | [ms]: https://github.com/banister/method_source
132 | [apimd]: https://github.com/kyrylo/fast_method_source/blob/master/docs/API.md
133 |
--------------------------------------------------------------------------------
/test/test_fast_method_source/test_fast_method_source_source_for.rb:
--------------------------------------------------------------------------------
1 | require_relative '../helper'
2 |
3 | class TestFastMethodSource < Minitest::Test
4 | def test_source_for_class
5 | method = SampleClass.instance_method(:sample_method)
6 | expected = " def sample_method\n :sample_method\n end\n"
7 | assert_equal expected, FastMethodSource.source_for(method)
8 | end
9 |
10 | def test_source_for_module
11 | method = SampleModule.instance_method(:sample_method)
12 | expected = " def sample_method\n :sample_method\n end\n"
13 | assert_equal expected, FastMethodSource.source_for(method)
14 | end
15 |
16 | def test_source_for_proc
17 | method = proc { |*args|
18 | args.first + args.last
19 | }
20 |
21 | expected = " method = proc { |*args|\n args.first + args.last\n }\n"
22 | assert_equal expected, FastMethodSource.source_for(method)
23 | end
24 |
25 | def test_source_for_proc_instance
26 | method = Proc.new { |*args|
27 | args.first + args.last
28 | }
29 |
30 | expected = " method = Proc.new { |*args|\n args.first + args.last\n }\n"
31 | assert_equal expected, FastMethodSource.source_for(method)
32 | end
33 |
34 | def test_source_for_lambda
35 | method = lambda { |*args|
36 | args.first + args.last
37 | }
38 |
39 | expected = " method = lambda { |*args|\n args.first + args.last\n }\n"
40 | assert_equal expected, FastMethodSource.source_for(method)
41 | end
42 |
43 | def test_source_for_lambda_arrow
44 | method = -> *args {
45 | args.first + args.last
46 | }
47 |
48 | expected = " method = -> *args {\n args.first + args.last\n }\n"
49 | assert_equal expected, FastMethodSource.source_for(method)
50 | end
51 |
52 | def test_source_for_rss_image_favicon
53 | require 'rss'
54 |
55 | method = RSS::ImageFaviconModel::ImageFavicon.instance_method(:dc_date_elements)
56 | expected = %r{klass.install_have_children_element\(name, DC_URI, \".+\n\s+full_name, full_plural_name\)}
57 | assert_match expected, FastMethodSource.source_for(method)
58 | end
59 |
60 | def test_source_for_erb_scan
61 | require 'erb'
62 |
63 | method = ERB::Compiler::SimpleScanner2.instance_method(:scan)
64 | expected = /def scan\n.+yield\(scanner.+end\n\s+end\n\z/m
65 | assert_match(expected, FastMethodSource.source_for(method))
66 | end
67 |
68 | def test_source_for_irb_change_workspace
69 | require 'irb'
70 |
71 | method = IRB::ExtendCommandBundle.instance_method(:irb_change_workspace)
72 | expected = /def \#{cmd_name}\(\*opts, &b\).+\n\s+end\n\z/m
73 | assert_match(expected, FastMethodSource.source_for(method))
74 | end
75 |
76 | def test_source_for_rss_time_w3cdtf
77 | require 'rss'
78 |
79 | method = Time.instance_method(:w3cdtf)
80 | expected = /def w3cdtf\n if usec.zero\?\n.+end\n\z/m
81 | assert_match(expected, FastMethodSource.source_for(method))
82 | end
83 |
84 | def test_source_for_kernel_require
85 | method = SampleClass.instance_method(:kernel_require)
86 | expected = /def kernel_require path\n RUBYGEMS_ACTIVATION_MONITOR.enter\n.+raise load_error\n\s+end\n\z/m
87 | assert_match(expected, FastMethodSource.source_for(method))
88 | end
89 |
90 | def test_source_for_irb_init_config
91 | require 'irb'
92 |
93 | method = IRB.singleton_class.instance_method(:init_config)
94 | expected = /def IRB.init_config\(ap_path\)\n # class instance variables\n.+@CONF\[:DEBUG_LEVEL\] = 0\n\s+end\n\z/m
95 | assert_match(expected, FastMethodSource.source_for(method))
96 | end
97 |
98 | def test_source_for_rss_def_classed_element
99 | require 'rss'
100 |
101 | method = RSS::Maker::Base.singleton_class.instance_method(:def_classed_element)
102 | expected = /def def_classed_element\(name, class_name=nil, attribute_name=nil\)\n.+attr_reader name\n\s+end\n\s+end\n\z/m
103 | assert_match(expected, FastMethodSource.source_for(method))
104 | end
105 |
106 | def test_source_for_rss_square_brackets
107 | require 'rss'
108 |
109 | method = REXML::Elements.instance_method(:[])
110 | expected = /def \[\]\( index, name=nil\)\n.+return nil\n.+end\n\z/m
111 | assert_match(expected, FastMethodSource.source_for(method))
112 | end
113 |
114 | def test_source_for_core_ext
115 | require 'fast_method_source/core_ext'
116 |
117 | method = proc { |*args|
118 | args.first + args.last
119 | }
120 |
121 | assert_raises(NameError) { Pry }
122 | assert_raises(NameError) { MethodSource }
123 |
124 | expected = " method = proc { |*args|\n args.first + args.last\n }\n"
125 | assert_equal expected, method.source
126 | end
127 |
128 | def test_source_for_mkmf_configuration
129 | require 'mkmf'
130 |
131 | method = MakeMakefile.instance_method(:configuration)
132 | expected = /def configuration\(srcdir\)\n.+mk\n\s+end\n\z/m
133 | assert_match(expected, FastMethodSource.source_for(method))
134 | end
135 |
136 | def test_source_for_rdoc_setup_parser
137 | require 'rdoc'
138 |
139 | method = RDoc::Markdown.instance_method(:setup_parser)
140 | expected = /def setup_parser\(str, debug=false\)\n.+setup_foreign_grammar\n\s+end\n\z/m
141 | assert_match(expected, FastMethodSource.source_for(method))
142 | end
143 |
144 | def test_source_for_rdoc_escape
145 | method = SampleClass.instance_method(:rdoc_escape)
146 | expected = /def rdoc_escape string\n.+end\n end\n end\n\z/m
147 | assert_match(expected, FastMethodSource.source_for(method))
148 | end
149 |
150 | def test_source_for_rss_to_feed
151 | require 'rss'
152 |
153 | method = RSS::Maker::DublinCoreModel::DublinCoreDatesBase.instance_method(:to_feed)
154 | expected = /def to_feed\(\*args\)\n.+end\n\s+end\n\z/m
155 | assert_match(expected, FastMethodSource.source_for(method))
156 | end
157 |
158 | def test_source_for_rdoc_find_attr_comment
159 | require 'rdoc'
160 |
161 | method = RDoc::Parser::C.instance_method(:find_attr_comment)
162 | expected = /def find_attr_comment var_name, attr_name, read.+RDoc::Comment.new comment, @top_level\n end\n\z/m
163 | assert_match(expected, FastMethodSource.source_for(method))
164 | end
165 |
166 | def test_source_for_fileutils_cd
167 | require 'fileutils'
168 |
169 | method = FileUtils::LowMethods.instance_method(:cd)
170 |
171 | # Reports wrong source_location (lineno 1981 instead of 1980).
172 | # https://github.com/ruby/ruby/blob/a9721a259665149b1b9ff0beabcf5f8dc0136120/lib/fileutils.rb#L1681
173 | expected = /:commands, :options, :have_option\?, :options_of, :collect_method\]\n\n/
174 | assert_match expected, FastMethodSource.source_for(method)
175 | end
176 |
177 | def test_source_for_process_utime
178 | method = Process::Tms.instance_method(:utime=)
179 |
180 | # Process::Tms.instance_method(:utime=).source_location
181 | # #=> ["", nil]
182 | assert_raises(FastMethodSource::SourceNotFoundError) {
183 | FastMethodSource.source_for(method)
184 | }
185 | end
186 |
187 | def test_source_for_etc_name
188 | method = Process::Tms.instance_method(:utime=)
189 |
190 | # Etc::Group.instance_method(:name=).source_location
191 | # #=> ["/opt/rubies/druby-2.2.2/lib/ruby/2.2.0/x86_64-linux/etc.so", nil]
192 | assert_raises(FastMethodSource::SourceNotFoundError) {
193 | FastMethodSource.source_for(method)
194 | }
195 | end
196 |
197 | def test_source_for_c_method
198 | method = Array.instance_method(:pop)
199 |
200 | assert_raises(FastMethodSource::SourceNotFoundError) {
201 | FastMethodSource.source_for(method)
202 | }
203 | end
204 |
205 | def test_source_for_accessor
206 | method = SampleClass.instance_method(:accessor)
207 | assert_match(/attr_accessor :accessor\n\z/, FastMethodSource.source_for(method))
208 | end
209 |
210 | def test_source_for_multiline_reader
211 | method = SampleClass.instance_method(:reader2)
212 | assert_match(/attr_reader :reader1,\n\s*:reader2\n\z/,
213 | FastMethodSource.source_for(method))
214 | end
215 |
216 | def test_source_for_rss_author
217 | require 'rss'
218 |
219 | # source_location returns bad line number (complete nonsense) and
220 | # method_source raises an exception when tries to parse this. In case of
221 | # Fast Method Source, it returns the closest method. Let's just make sure
222 | # here that it returns at least something.
223 | method = RSS::Atom::Feed::Entry.instance_method(:author=)
224 | assert_equal false, FastMethodSource.source_for(method).empty?
225 | end
226 |
227 | def test_source_for_one_line_method
228 | method = SampleClass.instance_method(:one_line_method)
229 | expected = " def one_line_method; 1; end\n"
230 | assert_equal(expected, FastMethodSource.source_for(method))
231 | end
232 |
233 | def test_source_for_rss_content
234 | require 'rss'
235 |
236 | method = RSS::DublinCoreModel::DublinCoreDate.instance_method(:content=)
237 | expected = /class DublinCore.+\n.+:content=\)\s+end\n\z/m
238 | assert_match expected, FastMethodSource.source_for(method)
239 | end
240 | end
241 |
--------------------------------------------------------------------------------
/ext/fast_method_source/fast_method_source.c:
--------------------------------------------------------------------------------
1 | // For getline(), fileno() and friends
2 | #define _XOPEN_SOURCE 700
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "node.h"
12 |
13 | #ifdef _WIN32
14 | #include
15 | static const char *null_filename = "NUL";
16 | #define DUP(fd) _dup(fd)
17 | #define DUP2(fd, newfd) _dup2(fd, newfd)
18 | #else
19 | #include
20 | static const char *null_filename = "/dev/null";
21 | #define DUP(fd) dup(fd)
22 | #define DUP2(fd, newfd) dup2(fd, newfd)
23 | #endif
24 |
25 | #ifndef HAVE_RB_SYM2STR
26 | # define rb_sym2str(obj) rb_id2str(SYM2ID(obj))
27 | #endif
28 |
29 | #ifdef __APPLE__
30 | void *
31 | memrchr(const void *s, int c, size_t n)
32 | {
33 | const unsigned char *cp;
34 |
35 | if (n != 0) {
36 | cp = (unsigned char *)s + n;
37 | do {
38 | if (*(--cp) == (unsigned char)c)
39 | return((void *)cp);
40 | } while (--n != 0);
41 | }
42 | return NULL;
43 | }
44 | #else
45 | void *memrchr(const void *s, int c, size_t n);
46 | #endif
47 |
48 | typedef struct {
49 | int source : 1;
50 | int comment : 1;
51 | } finder;
52 |
53 | struct method_data {
54 | unsigned method_location;
55 | const char *filename;
56 | VALUE method_name;
57 | };
58 |
59 | static VALUE read_lines(finder finder, struct method_data *data);
60 | static VALUE read_lines_before(struct method_data *data);
61 | static VALUE read_lines_after(struct method_data *data);
62 | static VALUE find_method_comment(struct method_data *data);
63 | static VALUE find_method_source(struct method_data *data);
64 | static VALUE find_comment_expression(struct method_data *data);
65 | static VALUE find_source_expression(struct method_data *data);
66 | static NODE *parse_expr(VALUE rb_str);
67 | static NODE *parse_with_silenced_stderr(VALUE rb_str);
68 | static void restore_ch(char *line, char ch, int last_ch_idx);
69 | static void close_map(int fd, char *map, size_t map_size, const char *filename);
70 | static void nulify_ch(char *line, char *ch, int last_ch_idx);
71 | static int contains_end_kw(const char *line);
72 | static int is_comment(const char *line, const size_t line_len);
73 | static int is_definition_end(const char *line);
74 | static int is_static_definition_start(const char *line);
75 | static size_t count_prefix_spaces(const char *line);
76 | static void raise_if_nil(VALUE val, VALUE method_name);
77 | static void method_data_init(VALUE self, struct method_data *data);
78 | static VALUE mMethodExtensions_source(VALUE self);
79 |
80 | static VALUE rb_eSourceNotFoundError;
81 |
82 | static VALUE
83 | find_method_source(struct method_data *data)
84 | {
85 | return read_lines_after(data);
86 | }
87 |
88 | static VALUE
89 | find_method_comment(struct method_data *data)
90 | {
91 | return read_lines_before(data);
92 | }
93 |
94 | static VALUE
95 | read_lines_after(struct method_data *data)
96 | {
97 | finder finder = {1, 0};
98 | return read_lines(finder, data);
99 | }
100 |
101 | static VALUE
102 | read_lines_before(struct method_data *data)
103 | {
104 | finder finder = {0, 1};
105 | return read_lines(finder, data);
106 | }
107 |
108 | static VALUE
109 | find_comment_expression(struct method_data *data)
110 | {
111 | struct stat filestat;
112 | int fd;
113 | char *map;
114 |
115 | VALUE comment = Qnil;
116 |
117 | if ((fd = open(data->filename, O_RDONLY)) == -1) {
118 | rb_raise(rb_eIOError, "failed to read - %s", data->filename);
119 | }
120 |
121 | if (fstat(fd, &filestat) == -1) {
122 | rb_raise(rb_eIOError, "filestat failed for %s", data->filename);
123 | }
124 |
125 | map = mmap(NULL, filestat.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
126 | if (map == MAP_FAILED) {
127 | rb_raise(rb_eIOError, "mmap failed for %s", data->filename);
128 | }
129 |
130 | char *rest = map;
131 | unsigned line_count = 0;
132 |
133 | while (line_count++ != data->method_location - 1) {
134 | rest = (char *) memchr(rest, '\n', filestat.st_size) + 1;
135 | }
136 |
137 | size_t end = filestat.st_size - strlen(rest);
138 | map[end] = '\0';
139 |
140 | size_t offset = end;
141 | char *prev = NULL;
142 |
143 | while (line_count-- != 0) {
144 | rest = memrchr(map, '\n', offset - 2);
145 |
146 | if (rest == NULL) {
147 | rest = memrchr(map, '\n', offset);
148 | } else {
149 | rest++;
150 | }
151 |
152 | offset = end - strlen(rest);
153 |
154 | if (is_comment(rest, offset)) {
155 | prev = rest;
156 | } else {
157 | if (prev == NULL) {
158 | comment = rb_str_new("", 0);
159 | } else {
160 | comment = rb_str_new2(prev);
161 | }
162 |
163 | break;
164 | }
165 | }
166 |
167 | if (munmap (map, filestat.st_size) == -1) {
168 | rb_raise(rb_eIOError, "munmap failed for %s", data->filename);
169 | }
170 | close(fd);
171 |
172 | return comment;
173 | }
174 |
175 | static VALUE
176 | find_source_expression(struct method_data *data)
177 | {
178 | struct stat filestat;
179 | int fd;
180 | char *map;
181 |
182 | VALUE source = Qnil;
183 |
184 | if ((fd = open(data->filename, O_RDONLY)) == -1) {
185 | rb_raise(rb_eIOError, "failed to read - %s", data->filename);
186 | }
187 |
188 | if (fstat(fd, &filestat) == -1) {
189 | rb_raise(rb_eIOError, "filestat failed for %s", data->filename);
190 | }
191 |
192 | map = mmap(NULL, filestat.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
193 | if (map == MAP_FAILED) {
194 | rb_raise(rb_eIOError, "mmap failed for %s", data->filename);
195 | }
196 |
197 | char *expr_start = map;
198 | char *expr_end, *next_expr, *line;
199 | unsigned line_count = 0;
200 |
201 | while (line_count++ != data->method_location - 1) {
202 | expr_start = (char *) memchr(expr_start, '\n', filestat.st_size) + 1;
203 | }
204 |
205 | size_t last_ch_idx;
206 | char ch, next_ch;
207 | next_expr = expr_end = expr_start;
208 | size_t leftover_size = filestat.st_size - strlen(expr_end);
209 | size_t offset = filestat.st_size - leftover_size;
210 | size_t next_offset;
211 | size_t prefix_len = 0;
212 | int inside_static_def = 0;
213 | int should_parse = 0;
214 | VALUE rb_expr;
215 |
216 | while (offset != 0) {
217 | next_expr = (char *) memchr(expr_end, '\n', offset) + 1;
218 | next_offset = strlen(next_expr);
219 | line = expr_end;
220 |
221 | last_ch_idx = offset - next_offset - 1;
222 |
223 | nulify_ch(line, &ch, last_ch_idx);
224 |
225 | expr_end = next_expr;
226 | offset = next_offset;
227 |
228 | if (is_static_definition_start(line) && !inside_static_def) {
229 | inside_static_def = 1;
230 | prefix_len = count_prefix_spaces(line);
231 |
232 | if (contains_end_kw(line)) {
233 | if (parse_expr(rb_str_new2(line)) != NULL) {
234 | restore_ch(line, ch, last_ch_idx);
235 | rb_expr = rb_str_new2(expr_start);
236 |
237 | close_map(fd, map, filestat.st_size, data->filename);
238 | return rb_expr;
239 | }
240 | }
241 | }
242 |
243 | if (line[0] == '\n' || is_comment(line, strlen(line))) {
244 | line[last_ch_idx] = ch;
245 | continue;
246 | }
247 |
248 | if (inside_static_def) {
249 | if (is_definition_end(line) &&
250 | count_prefix_spaces(line) == prefix_len)
251 | {
252 | restore_ch(line, ch, last_ch_idx);
253 | rb_expr = rb_str_new2(expr_start);
254 |
255 | close_map(fd, map, filestat.st_size, data->filename);
256 | return rb_expr;
257 | } else {
258 | line[last_ch_idx] = ch;
259 | continue;
260 | }
261 | } else {
262 | should_parse = 1;
263 | }
264 |
265 | if (should_parse) {
266 | line[last_ch_idx] = ch;
267 | nulify_ch(line, &next_ch, last_ch_idx + 1);
268 |
269 | if (parse_expr(rb_str_new2(expr_start)) != NULL) {
270 | rb_expr = rb_str_new2(expr_start);
271 | close_map(fd, map, filestat.st_size, data->filename);
272 | return rb_expr;
273 | } else {
274 | line[last_ch_idx+1] = next_ch;
275 | }
276 | }
277 |
278 | line[last_ch_idx] = ch;
279 | }
280 | close_map(fd, map, filestat.st_size, data->filename);
281 |
282 | return source;
283 | }
284 |
285 | static VALUE
286 | read_lines(finder finder, struct method_data *data)
287 | {
288 | if (finder.comment) {
289 | return find_comment_expression(data);
290 | } else if (finder.source) {
291 | return find_source_expression(data);
292 | }
293 |
294 | return Qnil;
295 | }
296 |
297 | static NODE *
298 | parse_with_silenced_stderr(VALUE rb_str)
299 | {
300 | int old_stderr;
301 | FILE *null_fd;
302 | VALUE last_exception = rb_errinfo();
303 |
304 | old_stderr = DUP(STDERR_FILENO);
305 | fflush(stderr);
306 | null_fd = fopen(null_filename, "w");
307 | DUP2(fileno(null_fd), STDERR_FILENO);
308 |
309 | volatile VALUE vparser = rb_parser_new();
310 | NODE *node = rb_parser_compile_string(vparser, "-", rb_str, 1);
311 | rb_set_errinfo(last_exception);
312 |
313 | fflush(stderr);
314 | fclose(null_fd);
315 |
316 | DUP2(old_stderr, STDERR_FILENO);
317 | close(old_stderr);
318 |
319 | return node;
320 | }
321 |
322 | static NODE *
323 | parse_expr(VALUE rb_str) {
324 | return parse_with_silenced_stderr(rb_str);
325 | }
326 |
327 | static void
328 | restore_ch(char *line, char ch, int last_ch_idx)
329 | {
330 | line[last_ch_idx] = ch;
331 | line[last_ch_idx + 1] = '\0';
332 | }
333 |
334 | static void
335 | close_map(int fd, char *map, size_t map_size, const char *filename)
336 | {
337 | close(fd);
338 | if (munmap(map, map_size) == -1) {
339 | rb_raise(rb_eIOError, "munmap failed for %s", filename);
340 | }
341 | }
342 |
343 | static void
344 | nulify_ch(char *line, char *ch, int last_ch_idx)
345 | {
346 | *ch = line[last_ch_idx];
347 | line[last_ch_idx] = '\0';
348 | }
349 |
350 | static size_t
351 | count_prefix_spaces(const char *line)
352 | {
353 | int i = 0;
354 | size_t spaces = 0;
355 |
356 | do {
357 | if (line[i] == ' ') {
358 | spaces++;
359 | } else {
360 | break;
361 | }
362 | } while (line[i++] != '\n');
363 |
364 | return spaces;
365 | }
366 |
367 | static int
368 | is_definition_end(const char *line)
369 | {
370 | int i = 0;
371 |
372 | do {
373 | if (line[i] == ' ') {
374 | continue;
375 | } else if (strncmp((line + i), "end", 3) == 0) {
376 | return 1;
377 | } else {
378 | return 0;
379 | }
380 | } while (line[i++] != '\0');
381 |
382 | return 0;
383 | }
384 |
385 | static int
386 | contains_end_kw(const char *line)
387 | {
388 | char *match;
389 | char prev_ch;
390 |
391 | if ((match = strstr(line, "end")) != NULL) {
392 | prev_ch = (match - 1)[0];
393 | return prev_ch == ' ' || prev_ch == '\0' || prev_ch == ';';
394 | } else {
395 | return 0;
396 | }
397 | }
398 |
399 | static int
400 | is_comment(const char *line, const size_t line_len)
401 | {
402 | for (size_t i = 0; i < line_len; i++) {
403 | if (line[i] == ' ')
404 | continue;
405 |
406 | return line[i] == '#' && line[i + 1] != '{';
407 | }
408 |
409 | return 0;
410 | }
411 |
412 | static int
413 | is_static_definition_start(const char *line)
414 | {
415 | int i = 0;
416 |
417 | do {
418 | if (line[i] == ' ') {
419 | continue;
420 | } else if (strncmp((line + i), "def ", 4) == 0) {
421 | return 1;
422 | } else if (strncmp((line + i), "class ", 6) == 0) {
423 | return 1;
424 | } else {
425 | return 0;
426 | }
427 | } while (line[i++] != '\0');
428 |
429 | return 0;
430 | }
431 |
432 | static void
433 | raise_if_nil(VALUE val, VALUE method_name)
434 | {
435 | if (NIL_P(val)) {
436 | if (SYMBOL_P(method_name)) {
437 | rb_raise(rb_eSourceNotFoundError, "could not locate source for %s",
438 | RSTRING_PTR(rb_sym2str(method_name)));
439 | } else {
440 | rb_raise(rb_eSourceNotFoundError, "could not locate source for %s",
441 | RSTRING_PTR(method_name));
442 | }
443 | }
444 | }
445 |
446 | static void
447 | method_data_init(VALUE self, struct method_data *data)
448 | {
449 | VALUE source_location = rb_funcall(self, rb_intern("source_location"), 0);
450 | VALUE name = rb_funcall(self, rb_intern("name"), 0);
451 |
452 | raise_if_nil(source_location, name);
453 |
454 | VALUE rb_filename = RARRAY_AREF(source_location, 0);
455 | VALUE rb_method_location = RARRAY_AREF(source_location, 1);
456 |
457 | raise_if_nil(rb_filename, name);
458 | raise_if_nil(rb_method_location, name);
459 |
460 | data->filename = RSTRING_PTR(rb_filename);
461 | data->method_location = FIX2INT(rb_method_location);
462 | data->method_name = name;
463 | }
464 |
465 | static VALUE
466 | mMethodExtensions_source(VALUE self)
467 | {
468 | struct method_data data;
469 | method_data_init(self, &data);
470 |
471 | VALUE source = find_method_source(&data);
472 | raise_if_nil(source, data.method_name);
473 |
474 | return source;
475 | }
476 |
477 | static VALUE
478 | mMethodExtensions_comment(VALUE self)
479 | {
480 | struct method_data data;
481 | method_data_init(self, &data);
482 |
483 | VALUE comment = find_method_comment(&data);
484 | raise_if_nil(comment, data.method_name);
485 |
486 | return comment;
487 | }
488 |
489 | void Init_fast_method_source(void)
490 | {
491 | VALUE rb_mFastMethodSource = rb_define_module_under(rb_cObject, "FastMethodSource");
492 |
493 | rb_eSourceNotFoundError = rb_define_class_under(rb_mFastMethodSource,"SourceNotFoundError", rb_eStandardError);
494 | VALUE rb_mMethodExtensions = rb_define_module_under(rb_mFastMethodSource, "MethodExtensions");
495 |
496 | rb_define_method(rb_mMethodExtensions, "source", mMethodExtensions_source, 0);
497 | rb_define_method(rb_mMethodExtensions, "comment", mMethodExtensions_comment, 0);
498 | }
499 |
--------------------------------------------------------------------------------
/ext/fast_method_source/node.h:
--------------------------------------------------------------------------------
1 | /**********************************************************************
2 |
3 | node.h -
4 |
5 | $Author$
6 | created at: Fri May 28 15:14:02 JST 1993
7 |
8 | Copyright (C) 1993-2007 Yukihiro Matsumoto
9 |
10 | **********************************************************************/
11 |
12 | #ifndef RUBY_NODE_H
13 | #define RUBY_NODE_H 1
14 |
15 | #if defined(__cplusplus)
16 | extern "C" {
17 | #if 0
18 | } /* satisfy cc-mode */
19 | #endif
20 | #endif
21 |
22 | enum node_type {
23 | NODE_SCOPE,
24 | #define NODE_SCOPE NODE_SCOPE
25 | NODE_BLOCK,
26 | #define NODE_BLOCK NODE_BLOCK
27 | NODE_IF,
28 | #define NODE_IF NODE_IF
29 | NODE_CASE,
30 | #define NODE_CASE NODE_CASE
31 | NODE_WHEN,
32 | #define NODE_WHEN NODE_WHEN
33 | NODE_OPT_N,
34 | #define NODE_OPT_N NODE_OPT_N
35 | NODE_WHILE,
36 | #define NODE_WHILE NODE_WHILE
37 | NODE_UNTIL,
38 | #define NODE_UNTIL NODE_UNTIL
39 | NODE_ITER,
40 | #define NODE_ITER NODE_ITER
41 | NODE_FOR,
42 | #define NODE_FOR NODE_FOR
43 | NODE_BREAK,
44 | #define NODE_BREAK NODE_BREAK
45 | NODE_NEXT,
46 | #define NODE_NEXT NODE_NEXT
47 | NODE_REDO,
48 | #define NODE_REDO NODE_REDO
49 | NODE_RETRY,
50 | #define NODE_RETRY NODE_RETRY
51 | NODE_BEGIN,
52 | #define NODE_BEGIN NODE_BEGIN
53 | NODE_RESCUE,
54 | #define NODE_RESCUE NODE_RESCUE
55 | NODE_RESBODY,
56 | #define NODE_RESBODY NODE_RESBODY
57 | NODE_ENSURE,
58 | #define NODE_ENSURE NODE_ENSURE
59 | NODE_AND,
60 | #define NODE_AND NODE_AND
61 | NODE_OR,
62 | #define NODE_OR NODE_OR
63 | NODE_MASGN,
64 | #define NODE_MASGN NODE_MASGN
65 | NODE_LASGN,
66 | #define NODE_LASGN NODE_LASGN
67 | NODE_DASGN,
68 | #define NODE_DASGN NODE_DASGN
69 | NODE_DASGN_CURR,
70 | #define NODE_DASGN_CURR NODE_DASGN_CURR
71 | NODE_GASGN,
72 | #define NODE_GASGN NODE_GASGN
73 | NODE_IASGN,
74 | #define NODE_IASGN NODE_IASGN
75 | NODE_IASGN2,
76 | #define NODE_IASGN2 NODE_IASGN2
77 | NODE_CDECL,
78 | #define NODE_CDECL NODE_CDECL
79 | NODE_CVASGN,
80 | #define NODE_CVASGN NODE_CVASGN
81 | NODE_CVDECL,
82 | #define NODE_CVDECL NODE_CVDECL
83 | NODE_OP_ASGN1,
84 | #define NODE_OP_ASGN1 NODE_OP_ASGN1
85 | NODE_OP_ASGN2,
86 | #define NODE_OP_ASGN2 NODE_OP_ASGN2
87 | NODE_OP_ASGN_AND,
88 | #define NODE_OP_ASGN_AND NODE_OP_ASGN_AND
89 | NODE_OP_ASGN_OR,
90 | #define NODE_OP_ASGN_OR NODE_OP_ASGN_OR
91 | NODE_OP_CDECL,
92 | #define NODE_OP_CDECL NODE_OP_CDECL
93 | NODE_CALL,
94 | #define NODE_CALL NODE_CALL
95 | NODE_FCALL,
96 | #define NODE_FCALL NODE_FCALL
97 | NODE_VCALL,
98 | #define NODE_VCALL NODE_VCALL
99 | NODE_SUPER,
100 | #define NODE_SUPER NODE_SUPER
101 | NODE_ZSUPER,
102 | #define NODE_ZSUPER NODE_ZSUPER
103 | NODE_ARRAY,
104 | #define NODE_ARRAY NODE_ARRAY
105 | NODE_ZARRAY,
106 | #define NODE_ZARRAY NODE_ZARRAY
107 | NODE_VALUES,
108 | #define NODE_VALUES NODE_VALUES
109 | NODE_HASH,
110 | #define NODE_HASH NODE_HASH
111 | NODE_RETURN,
112 | #define NODE_RETURN NODE_RETURN
113 | NODE_YIELD,
114 | #define NODE_YIELD NODE_YIELD
115 | NODE_LVAR,
116 | #define NODE_LVAR NODE_LVAR
117 | NODE_DVAR,
118 | #define NODE_DVAR NODE_DVAR
119 | NODE_GVAR,
120 | #define NODE_GVAR NODE_GVAR
121 | NODE_IVAR,
122 | #define NODE_IVAR NODE_IVAR
123 | NODE_CONST,
124 | #define NODE_CONST NODE_CONST
125 | NODE_CVAR,
126 | #define NODE_CVAR NODE_CVAR
127 | NODE_NTH_REF,
128 | #define NODE_NTH_REF NODE_NTH_REF
129 | NODE_BACK_REF,
130 | #define NODE_BACK_REF NODE_BACK_REF
131 | NODE_MATCH,
132 | #define NODE_MATCH NODE_MATCH
133 | NODE_MATCH2,
134 | #define NODE_MATCH2 NODE_MATCH2
135 | NODE_MATCH3,
136 | #define NODE_MATCH3 NODE_MATCH3
137 | NODE_LIT,
138 | #define NODE_LIT NODE_LIT
139 | NODE_STR,
140 | #define NODE_STR NODE_STR
141 | NODE_DSTR,
142 | #define NODE_DSTR NODE_DSTR
143 | NODE_XSTR,
144 | #define NODE_XSTR NODE_XSTR
145 | NODE_DXSTR,
146 | #define NODE_DXSTR NODE_DXSTR
147 | NODE_EVSTR,
148 | #define NODE_EVSTR NODE_EVSTR
149 | NODE_DREGX,
150 | #define NODE_DREGX NODE_DREGX
151 | NODE_DREGX_ONCE,
152 | #define NODE_DREGX_ONCE NODE_DREGX_ONCE
153 | NODE_ARGS,
154 | #define NODE_ARGS NODE_ARGS
155 | NODE_ARGS_AUX,
156 | #define NODE_ARGS_AUX NODE_ARGS_AUX
157 | NODE_OPT_ARG,
158 | #define NODE_OPT_ARG NODE_OPT_ARG
159 | NODE_KW_ARG,
160 | #define NODE_KW_ARG NODE_KW_ARG
161 | NODE_POSTARG,
162 | #define NODE_POSTARG NODE_POSTARG
163 | NODE_ARGSCAT,
164 | #define NODE_ARGSCAT NODE_ARGSCAT
165 | NODE_ARGSPUSH,
166 | #define NODE_ARGSPUSH NODE_ARGSPUSH
167 | NODE_SPLAT,
168 | #define NODE_SPLAT NODE_SPLAT
169 | NODE_TO_ARY,
170 | #define NODE_TO_ARY NODE_TO_ARY
171 | NODE_BLOCK_ARG,
172 | #define NODE_BLOCK_ARG NODE_BLOCK_ARG
173 | NODE_BLOCK_PASS,
174 | #define NODE_BLOCK_PASS NODE_BLOCK_PASS
175 | NODE_DEFN,
176 | #define NODE_DEFN NODE_DEFN
177 | NODE_DEFS,
178 | #define NODE_DEFS NODE_DEFS
179 | NODE_ALIAS,
180 | #define NODE_ALIAS NODE_ALIAS
181 | NODE_VALIAS,
182 | #define NODE_VALIAS NODE_VALIAS
183 | NODE_UNDEF,
184 | #define NODE_UNDEF NODE_UNDEF
185 | NODE_CLASS,
186 | #define NODE_CLASS NODE_CLASS
187 | NODE_MODULE,
188 | #define NODE_MODULE NODE_MODULE
189 | NODE_SCLASS,
190 | #define NODE_SCLASS NODE_SCLASS
191 | NODE_COLON2,
192 | #define NODE_COLON2 NODE_COLON2
193 | NODE_COLON3,
194 | #define NODE_COLON3 NODE_COLON3
195 | NODE_DOT2,
196 | #define NODE_DOT2 NODE_DOT2
197 | NODE_DOT3,
198 | #define NODE_DOT3 NODE_DOT3
199 | NODE_FLIP2,
200 | #define NODE_FLIP2 NODE_FLIP2
201 | NODE_FLIP3,
202 | #define NODE_FLIP3 NODE_FLIP3
203 | NODE_SELF,
204 | #define NODE_SELF NODE_SELF
205 | NODE_NIL,
206 | #define NODE_NIL NODE_NIL
207 | NODE_TRUE,
208 | #define NODE_TRUE NODE_TRUE
209 | NODE_FALSE,
210 | #define NODE_FALSE NODE_FALSE
211 | NODE_ERRINFO,
212 | #define NODE_ERRINFO NODE_ERRINFO
213 | NODE_DEFINED,
214 | #define NODE_DEFINED NODE_DEFINED
215 | NODE_POSTEXE,
216 | #define NODE_POSTEXE NODE_POSTEXE
217 | NODE_ALLOCA,
218 | #define NODE_ALLOCA NODE_ALLOCA
219 | NODE_BMETHOD,
220 | #define NODE_BMETHOD NODE_BMETHOD
221 | NODE_DSYM,
222 | #define NODE_DSYM NODE_DSYM
223 | NODE_ATTRASGN,
224 | #define NODE_ATTRASGN NODE_ATTRASGN
225 | NODE_PRELUDE,
226 | #define NODE_PRELUDE NODE_PRELUDE
227 | NODE_LAMBDA,
228 | #define NODE_LAMBDA NODE_LAMBDA
229 | NODE_LAST
230 | #define NODE_LAST NODE_LAST
231 | };
232 |
233 | typedef struct RNode {
234 | VALUE flags;
235 | VALUE nd_reserved; /* ex nd_file */
236 | union {
237 | struct RNode *node;
238 | ID id;
239 | VALUE value;
240 | VALUE (*cfunc)(ANYARGS);
241 | ID *tbl;
242 | } u1;
243 | union {
244 | struct RNode *node;
245 | ID id;
246 | long argc;
247 | VALUE value;
248 | } u2;
249 | union {
250 | struct RNode *node;
251 | ID id;
252 | long state;
253 | struct rb_global_entry *entry;
254 | struct rb_args_info *args;
255 | long cnt;
256 | VALUE value;
257 | } u3;
258 | } NODE;
259 |
260 | #define RNODE(obj) (R_CAST(RNode)(obj))
261 |
262 | /* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8: TAINT, 9: UNTRUSTERD, 10: EXIVAR, 11: FREEZE */
263 | /* NODE_FL: 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: NODE_FL_NEWLINE|NODE_FL_CREF_PUSHED_BY_EVAL,
264 | * 8..14: nd_type,
265 | * 15..: nd_line or
266 | * 15: NODE_FL_CREF_PUSHED_BY_EVAL
267 | * 16: NODE_FL_CREF_OMOD_SHARED
268 | */
269 | #define NODE_FL_NEWLINE (((VALUE)1)<<7)
270 |
271 | #define NODE_TYPESHIFT 8
272 | #define NODE_TYPEMASK (((VALUE)0x7f)<flags & NODE_TYPEMASK)>>NODE_TYPESHIFT))
275 | #define nd_set_type(n,t) \
276 | RNODE(n)->flags=((RNODE(n)->flags&~NODE_TYPEMASK)|((((unsigned long)(t))<flags>>NODE_LSHIFT)
281 | #define nd_set_line(n,l) \
282 | RNODE(n)->flags=((RNODE(n)->flags&~((VALUE)(-1)<