ripper-tags 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MTY5YzNjZGUwOTNkOGMwMzM0NTY1MjE3ZDc4MDkyNjVkNjM2ZDBhNw==
5
+ data.tar.gz: !binary |-
6
+ MWM1M2IzYTk2ODgyZjEwZjQ5ODVlZDYzN2E1Nzc1NGEzOWRmYTI0Ng==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NDM5MWNkMjk2Y2YxODE2ZTMxODcyY2I3ODcwN2M1YmY5ODFjZGU1ZWI3Mjhj
10
+ YmIzMzAwMDcyZWM0ZmMxZTA3MTkxYTA0ODk1N2MwY2EwMzUxMTZmZDg1ZDQz
11
+ YzljYWE2NzEzZDY1YTVlOGQ0MDViNjJmYzBlMzJkNmYzZThkOTU=
12
+ data.tar.gz: !binary |-
13
+ ZWU2ZDQ5Nzc0MzAyNjNjYjI0YjU5MTczMjRkMDUwZTJiZWRhN2Y2Y2YwYmQz
14
+ Yzg5MDg3MDJkOTMzNzIyMmI2ZjQzNzliYmIwMzIyNzliNzYzMWQxNjFlZTMz
15
+ MDAxNzVlZjk2YmRjMDlhYTZiYTAzMDRkYjViNjFjYTNlNDgwZGI=
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ tags
2
+ TAGS
3
+ ripper-tags-*gem
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
4
  gem 'rake'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Aman Gupta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -2,56 +2,26 @@
2
2
 
3
3
  fast, accurate ctags generator for ruby source code using Ripper
4
4
 
5
- ### usage (bin)
5
+ ### usage (command-line)
6
+
7
+ Typical usage:
8
+
9
+ ```
10
+ ripper-tags -R --exclude=vendor
11
+ ```
12
+
13
+ This parses all `*.rb` files in the current project, excluding ones in `vendor/`
14
+ directory, and saves tags in Vim format to a file named `./tags`.
15
+
16
+ To see all available options:
6
17
 
7
18
  ```
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:"}
19
+ ripper-tags --help
50
20
  ```
51
21
 
52
22
  ### usage (api)
53
23
 
54
24
  ``` ruby
55
- require 'tag_ripper'
56
- tags = TagRipper.extract("def abc() end", "mycode.rb")
25
+ require 'ripper-tags/parser'
26
+ tags = RipperTags::Parser.extract("def abc() end", "mycode.rb")
57
27
  ```
data/bin/ripper-tags CHANGED
@@ -1,98 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'ripper-tags'
2
3
 
3
- require 'tag_ripper'
4
- require 'pp'
4
+ trap(:INT) { abort }
5
+ trap(:PIPE) { abort }
5
6
 
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
- if ARGV.delete('--emacs')
18
- emacs = true
19
- end
20
- if ARGV.delete('--debug-full')
21
- debug_full = true
22
- end
23
-
24
- ARGV.each do |file|
25
- begin
26
- data = File.read(file)
27
- sexp = TagRipper.new(data, file).parse
28
- v = TagRipper::Visitor.new(sexp, file, data)
29
-
30
- if debug_full
31
- pp Ripper.sexp(data)
32
-
33
- elsif debug
34
- pp sexp
35
-
36
- elsif json
37
- v.tags.each do |tag|
38
- puts Yajl.dump(tag)
39
- end
40
-
41
- elsif emacs
42
- section = []
43
- v.tags.each do |tag|
44
- section << "#{tag[:pattern]}\x7F#{tag[:name]}\x01#{tag[:line]},0"
45
- end
46
- section = section.join("\n")
47
-
48
- print "\x0C\n#{file},#{section.bytesize}\n#{section}\n"
49
-
50
- elsif vim
51
- all_tags += v.tags
52
-
53
- else
54
- v.tags.each do |tag|
55
- kind = case tag[:kind]
56
- when /method$/ then 'def'
57
- when /^const/ then 'const'
58
- else tag[:kind]
59
- end
60
-
61
- if kind == 'class' && tag[:inherits]
62
- suffix = " < #{tag[:inherits]}"
63
- else
64
- suffix = ''
65
- end
66
-
67
- puts "#{tag[:line].to_s.rjust(5)} #{kind.to_s.rjust(6)} #{tag[:full_name]}#{suffix}"
68
- end
69
- end
70
- rescue Exception => e
71
- STDERR.puts [e, file].inspect
72
- raise e
73
- end
74
- end
75
-
76
- if vim
77
- puts <<-EOC
78
- !_TAG_FILE_FORMAT\t2\t/extended format; --format=1 will not append ;" to lines/
79
- !_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/
80
- EOC
81
-
82
- all_tags.sort_by!{ |t| t[:name] }
83
- all_tags.each do |tag|
84
- kwargs = ''
85
- kwargs << "\tclass:#{tag[:class].gsub('::','.')}" if tag[:class]
86
- kwargs << "\tinherits:#{tag[:inherits].gsub('::','.')}" if tag[:inherits]
87
-
88
- kind = case tag[:kind]
89
- when 'method' then 'f'
90
- when 'singleton method' then 'F'
91
- when 'constant' then 'C'
92
- else tag[:kind].slice(0,1)
93
- end
7
+ program_name = File.basename($0)
94
8
 
