We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
NLCOMP fails with python3 because dictionaries no longer support `has_key`
When using python3, I get:

```
$ ./case.cmpgen_namelists
Comparing namelists with baselines 'lilac_0703a'
Generating namelists to baselines 'lilac_0703b'
Exception during namelist operations:
'dict' object has no attribute 'has_key'
Traceback (most recent call last):
  File "/Users/sacks/ctsm/ctsm3/cime/scripts/Tools/../../scripts/lib/CIME/case/case_cmpgen_namelists.py", line 123, in case_cmpgen_namelists
    success, output = _do_full_nl_comp(self, test_name, compare_name, baseline_root)
  File "/Users/sacks/ctsm/ctsm3/cime/scripts/Tools/../../scripts/lib/CIME/case/case_cmpgen_namelists.py", line 45, in _do_full_nl_comp
    success, current_comments = compare_runconfigfiles(baseline_counterpart, item, test)
  File "/Users/sacks/ctsm/ctsm3/cime/scripts/Tools/../../scripts/lib/CIME/simple_compare.py", line 171, in compare_runconfigfiles
    comments = findDiff(gold_dict, compare_dict, case=case)
  File "/Users/sacks/ctsm/ctsm3/cime/scripts/Tools/../../scripts/lib/CIME/simple_compare.py", line 215, in findDiff
    if not d2.has_key(k):
AttributeError: 'dict' object has no attribute 'has_key'
```

