starscope 0.1.10 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -2,7 +2,6 @@ language: ruby
2
2
 
3
3
  rvm:
4
4
  - 1.8.7
5
- - 1.9.2
6
5
  - 1.9.3
7
6
  - 2.0.0
8
- - 2.1.0
7
+ - 2.1.1
data/CHANGELOG.md CHANGED
@@ -1,8 +1,33 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
- v0.1.10 (trunk)
5
- -------------------
4
+ v1.0.0 (trunk)
5
+ --------------------
6
+
7
+ New Features:
8
+ * Preliminary export of a few advanced ctags annotations
9
+ * New -x flag to exclude files from scan (such as compiled .o files)
10
+ * New --verbose flag for additional output
11
+
12
+ Bug Fixes:
13
+ * Correctly write out migrated databases
14
+ * Be compatible with ruby 1.8 everywhere
15
+ * Fix golang parsing untyped "var" declarations
16
+ * Fix golang parsing multi-line literals
17
+ * Record assignments to ruby constants as definitions
18
+ * Fix exporting to cscope when scanned files contain invalid unicode
19
+
20
+ Improvements:
21
+ * Faster file-type matching
22
+ * Reworked option flags:
23
+ * Merged -r and -w into -f
24
+ * Split -n into --no-read, --no-write, --no-update
25
+ * New, more flexible database format
26
+ * Substantially improved searching/matching logic
27
+ * Miscellanious others via updated dependencies
28
+
29
+ v0.1.10 (2014-02-24)
30
+ --------------------
6
31
 
7
32
  Improvements:
8
33
  * Import new ruby parser version and make necessary changes so that StarScope
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- starscope (0.1.10)
5
- oj (~> 2.5)
4
+ starscope (1.0.0)
5
+ oj (~> 2.7)
6
6
  parser (~> 2.1)
7
7
  ruby-progressbar (~> 1.4)
8
8
 
@@ -12,18 +12,18 @@ GEM
12
12
  ast (1.1.0)
13
13
  coderay (1.1.0)
14
14
  method_source (0.8.2)
15
- minitest (5.2.3)
16
- oj (2.5.5)
17
- parser (2.1.5)
15
+ minitest (5.3.3)
16
+ oj (2.7.3)
17
+ parser (2.1.7)
18
18
  ast (~> 1.1)
19
19
  slop (~> 3.4, >= 3.4.5)
20
20
  pry (0.9.12.6)
21
21
  coderay (~> 1.0)
22
22
  method_source (~> 0.8)
23
23
  slop (~> 3.4)
24
- rake (10.1.1)
25
- ruby-progressbar (1.4.1)
26
- slop (3.4.7)
24
+ rake (10.2.2)
25
+ ruby-progressbar (1.4.2)
26
+ slop (3.5.0)
27
27
 
28
28
  PLATFORMS
29
29
  ruby
data/README.md CHANGED
@@ -10,31 +10,28 @@ only works for C (and sort of works for C++).
10
10
 
