starscope 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,256 @@
1
+ module Starscope::Export
2
+
3
+ CTAGS_DEFAULT_PATH='tags'
4
+ CSCOPE_DEFAULT_PATH='cscope.out'
5
+
6
+ class UnknownExportFormatError < StandardError; end
7
+
8
+ def export(format, path=nil)
9
+ case format
10
+ when :ctags
11
+ path ||= CTAGS_DEFAULT_PATH
12
+ when :cscope
13
+ path ||= CSCOPE_DEFAULT_PATH
14
+ else
15
+ raise UnknownExportFormatError
16
+ end
17
+
18
+ @output.normal("Exporting to '#{path}' in format '#{format}'...")
19
+ File.open(path, 'w') do |file|
20
+ export_to(format, file)
21
+ end
22
+ @output.normal("Export complete.")
23
+ end
24
+
25
+ def export_to(format, io)
26
+ case format
27
+ when :ctags
28
+ export_ctags(io)
29
+ when :cscope
30
+ export_cscope(io)
31
+ else
32
+ raise UnknownExportFormatError
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # cscope has this funky issue where it refuses to recognize function calls that
39
+ # happen outside of a function definition - this isn't an issue in C, where all
40
+ # calls must occur in a function, but in ruby et al. it is perfectly legal to
41
+ # write normal code outside the "scope" of a function definition - we insert a
42
+ # fake shim "global" function everywhere we can to work around this
43
+ CSCOPE_GLOBAL_HACK_START = "\n\t$-\n"
44
+ CSCOPE_GLOBAL_HACK_STOP = "\n\t}\n"
45
+
46
+ def export_ctags(file)
47
+ file.puts <<END
48
+ !_TAG_FILE_FORMAT 2 /extended format/
49
+ !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
50
+ !_TAG_PROGRAM_AUTHOR Evan Huus /eapache@gmail.com/
51
+ !_TAG_PROGRAM_NAME Starscope //
52
+ !_TAG_PROGRAM_URL https://github.com/eapache/starscope //
53
+ !_TAG_PROGRAM_VERSION #{Starscope::VERSION} //
54
+ END
55
+ defs = (@tables[:defs] || {}).sort_by {|x| x[:name][-1].to_s}
56
+ defs.each do |record|
57
+ file.puts ctag_line(record, @meta[:files][record[:file]])
58
+ end
59
+ end
60
+
61
+ # ftp://ftp.eeng.dcu.ie/pub/ee454/cygwin/usr/share/doc/mlcscope-14.1.8/html/cscope.html
62
+ def export_cscope(file)
63
+ buf = ""
64
+ files = []
65
+ db_by_line().each do |filename, lines|
66
+ next if lines.empty?
67
+
68
+ buf << "\t@#{filename}\n\n"
69
+ buf << "0 #{CSCOPE_GLOBAL_HACK_START}\n"
70
+ files << filename
71
+ func_count = 0
72
+
73
+ lines.sort.each do |line_no, records|
74
+ line = line_for_record(records.first)
75
+ toks = tokenize_line(line, records)
76
+ next if toks.empty?
77
+
78
+ prev = 0
79
+ buf << line_no.to_s << " "
80
+ toks.each do |offset, record|
81
+
82
+ next if offset < prev # this probably indicates an extractor bug
83
+
84
+ # Don't export nested functions, cscope barfs on them since C doesn't
85
+ # have them at all. Skipping tokens is easy; since prev isn't updated
86
+ # they get turned into plain text automatically.
87
+ if record[:type] == :func
88
+ case record[:tbl]
89
+ when :defs
90
+ func_count += 1
91
+ next unless func_count == 1
92
+ when :end
93
+ func_count -= 1
94
+ next unless func_count == 0
95
+ end
96
+ end
97
+
98
+ buf << CSCOPE_GLOBAL_HACK_STOP if record[:type] == :func && record[:tbl] == :defs
99
+ buf << cscope_plaintext(line, prev, offset) << "\n"
100
+ buf << cscope_mark(record[:tbl], record) << record[:key] << "\n"
101
+ buf << CSCOPE_GLOBAL_HACK_START if record[:type] == :func && record[:tbl] == :end
102
+
103
+ prev = offset + record[:key].length
104
+
105
+ end
106
+ buf << cscope_plaintext(line, prev, line.length) << "\n\n"
107
+ end
108
+ end
109
+
110
+ buf << "\t@\n"
111
+
112
+ header = "cscope 15 #{Dir.pwd} -c "
113
+ offset = "%010d\n" % (header.length + 11 + buf.bytes.count)
114
+
115
+ file.print(header)
116
+ file.print(offset)
117
+ file.print(buf)
118
+
119
+ file.print("#{@meta[:paths].length}\n")
120
+ @meta[:paths].each {|p| file.print("#{p}\n")}
121
+ file.print("0\n")
122
+ file.print("#{files.length}\n")
123
+ buf = ""
124
+ files.each {|f| buf << f + "\n"}
125
+ file.print("#{buf.length}\n#{buf}")
126
+ end
127
+
128
+ def db_by_line()
129
+ db = {}
130
+ @tables.each do |tbl, records|
131
+ records.each do |record|
132
+ next if not record[:line_no]
133
+ record[:tbl] = tbl
134
+ db[record[:file]] ||= {}
135
+ db[record[:file]][record[:line_no]] ||= []
136
+ db[record[:file]][record[:line_no]] << record
137
+ end
138
+ end
139
+ return db
140
+ end
141
+
142
+ def tokenize_line(line, records)
143
+ toks = {}
144
+
145
+ records.each do |record|
146
+ key = record[:name][-1].to_s
147
+
148
+ # use the column if we have it, otherwise fall back to scanning
149
+ index = record[:col] || line.index(key)
150
+
151
+ # keep scanning if our current index doesn't actually match the key, or if
152
+ # either the preceeding or succeeding character is a word character
153
+ # (meaning we've accidentally matched the middle of some other token)
154
+ while !index.nil? &&
155
+ ((line[index, key.length] != key) ||
156
+ (index > 0 && line[index-1] =~ /\w/) ||
157
+ (index+key.length < line.length && line[index+key.length] =~ /\w/))
158
+ index = line.index(key, index+1)
159
+ end
160
+
161
+ next if index.nil?
162
+
163
+ # Strip trailing non-word characters, otherwise cscope barfs on
164
+ # function names like `include?`
165
+ if key =~ /^\W*$/
166
+ next unless [:defs, :end].include?(record[:tbl])
167
+ else
168
+ key.sub!(/\W+$/, '')
169
+ end
170
+
171
+ record[:key] = key
172
+ toks[index] = record
173
+
174
+ end
175
+
176
+ return toks.sort
177
+ end
178
+
179
+ def cscope_plaintext(line, start, stop)
180
+ ret = line.slice(start, stop-start)
181
+ ret.lstrip! if start == 0
182
+ ret.rstrip! if stop == line.length
183
+ ret.gsub(/\s+/, ' ')
184
+ rescue ArgumentError
185
+ # invalid utf-8 byte sequence in the line, oh well
186
+ line
187
+ end
188
+
189
+ def cscope_mark(tbl, rec)
190
+ case tbl
191
+ when :end
192
+ case rec[:type]
193
+ when :func
194
+ ret = "}"
195
+ else
196
+ return ""
197
+ end
198
+ when :file
199
+ ret = "@"
200
+ when :defs
201
+ case rec[:type]
202
+ when :func
203
+ ret = "$"
204
+ when :class, :module
205
+ ret = "c"
206
+ when :type
207
+ ret = "t"
208
+ else
209
+ ret = "g"
210
+ end
211
+ when :calls
212
+ ret = "`"
213
+ when :requires
214
+ ret = "~\""
215
+ when :imports
216
+ ret = "~<"
217
+ when :assigns
218
+ ret = "="
219
+ else
220
+ return ""
221
+ end
222
+
223
+ return "\t" + ret
224
+ end
225
+
226
+ def ctag_line(rec, file)
227
+ ret = "#{rec[:name][-1]}\t#{rec[:file]}\t/^#{line_for_record(rec)}$/"
228
+
229
+ ext = ctag_ext_tags(rec, file)
230
+ if not ext.empty?
231
+ ret << ";\""
232
+ ext.sort.each do |k, v|
233
+ ret << "\t#{k}:#{v}"
234
+ end
235
+ end
236
+
237
+ ret
238
+ end
239
+
240
+ def ctag_ext_tags(rec, file)
241
+ tag = {}
242
+
243
+ # these extensions are documented at http://ctags.sourceforge.net/FORMAT
244
+ case rec[:type]
245
+ when :func
246
+ tag["kind"] = "f"
247
+ when :module, :class
248
+ tag["kind"] = "c"
249
+ end
250
+
251
+ tag["language"] = file[:lang]
252
+
253
+ tag
254
+ end
255
+
256
+ end
@@ -1,5 +1,6 @@
1
- module StarScope::Lang
1
+ module Starscope::Lang
2
2
  module Coffeescript
