source: FCC/qc_utils.py @ 39

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/FCC/qc_utils.py@1037
Revision 39, 34.8 KB checked in by mjuckes, 7 years ago (diff)

2 new vocabularies, minor changes to accomodate these

Line 
1import string, os, re, stat
2
3ncdumpCmd = '/usr/local/5/bin/ncdump'
4ncdumpCmd = 'ncdump'
5##
6## this class carries a logging method, and is used to carry information about datasets being parsed.
7##
8class qcHandler:
9
10  def __init__( self, qcc, log, baseDir, logPasses=True ):
11    self.datasets = {}
12    self.groups = {}
13    self.baseDir = baseDir
14    self.logPasses = logPasses
15    self.log = log
16    self.nofail = True
17    self.hasTimeRange = False
18    for k in qcc.datasets.keys():
19      self.datasets[k] = {}
20    for g in qcc.groups:
21      self.groups[g[0]] = { 'pat':g[1]}
22    self.msg = {}
23    self.msgk = {}
24    self.msg['CQC.101.001.001'] = 'File size above 10 bytes'
25    self.msg['CQC.101.001.002'] = 'File name matches DRS syntax'
26    self.msg['CQC.101.001.003'] = 'File name time component matches DRS syntax'
27    self.msg['CQC.101.001.004'] = 'File name component not in vocabulary'
28    self.msg['CQC.101.001.005'] = 'File name component does not match regex'
29    self.msg['CQC.101.001.006'] = 'File name component does not match regex list'
30    self.msg['CQC.101.001.007'] = 'File name component does not match regex list with constraints'
31    self.msg['CQC.102.002.001'] = 'File name time components in ADS have same length'
32    self.msg['CQC.102.002.002'] = 'File name time components in ADS do not overlap'
33    self.msg['CQC.102.002.003'] = 'File name time components in ADS have no gaps'
34    self.msg['CQC.102.002.004'] = 'File name time components in ADS have correct gap for monthly data'
35    self.msg['CQC.102.002.005'] = 'File name time components present for multi-file dataset'
36    self.msg['CQC.102.002.006'] = 'Consistency checks'
37    self.msg['CQC.102.002.007'] = 'Required variables'
38    self.msg['CQC.102.002.008'] = 'Required data variables'
39    self.msg['CQC.102.002.009'] = 'File is a recognised NetCDF format'
40    self.msg['CQC.200.003.001'] = 'NetCDF files occur at one directory level'
41    self.msg['CQC.103.003.002'] = 'Conformant version directory'
42    self.msg['CQC.103.003.003'] = 'Latest link points to most recent version'
43    self.msg['CQC.200.003.004'] = 'ads occurs in a single directory'
44    self.msg['CQC.104.004.001'] = 'Consistent global attributes across experiment'
45    self.msg['CQC.105.004.002'] = 'Valid calendar attribute'
46    self.msg['CQC.101.004.003'] = 'Regular time step in file'
47    self.msg['CQC.102.004.004'] = 'Regular time step between files'
48    self.msg['CQC.102.004.005'] = 'Consistent global attributes across ADS'
49    self.msg['CQC.105.004.006'] = 'Consistent global attributes across ensemble'
50    self.msg['CQC.101.004.007'] = 'Required global attributes'
51    self.msg['CQC.103.900.001'] = 'Identifiedmost recent version'
52    self.msg['CQC.103.003.005'] = 'Version directories identified in directory containing "latest"'
53## error keys: when these occur, further processing of that file is blocked.
54    self.errorKeys = ['CQC.101.001.001', 'CQC.101.001.002']
55## keys in this list will not be recorded as failed tests.
56    self.ignoreKeys = []
57    for k in self.msg.keys():
58        self.msgk[k] = 0
59
60  def _log( self, key, item, msg, ok=False ):
61    if ok:
62      if self.logPasses:
63         thisMsg = '%s OK: %s: %s: %s' % (key,item,self.msg[key], msg)
64         self.log.info( thisMsg )
65      return
66
67    if key not in self.ignoreKeys:
68      self.nofail = False
69    item = string.replace( item, self.baseDir, '' )
70    if key in self.errorKeys:
71       self.log.error( '%s [ERROR] FAIL !(%s): %s: %s' % (key,self.msg[key], item,msg))
72       self.noerror = False
73    else:
74       thisMsg = '%s FAIL !(%s): %s: %s' % (key,self.msg[key],item, msg)
75       self.log.info( thisMsg )
76             
77    self.msgk[key] += 1
78
79class dirParser:
80
81  def __init__(self, qcc, linksOnly=True):
82    self.nclevs = []
83    self.qcc = qcc
84    self.dirNames = {}
85    self.count_nc  = 0
86    self.linksOnly=linksOnly
87
88  def parse( self, handler,dir, files ):
89    handler.log.info( 'Directory: %s [%s]' % (dir, len(files)) )
90    bits = string.split(dir,'/')
91    thisLev = len(bits)
92    files.sort()
93    skipf = []
94
95    for f in files:
96      if os.path.isdir( '%s/%s' % (dir,f) ) and f in self.qcc.omitDirectories:
97        skipf.append(f)
98    for f in skipf:
99      handler.log.info( 'skipping %s' % f )
100      files.pop( files.index(f) )
101     
102# record diretory names at each level
103    if thisLev not in self.dirNames.keys():
104       self.dirNames[thisLev] = []
105    if bits[-1] not in self.dirNames[thisLev]:
106       self.dirNames[thisLev].append( bits[-1] )
107
108    ncFiles = []
109    for f in files:
110      if f[-3:] == ".nc" and (not self.linksOnly or os.path.islink('%s/%s'%(dir,f))):
111        ncFiles.append(f)
112
113# record which directory levels contain netcdf files
114    if len(ncFiles) and thisLev not in self.nclevs:
115      self.nclevs.append( thisLev )
116     
117    tbits = []
118    ncFiles.sort()
119    self.count_nc += len( ncFiles )
120    dbits = string.split( string.strip(dir,'/'), '/' )
121    for f in ncFiles:
122      fpath = '%s/%s' % (dir,f)
123      handler.noerror = True
124      handler.nofail = True
125
126      if not os.path.islink( fpath ):
127         fsb = os.stat(  fpath  )[stat.ST_SIZE]
128      else:
129         fsb = os.stat(  fpath  )[stat.ST_SIZE]
130         if fsb < 10:
131           handler._log( 'CQC.101.001.001',  fpath, '' )
132
133      fbits = string.split( string.split(f,'.')[0], self.qcc.fileNameSegments.sep )
134      if not len( fbits ) in self.qcc.fileNameSegments.nn:
135        handler._log( 'CQC.101.001.002',  fpath, str(fbits) )
136######
137######
138      qfns = self.qcc.fileNameSegments
139      ns = {}
140      for k in range(len(fbits)): 
141         ns['fn_%s' % qfns.segments[k][1]] = fbits[k]
142         if qfns.segments[k][0] == 'vocabulary':
143           assert qfns.segments[k][1] in self.qcc.vocab.keys(), '%s not a valid vocabulary name' % qfns.segments[k][1]
144           if not fbits[k] in self.qcc.vocab[qfns.segments[k][1]]:
145              handler._log( 'CQC.101.001.004',  fpath, 'Not in vocab %s' % qfns.segments[k][1] )
146         elif qfns.segments[k][0] == 'abstractVocab':
147           assert qfns.segments[k][1] in self.qcc.vocab.keys(), '%s not a valid abstract vocabulary name' % qfns.segments[k][1]
148           this = self.qcc.vocab[qfns.segments[k][1]]
149           assert this[0] == 'regex', 'Unexpected start of abstractVocab, %s' % str( this )
150           match = False
151           for s,t,tt in this[1]:
152              if s.match( fbits[k] ):
153                match = True
154                ## print 'Match [%s] found for %s {%s}' % (t,fbits[k],tt)
155                for k in y.groupdict().keys():
156                    ns['fnre_%s' % k] = y.groupdict()[k]
157                if tt != None:
158                  ##print 'trying further test'
159                  tt1 = string.replace(tt,'$','_arg_')
160                  y = s.match( fbits[k] )
161                  for k in y.groupdict().keys():
162                    eval( '_arg_%s = int( %s )' % (k,y.groupdict()[k] ) )
163                  eval( 'res = tt1' )
164                  ##print res
165              else:
166                pass
167                ## print 'no match [%s] for %s ' % (t,fbits[k])
168                   
169           if not match:
170              handler._log( 'CQC.101.001.006',  fpath, 'Failed abstractVocab regex tests %s' % fbits[k] )
171         elif qfns.segments[k][0] == 'condAbstractVocab':
172           assert qfns.segments[k][1] in self.qcc.vocab.keys(), '%s not a valid abstract vocabulary name' % qfns.segments[k][1]
173           this = self.qcc.vocab[qfns.segments[k][1]]
174           assert this[0] == 'regex', 'Unexpected start of abstractVocab, %s' % str( this )
175           match = False
176           olc = 0
177           for sss in this[1]:
178             ol = False
179             if sss[0] == '*':
180               ol = True
181             else:
182               for b in string.split(sss[0],','):
183                 if b in fbits:
184                   ol = True
185             if ol:
186               nunc = 0
187               olc += 1
188               for s,t,tt in sss[1]:
189
190                 if not match:
191                  y = s.match( fbits[k] )
192                  if y:
193                    ## print 'Match [%s] found for %s {%s}' % (t,fbits[k],tt)
194                    nunc += 1
195                    for key in y.groupdict().keys():
196                        ns['fnre_%s' % key] = y.groupdict()[key]
197                    ##print '--- Match [%s] found for %s {%s}' % (t,fbits[k],tt)
198                    if tt != None:
199                      ## create string with test condition.`
200                      tt1 = string.replace(tt,'$','_arg_')
201                      y = s.match( fbits[k] )
202                      for key in y.groupdict().keys():
203                        locals()['_arg_%s' % key ] = int( y.groupdict()[key] )
204                        ##print '_arg_%s' % key , locals()['_arg_%s' % key ]
205                      res = eval( tt1 )
206                      ##print res,tt1
207                      if res:
208                        match = True
209                    else:
210                      match = True
211                  else:
212                    ##print 'no match [%s] for %s ' % (t,fbits[k])
213                    pass
214             ##else:
215               ##print 'No overlap for %s, %s' % (sss[0],str(fbits))
216           if olc == 0:
217             ##print 'No matches fround for %s' % str(fbits)
218             pass
219                   
220           if not match:
221              handler._log( 'CQC.101.001.007',  fpath, 'Failed constrained regex tests %s (%s uncomnditional matches)' % (fbits[k], nunc) )
222         elif qfns.segments[k][0] == 'regex-match':
223           res = qfns.segments[k][2].match( fbits[k] )
224           if res == None:
225               handler._log( 'CQC.101.001.005',  fpath, 'Failed regex-match test: %s [%s]' % (fbits[k],qfns.segments[k][1] ) )
226         elif qfns.segments[k][0] == 'vocabulary*':
227           pass
228         else:
229           print 'segment test id %s not recognised' % qfns.segments[k][0]
230           raise 'segment test id %s not recognised' % qfns.segments[k][0]
231##################################
232      versionned = False
233      if not versionned:
234        for k in self.qcc.datasets.keys():
235          if self.qcc.datasets[k].datasetIdArg == 'fileNameBits':
236            dsId = self.qcc.datasets[k].getDatasetId( fbits )
237          elif self.qcc.datasets[k].datasetIdArg == 'filePathBits':
238            try:
239              dsId = self.qcc.datasets[k].getDatasetId( fbits, dbits )
240            except:
241              print 'Failed to get dsID:',fbits,dbits
242              raise
243          else:
244            assert False, 'datasetIdMethod %s not supported yet' % self.qcc.datasets[k].datasetIdMethod
245           
246          if os.path.islink( fpath ):
247            dsId += '_lnk'
248          if not handler.datasets[k].has_key( dsId ):
249            handler.datasets[k][dsId] = []
250          handler.datasets[k][dsId].append( (dir,f, handler.nofail, ns) )
251
252class dataSetParser:
253
254  def __init__(self,qcc, log, handler):
255    self.qcc = qcc
256    self.log = log
257    self.h = handler
258    self.re_istr = re.compile( '^[0-9]*$' )
259
260  def parse(self,dsclass, dsid, files, inFileChecks=False, forceInFileChecks=True):
261      self.h.nofail = True
262      fns = map( lambda x: x[1], self.qcc.fileNameSegments.segments )
263      dsok = True
264      for dir,f, fok, ns in files:
265        dsok &= fok
266
267      self.h.nofail = dsok
268##
269## this test should have a switch -- only to be applied to one category of file group
270## need dsclass constraints
271##
272## constraint: setOnce:
273##
274      if dsok:
275        if self.qcc.hasTimeRange:
276          allOk = True
277          tbl = []
278          for dir,f, fok, ns in files:
279            thisOk = True
280            fbits = string.split( string.split(f,'.')[0], self.qcc.fileNameSegments.sep )
281            thisOk, tb = self.qcc.timeRange.get( fbits )
282
283            allOk &= thisOk
284            tbl.append( tb )
285
286          if allOk:
287            kkl = []
288            for tb in tbl:
289              kk = 0
290              for i in range(2):
291                if tb[i] != None:
292                  ## tb[i] = int(tb[i])
293                  kk+=1
294              kkl.append(kk)
295           
296            thisOk = True
297            cc = ''
298            for k in range( len(tbl)-1  ):
299              if kkl[k] != kkl[0]:
300                 thisOk = False
301                 cc += str(files[k])
302            self.h._log( 'CQC.102.002.001', cc, '', ok=thisOk )
303
304            self.h._log( 'CQC.102.002.005', '%s@%s' % (dsid,dsclass), '', ok=not(thisOk and kkl[0] == 0 and len(files) > 1) )
305
306            if thisOk and kkl[0] == 2:
307              cc = ''
308              for k in range( len(tbl) -1 ):
309                if tbl[k+1][0] <= tbl[k][1]:
310                  thisOk = False
311                  cc += '%s, %s [%s,%s];' % (str(files[k]), str(files[k+1]),tbl[k][1],tbl[k+1][0])
312              self.h._log( 'CQC.102.002.002', cc, '', ok=thisOk )
313
314###
315### run group constraints
316###
317          if self.qcc.groupConstraints.has_key( dsclass ):
318              for ct in self.qcc.groupConstraints[dsclass]:
319                 ct.__reset__()
320                 for dir,f, fok, ns in files:
321                     if fok:
322###
323                        rv,res = ct.check( ns )
324                        if rv != 'PASS':
325                          self.h._log( 'CQC.102.002.006', f, ct.msg, res )
326
327##
328## should only do the in-file checks once
329## intention is to be able to have multiple definitions of groups with different tests
330##
331      files2 = []
332      if (self.h.nofail and inFileChecks) or forceInFileChecks:
333          ##print 'starting in-file checks'
334          import ncd_parse
335          for dir,f, fok, ns in files:
336            if fok or forceInFileChecks:
337             tmpDumpFile = '/tmp/qc_ncdump_tmp.txt'
338             if os.path.isfile( tmpDumpFile ):
339               os.unlink( tmpDumpFile )
340             targf = '%s/%s' % (dir,f)
341             fsb = os.stat(  targf  )[stat.ST_SIZE]
342             assert fsb > 10, 'Small file slipped through: %s, %s' % (targ,fok)
343             cmd = '%s -k %s/%s 2>&1 > %s' % (ncdumpCmd,dir,f,tmpDumpFile)
344             res = os.popen( cmd ).readlines()
345             ii = open( tmpDumpFile ).readlines()
346             if len(ii) == 0:
347               this_ok = False
348             else:
349               this_ok = 'Unknown' not in ii[0]
350             self.h._log( 'CQC.102.002.009', '%s/%s' % (dir,f), '', ok=this_ok )
351             files2.append( (dir,f, this_ok, ns) )
352             if this_ok:
353               cmd = '%s -h %s/%s > %s' % (ncdumpCmd,dir,f,tmpDumpFile)
354               ii = os.popen( cmd ).readlines()
355               fsb = os.stat(  tmpDumpFile  )[stat.ST_SIZE]
356               assert fsb > 100, 'ncdump output too small, %s/%s' % (dir,f)
357             
358               rd = ncd_parse.read_ncdump( tmpDumpFile )
359               rd.parse()
360
361##
362## messy hack -- copying globals attributes into a new dictionary
363##
364               for k in rd.gats.keys():
365                 ns['g_%s' % k] = rd.gats[k]
366## rd.vars[k] is a tuple: (dims,atts), where atts is a dictionary of attributes.
367               for k in rd.vars.keys():
368                 ns['v_%s' % k] = rd.vars[k]
369               for k in rd.dims.keys():
370                 ns['d_%s' % k] = rd.dims[k]
371
372               if self.qcc.attributeTests:
373                 for a in self.qcc.requiredGlobalAttributes:
374                   self.h._log( 'CQC.101.004.007', '%s/%s' % (dir,f), 'Attribute: %s' % a, ok=a in rd.gats.keys() )
375
376          if self.qcc.variableTests:
377            for dir,f, fok, ns in files2:
378             if fok:
379              for rv in self.qcc.requiredVariables:
380                if rv[0][0] != '$':
381                  self.h._log( 'CQC.102.002.007', f, 'Required variable %s'% (rv[0]), 'v_%s' % rv[0] in ns.keys())
382
383          if self.qcc.groups:
384            for dir,f, fok, ns in files2:
385             if fok:
386               for g in self.qcc.groups:
387                  gid = g[1] % ns
388                  if not self.qcc.groupDict[g[0]].has_key( gid ):
389                    self.qcc.groupDict[g[0]][ gid ] = []
390                  self.qcc.groupDict[g[0]][ gid ].append( ( dir,f,fok) )
391                  ## print '%s:: %s' % (g[0],gid)
392           
393
394          if self.qcc.constraintTests:
395            for dir,f, fok, ns in files2:
396             if fok:
397              for ct in self.qcc.constraints:
398###
399                rv,res = ct.check( ns )
400                if rv != 'PASS':
401                  self.h._log( 'CQC.102.002.006', f, ct.msg, res )
402
403          if self.qcc.variableTests:
404            for dir,f, fok, ns in files2:
405             if fok:
406              for v in self.qcc.dataVariables:
407                var = ns[v[1]]
408                if v[0] == 'ns':
409                  isPresent = 'v_%s' % var in ns.keys()
410                  if v[3]:
411                    self.h._log( 'CQC.102.002.008', f, '%s [%s::%s]'% (var,v[1],v[2]), isPresent )
412       
413
414class dataset:
415   def __init__(self,name):
416     self.name = name
417
418class qcConfigParse:
419
420  def __init__( self, file ):
421    assert os.path.isfile( file ), '%s not found' % file
422    self.firstFile = True
423    self.fh = open( file )
424    self.file = file
425    self.sections = {}
426    self.omitDirectories = []
427
428  def close(self):
429    self.fh.close()
430    self.file = None
431
432  def open(self,file):
433    assert os.path.isfile( file ), '%s not found' % file
434    self.fh = open( file )
435    self.file = file
436
437  def parse_l0(self):
438    f = False
439    sname = None
440    for l in self.fh.readlines():
441      if f:
442        if l[0:4] == 'END ' and string.index( l,sname) == 4:
443          f = False
444          self._parse_l0_section.close()
445        else:
446          self._parse_l0_section.add( l )
447      elif l[0:6] == 'START ':
448        sname = string.strip( string.split(l)[1] )
449        self._parse_l0_section = section_parser_l0( self, sname )
450        f = True
451
452  def parse_l1(self):
453
454     if self.firstFile:
455       requiredSections = ['FILENAME', 'VOCABULARIES','PATH']
456     else:
457       requiredSections = []
458     self.firstFile = False
459
460     for s in requiredSections:
461       assert s in self.sections.keys(), 'Required section %s not found in %s [parsing %s]' % (s, self.section.keys(),self.file)
462     self._parse_l1 = section_parser_l1( self )
463     self._parse_l1.parse( 'GENERAL' )
464     self._parse_l1.parse( 'VOCABULARIES' )
465     self._parse_l1.parse( 'FILENAME' )
466     self._parse_l1.parse( 'PATH' )
467     self._parse_l1.parse( 'ATTRIBUTES' )
468     self._parse_l1.parse( 'VARIABLES' )
469     self._parse_l1.parse( 'CONSTRAINTS' )
470     self._parse_l1.parse( 'GROUPS' )
471
472regv = re.compile( 'version=([0-9.]+)' )
473refs = re.compile( 'separator=(.+)' )
474revsc = re.compile( 'validSectionCount,(.+)' )
475
476class section_parser_l1:
477
478  def __init__(self,parent):
479     self.parent = parent
480     self.currentSection = None
481     self.gc = {}
482
483  def _getVersion( self ):
484    assert self.currentSection != None, '_getVersion called with no section set'
485    x = regv.findall( self.currentSection[0] )
486    assert len(x) == 1, 'valid version not identified at start of section: %s\n%s' % (self.currentSectionName,self.currentSection[0])
487    self.version = x[0]
488
489  def parse( self, sname ):
490    if self.parent.sections.has_key( sname ):
491
492      self.currentSectionName = sname
493      self.currentSection = self.parent.sections[sname]
494      self._getVersion()
495    else:
496      self.currentSection = None
497
498    self.parent.constraintTests = False
499    ## print 'Parsing %s' % sname
500    if sname == 'VOCABULARIES':
501      self.parent.vocab = {}
502      self.parse_vocabularies()
503    elif sname == 'FILENAME':
504      self.parse_filename()
505    elif sname == 'PATH':
506      self.parse_path()
507    elif sname == 'ATTRIBUTES':
508      self.parse_attributes()
509    elif sname == 'VARIABLES':
510      self.parse_variables()
511    elif sname == 'CONSTRAINTS':
512      self.parse_constraints()
513    elif sname == 'GENERAL':
514      self.parse_general()
515    elif sname == 'GROUPS':
516      self.parse_groups()
517
518  def __get_match( self, regex, line, id ):
519    x = regex.findall( line )
520    assert len(x) == 1, 'No match found, id=%s, line=%s' % 'id,line'
521    return x[0]
522
523  def parse_vocabularies(self):
524    ## print len( self.currentSection )
525    for l in self.currentSection[1:]:
526     
527      bits = map( string.strip, string.split( l, ', ' ) )
528      id = bits[0]
529      isAbstract = False
530      if id[0] == '*':
531        id = id[1:]
532        isAbstract = True
533
534      sl = string.split( bits[1], '|' )
535      fromFile = 'file' in  sl
536      isRegex = 'regex' in sl
537      withSub = 'sub' in sl
538      isCond = 'cond' in sl
539
540      if not fromFile:
541        vlist = string.split( bits[2] )
542      else:
543        fn = bits[2]
544        assert os.path.isfile( fn), 'File %s (specified as vocabulary %s) not found' % (fn,bits[0] )
545        ii = open( fn ).readlines()
546        bb = string.split( bits[1], '|' )
547        if '1stonline' in sl:
548          vlist = []
549          for i in ii:
550            if i[0] != '#' and len( string.strip(i) ) > 0:
551              vlist.append( string.split( string.strip(i) )[0] )
552        elif '1perline' in sl:
553          vlist = map( string.strip, ii )
554        else:
555          assert False, 'file syntax option (%s) not recognised' % bits[1]
556
557      if isRegex:
558        cr = []
559        if withSub:
560          if isCond:
561            for ccc in vlist:
562               i0 = ccc.index(':')
563               cc = ccc[:i0]
564               cr0 = []
565               for v in string.split( ccc[i0+1:] ):
566                   v = string.strip(v)
567                   if v[0] == '{':
568                     i1 = v.index('}')
569                     tt = v[1:i1]
570                     v = v[i1+1:]
571                   else:
572                     tt = None
573                   v = string.strip( v, "'" )
574                   cr0.append( (re.compile( v % self.gc ),v % self.gc,tt) )
575                   ## print 'compiled ' +  v % self.gc, tt
576               cr.append( (cc,cr0) )
577          else:
578            for v in vlist:
579               v = string.strip( v, "'" )
580               cr.append( (re.compile( v % self.gc ),v % self.gc) )
581        else:
582          for v in vlist:
583             v = string.strip( v, "'" )
584             cr.append( (re.compile( v ),v) )
585        self.parent.vocab[id] = ('regex', cr )
586      else:
587        self.parent.vocab[id] = vlist[:]
588
589  def parse_filename(self):
590    sep = self.__get_match( refs, self.currentSection[1], 'File separator' )
591    nn = map( int, string.split( self.__get_match( revsc, self.currentSection[2], 'File separator' ),',') )
592    self.parent.fileNameSegments = fileNameSegments( self.parent, sep, nn )
593    for l in self.currentSection[3:]:
594       self.parent.fileNameSegments.add(l)
595    self.parent.fileNameSegments.finish()
596
597  def parse_attributes(self):
598    if self.currentSection == None:
599       self.parent.attributeTests = False
600       return
601    self.parent.requiredGlobalAttributes = []
602    self.parent.attributeTests = True
603    for l in self.currentSection[1:]:
604      bits = map( string.strip, string.split(l,','))
605      if bits[0] == 'global':
606        if bits[2] == 'required':
607           self.parent.requiredGlobalAttributes.append( bits[1] )
608
609  def parse_general(self):
610    if self.currentSection == None:
611       return
612    self.parent.requiredGlobalAttributes = []
613    self.parent.dataVariables = []
614    for l in self.currentSection[1:]:
615      if l[0] == '$':
616        bits = map( string.strip, string.split(l[1:],'='))
617        self.gc[bits[0]] = bits[1]
618      else:
619        bits = map( string.strip, string.split(l,','))
620        if bits[0] == 'DataVariable':
621          if bits[1] == 'byName':
622            isRequired = bits[3] == 'required'
623            key, msg = ref_to_key( bits[2] )
624            self.parent.dataVariables.append( ('ns',key,msg,isRequired) )
625
626  def parse_groups(self):
627    self.parent.groups = []
628    self.parent.groupDict = {}
629    if self.currentSection == None:
630       return
631    for l in self.currentSection[1:]:
632      bits = map( string.strip, string.split(l,','))
633      if bits[1] not in self.parent.groupDict.keys():
634          self.parent.groupDict[bits[1]] = {}
635      if bits[0] == 'group':
636        cc = []
637        for r in string.split( bits[2], '.' ):
638           cc.append( '%' + ('(%s)s' % ref_to_key( r )[0] ) )
639        self.parent.groups.append( (bits[1], string.join( cc, '.' ) ) )
640
641  def parse_constraints(self):
642    if self.currentSection == None:
643       self.parent.constraintTests = False
644       return
645    self.parent.constraintTests = True
646    self.parent.constraints = []
647    self.parent.groupConstraints = {}
648    for l in self.currentSection[1:]:
649       bits = map( string.strip, string.split(l,','))
650       bb = string.split( bits[0], ':' )
651       if len(bb) == 2:
652         gid = bb[0]
653         cid = bb[1]
654         if gid not in self.parent.groupConstraints.keys():
655            self.parent.groupConstraints[gid] = []
656       else:
657         gid = None
658         cid = bits[0]
659       assert cid in ['identity','onlyOnce','constant'], 'constraint id %s not recognised' % cid
660
661       if cid == 'identity':
662         cstr = Constraint__IdentityChecker( bits[1], bits[2] )
663       elif cid == 'onlyOnce':
664         ## print 'Set Constraint only once, %s ' % bits[1]
665         cstr = Constraint__OnlyOnce( bits[1] )
666       elif cid == 'constant':
667         ## print 'Set Constraint only once, %s ' % bits[1]
668         cstr = Constraint__Constant( bits[1] )
669         
670       if gid == None:
671           self.parent.constraints.append( cstr )
672       else:
673           self.parent.groupConstraints[gid].append( cstr )
674
675  def parse_variables(self):
676    if self.currentSection == None:
677       self.parent.variableTests = False
678       return
679    self.parent.variableTests = True
680    self.parent.requiredVariables = []
681    for l in self.currentSection[1:]:
682       bits = map( string.strip, string.split(l,','))
683       isDimension = bits[0] == 'dimension'
684       if bits[2] == 'required':
685         if bits[1][0] != '$':
686           self.parent.requiredVariables.append( (bits[1],isDimension) )
687         else:
688           key,info = ref_to_key( bits[1][1:] )
689           if key == 'VALUE':
690             self.parent.requiredVariables.append( (info,isDimension) )
691           else:
692             self.parent.requiredVariables.append( ('$%s' % key, isDimension) )
693           
694
695
696  def parse_path(self):
697    if self.currentSection == None:
698       self.pathTests = False
699       return
700    self.pathTests = True
701    self.datasetIdMethod = None
702    self.datasetVersionMode = [None,]
703    self.parent.datasets = {}
704    datasetHierarchy = None
705    for l in self.currentSection[1:]:
706       bits = map( string.strip, string.split(l,','))
707       if bits[0] == 'datasetVersion':
708         vdsName = bits[1]
709         if bits[2] == 'pathElement':
710           self.datasetVersionMode = ['pathElement',]
711           self.versionPathElement = int( bits[3] )
712         if bits[4] == 'regex':
713           self.datasetVersionMode.append( 'regex' )
714           self.datasetVersionRe = re.compile( string.strip( bits[5], '"' ) )
715         else:
716           self.datasetVersionMode.append( None )
717       elif bits[0] == 'datasetId':
718         thisDs = dataset(bits[1])
719         thisDs.datasetIdMethod = bits[2]
720         if bits[2] == 'prints':
721           thisDs.getDatasetId = lambda x: bits[3] % x
722           thisDs.datasetIdTuple = tuple( bits[4:] )
723         elif bits[2] == 'joinFileNameSegSlice':
724           thisSlice = slice( int(bits[4]), int(bits[5]) )
725           thisDs.getDatasetId = dsid1( thisSlice, bits[3] ).get
726           thisDs.datasetIdArg = 'fileNameBits'
727         elif bits[2] == 'cmip5':
728           thisSlice = slice( int(bits[4]), int(bits[5]) )
729           thisDs.getDatasetId = cmip5_dsid( thisSlice, bits[3] ).get
730           thisDs.datasetIdArg = 'filePathBits'
731         self.parent.datasets[bits[1]] = thisDs
732       elif bits[0] == 'datasetHierarchy':
733         datasetHierarchy = bits[1:]
734       elif bits[0] == 'omitDirectories':
735         self.parent.omitDirectories = string.split( string.strip( bits[1] ) )
736
737    if self.datasetVersionMode[0] != None:
738      assert vdsName in self.parent.datasets.keys(), 'Invalid dataset specified for version: %s [%s]' % (vdsName, str( self.parent.datasets.keys() ) )
739      self.versionnedDataset = self.parent.datasets[ vdsName ]
740
741    if datasetHierarchy == None:
742      self.datasetHierarchy = False
743    else:
744      self.datasetHierarchy = True
745      bb = string.split( string.strip( datasetHierarchy[0]), '/' )
746      for b in bb:
747        assert b in self.parent.datasets.keys(), 'Invalid dataset hierarchy, %s not among defined datasets' % b
748      for k in self.parent.datasets.keys():
749        self.parent.datasets[k].inHierarchy = k in bb
750         
751      for k in range( len(bb) ):
752        if k == 0:
753          self.parent.datasets[bb[k]].parent = None
754        else:
755          self.parent.datasets[bb[k]].parent = self.parent.datasets[bb[k-1]]
756        if k == len(bb)-1:
757          self.parent.datasets[bb[k]].child = None
758        else:
759          self.parent.datasets[bb[k]].child = self.parent.datasets[bb[k+1]]
760         
761class dsid1:
762
763  def __init__(self,slice,sep):
764    self.slice = slice
765    self.sep = sep
766
767  def get(self,x):
768    return string.join( x[self.slice], self.sep ) 
769
770class cmip5_dsid:
771
772  def __init__(self,slice,sep):
773    self.slice = slice
774    self.sep = sep
775
776  def get(self,x,y):
777    return '%s_%s.%s' % (string.join( x[self.slice], self.sep ) , y[-2], y[-1] )
778
779
780class get_trange:
781 
782  def __init__(self,pat,kseg):
783    self.kseg = kseg
784    self.re_istr = re.compile( '^[0-9]*$' )
785    if type( pat ) == type( 'x' ):
786      self.pat = pat
787      self.re = re.compile( pat )
788    else:
789      self.re = pat
790
791  def _test( self, s):
792    return self.re.match( s ) != None
793
794  def _get( self, s, handler=None ):
795    x = self.re.match( s )
796    tb = [None,None]
797    if x == None:
798      return False, tuple(tb)
799
800    thisOk = True
801    tb[0] = x.groupdict().get( 'start', None )
802    tb[1] = x.groupdict().get( 'end', None )
803    if x.groupdict().has_key( 'isClim' ):
804      tb.append( x.groupdict()['isClim'] )
805    for i in range(2):
806        b = tb[i]
807        if b != None:
808           if self.re_istr.match( b ) == None:
809              if handler != None:
810                handler._log( 'CQC.101.001.003',  dir + f, 'part of string not an integer' )
811              thisOk = False
812           else:
813             tb[i] = int(tb[i])
814
815    return thisOk, tb
816
817
818  def test(self, l ):
819    if len(l) < self.kseg + 1:
820      return True
821    return self._test( l[self.kseg] )
822
823  def get(self,l):
824    if len(l) < self.kseg + 1:
825      return True, (None,None)
826    return self._get( l[self.kseg] )
827         
828class fileNameSegments:
829  def __init__(self, parent, sep, nn ):
830    self.sep = sep
831    self.nn = nn
832    self.nn.sort()
833    self.__segments  = {}
834    self.parent = parent
835
836  def add( self, line ):
837    bits = map( string.strip, string.split( line, ', ' ) )
838    k = int(bits[0])
839    if bits[1] == 'vocabulary':
840      assert bits[2] in self.parent.vocab.keys(), 'Vocabulary specified in file name section not defined in vocab sections, %s' % bits[2]
841     
842      self.__segments[k] = ('vocabulary',bits[2])
843    elif bits[1][0:5] == 'regex' or bits[2] == 'TimeRange':
844      try:
845        regex = re.compile( string.strip( bits[3], "'") )
846      except:
847        print 'Failed to compile (in re): %s' % bits[3]
848        raise
849      self.__segments[k] = (bits[1],bits[2], regex)
850    else:
851      self.__segments[k] = tuple( bits[1:] )
852
853    if bits[2] == 'TimeRange':
854       self.parent.hasTimeRange = True
855       self.parent.timeRange = get_trange(regex,k)
856
857  def finish(self):
858    sl = []
859    for k in range(self.nn[-1]):
860      sl.append( self.__segments.get( k, None ) )
861    self.segments = tuple( sl )
862
863class Constraint__IdentityChecker:
864
865  def __init__(self, ref1, ref2 ):
866     self.mode = 'd'
867     self.Ref1 = self.__parse_ref(ref1)
868     self.Ref2 = self.__parse_ref(ref2)
869     if self.Ref1 == 'VALUE':
870       self.Ref1 = self.Ref2
871       self.Ref2 = 'VALUE'
872     if self.Ref2 == 'VALUE':
873       self.mode = 's'
874
875     if self.mode == 's':
876       self.msg = '%s equals %s' % (self.Ref1[1], self.value)
877     else:
878       self.msg = '%s equals %s' % (self.Ref1[1], self.Ref2[1])
879
880  def __parse_ref(self,ref):
881     bits = string.split(ref,'/')
882     assert bits[0] in ['VALUE','PATH','FILENAME','ATTRIBUTES','CONFIG','ARGS'], 'Bad line in CONSTRAINT section of config file'
883     if bits[0] == 'ATTRIBUTES':
884        if bits[1] == 'Global':
885           return ('g_%s' % bits[2],'Global attribute %s' % bits[2] )
886     elif bits[0] == 'FILENAME':
887           return ('fn_%s' % bits[1],'File name component %s' % bits[1] )
888     elif bits[0] == 'VALUE':
889           self.value = bits[1]
890           return 'VALUE'
891
892  def __reset__(self):
893    pass
894
895  def check(self,fns):
896    if self.mode == 's':
897      if fns.has_key( self.Ref1[0] ):
898        return ('ANS',fns[self.Ref1[0]] == self.value )
899      else:
900        return ('PASS',None)
901    else:
902      if fns.has_key( self.Ref1[0] ) and fns.has_key( self.Ref2[0] ):
903        return ('ANS',fns[self.Ref1[0]] == fns[self.Ref2[0]])
904      else:
905        return ('PASS',None)
906
907def parse_ref(ref):
908     bits = string.split(ref,'/')
909     assert bits[0] in ['VALUE','PATH','FILENAME','FILENAMEregex','ATTRIBUTES','CONFIG','ARGS'], 'Bad line in CONSTRAINT section of config file'
910     if bits[0] == 'ATTRIBUTES':
911        if bits[1] == 'Global':
912           return ('g_%s' % bits[2],'Global attribute %s' % bits[2] )
913     elif bits[0] == 'FILENAME':
914           return ('fn_%s' % bits[1],'File name component %s' % bits[1] )
915     elif bits[0] == 'FILENAMEregex':
916           return ('fnre_%s' % bits[1],'File name component %s' % bits[1] )
917     elif bits[0] == 'VALUE':
918           return ('VALUE', bits[1])
919
920class Constraint__OnlyOnce:
921
922  def __init__(self, ref1):
923     self.nn = 0
924     self.Ref1 = parse_ref(ref1)
925     self.msg = '%s occurs only once' % self.Ref1[1]
926
927  def __reset__(self):
928    self.nn = 0
929
930  def check(self,fns):
931      if fns.has_key( self.Ref1[0] ):
932        self.nn+=1
933        return ('ANS', self.nn <= 1)
934      else:
935        keys = fns.keys()
936        keys.sort()
937        return ('PASS',None)
938
939#### check whether a NS element is constant
940class Constraint__Constant:
941
942  def __init__(self, ref1, required=False):
943     self.nn = 0
944     self.Ref1 = parse_ref(ref1)
945     self.msg = '%s occurs only once' % self.Ref1[1]
946     self.value = None
947     self.required = required
948
949  def __reset__(self):
950    self.nn = 0
951    self.value = None
952
953  def check(self,fns):
954      if fns.has_key( self.Ref1[0] ):
955        if self.value == None:
956          self.value = fns[self.Ref1[0]]
957          return ('PASS', 'first element')
958        else:
959          return ('ANS', self.value == fns[self.Ref1[0]] )
960      else:
961        if self.required:
962          return ('FAIL', 'missing NS element %s' % self.Ref1[0] )
963        else:
964          return ('PASS',None)
965
966def ref_to_key(ref):
967     bits = string.split(ref,'/')
968     assert bits[0] in ['VALUE','PATH','FILENAME','ATTRIBUTES','CONFIG','ARGS'], 'Bad line in CONSTRAINT section of config file'
969     if bits[0] == 'ATTRIBUTES':
970        if bits[1] == 'Global':
971           return ('g_%s' % bits[2],'Global attribute %s' % bits[2] )
972     elif bits[0] == 'FILENAME':
973           return ('fn_%s' % bits[1],'File name component %s' % bits[1] )
974     elif bits[0] == 'VALUE':
975           return ('VALUE',bits[1])
976
977class section_parser_l0:
978
979  def __init__(self,parent,sectionName):
980     self.sname = sectionName
981     self.parent = parent
982     self.lines = []
983     
984  def add( self, l ):
985    self.lines.append( string.strip( l ) )
986
987  def close(self):
988    assert type(self.parent.sections) == type( {} ), 'parent.sections has wrong type (%s), should be a dictionary' % ( str( type( self.parent.sections ) ) )
989
990    self.parent.sections[self.sname] = self.lines[:]
991    self.lines = []
Note: See TracBrowser for help on using the repository browser.