source: FCC/qc_utils.py @ 22

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

first check in

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