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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9cc8ef281696106211f68e80221f662c49393bc0
4
- data.tar.gz: 489ce33b9f7a6db5c7206178e7bfdbbbc4d0f1ad
3
+ metadata.gz: fe59ab1f2e2cf5aaee13db7f085dc708323a3c50
4
+ data.tar.gz: 2d32eee21d9bc30996097deb13ac9c6fcb2363cb
5
5
  SHA512:
6
- metadata.gz: 1c1ebeefeb796053f86aeed6e774bdea8bc0848b6666ac0a26b37a06f39a0b2bdcaf0d90921685722dfcefbcf72feb8cc3de057f26ecf5fc34d6f1363eb4e146
7
- data.tar.gz: 896f6fd5750d5a41e4121e18e4f8b843bb8fae491b74408608f819202953df75e0d52919736e82a4d2442e642ffd76c2e5092a8e047cb79c47d5c6763c9ef96c
6
+ metadata.gz: 21d7d6d5d3cb0eb6b9f7903d54fdc90e1ac9abc34b1e0c6751e3814a664987bea98756a34c7f0d3b1fa08d78f61de9212b1714bfb38bccc48c3dc899ded71814
7
+ data.tar.gz: 5da04677144db25e80b40a96db6608f4ac32f1de8e2f7f36904726a7da0b6b1cff67df642df74a43b89419aa0bea9cb6ba731b98f5cd6244eac7917291ca41d5
@@ -1,5 +1,6 @@
1
- Lint/UnusedMethodArgument:
2
- Enabled: false
1
+ AllCops:
2
+ Exclude:
3
+ - 'test/fixtures/sample_ruby.rb'
3
4
 
4
5
  Metrics/AbcSize:
5
6
  Enabled: false
@@ -25,16 +26,9 @@ Metrics/ModuleLength:
25
26
  Metrics/PerceivedComplexity:
26
27
  Enabled: false
27
28
 
28
- Style/ClassAndModuleChildren:
29
- Enabled: false
30
-
31
29
  Style/Documentation:
32
30
  Enabled: false
33
31
 
34
- Style/Next:
35
- Exclude:
36
- - 'test/fixtures/sample_ruby.rb'
37
-
38
32
  Style/SpecialGlobalVars:
39
33
  Enabled: false
40
34
 
@@ -1,10 +1,10 @@
1
1
  language: ruby
2
2
  sudo: false
3
- cache: bundler
4
3
 
5
4
  rvm:
6
5
  - 1.9.3
7
6
  - 2.0
8
7
  - 2.1
9
8
  - 2.2
