source: CCCC/trunk/ceda_cc/fcc_utils.py @ 332

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/CCCC/trunk/ceda_cc/fcc_utils.py
Revision 332, 45.2 KB checked in by astephen, 5 years ago (diff)

Added test for no command-line arguments when ceda-cc is run.
Now it prints the doc string and informs the user of the error.

Also changed "diretory" to "directory" in various places.

Line 
1import string, os, re, stat, sys
2
3ncdumpCmd = 'ncdump'
4ncdumpCmd = '/usr/local/5/bin/ncdump'
5##
6
7from xceptions import *
8
9class mipTableScan(object):
10
11  def __init__(self, vats = ['standard_name','long_name','units','cell_methods'] ):
12    self.vats = vats
13    self.re_cmor_mip2 = re.compile( 'dimensions:(?P<dims>.*?):::' )
14    self.re_vats = { }
15    for v in vats:
16      self.re_vats[v] = re.compile( '%s:(?P<dims>.*?):::' % v )
17##
18  def scan_table(self,ll,log,asDict=False,appendTo=None,lax=False,tag=None):
19 
20    lll0 = map( string.strip, ll )
21    lll = []
22    for l in lll0:
23      if len(l) != 0:
24        if l[0] != '!':
25          lll.append(string.split(l,'!')[0])
26    sll = []
27    sll.append( ['header',[]] )
28    for l in lll:
29      k = string.split( l, ':' )[0]
30      if k in ['variable_entry','axis_entry']:
31        sll.append( [k,[]] )
32      sll[-1][1].append(l)
33
34    eee = []
35    for s in sll:
36      if s[0] == 'variable_entry':
37         bits = string.split(s[1][0],':')
38         assert len(bits) == 2, 'Can not unpack: %s' % str(s[1])
39         k,var =  map( string.strip, string.split(s[1][0],':') )
40         aa = {'standard_name':None, 'long_name':None,'units':None,'cell_methods':None }
41         ds = 'scalar'
42         for l in s[1][1:]:
43           bits = string.split(l,':')
44           k = string.strip(bits[0])
45           v = string.strip( string.join( bits[1:], ':' ) )
46           if k == 'dimensions':
47             ds = string.split(v)
48           else:
49             aa[k] = v
50         eee.append( (var,ds,aa,tag) )
51
52
53    checkOldMethod = False
54    if checkOldMethod:
55      ssss = string.join( lll, ':::' )
56      vitems = string.split( ssss, ':::variable_entry:' )[1:]
57 
58      ee = []
59      for i in vitems:
60        b1 = string.split( i, ':::')[0]
61        var = string.strip( b1 )
62        aa = {}
63        for v in self.vats:
64          mm = self.re_vats[v].findall(i)
65          if len(mm) == 1:
66             aa[v] = string.strip(mm[0])
67          else:
68             aa[v] = 'None'
69 
70        mm = self.re_cmor_mip2.findall( i )
71        if len(mm) == 1:
72          ds = string.split( string.strip(mm[0]) )
73        elif len(mm) == 0:
74          ds = 'scalar'
75        else:
76          if log != None:
77             log.warn(  'Mistake?? in scan_table %s' % str(mm) )
78          ds = mm
79          raise baseException( 'Mistake?? in scan_table %s' % str(mm) )
80        ee.append( (var,ds,aa,tag) )
81
82      for k in range(len(ee) ):
83        if ee[k][0:2] == eee[k][0:2] and ee[k][2]['standard_name'] == eee[k][2]['standard_name'] and ee[k][2]['long_name'] == eee[k][2]['long_name']:
84          print 'OK:::', ee[k]
85        else:
86          print 'DIFF: ',ee[k],eee[k]
87     
88    if not asDict:
89      return tuple( eee )
90    else:
91      ff = {}
92      for l in eee:
93        ff[l[0]] = ( l[1], l[2], l[3] )
94      if appendTo != None:
95        for k in ff.keys():
96          assert ff[k][1].has_key( 'standard_name' ), 'No standard name in %s:: %s' % (k,str(ff[k][1].keys()))
97          if appendTo.has_key(k):
98            if lax and  ff[k][1]['standard_name'] != appendTo[k][1]['standard_name']:
99              print 'ERROR[X1]: Inconsistent entry definitions %s:: %s [%s] --- %s [%s]' % (k,ff[k][1],ff[k][2], appendTo[k][1], appendTo[k][2])
100            if not lax:
101              assert ff[k][1] == appendTo[k][1], 'Inconsistent entry definitions %s:: %s [%s] --- %s [%s]' % (k,ff[k][1],ff[k][2], appendTo[k][1], appendTo[k][2])
102          else:
103            appendTo[k] = ff[k]
104        return appendTo
105      else:
106        return ff
107##
108## this class carries a logging method, and is used to carry information about datasets being parsed.
109##
110class qcHandler(object):
111
112  def __init__( self, qcc, log, baseDir, logPasses=True ):
113    self.datasets = {}
114    self.groups = {}
115    self.baseDir = baseDir
116    self.logPasses = logPasses
117    self.log = log
118    self.nofail = True
119    self.hasTimeRange = False
120    for k in qcc.datasets.keys():
121      self.datasets[k] = {}
122    for g in qcc.groups:
123      self.groups[g[0]] = { 'pat':g[1]}
124    self.msg = {}
125    self.msgk = {}
126    self.msg['CQC.101.001.001'] = 'File size above 10 bytes'
127    self.msg['CQC.101.001.002'] = 'File name matches DRS syntax'
128    self.msg['CQC.101.001.003'] = 'File name time component matches DRS syntax'
129    self.msg['CQC.101.001.004'] = 'File name component not in vocabulary'
130    self.msg['CQC.101.001.005'] = 'File name component does not match regex'
131    self.msg['CQC.101.001.006'] = 'File name component does not match regex list'
132    self.msg['CQC.101.001.007'] = 'File name component does not match regex list with constraints'
133    self.msg['CQC.102.002.001'] = 'File name time components in ADS have same length'
134    self.msg['CQC.102.002.002'] = 'File name time components in ADS do not overlap'
135    self.msg['CQC.102.002.003'] = 'File name time components in ADS have no gaps'
136    self.msg['CQC.102.002.004'] = 'File name time components in ADS have correct gap for monthly data'
137    self.msg['CQC.102.002.005'] = 'File name time components present for multi-file dataset'
138    self.msg['CQC.102.002.006'] = 'Consistency checks'
139    self.msg['CQC.102.002.007'] = 'Required variables'
140    self.msg['CQC.102.002.008'] = 'Required data variables'
141    self.msg['CQC.102.002.009'] = 'File is a recognised NetCDF format'
142    self.msg['CQC.102.002.010'] = 'Variable attributes match tables'
143    self.msg['CQC.200.003.001'] = 'NetCDF files occur at one directory level'
144    self.msg['CQC.103.003.002'] = 'Conformant version directory'
145    self.msg['CQC.103.003.003'] = 'Latest link points to most recent version'
146    self.msg['CQC.200.003.004'] = 'ads occurs in a single directory'
147    self.msg['CQC.104.004.001'] = 'Consistent global attributes across experiment'
148    self.msg['CQC.105.004.002'] = 'Valid calendar attribute'
149    self.msg['CQC.101.004.003'] = 'Regular time step in file'
150    self.msg['CQC.102.004.004'] = 'Regular time step between files'
151    self.msg['CQC.102.004.005'] = 'Exceptions to regular time period'
152    self.msg['CQC.102.004.005'] = 'Consistent global attributes across ADS'
153    self.msg['CQC.105.004.006'] = 'Consistent global attributes across ensemble'
154    self.msg['CQC.101.004.007'] = 'Required global attributes'
155    self.msg['CQC.103.900.001'] = 'Identifiedmost recent version'
156    self.msg['CQC.103.003.005'] = 'Version directories identified in directory containing "latest"'
157## error keys: when these occur, further processing of that file is blocked.
158    self.errorKeys = ['CQC.101.001.001', 'CQC.101.001.002']
159## keys in this list will not be recorded as failed tests.
160    self.ignoreKeys = []
161    for k in self.msg.keys():
162        self.msgk[k] = 0
163
164  def _log( self, key, item, msg, ok=False ):
165    if ok:
166      if self.logPasses:
167         thisMsg = '%s OK: %s: %s: %s' % (key,item,self.msg[key], msg)
168         self.log.info( thisMsg )
169      return
170
171    if key not in self.ignoreKeys:
172      self.nofail = False
173    item = string.replace( item, self.baseDir, '' )
174    if key in self.errorKeys:
175       self.log.error( '%s [ERROR] FAIL !(%s): %s: %s' % (key,self.msg[key], item,msg))
176       self.noerror = False
177    else:
178       thisMsg = '%s FAIL !(%s): %s: %s' % (key,self.msg[key],item, msg)
179       self.log.info( thisMsg )
180             
181    self.msgk[key] += 1
182
183class dirParser(object):
184
185  def __init__(self, qcc, linksOnly=True):
186    self.nclevs = []
187    self.qcc = qcc
188    self.dirNames = {}
189    self.count_nc  = 0
190    self.linksOnly=linksOnly
191
192  def parse( self, handler,dir, files ):
193    handler.log.info( 'Directory: %s [%s]' % (dir, len(files)) )
194    bits = string.split(dir,'/')
195    thisLev = len(bits)
196    files.sort()
197    skipf = []
198
199    for f in files:
200      if os.path.isdir( '%s/%s' % (dir,f) ) and f in self.qcc.omitDirectories:
201        skipf.append(f)
202    for f in skipf:
203      handler.log.info( 'skipping %s' % f )
204      files.pop( files.index(f) )
205     
206# record directory names at each level
207    if thisLev not in self.dirNames.keys():
208       self.dirNames[thisLev] = []
209    if bits[-1] not in self.dirNames[thisLev]:
210       self.dirNames[thisLev].append( bits[-1] )
211
212    ncFiles = []
213    for f in files:
214      if f[-3:] == ".nc" and (not self.linksOnly or os.path.islink('%s/%s'%(dir,f))):
215        ncFiles.append(f)
216
217# record which directory levels contain netcdf files
218    if len(ncFiles) and thisLev not in self.nclevs:
219      self.nclevs.append( thisLev )
220     
221    tbits = []
222    ncFiles.sort()
223    self.count_nc += len( ncFiles )
224    dbits = string.split( string.strip(dir,'/'), '/' )
225    for f in ncFiles:
226      fpath = '%s/%s' % (dir,f)
227      handler.noerror = True
228      handler.nofail = True
229
230      if not os.path.islink( fpath ):
231         fsb = os.stat(  fpath  )[stat.ST_SIZE]
232      else:
233         fsb = os.stat(  fpath  )[stat.ST_SIZE]
234         if fsb < 10:
235           handler._log( 'CQC.101.001.001',  fpath, '' )
236
237      fbits = string.split( string.split(f,'.')[0], self.qcc.fileNameSegments.sep )
238      if not len( fbits ) in self.qcc.fileNameSegments.nn:
239        handler._log( 'CQC.101.001.002',  fpath, str(fbits) )
240######
241######
242      else:
243       qfns = self.qcc.fileNameSegments
244       ns = {}
245       for k in range(len(fbits)): 
246         ns['fn_%s' % qfns.segments[k][1]] = fbits[k]
247         if qfns.segments[k][0] == 'vocabulary':
248           assert qfns.segments[k][1] in self.qcc.vocab.keys(), '%s not a valid vocabulary name' % qfns.segments[k][1]
249           if not fbits[k] in self.qcc.vocab[qfns.segments[k][1]]:
250              handler._log( 'CQC.101.001.004',  fpath, 'Not in vocab %s' % qfns.segments[k][1] )
251         elif qfns.segments[k][0] == 'abstractVocab':
252           assert qfns.segments[k][1] in self.qcc.vocab.keys(), '%s not a valid abstract vocabulary name' % qfns.segments[k][1]
253           this = self.qcc.vocab[qfns.segments[k][1]]
254           assert this[0] == 'regex', 'Unexpected start of abstractVocab, %s' % str( this )
255           match = False
256           for s,t,tt in this[1]:
257              if s.match( fbits[k] ):
258                match = True
259                ## print 'Match [%s] found for %s {%s}' % (t,fbits[k],tt)
260                for k in y.groupdict().keys():
261                    ns['fnre_%s' % k] = y.groupdict()[k]
262                if tt != None:
263                  ##print 'trying further test'
264                  tt1 = string.replace(tt,'$','_arg_')
265                  y = s.match( fbits[k] )
266                  for k in y.groupdict().keys():
267                    eval( '_arg_%s = int( %s )' % (k,y.groupdict()[k] ) )
268                  eval( 'res = tt1' )
269                  ##print res
270              else:
271                pass
272                ## print 'no match [%s] for %s ' % (t,fbits[k])
273                   
274           if not match:
275              handler._log( 'CQC.101.001.006',  fpath, 'Failed abstractVocab regex tests %s' % fbits[k] )
276         elif qfns.segments[k][0] == 'condAbstractVocab':
277           assert qfns.segments[k][1] in self.qcc.vocab.keys(), '%s not a valid abstract vocabulary name' % qfns.segments[k][1]
278           this = self.qcc.vocab[qfns.segments[k][1]]
279           assert this[0] == 'regex', 'Unexpected start of abstractVocab, %s' % str( this )
280           match = False
281           olc = 0
282           for sss in this[1]:
283             ol = False
284             if sss[0] == '*':
285               ol = True
286             else:
287               for b in string.split(sss[0],','):
288                 if b in fbits:
289                   ol = True
290             if ol:
291               nunc = 0
292               olc += 1
293               for s,t,tt in sss[1]:
294
295                 if not match:
296                  y = s.match( fbits[k] )
297                  if y:
298                    ## print 'Match [%s] found for %s {%s}' % (t,fbits[k],tt)
299                    nunc += 1
300                    for key in y.groupdict().keys():
301                        ns['fnre_%s' % key] = y.groupdict()[key]
302                    ##print '--- Match [%s] found for %s {%s}' % (t,fbits[k],tt)
303                    if tt != None:
304                      ## create string with test condition.`
305                      tt1 = string.replace(tt,'$','_arg_')
306                      y = s.match( fbits[k] )
307                      for key in y.groupdict().keys():
308                        locals()['_arg_%s' % key ] = int( y.groupdict()[key] )
309                        ##print '_arg_%s' % key , locals()['_arg_%s' % key ]
310                      res = eval( tt1 )
311                      ## print '#####', res,tt1
312                      if res:
313                        match = True
314                    else:
315                      match = True
316                  else:
317                    ##print 'no match [%s] for %s ' % (t,fbits[k])
318                    pass
319             ##else:
320               ##print 'No overlap for %s, %s' % (sss[0],str(fbits))
321           if olc == 0:
322             ##print 'No matches fround for %s' % str(fbits)
323             pass
324                   
325           if not match:
326              handler._log( 'CQC.101.001.007',  fpath, 'Failed constrained regex tests %s (%s unconditional matches)' % (fbits[k], nunc) )
327         elif qfns.segments[k][0] == 'regex-match':
328           res = qfns.segments[k][2].match( fbits[k] )
329           if res == None:
330               handler._log( 'CQC.101.001.005',  fpath, 'Failed regex-match test: %s [%s]' % (fbits[k],qfns.segments[k][1] ) )
331         elif qfns.segments[k][0] == 'vocabulary*':
332           pass
333         else:
334           print 'segment test id %s not recognised' % qfns.segments[k][0]
335           raise baseException( 'segment test id %s not recognised' % qfns.segments[k][0] )
336##################################
337       versionned = False
338       if not versionned:
339        for k in self.qcc.datasets.keys():
340          if self.qcc.datasets[k].datasetIdArg == 'fileNameBits':
341            dsId = self.qcc.datasets[k].getDatasetId( fbits )
342          elif self.qcc.datasets[k].datasetIdArg == 'filePathBits':
343            try:
344              dsId = self.qcc.datasets[k].getDatasetId( fbits, dbits )
345            except:
346              print 'Failed to get dsID:',fbits,dbits
347              raise baseException( 'Failed to get dsID: %s,%s' % (fbits,dbits) )
348          else:
349            assert False, 'datasetIdMethod %s not supported yet' % self.qcc.datasets[k].datasetIdMethod
350           
351          if os.path.islink( fpath ):
352            dsId += '_lnk'
353          if not handler.datasets[k].has_key( dsId ):
354            handler.datasets[k][dsId] = []
355          handler.datasets[k][dsId].append( (dir,f, handler.nofail, ns) )
356
357class dataSetParser(object):
358
359  def __init__(self,qcc, log, handler):
360    self.qcc = qcc
361    self.log = log
362    self.h = handler
363    self.re_istr = re.compile( '^[0-9]*$' )
364
365  def parse(self,dsclass, dsid, files, inFileChecks=False, forceInFileChecks=True):
366      self.h.nofail = True
367## allowEndPeriodEqual should only be permitted for time averaged fields, but in this version it is set true for all fields.
368      allowEndPeriodEqual = True
369      try:
370        fns = map( lambda x: x[1], self.qcc.fileNameSegments.segments )
371      except:
372        print self.qcc.fileNameSegments.segments
373        raise baseException(  str( self.qcc.fileNameSegments.segments ) )
374      dsok = True
375      for dir,f, fok, ns in files:
376        dsok &= fok
377
378      self.h.nofail = dsok
379##
380## this test should have a switch -- only to be applied to one category of file group
381## need dsclass constraints
382##
383## constraint: setOnce:
384##
385      if dsok:
386        if self.qcc.hasTimeRange:
387          allOk = True
388          tbl = []
389          for dir,f, fok, ns in files:
390            thisOk = True
391            fbits = string.split( string.split(f,'.')[0], self.qcc.fileNameSegments.sep )
392            thisOk, tb = self.qcc.timeRange.get( fbits )
393
394            allOk &= thisOk
395            tbl.append( tb )
396
397          if allOk:
398            kkl = []
399            for tb in tbl:
400              kk = 0
401              for i in range(2):
402                if tb[i] != None:
403                  ## tb[i] = int(tb[i])
404                  kk+=1
405              kkl.append(kk)
406           
407            thisOk = True
408            cc = ''
409            for k in range( len(tbl)-1  ):
410              if kkl[k] != kkl[0]:
411                 thisOk = False
412                 cc += str(files[k])
413            self.h._log( 'CQC.102.002.001', cc, '', ok=thisOk )
414
415            self.h._log( 'CQC.102.002.005', '%s@%s' % (dsid,dsclass), '', ok=not(thisOk and kkl[0] == 0 and len(files) > 1) )
416
417            if thisOk and kkl[0] == 2:
418              cc = ''
419              for k in range( len(tbl) -1 ):
420                if tbl[k+1][0] < tbl[k][1] or (tbl[k+1][0] == tbl[k][1] and not allowEndPeriodEqual):
421                  thisOk = False
422                  cc += '%s, %s [%s,%s];' % (str(files[k]), str(files[k+1]),tbl[k][1],tbl[k+1][0])
423              self.h._log( 'CQC.102.002.002', cc, '', ok=thisOk )
424
425###
426### run group constraints
427###
428          if self.qcc.groupConstraints.has_key( dsclass ):
429              for ct in self.qcc.groupConstraints[dsclass]:
430                 ct.__reset__()
431                 for dir,f, fok, ns in files:
432                     if fok:
433###
434                        rv,res = ct.check( ns )
435                        if rv != 'PASS':
436                          self.h._log( ct.code, f, ct.msg, False )
437
438##
439## should only do the in-file checks once
440## intention is to be able to have multiple definitions of groups with different tests
441##
442      files2 = []
443      if (self.h.nofail and inFileChecks) or forceInFileChecks:
444          ##print 'starting in-file checks'
445          import ncd_parse
446          for dir,f, fok, ns in files:
447            if fok or forceInFileChecks:
448             tmpDumpFile = '/tmp/qc_ncdump_tmp.txt'
449             if os.path.isfile( tmpDumpFile ):
450               os.unlink( tmpDumpFile )
451             targf = '%s/%s' % (dir,f)
452             fsb = os.stat(  targf  )[stat.ST_SIZE]
453             assert fsb > 10, 'Small file slipped through: %s, %s' % (targ,fok)
454             cmd = '%s -k %s/%s 2>&1 > %s' % (ncdumpCmd,dir,f,tmpDumpFile)
455             res = os.popen( cmd ).readlines()
456             ii = open( tmpDumpFile ).readlines()
457             if len(ii) == 0:
458               this_ok = False
459             else:
460               this_ok = 'Unknown' not in ii[0]
461             self.h._log( 'CQC.102.002.009', '%s/%s' % (dir,f), '', ok=this_ok )
462             files2.append( (dir,f, this_ok, ns) )
463             if this_ok:
464               cmd = '%s -h %s/%s > %s' % (ncdumpCmd,dir,f,tmpDumpFile)
465               ii = os.popen( cmd ).readlines()
466               fsb = os.stat(  tmpDumpFile  )[stat.ST_SIZE]
467               assert fsb > 100, 'ncdump output too small, %s/%s' % (dir,f)
468             
469               rd = ncd_parse.read_ncdump( tmpDumpFile )
470               rd.parse()
471
472##
473## messy hack -- copying globals attributes into a new dictionary
474##
475               for k in rd.gats.keys():
476                 ns['g_%s' % k] = rd.gats[k]
477## rd.vars[k] is a tuple: (dims,atts), where atts is a dictionary of attributes.
478               for k in rd.vars.keys():
479                 ns['v_%s' % k] = rd.vars[k]
480               for k in rd.dims.keys():
481                 ns['d_%s' % k] = rd.dims[k]
482
483               if self.qcc.attributeTests:
484                 for a in self.qcc.requiredGlobalAttributes:
485                   self.h._log( 'CQC.101.004.007', '%s/%s' % (dir,f), 'Attribute: %s' % a, ok=a in rd.gats.keys() )
486
487          if self.qcc.variableTests:
488            for dir,f, fok, ns in files2:
489             if fok:
490              for rv in self.qcc.requiredVariables:
491                if rv[0][0] != '$':
492                  self.h._log( 'CQC.102.002.007', f, 'Required variable %s'% (rv[0]), 'v_%s' % rv[0] in ns.keys())
493
494          if self.qcc.groups:
495            for dir,f, fok, ns in files2:
496             if fok:
497               for g in self.qcc.groups:
498                  gid = g[1] % ns
499                  if not self.qcc.groupDict[g[0]].has_key( gid ):
500                    self.qcc.groupDict[g[0]][ gid ] = []
501                  self.qcc.groupDict[g[0]][ gid ].append( ( dir,f,fok) )
502                  ## print '%s:: %s' % (g[0],gid)
503           
504
505          if self.qcc.constraintTests:
506            for dir,f, fok, ns in files2:
507             if fok:
508              for ct in self.qcc.constraints:
509###
510                rv,res = ct.check( ns )
511                if rv != 'PASS':
512                  self.h._log( ct.code, f, ct.msg, False )
513
514          if self.qcc.variableTests:
515            for dir,f, fok, ns in files2:
516             if fok:
517              for v in self.qcc.dataVariables:
518                var = ns[v[1]]
519                if v[0] == 'ns':
520                  isPresent = 'v_%s' % var in ns.keys()
521                  if v[3]:
522                    self.h._log( 'CQC.102.002.008', f, '%s [%s::%s]'% (var,v[1],v[2]), isPresent )
523       
524
525class dataset(object):
526   def __init__(self,name):
527     self.name = name
528
529class qcConfigParse(object):
530
531  def __init__( self, file, log=None ):
532    assert os.path.isfile( file ), '%s not found' % file
533    self.firstFile = True
534    self.fh = open( file )
535    self.file = file
536    self.sections = {}
537    self.omitDirectories = ['.svn']
538    self.log = log
539    self.mipTables = None
540    self.mipMapping = None
541    self.hasTimeRange = False
542
543  def close(self):
544    self.fh.close()
545    self.file = None
546
547  def open(self,file):
548    assert os.path.isfile( file ), '%s not found' % file
549    self.fh = open( file )
550    self.file = file
551
552  def parse_l0(self):
553    f = False
554    sname = None
555    for l in self.fh.readlines():
556      if f:
557        if l[0:4] == 'END ' and string.index( l,sname) == 4:
558          f = False
559          self._parse_l0_section.close()
560        else:
561          self._parse_l0_section.add( l )
562      elif l[0:6] == 'START ':
563        sname = string.strip( string.split(l)[1] )
564        self._parse_l0_section = section_parser_l0( self, sname )
565        f = True
566
567  def parse_l1(self):
568
569     if self.firstFile:
570       requiredSections = ['FILENAME', 'VOCABULARIES','PATH']
571     else:
572       requiredSections = []
573     self.firstFile = False
574
575     for s in requiredSections:
576       assert s in self.sections.keys(), 'Required section %s not found in %s [parsing %s]' % (s, self.section.keys(),self.file)
577     self._parse_l1 = section_parser_l1( self )
578     self._parse_l1.parse( 'GENERAL' )
579     self._parse_l1.parse( 'VOCABULARIES' )
580     if self.mipTables != None:
581        assert self.mipMapping != None, '"mipMapping" must be set if MIP tables are used'
582        ee = {}
583        for m in self.mipTables:
584          ee[m] = self.vocab[m]
585        self.mipConstraint = Constraint__VarAtts( ee, self.mipMapping,self.vocab['mipVarAtts'] )
586
587     self._parse_l1.parse( 'FILENAME' )
588     self._parse_l1.parse( 'PATH' )
589     self._parse_l1.parse( 'ATTRIBUTES' )
590     self._parse_l1.parse( 'VARIABLES' )
591     self._parse_l1.parse( 'CONSTRAINTS' )
592     self._parse_l1.parse( 'GROUPS' )
593
594regv = re.compile( 'version=([0-9.]+)' )
595refs = re.compile( 'separator=(.+)' )
596revsc = re.compile( 'validSectionCount,(.+)' )
597
598class section_parser_l1(object):
599
600  def __init__(self,parent):
601     self.parent = parent
602     self.currentSection = None
603     self.gc = {}
604     self.parent.constraintTests = False
605
606  def _getVersion( self ):
607    assert self.currentSection != None, '_getVersion called with no section set'
608    x = regv.findall( self.currentSection[0] )
609    assert len(x) == 1, 'valid version not identified at start of section: %s\n%s' % (self.currentSectionName,self.currentSection[0])
610    self.version = x[0]
611
612  def parse( self, sname ):
613    if self.parent.sections.has_key( sname ):
614
615      self.currentSectionName = sname
616      self.currentSection = self.parent.sections[sname]
617      self._getVersion()
618    else:
619      self.currentSection = None
620
621    ## print 'Parsing %s' % sname
622    if sname == 'VOCABULARIES':
623      self.parent.vocab = {}
624      self.parse_vocabularies()
625    elif sname == 'FILENAME':
626      self.parse_filename()
627    elif sname == 'PATH':
628      self.parse_path()
629    elif sname == 'ATTRIBUTES':
630      self.parse_attributes()
631    elif sname == 'VARIABLES':
632      self.parse_variables()
633    elif sname == 'CONSTRAINTS':
634      self.parse_constraints()
635    elif sname == 'GENERAL':
636      self.parse_general()
637    elif sname == 'GROUPS':
638      self.parse_groups()
639
640  def __get_match( self, regex, line, id ):
641    x = regex.findall( line )
642    assert len(x) == 1, 'No match found, id=%s, line=%s' % 'id,line'
643    return x[0]
644
645  def parse_vocabularies(self):
646    ## print len( self.currentSection )
647    base = ''
648    for l in self.currentSection[1:]:
649      bits = map( string.strip, string.split( l, ', ' ) )
650      id = bits[0]
651      if id[0:2] == '__':
652        assert id[2:] in ['base','mipMapping'], 'vocabulary record not recognised: %s' % l
653        if id[2:] == 'base':
654          assert os.path.isdir( bits[1] ), '!!!parse_vocabularies: directory %s not found' % bits[1]
655          base = bits[1]
656          if base[-1] != '/':
657            base += '/'
658        elif id[2:] == 'mipMapping':
659          self.parent.mipMapping = bits[1]
660      else:
661        isAbstract = False
662        if id[0] == '*':
663          id = id[1:]
664          isAbstract = True
665
666        sl = string.split( bits[1], '|' )
667        fromFile = 'file' in  sl
668        isRegex = 'regex' in sl
669        withSub = 'sub' in sl
670        isCond = 'cond' in sl
671
672        if not fromFile:
673          vlist = string.split( bits[2] )
674        else:
675          fn = '%s%s' % (base,bits[2])
676          assert os.path.isfile( fn), 'File %s (specified as vocabulary %s) not found' % (fn,bits[0] )
677          ii = open( fn ).readlines()
678          bb = string.split( bits[1], '|' )
679          if '1stonline' in sl:
680            vlist = []
681            for i in ii:
682              if i[0] != '#' and len( string.strip(i) ) > 0:
683                vlist.append( string.split( string.strip(i) )[0] )
684          elif '1perline' in sl:
685            vlist = map( string.strip, ii )
686          elif 'mip' in sl:
687            vlist = self.mipsc.scan_table( ii, self.parent.log )
688            if self.parent.mipTables == None:
689              self.parent.mipTables = []
690            self.parent.mipTables.append( id )
691          else:
692            assert False, 'file syntax option (%s) not recognised' % bits[1]
693 
694        if isRegex:
695          cr = []
696          if withSub:
697            if isCond:
698              for ccc in vlist:
699                 i0 = ccc.index(':')
700                 cc = ccc[:i0]
701                 cr0 = []
702                 for v in string.split( ccc[i0+1:] ):
703                     v = string.strip(v)
704                     if v[0] == '{':
705                       i1 = v.index('}')
706                       tt = v[1:i1]
707                       v = v[i1+1:]
708                     else:
709                       tt = None
710                     v = string.strip( v, "'" )
711                     try:
712                       cr0.append( (re.compile( v % self.gc ),v % self.gc,tt) )
713                     except:
714                       print 'Error trying to compile: ', v % self.gc
715                       print 'Pattern: ',v
716                       raise baseException(  'Error trying to compile: %s [%s]' % ( v % self.gc, v) )
717                     ## print 'compiled ' +  v % self.gc, tt
718                 cr.append( (cc,cr0) )
719            else:
720              for v in vlist:
721                 v = string.strip( v, "'" )
722                 cr.append( (re.compile( v % self.gc ),v % self.gc) )
723          else:
724            for v in vlist:
725               v = string.strip( v, "'" )
726               cr.append( (re.compile( v ),v) )
727          self.parent.vocab[id] = ('regex', cr )
728        else:
729          self.parent.vocab[id] = vlist[:]
730          if id == 'mipVarAtts':
731            self.mipsc = mipTableScan( vlist )
732 
733  def parse_filename(self):
734    sep = self.__get_match( refs, self.currentSection[1], 'File separator' )
735    nn = map( int, string.split( self.__get_match( revsc, self.currentSection[2], 'File separator' ),',') )
736    self.parent.fileNameSegments = fileNameSegments( self.parent, sep, nn )
737    for l in self.currentSection[3:]:
738       self.parent.fileNameSegments.add(l)
739    self.parent.fileNameSegments.finish()
740
741  def parse_attributes(self):
742    if self.currentSection == None:
743       self.parent.attributeTests = False
744       return
745    self.parent.requiredGlobalAttributes = []
746    self.parent.attributeTests = True
747    for l in self.currentSection[1:]:
748      bits = map( string.strip, string.split(l,','))
749      if bits[0] == 'global':
750        if bits[2] == 'required':
751           self.parent.requiredGlobalAttributes.append( bits[1] )
752
753  def parse_general(self):
754    if self.currentSection == None:
755       return
756    self.parent.requiredGlobalAttributes = []
757    self.parent.dataVariables = []
758    for l in self.currentSection[1:]:
759      if l[0] == '$':
760        bits = map( string.strip, string.split(l[1:],'='))
761        self.gc[bits[0]] = bits[1]
762      else:
763        bits = map( string.strip, string.split(l,','))
764        if bits[0] == 'DataVariable':
765          if bits[1] == 'byName':
766            isRequired = bits[3] == 'required'
767            key, msg = ref_to_key( bits[2] )
768            self.parent.dataVariables.append( ('ns',key,msg,isRequired) )
769
770  def parse_groups(self):
771    self.parent.groups = []
772    self.parent.groupDict = {}
773    if self.currentSection == None:
774       return
775    for l in self.currentSection[1:]:
776      bits = map( string.strip, string.split(l,','))
777      if bits[1] not in self.parent.groupDict.keys():
778          self.parent.groupDict[bits[1]] = {}
779      if bits[0] == 'group':
780        cc = []
781        for r in string.split( bits[2], '.' ):
782           cc.append( '%' + ('(%s)s' % ref_to_key( r )[0] ) )
783        self.parent.groups.append( (bits[1], string.join( cc, '.' ) ) )
784
785  def parse_constraints(self):
786    if self.currentSection == None:
787       self.parent.constraintTests = False
788       return
789    self.parent.constraintTests = True
790    self.parent.constraints = []
791    self.parent.groupConstraints = {}
792    for l in self.currentSection[1:]:
793       bits = map( string.strip, string.split(l,','))
794       bb = string.split( bits[0], ':' )
795       if len(bb) == 2:
796         gid = bb[0]
797         cid = bb[1]
798         if gid not in self.parent.groupConstraints.keys():
799            self.parent.groupConstraints[gid] = []
800       else:
801         gid = None
802         cid = bits[0]
803       assert cid in ['identity','onlyOnce','constant','special'], 'constraint id %s not recognised' % cid
804
805       if cid == 'identity':
806         cstr = Constraint__IdentityChecker( bits[1], bits[2] )
807       elif cid == 'onlyOnce':
808         ## print 'Set Constraint only once, %s ' % bits[1]
809         cstr = Constraint__OnlyOnce( bits[1] )
810       elif cid == 'constant':
811         ## print 'Set Constraint only once, %s ' % bits[1]
812         cstr = Constraint__Constant( bits[1] )
813       elif cid == 'special':
814         assert bits[1] in ['mip','CordexInterpolatedGrid'], 'Special constraint [%s] not recognised' % bits[1]
815         if bits[1] == 'mip':
816            cstr = self.parent.mipConstraint
817         elif bits[1] == 'CordexInterpolatedGrid':
818            cstr = Constraint__CordexInterpolatedGrid()
819         
820       if gid == None:
821           self.parent.constraints.append( cstr )
822       else:
823           self.parent.groupConstraints[gid].append( cstr )
824
825  def parse_variables(self):
826    if self.currentSection == None:
827       self.parent.variableTests = False
828       return
829    self.parent.variableTests = True
830    self.parent.requiredVariables = []
831    for l in self.currentSection[1:]:
832       bits = map( string.strip, string.split(l,','))
833       isDimension = bits[0] == 'dimension'
834       if bits[2] == 'required':
835         if bits[1][0] != '$':
836           self.parent.requiredVariables.append( (bits[1],isDimension) )
837         else:
838           key,info = ref_to_key( bits[1][1:] )
839           if key == 'VALUE':
840             self.parent.requiredVariables.append( (info,isDimension) )
841           else:
842             self.parent.requiredVariables.append( ('$%s' % key, isDimension) )
843           
844
845
846  def parse_path(self):
847    if self.currentSection == None:
848       self.pathTests = False
849       return
850    self.pathTests = True
851    self.datasetIdMethod = None
852    self.datasetVersionMode = [None,]
853    self.parent.datasets = {}
854    datasetHierarchy = None
855    for l in self.currentSection[1:]:
856       bits = map( string.strip, string.split(l,','))
857       if bits[0] == 'datasetVersion':
858         vdsName = bits[1]
859         if bits[2] == 'pathElement':
860           self.datasetVersionMode = ['pathElement',]
861           self.versionPathElement = int( bits[3] )
862         if bits[4] == 'regex':
863           self.datasetVersionMode.append( 'regex' )
864           self.datasetVersionRe = re.compile( string.strip( bits[5], '"' ) )
865         else:
866           self.datasetVersionMode.append( None )
867       elif bits[0] == 'datasetId':
868         thisDs = dataset(bits[1])
869         thisDs.datasetIdMethod = bits[2]
870         if bits[2] == 'prints':
871           thisDs.getDatasetId = lambda x: bits[3] % x
872           thisDs.datasetIdTuple = tuple( bits[4:] )
873         elif bits[2] == 'joinFileNameSegSlice':
874           thisSlice = slice( int(bits[4]), int(bits[5]) )
875           thisDs.getDatasetId = dsid1( thisSlice, bits[3] ).get
876           thisDs.datasetIdArg = 'fileNameBits'
877         elif bits[2] == 'cmip5':
878           thisSlice = slice( int(bits[4]), int(bits[5]) )
879           thisDs.getDatasetId = cmip5_dsid( thisSlice, bits[3] ).get
880           thisDs.datasetIdArg = 'filePathBits'
881         self.parent.datasets[bits[1]] = thisDs
882       elif bits[0] == 'datasetHierarchy':
883         datasetHierarchy = bits[1:]
884       elif bits[0] == 'omitDirectories':
885         self.parent.omitDirectories = string.split( string.strip( bits[1] ) )
886
887    if self.datasetVersionMode[0] != None:
888      assert vdsName in self.parent.datasets.keys(), 'Invalid dataset specified for version: %s [%s]' % (vdsName, str( self.parent.datasets.keys() ) )
889      self.versionnedDataset = self.parent.datasets[ vdsName ]
890
891    if datasetHierarchy == None:
892      self.datasetHierarchy = False
893    else:
894      self.datasetHierarchy = True
895      bb = string.split( string.strip( datasetHierarchy[0]), '/' )
896      for b in bb:
897        assert b in self.parent.datasets.keys(), 'Invalid dataset hierarchy, %s not among defined datasets' % b
898      for k in self.parent.datasets.keys():
899        self.parent.datasets[k].inHierarchy = k in bb
900         
901      for k in range( len(bb) ):
902        if k == 0:
903          self.parent.datasets[bb[k]].parent = None
904        else:
905          self.parent.datasets[bb[k]].parent = self.parent.datasets[bb[k-1]]
906        if k == len(bb)-1:
907          self.parent.datasets[bb[k]].child = None
908        else:
909          self.parent.datasets[bb[k]].child = self.parent.datasets[bb[k+1]]
910         
911class dsid1(object):
912
913  def __init__(self,slice,sep):
914    self.slice = slice
915    self.sep = sep
916
917  def get(self,x):
918    return string.join( x[self.slice], self.sep ) 
919
920class cmip5_dsid(object):
921
922  def __init__(self,slice,sep):
923    self.slice = slice
924    self.sep = sep
925
926  def get(self,x,y):
927    return '%s_%s.%s' % (string.join( x[self.slice], self.sep ) , y[-2], y[-1] )
928
929
930class get_trange(object):
931 
932  def __init__(self,pat,kseg):
933    self.kseg = kseg
934    self.re_istr = re.compile( '^[0-9]*$' )
935    if type( pat ) == type( 'x' ):
936      self.pat = pat
937      self.re = re.compile( pat )
938    else:
939      self.re = pat
940
941  def _test( self, s):
942    return self.re.match( s ) != None
943
944  def _get( self, s, handler=None ):
945    x = self.re.match( s )
946    tb = [None,None]
947    if x == None:
948      return False, tuple(tb)
949
950    thisOk = True
951    tb[0] = x.groupdict().get( 'start', None )
952    tb[1] = x.groupdict().get( 'end', None )
953    if x.groupdict().has_key( 'isClim' ):
954      tb.append( x.groupdict()['isClim'] )
955    for i in range(2):
956        b = tb[i]
957        if b != None:
958           if self.re_istr.match( b ) == None:
959              if handler != None:
960                handler._log( 'CQC.101.001.003',  dir + f, 'part of string not an integer' )
961              thisOk = False
962           else:
963             tb[i] = int(tb[i])
964
965    return thisOk, tb
966
967
968  def test(self, l ):
969    if len(l) < self.kseg + 1:
970      return True
971    return self._test( l[self.kseg] )
972
973  def get(self,l):
974    if len(l) < self.kseg + 1:
975      return True, (None,None)
976    return self._get( l[self.kseg] )
977         
978class fileNameSegments(object):
979  def __init__(self, parent, sep, nn ):
980    self.sep = sep
981    self.nn = nn
982    self.nn.sort()
983    self.__segments  = {}
984    self.parent = parent
985
986  def add( self, line ):
987    bits = map( string.strip, string.split( line, ', ' ) )
988    k = int(bits[0])
989    if bits[1] == 'vocabulary':
990      assert bits[2] in self.parent.vocab.keys(), 'Vocabulary specified in file name section not defined in vocab sections, %s' % bits[2]
991     
992      self.__segments[k] = ('vocabulary',bits[2])
993    elif bits[1][0:5] == 'regex' or bits[2] == 'TimeRange':
994      try:
995        regex = re.compile( string.strip( bits[3], "'") )
996      except:
997        print 'Failed to compile (in re): %s' % bits[3]
998        raise baseException(  'Failed to compile (in re): %s' % bits[3] )
999      self.__segments[k] = (bits[1],bits[2], regex)
1000    else:
1001      self.__segments[k] = tuple( bits[1:] )
1002
1003    if bits[2] == 'TimeRange':
1004       self.parent.hasTimeRange = True
1005       self.parent.timeRange = get_trange(regex,k)
1006
1007  def finish(self):
1008    sl = []
1009    for k in range(self.nn[-1]):
1010      sl.append( self.__segments.get( k, None ) )
1011    self.segments = tuple( sl )
1012
1013class Constraint__CordexInterpolatedGrid(object):
1014
1015  def __init__(self):
1016     self.code = 'CQC.999.999.999'
1017     self.name = 'CordexInterpolatedGrid'
1018     self.mode = 'd'
1019
1020  def __reset__(self):
1021    pass
1022
1023  def check(self,fns):
1024    region = fns.get( 'g_CORDEX_domain', 'unset' )
1025    assert region != 'unset', 'CORDEX domain not found in %s' % str(fns.keys())
1026    if region[-3:] != '44i':
1027       self.msg = 'Interpolated grid constraint not applicable to region %s' % region
1028       return ('PASS',self.msg)
1029    print 'WARNING -- check not implemented'
1030    self.msg = 'WARNING -- check not implemented'
1031    return ('PASS',self.msg)
1032
1033class Constraint__IdentityChecker(object):
1034
1035  def __init__(self, ref1, ref2 ):
1036     self.code = 'CQC.102.002.006'
1037     self.name = 'IdentityChecker'
1038     self.mode = 'd'
1039     self.Ref1 = self.__parse_ref(ref1)
1040     self.Ref2 = self.__parse_ref(ref2)
1041     if self.Ref1 == 'VALUE':
1042       self.Ref1 = self.Ref2
1043       self.Ref2 = 'VALUE'
1044     if self.Ref2 == 'VALUE':
1045       self.mode = 's'
1046
1047     if self.mode == 's':
1048       self.PassMsg = '%s (%s) equals %s' % (self.Ref1[1], ref1, self.value)
1049       self.FailMsg = '%s (%s) not equal %s' % (self.Ref1[1], ref1, self.value)
1050     else:
1051       self.PassMsg = '%s (%s) not equal %s (%s)' % (self.Ref1[1],ref1, self.Ref2[1],ref2)
1052       self.FailMsg = '%s (%s) not equal %s (%s)' % (self.Ref1[1],ref1, self.Ref2[1],ref2)
1053
1054  def __parse_ref(self,ref):
1055     bits = string.split(ref,'/')
1056     assert bits[0] in ['VALUE','PATH','FILENAME','ATTRIBUTES','CONFIG','ARGS'], 'Bad line in CONSTRAINT section of config file'
1057     if bits[0] == 'ATTRIBUTES':
1058        if bits[1] == 'Global':
1059           return ('g_%s' % bits[2],'Global attribute %s' % bits[2] )
1060     elif bits[0] == 'FILENAME':
1061           return ('fn_%s' % bits[1],'File name component %s' % bits[1] )
1062     elif bits[0] == 'VALUE':
1063           self.value = bits[1]
1064           return 'VALUE'
1065
1066  def __reset__(self):
1067    pass
1068
1069  def check(self,fns):
1070    if self.mode == 's':
1071      if fns.has_key( self.Ref1[0] ):
1072        if fns[self.Ref1[0]] == self.value:
1073          self.msg = self.PassMsg
1074          return ('PASS',self.msg)
1075        else:
1076          self.msg = self.FailMsg + ' [%s]' % fns[self.Ref1[0]] 
1077          return ('FAIL',self.msg)
1078      else:
1079        return ('DEFER', 'No entry in fns for %s' % self.Ref1[0])
1080    else:
1081      if fns.has_key( self.Ref1[0] ) and fns.has_key( self.Ref2[0] ):
1082        if fns[self.Ref1[0]] == fns[self.Ref2[0]]:
1083          self.msg = self.PassMsg
1084          return ('PASS',self.msg)
1085        else:
1086          self.msg = self.FailMsg + ' [%s,%s]' % (fns[self.Ref1[0]] , fns[self.Ref2[0]])
1087          return ('FAIL',self.msg)
1088      else:
1089        return ('DEFER', 'No entry in fns for %s,%s' % (self.Ref1[0],self.Ref2[0]))
1090
1091def parse_ref(ref):
1092     bits = string.split(ref,'/')
1093     assert bits[0] in ['VALUE','PATH','FILENAME','FILENAMEregex','ATTRIBUTES','CONFIG','ARGS'], 'Bad line in CONSTRAINT section of config file'
1094     if bits[0] == 'ATTRIBUTES':
1095        if bits[1] == 'Global':
1096           return ('g_%s' % bits[2],'Global attribute %s' % bits[2] )
1097     elif bits[0] == 'FILENAME':
1098           return ('fn_%s' % bits[1],'File name component %s' % bits[1] )
1099     elif bits[0] == 'FILENAMEregex':
1100           return ('fnre_%s' % bits[1],'File name component %s' % bits[1] )
1101     elif bits[0] == 'VALUE':
1102           return ('VALUE', bits[1])
1103
1104class Constraint__OnlyOnce(object):
1105
1106  def __init__(self, ref1):
1107     self.code = 'CQC.102.004.005'
1108     self.name = 'OnlyOnce'
1109     self.nn = 0
1110     self.Ref1 = parse_ref(ref1)
1111     self.msg = '%s occurs only once' % self.Ref1[1]
1112
1113  def __reset__(self):
1114    self.nn = 0
1115
1116  def check(self,fns):
1117      if fns.has_key( self.Ref1[0] ):
1118        self.nn+=1
1119        if self.nn <= 1:
1120          return ('PASS' ,'Occurence rate OK')
1121        else:     
1122          return ('FAIL', '%s occurs too often' % self.Ref1[0] )
1123      else:
1124        keys = fns.keys()
1125        keys.sort()
1126        return ('PASS',None)
1127
1128#### Mip table variable attribute check
1129
1130class Constraint__VarDimsCordexHardWired(object):
1131  def __init__(self, attribVocabs,  kpat, keys, logger=None):
1132     self.code = 'CQC.102.002.010'
1133     self.name = 'VarAtts'
1134     self.tables = {}
1135     self.keys = keys
1136     self.kpat = kpat
1137     self.logger = logger
1138     self.plev_vars = ['clh','clm','cll','ua850','va850']
1139
1140class Constraint__VarAtts(object):
1141
1142  def __init__(self, attribVocabs,  kpat, keys, logger=None):
1143     self.code = 'CQC.102.002.010'
1144     self.name = 'VarAtts'
1145     self.tables = {}
1146     self.keys = keys
1147     self.kpat = kpat
1148     self.logger = logger
1149     for t in attribVocabs.keys():
1150        self.tables[t] = {}
1151        for i in attribVocabs[t]:
1152          self.tables[t][i[0]] = (i[1],i[2])
1153        self.log( 'i', 'Initialising Constraint__VarAtts, table %s' % t )
1154
1155  def log( self, lev, msg ):
1156     if self.logger != None:
1157       if lev == 'i':
1158         self.logger.info( msg )
1159       elif lev == 'w':
1160         self.logger.warn( msg )
1161     
1162  def check(self, fns):
1163     self.msg = 'starting'
1164     mip = self.kpat % fns
1165     var = fns['fn_variable']
1166     if not self.tables.has_key( mip ):
1167       self.msg = 'VarAtts: no table found -- kpat = %s' % self.kpat
1168       return ('FAIL', self.msg )
1169     res = self.__check( mip, var, fns )
1170     return res
1171
1172  def __check( self, mip, var, fns ):
1173     ms = ''
1174     self.log( 'i', 'CHECKING %s' % var )
1175     nf = 0
1176     if not self.tables[mip].has_key(var):
1177       self.msg = 'Variable %s not present in table %s' % (var,mip)
1178       ##print ('FAIL',self.msg)
1179       return ('FAIL', self.msg)
1180     assert fns.has_key( 'v_%s' % var ), 'Internal error: attempt to check variable %s which is not in fns' % var
1181     mip_dims, mip_ats = self.tables[mip][var]
1182     var_dims = fns['v_%s' % var ][0]
1183     for k in self.keys:
1184        if mip_ats[k] != fns['v_%s' % var ][1][k]:
1185           self.log( 'w', 'Variable attribute mismatch: %s -- %s' % (mip_ats[k], str(fns['v_%s' % var ]) ) )
1186           nf += 1
1187           ms += '%s; ' % k
1188        else:
1189           self.log( 'i', 'Attribute OK: %s' % (self.tables[mip][var][1][k]) )
1190
1191     # unclear how to proceed here -- want to check, e.g., time dimension.  -- possibly easiest to have a CORDEX_SPECIAL flag for some ad hoc code..
1192     # ideally get axis information from "axis_entry" in mip tables -- need to improve the scanner for this.
1193     ##print 'DIMS:  %s -- %s -- %s' % (var, str(mip_dims), str(var_dims))
1194     if nf > 0:
1195       if nf == 1:
1196         self.msg = 'Failed 1 attribute test: %s' % ms
1197       else:
1198         self.msg = 'Failed %s attribute tests: %s' % (nf,ms)
1199       ##print ('FAIL',self.msg)
1200       return ('FAIL',self.msg)
1201     else:
1202       ##print ('PASS','%s attributes checked' % len(self.keys) )
1203       return  ('PASS','%s attributes checked' % len(self.keys) )
1204
1205#### check whether a NS element is constant
1206class Constraint__Constant(object):
1207
1208  def __init__(self, ref1, required=False):
1209     self.code = 'CQC.102.002.006'
1210     self.name = 'Constant'
1211     self.nn = 0
1212     self.Ref1 = parse_ref(ref1)
1213     self.msg = '%s occurs only once' % self.Ref1[1]
1214     self.value = None
1215     self.required = required
1216
1217  def __reset__(self):
1218    self.nn = 0
1219    self.value = None
1220
1221  def check(self,fns):
1222      if fns.has_key( self.Ref1[0] ):
1223        if self.value == None:
1224          self.value = fns[self.Ref1[0]]
1225          return ('DEFER', 'first element')
1226        else:
1227          if self.value == fns[self.Ref1[0]]:
1228            return ('PASS','%s checked' % self.Ref1[0] )
1229          else:
1230            return ('FAIL', '%s not constant across file group' %  self.Ref1[0] )
1231      else:
1232        if self.required:
1233          return ('FAIL', 'missing NS element %s' % self.Ref1[0] )
1234        else:
1235          return ('PASS',None)
1236
1237def ref_to_key(ref):
1238     bits = string.split(ref,'/')
1239     assert bits[0] in ['VALUE','PATH','FILENAME','ATTRIBUTES','CONFIG','ARGS'], 'Bad line in CONSTRAINT section of config file'
1240     if bits[0] == 'ATTRIBUTES':
1241        if bits[1] == 'Global':
1242           return ('g_%s' % bits[2],'Global attribute %s' % bits[2] )
1243     elif bits[0] == 'FILENAME':
1244           return ('fn_%s' % bits[1],'File name component %s' % bits[1] )
1245     elif bits[0] == 'VALUE':
1246           return ('VALUE',bits[1])
1247
1248class section_parser_l0(object):
1249
1250  def __init__(self,parent,sectionName):
1251     self.sname = sectionName
1252     self.parent = parent
1253     self.lines = []
1254     
1255  def add( self, l ):
1256    self.lines.append( string.strip( l ) )
1257
1258  def close(self):
1259    assert type(self.parent.sections) == type( {} ), 'parent.sections has wrong type (%s), should be a dictionary' % ( str( type( self.parent.sections ) ) )
1260
1261    self.parent.sections[self.sname] = self.lines[:]
1262    self.lines = []
Note: See TracBrowser for help on using the repository browser.