We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
linter false positive with selectors
https://github.com/conda-forge/staged-recipes/pull/3992 got a `Selectors are suggested to take a <two spaces>#<one space>[<expression>] form.` error, when the only selector actually is spaced properly.

Looking at it a bit, `selector_lines` thinks that the following two lines have selectors:
```
    - inotifywatch --help > /dev/null || [[ $? == 1 ]]
    - inotifywait --help > /dev/null || [[ $? == 1 ]]
```
I guess that conda-build would actually identify this line as a selector, but the linter message is pretty confusing. Should we print line numbers or something?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `conda_smithy/lint_recipe.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 from __future__ import unicode_literals
4 
5 import io
6 import itertools
7 import os
8 import re
9 import github
10 
11 import jinja2
12 import ruamel.yaml
13 
14 from conda_build.metadata import ensure_valid_license_family
15 
16 
17 EXPECTED_SECTION_ORDER = ['package', 'source', 'build', 'requirements',
18                           'test', 'app', 'about', 'extra']
19 
20 REQUIREMENTS_ORDER = ['build', 'run']
21 
22 TEST_KEYS = {'imports', 'commands'}
23 
24 
25 class NullUndefined(jinja2.Undefined):
26     def __unicode__(self):
27         return self._undefined_name
28 
29     def __getattr__(self, name):
30         return '{}.{}'.format(self, name)
31 
32     def __getitem__(self, name):
33         return '{}["{}"]'.format(self, name)
34 
35 
36 def get_section(parent, name, lints):
37     section = parent.get(name, {})
38     if not isinstance(section, dict):
39         lints.append('The "{}" section was expected to be a dictionary, but '
40                      'got a {}.'.format(name, type(section).__name__))
41         section = {}
42     return section
43 
44 
45 def lintify(meta, recipe_dir=None, conda_forge=False):
46     lints = []
47     major_sections = list(meta.keys())
48 
49     # If the recipe_dir exists (no guarantee within this function) , we can
50     # find the meta.yaml within it.
51     meta_fname = os.path.join(recipe_dir or '', 'meta.yaml')
52 
53     source_section = get_section(meta, 'source', lints)
54     build_section = get_section(meta, 'build', lints)
55     requirements_section = get_section(meta, 'requirements', lints)
56     test_section = get_section(meta, 'test', lints)
57     about_section = get_section(meta, 'about', lints)
58     extra_section = get_section(meta, 'extra', lints)
59     package_section = get_section(meta, 'package', lints)
60 
61     # 1: Top level meta.yaml keys should have a specific order.
62     section_order_sorted = sorted(major_sections,
63                                   key=EXPECTED_SECTION_ORDER.index)
64     if major_sections != section_order_sorted:
65         section_order_sorted_str = map(lambda s: "'%s'" % s,
66                                        section_order_sorted)
67         section_order_sorted_str = ", ".join(section_order_sorted_str)
68         section_order_sorted_str = "[" + section_order_sorted_str + "]"
69         lints.append('The top level meta keys are in an unexpected order. '
70                      'Expecting {}.'.format(section_order_sorted_str))
71 
72     # 2: The about section should have a home, license and summary.
73     for about_item in ['home', 'license', 'summary']:
74         # if the section doesn't exist, or is just empty, lint it.
75         if not about_section.get(about_item, ''):
76             lints.append('The {} item is expected in the about section.'
77                          ''.format(about_item))
78 
79     # 3a: The recipe should have some maintainers.
80     if not extra_section.get('recipe-maintainers', []):
81         lints.append('The recipe could do with some maintainers listed in '
82                      'the `extra/recipe-maintainers` section.')
83 
84     # 3b: Maintainers should be a list
85     if not isinstance(extra_section.get('recipe-maintainers', []), list):
86         lints.append('Recipe maintainers should be a json list.')
87 
88     # 4: The recipe should have some tests.
89     if not any(key in TEST_KEYS for key in test_section):
90         test_files = ['run_test.py', 'run_test.sh', 'run_test.bat',
91                       'run_test.pl']
92         a_test_file_exists = (recipe_dir is not None and
93                               any(os.path.exists(os.path.join(recipe_dir,
94                                                               test_file))
95                                   for test_file in test_files))
96         if not a_test_file_exists:
97             lints.append('The recipe must have some tests.')
98 
99     # 5: License cannot be 'unknown.'
100     license = about_section.get('license', '').lower()
101     if 'unknown' == license.strip():
102         lints.append('The recipe license cannot be unknown.')
103 
104     # 6: Selectors should be in a tidy form.
105     if recipe_dir is not None and os.path.exists(meta_fname):
106         bad_selectors = []
107         # Good selectors look like ".*\s\s#\s[...]"
108         good_selectors_pat = re.compile(r'(.+?)\s{2,}#\s\[(.+)\](?(2).*)$')
109         with io.open(meta_fname, 'rt') as fh:
110             for selector_line in selector_lines(fh):
111                 if not good_selectors_pat.match(selector_line):
112                     bad_selectors.append(selector_line)
113         if bad_selectors:
114             lints.append('Selectors are suggested to take a '
115                          '``<two spaces>#<one space>[<expression>]`` form.')
116 
117     # 7: The build section should have a build number.
118     if build_section.get('number', None) is None:
119         lints.append('The recipe must have a `build/number` section.')
120 
121     # 8: The build section should be before the run section in requirements.
122     requirements_order_sorted = sorted(requirements_section,
123                                        key=REQUIREMENTS_ORDER.index)
124     if list(requirements_section.keys()) != requirements_order_sorted:
125         lints.append('The `requirements/build` section should be defined '
126                      'before the `requirements/run` section.')
127 
128     # 9: Files downloaded should have a hash.
129     if ('url' in source_section and
130             not ({'sha1', 'sha256', 'md5'} & set(source_section.keys()))):
131         lints.append('When defining a source/url please add a sha256, sha1 '
132                      'or md5 checksum (sha256 preferably).')
133 
134     # 10: License should not include the word 'license'.
135     license = about_section.get('license', '').lower()
136     if 'license' in license.lower():
137         lints.append('The recipe `license` should not include the word '
138                      '"License".')
139 
140     # 11: There should be one empty line at the end of the file.
141     if recipe_dir is not None and os.path.exists(meta_fname):
142         with io.open(meta_fname, 'r') as f:
143             lines = f.read().split('\n')
144         # Count the number of empty lines from the end of the file
145         empty_lines = itertools.takewhile(lambda x: x == '', reversed(lines))
146         end_empty_lines_count = len(list(empty_lines))
147         if end_empty_lines_count > 1:
148             lints.append('There are {} too many lines.  '
149                          'There should be one empty line at the end of the '
150                          'file.'.format(end_empty_lines_count - 1))
151         elif end_empty_lines_count < 1:
152             lints.append('There are too few lines.  There should be one empty '
153                          'line at the end of the file.')
154 
155     # 12: License family must be valid (conda-build checks for that)
156     try:
157         ensure_valid_license_family(meta)
158     except RuntimeError as e:
159         lints.append(str(e))
160 
161     # 13: Check that the recipe name is valid
162     recipe_name = package_section.get('name', '').strip()
163     if re.match('^[a-z0-9_\-.]+$', recipe_name) is None:
164         lints.append('Recipe name has invalid characters. only lowercase alpha, numeric, '
165                      'underscores, hyphens and dots allowed')
166 
167     # 14: Run conda-forge specific lints
168     if conda_forge:
169         run_conda_forge_lints(meta, recipe_dir, lints)
170 
171     # 15: Check if we are using legacy patterns
172     build_reqs = requirements_section.get('build', None)
173     if build_reqs and ('numpy x.x' in build_reqs):
174         lints.append('Using pinned numpy packages is a deprecated pattern.  Consider '
175                      'using the method outlined '
176                      '[here](https://conda-forge.org/docs/meta.html#building-against-numpy).')
177 
178     return lints
179 
180 
181 def run_conda_forge_lints(meta, recipe_dir, lints):
182     gh = github.Github(os.environ['GH_TOKEN'])
183     package_section = get_section(meta, 'package', lints)
184     extra_section = get_section(meta, 'extra', lints)
185     recipe_dirname = os.path.basename(recipe_dir) if recipe_dir else 'recipe'
186     recipe_name = package_section.get('name', '').strip()
187     is_staged_recipes = recipe_dirname != 'recipe'
188 
189     # 1: Check that the recipe does not exist in conda-forge
190     if is_staged_recipes:
191         cf = gh.get_user(os.getenv('GH_ORG', 'conda-forge'))
192         try:
193             cf.get_repo('{}-feedstock'.format(recipe_name))
194             feedstock_exists = True
195         except github.UnknownObjectException as e:
196             feedstock_exists = False
197 
198         if feedstock_exists:
199             lints.append('Feedstock with the same name exists in conda-forge')
200 
201     # 2: Check that the recipe maintainers exists:
202     maintainers = extra_section.get('recipe-maintainers', [])
203     for maintainer in maintainers:
204         try:
205             gh.get_user(maintainer)
206         except github.UnknownObjectException as e:
207             lints.append('Recipe maintainer "{}" does not exist'.format(maintainer))
208 
209 
210 def selector_lines(lines):
211     # Using the same pattern defined in conda-build (metadata.py),
212     # we identify selectors.
213     sel_pat = re.compile(r'(.+?)\s*(#.*)?\[(.+)\](?(2).*)$')
214 
215     for line in lines:
216         line = line.rstrip()
217         if line.lstrip().startswith('#'):
218             # Don't bother with comment only lines
219             continue
220         m = sel_pat.match(line)
221         if m:
222             m.group(3)
223             yield line
224 
225 
226 def main(recipe_dir, conda_forge=False):
227     recipe_dir = os.path.abspath(recipe_dir)
228     recipe_meta = os.path.join(recipe_dir, 'meta.yaml')
229     if not os.path.exists(recipe_dir):
230         raise IOError('Feedstock has no recipe/meta.yaml.')
231 
232     env = jinja2.Environment(undefined=NullUndefined)
233 
234     with io.open(recipe_meta, 'rt') as fh:
235         content = env.from_string(''.join(fh)).render(os=os)
236         meta = ruamel.yaml.load(content, ruamel.yaml.RoundTripLoader)
237     results = lintify(meta, recipe_dir, conda_forge)
238     return results
239
```
--- 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/conda_smithy/lint_recipe.py b/conda_smithy/lint_recipe.py
--- a/conda_smithy/lint_recipe.py
+++ b/conda_smithy/lint_recipe.py
@@ -210,7 +210,7 @@
 def selector_lines(lines):
     # Using the same pattern defined in conda-build (metadata.py),
     # we identify selectors.