11
11
  StarScope is a similar tool for [Ruby](https://www.ruby-lang.org/) and
12
12
  [Golang](http://golang.org/), with a design intended to make it easy to add
13
- support for other languages at some point within the same framework (thus the
14
- name StarScope, ie \*scope).
13
+ support for other languages within the same framework (thus the name StarScope,
14
+ i.e. \*scope).
15
15
 
16
16
  Install it as a gem:
17
17
  ```
18
18
  $ gem install starscope
19
19
  ```
20
20
 
21
- Build your database, by just running it in the project directory:
21
+ Build your database by just running it in the project directory:
22
22
  ```
23
23
  $ cd ~/my-project
24
24
  $ starscope
25
25
  ```
26
26
 
27
- Ask it things with `-q`
27
+ Ask it things directly:
28
28
  ```
29
29
  $ starscope -q calls,new # Lists all callers of new
30
30
  ```
31
31
 
32
- Export it for use with your editor
32
+ Export it to various formats for use with your editor:
33
33
  ```
34
34
  $ starscope -e ctags
35
- ```
36
- or
37
- ```
38
35
  $ starscope -e cscope
39
36
  ```
40
37
 
data/bin/starscope CHANGED
@@ -7,18 +7,30 @@ require 'optparse'
7
7
  require 'readline'
8
8
  require 'starscope'
9
9
 
10
- options = {auto: true, progress: true}
11
- DEFAULT_DB=".starscope.db"
10
+ DEFAULT_DB='.starscope.db'
11
+ DEFAULT_CTAGS='tags'
12
+ DEFAULT_CSCOPE='cscope.out'
13
+
14
+ options = {:show_progress => true,
15
+ :read => true,
16
+ :write => true,
17
+ :update => true,
18
+ :verbose => false,
19
+ :db => DEFAULT_DB
20
+ }
12
21
 
13
22
  # Options Parsing
14
23
  OptionParser.new do |opts|
15
24
  opts.banner = <<END
16
25
  Usage: starscope [options] [PATHS]
17
26
 
18
- If you don't pass any of -n, -r, -w or PATHS, default behaviour is to recurse
19
- in the current directory and build or update the database `#{DEFAULT_DB}`.
27
+ The default database is `#{DEFAULT_DB}` if you don't specify one with -f.
28
+ The default behaviour is to read and update the database.
29
+ If no database exists and no PATHS are specified, StarScope builds a new
30
+ database by recursing in the current directory.
20
31
 
21
- Query scopes must be specified with `::`, for example -q calls,File::mtime.
32
+ Scoped queries must use `::` as the scope separator, even for languages which
33
+ have their own scope syntax.
22
34
  END
23
35
 
24
36
  opts.separator "\nQueries"
@@ -39,14 +51,21 @@ END
39
51
  opts.on("-e", "--export FORMAT[,PATH]", "Export in FORMAT to PATH, (see EXPORTING)") do |export|
40
52
  options[:export] = export
41
53
  end
42
- opts.on("-n", "--no-auto", "Don't automatically update/create the DB") do
43
- options[:auto] = false
54
+ opts.on("-f", "--file FILE", "Use FILE instead of `#{DEFAULT_DB}`") do |path|
55
+ options[:db] = path
44
56
  end
45
- opts.on("-r", "--read-db PATH", "Reads the DB from PATH instead of default") do |path|
46
- options[:read] = path
57
+ opts.on("-x", "--exclude PATTERN", "Skip files matching PATTERN") do |pattern|
58
+ options[:exclude] ||= []
59
+ options[:exclude] << pattern
47
60
  end
48
- opts.on("-w", "--write-db PATH", "Writes the DB to PATH instead of default") do |path|
49
- options[:write] = path
61
+ opts.on("--no-read", "Don't read the DB from a file") do
62
+ options[:read] = false
63
+ end
64
+ opts.on("--no-write", "Don't write the DB to a file") do
65
+ options[:write] = false
66
+ end
67
+ opts.on("--no-update", "Don't update the DB") do
68
+ options[:update] = false
50
69
  end
51
70
 
52
71
  opts.separator "\nMisc"
@@ -54,22 +73,25 @@ END
54
73
  puts StarScope::VERSION
55
74
  exit
56
75
  end
76
+ opts.on("--verbose", "Print extra details") do
77
+ options[:verbose] = true
78
+ end
57
79
  opts.on("--no-progress", "Don't show progress-bar when updating DB") do
58
- options[:progress] = false
80
+ options[:show_progress] = false
59
81
  end
60
82
 
61
83
  opts.separator <<END
62
84
  \nEXPORTING
63
85
  At the moment two export formats are supported: 'ctags' and 'cscope'. If
64
- you don't specify a path, the output is written to the files 'tags' (for
65
- ctags) or 'cscope.out' (for cscope) in the current directory.
86
+ you don't specify a path, the output is written to the files '#{DEFAULT_CTAGS}' (for
87
+ ctags) or '#{DEFAULT_CSCOPE}' (for cscope) in the current directory.
66
88
  END
