indexer 0.1.0

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.
Files changed (47) hide show
  1. data/.index +54 -0
  2. data/HISTORY.md +9 -0
  3. data/README.md +145 -0
  4. data/bin/index +7 -0
  5. data/data/indexer/r2013/index.kwalify +175 -0
  6. data/data/indexer/r2013/index.yes +172 -0
  7. data/data/indexer/r2013/index.yesi +67 -0
  8. data/data/indexer/r2013/ruby.txt +35 -0
  9. data/data/indexer/r2013/yaml.txt +30 -0
  10. data/lib/indexer.rb +65 -0
  11. data/lib/indexer/attributes.rb +171 -0
  12. data/lib/indexer/command.rb +260 -0
  13. data/lib/indexer/components.rb +8 -0
  14. data/lib/indexer/components/author.rb +140 -0
  15. data/lib/indexer/components/conflict.rb +78 -0
  16. data/lib/indexer/components/copyright.rb +95 -0
  17. data/lib/indexer/components/dependency.rb +18 -0
  18. data/lib/indexer/components/organization.rb +133 -0
  19. data/lib/indexer/components/repository.rb +140 -0
  20. data/lib/indexer/components/requirement.rb +360 -0
  21. data/lib/indexer/components/resource.rb +209 -0
  22. data/lib/indexer/conversion.rb +14 -0
  23. data/lib/indexer/conversion/gemfile.rb +44 -0
  24. data/lib/indexer/conversion/gemspec.rb +114 -0
  25. data/lib/indexer/conversion/gemspec_exporter.rb +304 -0
  26. data/lib/indexer/core_ext.rb +4 -0
  27. data/lib/indexer/error.rb +23 -0
  28. data/lib/indexer/gemfile.rb +75 -0
  29. data/lib/indexer/importer.rb +144 -0
  30. data/lib/indexer/importer/file.rb +94 -0
  31. data/lib/indexer/importer/gemfile.rb +27 -0
  32. data/lib/indexer/importer/gemspec.rb +43 -0
  33. data/lib/indexer/importer/html.rb +289 -0
  34. data/lib/indexer/importer/markdown.rb +45 -0
  35. data/lib/indexer/importer/ruby.rb +47 -0
  36. data/lib/indexer/importer/version.rb +38 -0
  37. data/lib/indexer/importer/yaml.rb +46 -0
  38. data/lib/indexer/loadable.rb +159 -0
  39. data/lib/indexer/metadata.rb +879 -0
  40. data/lib/indexer/model.rb +237 -0
  41. data/lib/indexer/revision.rb +43 -0
  42. data/lib/indexer/valid.rb +287 -0
  43. data/lib/indexer/validator.rb +313 -0
  44. data/lib/indexer/version/constraint.rb +124 -0
  45. data/lib/indexer/version/exceptions.rb +11 -0
  46. data/lib/indexer/version/number.rb +497 -0
  47. metadata +141 -0
