source: FCC2/trunk/source/fcc_utils.py @ 51

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/FCC2/trunk/source/fcc_utils.py@51
Revision 51, 42.4 KB checked in by mjuckes, 7 years ago (diff)

corrected time period check for 3hr,6hr; removed potential mis-handling of bad file names

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