@@ -176,6 +176,7 @@ class ExternalsDescription(dict):
176176 PATH = 'local_path'
177177 PROTOCOL = 'protocol'
178178 REPO_URL = 'repo_url'
179+ HASH = 'hash'
179180 NAME = 'name'
180181
181182 PROTOCOL_EXTERNALS_ONLY = 'externals_only'
@@ -197,6 +198,7 @@ class ExternalsDescription(dict):
197198 REPO_URL : 'string' ,
198199 TAG : 'string' ,
199200 BRANCH : 'string' ,
201+ HASH : 'string' ,
200202 }
201203 }
202204
@@ -250,10 +252,14 @@ def _check_user_input(self):
250252 NOTE(bja, 2018-03) These checks are called *after* the file is
251253 read. That means the schema check can not occur here.
252254
255+ Note: the order is important. check_optional will create
256+ optional with null data. run check_data first to ensure
257+ required data was provided correctly by the user.
258+
253259 """
260+ self ._check_data ()
254261 self ._check_optional ()
255262 self ._validate ()
256- self ._check_data ()
257263
258264 def _check_data (self ):
259265 """Check user supplied data is valid where possible.
@@ -265,25 +271,49 @@ def _check_data(self):
265271 self [ext_name ][self .REPO ][self .PROTOCOL ], ext_name )
266272 fatal_error (msg )
267273
268- if (self [ext_name ][self .REPO ][self .PROTOCOL ]
269- != self .PROTOCOL_EXTERNALS_ONLY ):
270- if (self [ext_name ][self .REPO ][self .TAG ] and
271- self [ext_name ][self .REPO ][self .BRANCH ]):
272- msg = ('Model description is over specified! Can not '
273- 'have both "tag" and "branch" in repo '
274- 'description for "{0}"' .format (ext_name ))
274+ if (self [ext_name ][self .REPO ][self .PROTOCOL ] ==
275+ self .PROTOCOL_SVN ):
276+ if self .HASH in self [ext_name ][self .REPO ]:
277+ msg = ('In repo description for "{0}". svn repositories '
278+ 'may not include the "hash" keyword.' .format (
279+ ext_name ))
275280 fatal_error (msg )
276281
277- if (not self [ext_name ][self .REPO ][self .TAG ] and
278- not self [ext_name ][self .REPO ][self .BRANCH ]):
279- msg = ('Model description is under specified! Must have '
280- 'either "tag" or "branch" in repo '
281- 'description for "{0}"' .format (ext_name ))
282+ if (self [ext_name ][self .REPO ][self .PROTOCOL ] !=
283+ self .PROTOCOL_EXTERNALS_ONLY ):
284+ ref_count = 0
285+ found_refs = ''
286+ if self .TAG in self [ext_name ][self .REPO ]:
287+ ref_count += 1
288+ found_refs = '"{0} = {1}", {2}' .format (
289+ self .TAG , self [ext_name ][self .REPO ][self .TAG ],
290+ found_refs )
291+ if self .BRANCH in self [ext_name ][self .REPO ]:
292+ ref_count += 1
293+ found_refs = '"{0} = {1}", {2}' .format (
294+ self .BRANCH , self [ext_name ][self .REPO ][self .BRANCH ],
295+ found_refs )
296+ if self .HASH in self [ext_name ][self .REPO ]:
297+ ref_count += 1
298+ found_refs = '"{0} = {1}", {2}' .format (
299+ self .HASH , self [ext_name ][self .REPO ][self .HASH ],
300+ found_refs )
301+
302+ if ref_count > 1 :
303+ msg = ('Model description is over specified! Only one of '
304+ '"tag", "branch", or "hash" may be specified for '
305+ 'repo description of "{0}".' .format (ext_name ))
306+ msg = '{0}\n Found: {1}' .format (msg , found_refs )
307+ fatal_error (msg )
308+ elif ref_count < 1 :
309+ msg = ('Model description is under specified! One of '
310+ '"tag", "branch", or "hash" must be specified for '
311+ 'repo description of "{0}"' .format (ext_name ))
282312 fatal_error (msg )
283313
284- if not self [ext_name ][self .REPO ][ self . REPO_URL ]:
314+ if self . REPO_URL not in self [ext_name ][self .REPO ]:
285315 msg = ('Model description is under specified! Must have '
286- 'either "repo_url" in repo '
316+ '"repo_url" in repo '
287317 'description for "{0}"' .format (ext_name ))
288318 fatal_error (msg )
289319
@@ -309,6 +339,8 @@ def _check_optional(self):
309339 self [field ][self .REPO ][self .TAG ] = EMPTY_STR
310340 if self .BRANCH not in self [field ][self .REPO ]:
311341 self [field ][self .REPO ][self .BRANCH ] = EMPTY_STR
342+ if self .HASH not in self [field ][self .REPO ]:
343+ self [field ][self .REPO ][self .HASH ] = EMPTY_STR
312344 if self .REPO_URL not in self [field ][self .REPO ]:
313345 self [field ][self .REPO ][self .REPO_URL ] = EMPTY_STR
314346
@@ -317,6 +349,26 @@ def _validate(self):
317349 fields.
318350
319351 """
352+ def print_compare_difference (data_a , data_b , loc_a , loc_b ):
353+ """Look through the data structures and print the differences.
354+
355+ """
356+ for item in data_a :
357+ if item in data_b :
358+ if not isinstance (data_b [item ], type (data_a [item ])):
359+ printlog (" {item}: {loc} = {val} ({val_type})" .format (
360+ item = item , loc = loc_a , val = data_a [item ],
361+ val_type = type (data_a [item ])))
362+ printlog (" {item} {loc} = {val} ({val_type})" .format (
363+ item = ' ' * len (item ), loc = loc_b , val = data_b [item ],
364+ val_type = type (data_b [item ])))
365+ else :
366+ printlog (" {item}: {loc} = {val} ({val_type})" .format (
367+ item = item , loc = loc_a , val = data_a [item ],
368+ val_type = type (data_a [item ])))
369+ printlog (" {item} {loc} missing" .format (
370+ item = ' ' * len (item ), loc = loc_b ))
371+
320372 def validate_data_struct (schema , data ):
321373 """Compare a data structure against a schema and validate all required
322374 fields are present.
@@ -326,26 +378,29 @@ def validate_data_struct(schema, data):
326378 in_ref = True
327379 valid = True
328380 if isinstance (schema , dict ) and isinstance (data , dict ):
381+ # Both are dicts, recursively verify that all fields
382+ # in schema are present in the data.
329383 for k in schema :
330384 in_ref = in_ref and (k in data )
331385 if in_ref :
332386 valid = valid and (
333387 validate_data_struct (schema [k ], data [k ]))
334388 is_valid = in_ref and valid
335389 else :
390+ # non-recursive structure. verify data and schema have
391+ # the same type.
336392 is_valid = isinstance (data , type (schema ))
393+
337394 if not is_valid :
338- printlog (" Unmatched schema and data :" )
395+ printlog (" Unmatched schema and input :" )
339396 if isinstance (schema , dict ):
340- for item in schema :
341- printlog (" {0} schema = {1} ({2})" .format (
342- item , schema [item ], type (schema [item ])))
343- printlog (" {0} data = {1} ({2})" .format (
344- item , data [item ], type (data [item ])))
397+ print_compare_difference (schema , data , 'schema' , 'input' )
398+ print_compare_difference (data , schema , 'input' , 'schema' )
345399 else :
346400 printlog (" schema = {0} ({1})" .format (
347401 schema , type (schema )))
348- printlog (" data = {0} ({1})" .format (data , type (data )))
402+ printlog (" input = {0} ({1})" .format (data , type (data )))
403+
349404 return is_valid
350405
351406 for field in self :
@@ -392,7 +447,7 @@ def __init__(self, model_data):
392447 """
393448 ExternalsDescription .__init__ (self )
394449 self ._schema_major = 1
395- self ._schema_minor = 0
450+ self ._schema_minor = 1
396451 self ._schema_patch = 0
397452 self ._input_major , self ._input_minor , self ._input_patch = \
398453 get_cfg_schema_version (model_data )
0 commit comments