diff options
Diffstat (limited to '')
-rw-r--r-- | weechat/python/grep.py | 1738 |
1 files changed, 1738 insertions, 0 deletions
diff --git a/weechat/python/grep.py b/weechat/python/grep.py new file mode 100644 index 0000000..30bf2a6 --- /dev/null +++ b/weechat/python/grep.py @@ -0,0 +1,1738 @@ +# -*- coding: utf-8 -*- +### +# Copyright (c) 2009-2011 by Elián Hanisch <lambdae2@gmail.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +### + +### +# Search in Weechat buffers and logs (for Weechat 0.3.*) +# +# Inspired by xt's grep.py +# Originally I just wanted to add some fixes in grep.py, but then +# I got carried away and rewrote everything, so new script. +# +# Commands: +# * /grep +# Search in logs or buffers, see /help grep +# * /logs: +# Lists logs in ~/.weechat/logs, see /help logs +# +# Settings: +# * plugins.var.python.grep.clear_buffer: +# Clear the results buffer before each search. Valid values: on, off +# +# * plugins.var.python.grep.go_to_buffer: +# Automatically go to grep buffer when search is over. Valid values: on, off +# +# * plugins.var.python.grep.log_filter: +# Coma separated list of patterns that grep will use for exclude logs, e.g. +# if you use '*server/*' any log in the 'server' folder will be excluded +# when using the command '/grep log' +# +# * plugins.var.python.grep.show_summary: +# Shows summary for each log. Valid values: on, off +# +# * plugins.var.python.grep.max_lines: +# Grep will only print the last matched lines that don't surpass the value defined here. +# +# * plugins.var.python.grep.size_limit: +# Size limit in KiB, is used for decide whenever grepping should run in background or not. If +# the logs to grep have a total size bigger than this value then grep run as a new process. +# It can be used for force or disable background process, using '0' forces to always grep in +# background, while using '' (empty string) will disable it. +# +# * plugins.var.python.grep.default_tail_head: +# Config option for define default number of lines returned when using --head or --tail options. +# Can be overriden in the command with --number option. +# +# +# TODO: +# * try to figure out why hook_process chokes in long outputs (using a tempfile as a +# workaround now) +# * possibly add option for defining time intervals +# +# +# History: +# +# 2016-06-23, mickael9 +# version 0.7.7: fix get_home function +# +# 2015-11-26 +# version 0.7.6: fix a typo +# +# 2015-01-31, Nicd- +# version 0.7.5: +# '~' is now expaned to the home directory in the log file path so +# paths like '~/logs/' should work. +# +# 2015-01-14, nils_2 +# version 0.7.4: make q work to quit grep buffer (requested by: gb) +# +# 2014-03-29, Felix Eckhofer <felix@tribut.de> +# version 0.7.3: fix typo +# +# 2011-01-09 +# version 0.7.2: bug fixes +# +# 2010-11-15 +# version 0.7.1: +# * use TempFile so temporal files are guaranteed to be deleted. +# * enable Archlinux workaround. +# +# 2010-10-26 +# version 0.7: +# * added templates. +# * using --only-match shows only unique strings. +# * fixed bug that inverted -B -A switches when used with -t +# +# 2010-10-14 +# version 0.6.8: by xt <xt@bash.no> +# * supress highlights when printing in grep buffer +# +# 2010-10-06 +# version 0.6.7: by xt <xt@bash.no> +# * better temporary file: +# use tempfile.mkstemp. to create a temp file in log dir, +# makes it safer with regards to write permission and multi user +# +# 2010-04-08 +# version 0.6.6: bug fixes +# * use WEECHAT_LIST_POS_END in log file completion, makes completion faster +# * disable bytecode if using python 2.6 +# * use single quotes in command string +# * fix bug that could change buffer's title when using /grep stop +# +# 2010-01-24 +# version 0.6.5: disable bytecode is a 2.6 feature, instead, resort to delete the bytecode manually +# +# 2010-01-19 +# version 0.6.4: bug fix +# version 0.6.3: added options --invert --only-match (replaces --exact, which is still available +# but removed from help) +# * use new 'irc_nick_color' info +# * don't generate bytecode when spawning a new process +# * show active options in buffer title +# +# 2010-01-17 +# version 0.6.2: removed 2.6-ish code +# version 0.6.1: fixed bug when grepping in grep's buffer +# +# 2010-01-14 +# version 0.6.0: implemented grep in background +# * improved context lines presentation. +# * grepping for big (or many) log files runs in a weechat_process. +# * added /grep stop. +# * added 'size_limit' option +# * fixed a infolist leak when grepping buffers +# * added 'default_tail_head' option +# * results are sort by line count +# * don't die if log is corrupted (has NULL chars in it) +# * changed presentation of /logs +# * log path completion doesn't suck anymore +# * removed all tabs, because I learned how to configure Vim so that spaces aren't annoying +# anymore. This was the script's original policy. +# +# 2010-01-05 +# version 0.5.5: rename script to 'grep.py' (FlashCode <flashcode@flashtux.org>). +# +# 2010-01-04 +# version 0.5.4.1: fix index error when using --after/before-context options. +# +# 2010-01-03 +# version 0.5.4: new features +# * added --after-context and --before-context options. +# * added --context as a shortcut for using both -A -B options. +# +# 2009-11-06 +# version 0.5.3: improvements for long grep output +# * grep buffer input accepts the same flags as /grep for repeat a search with different +# options. +# * tweaks in grep's output. +# * max_lines option added for limit grep's output. +# * code in update_buffer() optimized. +# * time stats in buffer title. +# * added go_to_buffer config option. +# * added --buffer for search only in buffers. +# * refactoring. +# +# 2009-10-12, omero +# version 0.5.2: made it python-2.4.x compliant +# +# 2009-08-17 +# version 0.5.1: some refactoring, show_summary option added. +# +# 2009-08-13 +# version 0.5: rewritten from xt's grep.py +# * fixed searching in non weechat logs, for cases like, if you're +# switching from irssi and rename and copy your irssi logs to %h/logs +# * fixed "timestamp rainbow" when you /grep in grep's buffer +# * allow to search in other buffers other than current or in logs +# of currently closed buffers with cmd 'buffer' +# * allow to search in any log file in %h/logs with cmd 'log' +# * added --count for return the number of matched lines +# * added --matchcase for case sensible search +# * added --hilight for color matches +# * added --head and --tail options, and --number +# * added command /logs for list files in %h/logs +# * added config option for clear the buffer before a search +# * added config option for filter logs we don't want to grep +# * added the posibility to repeat last search with another regexp by writing +# it in grep's buffer +# * changed spaces for tabs in the code, which is my preference +# +### + +from os import path +import sys, getopt, time, os, re, tempfile + +try: + import weechat + from weechat import WEECHAT_RC_OK, prnt, prnt_date_tags + import_ok = True +except ImportError: + import_ok = False + +SCRIPT_NAME = "grep" +SCRIPT_AUTHOR = "Elián Hanisch <lambdae2@gmail.com>" +SCRIPT_VERSION = "0.7.7" +SCRIPT_LICENSE = "GPL3" +SCRIPT_DESC = "Search in buffers and logs" +SCRIPT_COMMAND = "grep" + +### Default Settings ### +settings = { +'clear_buffer' : 'off', +'log_filter' : '', +'go_to_buffer' : 'on', +'max_lines' : '4000', +'show_summary' : 'on', +'size_limit' : '2048', +'default_tail_head' : '10', +} + +### Class definitions ### +class linesDict(dict): + """ + Class for handling matched lines in more than one buffer. + linesDict[buffer_name] = matched_lines_list + """ + def __setitem__(self, key, value): + assert isinstance(value, list) + if key not in self: + dict.__setitem__(self, key, value) + else: + dict.__getitem__(self, key).extend(value) + + def get_matches_count(self): + """Return the sum of total matches stored.""" + if dict.__len__(self): + return sum(map(lambda L: L.matches_count, self.itervalues())) + else: + return 0 + + def __len__(self): + """Return the sum of total lines stored.""" + if dict.__len__(self): + return sum(map(len, self.itervalues())) + else: + return 0 + + def __str__(self): + """Returns buffer count or buffer name if there's just one stored.""" + n = len(self.keys()) + if n == 1: + return self.keys()[0] + elif n > 1: + return '%s logs' %n + else: + return '' + + def items(self): + """Returns a list of items sorted by line count.""" + items = dict.items(self) + items.sort(key=lambda i: len(i[1])) + return items + + def items_count(self): + """Returns a list of items sorted by match count.""" + items = dict.items(self) + items.sort(key=lambda i: i[1].matches_count) + return items + + def strip_separator(self): + for L in self.itervalues(): + L.strip_separator() + + def get_last_lines(self, n): + total_lines = len(self) + #debug('total: %s n: %s' %(total_lines, n)) + if n >= total_lines: + # nothing to do + return + for k, v in reversed(self.items()): + l = len(v) + if n > 0: + if l > n: + del v[:l-n] + v.stripped_lines = l-n + n -= l + else: + del v[:] + v.stripped_lines = l + +class linesList(list): + """Class for list of matches, since sometimes I need to add lines that aren't matches, I need an + independent counter.""" + _sep = '...' + def __init__(self, *args): + list.__init__(self, *args) + self.matches_count = 0 + self.stripped_lines = 0 + + def append(self, item): + """Append lines, can be a string or a list with strings.""" + if isinstance(item, str): + list.append(self, item) + else: + self.extend(item) + + def append_separator(self): + """adds a separator into the list, makes sure it doen't add two together.""" + s = self._sep + if (self and self[-1] != s) or not self: + self.append(s) + + def onlyUniq(self): + s = set(self) + del self[:] + self.extend(s) + + def count_match(self, item=None): + if item is None or isinstance(item, str): + self.matches_count += 1 + else: + self.matches_count += len(item) + + def strip_separator(self): + """removes separators if there are first or/and last in the list.""" + if self: + s = self._sep + if self[0] == s: + del self[0] + if self[-1] == s: + del self[-1] + +### Misc functions ### +now = time.time +def get_size(f): + try: + return os.stat(f).st_size + except OSError: + return 0 + +sizeDict = {0:'b', 1:'KiB', 2:'MiB', 3:'GiB', 4:'TiB'} +def human_readable_size(size): + power = 0 + while size > 1024: + power += 1 + size /= 1024.0 + return '%.2f %s' %(size, sizeDict.get(power, '')) + +def color_nick(nick): + """Returns coloured nick, with coloured mode if any.""" + if not nick: return '' + wcolor = weechat.color + config_string = lambda s : weechat.config_string(weechat.config_get(s)) + config_int = lambda s : weechat.config_integer(weechat.config_get(s)) + # prefix and suffix + prefix = config_string('irc.look.nick_prefix') + suffix = config_string('irc.look.nick_suffix') + prefix_c = suffix_c = wcolor(config_string('weechat.color.chat_delimiters')) + if nick[0] == prefix: + nick = nick[1:] + else: + prefix = prefix_c = '' + if nick[-1] == suffix: + nick = nick[:-1] + suffix = wcolor(color_delimiter) + suffix + else: + suffix = suffix_c = '' + # nick mode + modes = '@!+%' + if nick[0] in modes: + mode, nick = nick[0], nick[1:] + mode_color = wcolor(config_string('weechat.color.nicklist_prefix%d' \ + %(modes.find(mode) + 1))) + else: + mode = mode_color = '' + # nick color + nick_color = weechat.info_get('irc_nick_color', nick) + if not nick_color: + # probably we're in WeeChat 0.3.0 + #debug('no irc_nick_color') + color_nicks_number = config_int('weechat.look.color_nicks_number') + idx = (sum(map(ord, nick))%color_nicks_number) + 1 + nick_color = wcolor(config_string('weechat.color.chat_nick_color%02d' %idx)) + return ''.join((prefix_c, prefix, mode_color, mode, nick_color, nick, suffix_c, suffix)) + +### Config and value validation ### +boolDict = {'on':True, 'off':False} +def get_config_boolean(config): + value = weechat.config_get_plugin(config) + try: + return boolDict[value] + except KeyError: + default = settings[config] + error("Error while fetching config '%s'. Using default value '%s'." %(config, default)) + error("'%s' is invalid, allowed: 'on', 'off'" %value) + return boolDict[default] + +def get_config_int(config, allow_empty_string=False): + value = weechat.config_get_plugin(config) + try: + return int(value) + except ValueError: + if value == '' and allow_empty_string: + return value + default = settings[config] + error("Error while fetching config '%s'. Using default value '%s'." %(config, default)) + error("'%s' is not a number." %value) + return int(default) + +def get_config_log_filter(): + filter = weechat.config_get_plugin('log_filter') + if filter: + return filter.split(',') + else: + return [] + +def get_home(): + home = weechat.config_string(weechat.config_get('logger.file.path')) + home = home.replace('%h', weechat.info_get('weechat_dir', '')) + home = path.abspath(path.expanduser(home)) + return home + +def strip_home(s, dir=''): + """Strips home dir from the begging of the log path, this makes them sorter.""" + if not dir: + global home_dir + dir = home_dir + l = len(dir) + if s[:l] == dir: + return s[l:] + return s + +### Messages ### +script_nick = SCRIPT_NAME +def error(s, buffer=''): + """Error msg""" + prnt(buffer, '%s%s %s' %(weechat.prefix('error'), script_nick, s)) + if weechat.config_get_plugin('debug'): + import traceback + if traceback.sys.exc_type: + trace = traceback.format_exc() + prnt('', trace) + +def say(s, buffer=''): + """normal msg""" + prnt_date_tags(buffer, 0, 'no_highlight', '%s\t%s' %(script_nick, s)) + + + +### Log files and buffers ### +cache_dir = {} # note: don't remove, needed for completion if the script was loaded recently +def dir_list(dir, filter_list=(), filter_excludes=True, include_dir=False): + """Returns a list of files in 'dir' and its subdirs.""" + global cache_dir + from os import walk + from fnmatch import fnmatch + #debug('dir_list: listing in %s' %dir) + key = (dir, include_dir) + try: + return cache_dir[key] + except KeyError: + pass + + filter_list = filter_list or get_config_log_filter() + dir_len = len(dir) + if filter_list: + def filter(file): + file = file[dir_len:] # pattern shouldn't match home dir + for pattern in filter_list: + if fnmatch(file, pattern): + return filter_excludes + return not filter_excludes + else: + filter = lambda f : not filter_excludes + + file_list = [] + extend = file_list.extend + join = path.join + def walk_path(): + for basedir, subdirs, files in walk(dir): + #if include_dir: + # subdirs = map(lambda s : join(s, ''), subdirs) + # files.extend(subdirs) + files_path = map(lambda f : join(basedir, f), files) + files_path = [ file for file in files_path if not filter(file) ] + extend(files_path) + + walk_path() + cache_dir[key] = file_list + #debug('dir_list: got %s' %str(file_list)) + return file_list + +def get_file_by_pattern(pattern, all=False): + """Returns the first log whose path matches 'pattern', + if all is True returns all logs that matches.""" + if not pattern: return [] + #debug('get_file_by_filename: searching for %s.' %pattern) + # do envvar expandsion and check file + file = path.expanduser(pattern) + file = path.expandvars(file) + if path.isfile(file): + return [file] + # lets see if there's a matching log + global home_dir + file = path.join(home_dir, pattern) + if path.isfile(file): + return [file] + else: + from fnmatch import fnmatch + file = [] + file_list = dir_list(home_dir) + n = len(home_dir) + for log in file_list: + basename = log[n:] + if fnmatch(basename, pattern): + file.append(log) + #debug('get_file_by_filename: got %s.' %file) + if not all and file: + file.sort() + return [ file[-1] ] + return file + +def get_file_by_buffer(buffer): + """Given buffer pointer, finds log's path or returns None.""" + #debug('get_file_by_buffer: searching for %s' %buffer) + infolist = weechat.infolist_get('logger_buffer', '', '') + if not infolist: return + try: + while weechat.infolist_next(infolist): + pointer = weechat.infolist_pointer(infolist, 'buffer') + if pointer == buffer: + file = weechat.infolist_string(infolist, 'log_filename') + if weechat.infolist_integer(infolist, 'log_enabled'): + #debug('get_file_by_buffer: got %s' %file) + return file + #else: + # debug('get_file_by_buffer: got %s but log not enabled' %file) + finally: + #debug('infolist gets freed') + weechat.infolist_free(infolist) + +def get_file_by_name(buffer_name): + """Given a buffer name, returns its log path or None. buffer_name should be in 'server.#channel' + or '#channel' format.""" + #debug('get_file_by_name: searching for %s' %buffer_name) + # common mask options + config_masks = ('logger.mask.irc', 'logger.file.mask') + # since there's no buffer pointer, we try to replace some local vars in mask, like $channel and + # $server, then replace the local vars left with '*', and use it as a mask for get the path with + # get_file_by_pattern + for config in config_masks: + mask = weechat.config_string(weechat.config_get(config)) + #debug('get_file_by_name: mask: %s' %mask) + if '$name' in mask: + mask = mask.replace('$name', buffer_name) + elif '$channel' in mask or '$server' in mask: + if '.' in buffer_name and \ + '#' not in buffer_name[:buffer_name.find('.')]: # the dot isn't part of the channel name + # ^ I'm asuming channel starts with #, i'm lazy. + server, channel = buffer_name.split('.', 1) + else: + server, channel = '*', buffer_name + if '$channel' in mask: + mask = mask.replace('$channel', channel) + if '$server' in mask: + mask = mask.replace('$server', server) + # change the unreplaced vars by '*' + from string import letters + if '%' in mask: + # vars for time formatting + mask = mask.replace('%', '$') + if '$' in mask: + masks = mask.split('$') + masks = map(lambda s: s.lstrip(letters), masks) + mask = '*'.join(masks) + if mask[0] != '*': + mask = '*' + mask + #debug('get_file_by_name: using mask %s' %mask) + file = get_file_by_pattern(mask) + #debug('get_file_by_name: got file %s' %file) + if file: + return file + return None + +def get_buffer_by_name(buffer_name): + """Given a buffer name returns its buffer pointer or None.""" + #debug('get_buffer_by_name: searching for %s' %buffer_name) + pointer = weechat.buffer_search('', buffer_name) + if not pointer: + try: + infolist = weechat.infolist_get('buffer', '', '') + while weechat.infolist_next(infolist): + short_name = weechat.infolist_string(infolist, 'short_name') + name = weechat.infolist_string(infolist, 'name') + if buffer_name in (short_name, name): + #debug('get_buffer_by_name: found %s' %name) + pointer = weechat.buffer_search('', name) + return pointer + finally: + weechat.infolist_free(infolist) + #debug('get_buffer_by_name: got %s' %pointer) + return pointer + +def get_all_buffers(): + """Returns list with pointers of all open buffers.""" + buffers = [] + infolist = weechat.infolist_get('buffer', '', '') + while weechat.infolist_next(infolist): + buffers.append(weechat.infolist_pointer(infolist, 'pointer')) + weechat.infolist_free(infolist) + grep_buffer = weechat.buffer_search('python', SCRIPT_NAME) + if grep_buffer and grep_buffer in buffers: + # remove it from list + del buffers[buffers.index(grep_buffer)] + return buffers + +### Grep ### +def make_regexp(pattern, matchcase=False): + """Returns a compiled regexp.""" + if pattern in ('.', '.*', '.?', '.+'): + # because I don't need to use a regexp if we're going to match all lines + return None + # matching takes a lot more time if pattern starts or ends with .* and it isn't needed. + if pattern[:2] == '.*': + pattern = pattern[2:] + if pattern[-2:] == '.*': + pattern = pattern[:-2] + try: + if not matchcase: + regexp = re.compile(pattern, re.IGNORECASE) + else: + regexp = re.compile(pattern) + except Exception, e: + raise Exception, 'Bad pattern, %s' %e + return regexp + +def check_string(s, regexp, hilight='', exact=False): + """Checks 's' with a regexp and returns it if is a match.""" + if not regexp: + return s + + elif exact: + matchlist = regexp.findall(s) + if matchlist: + if isinstance(matchlist[0], tuple): + # join tuples (when there's more than one match group in regexp) + return [ ' '.join(t) for t in matchlist ] + return matchlist + + elif hilight: + matchlist = regexp.findall(s) + if matchlist: + if isinstance(matchlist[0], tuple): + # flatten matchlist + matchlist = [ item for L in matchlist for item in L if item ] + matchlist = list(set(matchlist)) # remove duplicates if any + # apply hilight + color_hilight, color_reset = hilight.split(',', 1) + for m in matchlist: + s = s.replace(m, '%s%s%s' % (color_hilight, m, color_reset)) + return s + + # no need for findall() here + elif regexp.search(s): + return s + +def grep_file(file, head, tail, after_context, before_context, count, regexp, hilight, exact, invert): + """Return a list of lines that match 'regexp' in 'file', if no regexp returns all lines.""" + if count: + tail = head = after_context = before_context = False + hilight = '' + elif exact: + before_context = after_context = False + hilight = '' + elif invert: + hilight = '' + #debug(' '.join(map(str, (file, head, tail, after_context, before_context)))) + + lines = linesList() + # define these locally as it makes the loop run slightly faster + append = lines.append + count_match = lines.count_match + separator = lines.append_separator + if invert: + def check(s): + if check_string(s, regexp, hilight, exact): + return None + else: + return s + else: + check = lambda s: check_string(s, regexp, hilight, exact) + + try: + file_object = open(file, 'r') + except IOError: + # file doesn't exist + return lines + if tail or before_context: + # for these options, I need to seek in the file, but is slower and uses a good deal of + # memory if the log is too big, so we do this *only* for these options. + file_lines = file_object.readlines() + + if tail: + # instead of searching in the whole file and later pick the last few lines, we + # reverse the log, search until count reached and reverse it again, that way is a lot + # faster + file_lines.reverse() + # don't invert context switches + before_context, after_context = after_context, before_context + + if before_context: + before_context_range = range(1, before_context + 1) + before_context_range.reverse() + + limit = tail or head + + line_idx = 0 + while line_idx < len(file_lines): + line = file_lines[line_idx] + line = check(line) + if line: + if before_context: + separator() + trimmed = False + for id in before_context_range: + try: + context_line = file_lines[line_idx - id] + if check(context_line): + # match in before context, that means we appended these same lines in a + # previous match, so we delete them merging both paragraphs + if not trimmed: + del lines[id - before_context - 1:] + trimmed = True + else: + append(context_line) + except IndexError: + pass + append(line) + count_match(line) + if after_context: + id, offset = 0, 0 + while id < after_context + offset: + id += 1 + try: + context_line = file_lines[line_idx + id] + _context_line = check(context_line) + if _context_line: + offset = id + context_line = _context_line # so match is hilighted with --hilight + count_match() + append(context_line) + except IndexError: + pass + separator() + line_idx += id + if limit and lines.matches_count >= limit: + break + line_idx += 1 + + if tail: + lines.reverse() + else: + # do a normal grep + limit = head + + for line in file_object: + line = check(line) + if line: + count or append(line) + count_match(line) + if after_context: + id, offset = 0, 0 + while id < after_context + offset: + id += 1 + try: + context_line = file_object.next() + _context_line = check(context_line) + if _context_line: + offset = id + context_line = _context_line + count_match() + count or append(context_line) + except StopIteration: + pass + separator() + if limit and lines.matches_count >= limit: + break + + file_object.close() + return lines + +def grep_buffer(buffer, head, tail, after_context, before_context, count, regexp, hilight, exact, + invert): + """Return a list of lines that match 'regexp' in 'buffer', if no regexp returns all lines.""" + lines = linesList() + if count: + tail = head = after_context = before_context = False + hilight = '' + elif exact: + before_context = after_context = False + #debug(' '.join(map(str, (tail, head, after_context, before_context, count, exact, hilight)))) + + # Using /grep in grep's buffer can lead to some funny effects + # We should take measures if that's the case + def make_get_line_funcion(): + """Returns a function for get lines from the infolist, depending if the buffer is grep's or + not.""" + string_remove_color = weechat.string_remove_color + infolist_string = weechat.infolist_string + grep_buffer = weechat.buffer_search('python', SCRIPT_NAME) + if grep_buffer and buffer == grep_buffer: + def function(infolist): + prefix = infolist_string(infolist, 'prefix') + message = infolist_string(infolist, 'message') + if prefix: # only our messages have prefix, ignore it + return None + return message + else: + infolist_time = weechat.infolist_time + def function(infolist): + prefix = string_remove_color(infolist_string(infolist, 'prefix'), '') + message = string_remove_color(infolist_string(infolist, 'message'), '') + date = infolist_time(infolist, 'date') + return '%s\t%s\t%s' %(date, prefix, message) + return function + get_line = make_get_line_funcion() + + infolist = weechat.infolist_get('buffer_lines', buffer, '') + if tail: + # like with grep_file() if we need the last few matching lines, we move the cursor to + # the end and search backwards + infolist_next = weechat.infolist_prev + infolist_prev = weechat.infolist_next + else: + infolist_next = weechat.infolist_next + infolist_prev = weechat.infolist_prev + limit = head or tail + + # define these locally as it makes the loop run slightly faster + append = lines.append + count_match = lines.count_match + separator = lines.append_separator + if invert: + def check(s): + if check_string(s, regexp, hilight, exact): + return None + else: + return s + else: + check = lambda s: check_string(s, regexp, hilight, exact) + + if before_context: + before_context_range = range(1, before_context + 1) + before_context_range.reverse() + + while infolist_next(infolist): + line = get_line(infolist) + if line is None: continue + line = check(line) + if line: + if before_context: + separator() + trimmed = False + for id in before_context_range: + if not infolist_prev(infolist): + trimmed = True + for id in before_context_range: + context_line = get_line(infolist) + if check(context_line): + if not trimmed: + del lines[id - before_context - 1:] + trimmed = True + else: + append(context_line) + infolist_next(infolist) + count or append(line) + count_match(line) + if after_context: + id, offset = 0, 0 + while id < after_context + offset: + id += 1 + if infolist_next(infolist): + context_line = get_line(infolist) + _context_line = check(context_line) + if _context_line: + context_line = _context_line + offset = id + count_match() + append(context_line) + else: + # in the main loop infolist_next will start again an cause an infinite loop + # this will avoid it + infolist_next = lambda x: 0 + separator() + if limit and lines.matches_count >= limit: + break + weechat.infolist_free(infolist) + + if tail: + lines.reverse() + return lines + +### this is our main grep function +hook_file_grep = None +def show_matching_lines(): + """ + Greps buffers in search_in_buffers or files in search_in_files and updates grep buffer with the + result. + """ + global pattern, matchcase, number, count, exact, hilight, invert + global tail, head, after_context, before_context + global search_in_files, search_in_buffers, matched_lines, home_dir + global time_start + matched_lines = linesDict() + #debug('buffers:%s \nlogs:%s' %(search_in_buffers, search_in_files)) + time_start = now() + + # buffers + if search_in_buffers: + regexp = make_regexp(pattern, matchcase) + for buffer in search_in_buffers: + buffer_name = weechat.buffer_get_string(buffer, 'name') + matched_lines[buffer_name] = grep_buffer(buffer, head, tail, after_context, + before_context, count, regexp, hilight, exact, invert) + + # logs + if search_in_files: + size_limit = get_config_int('size_limit', allow_empty_string=True) + background = False + if size_limit or size_limit == 0: + size = sum(map(get_size, search_in_files)) + if size > size_limit * 1024: + background = True + elif size_limit == '': + background = False + + if not background: + # run grep normally + regexp = make_regexp(pattern, matchcase) + for log in search_in_files: + log_name = strip_home(log) + matched_lines[log_name] = grep_file(log, head, tail, after_context, before_context, + count, regexp, hilight, exact, invert) + buffer_update() + else: + # we hook a process so grepping runs in background. + #debug('on background') + global hook_file_grep, script_path, bytecode + timeout = 1000*60*5 # 5 min + + quotify = lambda s: '"%s"' %s + files_string = ', '.join(map(quotify, search_in_files)) + + global tmpFile + # we keep the file descriptor as a global var so it isn't deleted until next grep + tmpFile = tempfile.NamedTemporaryFile(prefix=SCRIPT_NAME, + dir=weechat.info_get('weechat_dir', '')) + cmd = grep_process_cmd %dict(logs=files_string, head=head, pattern=pattern, tail=tail, + hilight=hilight, after_context=after_context, before_context=before_context, + exact=exact, matchcase=matchcase, home_dir=home_dir, script_path=script_path, + count=count, invert=invert, bytecode=bytecode, filename=tmpFile.name, + python=weechat.info_get('python2_bin', '') or 'python') + + #debug(cmd) + hook_file_grep = weechat.hook_process(cmd, timeout, 'grep_file_callback', tmpFile.name) + global pattern_tmpl + if hook_file_grep: + buffer_create("Searching for '%s' in %s worth of data..." %(pattern_tmpl, + human_readable_size(size))) + else: + buffer_update() + +# defined here for commodity +grep_process_cmd = """%(python)s -%(bytecode)sc ' +import sys, cPickle, os +sys.path.append("%(script_path)s") # add WeeChat script dir so we can import grep +from grep import make_regexp, grep_file, strip_home +logs = (%(logs)s, ) +try: + regexp = make_regexp("%(pattern)s", %(matchcase)s) + d = {} + for log in logs: + log_name = strip_home(log, "%(home_dir)s") + lines = grep_file(log, %(head)s, %(tail)s, %(after_context)s, %(before_context)s, + %(count)s, regexp, "%(hilight)s", %(exact)s, %(invert)s) + d[log_name] = lines + fd = open("%(filename)s", "wb") + cPickle.dump(d, fd, -1) + fd.close() +except Exception, e: + print >> sys.stderr, e' +""" + +grep_stdout = grep_stderr = '' +def grep_file_callback(filename, command, rc, stdout, stderr): + global hook_file_grep, grep_stderr, grep_stdout + global matched_lines + #debug("rc: %s\nstderr: %s\nstdout: %s" %(rc, repr(stderr), repr(stdout))) + if stdout: + grep_stdout += stdout + if stderr: + grep_stderr += stderr + if int(rc) >= 0: + + def set_buffer_error(): + grep_buffer = buffer_create() + title = weechat.buffer_get_string(grep_buffer, 'title') + title = title + ' %serror' %color_title + weechat.buffer_set(grep_buffer, 'title', title) + + try: + if grep_stderr: + error(grep_stderr) + set_buffer_error() + #elif grep_stdout: + #debug(grep_stdout) + elif path.exists(filename): + import cPickle + try: + #debug(file) + fd = open(filename, 'rb') + d = cPickle.load(fd) + matched_lines.update(d) + fd.close() + except Exception, e: + error(e) + set_buffer_error() + else: + buffer_update() + global tmpFile + tmpFile = None + finally: + grep_stdout = grep_stderr = '' + hook_file_grep = None + return WEECHAT_RC_OK + +def get_grep_file_status(): + global search_in_files, matched_lines, time_start + elapsed = now() - time_start + if len(search_in_files) == 1: + log = '%s (%s)' %(strip_home(search_in_files[0]), + human_readable_size(get_size(search_in_files[0]))) + else: + size = sum(map(get_size, search_in_files)) + log = '%s log files (%s)' %(len(search_in_files), human_readable_size(size)) + return 'Searching in %s, running for %.4f seconds. Interrupt it with "/grep stop" or "stop"' \ + ' in grep buffer.' %(log, elapsed) + +### Grep buffer ### +def buffer_update(): + """Updates our buffer with new lines.""" + global pattern_tmpl, matched_lines, pattern, count, hilight, invert, exact + time_grep = now() + + buffer = buffer_create() + if get_config_boolean('clear_buffer'): + weechat.buffer_clear(buffer) + matched_lines.strip_separator() # remove first and last separators of each list + len_total_lines = len(matched_lines) + max_lines = get_config_int('max_lines') + if not count and len_total_lines > max_lines: + weechat.buffer_clear(buffer) + + def _make_summary(log, lines, note): + return '%s matches "%s%s%s"%s in %s%s%s%s' \ + %(lines.matches_count, color_summary, pattern_tmpl, color_info, + invert and ' (inverted)' or '', + color_summary, log, color_reset, note) + + if count: + make_summary = lambda log, lines : _make_summary(log, lines, ' (not shown)') + else: + def make_summary(log, lines): + if lines.stripped_lines: + if lines: + note = ' (last %s lines shown)' %len(lines) + else: + note = ' (not shown)' + else: + note = '' + return _make_summary(log, lines, note) + + global weechat_format + if hilight: + # we don't want colors if there's match highlighting + format_line = lambda s : '%s %s %s' %split_line(s) + else: + def format_line(s): + global nick_dict, weechat_format + date, nick, msg = split_line(s) + if weechat_format: + try: + nick = nick_dict[nick] + except KeyError: + # cache nick + nick_c = color_nick(nick) + nick_dict[nick] = nick_c + nick = nick_c + return '%s%s %s%s %s' %(color_date, date, nick, color_reset, msg) + else: + #no formatting + return msg + + prnt(buffer, '\n') + print_line('Search for "%s%s%s"%s in %s%s%s.' %(color_summary, pattern_tmpl, color_info, + invert and ' (inverted)' or '', color_summary, matched_lines, color_reset), + buffer) + # print last <max_lines> lines + if matched_lines.get_matches_count(): + if count: + # with count we sort by matches lines instead of just lines. + matched_lines_items = matched_lines.items_count() + else: + matched_lines_items = matched_lines.items() + + matched_lines.get_last_lines(max_lines) + for log, lines in matched_lines_items: + if lines.matches_count: + # matched lines + if not count: + # print lines + weechat_format = True + if exact: + lines.onlyUniq() + for line in lines: + #debug(repr(line)) + if line == linesList._sep: + # separator + prnt(buffer, context_sep) + else: + if '\x00' in line: + # log was corrupted + error("Found garbage in log '%s', maybe it's corrupted" %log) + line = line.replace('\x00', '') + prnt_date_tags(buffer, 0, 'no_highlight', format_line(line)) + + # summary + if count or get_config_boolean('show_summary'): + summary = make_summary(log, lines) + print_line(summary, buffer) + + # separator + if not count and lines: + prnt(buffer, '\n') + else: + print_line('No matches found.', buffer) + + # set title + global time_start + time_end = now() + # total time + time_total = time_end - time_start + # percent of the total time used for grepping + time_grep_pct = (time_grep - time_start)/time_total*100 + #debug('time: %.4f seconds (%.2f%%)' %(time_total, time_grep_pct)) + if not count and len_total_lines > max_lines: + note = ' (last %s lines shown)' %len(matched_lines) + else: + note = '' + title = "'q': close buffer | Search in %s%s%s %s matches%s | pattern \"%s%s%s\"%s %s | %.4f seconds (%.2f%%)" \ + %(color_title, matched_lines, color_reset, matched_lines.get_matches_count(), note, + color_title, pattern_tmpl, color_reset, invert and ' (inverted)' or '', format_options(), + time_total, time_grep_pct) + weechat.buffer_set(buffer, 'title', title) + + if get_config_boolean('go_to_buffer'): + weechat.buffer_set(buffer, 'display', '1') + + # free matched_lines so it can be removed from memory + del matched_lines + +def split_line(s): + """Splits log's line 's' in 3 parts, date, nick and msg.""" + global weechat_format + if weechat_format and s.count('\t') >= 2: + date, nick, msg = s.split('\t', 2) # date, nick, message + else: + # looks like log isn't in weechat's format + weechat_format = False # incoming lines won't be formatted + date, nick, msg = '', '', s + # remove tabs + if '\t' in msg: + msg = msg.replace('\t', ' ') + return date, nick, msg + +def print_line(s, buffer=None, display=False): + """Prints 's' in script's buffer as 'script_nick'. For displaying search summaries.""" + if buffer is None: + buffer = buffer_create() + say('%s%s' %(color_info, s), buffer) + if display and get_config_boolean('go_to_buffer'): + weechat.buffer_set(buffer, 'display', '1') + +def format_options(): + global matchcase, number, count, exact, hilight, invert + global tail, head, after_context, before_context + options = [] + append = options.append + insert = options.insert + chars = 'cHmov' + for i, flag in enumerate((count, hilight, matchcase, exact, invert)): + if flag: + append(chars[i]) + + if head or tail: + n = get_config_int('default_tail_head') + if head: + append('h') + if head != n: + insert(-1, ' -') + append('n') + append(head) + elif tail: + append('t') + if tail != n: + insert(-1, ' -') + append('n') + append(tail) + + if before_context and after_context and (before_context == after_context): + append(' -C') + append(before_context) + else: + if before_context: + append(' -B') + append(before_context) + if after_context: + append(' -A') + append(after_context) + + s = ''.join(map(str, options)).strip() + if s and s[0] != '-': + s = '-' + s + return s + +def buffer_create(title=None): + """Returns our buffer pointer, creates and cleans the buffer if needed.""" + buffer = weechat.buffer_search('python', SCRIPT_NAME) + if not buffer: + buffer = weechat.buffer_new(SCRIPT_NAME, 'buffer_input', '', '', '') + weechat.buffer_set(buffer, 'time_for_each_line', '0') + weechat.buffer_set(buffer, 'nicklist', '0') + weechat.buffer_set(buffer, 'title', title or 'grep output buffer') + weechat.buffer_set(buffer, 'localvar_set_no_log', '1') + elif title: + weechat.buffer_set(buffer, 'title', title) + return buffer + +def buffer_input(data, buffer, input_data): + """Repeats last search with 'input_data' as regexp.""" + try: + cmd_grep_stop(buffer, input_data) + except: + return WEECHAT_RC_OK + if input_data in ('q', 'Q'): + weechat.buffer_close(buffer) + return weechat.WEECHAT_RC_OK + + global search_in_buffers, search_in_files + global pattern + try: + if pattern and (search_in_files or search_in_buffers): + # check if the buffer pointers are still valid + for pointer in search_in_buffers: + infolist = weechat.infolist_get('buffer', pointer, '') + if not infolist: + del search_in_buffers[search_in_buffers.index(pointer)] + weechat.infolist_free(infolist) + try: + cmd_grep_parsing(input_data) + except Exception, e: + error('Argument error, %s' %e, buffer=buffer) + return WEECHAT_RC_OK + try: + show_matching_lines() + except Exception, e: + error(e) + except NameError: + error("There isn't any previous search to repeat.", buffer=buffer) + return WEECHAT_RC_OK + +### Commands ### +def cmd_init(): + """Resets global vars.""" + global home_dir, cache_dir, nick_dict + global pattern_tmpl, pattern, matchcase, number, count, exact, hilight, invert + global tail, head, after_context, before_context + hilight = '' + head = tail = after_context = before_context = invert = False + matchcase = count = exact = False + pattern_tmpl = pattern = number = None + home_dir = get_home() + cache_dir = {} # for avoid walking the dir tree more than once per command + nick_dict = {} # nick cache for don't calculate nick color every time + +def cmd_grep_parsing(args): + """Parses args for /grep and grep input buffer.""" + global pattern_tmpl, pattern, matchcase, number, count, exact, hilight, invert + global tail, head, after_context, before_context + global log_name, buffer_name, only_buffers, all + opts, args = getopt.gnu_getopt(args.split(), 'cmHeahtivn:bA:B:C:o', ['count', 'matchcase', 'hilight', + 'exact', 'all', 'head', 'tail', 'number=', 'buffer', 'after-context=', 'before-context=', + 'context=', 'invert', 'only-match']) + #debug(opts, 'opts: '); debug(args, 'args: ') + if len(args) >= 2: + if args[0] == 'log': + del args[0] + log_name = args.pop(0) + elif args[0] == 'buffer': + del args[0] + buffer_name = args.pop(0) + + def tmplReplacer(match): + """This function will replace templates with regexps""" + s = match.groups()[0] + tmpl_args = s.split() + tmpl_key, _, tmpl_args = s.partition(' ') + try: + template = templates[tmpl_key] + if callable(template): + r = template(tmpl_args) + if not r: + error("Template %s returned empty string "\ + "(WeeChat doesn't have enough data)." %t) + return r + else: + return template + except: + return t + + args = ' '.join(args) # join pattern for keep spaces + if args: + pattern_tmpl = args + pattern = _tmplRe.sub(tmplReplacer, args) + debug('Using regexp: %s', pattern) + if not pattern: + raise Exception, 'No pattern for grep the logs.' + + def positive_number(opt, val): + try: + number = int(val) + if number < 0: + raise ValueError + return number + except ValueError: + if len(opt) == 1: + opt = '-' + opt + else: + opt = '--' + opt + raise Exception, "argument for %s must be a positive integer." %opt + + for opt, val in opts: + opt = opt.strip('-') + if opt in ('c', 'count'): + count = not count + elif opt in ('m', 'matchcase'): + matchcase = not matchcase + elif opt in ('H', 'hilight'): + # hilight must be always a string! + if hilight: + hilight = '' + else: + hilight = '%s,%s' %(color_hilight, color_reset) + # we pass the colors in the variable itself because check_string() must not use + # weechat's module when applying the colors (this is for grep in a hooked process) + elif opt in ('e', 'exact', 'o', 'only-match'): + exact = not exact + invert = False + elif opt in ('a', 'all'): + all = not all + elif opt in ('h', 'head'): + head = not head + tail = False + elif opt in ('t', 'tail'): + tail = not tail + head = False + elif opt in ('b', 'buffer'): + only_buffers = True + elif opt in ('n', 'number'): + number = positive_number(opt, val) + elif opt in ('C', 'context'): + n = positive_number(opt, val) + after_context = n + before_context = n + elif opt in ('A', 'after-context'): + after_context = positive_number(opt, val) + elif opt in ('B', 'before-context'): + before_context = positive_number(opt, val) + elif opt in ('i', 'v', 'invert'): + invert = not invert + exact = False + # number check + if number is not None: + if number == 0: + head = tail = False + number = None + elif head: + head = number + elif tail: + tail = number + else: + n = get_config_int('default_tail_head') + if head: + head = n + elif tail: + tail = n + +def cmd_grep_stop(buffer, args): + global hook_file_grep, pattern, matched_lines, tmpFile + if hook_file_grep: + if args == 'stop': + weechat.unhook(hook_file_grep) + hook_file_grep = None + s = 'Search for \'%s\' stopped.' %pattern + say(s, buffer) + grep_buffer = weechat.buffer_search('python', SCRIPT_NAME) + if grep_buffer: + weechat.buffer_set(grep_buffer, 'title', s) + del matched_lines + tmpFile = None + else: + say(get_grep_file_status(), buffer) + raise Exception + +def cmd_grep(data, buffer, args): + """Search in buffers and logs.""" + global pattern, matchcase, head, tail, number, count, exact, hilight + try: + cmd_grep_stop(buffer, args) + except: + return WEECHAT_RC_OK + + if not args: + weechat.command('', '/help %s' %SCRIPT_COMMAND) + return WEECHAT_RC_OK + + cmd_init() + global log_name, buffer_name, only_buffers, all + log_name = buffer_name = '' + only_buffers = all = False + + # parse + try: + cmd_grep_parsing(args) + except Exception, e: + error('Argument error, %s' %e) + return WEECHAT_RC_OK + + # find logs + log_file = search_buffer = None + if log_name: + log_file = get_file_by_pattern(log_name, all) + if not log_file: + error("Couldn't find any log for %s. Try /logs" %log_name) + return WEECHAT_RC_OK + elif all: + search_buffer = get_all_buffers() + elif buffer_name: + search_buffer = get_buffer_by_name(buffer_name) + if not search_buffer: + # there's no buffer, try in the logs + log_file = get_file_by_name(buffer_name) + if not log_file: + error("Logs or buffer for '%s' not found." %buffer_name) + return WEECHAT_RC_OK + else: + search_buffer = [search_buffer] + else: + search_buffer = [buffer] + + # make the log list + global search_in_files, search_in_buffers + search_in_files = [] + search_in_buffers = [] + if log_file: + search_in_files = log_file + elif not only_buffers: + #debug(search_buffer) + for pointer in search_buffer: + log = get_file_by_buffer(pointer) + #debug('buffer %s log %s' %(pointer, log)) + if log: + search_in_files.append(log) + else: + search_in_buffers.append(pointer) + else: + search_in_buffers = search_buffer + + # grepping + try: + show_matching_lines() + except Exception, e: + error(e) + return WEECHAT_RC_OK + +def cmd_logs(data, buffer, args): + """List files in Weechat's log dir.""" + cmd_init() + global home_dir + sort_by_size = False + filter = [] + + try: + opts, args = getopt.gnu_getopt(args.split(), 's', ['size']) + if args: + filter = args + for opt, var in opts: + opt = opt.strip('-') + if opt in ('size', 's'): + sort_by_size = True + except Exception, e: + error('Argument error, %s' %e) + return WEECHAT_RC_OK + + # is there's a filter, filter_excludes should be False + file_list = dir_list(home_dir, filter, filter_excludes=not filter) + if sort_by_size: + file_list.sort(key=get_size) + else: + file_list.sort() + + file_sizes = map(lambda x: human_readable_size(get_size(x)), file_list) + # calculate column lenght + if file_list: + L = file_list[:] + L.sort(key=len) + bigest = L[-1] + column_len = len(bigest) + 3 + else: + column_len = '' + + buffer = buffer_create() + if get_config_boolean('clear_buffer'): + weechat.buffer_clear(buffer) + file_list = zip(file_list, file_sizes) + msg = 'Found %s logs.' %len(file_list) + + print_line(msg, buffer, display=True) + for file, size in file_list: + separator = column_len and '.'*(column_len - len(file)) + prnt(buffer, '%s %s %s' %(strip_home(file), separator, size)) + if file_list: + print_line(msg, buffer) + return WEECHAT_RC_OK + + +### Completion ### +def completion_log_files(data, completion_item, buffer, completion): + #debug('completion: %s' %', '.join((data, completion_item, buffer, completion))) + global home_dir + l = len(home_dir) + completion_list_add = weechat.hook_completion_list_add + WEECHAT_LIST_POS_END = weechat.WEECHAT_LIST_POS_END + for log in dir_list(home_dir): + completion_list_add(completion, log[l:], 0, WEECHAT_LIST_POS_END) + return WEECHAT_RC_OK + +def completion_grep_args(data, completion_item, buffer, completion): + for arg in ('count', 'all', 'matchcase', 'hilight', 'exact', 'head', 'tail', 'number', 'buffer', + 'after-context', 'before-context', 'context', 'invert', 'only-match'): + weechat.hook_completion_list_add(completion, '--' + arg, 0, weechat.WEECHAT_LIST_POS_SORT) + for tmpl in templates: + weechat.hook_completion_list_add(completion, '%{' + tmpl, 0, weechat.WEECHAT_LIST_POS_SORT) + return WEECHAT_RC_OK + + +### Templates ### +# template placeholder +_tmplRe = re.compile(r'%\{(\w+.*?)(?:\}|$)') +# will match 999.999.999.999 but I don't care +ipAddress = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' +domain = r'[\w-]{2,}(?:\.[\w-]{2,})*\.[a-z]{2,}' +url = r'\w+://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?' % (domain, ipAddress) + +def make_url_regexp(args): + #debug('make url: %s', args) + if args: + words = r'(?:%s)' %'|'.join(map(re.escape, args.split())) + return r'(?:\w+://|www\.)[^\s]*%s[^\s]*(?:/[^\])>\s]*)?' %words + else: + return url + +def make_simple_regexp(pattern): + s = '' + for c in pattern: + if c == '*': + s += '.*' + elif c == '?': + s += '.' + else: + s += re.escape(c) + return s + +templates = { + 'ip': ipAddress, + 'url': make_url_regexp, + 'escape': lambda s: re.escape(s), + 'simple': make_simple_regexp, + 'domain': domain, + } + +### Main ### +def delete_bytecode(): + global script_path + bytecode = path.join(script_path, SCRIPT_NAME + '.pyc') + if path.isfile(bytecode): + os.remove(bytecode) + return WEECHAT_RC_OK + +if __name__ == '__main__' and import_ok and \ + weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, \ + SCRIPT_DESC, 'delete_bytecode', ''): + home_dir = get_home() + + # for import ourselves + global script_path + script_path = path.dirname(__file__) + sys.path.append(script_path) + delete_bytecode() + + # check python version + import sys + global bytecode + if sys.version_info > (2, 6): + bytecode = 'B' + else: + bytecode = '' + + + weechat.hook_command(SCRIPT_COMMAND, cmd_grep.__doc__, + "[log <file> | buffer <name> | stop] [-a|--all] [-b|--buffer] [-c|--count] [-m|--matchcase] " + "[-H|--hilight] [-o|--only-match] [-i|-v|--invert] [(-h|--head)|(-t|--tail) [-n|--number <n>]] " + "[-A|--after-context <n>] [-B|--before-context <n>] [-C|--context <n> ] <expression>", +# help +""" + log <file>: Search in one log that matches <file> in the logger path. + Use '*' and '?' as wildcards. + buffer <name>: Search in buffer <name>, if there's no buffer with <name> it will + try to search for a log file. + stop: Stops a currently running search. + -a --all: Search in all open buffers. + If used with 'log <file>' search in all logs that matches <file>. + -b --buffer: Search only in buffers, not in file logs. + -c --count: Just count the number of matched lines instead of showing them. + -m --matchcase: Don't do case insensitive search. + -H --hilight: Colour exact matches in output buffer. +-o --only-match: Print only the matching part of the line (unique matches). + -v -i --invert: Print lines that don't match the regular expression. + -t --tail: Print the last 10 matching lines. + -h --head: Print the first 10 matching lines. +-n --number <n>: Overrides default number of lines for --tail or --head. +-A --after-context <n>: Shows <n> lines of trailing context after matching lines. +-B --before-context <n>: Shows <n> lines of leading context before matching lines. +-C --context <n>: Same as using both --after-context and --before-context simultaneously. + <expression>: Expression to search. + +Grep buffer: + Input line accepts most arguments of /grep, it'll repeat last search using the new + arguments provided. You can't search in different logs from the buffer's input. + Boolean arguments like --count, --tail, --head, --hilight, ... are toggleable + +Python regular expression syntax: + See http://docs.python.org/lib/re-syntax.html + +Grep Templates: + %{url [text]}: Matches anything like an url, or an url with text. + %{ip}: Matches anything that looks like an ip. + %{domain}: Matches anything like a domain. + %{escape text}: Escapes text in pattern. + %{simple pattern}: Converts a pattern with '*' and '?' wildcards into a regexp. + +Examples: + Search for urls with the word 'weechat' said by 'nick' + /grep nick\\t.*%{url weechat} + Search for '*.*' string + /grep %{escape *.*} +""", + # completion template + "buffer %(buffers_names) %(grep_arguments)|%*" + "||log %(grep_log_files) %(grep_arguments)|%*" + "||stop" + "||%(grep_arguments)|%*", + 'cmd_grep' ,'') + weechat.hook_command('logs', cmd_logs.__doc__, "[-s|--size] [<filter>]", + "-s --size: Sort logs by size.\n" + " <filter>: Only show logs that match <filter>. Use '*' and '?' as wildcards.", '--size', 'cmd_logs', '') + + weechat.hook_completion('grep_log_files', "list of log files", + 'completion_log_files', '') + weechat.hook_completion('grep_arguments', "list of arguments", + 'completion_grep_args', '') + + # settings + for opt, val in settings.iteritems(): + if not weechat.config_is_set_plugin(opt): + weechat.config_set_plugin(opt, val) + + # colors + color_date = weechat.color('brown') + color_info = weechat.color('cyan') + color_hilight = weechat.color('lightred') + color_reset = weechat.color('reset') + color_title = weechat.color('yellow') + color_summary = weechat.color('lightcyan') + color_delimiter = weechat.color('chat_delimiters') + color_script_nick = weechat.color('chat_nick') + + # pretty [grep] + script_nick = '%s[%s%s%s]%s' %(color_delimiter, color_script_nick, SCRIPT_NAME, color_delimiter, + color_reset) + script_nick_nocolor = '[%s]' %SCRIPT_NAME + # paragraph separator when using context options + context_sep = '%s\t%s--' %(script_nick, color_info) + + # ------------------------------------------------------------------------- + # Debug + + if weechat.config_get_plugin('debug'): + try: + # custom debug module I use, allows me to inspect script's objects. + import pybuffer + debug = pybuffer.debugBuffer(globals(), '%s_debug' % SCRIPT_NAME) + except: + def debug(s, *args): + if not isinstance(s, basestring): + s = str(s) + if args: + s = s %args + prnt('', '%s\t%s' %(script_nick, s)) + else: + def debug(*args): + pass + +# vim:set shiftwidth=4 tabstop=4 softtabstop=4 expandtab textwidth=100: |