reapack-index 1.0beta2 → 1.0beta3

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.
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