sourcify 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/HISTORY.txt +4 -0
- data/LICENSE +20 -0
- data/README.rdoc +154 -0
- data/Rakefile +114 -0
- data/VERSION +1 -0
- data/lib/sourcify.rb +12 -0
- data/lib/sourcify/proc.rb +53 -0
- data/lib/sourcify/proc/counter.rb +41 -0
- data/lib/sourcify/proc/lexer.rb +40 -0
- data/lib/sourcify/proc/lexer18.rb +224 -0
- data/lib/sourcify/proc/lexer19.rb +195 -0
- data/lib/sourcify/proc/parser.rb +74 -0
- data/sourcify.gemspec +109 -0
- data/spec/proc/19x_extras.rb +27 -0
- data/spec/proc/readme +5 -0
- data/spec/proc/to_sexp_variables_spec.rb +146 -0
- data/spec/proc/to_source_from_braced_block_w_nested_braced_block_spec.rb +33 -0
- data/spec/proc/to_source_from_braced_block_w_nested_hash_spec.rb +34 -0
- data/spec/proc/to_source_from_braced_block_wo_nesting_complication_spec.rb +46 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_begin_spec.rb +35 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_case_spec.rb +35 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_class_spec.rb +89 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_do_end_block_spec.rb +33 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_for_spec.rb +132 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_if_spec.rb +73 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_method_spec.rb +33 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_module_spec.rb +49 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_unless_spec.rb +73 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_until_spec.rb +176 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_while_spec.rb +176 -0
- data/spec/proc/to_source_from_do_end_block_wo_nesting_complication_spec.rb +46 -0
- data/spec/proc/to_source_from_multi_blocks_w_many_matches_spec.rb +73 -0
- data/spec/proc/to_source_from_multi_blocks_w_single_match_spec.rb +31 -0
- data/spec/proc/to_source_from_multi_do_end_blocks_w_single_match_spec.rb +31 -0
- data/spec/proc/to_source_magic_file_var_spec.rb +127 -0
- data/spec/proc/to_source_magic_line_var_spec.rb +127 -0
- data/spec/proc/to_source_variables_spec.rb +29 -0
- data/spec/spec_helper.rb +41 -0
- metadata +159 -0
data/.document
ADDED
data/.gitignore
ADDED
data/HISTORY.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 NgTzeYang
|
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.
|
data/README.rdoc
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
= Sourcify
|
2
|
+
|
3
|
+
*ParseTree* is great, it accesses the runtime AST (abstract syntax tree) and makes it
|
4
|
+
possible to convert any object to ruby code & S-expression, BUT ParseTree doesn't work
|
5
|
+
for 1.9.* & JRuby.
|
6
|
+
|
7
|
+
*RubyParser* is great, and it works for any rubies (of course, not 100% compatible for
|
8
|
+
1.9.* syntax yet), BUT it works only with static code.
|
9
|
+
|
10
|
+
I truely enjoy using the above tools, but with my other projects, the absence of ParseTree
|
11
|
+
on the different rubies is forcing me to hand-baked my own solution each time to extract
|
12
|
+
the proc code i need at runtime. This is frustrating, the solution for each of them is
|
13
|
+
never perfect, and i'm reinventing the wheel each time just to address a particular
|
14
|
+
pattern of usage (using regexp kungfu).
|
15
|
+
|
16
|
+
Enough is enough, and now we have Sourcify, a unified solution to extract proc code.
|
17
|
+
When ParseTree is available, it simply works as a wrapper round it, otherwise, it uses:
|
18
|
+
|
19
|
+
* Ripper (part of the stdlib for 1.9.*), OR
|
20
|
+
* RubyLex (under irb, which is part of the stdlib for any ruby)
|
21
|
+
|
22
|
+
to extract the code, and does further processing with RubyParser & Ruby2Ruby to ensure
|
23
|
+
100% compatbility with ParseTree (yup, there is no denying that i really like ParseTree).
|
24
|
+
|
25
|
+
== Installing It
|
26
|
+
|
27
|
+
The religiously standard way:
|
28
|
+
|
29
|
+
$ gem install ParseTree sourcify
|
30
|
+
|
31
|
+
Or on 1.9.* or JRuby:
|
32
|
+
|
33
|
+
$ gem install ruby_parser file-tail sourcify
|
34
|
+
|
35
|
+
== Using It
|
36
|
+
|
37
|
+
Sourcify adds 3 methods to Proc:
|
38
|
+
|
39
|
+
=== 1. Proc#to_source
|
40
|
+
|
41
|
+
Returns the code representation of the proc:
|
42
|
+
|
43
|
+
require 'sourcify'
|
44
|
+
|
45
|
+
proc1 = lambda { x + y }
|
46
|
+
puts proc1.to_source # proc { (x + y) }
|
47
|
+
|
48
|
+
proc2 = proc { x + y }
|
49
|
+
puts proc2.to_source # proc { (x + y) }
|
50
|
+
|
51
|
+
Like it or not, a lambda is represented as a proc when converted to source (exactly the
|
52
|
+
same way as ParseTree).
|
53
|
+
|
54
|
+
=== 2. Proc#to_sexp
|
55
|
+
|
56
|
+
Returns the S-expression of the proc:
|
57
|
+
|
58
|
+
require 'sourcify'
|
59
|
+
require 'pp'
|
60
|
+
|
61
|
+
x = 1
|
62
|
+
pp lambda { x + y }.to_sexp
|
63
|
+
# >> s(:iter,
|
64
|
+
# >> s(:call, nil, :proc, s(:arglist)),
|
65
|
+
# >> nil,
|
66
|
+
# >> s(:call, s(:lvar, :x), :+, s(:arglist, s(:call, nil, :y, s(:arglist)))))
|
67
|
+
|
68
|
+
=== 3. Proc#source_location
|
69
|
+
|
70
|
+
By default, this is only available on 1.9.*, it is added (as a bonus) to provide
|
71
|
+
consistency under 1.8.*:
|
72
|
+
|
73
|
+
# /tmp/test.rb
|
74
|
+
require 'sourcify'
|
75
|
+
require 'pp'
|
76
|
+
|
77
|
+
pp lambda { x + y }.source_location
|
78
|
+
# >> ["/tmp/test.rb", 5]
|
79
|
+
|
80
|
+
== Performance
|
81
|
+
|
82
|
+
Performance is reasonable (but can always be improved on):
|
83
|
+
|
84
|
+
ruby user system total real
|
85
|
+
1.8.7 (w ParseTree) 0.010000 0.000000 1.270000 1.273631
|
86
|
+
1.8.7 (w RubyLex) 0.000000 0.000000 1.640000 1.641778
|
87
|
+
1.9.1 (w Ripper) 0.000000 0.000000 1.070000 1.067248
|
88
|
+
JRuby (w RubyLex) 5.054000 0.000000 5.054000 5.055000
|
89
|
+
|
90
|
+
It would be great if Ripper can be ported over to 1.8.* & JRuby (hint hint).
|
91
|
+
|
92
|
+
== Gotchas
|
93
|
+
|
94
|
+
Nothing beats ParseTree's ability to access the runtime AST, it is a very powerful feature.
|
95
|
+
The lexer-based (static) implementations suffer the following gotchas:
|
96
|
+
|
97
|
+
=== 1. The source code is everything
|
98
|
+
|
99
|
+
Since static code analysis is involved, the subject code needs to physically exist within a
|
100
|
+
file, meaning Proc#source_location must return the expected [file, lineno], the following
|
101
|
+
will not work:
|
102
|
+
|
103
|
+
def test
|
104
|
+
eval('lambda { x + y }')
|
105
|
+
end
|
106
|
+
|
107
|
+
pp test.source_location
|
108
|
+
# >> ["(eval)", 1]
|
109
|
+
|
110
|
+
puts test.to_source
|
111
|
+
# >> `initialize': No such file or directory - (eval) (Errno::ENOENT)
|
112
|
+
|
113
|
+
=== 2. Multiple matching procs per line error
|
114
|
+
|
115
|
+
Sometimes, we may have multiple procs on a line, Sourcify can handle this as long as the
|
116
|
+
subject proc has arity that is unique from others:
|
117
|
+
|
118
|
+
# Yup, this works as expected :)
|
119
|
+
b1 = lambda {|a| a+1 }; b2 = lambda { 1+2 }
|
120
|
+
puts b2.to_source
|
121
|
+
# >> proc { (1 + 2) }
|
122
|
+
|
123
|
+
# Nope, this won't work :(
|
124
|
+
b1 = lambda { 1+2 }; b2 = lambda { 2+3 }
|
125
|
+
b2.to_source
|
126
|
+
# >> raises Sourcify::MultipleMatchingProcsPerLineError
|
127
|
+
|
128
|
+
Using Proc#arity is a pretty good way to uniquely identify the subject proc, since having
|
129
|
+
too many procs per line is not a good practice as it reduces readability. However, the
|
130
|
+
following bug under 1.8.* may trip u over:
|
131
|
+
|
132
|
+
lambda { }.arity # 1.8.* (-1) / 1.9.* (0) (?!)
|
133
|
+
lambda {|x| }.arity # 1.8.* (1) / 1.9.* (1)
|
134
|
+
lambda {|x,y| }.arity # 1.8.* (2) / 1.9.* (2)
|
135
|
+
lambda {|*x| }.arity # 1.8.* (-1) / 1.9.* (-1)
|
136
|
+
lambda {|x, *y| }.arity # 1.8.* (-2) / 1.9.* (-2)
|
137
|
+
lambda {|(x,y)| }.arity # 1.8.* (1) / 1.9.* (1)
|
138
|
+
|
139
|
+
This is another reason to install ParseTree when u are on 1.8.*. On JRuby, where u don't
|
140
|
+
have the choice, just avoid multiple procs here line.
|
141
|
+
|
142
|
+
== Note on Patches/Pull Requests
|
143
|
+
|
144
|
+
* Fork the project.
|
145
|
+
* Make your feature addition or bug fix.
|
146
|
+
* Add tests for it. This is important so I don't break it in a
|
147
|
+
future version unintentionally.
|
148
|
+
* Commit, do not mess with rakefile, version, or history.
|
149
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
150
|
+
* Send me a pull request. Bonus points for topic branches.
|
151
|
+
|
152
|
+
== Copyright
|
153
|
+
|
154
|
+
Copyright (c) 2010 NgTzeYang. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
8
|
+
gem.name = "sourcify"
|
9
|
+
gem.summary = %Q{Workarounds before ruby-core officially supports Proc#to_source (& friends)}
|
10
|
+
gem.description = %Q{}
|
11
|
+
gem.email = "ngty77@gmail.com"
|
12
|
+
gem.homepage = "http://github.com/ngty/sourcify"
|
13
|
+
gem.authors = ["NgTzeYang"]
|
14
|
+
gem.add_dependency 'ruby2ruby', '>= 1.2.4'
|
15
|
+
gem.add_development_dependency "bacon", ">= 0"
|
16
|
+
# Plus one of the following groups:
|
17
|
+
#
|
18
|
+
# 1). ParseTree (better performance + dynamic goodness, but not supported on java & 1.9.*)
|
19
|
+
# >> gem.add_dependency "ParseTree", ">= 3.0.5"
|
20
|
+
#
|
21
|
+
# 2). RubyParser (supported for all)
|
22
|
+
# >> gem.add_dependency "ruby_parser", ">= 2.0.4"
|
23
|
+
# >> gem.add_dependency "file-tail", ">= 1.0.5"
|
24
|
+
end
|
25
|
+
Jeweler::GemcutterTasks.new
|
26
|
+
rescue LoadError
|
27
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'rake/testtask'
|
31
|
+
Rake::TestTask.new(:spec) do |spec|
|
32
|
+
spec.libs << 'lib' << 'spec'
|
33
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
34
|
+
spec.verbose = true
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
require 'rcov/rcovtask'
|
39
|
+
Rcov::RcovTask.new do |spec|
|
40
|
+
spec.libs << 'spec'
|
41
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
42
|
+
spec.verbose = true
|
43
|
+
end
|
44
|
+
rescue LoadError
|
45
|
+
task :rcov do
|
46
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
task :spec => :check_dependencies
|
51
|
+
|
52
|
+
begin
|
53
|
+
require 'reek/adapters/rake_task'
|
54
|
+
Reek::RakeTask.new do |t|
|
55
|
+
t.fail_on_error = true
|
56
|
+
t.verbose = false
|
57
|
+
t.source_files = 'lib/**/*.rb'
|
58
|
+
end
|
59
|
+
rescue LoadError
|
60
|
+
task :reek do
|
61
|
+
abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
begin
|
66
|
+
require 'roodi'
|
67
|
+
require 'roodi_task'
|
68
|
+
RoodiTask.new do |t|
|
69
|
+
t.verbose = false
|
70
|
+
end
|
71
|
+
rescue LoadError
|
72
|
+
task :roodi do
|
73
|
+
abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
task :default => :spec
|
78
|
+
|
79
|
+
require 'rake/rdoctask'
|
80
|
+
Rake::RDocTask.new do |rdoc|
|
81
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
82
|
+
|
83
|
+
rdoc.rdoc_dir = 'rdoc'
|
84
|
+
rdoc.title = "sourcify #{version}"
|
85
|
+
rdoc.rdoc_files.include('README*')
|
86
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
87
|
+
end
|
88
|
+
|
89
|
+
# Benchmarking
|
90
|
+
task :benchmark, :task, :times do |t, args|
|
91
|
+
times, task = (args.times || 5).to_i.method(:times), args.task
|
92
|
+
title = " ~ Benchmark Results for Task :#{task} ~ "
|
93
|
+
results = [%w{nth}, %w{user}, %w{system}, %w{total}, %w{real}]
|
94
|
+
|
95
|
+
# Running benchmarking & collecting results
|
96
|
+
require 'benchmark'
|
97
|
+
times.call do |i|
|
98
|
+
result = Benchmark.measure{ Rake::Task[task].execute }.to_s
|
99
|
+
user, system, total, real =
|
100
|
+
result.match(/^\s*(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+\(\s*(\d+\.\d+)\)$/)[1..-1]
|
101
|
+
["##{i.succ}", user, system, total, real].each_with_index{|val, j| results[j] << val }
|
102
|
+
end
|
103
|
+
|
104
|
+
# Formatting benchmarking results
|
105
|
+
formatted_results = results.map do |rs|
|
106
|
+
width = rs.map(&:to_s).map(&:size).max
|
107
|
+
rs.map{|r| ' ' + r.ljust(width, ' ') }
|
108
|
+
end.transpose.map{|row| row.join }
|
109
|
+
|
110
|
+
# Showdown .. printout
|
111
|
+
line = '=' * ([title.size, formatted_results.map(&:size).max].max + 2)
|
112
|
+
puts [line, title, formatted_results.join("\n"), line].join("\n\n")
|
113
|
+
|
114
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/sourcify.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Sourcify
|
2
|
+
|
3
|
+
class MultipleMatchingProcsPerLineError < Exception ; end
|
4
|
+
|
5
|
+
module Proc
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
|
10
|
+
meths = instance_methods.map(&:to_s)
|
11
|
+
|
12
|
+
# Added as a bonus, by default, only 1.9.* implements this.
|
13
|
+
unless meths.include?('source_location')
|
14
|
+
def source_location
|
15
|
+
@source_location ||= (
|
16
|
+
file, line = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$/.match(inspect)[1..2]
|
17
|
+
[file, line.to_i]
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# When ParseTree is available, we just make use of all the convenience it offers :)
|
23
|
+
if Object.const_defined?(:ParseTree)
|
24
|
+
|
25
|
+
alias_method :to_source, :to_ruby
|
26
|
+
|
27
|
+
# Otherwise, we are going to do abit of static text parsing :(
|
28
|
+
else
|
29
|
+
|
30
|
+
unless meths.include?('to_source')
|
31
|
+
def to_source
|
32
|
+
require 'sourcify/proc/parser'
|
33
|
+
(@parser ||= Parser.new(self)).source
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
unless meths.include?('to_sexp')
|
38
|
+
def to_sexp
|
39
|
+
require 'sourcify/proc/parser'
|
40
|
+
(@parser ||= Parser.new(self)).sexp
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
::Proc.class_eval do
|
52
|
+
include Sourcify::Proc
|
53
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Sourcify
|
2
|
+
module Proc
|
3
|
+
class Counter
|
4
|
+
|
5
|
+
attr_accessor :marker
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@marker, @counter = nil, {:start => 0, :end => 0}
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
@counter[key]
|
13
|
+
end
|
14
|
+
|
15
|
+
def started?
|
16
|
+
@counter.values != [0,0]
|
17
|
+
end
|
18
|
+
|
19
|
+
def telly?
|
20
|
+
@counter[:start] == @counter[:end]
|
21
|
+
end
|
22
|
+
|
23
|
+
def decrement_start
|
24
|
+
@counter[:start] -= 1
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def increment_start
|
29
|
+
@counter[:start] += 1
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def increment_end
|
34
|
+
@counter[:end] += 1
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Sourcify
|
2
|
+
module Proc
|
3
|
+
module Lexer
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def new(*args)
|
7
|
+
begin
|
8
|
+
require 'sourcify/proc/lexer19'
|
9
|
+
Lexer19.new(*args)
|
10
|
+
rescue LoadError
|
11
|
+
require 'sourcify/proc/lexer18'
|
12
|
+
Lexer18.new(*args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Commons
|
18
|
+
|
19
|
+
class EndOfBlock < Exception ; end
|
20
|
+
class EndOfLine < Exception ; end
|
21
|
+
|
22
|
+
def work
|
23
|
+
begin
|
24
|
+
@results ||= []
|
25
|
+
@do_end_counter = Sourcify::Proc::Counter.new
|
26
|
+
@braced_counter = Sourcify::Proc::Counter.new
|
27
|
+
lex
|
28
|
+
rescue EndOfBlock
|
29
|
+
@results << @result.dup
|
30
|
+
@is_multiline_block ? @results : retry
|
31
|
+
rescue EndOfLine
|
32
|
+
@results
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|