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
42
msg = "The levels argument must be an integer."
43
raise errors.BzrCommandError(msg)
46
class GrepOptions(object):
47
"""Container to pass around grep options.
49
This class is used as a container to pass around user option and
50
some other params (like outf) to processing functions. This makes
51
it easier to add more options as grep evolves.
66
files_with_matches = False
67
files_without_match = False
82
class cmd_grep(Command):
83
"""Print lines matching PATTERN for specified files and revisions.
85
This command searches the specified files and revisions for a given
86
pattern. The pattern is specified as a Python regular expressions[1].
88
If the file name is not specified, the revisions starting with the
89
current directory are searched recursively. If the revision number is
90
not specified, the working copy is searched. To search the last committed
91
revision, use the '-r -1' or '-r last:1' option.
93
Unversioned files are not searched unless explicitly specified on the
94
command line. Unversioned directores are not searched.
96
When searching a pattern, the output is shown in the 'filepath:string'
97
format. If a revision is explicitly searched, the output is shown as
98
'filepath~N:string', where N is the revision number.
100
--include and --exclude options can be used to search only (or exclude
101
from search) files with base name matches the specified Unix style GLOB
102
pattern. The GLOB pattern an use *, ?, and [...] as wildcards, and \\
103
to quote wildcard or backslash character literally. Note that the glob
104
pattern is not a regular expression.
106
[1] http://docs.python.org/library/re.html#regular-expression-syntax
109
encoding_type = 'replace'
110
takes_args = ['pattern', 'path*']
114
Option('color', type=text_type, argname='when',
115
help='Show match in color. WHEN is never, always or auto.'),
116
Option('diff', short_name='p',
117
help='Grep for pattern in changeset for each revision.'),
118
ListOption('exclude', type=text_type, argname='glob', short_name='X',
119
help="Skip files whose base name matches GLOB."),
120
ListOption('include', type=text_type, argname='glob', short_name='I',
121
help="Search only files whose base name matches GLOB."),
122
Option('files-with-matches', short_name='l',
123
help='Print only the name of each input file in '
124
'which PATTERN is found.'),
125
Option('files-without-match', short_name='L',
126
help='Print only the name of each input file in '
127
'which PATTERN is not found.'),
128
Option('fixed-string', short_name='F',
129
help='Interpret PATTERN is a single fixed string (not regex).'),
131
help='Search for pattern starting from the root of the branch. '
132
'(implies --recursive)'),
133
Option('ignore-case', short_name='i',
134
help='ignore case distinctions while matching.'),
136
help='Number of levels to display - 0 for all, 1 for collapsed '
140
Option('line-number', short_name='n',
141
help='show 1-based line number.'),
142
Option('no-recursive',
143
help="Don't recurse into subdirectories. (default is --recursive)"),
144
Option('null', short_name='Z',
145
help='Write an ASCII NUL (\\0) separator '
146
'between output lines rather than a newline.'),
150
def run(self, verbose=False, ignore_case=False, no_recursive=False,
151
from_root=False, null=False, levels=None, line_number=False,
152
path_list=None, revision=None, pattern=None, include=None,
153
exclude=None, fixed_string=False, files_with_matches=False,
154
files_without_match=False, color=None, diff=False):
155
from breezy import _termcolor
158
if path_list is None:
162
raise errors.BzrCommandError(
163
'cannot specify both --from-root and PATH.')
165
if files_with_matches and files_without_match:
166
raise errors.BzrCommandError('cannot specify both '
167
'-l/--files-with-matches and -L/--files-without-matches.')
169
global_config = GlobalConfig()
172
color = global_config.get_user_option('grep_color')
177
if color not in ['always', 'never', 'auto']:
178
raise errors.BzrCommandError('Valid values for --color are '
179
'"always", "never" or "auto".')
185
if revision is not None or levels == 0:
186
# print revision numbers as we may be showing multiple revisions
193
if not ignore_case and grep.is_fixed_string(pattern):
194
# if the pattern isalnum, implicitly use to -F for faster grep
196
elif ignore_case and fixed_string:
197
# GZ 2010-06-02: Fall back to regexp rather than lowercasing
198
# pattern and text which will cause pain later
200
pattern = re.escape(pattern)
203
re_flags = re.MULTILINE
205
re_flags |= re.IGNORECASE
208
patternc = grep.compile_pattern(
209
pattern.encode(grep._user_encoding), re_flags)
211
if color == 'always':
213
elif color == 'never':
215
elif color == 'auto':
216
show_color = _termcolor.allow_color()
218
GrepOptions.verbose = verbose
219
GrepOptions.ignore_case = ignore_case
220
GrepOptions.no_recursive = no_recursive
221
GrepOptions.from_root = from_root
222
GrepOptions.null = null
223
GrepOptions.levels = levels
224
GrepOptions.line_number = line_number
225
GrepOptions.path_list = path_list
226
GrepOptions.revision = revision
227
GrepOptions.pattern = pattern
228
GrepOptions.include = include
229
GrepOptions.exclude = exclude
230
GrepOptions.fixed_string = fixed_string
231
GrepOptions.files_with_matches = files_with_matches
232
GrepOptions.files_without_match = files_without_match
233
GrepOptions.color = color
234
GrepOptions.diff = False
236
GrepOptions.eol_marker = eol_marker
237
GrepOptions.print_revno = print_revno
238
GrepOptions.patternc = patternc
239
GrepOptions.recursive = not no_recursive
240
GrepOptions.fixed_string = fixed_string
241
GrepOptions.outf = self.outf
242
GrepOptions.show_color = show_color
246
# files_with_matches, files_without_match
247
# levels(?), line_number, from_root
249
# These are silently ignored.
250
grep.grep_diff(GrepOptions)
251
elif revision is None:
252
grep.workingtree_grep(GrepOptions)
254
grep.versioned_grep(GrepOptions)