67
89
 
68
90
  end.parse!
69
91
 
70
92
  def print_summary(db)
71
93
  db.summary.each do |name, count|
72
- printf("%-8s %5d keys\n", name, count)
94
+ printf("%-8s %5d records\n", name, count)
73
95
  end
74
96
  end
75
97
 
@@ -78,9 +100,11 @@ def run_query(db, table, value)
78
100
  $stderr.puts "Invalid input - no query found."
79
101
  return false
80
102
  end
81
- key, results = db.query(table.to_sym, value)
103
+ results = db.query(table.to_sym, value)
82
104
  if results
83
- puts results.map {|val| StarScope::Datum.to_s(val)}
105
+ results.sort_by {|x| x[:name].join(' ')}.each do |rec|
106
+ puts StarScope::Record.format(rec)
107
+ end
84
108
  else
85
109
  puts "No results found."
86
110
  end
@@ -91,10 +115,12 @@ rescue StarScope::DB::NoTableError
91
115
  end
92
116
 
93
117
  def dump(db, table)
94
- if table
95
- db.dump_table(table.to_sym)
96
- else
118
+ if table.nil?
97
119
  db.dump_all
120
+ elsif table.start_with?('_')
121
+ db.dump_meta(table[1..-1].to_sym)
122
+ else
123
+ db.dump_table(table.to_sym)
98
124
  end
99
125
  return true
100
126
  rescue StarScope::DB::NoTableError
@@ -102,42 +128,49 @@ rescue StarScope::DB::NoTableError
102
128
  return false
103
129
  end
104
130
 
105
- if options[:auto] and not options[:write]
106
- options[:write] = DEFAULT_DB
107
- options[:auto_write] = true
108
- end
131
+ db = StarScope::DB.new(options[:show_progress], options[:verbose])
109
132
 
110
- if File.exists?(DEFAULT_DB) and not options[:read]
111
- options[:read] = DEFAULT_DB
112
- end
133
+ db_exists = File.exists?(options[:db])
113
134
 
114
- db = StarScope::DB.new(options[:progress])
135
+ if options[:read] and db_exists
136
+ new_data = db.load(options[:db])
137
+ else
138
+ # no need to run an update if we didn't read any old data
139
+ options[:update] = false
140
+ end
115
141
 
116
- if options[:read]
117
- db.load(options[:read])
118
- if !ARGV.empty?
119
- db.add_paths(ARGV)
120
- new_data = true
121
- end
122
- elsif ARGV.empty?
123
- db.add_paths(['.'])
142
+ if options[:exclude]
143
+ db.add_excludes(options[:exclude])
124
144
  new_data = true
125
- else
145
+ end
146
+
147
+ if not ARGV.empty?
148
+ # paths specified, add them
126
149
  db.add_paths(ARGV)
127
150
  new_data = true
151
+ elsif not (options[:read] and db_exists)
152
+ # no paths were specified and the database was not read or did not exist;
153
+ # default to building a new DB in the current directory
154
+ db.add_paths(['.'])
155
+ new_data = true
128
156
  end
129
157
 
130
- changed = db.update if options[:read] and options[:auto]
158
+ updated = db.update() if options[:update]
159
+ new_data ||= updated
131
160
 
132
- db.save(options[:write]) if options[:write] and (new_data or changed or not options[:auto_write])
161
+ db.save(options[:db]) if options[:write] and (new_data or not db_exists)
133
162
 
134
163
  if options[:export]
135
164
  format, path = options[:export].split(',', 2)
136
165
  case format
137
166
  when 'ctags'
138
- db.export_ctags(path || 'tags')
167
+ File.open(path || DEFAULT_CTAGS, 'w') do |file|
168
+ db.export_ctags(file)
169
+ end
139
170
  when 'cscope'
