tpkg 1.18.2 → 1.19.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,60 +1,225 @@
1
1
  require 'yaml'
2
2
  require 'rexml/document'
3
3
 
4
- module SymbolizeKeys
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
- # converts any current string keys to symbol keys
7
- def self.extended(hash)
8
- hash.each do |key,value|
9
- if key.is_a?(String)
10
- hash.delete key
11
- hash[key] = value #through overridden []=
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.extend(SymbolizeKeys)
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.extend(SymbolizeKeys)
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
- # assigns a new key/value pair
26
- # converts they key to a symbol if it is a string
27
- def []=(*args)
28
- args[0] = args[0].to_sym if args[0].is_a?(String)
29
- super
30
- end
31
-
32
- # returns new hash which is the merge of self and other hashes
33
- # the returned hash will also be extended by SymbolizeKeys
34
- def merge(*other_hashes , &resolution_proc )
35
- merged = Hash.new.extend SymbolizeKeys
36
- merged.merge! self , *other_hashes , &resolution_proc
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
- # merges the other hashes into self
40
- # if a proc is submitted , it's return will be the value for the key
41
- def merge!( *other_hashes , &resolution_proc )
42
-
43
- # default resolution: value of the other hash
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
- self
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.extend(SymbolizeKeys)
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
- YAML::dump(to_hash, file)
125
- end
126
-
127
- def get_files_list
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
- raise "Required field #{reqfield} not found"
386
+ errors << "Required field #{reqfield} not found"
185
387
  elsif to_hash[reqfield].to_s.empty?
186
- raise "Required field #{reqfield} is empty"
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
- metadata_hash[:filename] = metadata_xml.root.attributes['filename']
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.extend(SymbolizeKeys)
620
+ @hash = hash.with_indifferent_access
415
621
  elsif @format == 'yml'
416
622
  hash = YAML::load(@metadata_text)
417
- @hash = hash.extend(SymbolizeKeys)
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 } }