@@ -0,0 +1,45 @@
1
+ module Indexer
2
+
3
+ class Importer
4
+
5
+ # Import metadata from a markdown source.
6
+ #
7
+ module MarkdownImportation
8
+
9
+ #
10
+ # Markdown import procedure.
11
+ #
12
+ def import(source)
13
+ if File.file?(source)
14
+ case File.extname(source)
15
+ when '.md', '.markdown'
16
+ load_markdown(source)
17
+ return true
18
+ end
19
+ end
20
+ super(source) if defined?(super)
21
+ end
22
+
23
+ #
24
+ # Import metadata from HTML file.
25
+ #
26
+ def load_markdown(file)
27
+ require 'nokogiri'
28
+ require 'redcarpet'
29
+
30
+ renderer = Redcarpet::Render::HTML.new()
31
+ markdown = Redcarpet::Markdown.new(renderer, :autolink=>true, :tables=>true, :space_after_headers=>true)
32
+ html = markdown.render(File.read(file))
33
+ doc = Nokogiri::HTML(html)
34
+
35
+ load_html(doc)
36
+ end
37
+
38
+ end
39
+
40
+ # Include mixin into Importer class.
41
+ include MarkdownImportation
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,47 @@
1
+ module Indexer
2
+
3
+ class Importer
4
+
5
+ # Build metadata from a Ruby script.
6
+ #
7
+ module RubyImportation
8
+
9
+ #
10
+ # Ruby script import procedure.
11
+ #
12
+ def import(source)
13
+ if File.file?(source)
14
+ case File.extname(source)
15
+ when '.rb' # TODO: Other ruby extensions ?
16
+ load_ruby(source)
17
+ true
18
+ else
19
+ text = read(source)
20
+ if text !=~ /\A---/ # not YAML
21
+ load_ruby(source)
22
+ true
23
+ else
24
+ super(source) if defined?(super)
25
+ end
26
+ end
27
+ else
28
+ super(source) if defined?(super)
29
+ end
30
+ end
31
+
32
+ #
33
+ # Load ruby file by simply evaluating it in the context
34
+ # of the Builder instance.
35
+ #
36
+ def load_ruby(file)
37
+ instance_eval(File.read(file))
38
+ end
39
+
40
+ end #module RubyImportation
41
+
42
+ # Include RubyImportation mixin into Builder class.
43
+ include RubyImportation
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,38 @@
1
+ module Indexer
2
+
3
+ class Importer
4
+
5
+ # Build mixin for Bundler's Gemfile.
6
+ #
7
+ module VersionImportation
8
+ #
9
+ # If the source file is a Gemfile, import it.
10
+ #
11
+ def import(source)
12
+ case source
13
+ when 'VERSION.txt', 'Version.txt'
14
+ vers = File.read(source).strip
15
+ metadata.version = vers
16
+ when 'VERSION', 'Version'
17
+ text = File.read(source).strip
18
+ if yaml?(text)
19
+ # don't really like this style b/c it's too subjective
20
+ hash = YAML.load(text)
21
+ hash = hash.inject{ |h,(k,v)| h[k.to_sym] = v; h }
22
+ vers = hash.values_at(:major,:minor,:patch,:build).compact
23
+ else
24
+ vers = File.read(source).strip
25
+ end
26
+ metadata.version = vers
27
+ else
28
+ super(source) if defined?(super)
29
+ end
30
+ end
31
+ end
32
+
33
+ # Include VersionImportation mixin into Builder class.
34
+ include VersionImportation
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,46 @@
1
+ module Indexer
2
+
3
+ class Importer
4
+
5
+ # Import metadata from a YAML source.
6
+ #
7
+ module YAMLImportation
8
+
9
+ #
10
+ # YAML import procedure.
11
+ #
12
+ def import(source)
13
+ if File.file?(source)
14
+ case File.extname(source)
15
+ when '.yaml', '.yml'
16
+ load_yaml(source)
17
+ true
18
+ else
19
+ text = read(source)
20
+ if text =~ /\A---/
21
+ load_yaml(source)
22
+ true
23
+ else
24
+ super(source) if defined?(super)
25
+ end
26
+ end
27
+ else
28
+ super(source) if defined?(super)
29
+ end
30
+ end
31
+
32
+ #
33
+ # Import metadata from YAML file.
34
+ #
35
+ def load_yaml(file)
36
+ metadata.merge!(YAML.load_file(file))
37
+ end
38
+
39
+ end
40
+
41
+ # Include YAMLImportation mixin into Builder class.
42
+ include YAMLImportation
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,159 @@
1
+ module Indexer
2
+
3
+ module Loadable
4
+
5
+ #
6
+ # Open metadata file and ensure strict validation to canonical format.
7
+ #
8
+ # @param [String] file or directory
9
+ # The file name from which to read the YAML metadata,
10
+ # or a directory from which to lookup the file.
11
+ #
12
+ def open(file=Dir.pwd)
13
+ file = find(file) if File.directory?(file)
14
+ valid(YAML.load_file(file))
15
+ end
16
+
17
+ #
18
+ # Like #open, but do not ensure strict validation to canonical format.
19
+ #
20
+ # @param [String] file or directory
21
+ # The file name from which to read the YAML metadata,
22
+ # or a directory from which to lookup the file.
23
+ #
24
+ def read(file=Dir.pwd)
25
+ file = find(file) if File.directory?(file)
26
+ new(YAML.load_file(file))
27
+ end
28
+
29
+ #
30
+ # Create new Metadata instance from atypical sources.
31
+ #
32
+ # TODO: Use Importer to construct Metadata instance.
33
+ #
34
+ def import(*sources)
35
+ end
36
+
37
+ #
38
+ # Load from YAML string or IO.
39
+ #
40
+ # @param [String,#read] String or IO object
41
+ # The file name from which to read the YAML metadata.
42
+ #
43
+ def load(io)
44
+ new(YAML.load(io))
45
+ end
46
+
47
+ #
48
+ # Load from YAML file.
49
+ #
50
+ # @param [String] file
51
+ # The file name from which to read the YAML metadata.
52
+ #
53
+ def load_file(file)
54
+ new(YAML.load_file(file))
55
+ end
56
+
57
+ #
58
+ # Find project root and read the index file.
59
+ #
60
+ # @param [String] from
61
+ # The directory from which to start the upward search.
62
+ #
63
+ def find(from=Dir.pwd)
64
+ File.join(root(from), LOCK_FILE)
65
+ end
66
+
67
+ #
68
+ # Find project root by looking upward for a locked metadata file.
69
+ #
70
+ # @param [String] from
71
+ # The directory from which to start the upward search.
72
+ #
73
+ # @return [String]
74
+ # The path to the locked metadata file.
75
+ #
76
+ # @raise [Errno::ENOENT]
77
+ # The locked metadata file could not be located.
78
+ #
79
+ def root(from=Dir.pwd)
80
+ if not path = exists?(from)
81
+ lock_file_missing(from)
82
+ end
83
+ path
84
+ end
85
+
86
+ #
87
+ # Does a locked metadata file exist?
88
+ #
89
+ # @return [true,false] Whether locked metadata file exists.
90
+ #
91
+ def exists?(from=Dir.pwd)
92
+ home = File.expand_path('~')
93
+ path = File.expand_path(from)
94
+ while path != '/' and path != home
95
+ if File.file?(File.join(path,LOCK_FILE))
96
+ return path
97
+ else
98
+ path = File.dirname(path)
99
+ end
100
+ false #lock_file_missing(from)
101
+ end
102
+ false #lock_file_missing(from)
103
+ end
104
+
105
+ #
106
+ # Raise lock file missing error.
107
+ #
108
+ def lock_file_missing(from=nil)
109
+ raise Error.exception("could not locate .index file", Errno::ENOENT)
110
+ end
111
+
112
+ #
113
+ # Alias for exists?
114
+ #
115
+ def exist?(from=Dir.pwd)
116
+ exists?(from)
117
+ end
118
+
119
+ #
120
+ # Lockdown the metadata (via Importer) and return updated metadata.
121
+ #
122
+ # Options :force
123
+ #
124
+ def lock(*sources)
125
+ opts = (Hash === sources.last ? sources.pop : {})
126
+
127
+ file = nil
128
+ needed = true
129
+ sources = sources.flatten
130
+
131
+ if sources.empty?
132
+ if file = exists?
133
+ metadata = Metadata.open
134
+ sources = metadata.sources
135
+ else
136
+ sources = Dir.glob(USER_FILES, File::FNM_CASEFOLD)
137
+ raise Error.exception("could not find default sources") if sources.empty?
138
+ end
139
+ end
140
+
141
+ if file && !opts[:force]
142
+ date = sources.map{ |s| File.mtime(s) }.max
143
+ needed = false if File.mtime(file) > date
144
+ end
145
+
146
+ Importer.import(*sources) if needed
147
+ end
148
+
149
+ #
150
+ # Lockdown the metadata (via Importer) and save.
151
+ #
152
+ def lock!(*sources)
153
+ metadata = lock(*sources)
154
+ metadata.save! if metadata
155
+ end
156
+
157
+ end
158
+
159
+ end
@@ -0,0 +1,879 @@
1
+ require_relative 'loadable'
2
+ require_relative 'attributes'
3
+ require_relative 'conversion'
4
+ require_relative 'validator'
5
+
6
+ module Indexer
7
+
8
+ # Conventional interface for specification provides convenience
9
+ # for developers. It offers method aliases and models various parts
10
+ # of the specification with useful classes.
11
+ #
12
+ class Metadata < Model
13
+ extend Loadable
14
+
15
+ include Attributes
16
+ include Conversion
17
+
18
+ class << self
19
+ private
20
+ alias :create :new
21
+ end
22
+
23
+ #
24
+ # Revision factory returns a versioned instance of the model class.
25
+ #
26
+ # @param [Hash] data
27
+ # The data to populate the instance.
28
+ #
29
+ def self.new(data={})
30
+ data = revise(data)
31
+ super(data)
32
+ end
33
+
34
+ #
35
+ # Load metadata, ensuring canoncial validity.
36
+ #
37
+ # @param [String] data
38
+ # The data to be validate and then to populate the instance.
39
+ #
40
+ def self.valid(data)
41
+ data = revise(data)
42
+ data = Validator.new(data).to_h
43
+ create(data)
44
+ end
45
+
46
+ #
47
+ # Update metadata to current revision if using old revision.
48
+ #
49
+ def self.revise(data)
50
+ Revision.upconvert(data)
51
+ end
52
+
53
+ #
54
+ # Create a new Meta::Spec given a Gem::Specification or .gemspec file.
55
+ #
56
+ # @param [Gem::Specification,String] gemspec
57
+ # RubyGems Gem::Specification object or path to .gemspec file.
58
+ #
59
+ def self.from_gemspec(gemspec)
60
+ new.import_gemspec(gemspec)
61
+ end
62
+
63
+
64
+ # -- Writers ------------------------------------------------------------
65
+
66
+ #
67
+ # Set the revision. This is in a sense a dummy setting, since the actual
68
+ # revision is alwasy the latest.
69
+ #
70
+ def revision=(value)
71
+ @data['revision'] = value.to_i
72
+ end
73
+
74
+ #
75
+ # Set the revision. This is in a sense a dummy setting, since the actual
76
+ # revision is alwasy the latest.
77
+ #
78
+ def type=(value)
79
+ Valid.type!(value, :type)
80
+ @data['type'] = type.to_str
81
+ end
82
+
83
+ #
84
+ # Sources for building index file.
85
+ #
86
+ # @param [String, Array] path(s)
87
+ # Paths from which metadata can be extracted.
88
+ #
89
+ def sources=(list)
90
+ @data[:sources] = [list].flatten
91
+ end
92
+
93
+ #
94
+ alias :source :sources
95
+ alias :source= :sources=
96
+
97
+ #
98
+ # Sets the name of the project.
99
+ #
100
+ # @param [String] name
101
+ # The new name of the project.
102
+ #
103
+ def name=(name)
104
+ name = name.to_s if Symbol === name
105
+ Valid.name!(name, :name)
106
+ @data[:name] = name.to_str.downcase
107
+ @data[:title] = @data[:name].capitalize unless @data[:title] # TODO: use #titlecase
108
+ @data[:name]
109
+ end
110
+
111
+ #
112
+ # Title is sanitized so that all white space is reduced to a
113
+ # single space character.
114
+ #
115
+ def title=(title)
116
+ Valid.oneline!(title, :title)
117
+ @data[:title] = title.to_str.gsub(/\s+/, ' ')
118
+ end
119
+
120
+ #
121
+ # The toplevel namespace of API, e.g. `module Foo` or `class Bar`,
122
+ # would be `"Foo"` or `"Bar"`, repectively.
123
+ #
124
+ # @param [String] namespace
125
+ # The new toplevel namespace of the project's API.
126
+ #
127
+ def namespace=(namespace)
128
+ Valid.constant!(namespace)
129
+ @data[:namespace] = namespace
130
+ end
131
+
132
+ #
133
+ # Summary is sanitized to only have one line of text.
134
+ #
135
+ def summary=(summary)
136
+ Valid.string!(summary, :summary)
137
+ @data[:summary] = summary.to_str.gsub(/\s+/, ' ')
138
+ end
139
+
140
+ #
141
+ # Sets the version of the project.
142
+ #
143
+ # @param [Hash, String, Array, Version::Number] version
144
+ # The version from the metadata file.
145
+ #
146
+ # @raise [ValidationError]
147
+ # The version must either be a `String`, `Hash` or `Array`.
148
+ #
149
+ def version=(version)
150
+ case version
151
+ when Version::Number
152
+ @data[:version] = version
153
+ when Hash
154
+ major = version['major'] || version[:major]
155
+ minor = version['minor'] || version[:minor]
156
+ patch = version['patch'] || version[:patch]
157
+ build = version['build'] || version[:build]
158
+ @data[:version] = Version::Number.new(major,minor,patch,build)
159
+ when String
160
+ @data[:version] = Version::Number.parse(version.to_s)
161
+ when Array
162
+ @data[:version] = Version::Number.new(*version)
163
+ else
164
+ raise(ValidationError,"version must be a Hash or a String")
165
+ end
166
+ end
167
+
168
+ #
169
+ # Codename is the name of the particular version.
170
+ #
171
+ def codename=(codename)
172
+ codename = codename.to_s if Symbol === codename
173
+ Valid.oneline!(codename, :codename)
174
+ @data[:codename] = codename.to_str
175
+ end
176
+
177
+ #
178
+ # Sets the production date of the project.
179
+ #
180
+ # @param [String,Date,Time,DateTime] date
181
+ # The production date for this version.
182
+ #
183
+ def date=(date)
184
+ @data[:date] = \
185
+ case date
186
+ when String
187
+ begin
188
+ Date.parse(date)
189
+ rescue ArgumentError
190
+ raise ValidationError, "invalid date for `date' - #{date.inspect}"
191
+ end
192
+ when Date, Time, DateTime
193
+ date
194
+ else
195
+ raise ValidationError, "invalid date for `date' - #{date.inspect}"
196
+ end
197
+ end
198
+
199
+ #
200
+ # Sets the creation date of the project.
201
+ #
202
+ # @param [String,Date,Time,DateTime] date
203
+ # The creation date of this project.
204
+ #
205
+ def created=(date)
206
+ @data[:created] = \
207
+ case date
208
+ when String
209
+ Valid.utc_date!(date)
210
+ Date.parse(date)
211
+ when Date, Time, DateTime
212
+ date
213
+ else
214
+ raise ValidationError, "invalid date for `created' - #{date.inspect}"
215
+ end
216
+ end
217
+
218
+ # Set the copyrights and licenses for the project.
219
+ #
220
+ # Copyrights SHOULD be in order of significance. The first license
221
+ # given is taken to be the project's primary license.
222
+ #
223
+ # License fields SHOULD be offically recognized identifiers such
224
+ # as "GPL-3.0" as defined by SPDX (http://).
225
+ #
226
+ # @example
227
+ # spec.copyrights = [
228
+ # {
229
+ # 'year' => '2010',
230
+ # 'holder' => 'Thomas T. Thomas',
231
+ # 'license' => "MIT"
232
+ # }
233
+ # ]
234
+ #
235
+ # @param [Array<Hash,String,Array>]
236
+ # The copyrights and licenses of the project.
237
+ #
238
+ def copyrights=(copyrights)
239
+ @data[:copyrights] = \
240
+ case copyrights
241
+ when String
242
+ [Copyright.parse(copyrights, @_license)]
243
+ when Hash
244
+ [Copyright.parse(copyrights, @_license)]
245
+ when Array
246
+ copyrights.map do |copyright|
247
+ Copyright.parse(copyright, @_license)
248
+ end
249
+ else
250
+ raise(ValidationError, "copyright must be a String, Hash or Array")
251
+ end
252
+ @data[:copyrights]
253
+ end
254
+
255
+ #
256
+ # Singular form of `#copyrights=`. This is similar to `#copyrights=`
257
+ # but expects the parameter to represent only one copyright.
258
+ #
259
+ def copyright=(copyright)
260
+ @data[:copyrights] = [Copyright.parse(copyright)]
261
+ end
262
+
263
+ # TODO: Should their be a "primary" license field ?
264
+
265
+ #
266
+ # Set copyright license for all copyright holders.
267
+ #
268
+ def license=(license)
269
+ if copyrights = @data[:copyrights]
270
+ copyrights.each do |c|
271
+ c.license = license # TODO: unless c.license ?
272
+ end
273
+ end
274
+ @_license = license
275
+ end
276
+
277
+ # Set the authors of the project.
278
+ #
279
+ # @param [Array<String>, String] authors
280
+ # The originating authors of the project.
281
+ #
282
+ def authors=(authors)
283
+ @data[:authors] = (
284
+ list = Array(authors).map do |a|
285
+ Author.parse(a)
286
+ end
287
+ warn "Duplicate authors listed" if list != list.uniq
288
+ list
289
+ )
290
+ end
291
+
292
+ #
293
+ alias author= authors=
294
+
295
+ #
296
+ # Set the orgnaization to which the project belongs.
297
+ #
298
+ # @param [String] organization
299
+ # The name of the organization.
300
+ #
301
+ def organizations=(organizations)
302
+ @data[:organizations] = (
303
+ list = Array(organizations).map do |org|
304
+ Organization.parse(org)
305
+ end
306
+ warn "Duplicate organizations listed" if list != list.uniq
307
+ list
308
+ )
309
+ end
310
+
311
+ #
312
+ alias :organization= :organizations=
313
+
314
+ # Company is a typical synonym for organization.
315
+ alias :company :organizations
316
+ alias :company= :organizations=
317
+ alias :companies :organizations=
318
+ alias :companies= :organizations=
319
+
320
+ # TODO: should we warn if directory does not exist?
321
+
322
+ # Sets the require paths of the project.
323
+ #
324
+ # @param [Array<String>, String] paths
325
+ # The require-paths or a glob-pattern.
326
+ #
327
+ def load_path=(paths)
328
+ @data[:load_path] = \
329
+ Array(paths).map do |path|
330
+ Valid.path!(path)
331
+ path
332
+ end
333
+ end
334
+
335
+ # List of language engine/version family supported.
336
+ def engines=(value)
337
+ @data[:engines] = (
338
+ a = [value].flatten
339
+ a.each{ |x| Valid.oneline!(x) }
340
+ a
341
+ )
342
+ end
343
+
344
+ #
345
+ # List of platforms supported.
346
+ #
347
+ # @deprecated
348
+ #
349
+ def platforms=(value)
350
+ @data[:platforms] = (
351
+ a = [value].flatten
352
+ a.each{ |x| Valid.oneline!(x) }
353
+ a
354
+ )
355
+ end
356
+
357
+ #
358
+ # Sets the requirements of the project. Also commonly called dependencies.
359
+ #
360
+ # @param [Array<Hash>, Hash{String=>Hash}, Hash{String=>String}] requirements
361
+ # The requirement details.
362
+ #
363
+ # @raise [ValidationError]
364
+ # The requirements must be an `Array` or `Hash`.
365
+ #
366
+ def requirements=(requirements)
367
+ requirements = [requirements] if String === requirements
368
+ case requirements
369
+ when Array, Hash
370
+ @data[:requirements].clear
371
+ requirements.each do |specifics|
372
+ @data[:requirements] << Requirement.parse(specifics)
373
+ end
374
+ else
375
+ raise(ValidationError,"requirements must be an Array or Hash")
376
+ end
377
+ end
378
+
379
+ #
380
+ # Dependencies is an alias for requirements.
381
+ #
382
+ alias :dependencies :requirements
383
+ alias :dependencies= :requirements=
384
+
385
+ #
386
+ # Sets the packages with which this package is known to have
387
+ # incompatibilites.
388
+ #
389
+ # @param [Array<Hash>, Hash{String=>String}] conflicts
390
+ # The conflicts for the project.
391
+ #
392
+ # @raise [ValidationError]
393
+ # The conflicts list must be an `Array` or `Hash`.
394
+ #
395
+ # @todo lets get rid of the type check here and let the #parse method do it.
396
+ #
397
+ def conflicts=(conflicts)
398
+ case conflicts
399
+ when Array, Hash
400
+ @data[:conflicts].clear
401
+ conflicts.each do |specifics|
402
+ @data[:conflicts] << Conflict.parse(specifics)
403
+ end
404
+ else
405
+ raise(ValidationError, "conflicts must be an Array or Hash")
406
+ end
407
+ end
408
+
409
+ #
410
+ # Sets the packages this package could (more or less) replace.
411
+ #
412
+ # @param [Array<String>] alternatives
413
+ # The alternatives for the project.
414
+ #
415
+ def alternatives=(alternatives)
416
+ Valid.array!(alternatives, :alternatives)
417
+
418
+ @data[:alternatives].clear
419
+
420
+ alternatives.to_ary.each do |name|
421
+ @data[:alternatives] << name.to_s
422
+ end
423
+ end
424
+
425
+ #
426
+ # Sets the categories for this project.
427
+ #
428
+ # @param [Array<String>] categories
429
+ # List of purpose categories for the project.
430
+ #
431
+ def categories=(categories)
432
+ categories = Array(categories)
433
+
434
+ @data[:categories].clear
435
+
436
+ categories.to_ary.each do |name|
437
+ Valid.oneline!(name.to_s)
438
+ @data[:categories] << name.to_s
439
+ end
440
+ end
441
+
442
+ #
443
+ # Suite must be a single line string.
444
+ #
445
+ # @param [String] suite
446
+ # The suite to which the project belongs.
447
+ #
448
+ def suite=(value)
449
+ Valid.oneline!(value, :suite)
450
+ @data[:suite] = value
451
+ end
452
+
453
+ #
454
+ # Sets the repostiories this project has.
455
+ #
456
+ # @param [Array<String>, Hash] repositories
457
+ # The repositories for the project.
458
+ #
459
+ def repositories=(repositories)
460
+ case repositories
461
+ when Hash, Array
462
+ @data[:repositories].clear
463
+ repositories.each do |specifics|
464
+ @data[:repositories] << Repository.parse(specifics)
465
+ end
466
+ else
467
+ raise(ValidationError, "repositories must be an Array or Hash")
468
+ end
469
+ end
470
+
471
+ #
472
+ # Set the resources for the project.
473
+ #
474
+ # @param [Array,Hash] resources
475
+ # A list or map of resources.
476
+ #
477
+ def resources=(resources)
478
+ case resources
479
+ when Array
480
+ @data[:resources].clear
481
+ resources.each do |data|
482
+ @data[:resources] << Resource.parse(data)
483
+ end
484
+ when Hash
485
+ @data[:resources].clear
486
+ resources.each do |type, uri|
487
+ @data[:resources] << Resource.new(:uri=>uri, :type=>type.to_s)
488
+ end
489
+ else
490
+ raise(ValidationError, "repositories must be an Array or Hash")
491
+ end
492
+ end
493
+
494
+ #
495
+ # The webcvs prefix must be a valid URI.
496
+ #
497
+ # @param [String] uri
498
+ # The webcvs prefix, which must be a valid URI.
499
+ #
500
+ def webcvs=(uri)
501
+ Valid.uri!(uri, :webcvs)
502
+ @data[:webcvs] = uri
503
+ end
504
+
505
+ #
506
+ # Sets the post-install message of the project.
507
+ #
508
+ # @param [Array, String] message
509
+ # The post-installation message.
510
+ #
511
+ # @return [String]
512
+ # The new post-installation message.
513
+ #
514
+ def install_message=(message)
515
+ @data[:install_message] = \
516
+ case message
517
+ when Array
518
+ message.join($/)
519
+ else
520
+ Valid.string!(message)
521
+ message.to_str
522
+ end
523
+ end
524
+
525
+ ##
526
+ ## Set extraneous developer-defined metdata.
527
+ ##
528
+ #def extra=(extra)
529
+ # unless extra.kind_of?(Hash)
530
+ # raise(ValidationError, "extra must be a Hash")
531
+ # end
532
+ # @data[:extra] = extra
533
+ #end
534
+
535
+ # -- Utility Methods ----------------------------------------------------
536
+
537
+ #
538
+ # Adds a new requirement.
539
+ #
540
+ # @param [String] name
541
+ # The name of the requirement.
542
+ #
543
+ # @param [Hash] specifics
544
+ # The specifics of the requirement.
545
+ #
546
+ def add_requirement(name, specifics)
547
+ requirements << Requirement.parse([name, specifics])
548
+ end
549
+
550
+ #
551
+ # Same as #add_requirement.
552
+ #
553
+ # @param [String] name
554
+ # The name of the dependency.
555
+ #
556
+ # @param [Hash] specifics
557
+ # The specifics of the dependency.
558
+ #
559
+ alias add_dependency add_requirement
560
+
561
+ #
562
+ # Adds a new conflict.
563
+ #
564
+ # @param [String] name
565
+ # The name of the conflict package.
566
+ #
567
+ # @param [Hash] specifics
568
+ # The specifics of the conflict package.
569
+ #
570
+ def add_conflict(name, specifics)
571
+ conflicts << Requirement.parse([name, specifics])
572
+ end
573
+
574
+ #
575
+ # Adds a new alternative.
576
+ #
577
+ # @param [String] name
578
+ # The name of the alternative.
579
+ #
580
+ def add_alternative(name)
581
+ alternatives << name.to_s
582
+ end
583
+
584
+ #
585
+ #
586
+ #
587
+ def add_repository(id, url, scm=nil)
588
+ repositories << Repository.parse(:id=>id, :url=>url, :scm=>scm)
589
+ end
590
+
591
+ #
592
+ # A specification is not valid without a name and version.
593
+ #
594
+ # @return [Boolean] valid specification?
595
+ #
596
+ def valid?
597
+ return false unless name
598
+ return false unless version
599
+ true
600
+ end
601
+
602
+ # TODO: What was used for again, load_path ?
603
+ =begin
604
+ #
605
+ # Iterates over the paths.
606
+ #
607
+ # @param [Array<String>, String] paths
608
+ # The paths or path glob pattern to iterate over.
609
+ #
610
+ # @yield [path]
611
+ # The given block will be passed each individual path.
612
+ #
613
+ # @yieldparam [String] path
614
+ # An individual path.
615
+ #
616
+ def each_path(paths,&block)
617
+ case paths
618
+ when Array
619
+ paths.each(&block)
620
+ when String
621
+ Dir.glob(paths,&block) # TODO: should we be going this?
622
+ else
623
+ raise(ValidationError, "invalid path")
624
+ end
625
+ end
626
+
627
+ private :each_path
628
+ =end
629
+
630
+ # -- Calculations -------------------------------------------------------
631
+
632
+ #
633
+ # The primary copyright of the project.
634
+ #
635
+ # @return [String]
636
+ # The primary copyright for the project.
637
+ #
638
+ def copyright
639
+ copyrights.join("\n")
640
+ end
641
+
642
+ #
643
+ # The primary email address of the project. The email address
644
+ # is taken from the first listed author.
645
+ #
646
+ # @return [String, nil]
647
+ # The primary email address for the project.
648
+ #
649
+ def email
650
+ authors.find{ |a| a.email }
651
+ end
652
+
653
+ #
654
+ # Get homepage URI.
655
+ #
656
+ def homepage #(uri=nil)
657
+ #uri ? self.homepage = url
658
+ resources.each do |r|
659
+ if r.type == 'home'
660
+ return r.uri
661
+ end
662
+ end
663
+ end
664
+ alias_method :website, :homepage
665
+
666
+ #
667
+ # Convenience method for setting a `hompage` resource.
668
+ #
669
+ # @todo Rename to website?
670
+ #
671
+ def homepage=(uri)
672
+ resource = Resource.new(:name=>'homepage', :type=>'home', :uri=>uri)
673
+ resources.unshift(resource)
674
+ uri
675
+ end
676
+ alias_method :website=, :homepage=
677
+
678
+ #
679
+ # Returns the runtime requirements of the project.
680
+ #
681
+ # @return [Array<Requirement>] runtime requirements.
682
+ def runtime_requirements
683
+ requirements.select do |requirement|
684
+ requirement.runtime?
685
+ end
686
+ end
687
+
688
+ #
689
+ # Returns the development requirements of the project.
690
+ #
691
+ # @return [Array<Requirement>] development requirements.
692
+ #
693
+ def development_requirements
694
+ requirements.select do |requirement|
695
+ requirement.development?
696
+ end
697
+ end
698
+
699
+ #
700
+ # Returns the runtime requirements of the project.
701
+ #
702
+ # @return [Array<Requirement>] runtime requirements.
703
+ def external_requirements
704
+ requirements.select do |requirement|
705
+ requirement.external?
706
+ end
707
+ end
708
+
709
+ #
710
+ # Returns the external runtime requirements of the project.
711
+ #
712
+ # @return [Array<Requirement>] external runtime requirements.
713
+ def external_runtime_requirements
714
+ requirements.select do |requirement|
715
+ requirement.external? && requirement.runtime?
716
+ end
717
+ end
718
+
719
+ #
720
+ # Returns the external development requirements of the project.
721
+ #
722
+ # @return [Array<Requirement>] external development requirements.
723
+ def external_development_requirements
724
+ requirements.select do |requirement|
725
+ requirement.external? && requirement.development?
726
+ end
727
+ end
728
+
729
+ # -- Aliases ------------------------------------------------------------
730
+
731
+ #
732
+ #
733
+ # @return [Array] load paths
734
+ def loadpath
735
+ load_path
736
+ end
737
+
738
+ #
739
+ # RubyGems term for #load_path.
740
+ #
741
+ def loadpath=(value)
742
+ self.load_path = value
743
+ end
744
+
745
+ #
746
+ #
747
+ # @return [Array] load paths
748
+ def require_paths
749
+ load_path
750
+ end
751
+
752
+ #
753
+ # RubyGems term for #load_path.
754
+ #
755
+ def require_paths=(value)
756
+ self.load_path = value
757
+ end
758
+
759
+ #
760
+ # Alternate short name for #requirements.
761
+ #
762
+ def requires
763
+ requirements
764
+ end
765
+
766
+ #
767
+ # Alternate short name for #requirements.
768
+ #
769
+ def requires=(value)
770
+ self.requirements = value
771
+ end
772
+
773
+ #
774
+ # Alternate singular form of #engines.
775
+ #
776
+ def engine=(value)
777
+ self.engines = value
778
+ end
779
+
780
+ #
781
+ # Alternate singular form of #platforms.
782
+ #
783
+ def platform=(value)
784
+ self.platforms = value
785
+ end
786
+
787
+ # -- Conversion ---------------------------------------------------------
788
+
789
+ #
790
+ # Convert convenience form of metadata to canonical form.
791
+ #
792
+ # @todo: A more robust means of insuring sources never self references.
793
+ #
794
+ # @todo: Make sure this *always* generates the canonical form.
795
+ #
796
+ def to_h
797
+ date = self.date || Time.now
798
+
799
+ h = super
800
+
801
+ h['revision'] = REVISION
802
+ h['sources' ] = sources - ['.index'] # avoid self reference
803
+
804
+ h['version'] = version.to_s
805
+
806
+ h['date'] = date.strftime('%Y-%m-%d')
807
+ h['created'] = created.strftime('%Y-%m-%d') if created
808
+
809
+ h['authors'] = authors.map { |x| x.to_h }
810
+ h['organizations'] = organizations.map { |x| x.to_h }
811
+ h['copyrights'] = copyrights.map { |x| x.to_h }
812
+ h['requirements'] = requirements.map { |x| x.to_h }
813
+ h['conflicts'] = conflicts.map { |x| x.to_h }
814
+ h['repositories'] = repositories.map { |x| x.to_h }
815
+ h['resources'] = resources.map { |x| x.to_h }
816
+
817
+ h
818
+ end
819
+
820
+ # Create nicely formated project "about" text.
821
+ #
822
+ # @return [String] Formatted about text.
823
+ #
824
+ def about(*parts)
825
+ s = []
826
+ parts = [:header, :description, :resources, :copyright] if parts.empty?
827
+ parts.each do |part|
828
+ case part.to_sym
829
+ when :header
830
+ s << "%s %s (%s-%s)" % [title, version, name, version]
831
+ when :title
832
+ s << title
833
+ when :package
834
+ s << "%s-%s" % [name, version]
835
+ when :description
836
+ s << description || summary
837
+ when :summary
838
+ s << summary
839
+ when :resources
840
+ s << resources.map{ |resource|
841
+ "%s: %s" % [resource.label || resource.type, resource.uri]
842
+ }.join("\n")
843
+ when :repositories
844
+ s << repositories.map{ |repo|
845
+ "%s" % [repo.uri]
846
+ }.join("\n")
847
+ when :copyright, :copyrights
848
+ s << copyrights.map{ |c|
849
+ "Copyright (c) %s %s (%s)" % [c.year, c.holder, c.license]
850
+ }.join("\n")
851
+ else
852
+ s << __send__(part)
853
+ end
854
+ end
855
+ s.join("\n\n")
856
+ end
857
+
858
+ # Save metadata lock file.
859
+ #
860
+ # @param [String] file
861
+ # The file name in which to save the metadata as YAML.
862
+ #
863
+ def save!(file=LOCK_FILE)
864
+ v = Validator.new(to_h)
865
+ v.save!(file)
866
+ end
867
+
868
+ protected
869
+
870
+ #
871
+ # Initializes the {Metadata} attributes.
872
+ #
873
+ def initialize_attributes
874
+ super
875
+ end
876
+
877
+ end
878
+
879
+ end