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
155
from breezy.plugins.grep import (
159
if path_list is None:
163
raise errors.BzrCommandError('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 != 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(pattern, re_flags)
210
if color == 'always':
212
elif color == 'never':
214
elif color == 'auto':
215
show_color = _termcolor.allow_color()
217
GrepOptions.verbose = verbose
218
GrepOptions.ignore_case = ignore_case
219
GrepOptions.no_recursive = no_recursive
220
GrepOptions.from_root = from_root
221
GrepOptions.null = null
222
GrepOptions.levels = levels
223
GrepOptions.line_number = line_number
224
GrepOptions.path_list = path_list
225
GrepOptions.revision = revision
226
GrepOptions.pattern = pattern
227
GrepOptions.include = include
228
GrepOptions.exclude = exclude
229
GrepOptions.fixed_string = fixed_string
230
GrepOptions.files_with_matches = files_with_matches
231
GrepOptions.files_without_match = files_without_match
232
GrepOptions.color = color
233
GrepOptions.diff = False
235
GrepOptions.eol_marker = eol_marker
236
GrepOptions.print_revno = print_revno
237
GrepOptions.patternc = patternc
238
GrepOptions.recursive = not no_recursive
239
GrepOptions.fixed_string = fixed_string
240
GrepOptions.outf = self.outf
241
GrepOptions.show_color = show_color
245
# files_with_matches, files_without_match
246
# levels(?), line_number, from_root
248
# These are silently ignored.
249
grep.grep_diff(GrepOptions)
250
elif revision is None:
251
grep.workingtree_grep(GrepOptions)
253
grep.versioned_grep(GrepOptions)