-
-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read the existing MANIFEST.in file for files to ignore. #19
Changes from 4 commits
eee444f
9523976
0903222
76e42db
f8a593f
4fc8b69
6560439
3a5afc6
552e886
d4ef7d5
c68abc6
77fe8dc
cc6f20a
b62f61a
f83b42d
7538143
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -350,6 +350,12 @@ def add_directories(names): | |
'*.mo', | ||
] | ||
|
||
IGNORE_REGEXPS = [ | ||
# Regular expressions for filename to ignore. This is useful for | ||
# filename patterns where the '*' part must not search in | ||
# directories. | ||
] | ||
|
||
WARN_ABOUT_FILES_IN_VCS = [ | ||
# generated files should not be committed into the VCS | ||
'PKG-INFO', | ||
|
@@ -394,15 +400,77 @@ def read_config(): | |
IGNORE.extend(p for p in patterns if p) | ||
|
||
|
||
def read_manifest(): | ||
"""Read existing configuration from MANIFEST.in. | ||
|
||
We use that to ignore anything the MANIFEST.in ignores. | ||
""" | ||
# XXX modifies global state, which is kind of evil | ||
if not os.path.isfile('MANIFEST.in'): | ||
return | ||
contents = open('MANIFEST.in').read() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use the |
||
ignore, ignore_regexps = _get_ignore_from_manifest(contents) | ||
IGNORE.extend(ignore) | ||
IGNORE_REGEXPS.extend(ignore_regexps) | ||
|
||
|
||
def _get_ignore_from_manifest(contents): | ||
"""Gather the various ignore patterns from MANIFEST.in. | ||
|
||
'contents' should be a string, which may contain newlines. | ||
|
||
Returns a list of standard ignore patterns and a list of regular | ||
expressions to ignore. | ||
""" | ||
ignore = [] | ||
ignore_regexps = [] | ||
for line in contents.splitlines(): | ||
if line.startswith('exclude '): | ||
# An exclude of 'dirname/*css' can match 'dirname/foo.css' | ||
# but not 'dirname/subdir/bar.css'. We need a regular | ||
# expression for that. | ||
rest = line[len('exclude '):].strip().split() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
for pat in rest: | ||
if '*' in pat: | ||
pat = pat.replace('*', '[^/]*') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes |
||
ignore_regexps.append(pat) | ||
else: | ||
# No need for special handling. | ||
ignore.append(pat) | ||
elif line.startswith('global-exclude '): | ||
rest = line[len('global-exclude '):].strip().split() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The When I mentioned it last time, I didn't highlight every occurrence, since I didn't want to spam you with too many Github emails. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I should have checked. |
||
ignore.extend(rest) | ||
elif line.startswith('recursive-exclude '): | ||
rest = line[len('recursive-exclude '):].strip() | ||
dirname, patterns = rest.split(' ', 1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps I'm assuming distutils/setuptools accepts multiple spaces here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, I see you already have a test that tests So, no bug here, but I'd prefer Actually wait, if tabs are allowed, then there is a bug here, which would be fixed by using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, indeed, tabs are accepted too. I have refactored this part slightly to make it clearer, by first trying |
||
for pattern in patterns.strip().split(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
ignore.append(dirname + os.path.sep + pattern) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure this is right. If I do
it will add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd expect a pattern here, but it indeed works for a simple name too: |
||
elif line.startswith('prune '): | ||
dirname = line[len('prune '):].strip() | ||
ignore.append(dirname) | ||
ignore.append(dirname + os.path.sep + '*') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this work correctly if I think it will, but I should double-check before merging/releasing. A unit test would perhaps be good. |
||
return ignore, ignore_regexps | ||
|
||
|
||
def file_matches(filename, patterns): | ||
"""Does this filename match any of the patterns?""" | ||
return any(fnmatch.fnmatch(filename, pat) for pat in patterns) | ||
|
||
|
||
def file_matches_regexps(filename, patterns): | ||
"""Does this filename match any of the regular expressions?""" | ||
return any(re.match(pat, filename) for pat in patterns) | ||
|
||
|
||
def strip_sdist_extras(filelist): | ||
"""Strip generated files that are only present in source distributions.""" | ||
"""Strip generated files that are only present in source distributions. | ||
|
||
We also strip files that are ignored for other reasons, like | ||
command line arguments, setup.cfg rules or MANIFEST.in rules. | ||
""" | ||
return [name for name in filelist | ||
if not file_matches(name, IGNORE)] | ||
if not file_matches(name, IGNORE) | ||
and not file_matches_regexps(name, IGNORE_REGEXPS)] | ||
|
||
|
||
def find_bad_ideas(filelist): | ||
|
@@ -451,6 +519,7 @@ def check_manifest(source_tree='.', create=False, update=False, | |
if not is_package(source_tree): | ||
raise Failure('This is not a Python project (no setup.py).') | ||
read_config() | ||
read_manifest() | ||
info_begin("listing source files under version control") | ||
all_source_files = sorted(get_vcs_files()) | ||
source_files = strip_sdist_extras(all_source_files) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,6 +82,69 @@ def test_strip_sdist_extras(self): | |
] | ||
self.assertEqual(strip_sdist_extras(filelist), expected) | ||
|
||
def test_strip_sdist_extras_with_manifest(self): | ||
import check_manifest | ||
from check_manifest import strip_sdist_extras | ||
from check_manifest import _get_ignore_from_manifest as parse | ||
orig_ignore = check_manifest.IGNORE | ||
orig_ignore_regexps = check_manifest.IGNORE_REGEXPS | ||
manifest_in = """ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You used dedent() in the other place (thank you), but forgot this one :) |
||
graft src | ||
exclude *.cfg | ||
global-exclude *.mo | ||
prune src/dump | ||
recursive-exclude src/zope *.sh | ||
""" | ||
filelist = [ | ||
'.gitignore', | ||
'setup.py', | ||
'setup.cfg', | ||
'MANIFEST.in', | ||
'README.txt', | ||
'src', | ||
'src/helper.sh', | ||
'src/dump', | ||
'src/dump/__init__.py', | ||
'src/zope', | ||
'src/zope/__init__.py', | ||
'src/zope/zopehelper.sh', | ||
'src/zope/foo', | ||
'src/zope/foo/__init__.py', | ||
'src/zope/foo/language.po', | ||
'src/zope/foo/language.mo', | ||
'src/zope/foo/config.cfg', | ||
'src/zope/foo/foohelper.sh', | ||
'src/zope.foo.egg-info', | ||
'src/zope.foo.egg-info/SOURCES.txt', | ||
] | ||
expected = [ | ||
'setup.py', | ||
'MANIFEST.in', | ||
'README.txt', | ||
'src', | ||
'src/helper.sh', | ||
'src/zope', | ||
'src/zope/__init__.py', | ||
'src/zope/foo', | ||
'src/zope/foo/__init__.py', | ||
'src/zope/foo/language.po', | ||
'src/zope/foo/config.cfg', | ||
] | ||
|
||
# This will change the definitions. | ||
try: | ||
# This is normally done in read_manifest: | ||
ignore, ignore_regexps = parse(manifest_in) | ||
check_manifest.IGNORE.extend(ignore) | ||
check_manifest.IGNORE_REGEXPS.extend(ignore_regexps) | ||
# Filter the file list. | ||
result = strip_sdist_extras(filelist) | ||
finally: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After I merge this, please hit me with a fish until I refactor check-manifest to avoid mutable global state. OOP was invented in the last century, after all. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will see if I can find a large trout. :-) |
||
# Restore the original definitions | ||
check_manifest.IGNORE = orig_ignore | ||
check_manifest.IGNORE_REGEXPS = orig_ignore_regexps | ||
self.assertEqual(result, expected) | ||
|
||
def test_find_bad_ideas(self): | ||
from check_manifest import find_bad_ideas | ||
filelist = [ | ||
|
@@ -137,6 +200,70 @@ def test_find_suggestions_generic_fallback_rules(self): | |
self.assertEqual(find_suggestions(['src/id-lang.map']), | ||
(['recursive-include src *.map'], [])) | ||
|
||
def test_get_ignore_from_manifest(self): | ||
from check_manifest import _get_ignore_from_manifest as parse | ||
# The return value is a tuple with two lists: | ||
# ([<list of filename ignores>], [<list of regular expressions>]) | ||
self.assertEqual(parse(''), | ||
([], [])) | ||
self.assertEqual(parse(' \n '), | ||
([], [])) | ||
self.assertEqual(parse('exclude *.cfg'), | ||
([], ['[^/]*.cfg'])) | ||
self.assertEqual(parse('#exclude *.cfg'), | ||
([], [])) | ||
self.assertEqual(parse('exclude *.cfg'), | ||
([], ['[^/]*.cfg'])) | ||
self.assertEqual(parse('exclude *.cfg foo.* bar.txt'), | ||
(['bar.txt'], ['[^/]*.cfg', 'foo.[^/]*'])) | ||
self.assertEqual(parse('exclude some/directory/*.cfg'), | ||
([], ['some/directory/[^/]*.cfg'])) | ||
self.assertEqual(parse('include *.cfg'), | ||
([], [])) | ||
self.assertEqual(parse('global-exclude *.pyc'), | ||
(['*.pyc'], [])) | ||
self.assertEqual(parse('global-exclude *.pyc *.sh'), | ||
(['*.pyc', '*.sh'], [])) | ||
self.assertEqual(parse('recursive-exclude dir *.pyc'), | ||
(['dir/*.pyc'], [])) | ||
self.assertEqual(parse('recursive-exclude dir *.pyc *.sh'), | ||
(['dir/*.pyc', 'dir/*.sh'], [])) | ||
self.assertEqual(parse('prune dir'), | ||
(['dir', 'dir/*'], [])) | ||
text = """ | ||
#exclude *.01 | ||
exclude *.02 | ||
exclude *.03 04.* bar.txt | ||
exclude *.05 | ||
exclude some/directory/*.cfg | ||
global-exclude *.10 *.11 | ||
global-exclude *.12 | ||
include *.20 | ||
prune 30 | ||
recursive-exclude 40 *.41 | ||
recursive-exclude 42 *.43 44.* | ||
""" | ||
self.assertEqual( | ||
parse(text), | ||
([ | ||
'bar.txt', | ||
'*.10', | ||
'*.11', | ||
'*.12', | ||
'30', | ||
'30/*', | ||
'40/*.41', | ||
'42/*.43', | ||
'42/44.*', | ||
], | ||
[ | ||
'[^/]*.02', | ||
'[^/]*.03', | ||
'04.[^/]*', | ||
'[^/]*.05', | ||
'some/directory/[^/]*.cfg', | ||
])) | ||
|
||
|
||
class VCSMixin(object): | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The closing
]
should be unindented.I believe pep8 warns about this, but even if it doesn't, let's keep it consistent with the rest of the file.