ripper-tags 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem 'rake'
5
+ gem 'ripper', :platforms => :mri_18
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ ## ripper-tags
2
+
3
+ fast, accurate ctags generator for ruby source code using Ripper
4
+
5
+ ### usage (bin)
6
+
7
+ ```
8
+ $ ripper-tags /usr/lib/ruby/1.8/timeout.rb
9
+ 30 module Timeout
10
+ 35 class Timeout::Error < Interrupt
11
+ 37 class Timeout::ExitException < Exception
12
+ 40 const Timeout::THIS_FILE
13
+ 41 const Timeout::CALLER_OFFSET
14
+ 52 def Timeout#timeout
15
+ 100 def Object#timeout
16
+ 108 const TimeoutError
17
+
18
+ $ ripper-tags --debug /usr/lib/ruby/1.8/timeout.rb
19
+ [[:module,
20
+ ["Timeout", 30],
21
+ [[:class, ["Error", 35], ["Interrupt", 35], []],
22
+ [:class, ["ExitException", 37], ["Exception", 37], []],
23
+ [:assign, "THIS_FILE", 40],
24
+ [:assign, "CALLER_OFFSET", 41],
25
+ [:def, "timeout", 52]]],
26
+ [:def, "timeout", 100],
27
+ [:assign, "TimeoutError", 108]]
28
+
29
+ $ ripper-tags --vim /usr/lib/ruby/1.8/timeout.rb
30
+ !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
31
+ !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
32
+ CALLER_OFFSET /usr/lib/ruby/1.8/timeout.rb /^ CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0$/;" C class:Timeout
33
+ Error /usr/lib/ruby/1.8/timeout.rb /^ class Error < Interrupt$/;" c class:Timeout inherits:Interrupt
34
+ ExitException /usr/lib/ruby/1.8/timeout.rb /^ class ExitException < ::Exception # :nodoc:$/;" c class:Timeout inherits:Exception
35
+ THIS_FILE /usr/lib/ruby/1.8/timeout.rb /^ THIS_FILE = \/\\A#{Regexp.quote(__FILE__)}:\/o$/;" C class:Timeout
36
+ Timeout /usr/lib/ruby/1.8/timeout.rb /^module Timeout$/;" m
37
+ TimeoutError /usr/lib/ruby/1.8/timeout.rb /^TimeoutError = Timeout::Error # :nodoc:$/;" C class:
38
+ timeout /usr/lib/ruby/1.8/timeout.rb /^def timeout(n, e = nil, &block) # :nodoc:$/;" f class:Object
39
+ timeout /usr/lib/ruby/1.8/timeout.rb /^ def timeout(sec, klass = nil)$/;" f class:Timeout
40
+
41
+ $ ripper-tags --json /usr/lib/ruby/1.8/timeout.rb
42
+ {"full_name":"Timeout","name":"Timeout","kind":"module","line":30,"language":"Ruby","path":"/usr/lib/ruby/1.8/timeout.rb","pattern":"module Timeout"}
43
+ {"full_name":"Timeout::Error","name":"Error","class":"Timeout","inherits":"Interrupt","kind":"class","line":35,"language":"Ruby","path":"/usr/lib/ruby/1.8/timeout.rb","pattern":" class Error < Interrupt"}
44
+ {"full_name":"Timeout::ExitException","name":"ExitException","class":"Timeout","inherits":"Exception","kind":"class","line":37,"language":"Ruby","path":"/usr/lib/ruby/1.8/timeout.rb","pattern":" class ExitException < ::Exception # :nodoc:"}
45
+ {"name":"THIS_FILE","full_name":"Timeout::THIS_FILE","class":"Timeout","kind":"constant","line":40,"language":"Ruby","path":"/usr/lib/ruby/1.8/timeout.rb","pattern":" THIS_FILE = /\\A#{Regexp.quote(__FILE__)}:/o"}
46
+ {"name":"CALLER_OFFSET","full_name":"Timeout::CALLER_OFFSET","class":"Timeout","kind":"constant","line":41,"language":"Ruby","path":"/usr/lib/ruby/1.8/timeout.rb","pattern":" CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0"}
47
+ {"name":"timeout","full_name":"Timeout#timeout","class":"Timeout","kind":"method","line":52,"language":"Ruby","path":"/usr/lib/ruby/1.8/timeout.rb","pattern":" def timeout(sec, klass = nil)"}
48
+ {"name":"timeout","full_name":"Object#timeout","class":"Object","kind":"method","line":100,"language":"Ruby","path":"/usr/lib/ruby/1.8/timeout.rb","pattern":"def timeout(n, e = nil, &block) # :nodoc:"}
49
+ {"name":"TimeoutError","full_name":"TimeoutError","class":"","kind":"constant","line":108,"language":"Ruby","path":"/usr/lib/ruby/1.8/timeout.rb","pattern":"TimeoutError = Timeout::Error # :nodoc:"}
50
+ ```
51
+
52
+ ### usage (api)
53
+
54
+ ``` ruby
55
+ require 'tag_ripper'
56
+ tags = TagRipper.extract("def abc() end", "mycode.rb")
57
+ ```
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ task :default => :test
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new 'test' do |t|
5
+ t.test_files = FileList['test/test_*.rb']
6
+ end
data/bin/ripper-tags ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'tag_ripper'
4
+ require 'pp'
5
+
6
+ if ARGV.delete('--json')
7
+ require 'yajl'
8
+ json = true
9
+ end
10
+ if ARGV.delete('--debug')
11
+ debug = true
12
+ end
13
+ if ARGV.delete('--vim')
14
+ vim = true
15
+ all_tags = []
16
+ end
17
+
18
+ ARGV.each do |file|
19
+ begin
20
+ data = File.read(file)
21
+ sexp = TagRipper.new(data, file).parse
22
+ v = TagRipper::Visitor.new(sexp, file, data)
23
+
24
+ if debug
25
+ pp sexp
26
+ elsif json
27
+ v.tags.each do |tag|
28
+ puts Yajl.dump(tag)
29
+ end
30
+ elsif vim
31
+ all_tags += v.tags
32
+ else
33
+ v.tags.each do |tag|
34
+ kind = case tag[:kind]
35
+ when /method$/ then 'def'
36
+ when /^const/ then 'const'
37
+ else tag[:kind]
38
+ end
39
+ if kind == 'class' && tag[:inherits]
40
+ suffix = " < #{tag[:inherits]}"
41
+ else
42
+ suffix = ''
43
+ end
44
+
45
+ puts "#{tag[:line].to_s.rjust(5)} #{kind.to_s.rjust(6)} #{tag[:full_name]}#{suffix}"
46
+ end
47
+ end
48
+ rescue Exception => e
49
+ STDERR.puts [e, file].inspect
50
+ raise e
51
+ end
52
+ end
53
+
54
+ if vim
55
+ puts <<-EOC
56
+ !_TAG_FILE_FORMAT\t2\t/extended format; --format=1 will not append ;" to lines/
57
+ !_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/
58
+ EOC
59
+
60
+ all_tags.sort_by!{ |t| t[:name] }
61
+ all_tags.each do |tag|
62
+ kwargs = ''
63
+ kwargs << "\tclass:#{tag[:class].gsub('::','.')}" if tag[:class]
64
+ kwargs << "\tinherits:#{tag[:inherits].gsub('::','.')}" if tag[:inherits]
65
+
66
+ kind = case tag[:kind]
67
+ when 'method' then 'f'
68
+ when 'singleton method' then 'F'
69
+ when 'constant' then 'C'
70
+ else tag[:kind].slice(0,1)
71
+ end
72
+
73
+ code = tag[:pattern].gsub('\\','\\\\\\\\').gsub('/','\\/')
74
+ puts "%s\t%s\t/^%s$/;\"\t%c%s" % [tag[:name], tag[:path], code, kind, kwargs]
75
+ end
76
+ end
data/lib/tag_ripper.rb ADDED
@@ -0,0 +1,241 @@
1
+ require 'ripper'
2
+
3
+ class TagRipper < Ripper
4
+ def self.extract(data, file='(eval)')
5
+ sexp = new(data, file).parse
6
+ Visitor.new(sexp, file, data).tags
7
+ end
8
+
9
+ SCANNER_EVENTS.each do |event|
10
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
11
+ def on_#{event}(tok)
12
+ [tok, lineno]
13
+ end
14
+ End
15
+ end
16
+
17
+ def on_stmts_add(first, *rest)
18
+ (first || []) + rest.compact
19
+ end
20
+
21
+ def on_module(name, body)
22
+ [:module, name, *body.compact]
23
+ end
24
+ def on_class(name, superclass, body)
25
+ superclass.flatten!(1) if superclass
26
+ [:class, name, superclass, *body.compact]
27
+ end
28
+ def on_def(method, args, body)
29
+ [:def, *method]
30
+ end
31
+ def on_defs(receiver, op, method, args, body)
32
+ receiver.flatten!(1) if receiver
33
+ [:defs, receiver && receiver[0], *method]
34
+ end
35
+ def on_alias(lhs, rhs)
36
+ [:alias, lhs[0], rhs[0], rhs[1]]
37
+ end
38
+ def on_assign(lhs, rhs)
39
+ return if lhs.nil?
40
+ return if lhs[0] == :field
41
+ return if lhs[0] == :aref_field
42
+ [:assign, *lhs.flatten(1)]
43
+ end
44
+ def on_sclass(name, body)
45
+ [:sclass, name && name.flatten(1), *body.compact]
46
+ end
47
+ def on_field(lhs, op, rhs)
48
+ [:field, lhs && lhs[0], rhs[0], rhs[1]]
49
+ end
50
+ def on_aref_field(*args)
51
+ [:aref_field, *args]
52
+ end
53
+
54
+ def on_const_path_ref(a, b)
55
+ a.flatten!(1)
56
+ [[a && a[0], b[0]].join('::'), b[1]]
57
+ end
58
+
59
+ def on_binary(*args)
60
+ end
61
+
62
+ def on_command(name, *args)
63
+ # if name =~ /^(attr_|alias)/
64
+ # [name.to_sym, *args]
65
+ # end
66
+ end
67
+ def on_bodystmt(*args)
68
+ args
69
+ end
70
+ def on_if(condition, success, failure)
71
+ ret = [success, failure].flatten(1).compact
72
+ ret.any?? ret : nil
73
+ end
74
+
75
+ def on_unless_mod(condition, success)
76
+ nil
77
+ end
78
+ alias on_if_mod on_unless_mod
79
+
80
+ def on_var_ref(*args)
81
+ on_vcall(*args) || args
82
+ end
83
+
84
+ def on_vcall(name)
85
+ [name[0].to_sym] if name[0].to_s =~ /private|protected|public$/
86
+ end
87
+
88
+ def on_call(lhs, op, rhs)
89
+ [:call, lhs && lhs[0], rhs && rhs[0], rhs[1]]
90
+ end
91
+
92
+ def on_do_block(*args)
93
+ args
94
+ end
95
+
96
+ def on_method_add_block(method, body)
97
+ return unless method and body
98
+ if method[2] == 'class_eval'
99
+ [:class_eval, [method[1], method[3]], body.last]
100
+ end
101
+ end
102
+
103
+ class Visitor
104
+ attr_reader :tags
105
+
106
+ def initialize(sexp, path, data)
107
+ @path = path
108
+ @lines = data.split("\n")
109
+ @namespace = []
110
+ @tags = []
111
+
112
+ process(sexp)
113
+ end
114
+
115
+ def emit_tag(kind, line, opts={})
116
+ @tags << opts.merge(
117
+ :kind => kind.to_s,
118
+ :line => line,
119
+ :language => 'Ruby',
120
+ :path => @path,
121
+ :pattern => @lines[line-1].chomp,
122
+ :access => @current_access
123
+ ).delete_if{ |k,v| v.nil? }
124
+ end
125
+
126
+ def process(sexp)
127
+ return unless sexp
128
+ return if Symbol === sexp
129
+
130
+ case sexp[0]
131
+ when Array
132
+ sexp.each{ |child| process(child) }
133
+ when Symbol
134
+ name, *args = sexp
135
+ __send__("on_#{name}", *args)
136
+ when String, nil
137
+ # nothing
138
+ end
139
+ end
140
+
141
+ def on_module_or_class(kind, name, superclass, body)
142
+ name, line = *name
143
+ @namespace << name
144
+
145
+ prev_access = @current_access
146
+ @current_access = nil
147
+
148
+ if superclass
149
+ superclass_name = superclass[0] == :call ?
150
+ superclass[1] :
151
+ superclass[0]
152
+ end
153
+ full_name = @namespace.join('::')
154
+ parts = full_name.split('::')
155
+ class_name = parts.pop
156
+
157
+ emit_tag kind, line,
158
+ :full_name => full_name,
159
+ :name => class_name,
160
+ :class => parts.any? && parts.join('::') || nil,
161
+ :inherits => superclass_name
162
+
163
+ process(body)
164
+ ensure
165
+ @current_access = prev_access
166
+ @namespace.pop
167
+ end
168
+
169
+ def on_module(name, body)
170
+ on_module_or_class(:module, name, nil, body)
171
+ end
172
+
173
+ def on_class(name, superclass, body)
174
+ on_module_or_class(:class, name, superclass, body)
175
+ end
176
+
177
+ def on_private() @current_access = 'private' end
178
+ def on_protected() @current_access = 'protected' end
179
+ def on_public() @current_access = 'public' end
180
+
181
+ def on_assign(name, line)
182
+ return unless name =~ /^[A-Z]/
183
+
184
+ emit_tag :constant, line,
185
+ :name => name,
186
+ :full_name => (@namespace + [name]).join('::'),
187
+ :class => @namespace.join('::')
188
+ end
189
+
190
+ def on_alias(name, other, line)
191
+ ns = (@namespace.empty?? 'Object' : @namespace.join('::'))
192
+
193
+ emit_tag :alias, line,
194
+ :name => name,
195
+ :inherits => other,
196
+ :full_name => "#{ns}#{@is_singleton ? '.' : '#'}#{name}",
197
+ :class => ns
198
+ end
199
+
200
+ def on_def(name, line)
201
+ kind = @is_singleton ? 'singleton method' : 'method'
202
+ ns = (@namespace.empty?? 'Object' : @namespace.join('::'))
203
+
204
+ emit_tag kind, line,
205
+ :name => name,
206
+ :full_name => "#{ns}#{@is_singleton ? '.' : '#'}#{name}",
207
+ :class => ns
208
+ end
209
+
210
+ def on_defs(klass, name, line)
211
+ ns = (@namespace + [klass != 'self' ? klass : nil]).compact
212
+ emit_tag 'singleton method', line,
213
+ :name => name,
214
+ :full_name => ns.join('::') + ".#{name}",
215
+ :class => ns.join('::')
216
+ end
217
+
218
+ def on_sclass(name, body)
219
+ name, line = *name
220
+ @namespace << name unless name == 'self'
221
+ prev_is_singleton, @is_singleton = @is_singleton, true
222
+ process(body)
223
+ ensure
224
+ @namespace.pop unless name == 'self'
225
+ @is_singleton = prev_is_singleton
226
+ end
227
+
228
+ def on_class_eval(name, body)
229
+ name, line = *name
230
+ @namespace << name
231
+ process(body)
232
+ ensure
233
+ @namespace.pop
234
+ end
235
+
236
+ def on_call(*args)
237
+ end
238
+ alias on_aref_field on_call
239
+ alias on_field on_call
240
+ end
241
+ end
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'ripper-tags'
3
+ s.version = '0.1.0'
4
+
5
+ s.summary = 'ctags generator for ruby code'
6
+ s.description = 'fast, accurate ctags generator for ruby source code using Ripper'
7
+
8
+ s.homepage = 'http://github.com/tmm1/ripper-tags'
9
+ s.has_rdoc = false
10
+
11
+ s.authors = ['Aman Gupta']
12
+ s.email = ['aman@tmm1.net']
13
+
14
+ s.add_dependency 'yajl-ruby'
15
+
16
+ s.require_paths = ['lib']
17
+ s.bindir = 'bin'
18
+ s.executables << 'ripper-tags'
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ end
@@ -0,0 +1,76 @@
1
+ require File.expand_path('../../lib/tag_ripper', __FILE__)
2
+ require 'test/unit'
3
+
4
+ class TagRipperTest < Test::Unit::TestCase
5
+ def test_extract_basics
6
+ tags = TagRipper.extract(<<-EOC)
7
+ Const1 = 123
8
+ def gmethod
9
+ end
10
+ module M
11
+ class C
12
+ Const2 = 456
13
+ def imethod
14
+ end
15
+ alias imethod_alias imethod
16
+ def self.cmethod
17
+ end
18
+ end
19
+ end
20
+ class M::C
21
+ def imethod2
22
+ end
23
+ def self.cmethod2
24
+ end
25
+ class << self
26
+ def cmethod3
27
+ end
28
+ alias cmethod_alias cmethod3
29
+ end
30
+ end
31
+ M::C.class_eval do
32
+ def imethod3
33
+ end
34
+ def self.cmethod4
35
+ end
36
+ end
37
+ EOC
38
+
39
+ assert_equal %w[
40
+ Const1
41
+ Object#gmethod
42
+ M
43
+ M::C
44
+ M::C::Const2
45
+ M::C#imethod
46
+ M::C#imethod_alias
47
+ M::C.cmethod
48
+ M::C
49
+ M::C#imethod2
50
+ M::C.cmethod2
51
+ M::C.cmethod3
52
+ M::C.cmethod_alias
53
+ M::C#imethod3
54
+ M::C.cmethod4
55
+ ], tags.map{ |t| t[:full_name] }
56
+ end
57
+
58
+ def test_extract_access
59
+ tags = TagRipper.extract(<<-EOC)
60
+ class Test
61
+ def abc() end
62
+ private
63
+ def def() end
64
+ protected
65
+ def ghi() end
66
+ public
67
+ def jkl() end
68
+ end
69
+ EOC
70
+
71
+ assert_equal nil, tags.find{ |t| t[:name] == 'abc' }[:access]
72
+ assert_equal 'private', tags.find{ |t| t[:name] == 'def' }[:access]
73
+ assert_equal 'protected', tags.find{ |t| t[:name] == 'ghi' }[:access]
74
+ assert_equal 'public', tags.find{ |t| t[:name] == 'jkl' }[:access]
75
+ end
76
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ripper-tags
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Aman Gupta
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2013-01-01 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: yajl-ruby
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: fast, accurate ctags generator for ruby source code using Ripper
35
+ email:
36
+ - aman@tmm1.net
37
+ executables:
38
+ - ripper-tags
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - Gemfile
45
+ - README.md
46
+ - Rakefile
47
+ - bin/ripper-tags
48
+ - lib/tag_ripper.rb
49
+ - ripper-tags.gemspec
50
+ - test/test_ripper_tags.rb
51
+ homepage: http://github.com/tmm1/ripper-tags
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options: []
56
+
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.8.24
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: ctags generator for ruby code
84
+ test_files: []
85
+