indexer 0.1.0

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