starscope 1.0.4 → 1.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.
- checksums.yaml +7 -0
- data/.gitignore +0 -1
- data/CHANGELOG.md +24 -1
- data/Gemfile.lock +6 -6
- data/README.md +8 -0
- data/Rakefile +1 -2
- data/bin/starscope +42 -24
- data/doc/DB_FORMAT.md +42 -0
- data/doc/LANGUAGE_SUPPORT.md +80 -0
- data/doc/USER_GUIDE.md +135 -0
- data/lib/starscope/db.rb +135 -83
- data/lib/starscope/langs/coffeescript.rb +1 -1
- data/lib/starscope/langs/go.rb +3 -0
- data/lib/starscope/langs/ruby.rb +11 -11
- data/lib/starscope/output.rb +16 -9
- data/lib/starscope/version.rb +1 -1
- data/test/fixtures/db_old.json.gz +0 -0
- data/test/{files → fixtures}/empty +0 -0
- data/test/{files → fixtures}/sample_golang.go +4 -0
- data/test/{files → fixtures}/sample_ruby.rb +0 -0
- data/test/functional/test_starscope.rb +35 -0
- data/test/test_helper.rb +5 -3
- data/test/{lib → unit}/test_db.rb +25 -18
- data/test/{lib → unit}/test_golang.rb +2 -0
- data/test/{lib → unit}/test_matcher.rb +0 -0
- data/test/{lib → unit}/test_record.rb +2 -2
- data/test/{lib → unit}/test_ruby.rb +0 -0
- metadata +36 -52
- data/test/files/db_old.json.gz +0 -0
- data/test/lib/test_starscope.rb +0 -33
data/lib/starscope/db.rb
CHANGED
@@ -1,20 +1,27 @@
|
|
1
1
|
require 'date'
|
2
2
|
require 'oj'
|
3
|
+
require 'set'
|
3
4
|
require 'zlib'
|
4
5
|
|
5
6
|
require 'starscope/matcher'
|
6
7
|
require 'starscope/output'
|
7
8
|
require 'starscope/record'
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
# cscope has this funky issue where it refuses to recognize function calls that
|
11
|
+
# happen outside of a function definition - this isn't an issue in C, where all
|
12
|
+
# calls must occur in a function, but in ruby et al. it is perfectly legal to
|
13
|
+
# write normal code outside the "scope" of a function definition - we insert a
|
14
|
+
# fake shim "global" function everywhere we can to work around this
|
15
|
+
CSCOPE_GLOBAL_HACK_START = "\n\t$-\n"
|
16
|
+
CSCOPE_GLOBAL_HACK_STOP = "\n\t}\n"
|
17
|
+
|
18
|
+
# dynamically load all our language extractors
|
19
|
+
LANGS = []
|
20
|
+
Dir.glob("#{File.dirname(__FILE__)}/langs/*.rb").each do |path|
|
21
|
+
require path
|
22
|
+
lang = /(\w+)\.rb$/.match(path)[1]
|
23
|
+
LANGS << eval("StarScope::Lang::#{lang.capitalize}")
|
24
|
+
end
|
18
25
|
|
19
26
|
class StarScope::DB
|
20
27
|
|
@@ -23,8 +30,8 @@ class StarScope::DB
|
|
23
30
|
class NoTableError < StandardError; end
|
24
31
|
class UnknownDBFormatError < StandardError; end
|
25
32
|
|
26
|
-
def initialize(
|
27
|
-
@output = StarScope::Output.new(
|
33
|
+
def initialize(output_level)
|
34
|
+
@output = StarScope::Output.new(output_level)
|
28
35
|
@meta = {:paths => [], :files => {}, :excludes => [],
|
29
36
|
:version => StarScope::VERSION}
|
30
37
|
@tables = {}
|
@@ -72,27 +79,24 @@ class StarScope::DB
|
|
72
79
|
end
|
73
80
|
|
74
81
|
def add_excludes(paths)
|
82
|
+
@output.log("Excluding files in paths #{paths}...")
|
75
83
|
@meta[:paths] -= paths.map {|p| normalize_glob(p)}
|
76
84
|
paths = paths.map {|p| normalize_fnmatch(p)}
|
77
85
|
@meta[:excludes] += paths
|
78
86
|
@meta[:excludes].uniq!
|
79
87
|
|
80
|
-
|
81
|
-
|
82
|
-
ret = matches_exclude(paths, name)
|
83
|
-
deleted << name if ret
|
84
|
-
ret
|
85
|
-
end
|
86
|
-
remove_files(deleted)
|
88
|
+
excluded = @meta[:files].keys.select {|name| matches_exclude?(paths, name)}
|
89
|
+
remove_files(excluded)
|
87
90
|
end
|
88
91
|
|
89
92
|
def add_paths(paths)
|
93
|
+
@output.log("Adding files in paths #{paths}...")
|
90
94
|
@meta[:excludes] -= paths.map {|p| normalize_fnmatch(p)}
|
91
95
|
paths = paths.map {|p| normalize_glob(p)}
|
92
96
|
@meta[:paths] += paths
|
93
97
|
@meta[:paths].uniq!
|
94
98
|
files = Dir.glob(paths).select {|f| File.file? f}
|
95
|
-
files.delete_if {|f| matches_exclude(@meta[:excludes], f)}
|
99
|
+
files.delete_if {|f| matches_exclude?(@meta[:excludes], f)}
|
96
100
|
return if files.empty?
|
97
101
|
@output.new_pbar("Building", files.length)
|
98
102
|
add_new_files(files)
|
@@ -100,40 +104,33 @@ class StarScope::DB
|
|
100
104
|
end
|
101
105
|
|
102
106
|
def update
|
103
|
-
|
104
|
-
|
107
|
+
changes = @meta[:files].keys.group_by {|name| file_changed(name)}
|
108
|
+
changes[:modified] ||= []
|
109
|
+
changes[:deleted] ||= []
|
105
110
|
|
106
|
-
@
|
107
|
-
|
108
|
-
@tables, tmp_tbls = {}, @tables
|
109
|
-
to_prune = []
|
110
|
-
|
111
|
-
@meta[:files].delete_if do |name, record|
|
112
|
-
@output.log("Updating `#{name}`")
|
113
|
-
|
114
|
-
event = file_event(name, record)
|
115
|
-
if event != :unchanged
|
116
|
-
to_prune << name
|
117
|
-
changed = true
|
118
|
-
parse_file(name) if event == :modified
|
119
|
-
end
|
111
|
+
new_files = (Dir.glob(@meta[:paths]).select {|f| File.file? f}) - @meta[:files].keys
|
112
|
+
new_files.delete_if {|f| matches_exclude?(@meta[:excludes], f)}
|
120
113
|
|
121
|
-
|
122
|
-
|
114
|
+
if changes[:deleted].empty? && changes[:modified].empty? && new_files.empty?
|
115
|
+
@output.print("No changes detected.")
|
116
|
+
return false
|
123
117
|
end
|
124
118
|
|
125
|
-
@
|
126
|
-
remove_files(
|
127
|
-
|
128
|
-
|
119
|
+
@output.new_pbar("Updating", changes[:modified].length + new_files.length)
|
120
|
+
remove_files(changes[:deleted])
|
121
|
+
update_files(changes[:modified])
|
129
122
|
add_new_files(new_files)
|
130
123
|
@output.finish_pbar
|
131
|
-
|
124
|
+
|
125
|
+
true
|
132
126
|
end
|
133
127
|
|
134
128
|
def dump_table(table)
|
135
129
|
raise NoTableError if not @tables[table]
|
130
|
+
|
136
131
|
puts "== Table: #{table} =="
|
132
|
+
puts "No records" if @tables[table].empty?
|
133
|
+
|
137
134
|
@tables[table].sort {|a,b|
|
138
135
|
a[:name][-1].to_s.downcase <=> b[:name][-1].to_s.downcase
|
139
136
|
}.each do |record|
|
@@ -208,36 +205,42 @@ END
|
|
208
205
|
next if lines.empty?
|
209
206
|
|
210
207
|
buf << "\t@#{filename}\n\n"
|
208
|
+
buf << "0 #{CSCOPE_GLOBAL_HACK_START}\n"
|
211
209
|
files << filename
|
210
|
+
func_count = 0
|
212
211
|
|
213
212
|
lines.sort.each do |line_no, records|
|
214
|
-
|
215
|
-
|
216
|
-
rescue ArgumentError
|
217
|
-
# invalid utf-8 byte sequence in the line, just do our best
|
218
|
-
line = records.first[:line]
|
219
|
-
end
|
220
|
-
|
221
|
-
toks = {}
|
222
|
-
records.each do |record|
|
223
|
-
key = record[:name][-1].to_s
|
224
|
-
index = line.index(key)
|
225
|
-
while index
|
226
|
-
toks[index] = record
|
227
|
-
index = line.index(key, index + 1)
|
228
|
-
end
|
229
|
-
end
|
213
|
+
line = records.first[:line]
|
214
|
+
toks = tokenize_line(line, records)
|
230
215
|
next if toks.empty?
|
231
216
|
|
232
217
|
prev = 0
|
233
218
|
buf << line_no.to_s << " "
|
234
|
-
toks.
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
219
|
+
toks.each do |offset, record|
|
220
|
+
|
221
|
+
# Don't export nested functions, cscope barfs on them since C doesn't
|
222
|
+
# have them at all. Skipping tokens is easy; since prev isn't updated
|
223
|
+
# they get turned into plain text automatically.
|
224
|
+
if record[:type] == :func
|
225
|
+
case record[:tbl]
|
226
|
+
when :defs
|
227
|
+
func_count += 1
|
228
|
+
next unless func_count == 1
|
229
|
+
when :end
|
230
|
+
func_count -= 1
|
231
|
+
next unless func_count == 0
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
buf << CSCOPE_GLOBAL_HACK_STOP if record[:type] == :func && record[:tbl] == :defs
|
236
|
+
buf << cscope_plaintext(line, prev, offset) << "\n"
|
237
|
+
buf << StarScope::Record.cscope_mark(record[:tbl], record) << record[:key] << "\n"
|
238
|
+
buf << CSCOPE_GLOBAL_HACK_START if record[:type] == :func && record[:tbl] == :end
|
239
|
+
|
240
|
+
prev = offset + record[:key].length
|
241
|
+
|
239
242
|
end
|
240
|
-
buf << line.
|
243
|
+
buf << cscope_plaintext(line, prev, line.length) << "\n\n"
|
241
244
|
end
|
242
245
|
end
|
243
246
|
|
@@ -264,12 +267,27 @@ END
|
|
264
267
|
def add_new_files(files)
|
265
268
|
files.each do |file|
|
266
269
|
@output.log("Adding `#{file}`")
|
267
|
-
@meta[:files][file] = {}
|
268
270
|
parse_file(file)
|
269
271
|
@output.inc_pbar
|
270
272
|
end
|
271
273
|
end
|
272
274
|
|
275
|
+
def update_files(files)
|
276
|
+
remove_files(files)
|
277
|
+
add_new_files(files)
|
278
|
+
end
|
279
|
+
|
280
|
+
def remove_files(files)
|
281
|
+
files.each do |file|
|
282
|
+
@output.log("Removing `#{file}`")
|
283
|
+
@meta[:files].delete(file)
|
284
|
+
end
|
285
|
+
files = files.to_set
|
286
|
+
@tables.each do |name, tbl|
|
287
|
+
tbl.delete_if {|val| files.include?(val[:file])}
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
273
291
|
# File.fnmatch treats a "**" to match files and directories recursively
|
274
292
|
def normalize_fnmatch(path)
|
275
293
|
if path == "."
|
@@ -307,12 +325,59 @@ END
|
|
307
325
|
return db
|
308
326
|
end
|
309
327
|
|
310
|
-
def
|
328
|
+
def tokenize_line(line, records)
|
329
|
+
toks = {}
|
330
|
+
|
331
|
+
records.each do |record|
|
332
|
+
key = record[:name][-1].to_s
|
333
|
+
|
334
|
+
# use the column if we have it, otherwise fall back to scanning
|
335
|
+
index = record[:col] || line.index(key)
|
336
|
+
|
337
|
+
# keep scanning if our current index doesn't actually match the key, or if
|
338
|
+
# either the preceeding or succeeding character is a word character
|
339
|
+
# (meaning we've accidentally matched the middle of some other token)
|
340
|
+
while !index.nil? &&
|
341
|
+
((line[index, key.length] != key) ||
|
342
|
+
(index > 0 && line[index-1] =~ /\w/) ||
|
343
|
+
(index+key.length < line.length && line[index+key.length] =~ /\w/))
|
344
|
+
index = line.index(key, index+1)
|
345
|
+
end
|
346
|
+
|
347
|
+
next if index.nil?
|
348
|
+
|
349
|
+
# Strip trailing non-word characters, otherwise cscope barfs on
|
350
|
+
# function names like `include?`
|
351
|
+
if key =~ /^\W*$/
|
352
|
+
next unless [:defs, :end].include?(record[:tbl])
|
353
|
+
else
|
354
|
+
key.sub!(/\W+$/, '')
|
355
|
+
end
|
356
|
+
|
357
|
+
record[:key] = key
|
358
|
+
toks[index] = record
|
359
|
+
|
360
|
+
end
|
361
|
+
|
362
|
+
return toks.sort
|
363
|
+
end
|
364
|
+
|
365
|
+
def cscope_plaintext(line, start, stop)
|
366
|
+
ret = line.slice(start, stop-start)
|
367
|
+
ret.lstrip! if start == 0
|
368
|
+
ret.rstrip! if stop == line.length
|
369
|
+
ret.gsub(/\s+/, ' ')
|
370
|
+
rescue ArgumentError
|
371
|
+
# invalid utf-8 byte sequence in the line, oh well
|
372
|
+
line
|
373
|
+
end
|
374
|
+
|
375
|
+
def matches_exclude?(patterns, file)
|
311
376
|
patterns.map {|p| File.fnmatch(p, file)}.any?
|
312
377
|
end
|
313
378
|
|
314
379
|
def parse_file(file)
|
315
|
-
@meta[:files][file]
|
380
|
+
@meta[:files][file] = {:last_updated => File.mtime(file).to_i}
|
316
381
|
|
317
382
|
LANGS.each do |lang|
|
318
383
|
next if not lang.match_file file
|
@@ -321,27 +386,14 @@ END
|
|
321
386
|
@tables[tbl] << StarScope::Record.build(file, name, args)
|
322
387
|
end
|
323
388
|
@meta[:files][file][:lang] = lang.name.split('::').last.to_sym
|
389
|
+
return
|
324
390
|
end
|
325
391
|
end
|
326
392
|
|
327
|
-
def
|
328
|
-
new_tbls.each do |name, tbl|
|
329
|
-
@tables[name] ||= []
|
330
|
-
@tables[name].concat(tbl)
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
def remove_files(files)
|
335
|
-
check = Hash[files.map {|f| [f, true]}]
|
336
|
-
@tables.each do |name, tbl|
|
337
|
-
tbl.delete_if {|val| check[val[:file]]}
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
def file_event(name, record)
|
393
|
+
def file_changed(name)
|
342
394
|
if not File.exists?(name) or not File.file?(name)
|
343
395
|
:deleted
|
344
|
-
elsif
|
396
|
+
elsif @meta[:files][name][:last_updated] < File.mtime(name).to_i
|
345
397
|
:modified
|
346
398
|
else
|
347
399
|
:unchanged
|
data/lib/starscope/langs/go.rb
CHANGED
@@ -60,6 +60,7 @@ module StarScope::Lang
|
|
60
60
|
stack.pop
|
61
61
|
when /(.+)\s*=.*/
|
62
62
|
parse_def($1, line_no, scope, &block)
|
63
|
+
parse_call(line, line_no, scope, &block)
|
63
64
|
else
|
64
65
|
parse_def(line, line_no, scope, &block)
|
65
66
|
end
|
@@ -122,10 +123,12 @@ module StarScope::Lang
|
|
122
123
|
stack.push(:def)
|
123
124
|
when /^var\s+(\w+)\s/
|
124
125
|
yield :defs, scope + [$1], :line_no => line_no
|
126
|
+
parse_call(line, line_no, scope, &block)
|
125
127
|
when /^const\s+\(/
|
126
128
|
stack.push(:def)
|
127
129
|
when /^const\s+(\w+)\s/
|
128
130
|
yield :defs, scope + [$1], :line_no => line_no
|
131
|
+
parse_call(line, line_no, scope, &block)
|
129
132
|
when /^\s+(.*?) :?=[^=]/
|
130
133
|
$1.split(' ').each do |var|
|
131
134
|
next if CONTROL_KEYS.include?(var)
|
data/lib/starscope/langs/ruby.rb
CHANGED
@@ -55,34 +55,34 @@ module StarScope::Lang
|
|
55
55
|
|
56
56
|
case node.type
|
57
57
|
when :send
|
58
|
-
yield :calls, scoped_name(node), :line_no => loc.
|
58
|
+
yield :calls, scoped_name(node), :line_no => loc.line, :col => loc.column
|
59
59
|
if node.children[0].nil? and node.children[1] == :require and node.children[2].type == :str
|
60
60
|
yield :requires, node.children[2].children[0].split("/"),
|
61
|
-
:line_no => loc.
|
61
|
+
:line_no => loc.line, :col => loc.column
|
62
62
|
end
|
63
63
|
|
64
64
|
when :def
|
65
65
|
yield :defs, @scope + [node.children[0]],
|
66
|
-
:line_no => loc.
|
67
|
-
yield :end, :end, :line_no => loc.end.line, :type => :func
|
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
|
68
68
|
|
69
69
|
when :defs
|
70
70
|
yield :defs, @scope + [node.children[1]],
|
71
|
-
:line_no => loc.
|
72
|
-
yield :end, :end, :line_no => loc.end.line, :type => :func
|
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
|
73
73
|
|
74
74
|
when :module, :class
|
75
75
|
yield :defs, @scope + scoped_name(node.children[0]),
|
76
|
-
:line_no => loc.
|
77
|
-
yield :end, :end, :line_no => loc.end.line, :type => node.type
|
76
|
+
:line_no => loc.line, :type => node.type, :col => loc.name.column
|
77
|
+
yield :end, :end, :line_no => loc.end.line, :type => node.type, :col => loc.end.column
|
78
78
|
|
79
79
|
when :casgn
|
80
80
|
fqn = scoped_name(node)
|
81
|
-
yield :assigns, fqn, :line_no => loc.
|
82
|
-
yield :defs, fqn, :line_no => loc.
|
81
|
+
yield :assigns, fqn, :line_no => loc.line, :col => loc.name.column
|
82
|
+
yield :defs, fqn, :line_no => loc.line, :col => loc.name.column
|
83
83
|
|
84
84
|
when :lvasgn, :ivasgn, :cvasgn, :gvasgn
|
85
|
-
yield :assigns, @scope + [node.children[0]], :line_no => loc.
|
85
|
+
yield :assigns, @scope + [node.children[0]], :line_no => loc.line, :col => loc.name.column
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
data/lib/starscope/output.rb
CHANGED
@@ -4,14 +4,13 @@ class StarScope::Output
|
|
4
4
|
|
5
5
|
PBAR_FORMAT = '%t: %c/%C %E ||%b>%i||'
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@verbose = verbose
|
7
|
+
def initialize(level)
|
8
|
+
@level = level
|
10
9
|
@pbar = nil
|
11
10
|
end
|
12
11
|
|
13
12
|
def new_pbar(title, num_items)
|
14
|
-
if @
|
13
|
+
if @level != :quiet
|
15
14
|
@pbar = ProgressBar.create(:title => title, :total => num_items,
|
16
15
|
:format => PBAR_FORMAT, :length => 80)
|
17
16
|
end
|
@@ -22,15 +21,23 @@ class StarScope::Output
|
|
22
21
|
end
|
23
22
|
|
24
23
|
def finish_pbar
|
25
|
-
if @pbar
|
26
|
-
|
27
|
-
@pbar = nil
|
28
|
-
end
|
24
|
+
@pbar.finish if @pbar
|
25
|
+
@pbar = nil
|
29
26
|
end
|
30
27
|
|
31
28
|
def log(msg)
|
32
|
-
return if
|
29
|
+
return if @level != :verbose
|
30
|
+
output(msg)
|
31
|
+
end
|
32
|
+
|
33
|
+
def print(msg)
|
34
|
+
return if @level == :quiet
|
35
|
+
output(msg)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
33
39
|
|
40
|
+
def output(msg)
|
34
41
|
if @pbar
|
35
42
|
@pbar.log(msg)
|
36
43
|
else
|
data/lib/starscope/version.rb
CHANGED