reapack-index 1.0beta2 → 1.0beta3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -21,13 +21,16 @@ gem install reapack-index
21
21
  ### Usage
22
22
 
23
23
  ```
24
- reascript-indexer [options] [path-to-your-reascript-repository]
24
+ reapack-index [options] [path-to-your-repository]
25
25
  ```
26
26
 
27
27
  ```
28
28
  Options:
29
29
  -a, --[no-]amend Reindex existing versions
30
30
  -c, --check Test every package including uncommited changes and exit
31
+ -i, --ignore PATH Don't check or index any file starting with PATH
32
+ -n, --name NAME Set the name shown in ReaPack for this repository
33
+ -U, --url-template TEMPLATE=auto Set the template for implicit download links
31
34
  -o, --output FILE=./index.xml Set the output filename and path for the index
32
35
  -l, --link LINK Add or remove a website link
33
36
  --donation-link LINK Add or remove a donation link
@@ -64,12 +67,13 @@ This indexer uses metadata found at the start of the files to generate the
64
67
  database in ReaPack format.
65
68
  See also [MetaHeader](https://github.com/cfillion/metaheader)'s documentation.
66
69
 
67
- Tag not explicitly marked as required are optional.
70
+ All tags are optional unless explicitely marked as required.
68
71
 
69
72
  ### Package type by extension:
70
73
 
71
- - `.lua`, `.eel`, `.py`: ReaScripts – the package file itself will be used as a source.
72
- - `.ext`: For REAPER native extensions
74
+ - `.lua`, `.eel`, `.py`: ReaScripts
75
+ - `.jsfx`: JS effects
76
+ - `.ext`: Native extensions (all included files must be explicitely defined)
73
77
 
74
78
  ### Package Tags
75
79
 
data/Rakefile CHANGED
@@ -4,4 +4,5 @@ require 'rake/testtask'
4
4
  task :default => [:test]
5
5
  Rake::TestTask.new do |t|
6
6
  t.test_files = FileList['test/test_*.rb']
7
+ t.warning = false
7
8
  end
@@ -1,7 +1,9 @@
1
1
  require 'reapack/index/gem_version'
2
2
 
3
+ require 'addressable'
3
4
  require 'colorize'
4
5
  require 'fileutils'
6
+ require 'gitable'
5
7
  require 'io/console'
6
8
  require 'metaheader'
7
9
  require 'nokogiri'
@@ -11,9 +13,9 @@ require 'pathname'
11
13
  require 'rugged'
12
14
  require 'shellwords'
13
15
  require 'time'
14
- require 'uri'
15
16
 
16
17
  require 'reapack/index/cli'
18
+ require 'reapack/index/cli/options'
17
19
  require 'reapack/index/metadata'
18
20
  require 'reapack/index/named_node'
19
21
  require 'reapack/index/package'
@@ -24,12 +26,24 @@ class ReaPack::Index
24
26
  Error = Class.new RuntimeError
25
27
 
26
28
  FILE_TYPES = {
27
- 'lua' => :script,
28
- 'eel' => :script,
29
- 'py' => :script,
30
- 'ext' => :extension,
29
+ 'lua' => :script,
30
+ 'eel' => :script,
31
+ 'py' => :script,
32
+ 'ext' => :extension,
33
+ 'jsfx' => :effect,
31
34
  }.freeze
32
35
 
36
+ WITH_MAIN = [:script, :effect].freeze
37
+
38
+ PROVIDES_REGEX = /
39
+ \A
40
+ ( \[ \s* (?<platform> .+? ) \s* \] )?
41
+ \s*
42
+ (?<file> .+?)
43
+ ( \s+ (?<url> (?:file|https?):\/\/.+ ) )?
44
+ \z
45
+ /x.freeze
46
+
33
47
  PROVIDES_VALIDATOR = proc {|value|
34
48
  begin
35
49
  files = value.lines.map {|l|
@@ -54,27 +68,9 @@ class ReaPack::Index
54
68
  :provides => [MetaHeader::OPTIONAL, PROVIDES_VALIDATOR]
55
69
  }.freeze
56
70
 
57
- SOURCE_PATTERNS = {
58
- /\Agit@github\.com:([^\/]+)\/(.+)\.git\z/ =>
59
- 'https://github.com/\1/\2/raw/$commit/$path',
60
- /\Ahttps:\/\/github\.com\/([^\/]+)\/(.+)\.git\z/ =>
61
- 'https://github.com/\1/\2/raw/$commit/$path',
62
- }.freeze
63
-
64
- ROOT = File.expand_path('/').freeze
65
-
66
- PROVIDES_REGEX = /
67
- \A
68
- ( \[ \s* (?<platform> .+? ) \s* \] )?
69
- \s*
70
- (?<file> .+?)
71
- ( \s+ (?<url> (?:file|https?):\/\/.+ ) )?
72
- \z
73
- /x.freeze
74
-
75
- WITH_MAIN = [:script].freeze
71
+ FS_ROOT = File.expand_path('/').freeze
76
72
 
77
- attr_reader :path, :source_pattern
73
+ attr_reader :path, :url_template
78
74
  attr_accessor :amend, :files, :time
79
75
 
80
76
  def self.type_of(path)
@@ -82,14 +78,6 @@ class ReaPack::Index
82
78
  FILE_TYPES[ext]
83
79
  end
84
80
 
85
- def self.source_for(url)
86
- SOURCE_PATTERNS.each_pair {|regex, pattern|
87
- return url.gsub regex, pattern if url =~ regex
88
- }
89
-
90
- nil
91
- end
92
-
93
81
  def self.validate_file(path)
94
82
  mh = MetaHeader.from_file path
95
83
  return if mh[:noindex]
@@ -106,7 +94,7 @@ class ReaPack::Index
106
94
  if File.exist? path
107
95
  # noblanks: don't preserve the original white spaces
108
96
  # so we always output a neat document
109
- @doc = Nokogiri::XML File.open(path), &:noblanks
97
+ @doc = File.open(path) {|file| Nokogiri::XML file, &:noblanks }
110
98
  else
111
99
  @dirty = true
112
100
  @is_new = true
@@ -146,6 +134,9 @@ class ReaPack::Index
146
134
  pkg.version mh[:version] do |ver|
147
135
  next unless ver.is_new? || @amend
148
136
 
137
+ # store the version name for make_url
138
+ @currentVersion = ver.name
139
+
149
140
  ver.author = mh[:author]
150
141
  ver.time = @time if @time
151
142
  ver.changelog = mh[:changelog]
@@ -155,16 +146,10 @@ class ReaPack::Index
155
146
 
156
147
  if WITH_MAIN.include?(type) && sources.none? {|src| src.file.nil? }
157
148
  # add the package itself as a source
158
- sources.unshift Source.new nil, nil, url_for(path)
149
+ sources.unshift Source.new nil, nil, make_url(path)
159
150
  end
160
151
 
161
- sources.each {|src|
162
- # the $path variable is interpolated elsewhere
163
- # (in url_for for generated urls and make_sources for explicit urls)
164
- src.url.sub! '$commit', commit || 'master'
165
- src.url.sub! '$version', ver.name
166
- ver.add_source src
167
- }
152
+ sources.each {|src| ver.add_source src }
168
153
  end
169
154
  end
170
155
 
@@ -229,20 +214,43 @@ class ReaPack::Index
229
214
  log_change 'modified metadata' if old != @metadata.description
230
215
  end
231
216
 
232
- def source_pattern=(pattern)
233
- if pattern.nil?
234
- raise ArgumentError, 'Cannot use nil as a source pattern'
235
- elsif not pattern.include? '$path'
236
- raise ArgumentError, '$path not in source pattern'
217
+ def url_template=(tpl)
218
+ return @url_template = nil if tpl.nil?
219
+
220
+ uri = Addressable::URI.parse tpl
221
+ uri.normalize!
222
+
223
+ unless (uri.request_uri || uri.path).include? '$path'
224
+ raise Error, "$path placeholder is missing: #{tpl}"
237
225
  end
238
226
 
239
- @source_pattern = pattern
227
+ unless %w{http https file}.include? uri.scheme
228
+ raise Addressable::URI::InvalidURIError
229
+ end
230
+
231
+ @url_template = uri.to_s.freeze
232
+ rescue Addressable::URI::InvalidURIError
233
+ raise Error, "invalid URL or scheme: #{tpl}"
240
234
  end
241
235
 
242
236
  def version
243
237
  @doc.root[:version].to_i
244
238
  end
245
239
 
240
+ def name
241
+ @doc.root[:name].to_s
242
+ end
243
+
244
+ def name=(newName)
245
+ if !/\A[^~#%&*{}\\:<>?\/+|"]+\Z/.match(newName) || /\A\.+\Z/.match(newName)
246
+ raise Error, "Invalid name: '#{newName}'"
247
+ end
248
+
249
+ oldName = name
250
+ @doc.root['name'] = newName
251
+ log_change 'modified metadata' if oldName != newName
252
+ end
253
+
246
254
  def commit
247
255
  @doc.root[:commit]
248
256
  end
@@ -256,6 +264,9 @@ class ReaPack::Index
256
264
  end
257
265
 
258
266
  def write(path)
267
+ @doc.root.element_children.each {|n| sort n if n.name == 'category' }
268
+ sort @doc.root
269
+
259
270
  FileUtils.mkdir_p File.dirname(path)
260
271
  File.write path, @doc.to_xml
261
272
  end
@@ -307,45 +318,47 @@ private
307
318
  [cat, pkg]
308
319
  end
309
320
 
310
- def url_for(path)
311
- unless @source_pattern
312
- raise Error, "source pattern is unset " \
313
- "and the package doesn't specify its source url"
314
- end
321
+ def make_url(path, template = nil)
322
+ if template.nil?
323
+ raise Error, 'unable to generate a download link' \
324
+ ' the url template is unset' unless @url_template
315
325
 
316
- unless @files.include? path
317
- raise Error, "#{path}: No such file or directory"
326
+ raise Error, "#{path}: No such file or directory" unless @files.include? path
318
327
  end
319
328
 
320
- # other variables are interpolated in scan()
321
- @source_pattern.sub('$path', path)
329
+ (template || @url_template)
330
+ .sub('$path', path)
331
+ .sub('$commit', commit || 'master')
332
+ .sub('$version', @currentVersion || '0.0')
322
333
  end
323
334
 
324
- def parse_provides(provides, base)
325
- this_file = File.basename base
326
- basedir = dirname base
335
+ def parse_provides(provides, path)
336
+ basename = File.basename path
337
+ basedir = dirname path
327
338
 
328
339
  provides.to_s.lines.map {|line|
329
340
  line.chomp!
330
341
 
331
342
  m = line.match PROVIDES_REGEX
332
343
 
333
- platform, file, url = m[:platform], m[:file], m[:url]
334
- file = nil if file == this_file || file == '.'
344
+ platform, file, url_tpl = m[:platform], m[:file], m[:url]
345
+ file = nil if file == basename || file == '.'
335
346
 
336
- if url.nil?
337
- if file.nil?
338
- url = url_for base
339
- else
340
- path = File.expand_path file, ROOT + basedir.to_s
341
- url = url_for path[ROOT.size..-1]
342
- end
347
+ if file.nil?
348
+ url = make_url path, url_tpl
349
+ elsif url_tpl.nil?
350
+ normalized = File.expand_path file, FS_ROOT + basedir.to_s
351
+ url = make_url normalized[FS_ROOT.size..-1]
343
352
  else
344
- # for explicit urls which don't go through url_for
345
- url.sub! '$path', file || base
353
+ url = make_url file, url_tpl
346
354
  end
347
355
 
348
356
  Source.new platform, file, url
349
357
  }
350
358
  end
359
+
360
+ def sort(node)
361
+ sorted = node.children.sort_by{|n| n[:name].to_s }.sort_by {|n| n.name }
362
+ sorted.each {|n| node << n }
363
+ end
351
364
  end
@@ -1,20 +1,4 @@
1
1
  class ReaPack::Index::CLI
2
- CONFIG_SEARCH = [
3
- '~',
4
- '.',
5
- ].freeze
6
-
7
- PROGRAM_NAME = 'reapack-index'.freeze
8
-
9
- DEFAULTS = {
10
- verbose: false,
11
- warnings: true,
12
- progress: true,
13
- quiet: false,
14
- commit: nil,
15
- output: './index.xml',
16
- }.freeze
17
-
18
2
  def initialize(argv = [])
19
3
  @opts = parse_options(argv)
20
4
  path = argv.last || Dir.pwd
@@ -35,7 +19,7 @@ class ReaPack::Index::CLI
35
19
  def run
36
20
  return @exit unless @exit.nil?
37
21
 
38
- @db = ReaPack::Index.new File.expand_path(@opts[:output], @git.workdir)
22
+ @db = ReaPack::Index.new expand_path(@opts[:output])
39
23
  @db.amend = @opts[:amend]
40
24
 
41
25
  if @opts[:check]
@@ -52,13 +36,16 @@ class ReaPack::Index::CLI
52
36
  return true
53
37
  end
54
38
 
55
- if remote = @git.remotes['origin']
56
- @db.source_pattern = ReaPack::Index.source_for remote.url
39
+ begin
40
+ tpl = @opts[:url_template]
41
+ is_custom = tpl != DEFAULTS[:url_template]
42
+
43
+ @db.url_template = is_custom ? tpl : auto_url_tpl
44
+ rescue ReaPack::Index::Error => e
45
+ warn '--url-template: ' + e.message if is_custom
57
46
  end
58
47
 
59
- set_about
60
- eval_links
61
- scan_commits
48
+ do_name; do_about; eval_links; scan_commits
62
49
 
63
50
  unless @db.modified?
64
51
  $stderr.puts 'Nothing to do!' unless @opts[:quiet]
@@ -76,17 +63,6 @@ class ReaPack::Index::CLI
76
63
  end
77
64
 
78
65
  private
79
- def prompt(question, &block)
80
- $stderr.print "#{question} [y/N] "
81
- answer = $stdin.getch
82
- $stderr.puts answer
83
-
84
- yes = answer.downcase == 'y'
85
- block[] if block_given? && yes
86
-
87
- yes
88
- end
89
-
90
66
  def scan_commits
91
67
  if @git.empty?
92
68
  warn 'The current branch does not contains any commit.'
@@ -147,6 +123,7 @@ private
147
123
  file = delta.new_file
148
124
  end
149
125
 
126
+ return if ignored? expand_path(file[:path])
150
127
  return unless ReaPack::Index.type_of file[:path]
151
128
 
152
129
  log "-> indexing #{status} file #{file[:path]}"
@@ -185,7 +162,14 @@ private
185
162
  begin
186
163
  @db.eval_link *link
187
164
  rescue ReaPack::Index::Error => e
188
- warn e.message
165
+ opt = case link.first
166
+ when :website
167
+ '--link'
168
+ when :donation
169
+ '--donation-link'
170
+ end
171
+
172
+ warn "#{opt}: " + e.message
189
173
  end
190
174
  }
191
175
  end
@@ -200,7 +184,19 @@ private
200
184
  }
201
185
  end
202
186
 
203
- def set_about
187
+ def do_name
188
+ @db.name = @opts[:name] if @opts[:name]
189
+
190
+ if @db.name.empty?
191
+ warn "The name of this index is unset. " \
192
+ "Run the following command with a name of your choice:" \
193
+ "\n #{$0} --name 'FooBar Scripts'"
194
+ end
195
+ rescue ReaPack::Index::Error => e
196
+ warn '--name: ' + e.message
197
+ end
198
+
199
+ def do_about
204
200
  path = @opts[:about]
205
201
 
206
202
  unless path
@@ -224,9 +220,13 @@ private
224
220
 
225
221
  types = ReaPack::Index::FILE_TYPES.keys
226
222
  files = Dir.glob "#{Regexp.quote(root)}**/*.{#{types.join ','}}"
223
+ count = 0
227
224
 
228
225
  files.sort.each {|file|
226
+ next if ignored? file
227
+
229
228
  errors = ReaPack::Index.validate_file file
229
+ count += 1
230
230
 
231
231
  if errors
232
232
  $stderr.print 'F' unless @opts[:quiet]
@@ -251,7 +251,7 @@ private
251
251
  $stderr.puts "\n"
252
252
 
253
253
  $stderr.puts "Finished checks for %d package%s with %d failure%s" % [
254
- files.size, files.size == 1 ? '' : 's',
254
+ count, count == 1 ? '' : 's',
255
255
  failures.size, failures.size == 1 ? '' : 's'
256
256
  ]
257
257
  end
@@ -267,20 +267,25 @@ private
267
267
  prompt 'Commit the new index?'
268
268
  end
269
269
 
270
- target = @git.head.target
270
+ old_index = @git.index
271
+ target = @git.empty? ? nil : @git.head.target
272
+
273
+ if target
274
+ old_index.read_tree target.tree
275
+ else
276
+ old_index.clear
277
+ end
278
+
271
279
  root = Pathname.new @git.workdir
272
280
  file = Pathname.new @db.path
273
281
 
274
- old_index = @git.index
275
- old_index.read_tree target.tree
276
-
277
282
  index = @git.index
278
283
  index.add file.relative_path_from(root).to_s
279
284
 
280
285
  Rugged::Commit.create @git, \
281
286
  tree: index.write_tree(@git),
282
287
  message: "index: #{changelog}",
283
- parents: [target],
288
+ parents: [target].compact,
284
289
  update_ref: 'HEAD'
285
290
 
286
291
  old_index.write
@@ -288,6 +293,17 @@ private
288
293
  $stderr.puts 'commit created'
289
294
  end
290
295
 
296
+ def prompt(question, &block)
297
+ $stderr.print "#{question} [y/N] "
298
+ answer = $stdin.getch
299
+ $stderr.puts answer
300
+
301
+ yes = answer.downcase == 'y'
302
+ block[] if block_given? && yes
303
+
304
+ yes
305
+ end
306
+
291
307
  def log(line)
292
308
  $stderr.puts line if @opts[:verbose]
293
309
  end
@@ -313,125 +329,30 @@ private
313
329
  @add_nl = true
314
330
  end
315
331
 
316
- def parse_options(args)
317
- opts = Hash.new
318
-
319
- OptionParser.new do |op|
320
- op.program_name = PROGRAM_NAME
321
- op.version = ReaPack::Index::VERSION
322
- op.banner = "Package indexer for ReaPack-based repositories\n" +
323
- "Usage: #{PROGRAM_NAME} [options] [directory]"
324
-
325
- op.separator 'Options:'
326
-
327
- op.on '-a', '--[no-]amend', 'Reindex existing versions' do |bool|
328
- opts[:amend] = bool
329
- end
330
-
331
- op.on '-c', '--check', 'Test every package including uncommited changes and exit' do
332
- opts[:check] = true
333
- end
334
-
335
- op.on '-o', "--output FILE=#{DEFAULTS[:output]}",
336
- 'Set the output filename and path for the index' do |file|
337
- opts[:output] = file.strip
338
- end
339
-
340
- op.on '-l', '--link LINK', 'Add or remove a website link' do |link|
341
- opts[:links] ||= Array.new
342
- opts[:links] << [:website, link.strip]
343
- end
344
-
345
- op.on '--donation-link LINK', 'Add or remove a donation link' do |link|
346
- opts[:links] ||= Array.new
347
- opts[:links] << [:donation, link]
348
- end
349
-
350
- op.on '--ls-links', 'Display the link list then exit' do |link|
351
- opts[:lslinks] = true
352
- end
353
-
354
- op.on '-A', '--about=FILE', 'Set the about content from a file' do |file|
355
- opts[:about] = file.strip
356
- end
357
-
358
- op.on '--remove-about', 'Remove the about content from the index' do
359
- opts[:rmabout] = true
360
- end
361
-
362
- op.on '--dump-about', 'Dump the raw about content in RTF and exit' do
363
- opts[:dump_about] = true
364
- end
365
-
366
- op.on '--[no-]progress', 'Enable or disable progress information' do |bool|
367
- opts[:progress] = bool
368
- end
369
-
370
- op.on '-V', '--[no-]verbose', 'Activate diagnosis messages' do |bool|
371
- opts[:verbose] = bool
372
- end
373
-
374
- op.on '-C', '--[no-]commit', 'Select whether to commit the modified index' do |bool|
375
- opts[:commit] = bool
376
- end
377
-
378
- op.on '--prompt-commit', 'Ask at runtime whether to commit the index' do
379
- opts[:commit] = nil
380
- end
381
-
382
- op.on '-W', '--warnings', 'Enable warnings' do
383
- opts[:warnings] = true
384
- end
385
-
386
- op.on '-w', '--no-warnings', 'Turn off warnings' do
387
- opts[:warnings] = false
388
- end
389
-
390
- op.on '-q', '--[no-]quiet', 'Disable almost all output' do
391
- opts[:warnings] = false
392
- opts[:progress] = false
393
- opts[:verbose] = false
394
- opts[:quiet] = true
395
- end
396
-
397
- op.on '--no-config', 'Bypass the configuration files' do
398
- opts[:noconfig] = true
399
- end
400
-
401
- op.on_tail '-v', '--version', 'Display version information' do
402
- puts op.ver
403
- @exit = true
404
- end
332
+ def ignored?(path)
333
+ @opts[:ignore].each {|pattern|
334
+ return true if path.start_with? pattern
335
+ }
405
336
 
406
- op.on_tail '-h', '--help', 'Prints this help' do
407
- puts op
408
- @exit = true
409
- end
410
- end.parse! args
337
+ false
338
+ end
411
339
 
412
- opts
413
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
414
- $stderr.puts "#{PROGRAM_NAME}: #{e.message}"
415
- $stderr.puts "Try '#{PROGRAM_NAME} --help' for more information."
416
- @exit = false
417
- opts
340
+ def expand_path(path)
341
+ # expand from the repository root or from the current directory if
342
+ # the repository is not yet initialized
343
+ File.expand_path path, @git ? @git.workdir : Dir.pwd
418
344
  end
419
345
 
420
- def read_config
421
- CONFIG_SEARCH.map {|dir|
422
- dir = File.expand_path dir, @git.workdir
423
- path = File.expand_path '.reapack-index.conf', dir
346
+ def auto_url_tpl
347
+ remote = @git.remotes['origin']
348
+ return unless remote
424
349
 
425
- log 'reading configuration from %s' % path
350
+ uri = Gitable::URI.parse remote.url
351
+ return unless uri.path =~ /\A\/?(?<user>[^\/]+)\/(?<repo>[^\/]+)\.git\Z/
426
352
 
427
- unless File.readable? path
428
- log 'configuration file is unreadable, skipping'
429
- next
430
- end
353
+ tpl = uri.to_web_uri
354
+ tpl.path += '/raw/$commit/$path'
431
355
 
432
- opts = Array.new
433
- File.foreach(path) {|line| opts << Shellwords.split(line) }
434
- opts
435
- }.flatten.compact
356
+ tpl.to_s
436
357
  end
437
358
  end