starscope 1.5.3 → 1.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -9
- data/.travis.yml +2 -2
- data/CHANGELOG.md +11 -1
- data/bin/starscope +2 -0
- data/lib/starscope/db.rb +266 -264
- data/lib/starscope/exportable.rb +225 -221
- data/lib/starscope/fragment_extractor.rb +18 -16
- data/lib/starscope/langs/erb.rb +34 -32
- data/lib/starscope/langs/golang.rb +176 -174
- data/lib/starscope/langs/javascript.rb +96 -94
- data/lib/starscope/langs/ruby.rb +109 -91
- data/lib/starscope/matcher.rb +1 -2
- data/lib/starscope/output.rb +37 -35
- data/lib/starscope/queryable.rb +31 -29
- data/lib/starscope/version.rb +1 -1
- data/starscope.gemspec +4 -4
- data/test/fixtures/sample_ruby.rb +1 -0
- data/test/unit/exportable_test.rb +17 -4
- data/test/unit/fragment_extractor_test.rb +6 -1
- metadata +40 -40
data/lib/starscope/exportable.rb
CHANGED
@@ -1,41 +1,43 @@
|
|
1
|
-
module Starscope
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
1
|
+
module Starscope
|
2
|
+
module Exportable
|
3
|
+
CTAGS_DEFAULT_PATH = 'tags'.freeze
|
4
|
+
CSCOPE_DEFAULT_PATH = 'cscope.out'.freeze
|
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
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
@output.normal("Exporting to '#{path}' in format '#{format}'...")
|
19
|
+
path_prefix = Pathname.getwd.relative_path_from(Pathname.new(path).dirname.expand_path)
|
20
|
+
File.open(path, 'w') do |file|
|
21
|
+
export_to(format, file, path_prefix)
|
22
|
+
end
|
23
|
+
@output.normal('Export complete.')
|
20
24
|
end
|
21
|
-
@output.normal('Export complete.')
|
22
|
-
end
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
def export_to(format, io, path_prefix)
|
27
|
+
case format
|
28
|
+
when :ctags
|
29
|
+
export_ctags(io, path_prefix)
|
30
|
+
when :cscope
|
31
|
+
export_cscope(io, path_prefix)
|
32
|
+
else
|
33
|
+
raise UnknownExportFormatError
|
34
|
+
end
|
32
35
|
end
|
33
|
-
end
|
34
36
|
|
35
|
-
|
37
|
+
private
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
+
def export_ctags(file, path_prefix)
|
40
|
+
file.puts <<END
|
39
41
|
!_TAG_FILE_FORMAT 2 /extended format/
|
40
42
|
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
41
43
|
!_TAG_PROGRAM_AUTHOR Evan Huus /eapache@gmail.com/
|
@@ -43,240 +45,242 @@ module Starscope::Exportable
|
|
43
45
|
!_TAG_PROGRAM_URL https://github.com/eapache/starscope //
|
44
46
|
!_TAG_PROGRAM_VERSION #{Starscope::VERSION} //
|
45
47
|
END
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
defs = (@tables[:defs] || {}).sort_by { |x| x[:name][-1].to_s }
|
49
|
+
defs.each do |record|
|
50
|
+
file.puts ctag_line(record, @meta[:files][record[:file]], path_prefix)
|
51
|
+
end
|
49
52
|
end
|
50
|
-
end
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
def ctag_line(rec, file, path_prefix)
|
55
|
+
line = line_for_record(rec).gsub('/', '\/')
|
56
|
+
path = File.join(path_prefix, rec[:file])
|
57
|
+
ret = "#{rec[:name][-1]}\t#{path}\t/^#{line}$/"
|
55
58
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
59
|
+
ext = ctag_ext_tags(rec, file)
|
60
|
+
unless ext.empty?
|
61
|
+
ret << ';"'
|
62
|
+
ext.sort.each do |k, v|
|
63
|
+
ret << "\t#{k}:#{v}"
|
64
|
+
end
|
61
65
|
end
|
62
|
-
end
|
63
66
|
|
64
|
-
|
65
|
-
|
67
|
+
ret
|
68
|
+
end
|
66
69
|
|
67
|
-
|
68
|
-
|
70
|
+
def ctag_ext_tags(rec, file)
|
71
|
+
tag = {}
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
73
|
+
# these extensions are documented at http://ctags.sourceforge.net/FORMAT
|
74
|
+
case rec[:type]
|
75
|
+
when :func
|
76
|
+
tag['kind'] = 'f'
|
77
|
+
when :module, :class
|
78
|
+
tag['kind'] = 'c'
|
79
|
+
end
|
77
80
|
|
78
|
-
|
81
|
+
tag['language'] = file[:lang]
|
79
82
|
|
80
|
-
|
81
|
-
|
83
|
+
tag
|
84
|
+
end
|
82
85
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
86
|
+
# cscope has this funky issue where it refuses to recognize function calls that
|
87
|
+
# happen outside of a function definition - this isn't an issue in C, where all
|
88
|
+
# calls must occur in a function, but in ruby et al. it is perfectly legal to
|
89
|
+
# write normal code outside the "scope" of a function definition - we insert a
|
90
|
+
# fake shim "global" function everywhere we can to work around this
|
91
|
+
CSCOPE_GLOBAL_HACK_START = " \n\t$-\n".freeze
|
92
|
+
CSCOPE_GLOBAL_HACK_STOP = " \n\t}\n".freeze
|
93
|
+
|
94
|
+
# ftp://ftp.eeng.dcu.ie/pub/ee454/cygwin/usr/share/doc/mlcscope-14.1.8/html/cscope.html
|
95
|
+
def export_cscope(file, _path_prefix)
|
96
|
+
buf = ''
|
97
|
+
files = []
|
98
|
+
db_by_line.each do |filename, lines|
|
99
|
+
next if lines.empty?
|
100
|
+
|
101
|
+
buf << "\t@#{filename}\n\n"
|
102
|
+
buf << "0 #{CSCOPE_GLOBAL_HACK_START}\n"
|
103
|
+
files << filename
|
104
|
+
func_count = 0
|
105
|
+
|
106
|
+
lines.sort.each do |line_no, records|
|
107
|
+
line = line_for_record(records.first)
|
108
|
+
toks = tokenize_line(line, records)
|
109
|
+
next if toks.empty?
|
110
|
+
|
111
|
+
prev = 0
|
112
|
+
buf << line_no.to_s << ' '
|
113
|
+
toks.each do |offset, record|
|
114
|
+
next if offset < prev # this probably indicates an extractor bug
|
115
|
+
|
116
|
+
# Don't export nested functions, cscope barfs on them since C doesn't
|
117
|
+
# have them at all. Skipping tokens is easy; since prev isn't updated
|
118
|
+
# they get turned into plain text automatically.
|
119
|
+
if record[:type] == :func
|
120
|
+
case record[:tbl]
|
121
|
+
when :defs
|
122
|
+
func_count += 1
|
123
|
+
next unless func_count == 1
|
124
|
+
when :end
|
125
|
+
func_count -= 1
|
126
|
+
next unless func_count == 0
|
127
|
+
end
|
124
128
|
end
|
125
|
-
end
|
126
129
|
|
127
|
-
|
128
|
-
|
130
|
+
buf << cscope_output(line, prev, offset, record)
|
131
|
+
prev = offset + record[:key].length
|
132
|
+
end
|
133
|
+
buf << cscope_plaintext(line, prev, line.length) << "\n\n"
|
129
134
|
end
|
130
|
-
buf << cscope_plaintext(line, prev, line.length) << "\n\n"
|
131
135
|
end
|
132
|
-
end
|
133
136
|
|
134
|
-
|
137
|
+
buf << "\t@\n"
|
135
138
|
|
136
|
-
|
137
|
-
|
139
|
+
header = "cscope 15 #{Dir.pwd} -c "
|
140
|
+
offset = format("%010d\n", header.length + 11 + buf.bytes.count)
|
138
141
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
+
file.print(header)
|
143
|
+
file.print(offset)
|
144
|
+
file.print(buf)
|
142
145
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
146
|
+
file.print("#{@meta[:paths].length}\n")
|
147
|
+
@meta[:paths].each { |p| file.print("#{p}\n") }
|
148
|
+
file.print("0\n")
|
149
|
+
file.print("#{files.length}\n")
|
150
|
+
buf = ''
|
151
|
+
files.each { |f| buf << f + "\n" }
|
152
|
+
file.print("#{buf.length}\n#{buf}")
|
153
|
+
end
|
151
154
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
155
|
+
def db_by_line
|
156
|
+
db = {}
|
157
|
+
@tables.each do |tbl, records|
|
158
|
+
records.each do |record|
|
159
|
+
next unless record[:line_no]
|
160
|
+
record[:tbl] = tbl
|
161
|
+
db[record[:file]] ||= {}
|
162
|
+
db[record[:file]][record[:line_no]] ||= []
|
163
|
+
db[record[:file]][record[:line_no]] << record
|
164
|
+
end
|
161
165
|
end
|
166
|
+
db
|
162
167
|
end
|
163
|
-
db
|
164
|
-
end
|
165
168
|
|
166
|
-
|
167
|
-
|
169
|
+
def tokenize_line(line, records)
|
170
|
+
toks = {}
|
168
171
|
|
169
|
-
|
170
|
-
|
172
|
+
records.each do |record|
|
173
|
+
key = record[:name][-1].to_s
|
171
174
|
|
172
|
-
|
173
|
-
|
175
|
+
# use the column if we have it, otherwise fall back to scanning
|
176
|
+
index = record[:col] || line.index(key)
|
174
177
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
+
while index && !valid_index?(line, index, key)
|
179
|
+
index = line.index(key, index + 1)
|
180
|
+
end
|
178
181
|
|
179
|
-
|
182
|
+
next if index.nil?
|
180
183
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
184
|
+
# Strip trailing non-word characters, otherwise cscope barfs on
|
185
|
+
# function names like `include?`
|
186
|
+
if key =~ /^\W*$/
|
187
|
+
next unless [:defs, :end].include?(record[:tbl])
|
188
|
+
else
|
189
|
+
key.sub!(/\W+$/, '')
|
190
|
+
end
|
191
|
+
|
192
|
+
record[:key] = key
|
193
|
+
toks[index] = record
|
187
194
|
end
|
188
195
|
|
189
|
-
|
190
|
-
toks[index] = record
|
196
|
+
toks.sort
|
191
197
|
end
|
192
198
|
|
193
|
-
|
194
|
-
|
199
|
+
def cscope_output(line, prev, offset, record)
|
200
|
+
buf = ''
|
201
|
+
buf << CSCOPE_GLOBAL_HACK_STOP if record[:type] == :func && record[:tbl] == :defs
|
202
|
+
|
203
|
+
record[:name][0...-1].each do |key|
|
204
|
+
# output previous components of the name (ie the Foo in Foo::bar) as unmarked symbols
|
205
|
+
key = key.to_s.sub(/\W+$/, '')
|
206
|
+
next if key.empty?
|
195
207
|
|
196
|
-
|
197
|
-
buf = ''
|
198
|
-
buf << CSCOPE_GLOBAL_HACK_STOP if record[:type] == :func && record[:tbl] == :defs
|
208
|
+
index = line.index(key, prev)
|
199
209
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
next if key.empty?
|
210
|
+
while index && index + key.length < offset && !valid_index?(line, index, key)
|
211
|
+
index = line.index(key, index + 1)
|
212
|
+
end
|
204
213
|
|
205
|
-
|
214
|
+
next unless index && index + key.length < offset
|
206
215
|
|
207
|
-
|
208
|
-
|
216
|
+
buf << cscope_plaintext(line, prev, index) << "\n"
|
217
|
+
buf << "#{key}\n"
|
218
|
+
prev = index + key.length
|
209
219
|
end
|
210
220
|
|
211
|
-
|
221
|
+
buf << cscope_plaintext(line, prev, offset) << "\n"
|
222
|
+
buf << cscope_mark(record) << record[:key] << "\n"
|
212
223
|
|
213
|
-
buf <<
|
214
|
-
buf
|
215
|
-
|
224
|
+
buf << CSCOPE_GLOBAL_HACK_START if record[:type] == :func && record[:tbl] == :end
|
225
|
+
buf
|
226
|
+
rescue ArgumentError
|
227
|
+
# invalid utf-8 byte sequence in the line, oh well
|
228
|
+
line
|
216
229
|
end
|
217
230
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
# invalid utf-8 byte sequence in the line, oh well
|
225
|
-
line
|
226
|
-
end
|
227
|
-
|
228
|
-
def valid_index?(line, index, key)
|
229
|
-
# index is valid if the key exists at it, and the prev/next chars are not word characters
|
230
|
-
((line[index, key.length] == key) &&
|
231
|
-
(index == 0 || line[index - 1] !~ /[[:word:]]/) &&
|
232
|
-
(index + key.length == line.length || line[index + key.length] !~ /[[:word:]]/))
|
233
|
-
end
|
231
|
+
def valid_index?(line, index, key)
|
232
|
+
# index is valid if the key exists at it, and the prev/next chars are not word characters
|
233
|
+
((line[index, key.length] == key) &&
|
234
|
+
(index == 0 || line[index - 1] !~ /[[:word:]]/) &&
|
235
|
+
(index + key.length == line.length || line[index + key.length] !~ /[[:word:]]/))
|
236
|
+
end
|
234
237
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
238
|
+
def cscope_plaintext(line, start, stop)
|
239
|
+
ret = line.slice(start, stop - start)
|
240
|
+
ret.lstrip! if start == 0
|
241
|
+
ret.rstrip! if stop == line.length
|
242
|
+
ret.gsub!(/\s+/, ' ')
|
243
|
+
ret.empty? ? ' ' : ret
|
244
|
+
rescue ArgumentError
|
245
|
+
# invalid utf-8 byte sequence in the line, oh well
|
246
|
+
line
|
247
|
+
end
|
245
248
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
249
|
+
def cscope_mark(rec)
|
250
|
+
case rec[:tbl]
|
251
|
+
when :end
|
252
|
+
case rec[:type]
|
253
|
+
when :func
|
254
|
+
ret = '}'
|
255
|
+
else
|
256
|
+
return ''
|
257
|
+
end
|
258
|
+
when :file
|
259
|
+
ret = '@'
|
260
|
+
when :defs
|
261
|
+
case rec[:type]
|
262
|
+
when :func
|
263
|
+
ret = '$'
|
264
|
+
when :class, :module
|
265
|
+
ret = 'c'
|
266
|
+
when :type
|
267
|
+
ret = 't'
|
268
|
+
else
|
269
|
+
ret = 'g'
|
270
|
+
end
|
271
|
+
when :calls
|
272
|
+
ret = '`'
|
273
|
+
when :requires
|
274
|
+
ret = '~"'
|
275
|
+
when :imports
|
276
|
+
ret = '~<'
|
277
|
+
when :assigns
|
278
|
+
ret = '='
|
252
279
|
else
|
253
280
|
return ''
|
254
281
|
end
|
255
|
-
when :file
|
256
|
-
ret = '@'
|
257
|
-
when :defs
|
258
|
-
case rec[:type]
|
259
|
-
when :func
|
260
|
-
ret = '$'
|
261
|
-
when :class, :module
|
262
|
-
ret = 'c'
|
263
|
-
when :type
|
264
|
-
ret = 't'
|
265
|
-
else
|
266
|
-
ret = 'g'
|
267
|
-
end
|
268
|
-
when :calls
|
269
|
-
ret = '`'
|
270
|
-
when :requires
|
271
|
-
ret = '~"'
|
272
|
-
when :imports
|
273
|
-
ret = '~<'
|
274
|
-
when :assigns
|
275
|
-
ret = '='
|
276
|
-
else
|
277
|
-
return ''
|
278
|
-
end
|
279
282
|
|
280
|
-
|
283
|
+
"\t" + ret
|
284
|
+
end
|
281
285
|
end
|
282
286
|
end
|