reapack-index 1.0beta3 → 1.0beta4

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: a8fcd949ead46a7e862d70e2d45f886aa13a48e5
4
- data.tar.gz: b44bf961705b9582abc694e5a4782171002b0bcb
3
+ metadata.gz: b08f343ff91fe1f1f543b9ac82de72f698404ce8
4
+ data.tar.gz: 3b4f0c39955b88ba41f3bdf0759d7531d57e32ca
5
5
  SHA512:
6
- metadata.gz: 0204c31e2c928ccb7dda6f430701bccc6952473efbf855853e5b86d8c9f916cddb9871953871841efe75302d76d2c5ea92a75835cd7e4e2731186e3b05664566
7
- data.tar.gz: 9c39f26aef7f0249d6a7a78a7d58edc8c4ce9bc1078db134872b06cd955967eddba9d452fd70fa358e6d5897c3d022fe14ed66b007fb2f2138e15b4455c5a829
6
+ metadata.gz: 545316c4c749f62d19f68b1f3cb82f447a5d8b0b7d9aaaaba815258ee86424e8f6c0e06cfa2c9ef8876b4eb52074f5a5ac193a954070602d859d2c078a4803cc
7
+ data.tar.gz: b0f6a34ed77612b71fe28a62cea6f0b6b0a7375506e8a2ecfc9ac90496172b2c30228a8212a064ae8c78e9d26103155ff493fab5d749b14ae4e04f2e4e17c5b7
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.3
3
+ - 2.3.0
4
4
  before_install:
5
5
  - gem update bundler
6
6
  - sudo apt-get -qq update
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Package indexer for ReaPack-based repositories
1
+ # Package indexer for git-based ReaPack repositories
2
2
 