140
- db.export_cscope(path || 'cscope.out')
171
+ File.open(path || DEFAULT_CSCOPE, 'w') do |file|
172
+ db.export_cscope(file)
173
+ end
141
174
  else
142
175
  puts "Unrecognized export format"
143
176
  end
@@ -174,29 +207,32 @@ end
174
207
 
175
208
  if options[:linemode]
176
209
  puts "Run your query as 'TABLE QUERY' or run '!help' for more information."
177
- while input = Readline.readline("> ", true)
178
- cmd, param = input.split(' ', 2)
179
- if cmd[0] == '!'
180
- case cmd[1..-1]
181
- when "dump"
182
- dump(db, param)
183
- when "summary"
184
- print_summary(db)
185
- when "update"
186
- changed = db.update
187
- db.save(options[:write]) if options[:write] and changed
188
- when "help"
189
- puts linemode_help
190
- when "version"
191
- puts StarScope::VERSION
192
- when "quit"
193
- exit
210
+ begin
211
+ while input = Readline.readline("> ", true)
212
+ cmd, param = input.split(' ', 2)
213
+ if cmd[0] == '!'
214
+ case cmd[1..-1]
215
+ when "dump"
216
+ dump(db, param)
217
+ when "summary"
218
+ print_summary(db)
219
+ when "update"
220
+ changed = db.update
221
+ db.save(options[:write]) if options[:write] and changed
222
+ when "help"
223
+ puts linemode_help
224
+ when "version"
225
+ puts StarScope::VERSION
226
+ when "quit"
227
+ exit
228
+ else
229
+ puts "Unknown command: '#{input}', try '!help'."
230
+ end
194
231
  else
195
- puts "Unknown command: '#{input}', try '!help'."
232
+ success = run_query(db, cmd, param)
233
+ puts "Try '!help'." unless success
196
234
  end
197
- else
198
- success = run_query(db, cmd, param)
199
- puts "Try '!help'." unless success
200
235
  end
236
+ rescue Interrupt
201
237
  end
202
238
  end
data/lib/starscope/db.rb CHANGED
@@ -1,47 +1,56 @@
1
- require 'starscope/langs/go'
2
- require 'starscope/langs/ruby'
3
- require 'starscope/datum'
4
1
  require 'date'
5
2
  require 'oj'
6
3
  require 'zlib'
7
- require 'ruby-progressbar'
4
+
5
+ require 'starscope/matcher'
6
+ require 'starscope/output'
7
+ require 'starscope/record'
8
+
9
+ require 'starscope/langs/coffeescript'
10
+ require 'starscope/langs/go'
11
+ require 'starscope/langs/lua'
12
+ require 'starscope/langs/ruby'
8
13
 
9
14
  LANGS = [
15
+ StarScope::Lang::CoffeeScript,
10
16
  StarScope::Lang::Go,
17
+ StarScope::Lang::Lua,
11
18
  StarScope::Lang::Ruby
12
19
  ]
13
20
 
14
21
  class StarScope::DB
15
22
 
16
- DB_FORMAT = 4
17
- PBAR_FORMAT = '%t: %c/%C %E ||%b>%i||'
23
+ DB_FORMAT = 5
18
24
 
19
25
  class NoTableError < StandardError; end
20
26
  class UnknownDBFormatError < StandardError; end
21
27
 
22
- def initialize(progress)
23
- @progress = progress
24
- @paths = []
25
- @files = {}
28
+ def initialize(progress, verbose)
29
+ @output = StarScope::Output.new(progress, verbose)
30
+ @meta = {:paths => [], :files => {}, :excludes => []}
26
31
  @tables = {}
27
32
  end
28
33
 
34
+ # returns true if the database had to be up-converted from an old format
29
35
  def load(file)
36
+ @output.log("Reading database from `#{file}`... ")
30
37
  File.open(file, 'r') do |file|
31
38
  Zlib::GzipReader.wrap(file) do |file|
32
39
  format = file.gets.to_i
33
40
  if format == DB_FORMAT
