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.
@@ -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 } }