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.
- data/.index +54 -0
- data/HISTORY.md +9 -0
- data/README.md +145 -0
- data/bin/index +7 -0
- data/data/indexer/r2013/index.kwalify +175 -0
- data/data/indexer/r2013/index.yes +172 -0
- data/data/indexer/r2013/index.yesi +67 -0
- data/data/indexer/r2013/ruby.txt +35 -0
- data/data/indexer/r2013/yaml.txt +30 -0
- data/lib/indexer.rb +65 -0
- data/lib/indexer/attributes.rb +171 -0
- data/lib/indexer/command.rb +260 -0
- data/lib/indexer/components.rb +8 -0
- data/lib/indexer/components/author.rb +140 -0
- data/lib/indexer/components/conflict.rb +78 -0
- data/lib/indexer/components/copyright.rb +95 -0
- data/lib/indexer/components/dependency.rb +18 -0
- data/lib/indexer/components/organization.rb +133 -0
- data/lib/indexer/components/repository.rb +140 -0
- data/lib/indexer/components/requirement.rb +360 -0
- data/lib/indexer/components/resource.rb +209 -0
- data/lib/indexer/conversion.rb +14 -0
- data/lib/indexer/conversion/gemfile.rb +44 -0
- data/lib/indexer/conversion/gemspec.rb +114 -0
- data/lib/indexer/conversion/gemspec_exporter.rb +304 -0
- data/lib/indexer/core_ext.rb +4 -0
- data/lib/indexer/error.rb +23 -0
- data/lib/indexer/gemfile.rb +75 -0
- data/lib/indexer/importer.rb +144 -0
- data/lib/indexer/importer/file.rb +94 -0
- data/lib/indexer/importer/gemfile.rb +27 -0
- data/lib/indexer/importer/gemspec.rb +43 -0
- data/lib/indexer/importer/html.rb +289 -0
- data/lib/indexer/importer/markdown.rb +45 -0
- data/lib/indexer/importer/ruby.rb +47 -0
- data/lib/indexer/importer/version.rb +38 -0
- data/lib/indexer/importer/yaml.rb +46 -0
- data/lib/indexer/loadable.rb +159 -0
- data/lib/indexer/metadata.rb +879 -0
- data/lib/indexer/model.rb +237 -0
- data/lib/indexer/revision.rb +43 -0
- data/lib/indexer/valid.rb +287 -0
- data/lib/indexer/validator.rb +313 -0
- data/lib/indexer/version/constraint.rb +124 -0
- data/lib/indexer/version/exceptions.rb +11 -0
- data/lib/indexer/version/number.rb +497 -0
- 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
|