ripper-tags 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/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
+