reapack-index 1.0beta3 → 1.0beta4

Sign up to get free protection for your applications and to get access to all the features.
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