10
- - 2.3.0
9
+ - 2.3.3
10
+ - 2.4.0
@@ -1,7 +1,17 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
- Trunk
4
+ v1.5.4 (unreleased)
5
+ --------------------
6
+
7
+ Improvements:
8
+ * When dumping file metadata, don't include the file contents.
9
+
10
+ Bug Fixes:
11
+ * Fix parsing ruby files with invalidly-encoded literals (#160).
12
+ * Fix exporting ctags files to different output directories (#163).
13
+
14
+ v1.5.3 (2016-03-02)
5
15
  --------------------
6
16
 
7
17
  Improvements:
@@ -172,6 +172,8 @@ def dump(db, table)
172
172
  db.tables.each { |t| dump_table(db, t) }
173
173
  when '_meta'
174
174
  puts db.metadata
175
+ when '_files'
176
+ puts db.metadata(:files).keys
175
177
  when /^_/
176
178
  puts db.metadata(table[1..-1].to_sym)
177
179
  else
@@ -8,331 +8,333 @@ require 'starscope/fragment_extractor'
8
8
  require 'starscope/queryable'
9
9
  require 'starscope/output'
10
10
 
11
- class Starscope::DB
12
- include Starscope::Exportable
13
- include Starscope::Queryable
14
-
15
- DB_FORMAT = 5
16
- FRAGMENT = :'!fragment'
17
-
18
- # dynamically load all our language extractors
19
- Dir.glob("#{File.dirname(__FILE__)}/langs/*.rb").each { |path| require path }
20
-
21
- langs = {}
22
- extractors = []
23
- Starscope::Lang.constants.each do |lang|
24
- extractor = Starscope::Lang.const_get(lang)
25
- extractors << extractor
26
- langs[lang.to_sym] = extractor.const_get(:VERSION)
27
- end
28
- LANGS = langs.freeze
29
- EXTRACTORS = extractors.freeze
30
-
31
- class NoTableError < StandardError; end
32
- class UnknownDBFormatError < StandardError; end
33
-
34
- def initialize(output, config = {})
35
- @output = output
36
- @meta = { paths: [], files: {}, excludes: [],
37
- langs: LANGS.dup, version: Starscope::VERSION }
38
- @tables = {}
39
- @config = config
40
- end
11
+ module Starscope
12
+ class DB
13
+ include Starscope::Exportable
14
+ include Starscope::Queryable
15
+
16
+ DB_FORMAT = 5
17
+ FRAGMENT = :'!fragment'
18
+
19
+ # dynamically load all our language extractors
20
+ Dir.glob("#{File.dirname(__FILE__)}/langs/*.rb").each { |path| require path }
21
+
22
+ langs = {}
23
+ extractors = []
24
+ Starscope::Lang.constants.each do |lang|
25
+ extractor = Starscope::Lang.const_get(lang)
26
+ extractors << extractor
27
+ langs[lang.to_sym] = extractor.const_get(:VERSION)
28
+ end
29
+ LANGS = langs.freeze
30
+ EXTRACTORS = extractors.freeze
31
+
32
+ class NoTableError < StandardError; end
33
+ class UnknownDBFormatError < StandardError; end
34
+
35
+ def initialize(output, config = {})
36
+ @output = output
37
+ @meta = { paths: [], files: {}, excludes: [],
38
+ langs: LANGS.dup, version: Starscope::VERSION }
39
+ @tables = {}
40
+ @config = config
41
+ end
41
42
 
42
- # returns true iff the database was already in the most recent format
43
- def load(filename)
44
- @output.extra("Reading database from `#{filename}`... ")
45
- current_fmt = open_db(filename)
46
- fixup if current_fmt
47
- current_fmt
48
- end
43
+ # returns true iff the database was already in the most recent format
44
+ def load(filename)
45
+ @output.extra("Reading database from `#{filename}`... ")
46
+ current_fmt = open_db(filename)
47
+ fixup if current_fmt
48
+ current_fmt
49
+ end
49
50
 
50
- def save(filename)
51
- @output.extra("Writing database to `#{filename}`...")
51
+ def save(filename)
52
+ @output.extra("Writing database to `#{filename}`...")
52
53
 
53
- # regardless of what the old version was, the new version is written by us
54
- @meta[:version] = Starscope::VERSION
54
+ # regardless of what the old version was, the new version is written by us
55
+ @meta[:version] = Starscope::VERSION
55
56
 
56
- @meta[:langs].merge!(LANGS)
57
+ @meta[:langs].merge!(LANGS)
57
58
 
58
- File.open(filename, 'w') do |file|
59
- Zlib::GzipWriter.wrap(file) do |stream|
60
- stream.puts DB_FORMAT
61
- stream.puts Oj.dump @meta
62
- stream.puts Oj.dump @tables
59
+ File.open(filename, 'w') do |file|
60
+ Zlib::GzipWriter.wrap(file) do |stream|
61
+ stream.puts DB_FORMAT
62
+ stream.puts Oj.dump @meta
63
+ stream.puts Oj.dump @tables
64
+ end
63
65
  end
64
66
  end
65
- end
66
67
 
67
- def add_excludes(paths)
68
- @output.extra("Excluding files in paths #{paths}...")
69
- @meta[:paths] -= paths.map { |p| self.class.normalize_glob(p) }
70
- paths = paths.map { |p| self.class.normalize_fnmatch(p) }
71
- @meta[:excludes] += paths
72
- @meta[:excludes].uniq!
73
- @all_excludes = nil # clear cache
74
-
75
- excluded = @meta[:files].keys.select { |name| matches_exclude?(name, paths) }
76
- remove_files(excluded)
77
- end
68
+ def add_excludes(paths)
69
+ @output.extra("Excluding files in paths #{paths}...")
70
+ @meta[:paths] -= paths.map { |p| self.class.normalize_glob(p) }
71
+ paths = paths.map { |p| self.class.normalize_fnmatch(p) }
72
+ @meta[:excludes] += paths
73
+ @meta[:excludes].uniq!
74
+ @all_excludes = nil # clear cache
78
75
 
79
- def add_paths(paths)
80
- @output.extra("Adding files in paths #{paths}...")
81
- @meta[:excludes] -= paths.map { |p| self.class.normalize_fnmatch(p) }
82
- @all_excludes = nil # clear cache
83
- paths = paths.map { |p| self.class.normalize_glob(p) }
84
- @meta[:paths] += paths
85
- @meta[:paths].uniq!
86
- files = Dir.glob(paths).select { |f| File.file? f }
87
- files.delete_if { |f| matches_exclude?(f) }
88
- return if files.empty?
89
- @output.new_pbar('Building', files.length)
90
- add_files(files)
91
- @output.finish_pbar
92
- end
76
+ excluded = @meta[:files].keys.select { |name| matches_exclude?(name, paths) }
77
+ remove_files(excluded)
78
+ end
93
79
 
94
- def update
95
- changes = @meta[:files].keys.group_by { |name| file_changed(name) }
96
- changes[:modified] ||= []
97
- changes[:deleted] ||= []
80
+ def add_paths(paths)
81
+ @output.extra("Adding files in paths #{paths}...")
82
+ @meta[:excludes] -= paths.map { |p| self.class.normalize_fnmatch(p) }
83
+ @all_excludes = nil # clear cache
84
+ paths = paths.map { |p| self.class.normalize_glob(p) }
85
+ @meta[:paths] += paths
86
+ @meta[:paths].uniq!
87
+ files = Dir.glob(paths).select { |f| File.file? f }
88
+ files.delete_if { |f| matches_exclude?(f) }
89
+ return if files.empty?
90
+ @output.new_pbar('Building', files.length)
91
+ add_files(files)
92
+ @output.finish_pbar
93
+ end
98
94
 
99
- new_files = (Dir.glob(@meta[:paths]).select { |f| File.file? f }) - @meta[:files].keys
100
- new_files.delete_if { |f| matches_exclude?(f) }
95
+ def update
96
+ changes = @meta[:files].keys.group_by { |name| file_changed(name) }
97
+ changes[:modified] ||= []
98
+ changes[:deleted] ||= []
101
99
 
102
- if changes[:deleted].empty? && changes[:modified].empty? && new_files.empty?
103
- @output.normal('No changes detected.')
104
- return false
105
- end
100
+ new_files = (Dir.glob(@meta[:paths]).select { |f| File.file? f }) - @meta[:files].keys
101
+ new_files.delete_if { |f| matches_exclude?(f) }
106
102
 
107
- @output.new_pbar('Updating', changes[:modified].length + new_files.length)
108
- remove_files(changes[:deleted])
109
- update_files(changes[:modified])
110
- add_files(new_files)
111
- @output.finish_pbar
103
+ if changes[:deleted].empty? && changes[:modified].empty? && new_files.empty?
104
+ @output.normal('No changes detected.')
105
+ return false
106
+ end
112
107
 
113
- true
114
- end
108
+ @output.new_pbar('Updating', changes[:modified].length + new_files.length)
109
+ remove_files(changes[:deleted])
110
+ update_files(changes[:modified])
111
+ add_files(new_files)
112
+ @output.finish_pbar
115
113
 
116
- def line_for_record(rec)
117
- return rec[:line] if rec[:line]
114
+ true
115
+ end
118
116
 
119
- file = @meta[:files][rec[:file]]
117
+ def line_for_record(rec)
118
+ return rec[:line] if rec[:line]
120
119
 
121
- return file[:lines][rec[:line_no] - 1] if file[:lines]
122
- end
120
+ file = @meta[:files][rec[:file]]
123
121
 
124
- def tables
125
- @tables.keys
126
- end
122
+ return file[:lines][rec[:line_no] - 1] if file[:lines]
123
+ end
127
124
 
128
- def records(table)
129
- raise NoTableError unless @tables[table]
125
+ def tables
126
+ @tables.keys
127
+ end
130
128
 
131
- @tables[table]
132
- end
129
+ def records(table)
130
+ raise NoTableError unless @tables[table]
133
131
 
134
- def metadata(key = nil)
135
- return @meta.keys if key.nil?
132
+ @tables[table]
133
+ end
136
134
 
137
- raise NoTableError unless @meta[key]
135
+ def metadata(key = nil)
136
+ return @meta.keys if key.nil?
138
137
 
139
- @meta[key]
140
- end
138
+ raise NoTableError unless @meta[key]
141
139
 
142
- def drop_all
143
- @meta[:files] = {}
144
- @tables = {}
145
- end
140
+ @meta[key]
141
+ end
146
142
 
147
- private
143
+ def drop_all
144
+ @meta[:files] = {}
145
+ @tables = {}
146
+ end
148
147
 
149
- def open_db(filename)
150
- File.open(filename, 'r') do |file|
151
- begin
152
- Zlib::GzipReader.wrap(file) do |stream|
153
- parse_db(stream)
148
+ private
149
+
150
+ def open_db(filename)
151
+ File.open(filename, 'r') do |file|
152
+ begin
153
+ Zlib::GzipReader.wrap(file) do |stream|
154
+ parse_db(stream)
155
+ end
156
+ rescue Zlib::GzipFile::Error
157
+ file.rewind
158
+ parse_db(file)
154
159
  end
155
- rescue Zlib::GzipFile::Error
156
- file.rewind
157
- parse_db(file)
158
160
  end
159
161
  end
160
- end
161
162
 
162
- # returns true iff the database is in the most recent format
163
- def parse_db(stream)
164
- case stream.gets.to_i
165
- when DB_FORMAT
166
- @meta = Oj.load(stream.gets)
167
- @tables = Oj.load(stream.gets)
163
+ # returns true iff the database is in the most recent format
164
+ def parse_db(stream)
165
+ case stream.gets.to_i
166
+ when DB_FORMAT
167
+ @meta = Oj.load(stream.gets)
168
+ @tables = Oj.load(stream.gets)
169
+ return true
170
+ when 3..4
171
+ # Old format, so read the directories segment then rebuild
172
+ add_paths(Oj.load(stream.gets))
173
+ return false
174
+ when 0..2
175
+ # Old format (pre-json), so read the directories segment then rebuild
176
+ len = stream.gets.to_i
177
+ add_paths(Marshal.load(stream.read(len)))
178
+ return false
179
+ else
180
+ raise UnknownDBFormatError
181
+ end
182
+ rescue Oj::ParseError
183
+ stream.rewind
184
+ raise unless stream.gets.to_i == DB_FORMAT
185
+ # try reading as formated json, which is much slower, but it is sometimes
186
+ # useful to be able to directly read your db
187
+ objects = []
188
+ Oj.load(stream) { |obj| objects << obj }
189
+ @meta, @tables = objects
168
190
  return true
169
- when 3..4
170
- # Old format, so read the directories segment then rebuild
171
- add_paths(Oj.load(stream.gets))
172
- return false
173
- when 0..2
174
- # Old format (pre-json), so read the directories segment then rebuild
175
- len = stream.gets.to_i
176
- add_paths(Marshal.load(stream.read(len)))
177
- return false
178
- else
179
- raise UnknownDBFormatError
180
191
  end
181
- rescue Oj::ParseError
182
- stream.rewind
183
- raise unless stream.gets.to_i == DB_FORMAT
184
- # try reading as formated json, which is much slower, but it is sometimes
185
- # useful to be able to directly read your db
186
- objects = []
187
- Oj.load(stream) { |obj| objects << obj }
188
- @meta, @tables = objects
189
- return true
190
- end
191
-
192
- def fixup
193
- # misc things that were't worth bumping the format for, but which might not be written by old versions
194
- @meta[:langs] ||= {}
195
- end
196
-
197
- def all_excludes
198
- @all_excludes ||= @meta[:excludes] + (@config[:excludes] || []).map { |x| self.class.normalize_fnmatch(x) }
199
- end
200
-
201
- def matches_exclude?(file, patterns = all_excludes)
202
- patterns.map { |p| File.fnmatch(p, file) }.any?
203
- end
204
192
 
205
- def add_files(files)
206
- files.each do |file|
207
- @output.extra("Adding `#{file}`")
208
- parse_file(file)
209
- @output.inc_pbar
193
+ def fixup
194
+ # misc things that were't worth bumping the format for, but which might not be written by old versions
195
+ @meta[:langs] ||= {}
210
196
  end
211
- end
212
197
 
213
- def remove_files(files)
214
- files.each do |file|
215
- @output.extra("Removing `#{file}`")
216
- @meta[:files].delete(file)
217
- end
218
- files = files.to_set
219
- @tables.each do |_, tbl|
220
- tbl.delete_if { |val| files.include?(val[:file]) }
198
+ def all_excludes
199
+ @all_excludes ||= @meta[:excludes] + (@config[:excludes] || []).map { |x| self.class.normalize_fnmatch(x) }
221
200
  end
222
- end
223
-
224
- def update_files(files)
225
- remove_files(files)
226
- add_files(files)
227
- end
228
201
 
229
- def parse_file(file)
230
- @meta[:files][file] = { last_updated: File.mtime(file).to_i }
202
+ def matches_exclude?(file, patterns = all_excludes)
203
+ patterns.map { |p| File.fnmatch(p, file) }.any?
204
+ end
231
205
 
232
- self.class.extractors.each do |extractor|
233
- begin
234
- next unless extractor.match_file file
235
- rescue => e
236
- @output.normal("#{extractor} raised \"#{e}\" while matching #{file}")
237
- next
206
+ def add_files(files)
207
+ files.each do |file|
208
+ @output.extra("Adding `#{file}`")
209
+ parse_file(file)
210
+ @output.inc_pbar
238
211
  end
239
-
240
- line_cache = File.readlines(file)
241
- lines = Array.new(line_cache.length)
242
- @meta[:files][file][:sublangs] = []
243
- extract_file(extractor, file, line_cache, lines)
244
-
245
- break
246
212
  end
247
- end
248
213
 
249
- def extract_file(extractor, file, line_cache, lines)
250
- fragment_cache = {}
251
-
252
- extractor_metadata = extractor.extract(file, File.read(file)) do |tbl, name, args|
253
- case tbl
254
- when FRAGMENT
255
- fragment_cache[name] ||= []
256
- fragment_cache[name] << args
257
- else
258
- @tables[tbl] ||= []
259
- @tables[tbl] << self.class.normalize_record(file, name, args)
260
-
261
- if args[:line_no]
262
- line_cache ||= File.readlines(file)
263
- lines ||= Array.new(line_cache.length)
264
- lines[args[:line_no] - 1] = line_cache[args[:line_no] - 1].chomp
265
- end
214
+ def remove_files(files)
215
+ files.each do |file|
216
+ @output.extra("Removing `#{file}`")
217
+ @meta[:files].delete(file)
218
+ end
219
+ files = files.to_set
220
+ @tables.each do |_, tbl|
221
+ tbl.delete_if { |val| files.include?(val[:file]) }
266
222
  end
267
223
  end
268
224
 
269
- fragment_cache.each do |lang, frags|
270
- extract_file(Starscope::FragmentExtractor.new(lang, frags), file, line_cache, lines)
271
- @meta[:files][file][:sublangs] << lang
225
+ def update_files(files)
226
+ remove_files(files)
227
+ add_files(files)
272
228
  end
273
229
 
274
- @meta[:files][file][:lang] = extractor.name.split('::').last.to_sym
275
- @meta[:files][file][:lines] = lines
230
+ def parse_file(file)
231
+ @meta[:files][file] = { last_updated: File.mtime(file).to_i }
276
232
 
277
- if extractor_metadata.is_a? Hash
278
- @meta[:files][file] = extractor_metadata.merge!(@meta[:files][file])
279
- end
233
+ self.class.extractors.each do |extractor|
234
+ begin
235
+ next unless extractor.match_file file
236
+ rescue => e
237
+ @output.normal("#{extractor} raised \"#{e}\" while matching #{file}")
238
+ next
239
+ end
280
240
 
281
- rescue => e
282
- @output.normal("#{extractor} raised \"#{e}\" while extracting #{file}")
283
- end
241
+ line_cache = File.readlines(file)
242
+ lines = Array.new(line_cache.length)
243
+ @meta[:files][file][:sublangs] = []
244
+ extract_file(extractor, file, line_cache, lines)
284
245
 
285
- def file_changed(name)
286
- file_meta = @meta[:files][name]
287
- if matches_exclude?(name) || !File.exist?(name) || !File.file?(name)
288
- :deleted
289
- elsif (file_meta[:last_updated] < File.mtime(name).to_i) ||
290
- language_out_of_date(file_meta[:lang]) ||
291
- (file_meta[:sublangs] || []).any? { |lang| language_out_of_date(lang) }
292
- :modified
293
- else
294
- :unchanged
246
+ break
247
+ end
295
248
  end
296
- end
297
249
 
298
- def language_out_of_date(lang)
299
- return false unless lang
300
- return true unless LANGS[lang]
301
- (@meta[:langs][lang] || 0) < LANGS[lang]
302
- end
250
+ def extract_file(extractor, file, line_cache, lines)
251
+ fragment_cache = {}
252
+
253
+ extractor_metadata = extractor.extract(file, File.read(file)) do |tbl, name, args|
254
+ case tbl
255
+ when FRAGMENT
256
+ fragment_cache[name] ||= []
257
+ fragment_cache[name] << args
258
+ else
259
+ @tables[tbl] ||= []
260
+ @tables[tbl] << self.class.normalize_record(file, name, args)
261
+
262
+ if args[:line_no]
263
+ line_cache ||= File.readlines(file)
264
+ lines ||= Array.new(line_cache.length)
265
+ lines[args[:line_no] - 1] = line_cache[args[:line_no] - 1].chomp
266
+ end
267
+ end
268
+ end
303
269
 
304
- class << self
305
- # File.fnmatch treats a "**" to match files and directories recursively
306
- def normalize_fnmatch(path)
307
- if path == '.'
308
- '**'
309
- elsif File.directory?(path)
310
- File.join(path, '**')
311
- else
312
- path
270
+ fragment_cache.each do |lang, frags|
271
+ extract_file(Starscope::FragmentExtractor.new(lang, frags), file, line_cache, lines)
272
+ @meta[:files][file][:sublangs] << lang
313
273
  end
274
+
275
+ @meta[:files][file][:lang] = extractor.name.split('::').last.to_sym
276
+ @meta[:files][file][:lines] = lines
277
+
278
+ if extractor_metadata.is_a? Hash
279
+ @meta[:files][file] = extractor_metadata.merge!(@meta[:files][file])
280
+ end
281
+
282
+ rescue => e
283
+ @output.normal("#{extractor} raised \"#{e}\" while extracting #{file}")
314
284
  end
315
285
 
316
- # Dir.glob treats a "**" to only match directories recursively; you need
317
- # "**/*" to match all files recursively
318
- def normalize_glob(path)
319
- if path == '.'
320
- File.join('**', '*')
321
- elsif File.directory?(path)
322
- File.join(path, '**', '*')
286
+ def file_changed(name)
287
+ file_meta = @meta[:files][name]
288
+ if matches_exclude?(name) || !File.exist?(name) || !File.file?(name)
289
+ :deleted
290
+ elsif (file_meta[:last_updated] < File.mtime(name).to_i) ||
291
+ language_out_of_date(file_meta[:lang]) ||
292
+ (file_meta[:sublangs] || []).any? { |lang| language_out_of_date(lang) }
293
+ :modified
323
294
  else
324
- path
295
+ :unchanged
325
296
  end
326
297
  end
327
298
 
328
- def normalize_record(file, name, args)
329
- args[:file] = file
330
- args[:name] = Array(name).map(&:to_sym)
331
- args
299
+ def language_out_of_date(lang)
300
+ return false unless lang
301
+ return true unless LANGS[lang]
302
+ (@meta[:langs][lang] || 0) < LANGS[lang]
332
303
  end
333
304
 
334
- def extractors # so we can stub it in tests
335
- EXTRACTORS
305
+ class << self
306
+ # File.fnmatch treats a "**" to match files and directories recursively
307
+ def normalize_fnmatch(path)
308
+ if path == '.'
309
+ '**'
310
+ elsif File.directory?(path)
311
+ File.join(path, '**')
312
+ else
313
+ path
314
+ end
315
+ end
316
+
317
+ # Dir.glob treats a "**" to only match directories recursively; you need
318
+ # "**/*" to match all files recursively
319
+ def normalize_glob(path)
320
+ if path == '.'
321
+ File.join('**', '*')
322
+ elsif File.directory?(path)
323
+ File.join(path, '**', '*')
324
+ else
325
+ path
326
+ end
327
+ end
328
+
329
+ def normalize_record(file, name, args)
330
+ args[:file] = file
331
+ args[:name] = Array(name).map(&:to_sym)
332
+ args
333
+ end
334
+
335
+ def extractors # so we can stub it in tests
336
+ EXTRACTORS
337
+ end
336
338
  end
337
339
  end
338
340
  end