I have a fix incoming.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `scripts/lib/CIME/simple_compare.py`
Content:
```
1 import os, re
2 
3 from CIME.utils import expect
4 
5 ###############################################################################
6 def _normalize_string_value(value, case):
7 ###############################################################################
8     """
9     Some of the strings are inherently prone to diffs, like file
10     paths, etc. This function attempts to normalize that data so that
11     it will not cause diffs.
12     """
13     # Any occurance of case must be normalized because test-ids might not match
14     if (case is not None):
15         case_re = re.compile(r'{}[.]([GC])[.]([^./\s]+)'.format(case))
16         value = case_re.sub("{}.ACTION.TESTID".format(case), value)
17 
18     if ("/" in value):
19         # File path, just return the basename
20         return os.path.basename(value)
21     elif ("username" in value):
22         return ''
23     elif (".log." in value):
24         # Remove the part that's prone to diff
25         components = value.split(".")
26         return os.path.basename(".".join(components[0:-1]))
27     else:
28         return value
29 
30 ###############################################################################
31 def _skip_comments_and_whitespace(lines, idx):
32 ###############################################################################
33     """
34     Starting at idx, return next valid idx of lines that contains real data
35     """
36     if (idx == len(lines)):
37         return idx
38 
39     comment_re = re.compile(r'^[#!]')
40 
41     lines_slice = lines[idx:]
42     for line in lines_slice:
43         line = line.strip()
44         if (comment_re.match(line) is not None or line == ""):
45             idx += 1
46         else:
47             return idx
48 
49     return idx
50 
51 ###############################################################################
52 def _compare_data(gold_lines, comp_lines, case, offset_method=False):
53 ###############################################################################
54     """
55     >>> teststr = '''
56     ... data1
57     ... data2 data3
58     ... data4 data5 data6
59     ...
60     ... # Comment
61     ... data7 data8 data9 data10
62     ... '''
63     >>> _compare_data(teststr.splitlines(), teststr.splitlines(), None)
64     ('', 0)
65 
66     >>> teststr2 = '''
67     ... data1
68     ... data2 data30
69     ... data4 data5 data6
70     ... data7 data8 data9 data10
71     ... data00
72     ... '''
73     >>> results,_ = _compare_data(teststr.splitlines(), teststr2.splitlines(), None)
74     >>> print(results)
75     Inequivalent lines data2 data3 != data2 data30
76       NORMALIZED: data2 data3 != data2 data30
77     Found extra lines
78     data00
79     <BLANKLINE>
80     >>> teststr3 = '''
81     ... data1
82     ... data4 data5 data6
83     ... data7 data8 data9 data10
84     ... data00
85     ... '''
86     >>> results,_ = _compare_data(teststr3.splitlines(), teststr2.splitlines(), None, offset_method=True)
87     >>> print(results)
88     Inequivalent lines data4 data5 data6 != data2 data30
89       NORMALIZED: data4 data5 data6 != data2 data30
90     <BLANKLINE>
91     """
92     comments = ""
93     cnt = 0
94     gidx, cidx = 0, 0
95     gnum, cnum = len(gold_lines), len(comp_lines)
96     while (gidx < gnum or cidx < cnum):
97         gidx = _skip_comments_and_whitespace(gold_lines, gidx)
98         cidx = _skip_comments_and_whitespace(comp_lines, cidx)
99 
100         if (gidx == gnum):
101             if (cidx == cnum):
102                 return comments, cnt
103             else:
104                 comments += "Found extra lines\n"
105                 comments += "\n".join(comp_lines[cidx:]) + "\n"
106                 return comments, cnt
107         elif (cidx == cnum):
108             comments += "Missing lines\n"
109             comments += "\n".join(gold_lines[gidx:1]) + "\n"
110             return comments, cnt
111 
112         gold_value = gold_lines[gidx].strip()
113         gold_value = gold_value.replace('"',"'")
114         comp_value = comp_lines[cidx].strip()
115         comp_value = comp_value.replace('"',"'")
116 
117         norm_gold_value = _normalize_string_value(gold_value, case)
118         norm_comp_value = _normalize_string_value(comp_value, case)
119 
120         if (norm_gold_value != norm_comp_value):
121             comments += "Inequivalent lines {} != {}\n".format(gold_value, comp_value)
122             comments += "  NORMALIZED: {} != {}\n".format(norm_gold_value, norm_comp_value)
123             cnt += 1
124         if offset_method and (norm_gold_value != norm_comp_value):
125             if gnum > cnum:
126                 gidx += 1
127             else:
128                 cidx += 1
129         else:
130             gidx += 1
131             cidx += 1
132 
133     return comments, cnt
134 
135 ###############################################################################
136 def compare_files(gold_file, compare_file, case=None):
137 ###############################################################################
138     """
139     Returns true if files are the same, comments are returned too:
140     (success, comments)
141     """
142     expect(os.path.exists(gold_file), "File not found: {}".format(gold_file))
143     expect(os.path.exists(compare_file), "File not found: {}".format(compare_file))
144 
145     comments, cnt = _compare_data(open(gold_file, "r").readlines(),
146                              open(compare_file, "r").readlines(), case)
147 
148     if cnt > 0:
149         comments2, cnt2 = _compare_data(open(gold_file, "r").readlines(),
150                                         open(compare_file, "r").readlines(),
151                                         case, offset_method=True)
152         if cnt2 < cnt:
153             comments = comments2
154 
155     return comments == "", comments
156 
157 ###############################################################################
158 def compare_runconfigfiles(gold_file, compare_file, case=None):
159 ###############################################################################
160     """
161     Returns true if files are the same, comments are returned too:
162     (success, comments)
163     """
164     expect(os.path.exists(gold_file), "File not found: {}".format(gold_file))
165     expect(os.path.exists(compare_file), "File not found: {}".format(compare_file))
166 
167     #create dictionary's of the runconfig files and compare them
168     gold_dict = _parse_runconfig(gold_file)
169     compare_dict = _parse_runconfig(compare_file)
170 
171     comments = findDiff(gold_dict, compare_dict, case=case)
172     comments = comments.replace(" d1", " " + gold_file)
173     comments = comments.replace(" d2", " " + compare_file)
174     # this picks up the case that an entry in compare is not in gold
175     if comments == "":
176         comments = findDiff(compare_dict, gold_dict, case=case)
177         comments = comments.replace(" d2", " " + gold_file)
178         comments = comments.replace(" d1", " " + compare_file)
179 
180     return comments == "", comments
181 
182 def _parse_runconfig(filename):
183     runconfig = {}
184     inrunseq = False
185     insubsection = None
186     subsection_re = re.compile(r'\s*(\S+)::')
187     group_re = re.compile(r'\s*(\S+)\s*:\s*(\S+)')
188     var_re = re.compile(r'\s*(\S+)\s*=\s*(\S+)')
189     with open(filename, "r") as fd:
190         for line in fd:
191             # remove comments
192             line = line.split('#')[0]
193             subsection_match = subsection_re.match(line)
194             group_match = group_re.match(line)
195             var_match = var_re.match(line)
196             if re.match(r'\s*runSeq\s*::', line):
197                 runconfig['runSeq'] = []
198                 inrunseq = True
199             elif re.match(r'\s*::\s*', line):
200                 inrunseq = False
201             elif inrunseq:
202                 runconfig['runSeq'].append(line)
203             elif subsection_match:
204                 insubsection = subsection_match.group(1)
205                 runconfig[insubsection] = {}
206             elif group_match:
207                 runconfig[group_match.group(1)] = group_match.group(2)
208             elif insubsection and var_match:
209                 runconfig[insubsection][var_match.group(1)] = var_match.group(2)
210     return runconfig
211 
212 def findDiff(d1, d2, path="", case=None):
213     comment = ""
214     for k in d1.keys():
215         if not d2.has_key(k):
216             comment += path + ":\n"
217             comment +=  k + " as key not in d2\n"
218         else:
219             if type(d1[k]) is dict:
220                 if path == "":
221                     path = k
222                 else:
223                     path = path + "->" + k
224                 comment += findDiff(d1[k],d2[k], path=path, case=case)
225             else:
226                 if case in d1[k]:
227                     pass
228                 elif "username" in k:
229                     pass
230                 elif "logfile" in k:
231                     pass
232                 elif d1[k] != d2[k]:
233                     comment += path+":\n"
234                     comment += " - {} : {}\n".format(k,d1[k])
235                     comment += " + {} : {}\n".format(k,d2[k])
236     return comment
237
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/scripts/lib/CIME/simple_compare.py b/scripts/lib/CIME/simple_compare.py
--- a/scripts/lib/CIME/simple_compare.py
+++ b/scripts/lib/CIME/simple_compare.py
@@ -212,7 +212,7 @@
 def findDiff(d1, d2, path="", case=None):
     comment = ""
     for k in d1.keys():
-        if not d2.has_key(k):
+        if not k in d2:
             comment += path + ":\n"
             comment +=  k + " as key not in d2\n"
         else:

{"golden_diff": "diff --git a/scripts/lib/CIME/simple_compare.py b/scripts/lib/CIME/simple_compare.py\n--- a/scripts/lib/CIME/simple_compare.py\n+++ b/scripts/lib/CIME/simple_compare.py\n@@ -212,7 +212,7 @@\n def findDiff(d1, d2, path=\"\", case=None):\n     comment = \"\"\n     for k in d1.keys():\n-        if not d2.has_key(k):\n+        if not k in d2:\n             comment += path + \":\\n\"\n             comment +=  k + \" as key not in d2\\n\"\n         else:\n", "issue": "NLCOMP fails with python3 because dictionaries no longer support `has_key`\nWhen using python3, I get:\r\n\r\n```\r\n$ ./case.cmpgen_namelists\r\nComparing namelists with baselines 'lilac_0703a'\r\nGenerating namelists to baselines 'lilac_0703b'\r\nException during namelist operations:\r\n'dict' object has no attribute 'has_key'\r\nTraceback (most recent call last):\r\n  File \"/Users/sacks/ctsm/ctsm3/cime/scripts/Tools/../../scripts/lib/CIME/case/case_cmpgen_namelists.py\", line 123, in case_cmpgen_namelists\r\n    success, output = _do_full_nl_comp(self, test_name, compare_name, baseline_root)\r\n  File \"/Users/sacks/ctsm/ctsm3/cime/scripts/Tools/../../scripts/lib/CIME/case/case_cmpgen_namelists.py\", line 45, in _do_full_nl_comp\r\n    success, current_comments = compare_runconfigfiles(baseline_counterpart, item, test)\r\n  File \"/Users/sacks/ctsm/ctsm3/cime/scripts/Tools/../../scripts/lib/CIME/simple_compare.py\", line 171, in compare_runconfigfiles\r\n    comments = findDiff(gold_dict, compare_dict, case=case)\r\n  File \"/Users/sacks/ctsm/ctsm3/cime/scripts/Tools/../../scripts/lib/CIME/simple_compare.py\", line 215, in findDiff\r\n    if not d2.has_key(k):\r\nAttributeError: 'dict' object has no attribute 'has_key'\r\n```\r\n\r\nI have a fix incoming.\n", "before_files": [{"content": "import os, re\n\nfrom CIME.utils import expect\n\n###############################################################################\ndef _normalize_string_value(value, case):\n###############################################################################\n    \"\"\"\n    Some of the strings are inherently prone to diffs, like file\n    paths, etc. This function attempts to normalize that data so that\n    it will not cause diffs.\n    \"\"\"\n    # Any occurance of case must be normalized because test-ids might not match\n    if (case is not None):\n        case_re = re.compile(r'{}[.]([GC])[.]([^./\\s]+)'.format(case))\n        value = case_re.sub(\"{}.ACTION.TESTID\".format(case), value)\n\n    if (\"/\" in value):\n        # File path, just return the basename\n        return os.path.basename(value)\n    elif (\"username\" in value):\n        return ''\n    elif (\".log.\" in value):\n        # Remove the part that's prone to diff\n        components = value.split(\".\")\n        return os.path.basename(\".\".join(components[0:-1]))\n    else:\n        return value\n\n###############################################################################\ndef _skip_comments_and_whitespace(lines, idx):\n###############################################################################\n    \"\"\"\n    Starting at idx, return next valid idx of lines that contains real data\n    \"\"\"\n    if (idx == len(lines)):\n        return idx\n\n    comment_re = re.compile(r'^[#!]')\n\n    lines_slice = lines[idx:]\n    for line in lines_slice:\n        line = line.strip()\n        if (comment_re.match(line) is not None or line == \"\"):\n            idx += 1\n        else:\n            return idx\n\n    return idx\n\n###############################################################################\ndef _compare_data(gold_lines, comp_lines, case, offset_method=False):\n###############################################################################\n    \"\"\"\n    >>> teststr = '''\n    ... data1\n    ... data2 data3\n    ... data4 data5 data6\n    ...\n    ... # Comment\n    ... data7 data8 data9 data10\n    ... '''\n    >>> _compare_data(teststr.splitlines(), teststr.splitlines(), None)\n    ('', 0)\n\n    >>> teststr2 = '''\n    ... data1\n    ... data2 data30\n    ... data4 data5 data6\n    ... data7 data8 data9 data10\n    ... data00\n    ... '''\n    >>> results,_ = _compare_data(teststr.splitlines(), teststr2.splitlines(), None)\n    >>> print(results)\n    Inequivalent lines data2 data3 != data2 data30\n      NORMALIZED: data2 data3 != data2 data30\n    Found extra lines\n    data00\n    <BLANKLINE>\n    >>> teststr3 = '''\n    ... data1\n    ... data4 data5 data6\n    ... data7 data8 data9 data10\n    ... data00\n    ... '''\n    >>> results,_ = _compare_data(teststr3.splitlines(), teststr2.splitlines(), None, offset_method=True)\n    >>> print(results)\n    Inequivalent lines data4 data5 data6 != data2 data30\n      NORMALIZED: data4 data5 data6 != data2 data30\n    <BLANKLINE>\n    \"\"\"\n    comments = \"\"\n    cnt = 0\n    gidx, cidx = 0, 0\n    gnum, cnum = len(gold_lines), len(comp_lines)\n    while (gidx < gnum or cidx < cnum):\n        gidx = _skip_comments_and_whitespace(gold_lines, gidx)\n        cidx = _skip_comments_and_whitespace(comp_lines, cidx)\n\n        if (gidx == gnum):\n            if (cidx == cnum):\n                return comments, cnt\n            else:\n                comments += \"Found extra lines\\n\"\n                comments += \"\\n\".join(comp_lines[cidx:]) + \"\\n\"\n                return comments, cnt\n        elif (cidx == cnum):\n            comments += \"Missing lines\\n\"\n            comments += \"\\n\".join(gold_lines[gidx:1]) + \"\\n\"\n            return comments, cnt\n\n        gold_value = gold_lines[gidx].strip()\n        gold_value = gold_value.replace('\"',\"'\")\n        comp_value = comp_lines[cidx].strip()\n        comp_value = comp_value.replace('\"',\"'\")\n\n        norm_gold_value = _normalize_string_value(gold_value, case)\n        norm_comp_value = _normalize_string_value(comp_value, case)\n\n        if (norm_gold_value != norm_comp_value):\n            comments += \"Inequivalent lines {} != {}\\n\".format(gold_value, comp_value)\n            comments += \"  NORMALIZED: {} != {}\\n\".format(norm_gold_value, norm_comp_value)\n            cnt += 1\n        if offset_method and (norm_gold_value != norm_comp_value):\n            if gnum > cnum:\n                gidx += 1\n            else:\n                cidx += 1\n        else:\n            gidx += 1\n            cidx += 1\n\n    return comments, cnt\n\n###############################################################################\ndef compare_files(gold_file, compare_file, case=None):\n###############################################################################\n    \"\"\"\n    Returns true if files are the same, comments are returned too:\n    (success, comments)\n    \"\"\"\n    expect(os.path.exists(gold_file), \"File not found: {}\".format(gold_file))\n    expect(os.path.exists(compare_file), \"File not found: {}\".format(compare_file))\n\n    comments, cnt = _compare_data(open(gold_file, \"r\").readlines(),\n                             open(compare_file, \"r\").readlines(), case)\n\n    if cnt > 0:\n        comments2, cnt2 = _compare_data(open(gold_file, \"r\").readlines(),\n                                        open(compare_file, \"r\").readlines(),\n                                        case, offset_method=True)\n        if cnt2 < cnt:\n            comments = comments2\n\n    return comments == \"\", comments\n\n###############################################################################\ndef compare_runconfigfiles(gold_file, compare_file, case=None):\n###############################################################################\n    \"\"\"\n    Returns true if files are the same, comments are returned too:\n    (success, comments)\n    \"\"\"\n    expect(os.path.exists(gold_file), \"File not found: {}\".format(gold_file))\n    expect(os.path.exists(compare_file), \"File not found: {}\".format(compare_file))\n\n    #create dictionary's of the runconfig files and compare them\n    gold_dict = _parse_runconfig(gold_file)\n    compare_dict = _parse_runconfig(compare_file)\n\n    comments = findDiff(gold_dict, compare_dict, case=case)\n    comments = comments.replace(\" d1\", \" \" + gold_file)\n    comments = comments.replace(\" d2\", \" \" + compare_file)\n    # this picks up the case that an entry in compare is not in gold\n    if comments == \"\":\n        comments = findDiff(compare_dict, gold_dict, case=case)\n        comments = comments.replace(\" d2\", \" \" + gold_file)\n        comments = comments.replace(\" d1\", \" \" + compare_file)\n\n    return comments == \"\", comments\n\ndef _parse_runconfig(filename):\n    runconfig = {}\n    inrunseq = False\n    insubsection = None\n    subsection_re = re.compile(r'\\s*(\\S+)::')\n    group_re = re.compile(r'\\s*(\\S+)\\s*:\\s*(\\S+)')\n    var_re = re.compile(r'\\s*(\\S+)\\s*=\\s*(\\S+)')\n    with open(filename, \"r\") as fd:\n        for line in fd:\n            # remove comments\n            line = line.split('#')[0]\n            subsection_match = subsection_re.match(line)\n            group_match = group_re.match(line)\n            var_match = var_re.match(line)\n            if re.match(r'\\s*runSeq\\s*::', line):\n                runconfig['runSeq'] = []\n                inrunseq = True\n            elif re.match(r'\\s*::\\s*', line):\n                inrunseq = False\n            elif inrunseq:\n                runconfig['runSeq'].append(line)\n            elif subsection_match:\n                insubsection = subsection_match.group(1)\n                runconfig[insubsection] = {}\n            elif group_match:\n                runconfig[group_match.group(1)] = group_match.group(2)\n            elif insubsection and var_match:\n                runconfig[insubsection][var_match.group(1)] = var_match.group(2)\n    return runconfig\n\ndef findDiff(d1, d2, path=\"\", case=None):\n    comment = \"\"\n    for k in d1.keys():\n        if not d2.has_key(k):\n            comment += path + \":\\n\"\n            comment +=  k + \" as key not in d2\\n\"\n        else:\n            if type(d1[k]) is dict:\n                if path == \"\":\n                    path = k\n                else:\n                    path = path + \"->\" + k\n                comment += findDiff(d1[k],d2[k], path=path, case=case)\n            else:\n                if case in d1[k]:\n                    pass\n                elif \"username\" in k:\n                    pass\n                elif \"logfile\" in k:\n                    pass\n                elif d1[k] != d2[k]:\n                    comment += path+\":\\n\"\n                    comment += \" - {} : {}\\n\".format(k,d1[k])\n                    comment += \" + {} : {}\\n\".format(k,d2[k])\n    return comment\n", "path": "scripts/lib/CIME/simple_compare.py"}], "after_files": [{"content": "import os, re\n\nfrom CIME.utils import expect\n\n###############################################################################\ndef _normalize_string_value(value, case):\n###############################################################################\n    \"\"\"\n    Some of the strings are inherently prone to diffs, like file\n    paths, etc. This function attempts to normalize that data so that\n    it will not cause diffs.\n    \"\"\"\n    # Any occurance of case must be normalized because test-ids might not match\n    if (case is not None):\n        case_re = re.compile(r'{}[.]([GC])[.]([^./\\s]+)'.format(case))\n        value = case_re.sub(\"{}.ACTION.TESTID\".format(case), value)\n\n    if (\"/\" in value):\n        # File path, just return the basename\n        return os.path.basename(value)\n    elif (\"username\" in value):\n        return ''\n    elif (\".log.\" in value):\n        # Remove the part that's prone to diff\n        components = value.split(\".\")\n        return os.path.basename(\".\".join(components[0:-1]))\n    else:\n        return value\n\n###############################################################################\ndef _skip_comments_and_whitespace(lines, idx):\n###############################################################################\n    \"\"\"\n    Starting at idx, return next valid idx of lines that contains real data\n    \"\"\"\n    if (idx == len(lines)):\n        return idx\n\n    comment_re = re.compile(r'^[#!]')\n\n    lines_slice = lines[idx:]\n    for line in lines_slice:\n        line = line.strip()\n        if (comment_re.match(line) is not None or line == \"\"):\n            idx += 1\n        else:\n            return idx\n\n    return idx\n\n###############################################################################\ndef _compare_data(gold_lines, comp_lines, case, offset_method=False):\n###############################################################################\n    \"\"\"\n    >>> teststr = '''\n    ... data1\n    ... data2 data3\n    ... data4 data5 data6\n    ...\n    ... # Comment\n    ... data7 data8 data9 data10\n    ... '''\n    >>> _compare_data(teststr.splitlines(), teststr.splitlines(), None)\n    ('', 0)\n\n    >>> teststr2 = '''\n    ... data1\n    ... data2 data30\n    ... data4 data5 data6\n    ... data7 data8 data9 data10\n    ... data00\n    ... '''\n    >>> results,_ = _compare_data(teststr.splitlines(), teststr2.splitlines(), None)\n    >>> print(results)\n    Inequivalent lines data2 data3 != data2 data30\n      NORMALIZED: data2 data3 != data2 data30\n    Found extra lines\n    data00\n    <BLANKLINE>\n    >>> teststr3 = '''\n    ... data1\n    ... data4 data5 data6\n    ... data7 data8 data9 data10\n    ... data00\n    ... '''\n    >>> results,_ = _compare_data(teststr3.splitlines(), teststr2.splitlines(), None, offset_method=True)\n    >>> print(results)\n    Inequivalent lines data4 data5 data6 != data2 data30\n      NORMALIZED: data4 data5 data6 != data2 data30\n    <BLANKLINE>\n    \"\"\"\n    comments = \"\"\n    cnt = 0\n    gidx, cidx = 0, 0\n    gnum, cnum = len(gold_lines), len(comp_lines)\n    while (gidx < gnum or cidx < cnum):\n        gidx = _skip_comments_and_whitespace(gold_lines, gidx)\n        cidx = _skip_comments_and_whitespace(comp_lines, cidx)\n\n        if (gidx == gnum):\n            if (cidx == cnum):\n                return comments, cnt\n            else:\n                comments += \"Found extra lines\\n\"\n                comments += \"\\n\".join(comp_lines[cidx:]) + \"\\n\"\n                return comments, cnt\n        elif (cidx == cnum):\n            comments += \"Missing lines\\n\"\n            comments += \"\\n\".join(gold_lines[gidx:1]) + \"\\n\"\n            return comments, cnt\n\n        gold_value = gold_lines[gidx].strip()\n        gold_value = gold_value.replace('\"',\"'\")\n        comp_value = comp_lines[cidx].strip()\n        comp_value = comp_value.replace('\"',\"'\")\n\n        norm_gold_value = _normalize_string_value(gold_value, case)\n        norm_comp_value = _normalize_string_value(comp_value, case)\n\n        if (norm_gold_value != norm_comp_value):\n            comments += \"Inequivalent lines {} != {}\\n\".format(gold_value, comp_value)\n            comments += \"  NORMALIZED: {} != {}\\n\".format(norm_gold_value, norm_comp_value)\n            cnt += 1\n        if offset_method and (norm_gold_value != norm_comp_value):\n            if gnum > cnum:\n                gidx += 1\n            else:\n                cidx += 1\n        else:\n            gidx += 1\n            cidx += 1\n\n    return comments, cnt\n\n###############################################################################\ndef compare_files(gold_file, compare_file, case=None):\n###############################################################################\n    \"\"\"\n    Returns true if files are the same, comments are returned too:\n    (success, comments)\n    \"\"\"\n    expect(os.path.exists(gold_file), \"File not found: {}\".format(gold_file))\n    expect(os.path.exists(compare_file), \"File not found: {}\".format(compare_file))\n\n    comments, cnt = _compare_data(open(gold_file, \"r\").readlines(),\n                             open(compare_file, \"r\").readlines(), case)\n\n    if cnt > 0:\n        comments2, cnt2 = _compare_data(open(gold_file, \"r\").readlines(),\n                                        open(compare_file, \"r\").readlines(),\n                                        case, offset_method=True)\n        if cnt2 < cnt:\n            comments = comments2\n\n    return comments == \"\", comments\n\n###############################################################################\ndef compare_runconfigfiles(gold_file, compare_file, case=None):\n###############################################################################\n    \"\"\"\n    Returns true if files are the same, comments are returned too:\n    (success, comments)\n    \"\"\"\n    expect(os.path.exists(gold_file), \"File not found: {}\".format(gold_file))\n    expect(os.path.exists(compare_file), \"File not found: {}\".format(compare_file))\n\n    #create dictionary's of the runconfig files and compare them\n    gold_dict = _parse_runconfig(gold_file)\n    compare_dict = _parse_runconfig(compare_file)\n\n    comments = findDiff(gold_dict, compare_dict, case=case)\n    comments = comments.replace(\" d1\", \" \" + gold_file)\n    comments = comments.replace(\" d2\", \" \" + compare_file)\n    # this picks up the case that an entry in compare is not in gold\n    if comments == \"\":\n        comments = findDiff(compare_dict, gold_dict, case=case)\n        comments = comments.replace(\" d2\", \" \" + gold_file)\n        comments = comments.replace(\" d1\", \" \" + compare_file)\n\n    return comments == \"\", comments\n\ndef _parse_runconfig(filename):\n    runconfig = {}\n    inrunseq = False\n    insubsection = None\n    subsection_re = re.compile(r'\\s*(\\S+)::')\n    group_re = re.compile(r'\\s*(\\S+)\\s*:\\s*(\\S+)')\n    var_re = re.compile(r'\\s*(\\S+)\\s*=\\s*(\\S+)')\n    with open(filename, \"r\") as fd:\n        for line in fd:\n            # remove comments\n            line = line.split('#')[0]\n            subsection_match = subsection_re.match(line)\n            group_match = group_re.match(line)\n            var_match = var_re.match(line)\n            if re.match(r'\\s*runSeq\\s*::', line):\n                runconfig['runSeq'] = []\n                inrunseq = True\n            elif re.match(r'\\s*::\\s*', line):\n                inrunseq = False\n            elif inrunseq:\n                runconfig['runSeq'].append(line)\n            elif subsection_match:\n                insubsection = subsection_match.group(1)\n                runconfig[insubsection] = {}\n            elif group_match:\n                runconfig[group_match.group(1)] = group_match.group(2)\n            elif insubsection and var_match:\n                runconfig[insubsection][var_match.group(1)] = var_match.group(2)\n    return runconfig\n\ndef findDiff(d1, d2, path=\"\", case=None):\n    comment = \"\"\n    for k in d1.keys():\n        if not k in d2:\n            comment += path + \":\\n\"\n            comment +=  k + \" as key not in d2\\n\"\n        else:\n            if type(d1[k]) is dict:\n                if path == \"\":\n                    path = k\n                else:\n                    path = path + \"->\" + k\n                comment += findDiff(d1[k],d2[k], path=path, case=case)\n            else:\n                if case in d1[k]:\n                    pass\n                elif \"username\" in k:\n                    pass\n                elif \"logfile\" in k:\n                    pass\n                elif d1[k] != d2[k]:\n                    comment += path+\":\\n\"\n                    comment += \" - {} : {}\\n\".format(k,d1[k])\n                    comment += \" + {} : {}\\n\".format(k,d2[k])\n    return comment\n", "path": "scripts/lib/CIME/simple_compare.py"}]}