starscope 1.5.3 → 1.5.4
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.
- 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
|