1
# Copyright (C) 2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Print lines matching PATTERN for specified files and revisions."""
19
from __future__ import absolute_import
21
from ... import errors
22
from ...commands import Command, display_command
23
from ...option import Option, ListOption
24
from ...config import GlobalConfig
26
from ...sixish import (
30
# FIXME: _parse_levels should be shared with breezy.builtins. this is a copy
32
# "IllegalUseOfScopeReplacer: ScopeReplacer object '_parse_levels' was used
33
# incorrectly: Object already cleaned up, did you assign it to another
40
msg = "The levels argument must be an integer."
41
raise errors.BzrCommandError(msg)
44
class GrepOptions(object):
45
"""Container to pass around grep options.
47
This class is used as a container to pass around user option and
48
some other params (like outf) to processing functions. This makes
49
it easier to add more options as grep evolves.
64
files_with_matches = False
65
files_without_match = False
80
class cmd_grep(Command):
81
"""Print lines matching PATTERN for specified files and revisions.
83
This command searches the specified files and revisions for a given
84
pattern. The pattern is specified as a Python regular expressions[1].
86
If the file name is not specified, the revisions starting with the
87
current directory are searched recursively. If the revision number is
88
not specified, the working copy is searched. To search the last committed
89
revision, use the '-r -1' or '-r last:1' option.
91
Unversioned files are not searched unless explicitly specified on the
92
command line. Unversioned directores are not searched.
94
When searching a pattern, the output is shown in the 'filepath:string'
95
format. If a revision is explicitly searched, the output is shown as
96
'filepath~N:string', where N is the revision number.
98
--include and --exclude options can be used to search only (or exclude
99
from search) files with base name matches the specified Unix style GLOB
100
pattern. The GLOB pattern an use *, ?, and [...] as wildcards, and \\
101
to quote wildcard or backslash character literally. Note that the glob
102
pattern is not a regular expression.
104
[1] http://docs.python.org/library/re.html#regular-expression-syntax
107
encoding_type = 'replace'
108
takes_args = ['pattern', 'path*']
112
Option('color', type=text_type, argname='when',
113
help='Show match in color. WHEN is never, always or auto.'),
114
Option('diff', short_name='p',
115
help='Grep for pattern in changeset for each revision.'),
116
ListOption('exclude', type=text_type, argname='glob', short_name='X',
117
help="Skip files whose base name matches GLOB."),
118
ListOption('include', type=text_type, argname='glob', short_name='I',
119
help="Search only files whose base name matches GLOB."),
120
Option('files-with-matches', short_name='l',
121
help='Print only the name of each input file in '
122
'which PATTERN is found.'),
123
Option('files-without-match', short_name='L',
124
help='Print only the name of each input file in '
125
'which PATTERN is not found.'),
126
Option('fixed-string', short_name='F',
127
help='Interpret PATTERN is a single fixed string (not regex).'),
129
help='Search for pattern starting from the root of the branch. '
130
'(implies --recursive)'),
131
Option('ignore-case', short_name='i',
132
help='ignore case distinctions while matching.'),
134
help='Number of levels to display - 0 for all, 1 for collapsed '
138
Option('line-number', short_name='n',
139
help='show 1-based line number.'),
140
Option('no-recursive',
141
help="Don't recurse into subdirectories. (default is --recursive)"),
142
Option('null', short_name='Z',
143
help='Write an ASCII NUL (\\0) separator '
144
'between output lines rather than a newline.'),
149
def run(self, verbose=False, ignore_case=False, no_recursive=False,
150
from_root=False, null=False, levels=None, line_number=False,
151
path_list=None, revision=None, pattern=None, include=None,
152
exclude=None, fixed_string=False, files_with_matches=False,
153
files_without_match=False, color=None, diff=False):
154
from breezy import _termcolor
157
if path_list is None:
161
raise errors.BzrCommandError('cannot specify both --from-root and PATH.')
163
if files_with_matches and files_without_match:
164
raise errors.BzrCommandError('cannot specify both '
165
'-l/--files-with-matches and -L/--files-without-matches.')
167
global_config = GlobalConfig()
170
color = global_config.get_user_option('grep_color')
175
if color not in ['always', 'never', 'auto']:
176
raise errors.BzrCommandError('Valid values for --color are '
177
'"always", "never" or "auto".')
183
if revision != None or levels == 0:
184
# print revision numbers as we may be showing multiple revisions
191
if not ignore_case and grep.is_fixed_string(pattern):
192
# if the pattern isalnum, implicitly use to -F for faster grep
194
elif ignore_case and fixed_string:
195
# GZ 2010-06-02: Fall back to regexp rather than lowercasing
196
# pattern and text which will cause pain later
198
pattern = re.escape(pattern)
201
re_flags = re.MULTILINE
203
re_flags |= re.IGNORECASE
206
patternc = grep.compile_pattern(pattern.encode(grep._user_encoding), re_flags)
208
if color == 'always':
210
elif color == 'never':
212
elif color == 'auto':
213
show_color = _termcolor.allow_color()
215
GrepOptions.verbose = verbose
216
GrepOptions.ignore_case = ignore_case
217
GrepOptions.no_recursive = no_recursive
218
GrepOptions.from_root = from_root
219
GrepOptions.null = null
220
GrepOptions.levels = levels
221
GrepOptions.line_number = line_number
222
GrepOptions.path_list = path_list
223
GrepOptions.revision = revision
224
GrepOptions.pattern = pattern
225
GrepOptions.include = include
226
GrepOptions.exclude = exclude
227
GrepOptions.fixed_string = fixed_string
228
GrepOptions.files_with_matches = files_with_matches
229
GrepOptions.files_without_match = files_without_match
230
GrepOptions.color = color
231
GrepOptions.diff = False
233
GrepOptions.eol_marker = eol_marker
234
GrepOptions.print_revno = print_revno
235
GrepOptions.patternc = patternc
236
GrepOptions.recursive = not no_recursive
237
GrepOptions.fixed_string = fixed_string
238
GrepOptions.outf = self.outf
239
GrepOptions.show_color = show_color
243
# files_with_matches, files_without_match
244
# levels(?), line_number, from_root
246
# These are silently ignored.
247
grep.grep_diff(GrepOptions)
248
elif revision is None:
249
grep.workingtree_grep(GrepOptions)
251
grep.versioned_grep(GrepOptions)