tpkg 1.18.2 → 1.19.2
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/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 } }
|