tpkg 1.18.2 → 1.19.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +2 -1
- data/bin/cpan2tpkg +12 -1
- data/bin/gem2tpkg +89 -46
- data/bin/tpkg +5 -1
- data/bin/tpkg_xml_to_yml +3 -0
- data/lib/tpkg.rb +458 -241
- data/lib/tpkg/metadata.rb +253 -47
- data/schema/schema-1.0.yml +84 -0
- data/schema/schema.yml +84 -0
- data/schema/tpkg-1.0.0.dtd +34 -0
- data/schema/tpkg-1.0.1.dtd +35 -0
- data/schema/tpkg-1.0.2.dtd +39 -0
- data/schema/tpkg-1.0.3.dtd +40 -0
- data/schema/tpkg-1.0.4.dtd +40 -0
- data/schema/tpkg-1.0.5.dtd +41 -0
- data/schema/tpkg.dtd +41 -0
- metadata +21 -2
data/lib/tpkg/metadata.rb
CHANGED
@@ -1,60 +1,225 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'rexml/document'
|
3
3
|
|
4
|
-
|
4
|
+
# We store this gem in our thirdparty directory. So we need to add it
|
5
|
+
# it to the search path
|
6
|
+
# This one is for when everything is installed
|
7
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'thirdparty/kwalify-0.7.1/lib'))
|
8
|
+
# And this one for when we're in the svn directory structure
|
9
|
+
$:.unshift(File.join(File.dirname(File.dirname(__FILE__)), 'thirdparty/kwalify-0.7.1/lib'))
|
10
|
+
require 'kwalify' # for validating yaml
|
11
|
+
|
12
|
+
# This class is taken from the ActiveSupport gem.
|
13
|
+
# With yaml, keys are stored as string. But when we convert xml to hash, we store the key as
|
14
|
+
# symbol. To make it more convenient, we'll be subclassing our metadata hash with this class.
|
15
|
+
# That way, we can access our metadata using either string or symbol as the key.
|
16
|
+
class HashWithIndifferentAccess < Hash
|
17
|
+
def initialize(constructor = {})
|
18
|
+
if constructor.is_a?(Hash)
|
19
|
+
super()
|
20
|
+
update(constructor)
|
21
|
+
else
|
22
|
+
super(constructor)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def default(key = nil)
|
27
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
28
|
+
self[key]
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
5
33
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
34
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
35
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
36
|
+
|
37
|
+
# Assigns a new value to the hash:
|
38
|
+
#
|
39
|
+
# hash = HashWithIndifferentAccess.new
|
40
|
+
# hash[:key] = "value"
|
41
|
+
#
|
42
|
+
def []=(key, value)
|
43
|
+
regular_writer(convert_key(key), convert_value(value))
|
44
|
+
end
|
45
|
+
|
46
|
+
# Updates the instantized hash with values from the second:
|
47
|
+
#
|
48
|
+
# hash_1 = HashWithIndifferentAccess.new
|
49
|
+
# hash_1[:key] = "value"
|
50
|
+
#
|
51
|
+
# hash_2 = HashWithIndifferentAccess.new
|
52
|
+
# hash_2[:key] = "New Value!"
|
53
|
+
#
|
54
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
55
|
+
#
|
56
|
+
def update(other_hash)
|
57
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :merge!, :update
|
62
|
+
|
63
|
+
# Checks the hash for a key matching the argument passed in:
|
64
|
+
#
|
65
|
+
# hash = HashWithIndifferentAccess.new
|
66
|
+
# hash["key"] = "value"
|
67
|
+
# hash.key? :key # => true
|
68
|
+
# hash.key? "key" # => true
|
69
|
+
#
|
70
|
+
def key?(key)
|
71
|
+
super(convert_key(key))
|
72
|
+
end
|
73
|
+
|
74
|
+
alias_method :include?, :key?
|
75
|
+
alias_method :has_key?, :key?
|
76
|
+
alias_method :member?, :key?
|
77
|
+
|
78
|
+
# Fetches the value for the specified key, same as doing hash[key]
|
79
|
+
def fetch(key, *extras)
|
80
|
+
super(convert_key(key), *extras)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns an array of the values at the specified indices:
|
84
|
+
#
|
85
|
+
# hash = HashWithIndifferentAccess.new
|
86
|
+
# hash[:a] = "x"
|
87
|
+
# hash[:b] = "y"
|
88
|
+
# hash.values_at("a", "b") # => ["x", "y"]
|
89
|
+
#
|
90
|
+
def values_at(*indices)
|
91
|
+
indices.collect {|key| self[convert_key(key)]}
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns an exact copy of the hash.
|
95
|
+
def dup
|
96
|
+
HashWithIndifferentAccess.new(self)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
|
100
|
+
# Does not overwrite the existing hash.
|
101
|
+
def merge(hash)
|
102
|
+
self.dup.update(hash)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Removes a specified key from the hash.
|
106
|
+
def delete(key)
|
107
|
+
super(convert_key(key))
|
108
|
+
end
|
109
|
+
|
110
|
+
def stringify_keys!; self end
|
111
|
+
def symbolize_keys!; self end
|
112
|
+
def to_options!; self end
|
113
|
+
|
114
|
+
# Convert to a Hash with String keys.
|
115
|
+
def to_hash
|
116
|
+
Hash.new(default).merge(self)
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
def convert_key(key)
|
121
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
122
|
+
end
|
123
|
+
|
124
|
+
def convert_value(value)
|
125
|
+
case value
|
126
|
+
when Hash
|
127
|
+
value.with_indifferent_access
|
128
|
+
when Array
|
129
|
+
value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
|
130
|
+
else
|
131
|
+
value
|
12
132
|
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
module IndifferentAccess
|
137
|
+
def with_indifferent_access
|
138
|
+
hash = HashWithIndifferentAccess.new(self)
|
139
|
+
hash.default = self.default
|
140
|
+
hash
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# modules with some handy methods for dealing with hash. taken from
|
145
|
+
# ActiveSupport and Facets
|
146
|
+
module HashUtils
|
147
|
+
# Return a new hash with all keys converted to strings.
|
148
|
+
def stringify_keys
|
149
|
+
inject({}) do |options, (key, value)|
|
150
|
+
options[key.to_s] = value
|
151
|
+
options
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Return a new hash with all keys converted to symbols.
|
156
|
+
def symbolize_keys
|
157
|
+
inject({}) do |options, (key, value)|
|
158
|
+
options[(key.to_sym rescue key) || key] = value
|
159
|
+
options
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def recursively(&block)
|
164
|
+
h = inject({}) do |hash, (key, value)|
|
13
165
|
if value.is_a?(Hash)
|
14
|
-
hash[key]=value.
|
166
|
+
hash[key] = value.recursively(&block)
|
15
167
|
elsif value.is_a?(Array)
|
168
|
+
array = []
|
16
169
|
value.each do |val|
|
17
170
|
if val.is_a?(Hash)
|
18
|
-
val.
|
171
|
+
array << val.recursively(&block)
|
172
|
+
else
|
173
|
+
array << val
|
19
174
|
end
|
20
175
|
end
|
176
|
+
hash[key] = array
|
177
|
+
else
|
178
|
+
hash[key] = value
|
21
179
|
end
|
180
|
+
hash
|
22
181
|
end
|
182
|
+
yield h
|
23
183
|
end
|
24
184
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
185
|
+
def rekey(*args, &block)
|
186
|
+
result = {}
|
187
|
+
# for backward comptability (TODO: DEPRECATE).
|
188
|
+
block = args.pop.to_sym.to_proc if args.size == 1
|
189
|
+
# if no args use block.
|
190
|
+
if args.empty?
|
191
|
+
block = lambda{|k| k.to_sym} unless block
|
192
|
+
keys.each do |k|
|
193
|
+
nk = block[k]
|
194
|
+
result[nk]=self[k] if nk
|
195
|
+
end
|
196
|
+
else
|
197
|
+
raise ArgumentError, "3 for 2" if block
|
198
|
+
to, from = *args
|
199
|
+
result[to] = self[from]
|
200
|
+
end
|
201
|
+
result
|
37
202
|
end
|
203
|
+
end
|
38
204
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
resolution_proc ||= proc{ |key,oldval,newval| newval }
|
45
|
-
|
46
|
-
# merge each hash into self
|
47
|
-
other_hashes.each do |hash|
|
48
|
-
hash.each{ |k,v|
|
49
|
-
# assign new k/v into self, resolving conflicts with resolution_proc
|
50
|
-
self[k] = self.has_key?(k) ? resolution_proc[k,self[k],v] : v
|
51
|
-
}
|
52
|
-
end
|
205
|
+
# Adding new capabilities to hash
|
206
|
+
class Hash
|
207
|
+
include IndifferentAccess
|
208
|
+
include HashUtils
|
209
|
+
end
|
53
210
|
|
54
|
-
|
211
|
+
# This is needed for backward compatibility
|
212
|
+
# We were using SymbolizeKeys rather than the HashWithIndifferentAccess
|
213
|
+
# class
|
214
|
+
module SymbolizeKeys
|
215
|
+
def self.extended(hash)
|
216
|
+
hash.extend(HashWithIndifferentAccess)
|
55
217
|
end
|
56
218
|
end
|
57
219
|
|
220
|
+
# This class is used for storing metadata of a package. The idea behind this class
|
221
|
+
# is that you can give it a metadata file of any format, such as yaml or xml,
|
222
|
+
# and it will provide you a uniform interface for accessing/dealing with the metadata.
|
58
223
|
class Metadata
|
59
224
|
attr_accessor :source
|
60
225
|
REQUIRED_FIELDS = [:name, :version, :maintainer]
|
@@ -101,7 +266,7 @@ class Metadata
|
|
101
266
|
|
102
267
|
if @format == 'yml'
|
103
268
|
hash = YAML::load(@metadata_text)
|
104
|
-
@hash = hash.
|
269
|
+
@hash = hash.with_indifferent_access
|
105
270
|
|
106
271
|
# We need this for backward compatibility. With xml, we specify
|
107
272
|
# native dependency as type: :native rather then native: true
|
@@ -115,16 +280,16 @@ class Metadata
|
|
115
280
|
end
|
116
281
|
end if @hash[:dependencies]
|
117
282
|
else
|
118
|
-
@hash = metadata_xml_to_hash
|
283
|
+
@hash = metadata_xml_to_hash.with_indifferent_access
|
119
284
|
end
|
120
285
|
return @hash
|
121
286
|
end
|
122
287
|
|
123
288
|
def write(file)
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
289
|
+
# When we convert xml to hash, we store the key as symbol. So when we
|
290
|
+
# write back out to file, we should stringify all the keys for readability.
|
291
|
+
data = to_hash.recursively{|h| h.stringify_keys }
|
292
|
+
YAML::dump(data, file)
|
128
293
|
end
|
129
294
|
|
130
295
|
def generate_package_filename
|
@@ -178,14 +343,52 @@ class Metadata
|
|
178
343
|
return package_filename
|
179
344
|
end
|
180
345
|
|
346
|
+
# Validate the metadata against the schema/dtd specified by the user
|
347
|
+
# or use the default one in schema_dir
|
348
|
+
# Return array of errors (if there are any)
|
349
|
+
def validate(schema_dir)
|
350
|
+
errors = []
|
351
|
+
if @format == 'yml'
|
352
|
+
if to_hash[:schema_file]
|
353
|
+
schema_file = File.join(schema_dir, to_hash[:schema_file])
|
354
|
+
else
|
355
|
+
schema_file = File.join(schema_dir, "schema.yml")
|
356
|
+
end
|
357
|
+
unless File.exists?(schema_file)
|
358
|
+
warn "Warning: unable to validate metadata because #{schema_file} does not exist"
|
359
|
+
return
|
360
|
+
end
|
361
|
+
errors = verify_yaml(schema_file, @metadata_text)
|
362
|
+
elsif @format == 'xml'
|
363
|
+
# TODO: use DTD to validate XML
|
364
|
+
errors = verify_required_fields
|
365
|
+
end
|
366
|
+
errors
|
367
|
+
end
|
368
|
+
|
369
|
+
# Verify the yaml text against the given schema
|
370
|
+
# Return array of errors (if there are any)
|
371
|
+
def verify_yaml(schema, yaml_text)
|
372
|
+
schema = Kwalify::Yaml.load_file(schema)
|
373
|
+
|
374
|
+
## create validator
|
375
|
+
validator = Kwalify::Validator.new(schema.with_indifferent_access)
|
376
|
+
## validate
|
377
|
+
errors = validator.validate(YAML::load(yaml_text).with_indifferent_access)
|
378
|
+
end
|
379
|
+
|
380
|
+
# Once we implement validating the XML using the DTD, we won't need
|
381
|
+
# this method anymore
|
181
382
|
def verify_required_fields
|
383
|
+
errors = []
|
182
384
|
REQUIRED_FIELDS.each do |reqfield|
|
183
385
|
if to_hash[reqfield].nil?
|
184
|
-
|
386
|
+
errors << "Required field #{reqfield} not found"
|
185
387
|
elsif to_hash[reqfield].to_s.empty?
|
186
|
-
|
388
|
+
errors << "Required field #{reqfield} is empty"
|
187
389
|
end
|
188
390
|
end
|
391
|
+
errors
|
189
392
|
end
|
190
393
|
|
191
394
|
def metadata_xml_to_hash
|
@@ -194,7 +397,10 @@ class Metadata
|
|
194
397
|
|
195
398
|
metadata_hash = {}
|
196
399
|
metadata_xml = REXML::Document.new(@metadata_text)
|
197
|
-
|
400
|
+
|
401
|
+
if metadata_xml.root.attributes['filename'] # && !metadata_xml.root.attributes['filename'].empty?
|
402
|
+
metadata_hash[:filename] = metadata_xml.root.attributes['filename']
|
403
|
+
end
|
198
404
|
|
199
405
|
REQUIRED_FIELDS.each do |reqfield|
|
200
406
|
if metadata_xml.elements["/tpkg/#{reqfield}"]
|
@@ -411,10 +617,10 @@ class FileMetadata < Metadata
|
|
411
617
|
|
412
618
|
if @format == 'bin'
|
413
619
|
hash = Marshal::load(@metadata_text)
|
414
|
-
@hash = hash.
|
620
|
+
@hash = hash.with_indifferent_access
|
415
621
|
elsif @format == 'yml'
|
416
622
|
hash = YAML::load(@metadata_text)
|
417
|
-
@hash = hash.
|
623
|
+
@hash = hash.with_indifferent_access
|
418
624
|
elsif @format == 'xml'
|
419
625
|
@hash = file_metadata_xml_to_hash
|
420
626
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
type: map
|
2
|
+
mapping:
|
3
|
+
"schema_file": { type: text }
|
4
|
+
"name": { type: str, required: yes }
|
5
|
+
"version": { type: text, required: yes }
|
6
|
+
"package_version": { type: text }
|
7
|
+
"maintainer": { type: str, required: yes }
|
8
|
+
"operatingsystem": { type: seq, sequence: [ {type: str} ] }
|
9
|
+
"architecture": { type: seq, sequence: [ {type: str} ] }
|
10
|
+
"description": { type: str }
|
11
|
+
"bugreporting": { type: str }
|
12
|
+
"dependencies":
|
13
|
+
type: seq
|
14
|
+
sequence:
|
15
|
+
- type: map
|
16
|
+
mapping:
|
17
|
+
"name": { type: str, required: yes }
|
18
|
+
"minimum_version": { type: text }
|
19
|
+
"maximum_version": { type: text }
|
20
|
+
"minimum_package_version": { type: text }
|
21
|
+
"maximum_package_version": { type: text }
|
22
|
+
"native": { type: bool }
|
23
|
+
"type": { type: any, pattern: /(native|tpkg)$/ }
|
24
|
+
"conflicts":
|
25
|
+
type: seq
|
26
|
+
sequence:
|
27
|
+
- type: map
|
28
|
+
mapping:
|
29
|
+
"name": { type: str, required: yes }
|
30
|
+
"minimum_version": { type: text }
|
31
|
+
"maximum_version": { type: text }
|
32
|
+
"minimum_package_version": { type: text }
|
33
|
+
"maximum_package_version": { type: text }
|
34
|
+
"native": { type: bool }
|
35
|
+
"type": { type: any, pattern: /(native|tpkg)$/ }
|
36
|
+
"externals":
|
37
|
+
type: seq
|
38
|
+
sequence:
|
39
|
+
- type: map
|
40
|
+
mapping:
|
41
|
+
"name": { type: text, required: yes }
|
42
|
+
"data": { type: text }
|
43
|
+
"datascript": { type: text }
|
44
|
+
"files":
|
45
|
+
type: map
|
46
|
+
mapping:
|
47
|
+
"file_defaults":
|
48
|
+
type: map
|
49
|
+
mapping:
|
50
|
+
"posix":
|
51
|
+
type: map
|
52
|
+
mapping:
|
53
|
+
"owner": { type: text }
|
54
|
+
"group": { type: text }
|
55
|
+
"perms": { type: text }
|
56
|
+
"dirs_defaults":
|
57
|
+
type: map
|
58
|
+
mapping:
|
59
|
+
"posix":
|
60
|
+
type: map
|
61
|
+
mapping:
|
62
|
+
"owner": { type: text }
|
63
|
+
"group": { type: text }
|
64
|
+
"perms": { type: text }
|
65
|
+
"files":
|
66
|
+
type: seq
|
67
|
+
sequence:
|
68
|
+
- type: map
|
69
|
+
mapping:
|
70
|
+
"path": { type: text, required: yes }
|
71
|
+
"encrypt": { type: any, pattern: /^true$|^precrypt$/ }
|
72
|
+
"init":
|
73
|
+
type: map
|
74
|
+
mapping:
|
75
|
+
"start": { type: int }
|
76
|
+
"levels": { type: seq, sequence: [ { type: int } ] }
|
77
|
+
"crontab":
|
78
|
+
type: map
|
79
|
+
mapping: { "user": { type: str } }
|
80
|
+
"posix":
|
81
|
+
type: map
|
82
|
+
mapping: { "owner": { type: text },
|
83
|
+
"group": { type: text },
|
84
|
+
"perms": { type: text } }
|