34
- @paths = Oj.load(file.gets)
35
- @files = Oj.load(file.gets)
36
- @tables = Oj.load(file.gets, :symbol_keys => true)
41
+ @meta = Oj.load(file.gets)
42
+ @tables = Oj.load(file.gets)
43
+ return false
37
44
  elsif format <= 2
38
45
  # Old format (pre-json), so read the directories segment then rebuild
39
46
  len = file.gets.to_i
40
47
  add_paths(Marshal::load(file.read(len)))
41
- elsif format < DB_FORMAT
48
+ return true
49
+ elsif format <= 4
42
50
  # Old format, so read the directories segment then rebuild
43
51
  add_paths(Oj.load(file.gets))
44
- elsif format > DB_FORMAT
52
+ return true
53
+ else
45
54
  raise UnknownDBFormatError
46
55
  end
47
56
  end
@@ -49,57 +58,85 @@ class StarScope::DB
49
58
  end
50
59
 
51
60
  def save(file)
61
+ @output.log("Writing database to `#{file}`...")
52
62
  File.open(file, 'w') do |file|
53
63
  Zlib::GzipWriter.wrap(file) do |file|
54
64
  file.puts DB_FORMAT
55
- file.puts Oj.dump @paths
56
- file.puts Oj.dump @files
65
+ file.puts Oj.dump @meta
57
66
  file.puts Oj.dump @tables
58
67
  end
59
68
  end
60
69
  end
61
70
 
71
+ def add_excludes(paths)
72
+ @meta[:paths] -= paths.map {|p| normalize_glob(p)}
73
+ paths = paths.map {|p| normalize_fnmatch(p)}
74
+ @meta[:excludes] += paths
75
+ @meta[:excludes].uniq!
76
+ @meta[:files].delete_if do |name, record|
77
+ if matches_exclude(paths, name)
78
+ remove_file(name)
79
+ true
80
+ else
81
+ false
82
+ end
83
+ end
84
+ end
85
+
62
86
  def add_paths(paths)
63
- paths -= @paths
64
- return if paths.empty?
65
- @paths += paths
66
- files = paths.map {|p| self.class.files_from_path(p)}.flatten
87
+ @meta[:excludes] -= paths.map {|p| normalize_fnmatch(p)}
88
+ paths = paths.map {|p| normalize_glob(p)}
89
+ @meta[:paths] += paths
90
+ @meta[:paths].uniq!
91
+ files = Dir.glob(paths).select {|f| File.file? f}
92
+ files.delete_if {|f| matches_exclude(@meta[:excludes], f)}
67
93
  return if files.empty?
68
- if @progress
69
- pbar = ProgressBar.create(:title => "Building", :total => files.length, :format => PBAR_FORMAT, :length => 80)
70
- end
71
- files.each do |f|
72
- add_file(f)
73
- pbar.increment if @progress
74
- end
94
+ @output.new_pbar("Building", files.length)
95
+ add_new_files(files)
96
+ @output.finish_pbar
75
97
  end
76
98
 
77
99
  def update
78
- new_files = (@paths.map {|p| self.class.files_from_path(p)}.flatten) - @files.keys
79
- if @progress
80
- pbar = ProgressBar.create(:title => "Updating", :total => new_files.length + @files.length, :format => PBAR_FORMAT, :length => 80)
81
- end
82
- changed = @files.keys.map do |f|
83
- changed = update_file(f)
84
- pbar.increment if @progress
85
- changed
86
- end
87
- new_files.each do |f|
88
- add_file(f)
89
- pbar.increment if @progress
100
+ new_files = (Dir.glob(@meta[:paths]).select {|f| File.file? f}) - @meta[:files].keys
101
+ new_files.delete_if {|f| matches_exclude(@meta[:excludes], f)}
102
+ @output.new_pbar("Updating", new_files.length + @meta[:files].length)
103
+ changed = false
104
+ @meta[:files].delete_if do |name, record|
105
+ @output.log("Updating `#{name}`")
106
+ ret = update_file(name)
107
+ @output.inc_pbar
108
+ changed = true if ret == :update
109
+ ret == :delete
90
110
  end
