1 | import re |
---|
2 | from argparse import ArgumentParser |
---|
3 | from difflib import SequenceMatcher |
---|
4 | # |
---|
5 | def read_nmls( fin, tolower ): |
---|
6 | # |
---|
7 | # function to read namelist files and construct lists of named namelists |
---|
8 | # and their constituent variables (the latter being a list of lists) |
---|
9 | # If tolower is True then all names are converted to lowercase. |
---|
10 | # NEMO conventions are assumed here. In particular: |
---|
11 | # 1. namelists are started with &name_of_namelist appearing as the first non-whitespace |
---|
12 | # on a line (and not followed by anything other than an optional comment) |
---|
13 | # 2. Only one variable is set on each line |
---|
14 | # 3. namelists are closed with / appearing as the first non-whitespace on a line |
---|
15 | # Compliance with these conventions is not checked. |
---|
16 | # |
---|
17 | try: |
---|
18 | f = open(fin,'r') |
---|
19 | except: |
---|
20 | print('Unable to open ',fin) |
---|
21 | exit() |
---|
22 | nmls = [] |
---|
23 | nvars = [] |
---|
24 | linnos = [] |
---|
25 | inside = 0 |
---|
26 | blank = 0 |
---|
27 | stripa=re.compile('\s*&') # match whitespace up to leading ampersand |
---|
28 | stripc=re.compile('\s*!.*$') # match whitespace up to leading comment marker |
---|
29 | stripe=re.compile('\s*=.*$') # match = (with preceding whitespace) and anything following |
---|
30 | lc=0 |
---|
31 | for line in f: |
---|
32 | lc = lc + 1 |
---|
33 | nm = re.match(r'^\S*&',line) |
---|
34 | if nm != None: # Found a new namelist; extract and store name |
---|
35 | #print( stripa.sub('',stripc.sub('',line) ) ) |
---|
36 | inside = 1 |
---|
37 | if tolower: line = line.lower() |
---|
38 | nmls.append(stripa.sub('',stripc.sub('',line.strip()) )) |
---|
39 | newl = [] # new temporary list |
---|
40 | lins = [] # new temporary list |
---|
41 | if nm == None and inside == 1 and re.match(r'^\s*!', line) == None : |
---|
42 | # still inside namelist and not a comment |
---|
43 | if re.match(r'^\s*$', line) != None : |
---|
44 | # ignore blank lines |
---|
45 | blank = blank + 1 |
---|
46 | elif re.match(r'^\s*/', line) == None : |
---|
47 | # It is not the closing slash, therefore extract and store variable name |
---|
48 | if tolower: line = line.lower() |
---|
49 | #print( stripe.sub('',stripc.sub('',line) ) ) |
---|
50 | newl.append(stripe.sub('',stripc.sub('',line.strip()) )) |
---|
51 | lins.append(lc) |
---|
52 | else : |
---|
53 | # Reached the end of this particular namelist. Append temporary list to list of lists |
---|
54 | inside = 0 |
---|
55 | nvars.append(newl) |
---|
56 | linnos.append(lins) |
---|
57 | f.close() |
---|
58 | return nmls, nvars, linnos |
---|
59 | |
---|
60 | def main(): |
---|
61 | parser = ArgumentParser(description= |
---|
62 | """ |
---|
63 | Check for consistency between cfg namelists and ref versions |
---|
64 | e.g. python nemo_nml_check.py --cfg <cfg namelist> --ref <ref namelist> --match <closeness factor> |
---|
65 | """) |
---|
66 | parser.add_argument('--cfg', '-c',dest='cfgfile',help='configuration namelist', nargs= '*',default='namelist_cfg') |
---|
67 | parser.add_argument('--ref', '-r',dest='reffile',help='reference namelist', nargs= '*',default='namelist_ref') |
---|
68 | parser.add_argument('--match', '-m',dest='mfactor',help='closeness factor (0-1)', nargs= '*',default='0.75') |
---|
69 | args = parser.parse_args() |
---|
70 | # |
---|
71 | mfact = float(''.join(args.mfactor)) |
---|
72 | nmls, nvars, lnos = read_nmls(''.join(args.reffile), True ) # Construct lists from the reference namelist |
---|
73 | cnmls, cnvars, clnos = read_nmls(''.join(args.cfgfile), False) # Construct lists from the configuration namelist |
---|
74 | # |
---|
75 | # for each non-empty, named namelist in the configuration namelist file: find the equivalent namelist in the |
---|
76 | # reference set and confirm that a match for each variable exists. |
---|
77 | # Checks are performed on strings converted to lowercase. This was done on read for the reference set but |
---|
78 | # the configuration set has been keep in original form so that reports match the file contents. |
---|
79 | # |
---|
80 | nomatch = 0 |
---|
81 | for k, nl in enumerate(cnmls): |
---|
82 | #print(k, nl) |
---|
83 | if len(cnvars[k]) > 0: |
---|
84 | for rk, rnl in enumerate(nmls): |
---|
85 | if rnl == nl.lower() : |
---|
86 | for nv, v in enumerate(cnvars[k]): |
---|
87 | #print(k,v,rnl) |
---|
88 | if v.lower() not in nvars[rk] : |
---|
89 | if nomatch == 0 : |
---|
90 | print('No match in matching blocks for: | Possible close match:') |
---|
91 | nomatch = 1 |
---|
92 | print( '{:<22} in {:<12} (at line no. {:4d})'.format(v,nl,clnos[k][nv]), end=' | ') |
---|
93 | rapprox=0.0 |
---|
94 | for nk2, nv2 in enumerate(nvars[rk]): |
---|
95 | r=SequenceMatcher(None, v, nv2).ratio() |
---|
96 | if r >= rapprox: |
---|
97 | (rapprox, vapprox) = (r, nv2) |
---|
98 | if rapprox > mfact : |
---|
99 | print( vapprox ) |
---|
100 | else : |
---|
101 | print(" ") |
---|
102 | |
---|
103 | # |
---|
104 | |
---|
105 | if __name__ == '__main__': |
---|
106 | main() |
---|