3
3
  Parent project: [https://github.com/cfillion/reapack](https://github.com/cfillion/reapack)
4
4
  Subproject: [https://github.com/cfillion/metaheader](https://github.com/cfillion/metaheader)
@@ -25,19 +25,23 @@ reapack-index [options] [path-to-your-repository]
25
25
  ```
26
26
 
27
27
  ```
28
- Options:
29
- -a, --[no-]amend Reindex existing versions
28
+ Modes:
30
29
  -c, --check Test every package including uncommited changes and exit
30
+ -s, --scan [COMMIT] Scan new commits (default) or specific commits
31
+ Indexer options:
32
+ -a, --[no-]amend Reindex existing versions
31
33
  -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
34
  -U, --url-template TEMPLATE=auto Set the template for implicit download links
34
35
  -o, --output FILE=./index.xml Set the output filename and path for the index
36
+ Repository metadata:
37
+ -n, --name NAME Set the name shown in ReaPack for this repository
35
38
  -l, --link LINK Add or remove a website link
36
39
  --donation-link LINK Add or remove a donation link
37
40
  --ls-links Display the link list then exit
38
41
  -A, --about=FILE Set the about content from a file
39
42
  --remove-about Remove the about content from the index
40
43
  --dump-about Dump the raw about content in RTF and exit
44
+ Misc options:
41
45
  --[no-]progress Enable or disable progress information
42
46
  -V, --[no-]verbose Activate diagnosis messages
43
47
  -C, --[no-]commit Select whether to commit the modified index
@@ -50,6 +54,9 @@ Options:
50
54
  -h, --help Prints this help
51
55
  ```
52
56
 
57
+ A getting started guide and packaging documentation are available in
58
+ the [wiki](https://github.com/cfillion/reapack-index/wiki).
59
+
53
60
  ### Configuration
54
61
 
55
62
  Options can be specified from the command line or stored in configuration files.
@@ -60,112 +67,3 @@ The settings are applied in the following order:
60
67
  - ~/.reapack-index.conf (`~` = home directory)
61
68
  - ./.reapack-index.conf (`.` = repository root)
62
69
  - command line
63
-
64
- ## Packaging Documentation
65
-
66
- This indexer uses metadata found at the start of the files to generate the
67
- database in ReaPack format.
68
- See also [MetaHeader](https://github.com/cfillion/metaheader)'s documentation.
69
-
70
- All tags are optional unless explicitely marked as required.
71
-
72
- ### Package type by extension:
73
-
74
- - `.lua`, `.eel`, `.py`: ReaScripts
75
- - `.jsfx`: JS effects
76
- - `.ext`: Native extensions (all included files must be explicitely defined)
77
-
78
- ### Package Tags
79
-
80
- These tags affects an entire package. Changes to any of those tags are
81
- applied immediately and may affect released versions.
82
-
83
- **@noindex**
84
-
85
- Disable indexing for this file. Set this on included files that
86
- should not be distributed alone.
87
-
88
- ```
89
- @noindex
90
-
91
- NoIndex: true
92
- ```
93
-
94
- **@version** [required]
95
-
96
- The current package version.
97
- Value must contain between one and four groups of digits.
98
-
99
- ```
100
- @version 1.0
101
- @version 1.2pre3
102
-
103
- Version: 0.2015.12.25
104
- ```
105
-
106
- ### Version Tags
107
-
108
- These tags are specific to a single package version. You may still edit them
109
- after a release by running the indexer with the `--amend` option.
110
-
111
- **@author**
112
-
113
- ```
114
- @author cfillion
115
-
116
- Author: Christian Fillion
117
- ```
118
-
119
- **@changelog**
120
-
121
- ```
122
- @changelog
123
- Documented the metadata syntax
124
- Added support for deleted scripts
125
-
126
- Changelog:
127
- Added an alternate syntax for metadata tags
128
- ```
129
-
130
- **@provides**
131
-
132
- Add additional files to the package. This is also used to add platform restrictions
133
- or set a custom download url (by default the download url is based on the "origin"
134
- git remote). These files will be installed/updated together with the package.
135
-
136
- ```
137
- @provides unicode.dat
138
-
139
- Provides:
140
- Images/background.png
141
- Images/fader_small.png
142
- Images/fader_big.png
143
-
144
- @provides
145
- [windows] reaper_extension.dll http://mysite.com/download/$version/$path
146
- ```
147
-
148
- List of supported platform strings:
149
- - `windows`: All versions of Windows
150
- - `win32`: Windows 32-bit
151
- - `win64`: Windows 64-bit
152
- - `darwin`: All versions of OS X
153
- - `darwin32`: OS X 32-bit
154
- - `darwin64`: OS X 64-bit
155
-
156
- The following variables will be interpolated if found in the URL:
157
- - `$path`: The path of the file relative to the package
158
- - `$commit`: The hash of the commit being indexed or "master" if unavailable
159
- - `$version`: The version of the package being indexed
160
-
161
- Platform restriction and custom url can be set for the package itself,
162
- either by using its file name or a dot:
163
-
164
- ```
165
- -- this is a lua script named `hello_osx.lua`
166
- -- @provides
167
- -- [darwin] hello_osx.lua
168
-
169
- -- @provides
170
- -- [darwin] .
171
- ```
data/Rakefile CHANGED
@@ -3,6 +3,6 @@ require 'rake/testtask'
3
3
 
4
4
  task :default => [:test]
5
5
  Rake::TestTask.new do |t|
6
- t.test_files = FileList['test/test_*.rb']
6
+ t.test_files = FileList['test/**/test_*.rb']
7
7
  t.warning = false
8
8
  end
@@ -14,58 +14,47 @@ require 'rugged'
14
14
  require 'shellwords'
15
15
  require 'time'
16
16
 
17
+ require 'reapack/index/cdetector'
17
18
  require 'reapack/index/cli'
18
19
  require 'reapack/index/cli/options'
20
+ require 'reapack/index/git'
19
21
  require 'reapack/index/metadata'
20
22
  require 'reapack/index/named_node'
21
23
  require 'reapack/index/package'
22
24
  require 'reapack/index/parsers'
25
+ require 'reapack/index/provides'
26
+ require 'reapack/index/source'
23
27
  require 'reapack/index/version'
24
28
 
25
29
  class ReaPack::Index
26
30
  Error = Class.new RuntimeError
27
31
 
28
- FILE_TYPES = {
29
- 'lua' => :script,
30
- 'eel' => :script,
31
- 'py' => :script,
32
- 'ext' => :extension,
33
- 'jsfx' => :effect,
32
+ PKG_TYPES = {
33
+ script: %w{lua eel py},
34
+ extension: %w{ext},
35
+ effect: %w{jsfx},
36
+ data: %w{data},
34
37
  }.freeze
35
38
 
36
39
  WITH_MAIN = [:script, :effect].freeze
37
40
 
38
- PROVIDES_REGEX = /
39
- \A
40
- ( \[ \s* (?<platform> .+? ) \s* \] )?
41
- \s*
42
- (?<file> .+?)
43
- ( \s+ (?<url> (?:file|https?):\/\/.+ ) )?
44
- \z
45
- /x.freeze
46
-
47
41
  PROVIDES_VALIDATOR = proc {|value|
48
42
  begin
49
- files = value.lines.map {|l|
50
- m = l.chomp.match PROVIDES_REGEX
51
- Source.validate_platform m[:platform]
52
- [m[:platform], m[:file]]
53
- }
54
- dup = files.detect {|f| files.count(f) > 1 }
55
- "duplicate file (%s)" % dup[1] if dup
43
+ Provides.parse_each(value).to_a and nil
56
44
  rescue Error => e
57
45
  e.message
58
46
  end
59
- }.freeze
47
+ }
60
48
 
61
49
  HEADER_RULES = {
62
50
  # package-wide tags
63
- :version => /\A(?:[^\d]*\d{1,4}[^\d]*){1,4}\z/,
51
+ :version => [
52
+ MetaHeader::REQUIRED, MetaHeader::VALUE, MetaHeader::SINGLELINE, /\d/],
64
53
 
65
54
  # version-specific tags
66
- :author => [MetaHeader::OPTIONAL, /\A[^\n]+\z/],
67
- :changelog => [MetaHeader::OPTIONAL, /.+/],
68
- :provides => [MetaHeader::OPTIONAL, PROVIDES_VALIDATOR]
55
+ :author => [MetaHeader::VALUE, MetaHeader::SINGLELINE],
56
+ :changelog => [MetaHeader::VALUE],
57
+ :provides => [MetaHeader::VALUE, PROVIDES_VALIDATOR]
69
58
  }.freeze
70
59
 
71
60
  FS_ROOT = File.expand_path('/').freeze
@@ -73,28 +62,51 @@ class ReaPack::Index
73
62
  attr_reader :path, :url_template
74
63
  attr_accessor :amend, :files, :time
75
64
 
76
- def self.type_of(path)
77
- ext = File.extname(path)[1..-1]
78
- FILE_TYPES[ext]
79
- end
65
+ class << self
66
+ def is_type?(input)
67
+ PKG_TYPES.has_key? input&.to_sym
68
+ end
80
69
 
81
- def self.validate_file(path)
82
- mh = MetaHeader.from_file path
83
- return if mh[:noindex]
70
+ def type_of(path)
71
+ # don't treat files in the root directory as packages
72
+ # because they don't have a category
73
+ return if File.dirname(path) == '.'
84
74
 
85
- mh.validate HEADER_RULES
75
+ ext = File.extname(path)[1..-1]
76
+ PKG_TYPES.find {|_, v| v.include? ext }&.first if ext
77
+ end
78
+
79
+ alias :is_package? :type_of
80
+
81
+ def resolve_type(input)
82
+ PKG_TYPES
83
+ .find {|name, exts| input.to_sym == name || exts.include?(input.to_s) }
84
+ &.first
85
+ end
86
86
  end
87
87
 
88
88
  def initialize(path)
89
89
  @amend = false
90
90
  @changes = {}
91
+ @changed_nodes = []
91
92
  @files = []
92
93
  @path = path
93
94
 
95
+ @cdetector = ConflictDetector.new
96
+
94
97
  if File.exist? path
95
- # noblanks: don't preserve the original white spaces
96
- # so we always output a neat document
97
- @doc = File.open(path) {|file| Nokogiri::XML file, &:noblanks }
98
+ begin
99
+ # noblanks: don't preserve the original white spaces
100
+ # so we always output a neat document
101
+ @doc = File.open(path) {|file| Nokogiri::XML file, &:noblanks }
102
+ rescue Nokogiri::XML::SyntaxError
103
+ end
104
+
105
+ unless @doc&.root&.name == 'index'
106
+ raise Error, "'#{path}' is not a ReaPack index file"
107
+ end
108
+
109
+ @cdetector.load_xml @doc.root
98
110
  else
99
111
  @dirty = true
100
112
  @is_new = true
@@ -113,9 +125,12 @@ class ReaPack::Index
113
125
  type = self.class.type_of path
114
126
  return unless type
115
127
 
116
- mh = MetaHeader.new contents
128
+ # variables to restore if an error occur
129
+ backups = Hash[[:@doc, :@cdetector].map {|var|
130
+ [var, instance_variable_get(var).clone]
131
+ }]
117
132
 
118
- backup = @doc.root.dup
133
+ mh = MetaHeader.new contents
119
134
 
120
135
  if mh[:noindex]
121
136
  remove path
@@ -123,13 +138,12 @@ class ReaPack::Index
123
138
  end
124
139
 
125
140
  if errors = mh.validate(HEADER_RULES)
126
- prefix = errors.size == 1 ? "\x20" : "\n\x20\x20"
127
- raise Error, "invalid metadata:%s" %
128
- [prefix + errors.join(prefix)]
141
+ raise Error, errors.join("\n")
129
142
  end
130
143
 
131
- cat, pkg = find path
132
- pkg.type = type.to_s
144
+ cat, pkg = package_for path
145
+
146
+ cselector = @cdetector[pkg.type = type, path]
133
147
 
134
148
  pkg.version mh[:version] do |ver|
135
149
  next unless ver.is_new? || @amend
@@ -138,48 +152,59 @@ class ReaPack::Index
138
152
  @currentVersion = ver.name
139
153
 
140
154
  ver.author = mh[:author]
141
- ver.time = @time if @time
155
+ ver.time = @time if @time && ver.is_new?
142
156
  ver.changelog = mh[:changelog]
143
157
 
144
158
  ver.replace_sources do
145
- sources = parse_provides mh[:provides], path
159
+ cselector.clear
160
+ sources = parse_provides mh[:provides], pkg
146
161
 
147
162
  if WITH_MAIN.include?(type) && sources.none? {|src| src.file.nil? }
148
163
  # add the package itself as a source
149
- sources.unshift Source.new nil, nil, make_url(path)
164
+ src = Source.new make_url(path)
165
+ sources.unshift src
166
+
167
+ cselector.push src.platform, path
150
168
  end
151
169
 
152
170
  sources.each {|src| ver.add_source src }
153
171
  end
154
172
  end
155
173
 
174
+ if cons = cselector.resolve
175
+ raise Error, cons.first
176
+ end
177
+
156
178
  log_change 'new category', 'new categories' if cat.is_new?
157
179
 
158
- if pkg.is_new?
159
- log_change 'new package'
160
- elsif pkg.modified?
161
- log_change 'modified package'
180
+ if pkg.modified? && !@changed_nodes.include?(pkg.node)
181
+ log_change "#{pkg.is_new? ? 'new' : 'modified'} package"
182
+ @changed_nodes << pkg.node
162
183
  end
163
184
 
164
185
  pkg.versions.each {|ver|
165
- if ver.is_new?
166
- log_change 'new version'
167
- elsif ver.modified?
168
- log_change 'modified version'
186
+ if ver.modified? && !@changed_nodes.include?(ver.node)
187
+ log_change "#{ver.is_new? ? 'new' : 'modified'} version"
188
+ @changed_nodes << ver.node
169
189
  end
170
190
  }
191
+
192
+ bump_commit
171
193
  rescue Error
172
- @doc.root = backup
194
+ backups.each {|var, value| instance_variable_set var, value }
173
195
  raise
174
196
  end
175
197
 
176
198
  def remove(path)
177
- cat, pkg = find path, false
199
+ cat, pkg = package_for path, false
178
200
  return unless pkg
179
201
 
202
+ @cdetector[pkg.type, path].clear
203
+
180
204
  pkg.remove
181
205
  cat.remove if cat.empty?
182
206
 
207
+ bump_commit
183
208
  log_change 'removed package'
184
209
  end
185
210
 
@@ -221,7 +246,7 @@ class ReaPack::Index
221
246
  uri.normalize!
222
247
 
223
248
  unless (uri.request_uri || uri.path).include? '$path'
224
- raise Error, "$path placeholder is missing: #{tpl}"
249
+ raise Error, "missing $path placeholder in '#{tpl}'"
225
250
  end
226
251
 
227
252
  unless %w{http https file}.include? uri.scheme
@@ -230,7 +255,7 @@ class ReaPack::Index
230
255
 
231
256
  @url_template = uri.to_s.freeze
232
257
  rescue Addressable::URI::InvalidURIError
233
- raise Error, "invalid URL or scheme: #{tpl}"
258
+ raise Error, "invalid template '#{tpl}'"
234
259
  end
235
260
 
236
261
  def version
@@ -243,7 +268,7 @@ class ReaPack::Index
243
268
 
244
269
  def name=(newName)
245
270
  if !/\A[^~#%&*{}\\:<>?\/+|"]+\Z/.match(newName) || /\A\.+\Z/.match(newName)
246
- raise Error, "Invalid name: '#{newName}'"
271
+ raise Error, "invalid name '#{newName}'"
247
272
  end
248
273
 
249
274
  oldName = name
@@ -252,19 +277,12 @@ class ReaPack::Index
252
277
  end
253
278
 
254
279
  def commit
255
- @doc.root[:commit]
280
+ @commit ||= @doc.root[:commit]
256
281
  end
257
282
 
258
- def commit=(sha1)
259
- if sha1.nil?
260
- @doc.root.remove_attribute 'commit'
261
- else
262
- @doc.root['commit'] = sha1
263
- end
264
- end
283
+ attr_writer :commit
265
284
 
266
285
  def write(path)
267
- @doc.root.element_children.each {|n| sort n if n.name == 'category' }
268
286
  sort @doc.root
269
287
 
270
288
  FileUtils.mkdir_p File.dirname(path)
@@ -295,6 +313,29 @@ class ReaPack::Index
295
313
  list.join ', '
296
314
  end
297
315
 
316
+ def make_url(path, template = nil)
317
+ unless template
318
+ unless @url_template
319
+ raise Error, 'unable to generate download links: empty url template'
320
+ end
321
+
322
+ unless @files.include? path
323
+ raise Error, "file not found '#{path}'"
324
+ end
325
+ end
326
+
327
+ (template || @url_template)
328
+ .sub('$path', path)
329
+ .sub('$commit', commit || 'master')
330
+ .sub('$version', @currentVersion.to_s)
331
+ end
332
+
333
+ def self.expand(filepath, basedir)
334
+ expanded = File.expand_path filepath, FS_ROOT + basedir
335
+ expanded[0...FS_ROOT.size] = ''
336
+ expanded
337
+ end
338
+
298
339
  private
299
340
  def log_change(desc, plural = nil)
300
341
  @dirty = true
@@ -303,62 +344,71 @@ private
303
344
  @changes[desc][0] += 1
304
345
  end
305
346
 
306
- def dirname(path)
307
- name = File.dirname path
308
- name == '.' ? nil : name
309
- end
310
-
311
- def find(path, create = true)
312
- cat_name = dirname(path) || 'Other'
347
+ def package_for(path, create = true)
348
+ cat_name = File.dirname path
313
349
  pkg_name = File.basename path
314
350
 
315
- cat = Category.get cat_name, @doc.root, create
316
- pkg = Package.get pkg_name, cat && cat.node, create
351
+ cat = Category.fetch cat_name, @doc.root, create
352
+ pkg = Package.fetch pkg_name, cat&.node, create
317
353
 
318
354
  [cat, pkg]
319
355
  end
320
356
 
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
325
-
326
- raise Error, "#{path}: No such file or directory" unless @files.include? path
327
- end
357
+ def parse_provides(provides, pkg)
358
+ pathdir = Pathname.new pkg.category
328
359
 
329
- (template || @url_template)
330
- .sub('$path', path)
331
- .sub('$commit', commit || 'master')
332
- .sub('$version', @currentVersion || '0.0')
333
- end
360
+ Provides.parse_each(provides).map {|line|
361
+ line.file_pattern = pkg.name if line.file_pattern == '.'
334
362
 
335
- def parse_provides(provides, path)
336
- basename = File.basename path
337
- basedir = dirname path
363
+ expanded = self.class.expand line.file_pattern, pkg.category
364
+ cselector = @cdetector[line.type || pkg.type, pkg.path]
338
365
 
339
- provides.to_s.lines.map {|line|
340
- line.chomp!
366
+ if expanded == pkg.path
367
+ # always resolve path even when an url template is set
368
+ files = [expanded]
369
+ elsif line.url_template.nil?
370
+ files = @files.select {|f| File.fnmatch expanded, f, File::FNM_PATHNAME }
371
+ raise Error, "file not found '#{line.file_pattern}'" if files.empty?
372
+ else
373
+ # use the relative path for external urls
374
+ files = [line.file_pattern]
375
+ end
341
376
 
342
- m = line.match PROVIDES_REGEX
377
+ files.map {|file|
378
+ src = Source.new make_url(file, line.url_template)
379
+ src.platform = line.platform
380
+ src.type = line.type
343
381
 
344
- platform, file, url_tpl = m[:platform], m[:file], m[:url]
345
- file = nil if file == basename || file == '.'
382
+ cselector.push src.platform, line.url_template ? expanded : file
346
383
 
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]
352
- else
353
- url = make_url file, url_tpl
354
- end
384
+ if file != pkg.path
385
+ if line.url_template
386
+ src.file = file
387
+ else
388
+ src.file = Pathname.new(file).relative_path_from(pathdir).to_s
389
+ end
390
+ end
355
391
 
356
- Source.new platform, file, url
357
- }
392
+ src
393
+ }
394
+ }.flatten
358
395
  end
359
396
 
360
397
  def sort(node)
398
+ node.children.each {|n| sort n }
399
+
400
+ return if node.name == Package.tag
361
401
  sorted = node.children.sort_by{|n| n[:name].to_s }.sort_by {|n| n.name }
362
402
  sorted.each {|n| node << n }
363
403
  end
404
+
405
+ def bump_commit
406
+ sha1 = commit()
407
+
408
+ if sha1.nil?
409
+ @doc.root.remove_attribute 'commit'
410
+ else
411
+ @doc.root['commit'] = sha1
412
+ end
413
+ end
364
414
  end