3
+ VERSION = 0
3
4
 
4
5
  def self.match_file(name)
5
6
  name.end_with?(".coffee")
@@ -1,5 +1,7 @@
1
- module StarScope::Lang
1
+ module Starscope::Lang
2
2
  module Go
3
+ VERSION = 1
4
+
3
5
  FUNC_CALL = /([\w\.]*?\w)\(/
4
6
  END_OF_BLOCK = /^\s*\}\s*$/
5
7
  END_OF_GROUP = /^\s*\)\s*$/
@@ -43,7 +45,8 @@ module StarScope::Lang
43
45
  # strip string literals like "foo" unless they're part of an import
44
46
  pos = 0
45
47
  while match = STRING_LITERAL.match(line[pos..-1])
46
- line = line[0...pos] + match.pre_match + "\"\"" + match.post_match
48
+ eos = find_end_of_string(line, match.begin(0))
49
+ line = line[0..match.begin(0)] + line[eos..-1]
47
50
  pos += match.begin(0) + 2
48
51
  end
49
52
  end
@@ -188,5 +191,20 @@ module StarScope::Lang
188
191
  stack.pop
189
192
  scope.pop
190
193
  end
194
+
195
+ def self.find_end_of_string(line, start)
196
+ escape = false
197
+ (start+1...line.length).each do |i|
198
+ if escape
199
+ escape = false
200
+ elsif line[i].chr == '\\'
201
+ escape = true
202
+ elsif line[i].chr == '"'
203
+ return i
204
+ end
205
+ end
206
+
207
+ return line.length
208
+ end
191
209
  end
