diff --git a/conda/dev-environment.yml b/conda/dev-environment.yml index 15b97f0..2e3eb2f 100644 --- a/conda/dev-environment.yml +++ b/conda/dev-environment.yml @@ -13,3 +13,4 @@ dependencies: - mule - cfunits - dask + - f90nml diff --git a/conda/meta.yaml b/conda/meta.yaml index 965ba53..b7281a9 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -23,6 +23,7 @@ requirements: - scipy - sparse - xarray + - f90nml test: requires: diff --git a/setup.py b/setup.py index 99c9fc6..d0aa895 100644 --- a/setup.py +++ b/setup.py @@ -23,5 +23,6 @@ ], entry_points = { 'console_scripts': [ + 'diffnml = coecms.diffnml:main' ]} ) diff --git a/src/coecms/diffnml.py b/src/coecms/diffnml.py new file mode 100644 index 0000000..bd61c1a --- /dev/null +++ b/src/coecms/diffnml.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# Copyright 2019 ARC Centre of Excellence for Climate Extremes +# author: Scott Wales +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import f90nml +import argparse +from collections import OrderedDict + + +def diffnml(a, b): + """ + Create a diff of two f90nml instances + + Returns an ordered dict mapping tuples + + (section, key) -> (left, right) + + for (section, key) pairs where there is a difference, with left and right + being the values of that (section, key) from namelists a and b respectively + + If that (section, key) is missing from one file then that file will report + None + """ + groups = set([*a.keys(), *b.keys()]) + output = OrderedDict() + + for g in groups: + a_g = a.get(g, {}) + b_g = b.get(g, {}) + + keys = set([*a_g.keys(), *b_g.keys()]) + for k in keys: + a_k = a_g.get(k, None) + b_k = b_g.get(k, None) + + if a_k != b_k: + output[(g,k)] = (a_k, b_k) + + return output + + +def main(argv=None): + parser = argparse.ArgumentParser(description="Creates a diff of two namelist files") + parser.add_argument('filea', metavar='FILE1') + parser.add_argument('fileb', metavar='FILE2') + args = parser.parse_args(argv) + + print("< %s"%args.filea) + print("> %s"%args.fileb) + + a = f90nml.read(args.filea) + b = f90nml.read(args.fileb) + + for section, diff in diffnml(a,b).items(): + group, key = section + left, right = diff + print(f'&{group}%{key}:\n\t< {left}\n\t> {right}') + + +if __name__ == '__main__': + main() + diff --git a/test/test_diffnml.py b/test/test_diffnml.py new file mode 100644 index 0000000..1018b27 --- /dev/null +++ b/test/test_diffnml.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# Copyright 2019 ARC Centre of Excellence for Climate Extremes +# author: Scott Wales +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import print_function + +from coecms.diffnml import * +import f90nml +import io + +def make_nml(string): + return f90nml.read(io.StringIO(string)) + + +def test_diffnml(): + """ + Check diff logic + """ + a = make_nml(""" + &foo + bar = 1 + / + """) + b = make_nml(""" + &foo + bar = 'a' + baz = 1 + / + """) + + d = diffnml(a,b) + + assert d[('foo','bar')] == (1, 'a') + assert d[('foo','baz')] == (None, 1) + + +def test_main(tmpdir, capsys): + """ + Check output formatting + """ + a = tmpdir.join('a') + b = tmpdir.join('b') + + a.write(""" + &foo + bar = 1 + / + """) + b.write(""" + &foo + bar = 2 + / + """) + + main([str(a),str(b)]) + + captured = capsys.readouterr() + assert captured.out == f"< {str(a)}\n> {str(b)}\n&foo%bar:\n\t< 1\n\t> 2\n"