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,237 @@
|
|
1
|
+
module Indexer
|
2
|
+
|
3
|
+
class Model
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
=begin
|
8
|
+
#
|
9
|
+
# Revision factory returns a versioned instance of the model class.
|
10
|
+
#
|
11
|
+
# @param [Hash] data
|
12
|
+
# The data to populate the instance.
|
13
|
+
#
|
14
|
+
def new(data={})
|
15
|
+
revision, data = revised(data)
|
16
|
+
basename = name.split('::').last
|
17
|
+
V[revision].const_get(basename).new(data)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Revision factory returns a validated versioned instance of the model class.
|
22
|
+
#
|
23
|
+
# @param [Hash] data
|
24
|
+
# The data to populate the instance.
|
25
|
+
#
|
26
|
+
def valid(data)
|
27
|
+
revision, data = revised(data)
|
28
|
+
basename = name.split('::').last
|
29
|
+
V[revision].const_get(basename).valid(data)
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# When Model is inherited alias `#valid` to `#new` and setup an inherited
|
34
|
+
# callback for subclass which will do the same for `#new`.
|
35
|
+
#
|
36
|
+
# @param [Class] child
|
37
|
+
# The subclass inheriting Model.
|
38
|
+
#
|
39
|
+
def inherited(child)
|
40
|
+
def child.inherited(child)
|
41
|
+
class << child
|
42
|
+
alias :new :_new
|
43
|
+
alias :valid :_new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
=end
|
48
|
+
|
49
|
+
#
|
50
|
+
#
|
51
|
+
#
|
52
|
+
def attr_reader(name)
|
53
|
+
module_eval %{
|
54
|
+
def #{name}
|
55
|
+
@data[:#{name}]
|
56
|
+
end
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
#
|
62
|
+
#
|
63
|
+
alias :attr :attr_reader
|
64
|
+
|
65
|
+
#
|
66
|
+
#
|
67
|
+
#
|
68
|
+
def attr_writer(name)
|
69
|
+
module_eval %{
|
70
|
+
def #{name}=(value)
|
71
|
+
@data[:#{name}] = value
|
72
|
+
end
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# New instance.
|
80
|
+
#
|
81
|
+
# @param [Hash] data
|
82
|
+
# The metadata to populate the instance.
|
83
|
+
#
|
84
|
+
def initialize(data={})
|
85
|
+
initialize_attributes
|
86
|
+
|
87
|
+
merge!(data)
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Set default attributes.
|
92
|
+
#
|
93
|
+
def initialize_attributes
|
94
|
+
@data = {}
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Access metadata with hash-like getter method.
|
99
|
+
#
|
100
|
+
# @param [String, Symbol] key
|
101
|
+
# The name of the metadata field.
|
102
|
+
#
|
103
|
+
# @return [Object]
|
104
|
+
# The value associated with the key.
|
105
|
+
#
|
106
|
+
def [](key)
|
107
|
+
if respond_to?(key)
|
108
|
+
__send__("#{key}")
|
109
|
+
else
|
110
|
+
@data[key.to_sym]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# Assign metadata with hash-like setter method.
|
116
|
+
#
|
117
|
+
# @param [String, Symbol] key
|
118
|
+
# The name of the metadata field.
|
119
|
+
#
|
120
|
+
# @param [Object] value
|
121
|
+
# The value of the metadata field.
|
122
|
+
#
|
123
|
+
# @return [Object]
|
124
|
+
# The newly set value.
|
125
|
+
#
|
126
|
+
# @raise [ArgumentError]
|
127
|
+
# The key is not known.
|
128
|
+
#
|
129
|
+
def []=(key, value)
|
130
|
+
#unless self.class.attributes.include?(key)
|
131
|
+
# #error = ArgumentError.new("unknown attribute: #{key.inspect}")
|
132
|
+
# #error.extend Error
|
133
|
+
# #raise(error)
|
134
|
+
#end
|
135
|
+
if respond_to?("#{key}=")
|
136
|
+
__send__("#{key}=", value)
|
137
|
+
else
|
138
|
+
@data[key.to_sym] = value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Merge data source into metadata.
|
144
|
+
#
|
145
|
+
# @param [Hash] data
|
146
|
+
# The data source which responds to #each like a Hash.
|
147
|
+
#
|
148
|
+
def merge!(data)
|
149
|
+
data.each do |key, value|
|
150
|
+
self[key] = value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
#
|
156
|
+
#
|
157
|
+
def key?(name)
|
158
|
+
@data.key?(name.to_sym)
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Convert metadata to a Hash.
|
163
|
+
#
|
164
|
+
# @return [Hash{String => Object}]
|
165
|
+
#
|
166
|
+
def to_h
|
167
|
+
h = {}
|
168
|
+
#self.class.attributes.each do |key|
|
169
|
+
# h[key.to_s] = __send__(name)
|
170
|
+
#end
|
171
|
+
@data.each do |k, v|
|
172
|
+
h[k.to_s] = v if v
|
173
|
+
end
|
174
|
+
h
|
175
|
+
end
|
176
|
+
|
177
|
+
#
|
178
|
+
# Converts the Hash-like object to YAML.
|
179
|
+
#
|
180
|
+
# @param [Hash] opts
|
181
|
+
# Options used by YAML.
|
182
|
+
#
|
183
|
+
# TODO: Should we have #to_yaml in here?
|
184
|
+
def to_yaml(io={})
|
185
|
+
to_h.to_yaml(io)
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
# Models are open collections. Any arbitrary settings can be made
|
190
|
+
# in order to support non-specification indexing.
|
191
|
+
#
|
192
|
+
def method_missing(sym, *args, &blk)
|
193
|
+
super(sym, *args, &blk) if blk
|
194
|
+
name = sym.to_s
|
195
|
+
type = name[-1,1]
|
196
|
+
|
197
|
+
case type
|
198
|
+
when '='
|
199
|
+
value = (
|
200
|
+
case v = args.first
|
201
|
+
when String then String(v)
|
202
|
+
when Integer then Integer(v)
|
203
|
+
when Float then Float(v)
|
204
|
+
when Array then Array(v)
|
205
|
+
when Hash then Hash(v)
|
206
|
+
else
|
207
|
+
raise ValidationError, "custom metadata for `#{sym}' not simple type -- `#{value.class}'"
|
208
|
+
end
|
209
|
+
)
|
210
|
+
@data[name.chomp('=').to_sym] = value
|
211
|
+
when '!'
|
212
|
+
super(sym, *args, &blk)
|
213
|
+
when '?'
|
214
|
+
super(sym, *args, &blk)
|
215
|
+
else
|
216
|
+
key?(name) ? @data[name.to_sym] : nil #super(sym, *args, &blk)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
#
|
223
|
+
def store(key, value)
|
224
|
+
@data[key.to_sym] = value
|
225
|
+
end
|
226
|
+
|
227
|
+
#
|
228
|
+
def validate(value, field, *types)
|
229
|
+
types.each do |type|
|
230
|
+
Valid.send(type, value, field)
|
231
|
+
end
|
232
|
+
return value
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Indexer
|
2
|
+
|
3
|
+
# Indexer's revision strategy is a "no project left behind" strategy.
|
4
|
+
# When using the API, is a specification is loaded that is outdated,
|
5
|
+
# it will be upconverted to the latest standard.
|
6
|
+
#
|
7
|
+
# If you absolutely *must* support an old revision then use an older
|
8
|
+
# version of Indexer, or work with the metadata manually (via YAML).
|
9
|
+
#
|
10
|
+
module Revision
|
11
|
+
extend self
|
12
|
+
|
13
|
+
#
|
14
|
+
# Revise the metadata to current standard.
|
15
|
+
#
|
16
|
+
def upconvert(data)
|
17
|
+
revision = data['revision'] || data[:revision]
|
18
|
+
|
19
|
+
revision = REVISION unless revision
|
20
|
+
|
21
|
+
if revision != REVISION
|
22
|
+
begin
|
23
|
+
require "revisions/r#{revision}"
|
24
|
+
__send__("r#{revision}", data)
|
25
|
+
rescue LoadError
|
26
|
+
raise ValidationError, "unknown revision #{revision}"
|
27
|
+
end
|
28
|
+
else
|
29
|
+
data
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Update R2013 specification to current specification.
|
35
|
+
#
|
36
|
+
def r2013(data)
|
37
|
+
data
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,287 @@
|
|
1
|
+
module Indexer
|
2
|
+
|
3
|
+
# Validation functions.
|
4
|
+
module Valid
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Valid name regular expression.
|
8
|
+
TYPE = /^[A-Za-z][\/:A-Za-z0-9_-]*[A-Za-z0-9]$/
|
9
|
+
|
10
|
+
# Valid name regular expression.
|
11
|
+
NAME = /^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$/
|
12
|
+
|
13
|
+
# Valid URL regular expression.
|
14
|
+
URL = /^(\w+)\:\/\/\S+$/
|
15
|
+
|
16
|
+
# Valid IRC channel.
|
17
|
+
IRC = /^\#\w+$/
|
18
|
+
|
19
|
+
# Regular expression for matching valid email addresses.
|
20
|
+
EMAIL = /\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i #/<.*?>/
|
21
|
+
|
22
|
+
# FIXME: Regular expression to limit date-time fields to ISO 8601 (Zulu).
|
23
|
+
DATE = /^\d\d\d\d-\d\d-\d\d(\s+\d\d:\d\d:\d\d)?$/
|
24
|
+
|
25
|
+
#
|
26
|
+
def type?(type)
|
27
|
+
TYPE =~ type
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
def type!(type, field=nil)
|
32
|
+
string!(type, field)
|
33
|
+
raise_invalid("type", type, field) unless type?(name)
|
34
|
+
return type
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
def name?(name)
|
39
|
+
NAME =~ name
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
def name!(name, field=nil)
|
44
|
+
string!(name, field)
|
45
|
+
raise_invalid("name", name, field) unless name?(name)
|
46
|
+
return name
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
def url?(url)
|
51
|
+
URL =~ url
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
def url!(url, field=nil)
|
56
|
+
raise_invalid("URL", url, field) unless url?(url)
|
57
|
+
return url
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
def irc?(irc)
|
62
|
+
IRC =~ irc
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
def irc!(irc, field=nil)
|
67
|
+
raise_invalid("IRC", irc, field) unless irc?(irc)
|
68
|
+
return irc
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
def uri?(uri)
|
73
|
+
url?(uri) || irc?(uri)
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
def uri!(uri, field=nil)
|
78
|
+
raise_invalid("URI", uri, field) unless uri?(uri)
|
79
|
+
return uri
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
def email?(email)
|
84
|
+
EMAIL =~ email
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
def email!(email, field=nil)
|
89
|
+
unless email?(email)
|
90
|
+
raise_invalid("email address", email, field)
|
91
|
+
end
|
92
|
+
return email
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
def oneline?(string)
|
97
|
+
string?(string) && !string.index("\n")
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
def oneline!(string, field=nil)
|
102
|
+
unless oneline?(string)
|
103
|
+
raise_invalid("one line string", string, field)
|
104
|
+
end
|
105
|
+
return string
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
def string?(string)
|
110
|
+
String === string
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
def string!(string, field=nil)
|
115
|
+
unless string?(string)
|
116
|
+
raise_invalid("string", string, field)
|
117
|
+
end
|
118
|
+
return string
|
119
|
+
end
|
120
|
+
|
121
|
+
# TODO: Should we bother with #to_ary?
|
122
|
+
def array?(array)
|
123
|
+
Array === array || array.respond_to?(:to_ary)
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
def array!(array, field=nil)
|
128
|
+
unless array?(array)
|
129
|
+
raise_invalid("array", array, field)
|
130
|
+
end
|
131
|
+
return array
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
def hash?(hash)
|
136
|
+
Hash === hash
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
def hash!(hash, field=nil)
|
141
|
+
unless hash?(hash)
|
142
|
+
raise_invalid("hash", hash, field)
|
143
|
+
end
|
144
|
+
return hash
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
def word?(word, field=nil)
|
149
|
+
return false unless string?(word)
|
150
|
+
return false if /^[A-Za-z]/ !~ word
|
151
|
+
return false if /[A-Za-z0-9]$/ !~ word
|
152
|
+
return false if /[^A-Za-z0-9_-]/ =~ word
|
153
|
+
return true
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
#--
|
158
|
+
# TODO: Do we really need to be so detailed about the error?
|
159
|
+
# Doing so prevent us from using #word? here.
|
160
|
+
#++
|
161
|
+
def word!(word, field=nil)
|
162
|
+
string!(word, field)
|
163
|
+
raise_invalid_message("#{field} must start with a letter -- #{word}") if /^[A-Za-z]/ !~ word
|
164
|
+
raise_invalid_message("#{field} must end with a letter or number -- #{word}") if /[A-Za-z0-9]$/ !~ word
|
165
|
+
raise_invalid_message("#{field} must be a word -- #{word}") if /[^A-Za-z0-9_-]/ =~ word
|
166
|
+
return word
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
#def integer_string?(integer)
|
171
|
+
# /^\d+$/ =~ integer
|
172
|
+
#end
|
173
|
+
|
174
|
+
# TODO: This is probably the wrong name for iso8601
|
175
|
+
def utc_date?(date)
|
176
|
+
return false unless string?(date)
|
177
|
+
return false unless DATE =~ date
|
178
|
+
begin
|
179
|
+
Time.parse(date)
|
180
|
+
rescue
|
181
|
+
return false
|
182
|
+
end
|
183
|
+
true
|
184
|
+
end
|
185
|
+
|
186
|
+
# TODO: This is probably the wrong name for iso8601
|
187
|
+
def utc_date!(date, field=nil)
|
188
|
+
unless utc_date?(date)
|
189
|
+
raise_invalid("ISO 8601 formatted date", date, field)
|
190
|
+
end
|
191
|
+
return date
|
192
|
+
end
|
193
|
+
|
194
|
+
# Four digit year.
|
195
|
+
def copyright_year?(year)
|
196
|
+
year = year.to_s
|
197
|
+
return true if /^\d\d\d\d$/ =~ year
|
198
|
+
return true if /^\d\d\d\d\-\d\d\d\d$/ =~ year
|
199
|
+
return true if /^\d\d\d\d(\,\d\d\d\d)+$/ =~ year
|
200
|
+
false
|
201
|
+
end
|
202
|
+
|
203
|
+
# Four digit year.
|
204
|
+
def copyright_year!(year, field=nil)
|
205
|
+
unless copyright_year?(year)
|
206
|
+
raise_invalid("copyright year", year, field)
|
207
|
+
end
|
208
|
+
return year
|
209
|
+
end
|
210
|
+
|
211
|
+
#
|
212
|
+
def version_string?(string)
|
213
|
+
return false unless string?(string)
|
214
|
+
return false if /^\D/ =~ string
|
215
|
+
return false if /[^.A-Za-z0-9]/ =~ string
|
216
|
+
return true
|
217
|
+
end
|
218
|
+
|
219
|
+
#
|
220
|
+
def version_string!(value, field=nil)
|
221
|
+
string!(value, field)
|
222
|
+
case value
|
223
|
+
when /^\D/
|
224
|
+
raise_invalid_message("#{field} must start with number - #{value.inspect}")
|
225
|
+
when /[^.A-Za-z0-9]/
|
226
|
+
raise_invalid_message("#{field} contains invalid characters - #{value.inspect}")
|
227
|
+
end
|
228
|
+
return value
|
229
|
+
end
|
230
|
+
|
231
|
+
# FIXME: better validation for path
|
232
|
+
def path?(path)
|
233
|
+
return false unless string?(path)
|
234
|
+
return true
|
235
|
+
end
|
236
|
+
|
237
|
+
#
|
238
|
+
def path!(path, field=nil)
|
239
|
+
unless path?(path)
|
240
|
+
raise_invalid("path", path, field)
|
241
|
+
end
|
242
|
+
return path
|
243
|
+
end
|
244
|
+
|
245
|
+
#
|
246
|
+
# TODO: Only allow double colons.
|
247
|
+
def constant?(name)
|
248
|
+
name = name.to_s if Symbol === name
|
249
|
+
/^[A-Z][A-Za-z0-9_:]*/ =~ name
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
def constant!(name, field=nil)
|
254
|
+
unless constant?(name)
|
255
|
+
raise_invalid("constant name", name, field)
|
256
|
+
end
|
257
|
+
return name
|
258
|
+
end
|
259
|
+
|
260
|
+
#
|
261
|
+
def raise_invalid(type, value, field=nil)
|
262
|
+
case field
|
263
|
+
when Exception
|
264
|
+
raise(field)
|
265
|
+
else
|
266
|
+
if field
|
267
|
+
message = "invalid %s for `%s' - %s" % [type, field, value.inspect]
|
268
|
+
else
|
269
|
+
message = "invalid %s - %s" % [type, value.inspect]
|
270
|
+
end
|
271
|
+
raise(ValidationError, message.strip)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
#
|
276
|
+
def raise_invalid_message(message)
|
277
|
+
raise(ValidationError, message.strip)
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
# Use this error for all validation errors when reading a spec.
|
283
|
+
class ValidationError < ArgumentError
|
284
|
+
include Error
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|