sourcify 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/HISTORY.txt +4 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +154 -0
  6. data/Rakefile +114 -0
  7. data/VERSION +1 -0
  8. data/lib/sourcify.rb +12 -0
  9. data/lib/sourcify/proc.rb +53 -0
  10. data/lib/sourcify/proc/counter.rb +41 -0
  11. data/lib/sourcify/proc/lexer.rb +40 -0
  12. data/lib/sourcify/proc/lexer18.rb +224 -0
  13. data/lib/sourcify/proc/lexer19.rb +195 -0
  14. data/lib/sourcify/proc/parser.rb +74 -0
  15. data/sourcify.gemspec +109 -0
  16. data/spec/proc/19x_extras.rb +27 -0
  17. data/spec/proc/readme +5 -0
  18. data/spec/proc/to_sexp_variables_spec.rb +146 -0
  19. data/spec/proc/to_source_from_braced_block_w_nested_braced_block_spec.rb +33 -0
  20. data/spec/proc/to_source_from_braced_block_w_nested_hash_spec.rb +34 -0
  21. data/spec/proc/to_source_from_braced_block_wo_nesting_complication_spec.rb +46 -0
  22. data/spec/proc/to_source_from_do_end_block_w_nested_begin_spec.rb +35 -0
  23. data/spec/proc/to_source_from_do_end_block_w_nested_case_spec.rb +35 -0
  24. data/spec/proc/to_source_from_do_end_block_w_nested_class_spec.rb +89 -0
  25. data/spec/proc/to_source_from_do_end_block_w_nested_do_end_block_spec.rb +33 -0
  26. data/spec/proc/to_source_from_do_end_block_w_nested_for_spec.rb +132 -0
  27. data/spec/proc/to_source_from_do_end_block_w_nested_if_spec.rb +73 -0
  28. data/spec/proc/to_source_from_do_end_block_w_nested_method_spec.rb +33 -0
  29. data/spec/proc/to_source_from_do_end_block_w_nested_module_spec.rb +49 -0
  30. data/spec/proc/to_source_from_do_end_block_w_nested_unless_spec.rb +73 -0
  31. data/spec/proc/to_source_from_do_end_block_w_nested_until_spec.rb +176 -0
  32. data/spec/proc/to_source_from_do_end_block_w_nested_while_spec.rb +176 -0
  33. data/spec/proc/to_source_from_do_end_block_wo_nesting_complication_spec.rb +46 -0
  34. data/spec/proc/to_source_from_multi_blocks_w_many_matches_spec.rb +73 -0
  35. data/spec/proc/to_source_from_multi_blocks_w_single_match_spec.rb +31 -0
  36. data/spec/proc/to_source_from_multi_do_end_blocks_w_single_match_spec.rb +31 -0
  37. data/spec/proc/to_source_magic_file_var_spec.rb +127 -0
  38. data/spec/proc/to_source_magic_line_var_spec.rb +127 -0
  39. data/spec/proc/to_source_variables_spec.rb +29 -0
  40. data/spec/spec_helper.rb +41 -0
  41. metadata +159 -0
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
@@ -0,0 +1,4 @@
1
+ === 0.1.0 (Aug 28, 2010)
2
+
3
+ * 1st gem release !! [#ngty]
4
+
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.
@@ -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.
@@ -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
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'ruby2ruby'
3
+
4
+ begin
5
+ require 'parse_tree'
6
+ require 'parse_tree_extensions'
7
+ rescue LoadError
8
+ require 'ruby_parser'
9
+ require 'file/tail'
10
+ end
11
+
12
+ require 'sourcify/proc'
@@ -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