192
210
  end
@@ -1,7 +1,9 @@
1
1
  require "parser/current"
2
2
 
3
- module StarScope::Lang
3
+ module Starscope::Lang
4
4
  module Ruby
5
+ VERSION = 1
6
+
5
7
  def self.match_file(name)
6
8
  return true if name.end_with?(".rb")
7
9
  File.open(name) do |f|
@@ -18,97 +20,84 @@ module StarScope::Lang
18
20
  ast = Parser::CurrentRuby.parse_file(file)
19
21
  rescue
20
22
  else
21
- Extractor.new(ast).extract &block
23
+ extract_tree(ast, [], &block) if not ast.nil?
22
24
  end
23
25
  end
24
26
 
25
27
  private
26
28
 
27
- class Extractor
28
- def initialize(ast)
29
- @ast = ast
30
- @scope = []
31
- end
29
+ def self.extract_tree(tree, scope, &block)
30
+ extract_node(tree, scope, &block)
32
31
 
33
- def extract(&block)
34
- extract_tree(@ast, &block) if not @ast.nil?
32
+ new_scope = []
33
+ if [:class, :module].include? tree.type
34
+ new_scope = scoped_name(tree.children[0], scope)
35
+ scope += new_scope
35
36
  end
36
37
 
37
- private
38
+ tree.children.each {|node| extract_tree(node, scope, &block) if node.is_a? AST::Node}
38
39
 
39
- def extract_tree(tree, &block)
40
- extract_node tree, &block
41
-
42
- new_scope = []
43
- if [:class, :module].include? tree.type
44
- new_scope = scoped_name(tree.children[0])
45
- @scope += new_scope
46
- end
47
-
48
- tree.children.each {|node| extract_tree node, &block if node.is_a? AST::Node}
49
-
50
- @scope.pop(new_scope.count)
51
- end
40
+ scope.pop(new_scope.count)
41
+ end
52
42
 
53
- def extract_node(node)
54
- loc = node.location
43
+ def self.extract_node(node, scope)
44
+ loc = node.location
55
45
 
56
- case node.type
57
- when :send
58
- name = scoped_name(node)
59
- yield :calls, name, :line_no => loc.line, :col => loc.column
46
+ case node.type
47
+ when :send
48
+ name = scoped_name(node, scope)
49
+ yield :calls, name, :line_no => loc.line, :col => loc.column
60
50
 
