source: FCC/qc_utils.py @ 38

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

add constant constraint

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