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