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.
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