# HG changeset patch # User Steve Losh # Date 1349113298 14400 # Node ID 46cb98ff7fe08c1a70af90d4a92d427d50000d4d # Parent 48df53581f1c60e157823ad035b37a620dc13c7b Add .hgignore support and ffignore syntax: glob. diff -r 48df53581f1c -r 46cb98ff7fe0 README.markdown --- a/README.markdown Thu Sep 27 13:33:26 2012 -0400 +++ b/README.markdown Mon Oct 01 13:41:38 2012 -0400 @@ -10,8 +10,7 @@ notable exceptions: * Time filtering is unimplemented. -* VCS ignore files aren't parsed (however: VCS data directories *are* skipped, - and the `.ffignore` file *is* parsed). +* SVN ignores aren't parsed. * It's pretty slow (though pruning VCS data directories saves lots of time). Feedback is welcome, though remember that it's still a prototype, and is diff -r 48df53581f1c -r 46cb98ff7fe0 ffind --- a/ffind Thu Sep 27 13:33:26 2012 -0400 +++ b/ffind Mon Oct 01 13:41:38 2012 -0400 @@ -93,6 +93,11 @@ GITIGNORE_BLANK_RE = re.compile(r'^\s*$') GITIGNORE_NEGATE_RE = re.compile(r'^\s*!') +HGIGNORE_SYNTAX_RE = re.compile(r'^\s*syntax:\s*(glob|regexp|re)\s*$', + re.IGNORECASE) +HGIGNORE_COMMENT_RE = re.compile(r'^\s*#') +HGIGNORE_BLANK_RE = re.compile(r'^\s*$') + # Global Options -------------------------------------------------------------- # (it's a prototype, shut up) @@ -123,9 +128,33 @@ warn('could not compile regular expression "%s"' % line) return lambda s: False -def compile_glob(line): - # TODO - die('glob ignore patterns are not supported yet, sorry!') +def glob_to_re(glob): + pat = '' + + chs = list(glob) + while chs: + ch = chs.pop(0) + if ch == '\\': + pat += re.escape(chs.pop(0)) + elif ch == '?': + pat += '.' + elif ch == '*': + if chs and chs[0] == '*': + chs.pop(0) + pat += '.*' + else: + pat += '[^/]*' + elif ch == '[': + pat += '[' + ch = chs.pop(0) + while chs and ch != ']': + pat += ch + ch = chs.pop(0) + pat += ']' + else: + pat += re.escape(ch) + + return pat def compile_literal(line): l = line @@ -227,12 +256,37 @@ pat += '$' try: - regex = re.compile(pat) - return lambda s: regex.search(s) + return compile_re(pat) except: warn("could not parse gitignore pattern '%s'" % original_line) return lambda s: True +def compile_hg_glob(line): + pat = glob_to_re(line) + + # Mercurial ignore globs are quasi-rooted at directory boundaries or the + # beginning of the pattern. + pat = '(^|/)' + pat + + # Mercurial globs also have to match to the end of the pattern. + pat = pat + '$' + + try: + regex = re.compile(pat) + return lambda s: regex.search(s[2:] if s.startswith('./') else s) + except: + warn("could not parse hgignore pattern '%s'" % line) + return lambda s: True + +def compile_ff_glob(line): + pat = glob_to_re(line) + + try: + return compile_re(pat) + except: + warn("could not parse ffignore pattern '%s'" % line) + return lambda s: True + def parse_gitignore_file(path): if not os.path.isfile(path): @@ -255,6 +309,34 @@ return ignorers +def parse_hgignore_file(path): + if not os.path.isfile(path): + return [] + + syntax = IGNORE_SYNTAX_REGEX + ignorers = [] + with open(path) as f: + for line in f.readlines(): + line = line.rstrip('\n') + if HGIGNORE_BLANK_RE.match(line): + continue + elif HGIGNORE_COMMENT_RE.match(line): + continue + elif HGIGNORE_SYNTAX_RE.match(line): + s = HGIGNORE_SYNTAX_RE.match(line).groups()[0].lower() + if s == 'glob': + syntax = IGNORE_SYNTAX_GLOB + elif s in ['re', 'regexp']: + syntax = IGNORE_SYNTAX_REGEX + else: + # This line is a pattern. + if syntax == IGNORE_SYNTAX_REGEX: + ignorers.append(compile_re(line)) + elif syntax == IGNORE_SYNTAX_GLOB: + ignorers.append(compile_hg_glob(line)) + + return ignorers + def parse_ffignore_file(path): if not os.path.isfile(path): return [] @@ -283,7 +365,7 @@ elif syntax == IGNORE_SYNTAX_REGEX: ignorers.append(compile_re(line)) elif syntax == IGNORE_SYNTAX_GLOB: - ignorers.append(compile_glob(line)) + ignorers.append(compile_ff_glob(line)) return ignorers @@ -295,6 +377,8 @@ ignorers.extend(parse_ffignore_file(target)) elif filename == '.gitignore': ignorers.extend(parse_gitignore_file(target)) + elif filename == '.hgignore': + ignorers.extend(parse_hgignore_file(target)) return ignorers @@ -709,7 +793,7 @@ # Ignore files if options.ignore_mode == IGNORE_MODE_RESTRICTED: - options.ignore_files = ['.ffignore', '.gitignore'] + options.ignore_files = ['.ffignore', '.gitignore', '.hgignore'] options.ignore_vcs_dirs = True elif options.ignore_mode == IGNORE_MODE_SEMI: options.ignore_files = ['.ffignore']