91
- changed.any? || !new_files.empty?
111
+ add_new_files(new_files)
112
+ @output.finish_pbar
113
+ changed || !new_files.empty?
92
114
  end
93
115
 
94
116
  def dump_table(table)
95
117
  raise NoTableError if not @tables[table]
96
118
  puts "== Table: #{table} =="
97
- @tables[table].sort_by{|k,v| k.downcase}.each do |val, data|
98
- puts "#{val}"
99
- data.each do |datum|
100
- print "\t"
101
- puts StarScope::Datum.to_s(datum)
119
+ @tables[table].sort {|a,b|
120
+ a[:name][-1].to_s.downcase <=> b[:name][-1].to_s.downcase
121
+ }.each do |record|
122
+ puts StarScope::Record.format(record)
123
+ end
124
+ end
125
+
126
+ def dump_meta(key)
127
+ if key == :meta
128
+ puts "== Metadata Summary =="
129
+ @meta.each do |k, v|
130
+ puts "#{k}: #{v.count}"
102
131
  end
132
+ return
133
+ end
134
+ raise NoTableError if not @meta[key]
135
+ puts "== Metadata: #{key} =="
136
+ if @meta[key].is_a? Array
137
+ @meta[key].sort.each {|x| puts x}
138
+ else
139
+ @meta[key].sort.each {|k,v| puts "#{k}: #{v}"}
103
140
  end
104
141
  end
105
142
 
@@ -111,74 +148,56 @@ class StarScope::DB
111
148
  ret = {}
112
149
 
113
150
  @tables.each_key do |key|
114
- ret[key] = @tables[key].keys.count
151
+ ret[key] = @tables[key].count
115
152
  end
116
153
 
117
154
  ret
118
155
  end
119
156
 
120
157
  def query(table, value)
121
- fqn = value.split("::")
122
158
  raise NoTableError if not @tables[table]
123
- key = fqn.last
124
- results = @tables[table][key.to_sym] || []
125
- if results.empty?
126
- matcher = Regexp.new(key, Regexp::IGNORECASE)
127
- @tables[table].each do |k,v|
128
- if matcher.match(k)
129
- results << v
130
- end
131
- end
132
- results.flatten!
133
- end
134
- return results if results.empty?
135
- results.sort! do |a,b|
136
- StarScope::Datum.score_match(b, fqn) <=> StarScope::Datum.score_match(a, fqn)
137
- end
138
- best_score = StarScope::Datum.score_match(results[0], fqn)
139
- results = results.select do |result|
140
- best_score - StarScope::Datum.score_match(result, fqn) < 4
141
- end
142
- return fqn.last.to_sym, results
159
+ input = @tables[table]
160
+ StarScope::Matcher.new(value, input).query()
143
161
  end
144
162
 
145
- def export_ctags(filename)
146
- File.open(filename, 'w') do |file|
147
- file.puts <<END
148
- !_TAG_FILE_FORMAT 2 //
163
+ def export_ctags(file)
164
+ file.puts <<END
165
+ !_TAG_FILE_FORMAT 2 /extended format/
149
166
  !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
150
- !_TAG_PROGRAM_AUTHOR Evan Huus //
151
- !_TAG_PROGRAM_NAME Starscope //
167
+ !_TAG_PROGRAM_AUTHOR Evan Huus /eapache@gmail.com/
168
+ !_TAG_PROGRAM_NAME StarScope //
152
169
  !_TAG_PROGRAM_URL https://github.com/eapache/starscope //
153
170
  !_TAG_PROGRAM_VERSION #{StarScope::VERSION} //
154
171
  END