95
- code = tag[:pattern].gsub('\\','\\\\\\\\').gsub('/','\\/')
96
- puts "%s\t%s\t/^%s$/;\"\t%c%s" % [tag[:name], tag[:path], code, kind, kwargs]
97
- end
9
+ begin
10
+ RipperTags.process_args(ARGV)
11
+ rescue Errno::ENOENT, OptionParser::InvalidOption => err
12
+ abort "#{program_name}: #{err.message}"
98
13
  end
@@ -0,0 +1,125 @@
1
+ require 'pp'
2
+ require 'ripper-tags/parser'
3
+
4
+ module RipperTags
5
+ class FileFinder
6
+ attr_reader :options
7
+
8
+ def initialize(options)
9
+ @options = options
10
+ end
11
+
12
+ def exclude_patterns
13
+ @exclude ||= Array(options.exclude).map { |pattern|
14
+ if pattern.index('*')
15
+ Regexp.new(Regexp.escape(pattern).gsub('\\*', '[^/]+'))
16
+ else
17
+ pattern
18
+ end
19
+ }
20
+ end
21
+
22
+ def exclude_file?(file)
23
+ base = File.basename(file)
24
+ exclude_patterns.any? {|ex|
25
+ case ex
26
+ when Regexp then base =~ ex
27
+ else base == ex
28
+ end
29
+ } || exclude_patterns.any? {|ex|
30
+ case ex
31
+ when Regexp then file =~ ex
32
+ else file.include?(ex)
33
+ end
34
+ }
35
+ end
36
+
37
+ def include_file?(file)
38
+ (options.all_files || file =~ /\.rb\z/) && !exclude_file?(file)
39
+ end
40
+
41
+ def find_files(list, depth = 0)
42
+ list.each do |file|
43
+ if File.directory?(file)
44
+ if options.recursive
45
+ files = Dir.entries(file).map { |name|
46
+ File.join(file, name) unless '.' == name || '..' == name
47
+ }.compact
48
+ find_files(files, depth + 1) {|f| yield f }
49
+ end
50
+ else
51
+ yield file if include_file?(file)
52
+ end
53
+ end
54
+ end
55
+
56
+ def each_file
57
+ return to_enum(__method__) unless block_given?
58
+ find_files(options.files) {|f| yield f }
59
+ end
60
+ end
61
+
62
+ class DataReader
63
+ attr_reader :options
64
+
65
+ def initialize(options)
66
+ @options = options
67
+ end
68
+
69
+ def file_finder
70
+ FileFinder.new(options)
71
+ end
72
+
73
+ def read_file(filename)
74
+ str = File.open(filename, 'r:utf-8') {|f| f.read }
75
+ normalize_encoding(str)
76
+ end
77
+
78
+ def normalize_encoding(str)
79
+ if str.respond_to?(:encode!)
80
+ # strip invalid byte sequences
81
+ str.encode!('utf-16', :invalid => :replace, :undef => :replace)
82
+ str.encode!('utf-8')
83
+ else
84
+ str
85
+ end
86
+ end
87
+
88
+ def each_tag
89
+ return to_enum(__method__) unless block_given?
90
+ file_finder.each_file do |file|
91
+ begin
92
+ $stderr.puts "Parsing file #{file}" if options.verbose
93
+ extractor = tag_extractor(file)
94
+ rescue => err
95
+ if options.force
96
+ $stderr.puts "Error parsing `#{file}': #{err.message}"
97
+ else
98
+ raise err
99
+ end
100
+ else
101
+ extractor.tags.each do |tag|
102
+ yield tag
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def debug_dump(obj)
109
+ pp(obj, $stderr)
110
+ end
111
+
112
+ def parse_file(contents, filename)
113
+ sexp = Parser.new(contents, filename).parse
114
+ debug_dump(sexp) if options.debug
115
+ sexp
116
+ end
117
+
118
+ def tag_extractor(file)
119
+ file_contents = read_file(file)
120
+ debug_dump(Ripper.sexp(file_contents)) if options.verbose_debug
121
+ sexp = parse_file(file_contents, file)
122
+ Visitor.new(sexp, file, file_contents)
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,67 @@
1
+ require 'pathname'
2
+
3
+ module RipperTags
4
+ class DefaultFormatter
5
+ attr_reader :options
6
+
7
+ def initialize(options)
8
+ @options = options
9
+ end
10
+
11
+ def stdout?
12
+ '-' == options.tag_file_name
13
+ end
14
+
15
+ def with_output
16
+ if stdout?
17
+ yield $stdout
18
+ else
19
+ File.open(options.tag_file_name, 'w+') do |outfile|
20
+ yield outfile
21
+ end
22
+ end
23
+ end
24
+
25
+ def tag_file_dir
26
+ @tag_file_dir ||= Pathname.new(options.tag_file_name).dirname
27
+ end
28
+
29
+ def relative_path(tag)
30
+ path = tag.fetch(:path)
31
+ if options.tag_relative && !stdout? && path.index('/') != 0
32
+ Pathname.new(path).relative_path_from(tag_file_dir).to_s
33
+ else
34
+ path
35
+ end
36
+ end
37
+
38
+ def display_kind(tag)
39
+ case tag.fetch(:kind)
40
+ when /method$/ then 'def'
41
+ when /^const/ then 'const'
42
+ else tag[:kind]
43
+ end
44
+ end
45
+
46
+ def display_inheritance(tag)
47
+ if 'class' == tag[:kind] && tag[:inherits]
48
+ " < #{tag[:inherits]}"
49
+ else
50
+ ""
51
+ end
52
+ end
53
+
54
+ def format(tag)
55
+ "%5s %8s %s%s" % [
56
+ tag.fetch(:line).to_s,
57
+ display_kind(tag),
58
+ tag.fetch(:full_name),
59
+ display_inheritance(tag)
60
+ ]
61
+ end
62
+
63
+ def write(tag, io)
64
+ io.puts format(tag)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,69 @@
1
+ require 'ripper-tags/default_formatter'
2
+ require 'stringio'
3
+
4
+ module RipperTags
5
+ # Generates etags format as described in
6
+ # https://en.wikipedia.org/wiki/Ctags#Etags_2
7
+ #
8
+ # The format is non-trivial since it requires section header for each source
9
+ # file to contain the size of tag data in bytes. This is accomplished by
10
+ # buffering tag definitions per-file and flushing them to target IO when a
11
+ # new source file is encountered or when `with_output` block finishes. This
12
+ # assumes that incoming tags are ordered by source file.
13
+ class EmacsFormatter < DefaultFormatter
14
+ def initialize(*)
15
+ super
16
+ @current_file = nil
17
+ @section_io = nil
18
+ end
19
+
20
+ def with_output
21
+ super do |io|
22
+ begin
23
+ yield io
24
+ ensure
25
+ flush_file_section(io)
26
+ end
27
+ end
28
+ end
29
+
30
+ def write(tag, io)
31
+ filename = relative_path(tag)
32
+ section_io = start_file_section(filename, io)
33
+ super(tag, section_io)
34
+ end
35
+
36
+ def start_file_section(filename, io)
37
+ if filename != @current_file
38
+ flush_file_section(io)
39
+ @current_file = filename
40
+ @section_io = StringIO.new
41
+ else
42
+ @section_io
43
+ end
44
+ end
45
+
46
+ def flush_file_section(out)
47
+ if @section_io
48
+ data = @section_io.string
49
+ out.write format_section_header(@current_file, data)
50
+ out.write data
51
+ end
52
+ end
53
+
54
+ def format_section_header(filename, data)
55
+ data_size = data.respond_to?(:bytesize) ? data.bytesize : data.size
56
+ "\x0C\n%s,%d\n" % [ filename, data_size ]
57
+ end
58
+
59
+ def format(tag)
60
+ pattern = tag.fetch(:pattern)
61
+ "%s\x7F%s\x01%d,%d" % [
62
+ tag.fetch(:pattern),
63
+ tag.fetch(:name),
64
+ tag.fetch(:line),
65
+ 0,
66
+ ]
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,10 @@
1
+ require 'yajl'
2
+ require 'ripper-tags/default_formatter'
3
+
4
+ module RipperTags
5
+ class JSONFormatter < DefaultFormatter
6
+ def format(tag)
7
+ Yajl.dump(tag)
8
+ end
9
+ end
10
+ end