We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Many tests failing with YangModelExtractor error
### Describe the issue

Starting with [test run 790](https://github.com/ietf-tools/datatracker/actions/runs/3172961553), a few dozen tests are failing with the error
```
AttributeError: 'YangModuleExtractor' object has no attribute 'extract_yang_model'
```
The last passing test run was a couple hours before the release of [xym 0.6.0](https://pypi.org/project/xym/0.6.0/#history), so it seems likely this is related.

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ietf/submit/checkers.py`
Content:
```
1 # Copyright The IETF Trust 2016-2020, All Rights Reserved
2 # -*- coding: utf-8 -*-
3 
4 
5 import io
6 import os
7 import re
8 import shutil
9 import sys
10 import tempfile
11 
12 from xym import xym
13 from django.conf import settings
14 
15 import debug                            # pyflakes:ignore
16 
17 from ietf.utils.log import log, assertion
18 from ietf.utils.models import VersionInfo
19 from ietf.utils.pipe import pipe
20 from ietf.utils.test_runner import set_coverage_checking
21 
22 class DraftSubmissionChecker(object):
23     name = ""
24 
25     def check_file_txt(self, text):
26         "Run checks on a text file"
27         raise NotImplementedError
28 
29     def check_file_xml(self, xml):
30         "Run checks on an xml file"
31         raise NotImplementedError
32 
33     def check_fragment_txt(self, text):
34         "Run checks on a fragment from a text file"
35         raise NotImplementedError
36 
37     def check_fragment_xml(self, xml):
38         "Run checks on a fragment from an xml file"
39         raise NotImplementedError
40 
41 
42 class DraftIdnitsChecker(object):
43     """
44     Draft checker class for idnits.  Idnits can only handle whole text files,
45     so only check_file_txt() is defined; check_file_xml and check_fragment_*
46     methods are undefined.
47 
48     Furthermore, idnits doesn't provide an error code or line-by-line errors,
49     so a bit of massage is needed in order to return the expected failure flag.
50     """
51     name = "idnits check"
52 
53     # start using this when we provide more in the way of warnings during
54     # submission checking:
55     # symbol = '<span class="bi bi-check-square"></span>'
56     # symbol = u'<span class="large">\ua17d</span>' # Yi syllable 'nit'
57     # symbol = u'<span class="large">\ub2e1</span>' # Hangul syllable 'nit'
58 
59     symbol = ""
60 
61     def __init__(self, options=["--submitcheck", "--nitcount", ]):
62         assert isinstance(options, list)
63         if not "--nitcount" in options:
64             options.append("--nitcount")
65         self.options = ' '.join(options)
66 
67     def check_file_txt(self, path):
68         """
69         Run an idnits check, and return a passed/failed indication, a message,
70         and error and warning messages.
71 
72         Error and warning list items are tuples:
73             (line_number, line_text, message)
74         """
75         items = []
76         errors = 0
77         warnings = 0
78         errstart = ['  ** ', '  ~~ ']
79         warnstart = ['  == ', '  -- ']
80         
81 
82         cmd = "%s %s %s" % (settings.IDSUBMIT_IDNITS_BINARY, self.options, path)
83         code, out, err = pipe(cmd)
84         out = out.decode('utf-8')
85         err = err.decode('utf-8')
86         if code != 0 or out == "":
87             message = "idnits error: %s:\n  Error %s: %s" %( cmd, code, err)
88             log(message)
89             passed = False
90             
91         else:
92             message = out
93             if re.search(r"\s+Summary:\s+0\s+|No nits found", out):
94                 passed  = True
95             else:
96                 passed  = False
97 
98         item = ""
99         for line in message.splitlines():
100             if   line[:5] in (errstart + warnstart):
101                 item = line.rstrip()
102             elif line.strip() == "" and item:
103                 tuple = (None, None, item)
104                 items.append(tuple)
105                 if item[:5] in errstart:
106                     errors += 1
107                 elif item[:5] in warnstart:
108                     warnings += 1
109                 else:
110                     raise RuntimeError("Unexpected state in idnits checker: item: %s, line: %s" % (item, line))
111                 item = ""
112             elif item and line.strip() != "":
113                 item += " " + line.strip()
114             else:
115                 pass
116         info = {'checker': self.name, 'items': [], 'code': {}}
117 
118         return passed, message, errors, warnings, info
119 
120 class DraftYangChecker(object):
121 
122     name = "yang validation"
123     symbol = '<i class="bi bi-yin-yang"></i>'
124 
125     def check_file_txt(self, path):
126         name = os.path.basename(path)
127         workdir = tempfile.mkdtemp()
128         model_name_re = r'^[A-Za-z_][A-Za-z0-9_.-]*(@\d\d\d\d-\d\d-\d\d)?\.yang$'
129         errors = 0
130         warnings = 0
131         message = ""
132         results = []
133         passed = True                   # Used by the submission tool.  Yang checks always pass.
134         model_list = []
135         info = {'checker': self.name, 'items': [], 'code': {}}
136 
137         extractor = xym.YangModuleExtractor(path, workdir, strict=True, strict_examples=False, debug_level=1)
138         if not os.path.exists(path):
139             return None, "%s: No such file or directory: '%s'"%(name.capitalize(), path), errors, warnings, info
140         with open(path) as file:
141             out = ""
142             err = ""
143             code = 0
144             try:
145                 # This places the yang models as files in workdir
146                 saved_stdout = sys.stdout
147                 saved_stderr = sys.stderr
148                 sys.stdout = io.StringIO()
149                 sys.stderr = io.StringIO()
150                 extractor.extract_yang_model(file.readlines())
151                 model_list = extractor.get_extracted_models(False, True)
152                 out = sys.stdout.getvalue()
153                 err = sys.stderr.getvalue()
154                 # signature change in xym:
155             except Exception as exc:
156                 sys.stdout = saved_stdout
157                 sys.stderr = saved_stderr
158                 msg = "Exception when running xym on %s: %s" % (name, exc)
159                 log(msg)
160                 raise
161                 return None, msg, 0, 0, info
162             finally:
163                 sys.stdout = saved_stdout
164                 sys.stderr = saved_stderr
165         if not model_list:
166             # Found no yang models, don't deliver any YangChecker result
167             return None, "", 0, 0, info
168 
169         for m in model_list:
170             if not re.search(model_name_re, m):
171                 code += 1
172                 err += "Error: Bad extracted model name: '%s'\n" % m
173         if len(set(model_list)) != len(model_list):
174             code += 1
175             err += "Error: Multiple models with the same name:\n  %s\n" % ("\n  ".join(model_list))
176 
177         model_list = list(set(model_list))
178 
179         command = "xym"
180         cmd_version = VersionInfo.objects.get(command=command).version
181         message = "%s:\n%s\n\n" % (cmd_version, out.replace('\n\n','\n').strip() if code == 0 else err)
182 
183         results.append({
184             "name": name,
185             "passed":  passed,
186             "message": message,
187             "warnings": 0,
188             "errors":  code,
189             "items": [],
190         })
191 
192         for model in model_list:
193             path = os.path.join(workdir, model)
194             message = ""
195             passed = True
196             errors = 0
197             warnings = 0
198             items = []
199             modpath = ':'.join([
200                                 workdir,
201                                 settings.SUBMIT_YANG_RFC_MODEL_DIR,
202                                 settings.SUBMIT_YANG_DRAFT_MODEL_DIR,
203                                 settings.SUBMIT_YANG_IANA_MODEL_DIR,
204                                 settings.SUBMIT_YANG_CATALOG_MODEL_DIR,
205                             ])
206             if os.path.exists(path):
207                 with io.open(path) as file:
208                     text = file.readlines()
209                 # pyang
210                 cmd_template = settings.SUBMIT_PYANG_COMMAND
211                 command = [ w for w in cmd_template.split() if not '=' in w ][0]
212                 cmd_version = VersionInfo.objects.get(command=command).version
213                 cmd = cmd_template.format(libs=modpath, model=path)
214                 venv_path = os.environ.get('VIRTUAL_ENV') or os.path.join(os.getcwd(), 'env')
215                 venv_bin = os.path.join(venv_path, 'bin')
216                 if not venv_bin in os.environ.get('PATH', '').split(':'):
217                     os.environ['PATH'] = os.environ.get('PATH', '') + ":" + venv_bin
218                 code, out, err = pipe(cmd)
219                 out = out.decode('utf-8')
220                 err = err.decode('utf-8')
221                 if code > 0 or len(err.strip()) > 0 :
222                     error_lines = err.splitlines()
223                     assertion('len(error_lines) > 0')
224                     for line in error_lines:
225                         if line.strip():
226                             try:
227                                 fn, lnum, msg = line.split(':', 2)
228                                 lnum = int(lnum)
229                                 if fn == model and (lnum-1) in range(len(text)):
230                                     line = text[lnum-1].rstrip()
231                                 else:
232                                     line = None
233                                 items.append((lnum, line, msg))
234                                 if 'error: ' in msg:
235                                     errors += 1
236                                 if 'warning: ' in msg:
237                                     warnings += 1
238                             except ValueError:
239                                 pass
240                 #passed = passed and code == 0 # For the submission tool.  Yang checks always pass
241                 message += "%s: %s:\n%s\n" % (cmd_version, cmd_template, out+"No validation errors\n" if (code == 0 and len(err) == 0) else out+err)
242 
243                 # yanglint
244                 set_coverage_checking(False) # we can't count the following as it may or may not be run, depending on setup
245                 if settings.SUBMIT_YANGLINT_COMMAND and os.path.exists(settings.YANGLINT_BINARY):
246                     cmd_template = settings.SUBMIT_YANGLINT_COMMAND
247                     command = [ w for w in cmd_template.split() if not '=' in w ][0]
248                     cmd_version = VersionInfo.objects.get(command=command).version
249                     cmd = cmd_template.format(model=path, rfclib=settings.SUBMIT_YANG_RFC_MODEL_DIR, tmplib=workdir,
250                         draftlib=settings.SUBMIT_YANG_DRAFT_MODEL_DIR, ianalib=settings.SUBMIT_YANG_IANA_MODEL_DIR,
251                         cataloglib=settings.SUBMIT_YANG_CATALOG_MODEL_DIR, )
252                     code, out, err = pipe(cmd)
253                     out = out.decode('utf-8')
254                     err = err.decode('utf-8')
255                     if code > 0 or len(err.strip()) > 0:
256                         err_lines = err.splitlines()
257                         for line in err_lines:
258                             if line.strip():
259                                 try:
260                                     if 'err : ' in line:
261                                         errors += 1
262                                     if 'warn: ' in line:
263                                         warnings += 1
264                                 except ValueError:
265                                     pass
266                     #passed = passed and code == 0 # For the submission tool.  Yang checks always pass
267                     message += "%s: %s:\n%s\n" % (cmd_version, cmd_template, out+"No validation errors\n" if (code == 0 and len(err) == 0) else out+err)
268                 set_coverage_checking(True)
269             else:
270                 errors += 1
271                 message += "No such file: %s\nPossible mismatch between extracted xym file name and returned module name?\n" % (path)
272 
273             dest = os.path.join(settings.SUBMIT_YANG_DRAFT_MODEL_DIR, model)
274             shutil.move(path, dest)
275 
276             # summary result
277             results.append({
278                 "name": model,
279                 "passed":  passed,
280                 "message": message,
281                 "warnings": warnings,
282                 "errors":  errors,
283                 "items": items,
284             })
285 
286 
287         shutil.rmtree(workdir)
288 
289         passed  = all( res["passed"] for res in results )
290         message = "\n".join([ "\n".join([res['name']+':', res["message"]]) for res in results ])
291         errors  = sum(res["errors"] for res in results )
292         warnings  = sum(res["warnings"] for res in results )
293         items  = [ e for res in results for e in res["items"] ]
294         info['items'] = items
295         info['code']['yang'] = model_list
296         return passed, message, errors, warnings, info
```
--- 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/ietf/submit/checkers.py b/ietf/submit/checkers.py
--- a/ietf/submit/checkers.py
+++ b/ietf/submit/checkers.py
@@ -147,7 +147,7 @@
                 saved_stderr = sys.stderr
                 sys.stdout = io.StringIO()
                 sys.stderr = io.StringIO()
-                extractor.extract_yang_model(file.readlines())
+                extractor.extract_yang_model_text(file.read())
                 model_list = extractor.get_extracted_models(False, True)
                 out = sys.stdout.getvalue()
                 err = sys.stderr.getvalue()

{"golden_diff": "diff --git a/ietf/submit/checkers.py b/ietf/submit/checkers.py\n--- a/ietf/submit/checkers.py\n+++ b/ietf/submit/checkers.py\n@@ -147,7 +147,7 @@\n                 saved_stderr = sys.stderr\n                 sys.stdout = io.StringIO()\n                 sys.stderr = io.StringIO()\n-                extractor.extract_yang_model(file.readlines())\n+                extractor.extract_yang_model_text(file.read())\n                 model_list = extractor.get_extracted_models(False, True)\n                 out = sys.stdout.getvalue()\n                 err = sys.stderr.getvalue()\n", "issue": "Many tests failing with YangModelExtractor error\n### Describe the issue\n\nStarting with [test run 790](https://github.com/ietf-tools/datatracker/actions/runs/3172961553), a few dozen tests are failing with the error\r\n```\r\nAttributeError: 'YangModuleExtractor' object has no attribute 'extract_yang_model'\r\n```\r\nThe last passing test run was a couple hours before the release of [xym 0.6.0](https://pypi.org/project/xym/0.6.0/#history), so it seems likely this is related.\n\n### Code of Conduct\n\n- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)\n", "before_files": [{"content": "# Copyright The IETF Trust 2016-2020, All Rights Reserved\n# -*- coding: utf-8 -*-\n\n\nimport io\nimport os\nimport re\nimport shutil\nimport sys\nimport tempfile\n\nfrom xym import xym\nfrom django.conf import settings\n\nimport debug                            # pyflakes:ignore\n\nfrom ietf.utils.log import log, assertion\nfrom ietf.utils.models import VersionInfo\nfrom ietf.utils.pipe import pipe\nfrom ietf.utils.test_runner import set_coverage_checking\n\nclass DraftSubmissionChecker(object):\n    name = \"\"\n\n    def check_file_txt(self, text):\n        \"Run checks on a text file\"\n        raise NotImplementedError\n\n    def check_file_xml(self, xml):\n        \"Run checks on an xml file\"\n        raise NotImplementedError\n\n    def check_fragment_txt(self, text):\n        \"Run checks on a fragment from a text file\"\n        raise NotImplementedError\n\n    def check_fragment_xml(self, xml):\n        \"Run checks on a fragment from an xml file\"\n        raise NotImplementedError\n\n\nclass DraftIdnitsChecker(object):\n    \"\"\"\n    Draft checker class for idnits.  Idnits can only handle whole text files,\n    so only check_file_txt() is defined; check_file_xml and check_fragment_*\n    methods are undefined.\n\n    Furthermore, idnits doesn't provide an error code or line-by-line errors,\n    so a bit of massage is needed in order to return the expected failure flag.\n    \"\"\"\n    name = \"idnits check\"\n\n    # start using this when we provide more in the way of warnings during\n    # submission checking:\n    # symbol = '<span class=\"bi bi-check-square\"></span>'\n    # symbol = u'<span class=\"large\">\\ua17d</span>' # Yi syllable 'nit'\n    # symbol = u'<span class=\"large\">\\ub2e1</span>' # Hangul syllable 'nit'\n\n    symbol = \"\"\n\n    def __init__(self, options=[\"--submitcheck\", \"--nitcount\", ]):\n        assert isinstance(options, list)\n        if not \"--nitcount\" in options:\n            options.append(\"--nitcount\")\n        self.options = ' '.join(options)\n\n    def check_file_txt(self, path):\n        \"\"\"\n        Run an idnits check, and return a passed/failed indication, a message,\n        and error and warning messages.\n\n        Error and warning list items are tuples:\n            (line_number, line_text, message)\n        \"\"\"\n        items = []\n        errors = 0\n        warnings = 0\n        errstart = ['  ** ', '  ~~ ']\n        warnstart = ['  == ', '  -- ']\n        \n\n        cmd = \"%s %s %s\" % (settings.IDSUBMIT_IDNITS_BINARY, self.options, path)\n        code, out, err = pipe(cmd)\n        out = out.decode('utf-8')\n        err = err.decode('utf-8')\n        if code != 0 or out == \"\":\n            message = \"idnits error: %s:\\n  Error %s: %s\" %( cmd, code, err)\n            log(message)\n            passed = False\n            \n        else:\n            message = out\n            if re.search(r\"\\s+Summary:\\s+0\\s+|No nits found\", out):\n                passed  = True\n            else:\n                passed  = False\n\n        item = \"\"\n        for line in message.splitlines():\n            if   line[:5] in (errstart + warnstart):\n                item = line.rstrip()\n            elif line.strip() == \"\" and item:\n                tuple = (None, None, item)\n                items.append(tuple)\n                if item[:5] in errstart:\n                    errors += 1\n                elif item[:5] in warnstart:\n                    warnings += 1\n                else:\n                    raise RuntimeError(\"Unexpected state in idnits checker: item: %s, line: %s\" % (item, line))\n                item = \"\"\n            elif item and line.strip() != \"\":\n                item += \" \" + line.strip()\n            else:\n                pass\n        info = {'checker': self.name, 'items': [], 'code': {}}\n\n        return passed, message, errors, warnings, info\n\nclass DraftYangChecker(object):\n\n    name = \"yang validation\"\n    symbol = '<i class=\"bi bi-yin-yang\"></i>'\n\n    def check_file_txt(self, path):\n        name = os.path.basename(path)\n        workdir = tempfile.mkdtemp()\n        model_name_re = r'^[A-Za-z_][A-Za-z0-9_.-]*(@\\d\\d\\d\\d-\\d\\d-\\d\\d)?\\.yang$'\n        errors = 0\n        warnings = 0\n        message = \"\"\n        results = []\n        passed = True                   # Used by the submission tool.  Yang checks always pass.\n        model_list = []\n        info = {'checker': self.name, 'items': [], 'code': {}}\n\n        extractor = xym.YangModuleExtractor(path, workdir, strict=True, strict_examples=False, debug_level=1)\n        if not os.path.exists(path):\n            return None, \"%s: No such file or directory: '%s'\"%(name.capitalize(), path), errors, warnings, info\n        with open(path) as file:\n            out = \"\"\n            err = \"\"\n            code = 0\n            try:\n                # This places the yang models as files in workdir\n                saved_stdout = sys.stdout\n                saved_stderr = sys.stderr\n                sys.stdout = io.StringIO()\n                sys.stderr = io.StringIO()\n                extractor.extract_yang_model(file.readlines())\n                model_list = extractor.get_extracted_models(False, True)\n                out = sys.stdout.getvalue()\n                err = sys.stderr.getvalue()\n                # signature change in xym:\n            except Exception as exc:\n                sys.stdout = saved_stdout\n                sys.stderr = saved_stderr\n                msg = \"Exception when running xym on %s: %s\" % (name, exc)\n                log(msg)\n                raise\n                return None, msg, 0, 0, info\n            finally:\n                sys.stdout = saved_stdout\n                sys.stderr = saved_stderr\n        if not model_list:\n            # Found no yang models, don't deliver any YangChecker result\n            return None, \"\", 0, 0, info\n\n        for m in model_list:\n            if not re.search(model_name_re, m):\n                code += 1\n                err += \"Error: Bad extracted model name: '%s'\\n\" % m\n        if len(set(model_list)) != len(model_list):\n            code += 1\n            err += \"Error: Multiple models with the same name:\\n  %s\\n\" % (\"\\n  \".join(model_list))\n\n        model_list = list(set(model_list))\n\n        command = \"xym\"\n        cmd_version = VersionInfo.objects.get(command=command).version\n        message = \"%s:\\n%s\\n\\n\" % (cmd_version, out.replace('\\n\\n','\\n').strip() if code == 0 else err)\n\n        results.append({\n            \"name\": name,\n            \"passed\":  passed,\n            \"message\": message,\n            \"warnings\": 0,\n            \"errors\":  code,\n            \"items\": [],\n        })\n\n        for model in model_list:\n            path = os.path.join(workdir, model)\n            message = \"\"\n            passed = True\n            errors = 0\n            warnings = 0\n            items = []\n            modpath = ':'.join([\n                                workdir,\n                                settings.SUBMIT_YANG_RFC_MODEL_DIR,\n                                settings.SUBMIT_YANG_DRAFT_MODEL_DIR,\n                                settings.SUBMIT_YANG_IANA_MODEL_DIR,\n                                settings.SUBMIT_YANG_CATALOG_MODEL_DIR,\n                            ])\n            if os.path.exists(path):\n                with io.open(path) as file:\n                    text = file.readlines()\n                # pyang\n                cmd_template = settings.SUBMIT_PYANG_COMMAND\n                command = [ w for w in cmd_template.split() if not '=' in w ][0]\n                cmd_version = VersionInfo.objects.get(command=command).version\n                cmd = cmd_template.format(libs=modpath, model=path)\n                venv_path = os.environ.get('VIRTUAL_ENV') or os.path.join(os.getcwd(), 'env')\n                venv_bin = os.path.join(venv_path, 'bin')\n                if not venv_bin in os.environ.get('PATH', '').split(':'):\n                    os.environ['PATH'] = os.environ.get('PATH', '') + \":\" + venv_bin\n                code, out, err = pipe(cmd)\n                out = out.decode('utf-8')\n                err = err.decode('utf-8')\n                if code > 0 or len(err.strip()) > 0 :\n                    error_lines = err.splitlines()\n                    assertion('len(error_lines) > 0')\n                    for line in error_lines:\n                        if line.strip():\n                            try:\n                                fn, lnum, msg = line.split(':', 2)\n                                lnum = int(lnum)\n                                if fn == model and (lnum-1) in range(len(text)):\n                                    line = text[lnum-1].rstrip()\n                                else:\n                                    line = None\n                                items.append((lnum, line, msg))\n                                if 'error: ' in msg:\n                                    errors += 1\n                                if 'warning: ' in msg:\n                                    warnings += 1\n                            except ValueError:\n                                pass\n                #passed = passed and code == 0 # For the submission tool.  Yang checks always pass\n                message += \"%s: %s:\\n%s\\n\" % (cmd_version, cmd_template, out+\"No validation errors\\n\" if (code == 0 and len(err) == 0) else out+err)\n\n                # yanglint\n                set_coverage_checking(False) # we can't count the following as it may or may not be run, depending on setup\n                if settings.SUBMIT_YANGLINT_COMMAND and os.path.exists(settings.YANGLINT_BINARY):\n                    cmd_template = settings.SUBMIT_YANGLINT_COMMAND\n                    command = [ w for w in cmd_template.split() if not '=' in w ][0]\n                    cmd_version = VersionInfo.objects.get(command=command).version\n                    cmd = cmd_template.format(model=path, rfclib=settings.SUBMIT_YANG_RFC_MODEL_DIR, tmplib=workdir,\n                        draftlib=settings.SUBMIT_YANG_DRAFT_MODEL_DIR, ianalib=settings.SUBMIT_YANG_IANA_MODEL_DIR,\n                        cataloglib=settings.SUBMIT_YANG_CATALOG_MODEL_DIR, )\n                    code, out, err = pipe(cmd)\n                    out = out.decode('utf-8')\n                    err = err.decode('utf-8')\n                    if code > 0 or len(err.strip()) > 0:\n                        err_lines = err.splitlines()\n                        for line in err_lines:\n                            if line.strip():\n                                try:\n                                    if 'err : ' in line:\n                                        errors += 1\n                                    if 'warn: ' in line:\n                                        warnings += 1\n                                except ValueError:\n                                    pass\n                    #passed = passed and code == 0 # For the submission tool.  Yang checks always pass\n                    message += \"%s: %s:\\n%s\\n\" % (cmd_version, cmd_template, out+\"No validation errors\\n\" if (code == 0 and len(err) == 0) else out+err)\n                set_coverage_checking(True)\n            else:\n                errors += 1\n                message += \"No such file: %s\\nPossible mismatch between extracted xym file name and returned module name?\\n\" % (path)\n\n            dest = os.path.join(settings.SUBMIT_YANG_DRAFT_MODEL_DIR, model)\n            shutil.move(path, dest)\n\n            # summary result\n            results.append({\n                \"name\": model,\n                \"passed\":  passed,\n                \"message\": message,\n                \"warnings\": warnings,\n                \"errors\":  errors,\n                \"items\": items,\n            })\n\n\n        shutil.rmtree(workdir)\n\n        passed  = all( res[\"passed\"] for res in results )\n        message = \"\\n\".join([ \"\\n\".join([res['name']+':', res[\"message\"]]) for res in results ])\n        errors  = sum(res[\"errors\"] for res in results )\n        warnings  = sum(res[\"warnings\"] for res in results )\n        items  = [ e for res in results for e in res[\"items\"] ]\n        info['items'] = items\n        info['code']['yang'] = model_list\n        return passed, message, errors, warnings, info", "path": "ietf/submit/checkers.py"}], "after_files": [{"content": "# Copyright The IETF Trust 2016-2020, All Rights Reserved\n# -*- coding: utf-8 -*-\n\n\nimport io\nimport os\nimport re\nimport shutil\nimport sys\nimport tempfile\n\nfrom xym import xym\nfrom django.conf import settings\n\nimport debug                            # pyflakes:ignore\n\nfrom ietf.utils.log import log, assertion\nfrom ietf.utils.models import VersionInfo\nfrom ietf.utils.pipe import pipe\nfrom ietf.utils.test_runner import set_coverage_checking\n\nclass DraftSubmissionChecker(object):\n    name = \"\"\n\n    def check_file_txt(self, text):\n        \"Run checks on a text file\"\n        raise NotImplementedError\n\n    def check_file_xml(self, xml):\n        \"Run checks on an xml file\"\n        raise NotImplementedError\n\n    def check_fragment_txt(self, text):\n        \"Run checks on a fragment from a text file\"\n        raise NotImplementedError\n\n    def check_fragment_xml(self, xml):\n        \"Run checks on a fragment from an xml file\"\n        raise NotImplementedError\n\n\nclass DraftIdnitsChecker(object):\n    \"\"\"\n    Draft checker class for idnits.  Idnits can only handle whole text files,\n    so only check_file_txt() is defined; check_file_xml and check_fragment_*\n    methods are undefined.\n\n    Furthermore, idnits doesn't provide an error code or line-by-line errors,\n    so a bit of massage is needed in order to return the expected failure flag.\n    \"\"\"\n    name = \"idnits check\"\n\n    # start using this when we provide more in the way of warnings during\n    # submission checking:\n    # symbol = '<span class=\"bi bi-check-square\"></span>'\n    # symbol = u'<span class=\"large\">\\ua17d</span>' # Yi syllable 'nit'\n    # symbol = u'<span class=\"large\">\\ub2e1</span>' # Hangul syllable 'nit'\n\n    symbol = \"\"\n\n    def __init__(self, options=[\"--submitcheck\", \"--nitcount\", ]):\n        assert isinstance(options, list)\n        if not \"--nitcount\" in options:\n            options.append(\"--nitcount\")\n        self.options = ' '.join(options)\n\n    def check_file_txt(self, path):\n        \"\"\"\n        Run an idnits check, and return a passed/failed indication, a message,\n        and error and warning messages.\n\n        Error and warning list items are tuples:\n            (line_number, line_text, message)\n        \"\"\"\n        items = []\n        errors = 0\n        warnings = 0\n        errstart = ['  ** ', '  ~~ ']\n        warnstart = ['  == ', '  -- ']\n        \n\n        cmd = \"%s %s %s\" % (settings.IDSUBMIT_IDNITS_BINARY, self.options, path)\n        code, out, err = pipe(cmd)\n        out = out.decode('utf-8')\n        err = err.decode('utf-8')\n        if code != 0 or out == \"\":\n            message = \"idnits error: %s:\\n  Error %s: %s\" %( cmd, code, err)\n            log(message)\n            passed = False\n            \n        else:\n            message = out\n            if re.search(r\"\\s+Summary:\\s+0\\s+|No nits found\", out):\n                passed  = True\n            else:\n                passed  = False\n\n        item = \"\"\n        for line in message.splitlines():\n            if   line[:5] in (errstart + warnstart):\n                item = line.rstrip()\n            elif line.strip() == \"\" and item:\n                tuple = (None, None, item)\n                items.append(tuple)\n                if item[:5] in errstart:\n                    errors += 1\n                elif item[:5] in warnstart:\n                    warnings += 1\n                else:\n                    raise RuntimeError(\"Unexpected state in idnits checker: item: %s, line: %s\" % (item, line))\n                item = \"\"\n            elif item and line.strip() != \"\":\n                item += \" \" + line.strip()\n            else:\n                pass\n        info = {'checker': self.name, 'items': [], 'code': {}}\n\n        return passed, message, errors, warnings, info\n\nclass DraftYangChecker(object):\n\n    name = \"yang validation\"\n    symbol = '<i class=\"bi bi-yin-yang\"></i>'\n\n    def check_file_txt(self, path):\n        name = os.path.basename(path)\n        workdir = tempfile.mkdtemp()\n        model_name_re = r'^[A-Za-z_][A-Za-z0-9_.-]*(@\\d\\d\\d\\d-\\d\\d-\\d\\d)?\\.yang$'\n        errors = 0\n        warnings = 0\n        message = \"\"\n        results = []\n        passed = True                   # Used by the submission tool.  Yang checks always pass.\n        model_list = []\n        info = {'checker': self.name, 'items': [], 'code': {}}\n\n        extractor = xym.YangModuleExtractor(path, workdir, strict=True, strict_examples=False, debug_level=1)\n        if not os.path.exists(path):\n            return None, \"%s: No such file or directory: '%s'\"%(name.capitalize(), path), errors, warnings, info\n        with open(path) as file:\n            out = \"\"\n            err = \"\"\n            code = 0\n            try:\n                # This places the yang models as files in workdir\n                saved_stdout = sys.stdout\n                saved_stderr = sys.stderr\n                sys.stdout = io.StringIO()\n                sys.stderr = io.StringIO()\n                extractor.extract_yang_model_text(file.read())\n                model_list = extractor.get_extracted_models(False, True)\n                out = sys.stdout.getvalue()\n                err = sys.stderr.getvalue()\n                # signature change in xym:\n            except Exception as exc:\n                sys.stdout = saved_stdout\n                sys.stderr = saved_stderr\n                msg = \"Exception when running xym on %s: %s\" % (name, exc)\n                log(msg)\n                raise\n                return None, msg, 0, 0, info\n            finally:\n                sys.stdout = saved_stdout\n                sys.stderr = saved_stderr\n        if not model_list:\n            # Found no yang models, don't deliver any YangChecker result\n            return None, \"\", 0, 0, info\n\n        for m in model_list:\n            if not re.search(model_name_re, m):\n                code += 1\n                err += \"Error: Bad extracted model name: '%s'\\n\" % m\n        if len(set(model_list)) != len(model_list):\n            code += 1\n            err += \"Error: Multiple models with the same name:\\n  %s\\n\" % (\"\\n  \".join(model_list))\n\n        model_list = list(set(model_list))\n\n        command = \"xym\"\n        cmd_version = VersionInfo.objects.get(command=command).version\n        message = \"%s:\\n%s\\n\\n\" % (cmd_version, out.replace('\\n\\n','\\n').strip() if code == 0 else err)\n\n        results.append({\n            \"name\": name,\n            \"passed\":  passed,\n            \"message\": message,\n            \"warnings\": 0,\n            \"errors\":  code,\n            \"items\": [],\n        })\n\n        for model in model_list:\n            path = os.path.join(workdir, model)\n            message = \"\"\n            passed = True\n            errors = 0\n            warnings = 0\n            items = []\n            modpath = ':'.join([\n                                workdir,\n                                settings.SUBMIT_YANG_RFC_MODEL_DIR,\n                                settings.SUBMIT_YANG_DRAFT_MODEL_DIR,\n                                settings.SUBMIT_YANG_IANA_MODEL_DIR,\n                                settings.SUBMIT_YANG_CATALOG_MODEL_DIR,\n                            ])\n            if os.path.exists(path):\n                with io.open(path) as file:\n                    text = file.readlines()\n                # pyang\n                cmd_template = settings.SUBMIT_PYANG_COMMAND\n                command = [ w for w in cmd_template.split() if not '=' in w ][0]\n                cmd_version = VersionInfo.objects.get(command=command).version\n                cmd = cmd_template.format(libs=modpath, model=path)\n                venv_path = os.environ.get('VIRTUAL_ENV') or os.path.join(os.getcwd(), 'env')\n                venv_bin = os.path.join(venv_path, 'bin')\n                if not venv_bin in os.environ.get('PATH', '').split(':'):\n                    os.environ['PATH'] = os.environ.get('PATH', '') + \":\" + venv_bin\n                code, out, err = pipe(cmd)\n                out = out.decode('utf-8')\n                err = err.decode('utf-8')\n                if code > 0 or len(err.strip()) > 0 :\n                    error_lines = err.splitlines()\n                    assertion('len(error_lines) > 0')\n                    for line in error_lines:\n                        if line.strip():\n                            try:\n                                fn, lnum, msg = line.split(':', 2)\n                                lnum = int(lnum)\n                                if fn == model and (lnum-1) in range(len(text)):\n                                    line = text[lnum-1].rstrip()\n                                else:\n                                    line = None\n                                items.append((lnum, line, msg))\n                                if 'error: ' in msg:\n                                    errors += 1\n                                if 'warning: ' in msg:\n                                    warnings += 1\n                            except ValueError:\n                                pass\n                #passed = passed and code == 0 # For the submission tool.  Yang checks always pass\n                message += \"%s: %s:\\n%s\\n\" % (cmd_version, cmd_template, out+\"No validation errors\\n\" if (code == 0 and len(err) == 0) else out+err)\n\n                # yanglint\n                set_coverage_checking(False) # we can't count the following as it may or may not be run, depending on setup\n                if settings.SUBMIT_YANGLINT_COMMAND and os.path.exists(settings.YANGLINT_BINARY):\n                    cmd_template = settings.SUBMIT_YANGLINT_COMMAND\n                    command = [ w for w in cmd_template.split() if not '=' in w ][0]\n                    cmd_version = VersionInfo.objects.get(command=command).version\n                    cmd = cmd_template.format(model=path, rfclib=settings.SUBMIT_YANG_RFC_MODEL_DIR, tmplib=workdir,\n                        draftlib=settings.SUBMIT_YANG_DRAFT_MODEL_DIR, ianalib=settings.SUBMIT_YANG_IANA_MODEL_DIR,\n                        cataloglib=settings.SUBMIT_YANG_CATALOG_MODEL_DIR, )\n                    code, out, err = pipe(cmd)\n                    out = out.decode('utf-8')\n                    err = err.decode('utf-8')\n                    if code > 0 or len(err.strip()) > 0:\n                        err_lines = err.splitlines()\n                        for line in err_lines:\n                            if line.strip():\n                                try:\n                                    if 'err : ' in line:\n                                        errors += 1\n                                    if 'warn: ' in line:\n                                        warnings += 1\n                                except ValueError:\n                                    pass\n                    #passed = passed and code == 0 # For the submission tool.  Yang checks always pass\n                    message += \"%s: %s:\\n%s\\n\" % (cmd_version, cmd_template, out+\"No validation errors\\n\" if (code == 0 and len(err) == 0) else out+err)\n                set_coverage_checking(True)\n            else:\n                errors += 1\n                message += \"No such file: %s\\nPossible mismatch between extracted xym file name and returned module name?\\n\" % (path)\n\n            dest = os.path.join(settings.SUBMIT_YANG_DRAFT_MODEL_DIR, model)\n            shutil.move(path, dest)\n\n            # summary result\n            results.append({\n                \"name\": model,\n                \"passed\":  passed,\n                \"message\": message,\n                \"warnings\": warnings,\n                \"errors\":  errors,\n                \"items\": items,\n            })\n\n\n        shutil.rmtree(workdir)\n\n        passed  = all( res[\"passed\"] for res in results )\n        message = \"\\n\".join([ \"\\n\".join([res['name']+':', res[\"message\"]]) for res in results ])\n        errors  = sum(res[\"errors\"] for res in results )\n        warnings  = sum(res[\"warnings\"] for res in results )\n        items  = [ e for res in results for e in res[\"items\"] ]\n        info['items'] = items\n        info['code']['yang'] = model_list\n        return passed, message, errors, warnings, info", "path": "ietf/submit/checkers.py"}]}