155
- defs = (@tables[:defs] || {}).sort
156
- defs.each do |key, val|
157
- val.each do |entry|
158
- file.puts StarScope::Datum.ctag_line(entry)
159
- end
160
- end
172
+ defs = (@tables[:defs] || {}).sort_by {|x| x[:name][-1].to_s}
173
+ defs.each do |record|
174
+ file.puts StarScope::Record.ctag_line(record)
161
175
  end
162
176
  end
163
177
 
164
178
  # ftp://ftp.eeng.dcu.ie/pub/ee454/cygwin/usr/share/doc/mlcscope-14.1.8/html/cscope.html
165
- def export_cscope(filename)
179
+ def export_cscope(file)
166
180
  buf = ""
167
181
  files = []
168
- db_by_line().each do |file, lines|
182
+ db_by_line().each do |filename, lines|
169
183
  if not lines.empty?
170
- buf << "\t@#{file}\n\n"
171
- files << file
184
+ buf << "\t@#{filename}\n\n"
185
+ files << filename
172
186
  end
173
- lines.sort.each do |line_no, vals|
174
- line = vals.first[:entry][:line].strip.gsub(/\s+/, ' ')
187
+ lines.sort.each do |line_no, records|
188
+ begin
189
+ line = records.first[:line].strip.gsub(/\s+/, ' ')
190
+ rescue ArgumentError
191
+ # invalid utf-8 byte sequence in the line, just do our best
192
+ line = records.first[:line]
193
+ end
175
194
  toks = {}
176
195
 
177
- vals.each do |val|
178
- index = line.index(val[:key].to_s)
196
+ records.each do |record|
197
+ index = line.index(record[:name][-1].to_s)
179
198
  while index
180
- toks[index] = val
181
- index = line.index(val[:key].to_s, index + 1)
199
+ toks[index] = record
200
+ index = line.index(record[:name][-1].to_s, index + 1)
182
201
  end
183
202
  end
184
203
 
@@ -186,11 +205,11 @@ END
186
205
 
187
206
  prev = 0
188
207
  buf << line_no.to_s << " "
189
- toks.sort().each do |offset, val|
208
+ toks.sort().each do |offset, record|
190
209
  buf << line.slice(prev...offset) << "\n"
191
- buf << StarScope::Datum.cscope_mark(val[:tbl], val[:entry])
192
- buf << val[:key].to_s << "\n"
193
- prev = offset + val[:key].to_s.length
210
+ buf << StarScope::Record.cscope_mark(record[:tbl], record)
211
+ buf << record[:name][-1].to_s << "\n"
212
+ prev = offset + record[:name][-1].to_s.length
194
213
  end
195
214
  buf << line.slice(prev..-1) << "\n\n"
196
215
  end
@@ -199,86 +218,100 @@ END
199
218
  buf << "\t@\n"
200
219
 
201
220
  header = "cscope 15 #{Dir.pwd} -c "
202
- offset = "%010d\n" % (header.length + 11 + buf.length)
203
-
204
- File.open(filename, 'w') do |file|
205
- file.print(header)
206
- file.print(offset)
207
- file.print(buf)
208
-
209
- file.print("#{@paths.length}\n")
210
- @paths.each {|p| file.print("#{p}\n")}
211
- file.print("0\n")
212
- file.print("#{files.length}\n")
213
- buf = ""
214
- files.each {|f| buf << f + "\n"}
215
- file.print("#{buf.length}\n#{buf}")
216
- end
221
+ offset = "%010d\n" % (header.length + 11 + buf.bytes.to_a.length)
222
+
223
+ file.print(header)
224
+ file.print(offset)
225
+ file.print(buf)
226
+
227
+ file.print("#{@meta[:paths].length}\n")
228
+ @meta[:paths].each {|p| file.print("#{p}\n")}
229
+ file.print("0\n")
230
+ file.print("#{files.length}\n")
231
+ buf = ""
232
+ files.each {|f| buf << f + "\n"}
233
+ file.print("#{buf.length}\n#{buf}")
217
234
  end
218
235
 
219
236
  private
220
237
 
