starscope 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
- require 'starscope/langs/coffeescript'
10
- require 'starscope/langs/go'
11
- require 'starscope/langs/ruby'
12
-
13
- LANGS = [
14
- StarScope::Lang::CoffeeScript,
15
- StarScope::Lang::Go,
16
- StarScope::Lang::Ruby
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(progress, verbose)
27
- @output = StarScope::Output.new(progress, verbose)
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
- deleted = []
81
- @meta[:files].delete_if do |name, record|
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
- new_files = (Dir.glob(@meta[:paths]).select {|f| File.file? f}) - @meta[:files].keys
104
- new_files.delete_if {|f| matches_exclude(@meta[:excludes], f)}
107
+ changes = @meta[:files].keys.group_by {|name| file_changed(name)}
108
+ changes[:modified] ||= []
109
+ changes[:deleted] ||= []
105
110
 
106
- @output.new_pbar("Updating", new_files.length + @meta[:files].length)
107
- changed = false
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
- @output.inc_pbar
122
- event == :deleted
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
- @tables, tmp_tbls = tmp_tbls, @tables
126
- remove_files(to_prune)
127
- merge_db(tmp_tbls)
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
- changed || !new_files.empty?
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
- begin
215
- line = records.first[:line].strip.gsub(/\s+/, ' ')
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.sort.each do |offset, record|
235
- key = record[:name][-1].to_s
236
- buf << line.slice(prev...offset) << "\n"
237
- buf << StarScope::Record.cscope_mark(record[:tbl], record) << key << "\n"
238
- prev = offset + key.length
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.slice(prev..-1) << "\n\n"
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 matches_exclude(patterns, file)
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][:last_updated] = File.mtime(file).to_i
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 merge_db(new_tbls)
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 record[:last_updated] < File.mtime(name).to_i
396
+ elsif @meta[:files][name][:last_updated] < File.mtime(name).to_i
345
397
  :modified
346
398
  else
347
399
  :unchanged
@@ -1,5 +1,5 @@
1
1
  module StarScope::Lang
2
- module CoffeeScript
2
+ module Coffeescript
3
3
 
4
4
  def self.match_file(name)
5
5
  name.end_with?(".coffee")
@@ -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)
@@ -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.expression.line
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.expression.line
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.expression.line, :type => :func
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.expression.line, :type => :func
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.expression.line, :type => node.type
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.expression.line
82
- yield :defs, fqn, :line_no => loc.expression.line
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.expression.line
85
+ yield :assigns, @scope + [node.children[0]], :line_no => loc.line, :col => loc.name.column
86
86
  end
87
87
  end
88
88
 
@@ -4,14 +4,13 @@ class StarScope::Output
4
4
 
5
5
  PBAR_FORMAT = '%t: %c/%C %E ||%b>%i||'
6
6
 
7
- def initialize(progress, verbose)
8
- @progress = progress
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 @progress
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
- @pbar.finish
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 not @verbose
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
@@ -1,3 +1,3 @@
1
1
  module StarScope
2
- VERSION = "1.0.4"
2
+ VERSION = "1.1.0"
3
3
  end