source: CCCC/tags/0.1/fcc_utils.py @ 60

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/CCCC/tags/0.1/fcc_utils.py
Revision 60, 44.8 KB checked in by mjuckes, 7 years ago (diff)

adding CCCC code

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