221
- def self.files_from_path(path)
222
- if File.file?(path)
223
- [path]
238
+ def add_new_files(files)
239
+ files.each do |file|
240
+ @output.log("Adding `#{file}`")
241
+ @meta[:files][file] = {}
242
+ parse_file(file)
243
+ @output.inc_pbar
244
+ end
245
+ end
246
+
247
+ # File.fnmatch treats a "**" to match files and directories recursively
248
+ def normalize_fnmatch(path)
249
+ if path == "."
250
+ "**"
251
+ elsif File.directory?(path)
252
+ File.join(path, "**")
253
+ else
254
+ path
255
+ end
256
+ end
257
+
258
+ # Dir.glob treats a "**" to only match directories recursively; you need
259
+ # "**/*" to match all files recursively
260
+ def normalize_glob(path)
261
+ if path == "."
262
+ File.join("**", "*")
224
263
  elsif File.directory?(path)
225
- Dir[File.join(path, "**", "*")].select {|p| File.file?(p)}
264
+ File.join(path, "**", "*")
226
265
  else
227
- []
266
+ path
228
267
  end
229
268
  end
230
269
 
231
270
  def db_by_line()
232
- tmpdb = {}
233
- @tables.each do |tbl, vals|
234
- vals.each do |key, val|
235
- val.each do |entry|
236
- if entry[:line_no]
237
- tmpdb[entry[:file]] ||= {}
238
- tmpdb[entry[:file]][entry[:line_no]] ||= []
239
- tmpdb[entry[:file]][entry[:line_no]] << {:tbl => tbl, :key => key, :entry => entry}
240
- end
241
- end
271
+ db = {}
272
+ @tables.each do |tbl, records|
273
+ records.each do |record|
274
+ next if not record[:line_no]
275
+ record[:tbl] = tbl
276
+ db[record[:file]] ||= {}
277
+ db[record[:file]][record[:line_no]] ||= []
278
+ db[record[:file]][record[:line_no]] << record
242
279
  end
243
280
  end
244
- return tmpdb
281
+ return db
245
282
  end
246
283
 
247
- def add_file(file)
248
- return if not File.file? file
284
+ def matches_exclude(patterns, file)
285
+ patterns.map {|p| File.fnmatch(p, file)}.any?
286
+ end
249
287
 
250
- @files[file] = File.mtime(file).to_s
288
+ def parse_file(file)
289
+ @meta[:files][file][:last_updated] = File.mtime(file).to_i
251
290
 
252
291
  LANGS.each do |lang|
253
292
  next if not lang.match_file file
254
- lang.extract file do |tbl, key, args|
255
- key = key.to_sym
256
- @tables[tbl] ||= {}
257
- @tables[tbl][key] ||= []
258
- @tables[tbl][key] << StarScope::Datum.build(file, key, args)
293
+ lang.extract file do |tbl, name, args|
294
+ @tables[tbl] ||= []
295
+ @tables[tbl] << StarScope::Record.build(file, name, args)
259
296
  end
297
+ @meta[:files][file][:lang] = lang.name.split('::').last.to_sym
260
298
  end
261
299
  end
262
300
 
263
301
  def remove_file(file)
264
- @files.delete(file)
265
302
  @tables.each do |name, tbl|
266
- tbl.each do |key, val|
267
- val.delete_if {|dat| dat[:file] == file}
268
- end
303
+ tbl.delete_if {|val| val[:file] == file}
269
304
  end
270
305
  end
271
306
 
272
307
  def update_file(file)
273
308
  if not File.exists?(file) or not File.file?(file)
274
309
  remove_file(file)
275
- true
276
- elsif DateTime.parse(@files[file]).to_time.to_i < File.mtime(file).to_i
310
+ :delete
311
+ elsif @meta[:files][file][:last_updated] < File.mtime(file).to_i
277
312
  remove_file(file)
278
- add_file(file)
279
- true
280
- else
281
- false
313
+ parse_file(file)
314
+ :update
282
315
  end
283
316
  end
284
317