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

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

Correcting typo in error message

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