-    sel_pat = re.compile(r'(.+?)\s*(#.*)?\[(.+)\](?(2).*)$')
+    sel_pat = re.compile(r'(.+?)\s*(#.*)?\[([^\[\]]+)\](?(2).*)$')
 
     for line in lines:
         line = line.rstrip()

{"golden_diff": "diff --git a/conda_smithy/lint_recipe.py b/conda_smithy/lint_recipe.py\n--- a/conda_smithy/lint_recipe.py\n+++ b/conda_smithy/lint_recipe.py\n@@ -210,7 +210,7 @@\n def selector_lines(lines):\n     # Using the same pattern defined in conda-build (metadata.py),\n     # we identify selectors.\n-    sel_pat = re.compile(r'(.+?)\\s*(#.*)?\\[(.+)\\](?(2).*)$')\n+    sel_pat = re.compile(r'(.+?)\\s*(#.*)?\\[([^\\[\\]]+)\\](?(2).*)$')\n \n     for line in lines:\n         line = line.rstrip()\n", "issue": "linter false positive with selectors\nhttps://github.com/conda-forge/staged-recipes/pull/3992 got a `Selectors are suggested to take a <two spaces>#<one space>[<expression>] form.` error, when the only selector actually is spaced properly.\r\n\r\nLooking at it a bit, `selector_lines` thinks that the following two lines have selectors:\r\n```\r\n    - inotifywatch --help > /dev/null || [[ $? == 1 ]]\r\n    - inotifywait --help > /dev/null || [[ $? == 1 ]]\r\n```\r\nI guess that conda-build would actually identify this line as a selector, but the linter message is pretty confusing. Should we print line numbers or something?\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\nfrom __future__ import unicode_literals\n\nimport io\nimport itertools\nimport os\nimport re\nimport github\n\nimport jinja2\nimport ruamel.yaml\n\nfrom conda_build.metadata import ensure_valid_license_family\n\n\nEXPECTED_SECTION_ORDER = ['package', 'source', 'build', 'requirements',\n                          'test', 'app', 'about', 'extra']\n\nREQUIREMENTS_ORDER = ['build', 'run']\n\nTEST_KEYS = {'imports', 'commands'}\n\n\nclass NullUndefined(jinja2.Undefined):\n    def __unicode__(self):\n        return self._undefined_name\n\n    def __getattr__(self, name):\n        return '{}.{}'.format(self, name)\n\n    def __getitem__(self, name):\n        return '{}[\"{}\"]'.format(self, name)\n\n\ndef get_section(parent, name, lints):\n    section = parent.get(name, {})\n    if not isinstance(section, dict):\n        lints.append('The \"{}\" section was expected to be a dictionary, but '\n                     'got a {}.'.format(name, type(section).__name__))\n        section = {}\n    return section\n\n\ndef lintify(meta, recipe_dir=None, conda_forge=False):\n    lints = []\n    major_sections = list(meta.keys())\n\n    # If the recipe_dir exists (no guarantee within this function) , we can\n    # find the meta.yaml within it.\n    meta_fname = os.path.join(recipe_dir or '', 'meta.yaml')\n\n    source_section = get_section(meta, 'source', lints)\n    build_section = get_section(meta, 'build', lints)\n    requirements_section = get_section(meta, 'requirements', lints)\n    test_section = get_section(meta, 'test', lints)\n    about_section = get_section(meta, 'about', lints)\n    extra_section = get_section(meta, 'extra', lints)\n    package_section = get_section(meta, 'package', lints)\n\n    # 1: Top level meta.yaml keys should have a specific order.\n    section_order_sorted = sorted(major_sections,\n                                  key=EXPECTED_SECTION_ORDER.index)\n    if major_sections != section_order_sorted:\n        section_order_sorted_str = map(lambda s: \"'%s'\" % s,\n                                       section_order_sorted)\n        section_order_sorted_str = \", \".join(section_order_sorted_str)\n        section_order_sorted_str = \"[\" + section_order_sorted_str + \"]\"\n        lints.append('The top level meta keys are in an unexpected order. '\n                     'Expecting {}.'.format(section_order_sorted_str))\n\n    # 2: The about section should have a home, license and summary.\n    for about_item in ['home', 'license', 'summary']:\n        # if the section doesn't exist, or is just empty, lint it.\n        if not about_section.get(about_item, ''):\n            lints.append('The {} item is expected in the about section.'\n                         ''.format(about_item))\n\n    # 3a: The recipe should have some maintainers.\n    if not extra_section.get('recipe-maintainers', []):\n        lints.append('The recipe could do with some maintainers listed in '\n                     'the `extra/recipe-maintainers` section.')\n\n    # 3b: Maintainers should be a list\n    if not isinstance(extra_section.get('recipe-maintainers', []), list):\n        lints.append('Recipe maintainers should be a json list.')\n\n    # 4: The recipe should have some tests.\n    if not any(key in TEST_KEYS for key in test_section):\n        test_files = ['run_test.py', 'run_test.sh', 'run_test.bat',\n                      'run_test.pl']\n        a_test_file_exists = (recipe_dir is not None and\n                              any(os.path.exists(os.path.join(recipe_dir,\n                                                              test_file))\n                                  for test_file in test_files))\n        if not a_test_file_exists:\n            lints.append('The recipe must have some tests.')\n\n    # 5: License cannot be 'unknown.'\n    license = about_section.get('license', '').lower()\n    if 'unknown' == license.strip():\n        lints.append('The recipe license cannot be unknown.')\n\n    # 6: Selectors should be in a tidy form.\n    if recipe_dir is not None and os.path.exists(meta_fname):\n        bad_selectors = []\n        # Good selectors look like \".*\\s\\s#\\s[...]\"\n        good_selectors_pat = re.compile(r'(.+?)\\s{2,}#\\s\\[(.+)\\](?(2).*)$')\n        with io.open(meta_fname, 'rt') as fh:\n            for selector_line in selector_lines(fh):\n                if not good_selectors_pat.match(selector_line):\n                    bad_selectors.append(selector_line)\n        if bad_selectors:\n            lints.append('Selectors are suggested to take a '\n                         '``<two spaces>#<one space>[<expression>]`` form.')\n\n    # 7: The build section should have a build number.\n    if build_section.get('number', None) is None:\n        lints.append('The recipe must have a `build/number` section.')\n\n    # 8: The build section should be before the run section in requirements.\n    requirements_order_sorted = sorted(requirements_section,\n                                       key=REQUIREMENTS_ORDER.index)\n    if list(requirements_section.keys()) != requirements_order_sorted:\n        lints.append('The `requirements/build` section should be defined '\n                     'before the `requirements/run` section.')\n\n    # 9: Files downloaded should have a hash.\n    if ('url' in source_section and\n            not ({'sha1', 'sha256', 'md5'} & set(source_section.keys()))):\n        lints.append('When defining a source/url please add a sha256, sha1 '\n                     'or md5 checksum (sha256 preferably).')\n\n    # 10: License should not include the word 'license'.\n    license = about_section.get('license', '').lower()\n    if 'license' in license.lower():\n        lints.append('The recipe `license` should not include the word '\n                     '\"License\".')\n\n    # 11: There should be one empty line at the end of the file.\n    if recipe_dir is not None and os.path.exists(meta_fname):\n        with io.open(meta_fname, 'r') as f:\n            lines = f.read().split('\\n')\n        # Count the number of empty lines from the end of the file\n        empty_lines = itertools.takewhile(lambda x: x == '', reversed(lines))\n        end_empty_lines_count = len(list(empty_lines))\n        if end_empty_lines_count > 1:\n            lints.append('There are {} too many lines.  '\n                         'There should be one empty line at the end of the '\n                         'file.'.format(end_empty_lines_count - 1))\n        elif end_empty_lines_count < 1:\n            lints.append('There are too few lines.  There should be one empty '\n                         'line at the end of the file.')\n\n    # 12: License family must be valid (conda-build checks for that)\n    try:\n        ensure_valid_license_family(meta)\n    except RuntimeError as e:\n        lints.append(str(e))\n\n    # 13: Check that the recipe name is valid\n    recipe_name = package_section.get('name', '').strip()\n    if re.match('^[a-z0-9_\\-.]+$', recipe_name) is None:\n        lints.append('Recipe name has invalid characters. only lowercase alpha, numeric, '\n                     'underscores, hyphens and dots allowed')\n\n    # 14: Run conda-forge specific lints\n    if conda_forge:\n        run_conda_forge_lints(meta, recipe_dir, lints)\n\n    # 15: Check if we are using legacy patterns\n    build_reqs = requirements_section.get('build', None)\n    if build_reqs and ('numpy x.x' in build_reqs):\n        lints.append('Using pinned numpy packages is a deprecated pattern.  Consider '\n                     'using the method outlined '\n                     '[here](https://conda-forge.org/docs/meta.html#building-against-numpy).')\n\n    return lints\n\n\ndef run_conda_forge_lints(meta, recipe_dir, lints):\n    gh = github.Github(os.environ['GH_TOKEN'])\n    package_section = get_section(meta, 'package', lints)\n    extra_section = get_section(meta, 'extra', lints)\n    recipe_dirname = os.path.basename(recipe_dir) if recipe_dir else 'recipe'\n    recipe_name = package_section.get('name', '').strip()\n    is_staged_recipes = recipe_dirname != 'recipe'\n\n    # 1: Check that the recipe does not exist in conda-forge\n    if is_staged_recipes:\n        cf = gh.get_user(os.getenv('GH_ORG', 'conda-forge'))\n        try:\n            cf.get_repo('{}-feedstock'.format(recipe_name))\n            feedstock_exists = True\n        except github.UnknownObjectException as e:\n            feedstock_exists = False\n\n        if feedstock_exists:\n            lints.append('Feedstock with the same name exists in conda-forge')\n\n    # 2: Check that the recipe maintainers exists:\n    maintainers = extra_section.get('recipe-maintainers', [])\n    for maintainer in maintainers:\n        try:\n            gh.get_user(maintainer)\n        except github.UnknownObjectException as e:\n            lints.append('Recipe maintainer \"{}\" does not exist'.format(maintainer))\n\n\ndef selector_lines(lines):\n    # Using the same pattern defined in conda-build (metadata.py),\n    # we identify selectors.\n    sel_pat = re.compile(r'(.+?)\\s*(#.*)?\\[(.+)\\](?(2).*)$')\n\n    for line in lines:\n        line = line.rstrip()\n        if line.lstrip().startswith('#'):\n            # Don't bother with comment only lines\n            continue\n        m = sel_pat.match(line)\n        if m:\n            m.group(3)\n            yield line\n\n\ndef main(recipe_dir, conda_forge=False):\n    recipe_dir = os.path.abspath(recipe_dir)\n    recipe_meta = os.path.join(recipe_dir, 'meta.yaml')\n    if not os.path.exists(recipe_dir):\n        raise IOError('Feedstock has no recipe/meta.yaml.')\n\n    env = jinja2.Environment(undefined=NullUndefined)\n\n    with io.open(recipe_meta, 'rt') as fh:\n        content = env.from_string(''.join(fh)).render(os=os)\n        meta = ruamel.yaml.load(content, ruamel.yaml.RoundTripLoader)\n    results = lintify(meta, recipe_dir, conda_forge)\n    return results\n", "path": "conda_smithy/lint_recipe.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\nfrom __future__ import unicode_literals\n\nimport io\nimport itertools\nimport os\nimport re\nimport github\n\nimport jinja2\nimport ruamel.yaml\n\nfrom conda_build.metadata import ensure_valid_license_family\n\n\nEXPECTED_SECTION_ORDER = ['package', 'source', 'build', 'requirements',\n                          'test', 'app', 'about', 'extra']\n\nREQUIREMENTS_ORDER = ['build', 'run']\n\nTEST_KEYS = {'imports', 'commands'}\n\n\nclass NullUndefined(jinja2.Undefined):\n    def __unicode__(self):\n        return self._undefined_name\n\n    def __getattr__(self, name):\n        return '{}.{}'.format(self, name)\n\n    def __getitem__(self, name):\n        return '{}[\"{}\"]'.format(self, name)\n\n\ndef get_section(parent, name, lints):\n    section = parent.get(name, {})\n    if not isinstance(section, dict):\n        lints.append('The \"{}\" section was expected to be a dictionary, but '\n                     'got a {}.'.format(name, type(section).__name__))\n        section = {}\n    return section\n\n\ndef lintify(meta, recipe_dir=None, conda_forge=False):\n    lints = []\n    major_sections = list(meta.keys())\n\n    # If the recipe_dir exists (no guarantee within this function) , we can\n    # find the meta.yaml within it.\n    meta_fname = os.path.join(recipe_dir or '', 'meta.yaml')\n\n    source_section = get_section(meta, 'source', lints)\n    build_section = get_section(meta, 'build', lints)\n    requirements_section = get_section(meta, 'requirements', lints)\n    test_section = get_section(meta, 'test', lints)\n    about_section = get_section(meta, 'about', lints)\n    extra_section = get_section(meta, 'extra', lints)\n    package_section = get_section(meta, 'package', lints)\n\n    # 1: Top level meta.yaml keys should have a specific order.\n    section_order_sorted = sorted(major_sections,\n                                  key=EXPECTED_SECTION_ORDER.index)\n    if major_sections != section_order_sorted:\n        section_order_sorted_str = map(lambda s: \"'%s'\" % s,\n                                       section_order_sorted)\n        section_order_sorted_str = \", \".join(section_order_sorted_str)\n        section_order_sorted_str = \"[\" + section_order_sorted_str + \"]\"\n        lints.append('The top level meta keys are in an unexpected order. '\n                     'Expecting {}.'.format(section_order_sorted_str))\n\n    # 2: The about section should have a home, license and summary.\n    for about_item in ['home', 'license', 'summary']:\n        # if the section doesn't exist, or is just empty, lint it.\n        if not about_section.get(about_item, ''):\n            lints.append('The {} item is expected in the about section.'\n                         ''.format(about_item))\n\n    # 3a: The recipe should have some maintainers.\n    if not extra_section.get('recipe-maintainers', []):\n        lints.append('The recipe could do with some maintainers listed in '\n                     'the `extra/recipe-maintainers` section.')\n\n    # 3b: Maintainers should be a list\n    if not isinstance(extra_section.get('recipe-maintainers', []), list):\n        lints.append('Recipe maintainers should be a json list.')\n\n    # 4: The recipe should have some tests.\n    if not any(key in TEST_KEYS for key in test_section):\n        test_files = ['run_test.py', 'run_test.sh', 'run_test.bat',\n                      'run_test.pl']\n        a_test_file_exists = (recipe_dir is not None and\n                              any(os.path.exists(os.path.join(recipe_dir,\n                                                              test_file))\n                                  for test_file in test_files))\n        if not a_test_file_exists:\n            lints.append('The recipe must have some tests.')\n\n    # 5: License cannot be 'unknown.'\n    license = about_section.get('license', '').lower()\n    if 'unknown' == license.strip():\n        lints.append('The recipe license cannot be unknown.')\n\n    # 6: Selectors should be in a tidy form.\n    if recipe_dir is not None and os.path.exists(meta_fname):\n        bad_selectors = []\n        # Good selectors look like \".*\\s\\s#\\s[...]\"\n        good_selectors_pat = re.compile(r'(.+?)\\s{2,}#\\s\\[(.+)\\](?(2).*)$')\n        with io.open(meta_fname, 'rt') as fh:\n            for selector_line in selector_lines(fh):\n                if not good_selectors_pat.match(selector_line):\n                    bad_selectors.append(selector_line)\n        if bad_selectors:\n            lints.append('Selectors are suggested to take a '\n                         '``<two spaces>#<one space>[<expression>]`` form.')\n\n    # 7: The build section should have a build number.\n    if build_section.get('number', None) is None:\n        lints.append('The recipe must have a `build/number` section.')\n\n    # 8: The build section should be before the run section in requirements.\n    requirements_order_sorted = sorted(requirements_section,\n                                       key=REQUIREMENTS_ORDER.index)\n    if list(requirements_section.keys()) != requirements_order_sorted:\n        lints.append('The `requirements/build` section should be defined '\n                     'before the `requirements/run` section.')\n\n    # 9: Files downloaded should have a hash.\n    if ('url' in source_section and\n            not ({'sha1', 'sha256', 'md5'} & set(source_section.keys()))):\n        lints.append('When defining a source/url please add a sha256, sha1 '\n                     'or md5 checksum (sha256 preferably).')\n\n    # 10: License should not include the word 'license'.\n    license = about_section.get('license', '').lower()\n    if 'license' in license.lower():\n        lints.append('The recipe `license` should not include the word '\n                     '\"License\".')\n\n    # 11: There should be one empty line at the end of the file.\n    if recipe_dir is not None and os.path.exists(meta_fname):\n        with io.open(meta_fname, 'r') as f:\n            lines = f.read().split('\\n')\n        # Count the number of empty lines from the end of the file\n        empty_lines = itertools.takewhile(lambda x: x == '', reversed(lines))\n        end_empty_lines_count = len(list(empty_lines))\n        if end_empty_lines_count > 1:\n            lints.append('There are {} too many lines.  '\n                         'There should be one empty line at the end of the '\n                         'file.'.format(end_empty_lines_count - 1))\n        elif end_empty_lines_count < 1:\n            lints.append('There are too few lines.  There should be one empty '\n                         'line at the end of the file.')\n\n    # 12: License family must be valid (conda-build checks for that)\n    try:\n        ensure_valid_license_family(meta)\n    except RuntimeError as e:\n        lints.append(str(e))\n\n    # 13: Check that the recipe name is valid\n    recipe_name = package_section.get('name', '').strip()\n    if re.match('^[a-z0-9_\\-.]+$', recipe_name) is None:\n        lints.append('Recipe name has invalid characters. only lowercase alpha, numeric, '\n                     'underscores, hyphens and dots allowed')\n\n    # 14: Run conda-forge specific lints\n    if conda_forge:\n        run_conda_forge_lints(meta, recipe_dir, lints)\n\n    # 15: Check if we are using legacy patterns\n    build_reqs = requirements_section.get('build', None)\n    if build_reqs and ('numpy x.x' in build_reqs):\n        lints.append('Using pinned numpy packages is a deprecated pattern.  Consider '\n                     'using the method outlined '\n                     '[here](https://conda-forge.org/docs/meta.html#building-against-numpy).')\n\n    return lints\n\n\ndef run_conda_forge_lints(meta, recipe_dir, lints):\n    gh = github.Github(os.environ['GH_TOKEN'])\n    package_section = get_section(meta, 'package', lints)\n    extra_section = get_section(meta, 'extra', lints)\n    recipe_dirname = os.path.basename(recipe_dir) if recipe_dir else 'recipe'\n    recipe_name = package_section.get('name', '').strip()\n    is_staged_recipes = recipe_dirname != 'recipe'\n\n    # 1: Check that the recipe does not exist in conda-forge\n    if is_staged_recipes:\n        cf = gh.get_user(os.getenv('GH_ORG', 'conda-forge'))\n        try:\n            cf.get_repo('{}-feedstock'.format(recipe_name))\n            feedstock_exists = True\n        except github.UnknownObjectException as e:\n            feedstock_exists = False\n\n        if feedstock_exists:\n            lints.append('Feedstock with the same name exists in conda-forge')\n\n    # 2: Check that the recipe maintainers exists:\n    maintainers = extra_section.get('recipe-maintainers', [])\n    for maintainer in maintainers:\n        try:\n            gh.get_user(maintainer)\n        except github.UnknownObjectException as e:\n            lints.append('Recipe maintainer \"{}\" does not exist'.format(maintainer))\n\n\ndef selector_lines(lines):\n    # Using the same pattern defined in conda-build (metadata.py),\n    # we identify selectors.\n    sel_pat = re.compile(r'(.+?)\\s*(#.*)?\\[([^\\[\\]]+)\\](?(2).*)$')\n\n    for line in lines:\n        line = line.rstrip()\n        if line.lstrip().startswith('#'):\n            # Don't bother with comment only lines\n            continue\n        m = sel_pat.match(line)\n        if m:\n            m.group(3)\n            yield line\n\n\ndef main(recipe_dir, conda_forge=False):\n    recipe_dir = os.path.abspath(recipe_dir)\n    recipe_meta = os.path.join(recipe_dir, 'meta.yaml')\n    if not os.path.exists(recipe_dir):\n        raise IOError('Feedstock has no recipe/meta.yaml.')\n\n    env = jinja2.Environment(undefined=NullUndefined)\n\n    with io.open(recipe_meta, 'rt') as fh:\n        content = env.from_string(''.join(fh)).render(os=os)\n        meta = ruamel.yaml.load(content, ruamel.yaml.RoundTripLoader)\n    results = lintify(meta, recipe_dir, conda_forge)\n    return results\n", "path": "conda_smithy/lint_recipe.py"}]}