61
- if name.last.to_s =~ /\w+=$/
62
- name[-1] = name.last.to_s.chop.to_sym
63
- yield :assigns, name, :line_no => loc.line, :col => loc.column
64
- elsif node.children[0].nil? and node.children[1] == :require and node.children[2].type == :str
65
- yield :requires, node.children[2].children[0].split("/"),
66
- :line_no => loc.line, :col => loc.column
67
- end
51
+ if name.last.to_s =~ /\w+=$/
52
+ name[-1] = name.last.to_s.chop.to_sym
53
+ yield :assigns, name, :line_no => loc.line, :col => loc.column
54
+ elsif node.children[0].nil? and node.children[1] == :require and node.children[2].type == :str
55
+ yield :requires, node.children[2].children[0].split("/"),
56
+ :line_no => loc.line, :col => loc.column
57
+ end
68
58
 
69
- when :def
70
- yield :defs, @scope + [node.children[0]],
71
- :line_no => loc.line, :type => :func, :col => loc.name.column
72
- yield :end, :end, :line_no => loc.end.line, :type => :func, :col => loc.end.column
59
+ when :def
60
+ yield :defs, scope + [node.children[0]],
61
+ :line_no => loc.line, :type => :func, :col => loc.name.column
62
+ yield :end, :end, :line_no => loc.end.line, :type => :func, :col => loc.end.column
73
63
 
74
- when :defs
75
- yield :defs, @scope + [node.children[1]],
76
- :line_no => loc.line, :type => :func, :col => loc.name.column
77
- yield :end, :end, :line_no => loc.end.line, :type => :func, :col => loc.end.column
64
+ when :defs
65
+ yield :defs, scope + [node.children[1]],
66
+ :line_no => loc.line, :type => :func, :col => loc.name.column
67
+ yield :end, :end, :line_no => loc.end.line, :type => :func, :col => loc.end.column
78
68
 
79
- when :module, :class
80
- yield :defs, @scope + scoped_name(node.children[0]),
81
- :line_no => loc.line, :type => node.type, :col => loc.name.column
82
- yield :end, :end, :line_no => loc.end.line, :type => node.type, :col => loc.end.column
69
+ when :module, :class
70
+ yield :defs, scope + scoped_name(node.children[0], scope),
71
+ :line_no => loc.line, :type => node.type, :col => loc.name.column
72
+ yield :end, :end, :line_no => loc.end.line, :type => node.type, :col => loc.end.column
83
73
 
84
- when :casgn
85
- fqn = scoped_name(node)
86
- yield :assigns, fqn, :line_no => loc.line, :col => loc.name.column
87
- yield :defs, fqn, :line_no => loc.line, :col => loc.name.column
74
+ when :casgn
75
+ fqn = scoped_name(node, scope)
76
+ yield :assigns, fqn, :line_no => loc.line, :col => loc.name.column
77
+ yield :defs, fqn, :line_no => loc.line, :col => loc.name.column
88
78
 
89
- when :lvasgn, :ivasgn, :cvasgn, :gvasgn
90
- yield :assigns, @scope + [node.children[0]], :line_no => loc.line, :col => loc.name.column
91
- end
79
+ when :lvasgn, :ivasgn, :cvasgn, :gvasgn
80
+ yield :assigns, scope + [node.children[0]], :line_no => loc.line, :col => loc.name.column
92
81
  end
82
+ end
93
83
 
94
- def scoped_name(node)
95
- if node.type == :block
96
- scoped_name(node.children[0])
97
- elsif [:lvar, :ivar, :cvar, :gvar, :const, :send, :casgn].include? node.type
98
- if node.children[0].is_a? Symbol
99
- [node.children[0]]
100
- elsif node.children[0].is_a? AST::Node
101
- scoped_name(node.children[0]) << node.children[1]
102
- elsif node.children[0].nil?
103
- if node.type == :const
104
- [node.children[1]]
105
- else
106
- @scope + [node.children[1]]
107
- end
84
+ def self.scoped_name(node, scope)
85
+ if node.type == :block
86
+ scoped_name(node.children[0], scope)
87
+ elsif [:lvar, :ivar, :cvar, :gvar, :const, :send, :casgn].include? node.type
88
+ if node.children[0].is_a? Symbol
89
+ [node.children[0]]
90
+ elsif node.children[0].is_a? AST::Node
91
+ scoped_name(node.children[0], scope) << node.children[1]
92
+ elsif node.children[0].nil?
93
+ if node.type == :const
94
+ [node.children[1]]
95
+ else
96
+ scope + [node.children[1]]
108
97
  end
109
- else
110
- [node.type]
111
98
  end
99
+ else
100
+ [node.type]
112
101
  end
113
102
  end
114
103
  end