1 # -*- coding: utf-8 -*-
3 # Copyright (c) 2009-2011 by Elián Hanisch <lambdae2@gmail.com>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 # Search in Weechat buffers and logs (for Weechat 0.3.*)
22 # Inspired by xt's grep.py
23 # Originally I just wanted to add some fixes in grep.py, but then
24 # I got carried away and rewrote everything, so new script.
28 # Search in logs or buffers, see /help grep
30 # Lists logs in ~/.weechat/logs, see /help logs
33 # * plugins.var.python.grep.clear_buffer:
34 # Clear the results buffer before each search. Valid values: on, off
36 # * plugins.var.python.grep.go_to_buffer:
37 # Automatically go to grep buffer when search is over. Valid values: on, off
39 # * plugins.var.python.grep.log_filter:
40 # Coma separated list of patterns that grep will use for exclude logs, e.g.
41 # if you use '*server/*' any log in the 'server' folder will be excluded
42 # when using the command '/grep log'
44 # * plugins.var.python.grep.show_summary:
45 # Shows summary for each log. Valid values: on, off
47 # * plugins.var.python.grep.max_lines:
48 # Grep will only print the last matched lines that don't surpass the value defined here.
50 # * plugins.var.python.grep.size_limit:
51 # Size limit in KiB, is used for decide whenever grepping should run in background or not. If
52 # the logs to grep have a total size bigger than this value then grep run as a new process.
53 # It can be used for force or disable background process, using '0' forces to always grep in
54 # background, while using '' (empty string) will disable it.
56 # * plugins.var.python.grep.timeout_secs:
57 # Timeout (in seconds) for background grepping.
59 # * plugins.var.python.grep.default_tail_head:
60 # Config option for define default number of lines returned when using --head or --tail options.
61 # Can be overriden in the command with --number option.
65 # * try to figure out why hook_process chokes in long outputs (using a tempfile as a
67 # * possibly add option for defining time intervals
72 # 2022-11-11, anonymous2ch
73 # version 0.8.6: ignore utf-8 decoding errors
75 # 2021-05-02, Sébastien Helleu <flashcode@flashtux.org>
76 # version 0.8.5: add compatibility with WeeChat >= 3.2 (XDG directories)
78 # 2020-10-11, Thom Wiggers <thom@thomwiggers.nl>
79 # version 0.8.4: Python3 compatibility fix
81 # 2020-05-06, Dominique Martinet <asmadeus@codewreck.org> and hexa-
82 # version 0.8.3: more python3 compatibility fixes...
84 # 2019-06-30, dabbill <dabbill@gmail.com>
85 # and Sébastien Helleu <flashcode@flashtux.org>
86 # version 0.8.2: make script compatible with Python 3
88 # 2018-04-10, Sébastien Helleu <flashcode@flashtux.org>
89 # version 0.8.1: fix infolist_time for WeeChat >= 2.2 (WeeChat returns a long
90 # integer instead of a string)
92 # 2017-09-20, mickael9
94 # * use weechat 1.5+ api for background processing (old method was unsafe and buggy)
95 # * add timeout_secs setting (was previously hardcoded to 5 mins)
97 # 2017-07-23, Sébastien Helleu <flashcode@flashtux.org>
98 # version 0.7.8: fix modulo by zero when nick is empty string
100 # 2016-06-23, mickael9
101 # version 0.7.7: fix get_home function
104 # version 0.7.6: fix a typo
108 # '~' is now expaned to the home directory in the log file path so
109 # paths like '~/logs/' should work.
112 # version 0.7.4: make q work to quit grep buffer (requested by: gb)
114 # 2014-03-29, Felix Eckhofer <felix@tribut.de>
115 # version 0.7.3: fix typo
118 # version 0.7.2: bug fixes
122 # * use TempFile so temporal files are guaranteed to be deleted.
123 # * enable Archlinux workaround.
128 # * using --only-match shows only unique strings.
129 # * fixed bug that inverted -B -A switches when used with -t
132 # version 0.6.8: by xt <xt@bash.no>
133 # * supress highlights when printing in grep buffer
136 # version 0.6.7: by xt <xt@bash.no>
137 # * better temporary file:
138 # use tempfile.mkstemp. to create a temp file in log dir,
139 # makes it safer with regards to write permission and multi user
142 # version 0.6.6: bug fixes
143 # * use WEECHAT_LIST_POS_END in log file completion, makes completion faster
144 # * disable bytecode if using python 2.6
145 # * use single quotes in command string
146 # * fix bug that could change buffer's title when using /grep stop
149 # version 0.6.5: disable bytecode is a 2.6 feature, instead, resort to delete the bytecode manually
152 # version 0.6.4: bug fix
153 # version 0.6.3: added options --invert --only-match (replaces --exact, which is still available
154 # but removed from help)
155 # * use new 'irc_nick_color' info
156 # * don't generate bytecode when spawning a new process
157 # * show active options in buffer title
160 # version 0.6.2: removed 2.6-ish code
161 # version 0.6.1: fixed bug when grepping in grep's buffer
164 # version 0.6.0: implemented grep in background
165 # * improved context lines presentation.
166 # * grepping for big (or many) log files runs in a weechat_process.
167 # * added /grep stop.
168 # * added 'size_limit' option
169 # * fixed a infolist leak when grepping buffers
170 # * added 'default_tail_head' option
171 # * results are sort by line count
172 # * don't die if log is corrupted (has NULL chars in it)
173 # * changed presentation of /logs
174 # * log path completion doesn't suck anymore
175 # * removed all tabs, because I learned how to configure Vim so that spaces aren't annoying
176 # anymore. This was the script's original policy.
179 # version 0.5.5: rename script to 'grep.py' (FlashCode <flashcode@flashtux.org>).
182 # version 0.5.4.1: fix index error when using --after/before-context options.
185 # version 0.5.4: new features
186 # * added --after-context and --before-context options.
187 # * added --context as a shortcut for using both -A -B options.
190 # version 0.5.3: improvements for long grep output
191 # * grep buffer input accepts the same flags as /grep for repeat a search with different
193 # * tweaks in grep's output.
194 # * max_lines option added for limit grep's output.
195 # * code in update_buffer() optimized.
196 # * time stats in buffer title.
197 # * added go_to_buffer config option.
198 # * added --buffer for search only in buffers.
202 # version 0.5.2: made it python-2.4.x compliant
205 # version 0.5.1: some refactoring, show_summary option added.
208 # version 0.5: rewritten from xt's grep.py
209 # * fixed searching in non weechat logs, for cases like, if you're
210 # switching from irssi and rename and copy your irssi logs to %h/logs
211 # * fixed "timestamp rainbow" when you /grep in grep's buffer
212 # * allow to search in other buffers other than current or in logs
213 # of currently closed buffers with cmd 'buffer'
214 # * allow to search in any log file in %h/logs with cmd 'log'
215 # * added --count for return the number of matched lines
216 # * added --matchcase for case sensible search
217 # * added --hilight for color matches
218 # * added --head and --tail options, and --number
219 # * added command /logs for list files in %h/logs
220 # * added config option for clear the buffer before a search
221 # * added config option for filter logs we don't want to grep
222 # * added the posibility to repeat last search with another regexp by writing
223 # it in grep's buffer
224 # * changed spaces for tabs in the code, which is my preference
229 import sys
, getopt
, time
, os
, re
232 import cPickle
as pickle
238 from weechat
import WEECHAT_RC_OK
, prnt
, prnt_date_tags
244 SCRIPT_AUTHOR
= "Elián Hanisch <lambdae2@gmail.com>"
245 SCRIPT_VERSION
= "0.8.6"
246 SCRIPT_LICENSE
= "GPL3"
247 SCRIPT_DESC
= "Search in buffers and logs"
248 SCRIPT_COMMAND
= "grep"
250 ### Default Settings ###
252 'clear_buffer' : 'off',
254 'go_to_buffer' : 'on',
255 'max_lines' : '4000',
256 'show_summary' : 'on',
257 'size_limit' : '2048',
258 'default_tail_head' : '10',
259 'timeout_secs' : '300',
262 ### Class definitions ###
263 class linesDict(dict):
265 Class for handling matched lines in more than one buffer.
266 linesDict[buffer_name] = matched_lines_list
268 def __setitem__(self
, key
, value
):
269 assert isinstance(value
, list)
271 dict.__setitem
__(self
, key
, value
)
273 dict.__getitem
__(self
, key
).extend(value
)
275 def get_matches_count(self
):
276 """Return the sum of total matches stored."""
277 if dict.__len
__(self
):
278 return sum(map(lambda L
: L
.matches_count
, self
.values()))
283 """Return the sum of total lines stored."""
284 if dict.__len
__(self
):
285 return sum(map(len, self
.values()))
290 """Returns buffer count or buffer name if there's just one stored."""
293 return list(self
.keys())[0]
300 """Returns a list of items sorted by line count."""
301 items
= list(dict.items(self
))
302 items
.sort(key
=lambda i
: len(i
[1]))
305 def items_count(self
):
306 """Returns a list of items sorted by match count."""
307 items
= list(dict.items(self
))
308 items
.sort(key
=lambda i
: i
[1].matches_count
)
311 def strip_separator(self
):
312 for L
in self
.values():
315 def get_last_lines(self
, n
):
316 total_lines
= len(self
)
317 #debug('total: %s n: %s' %(total_lines, n))
321 for k
, v
in reversed(list(self
.items())):
326 v
.stripped_lines
= l
-n
332 class linesList(list):
333 """Class for list of matches, since sometimes I need to add lines that aren't matches, I need an
334 independent counter."""
336 def __init__(self
, *args
):
337 list.__init
__(self
, *args
)
338 self
.matches_count
= 0
339 self
.stripped_lines
= 0
341 def append(self
, item
):
342 """Append lines, can be a string or a list with strings."""
343 if isinstance(item
, str):
344 list.append(self
, item
)
348 def append_separator(self
):
349 """adds a separator into the list, makes sure it doen't add two together."""
351 if (self
and self
[-1] != s
) or not self
:
359 def count_match(self
, item
=None):
360 if item
is None or isinstance(item
, str):
361 self
.matches_count
+= 1
363 self
.matches_count
+= len(item
)
365 def strip_separator(self
):
366 """removes separators if there are first or/and last in the list."""
374 ### Misc functions ###
378 return os
.stat(f
).st_size
382 sizeDict
= {0:'b', 1:'KiB', 2:'MiB', 3:'GiB', 4:'TiB'}
383 def human_readable_size(size
):
388 return '%.2f %s' %(size
, sizeDict
.get(power
, ''))
390 def color_nick(nick
):
391 """Returns coloured nick, with coloured mode if any."""
392 if not nick
: return ''
393 wcolor
= weechat
.color
394 config_string
= lambda s
: weechat
.config_string(weechat
.config_get(s
))
395 config_int
= lambda s
: weechat
.config_integer(weechat
.config_get(s
))
397 prefix
= config_string('irc.look.nick_prefix')
398 suffix
= config_string('irc.look.nick_suffix')
399 prefix_c
= suffix_c
= wcolor(config_string('weechat.color.chat_delimiters'))
400 if nick
[0] == prefix
:
403 prefix
= prefix_c
= ''
404 if nick
[-1] == suffix
:
406 suffix
= wcolor(color_delimiter
) + suffix
408 suffix
= suffix_c
= ''
412 mode
, nick
= nick
[0], nick
[1:]
413 mode_color
= wcolor(config_string('weechat.color.nicklist_prefix%d' \
414 %(modes
.find(mode
) + 1)))
416 mode
= mode_color
= ''
420 nick_color
= weechat
.info_get('irc_nick_color', nick
)
422 # probably we're in WeeChat 0.3.0
423 #debug('no irc_nick_color')
424 color_nicks_number
= config_int('weechat.look.color_nicks_number')
425 idx
= (sum(map(ord, nick
))%color
_nicks
_number
) + 1
426 nick_color
= wcolor(config_string('weechat.color.chat_nick_color%02d' %idx))
427 return ''.join((prefix_c
, prefix
, mode_color
, mode
, nick_color
, nick
, suffix_c
, suffix
))
429 ### Config and value validation ###
430 boolDict
= {'on':True, 'off':False}
431 def get_config_boolean(config
):
432 value
= weechat
.config_get_plugin(config
)
434 return boolDict
[value
]
436 default
= settings
[config
]
437 error("Error while fetching config '%s'. Using default value '%s'." %(config
, default
))
438 error("'%s' is invalid, allowed: 'on', 'off'" %value
)
439 return boolDict
[default
]
441 def get_config_int(config
, allow_empty_string
=False):
442 value
= weechat
.config_get_plugin(config
)
446 if value
== '' and allow_empty_string
:
448 default
= settings
[config
]
449 error("Error while fetching config '%s'. Using default value '%s'." %(config
, default
))
450 error("'%s' is not a number." %value
)
453 def get_config_log_filter():
454 filter = weechat
.config_get_plugin('log_filter')
456 return filter.split(',')
464 home
= weechat
.string_eval_path_home(
465 weechat
.config_string(weechat
.config_get('logger.file.path')),
470 def strip_home(s
, dir=''):
471 """Strips home dir from the begging of the log path, this makes them sorter."""
481 script_nick
= SCRIPT_NAME
482 def error(s
, buffer=''):
484 prnt(buffer, '%s%s %s' %(weechat
.prefix('error'), script_nick
, s
))
485 if weechat
.config_get_plugin('debug'):
487 if traceback
.sys
.exc_type
:
488 trace
= traceback
.format_exc()
491 def say(s
, buffer=''):
493 prnt_date_tags(buffer, 0, 'no_highlight', '%s\t%s' %(script_nick
, s
))
497 ### Log files and buffers ###
498 cache_dir
= {} # note: don't remove, needed for completion if the script was loaded recently
499 def dir_list(dir, filter_list
=(), filter_excludes
=True, include_dir
=False):
500 """Returns a list of files in 'dir' and its subdirs."""
503 from fnmatch
import fnmatch
504 #debug('dir_list: listing in %s' %dir)
505 key
= (dir, include_dir
)
507 return cache_dir
[key
]
511 filter_list
= filter_list
or get_config_log_filter()
515 file = file[dir_len
:] # pattern shouldn't match home dir
516 for pattern
in filter_list
:
517 if fnmatch(file, pattern
):
518 return filter_excludes
519 return not filter_excludes
521 filter = lambda f
: not filter_excludes
524 extend
= file_list
.extend
527 for basedir
, subdirs
, files
in walk(dir):
529 # subdirs = map(lambda s : join(s, ''), subdirs)
530 # files.extend(subdirs)
531 files_path
= map(lambda f
: join(basedir
, f
), files
)
532 files_path
= [ file for file in files_path
if not filter(file) ]
536 cache_dir
[key
] = file_list
537 #debug('dir_list: got %s' %str(file_list))
540 def get_file_by_pattern(pattern
, all
=False):
541 """Returns the first log whose path matches 'pattern',
542 if all is True returns all logs that matches."""
543 if not pattern
: return []
544 #debug('get_file_by_filename: searching for %s.' %pattern)
545 # do envvar expandsion and check file
546 file = path
.expanduser(pattern
)
547 file = path
.expandvars(file)
548 if path
.isfile(file):
550 # lets see if there's a matching log
552 file = path
.join(home_dir
, pattern
)
553 if path
.isfile(file):
556 from fnmatch
import fnmatch
558 file_list
= dir_list(home_dir
)
560 for log
in file_list
:
562 if fnmatch(basename
, pattern
):
564 #debug('get_file_by_filename: got %s.' %file)
570 def get_file_by_buffer(buffer):
571 """Given buffer pointer, finds log's path or returns None."""
572 #debug('get_file_by_buffer: searching for %s' %buffer)
573 infolist
= weechat
.infolist_get('logger_buffer', '', '')
574 if not infolist
: return
576 while weechat
.infolist_next(infolist
):
577 pointer
= weechat
.infolist_pointer(infolist
, 'buffer')
578 if pointer
== buffer:
579 file = weechat
.infolist_string(infolist
, 'log_filename')
580 if weechat
.infolist_integer(infolist
, 'log_enabled'):
581 #debug('get_file_by_buffer: got %s' %file)
584 # debug('get_file_by_buffer: got %s but log not enabled' %file)
586 #debug('infolist gets freed')
587 weechat
.infolist_free(infolist
)
589 def get_file_by_name(buffer_name
):
590 """Given a buffer name, returns its log path or None. buffer_name should be in 'server.#channel'
591 or '#channel' format."""
592 #debug('get_file_by_name: searching for %s' %buffer_name)
593 # common mask options
594 config_masks
= ('logger.mask.irc', 'logger.file.mask')
595 # since there's no buffer pointer, we try to replace some local vars in mask, like $channel and
596 # $server, then replace the local vars left with '*', and use it as a mask for get the path with
597 # get_file_by_pattern
598 for config
in config_masks
:
599 mask
= weechat
.config_string(weechat
.config_get(config
))
600 #debug('get_file_by_name: mask: %s' %mask)
602 mask
= mask
.replace('$name', buffer_name
)
603 elif '$channel' in mask
or '$server' in mask
:
604 if '.' in buffer_name
and \
605 '#' not in buffer_name
[:buffer_name
.find('.')]: # the dot isn't part of the channel name
606 # ^ I'm asuming channel starts with #, i'm lazy.
607 server
, channel
= buffer_name
.split('.', 1)
609 server
, channel
= '*', buffer_name
610 if '$channel' in mask
:
611 mask
= mask
.replace('$channel', channel
)
612 if '$server' in mask
:
613 mask
= mask
.replace('$server', server
)
614 # change the unreplaced vars by '*'
616 from string
import letters
618 from string
import ascii_letters
as letters
620 # vars for time formatting
621 mask
= mask
.replace('%', '$')
623 masks
= mask
.split('$')
624 masks
= map(lambda s
: s
.lstrip(letters
), masks
)
625 mask
= '*'.join(masks
)
628 #debug('get_file_by_name: using mask %s' %mask)
629 file = get_file_by_pattern(mask
)
630 #debug('get_file_by_name: got file %s' %file)
635 def get_buffer_by_name(buffer_name
):
636 """Given a buffer name returns its buffer pointer or None."""
637 #debug('get_buffer_by_name: searching for %s' %buffer_name)
638 pointer
= weechat
.buffer_search('', buffer_name
)
641 infolist
= weechat
.infolist_get('buffer', '', '')
642 while weechat
.infolist_next(infolist
):
643 short_name
= weechat
.infolist_string(infolist
, 'short_name')
644 name
= weechat
.infolist_string(infolist
, 'name')
645 if buffer_name
in (short_name
, name
):
646 #debug('get_buffer_by_name: found %s' %name)
647 pointer
= weechat
.buffer_search('', name
)
650 weechat
.infolist_free(infolist
)
651 #debug('get_buffer_by_name: got %s' %pointer)
654 def get_all_buffers():
655 """Returns list with pointers of all open buffers."""
657 infolist
= weechat
.infolist_get('buffer', '', '')
658 while weechat
.infolist_next(infolist
):
659 buffers
.append(weechat
.infolist_pointer(infolist
, 'pointer'))
660 weechat
.infolist_free(infolist
)
661 grep_buffer
= weechat
.buffer_search('python', SCRIPT_NAME
)
662 if grep_buffer
and grep_buffer
in buffers
:
663 # remove it from list
664 del buffers
[buffers
.index(grep_buffer
)]
668 def make_regexp(pattern
, matchcase
=False):
669 """Returns a compiled regexp."""
670 if pattern
in ('.', '.*', '.?', '.+'):
671 # because I don't need to use a regexp if we're going to match all lines
673 # matching takes a lot more time if pattern starts or ends with .* and it isn't needed.
674 if pattern
[:2] == '.*':
675 pattern
= pattern
[2:]
676 if pattern
[-2:] == '.*':
677 pattern
= pattern
[:-2]
680 regexp
= re
.compile(pattern
, re
.IGNORECASE
)
682 regexp
= re
.compile(pattern
)
683 except Exception as e
:
684 raise Exception('Bad pattern, %s' % e
)
687 def check_string(s
, regexp
, hilight
='', exact
=False):
688 """Checks 's' with a regexp and returns it if is a match."""
693 matchlist
= regexp
.findall(s
)
695 if isinstance(matchlist
[0], tuple):
696 # join tuples (when there's more than one match group in regexp)
697 return [ ' '.join(t
) for t
in matchlist
]
701 matchlist
= regexp
.findall(s
)
703 if isinstance(matchlist
[0], tuple):
705 matchlist
= [ item
for L
in matchlist
for item
in L
if item
]
706 matchlist
= list(set(matchlist
)) # remove duplicates if any
708 color_hilight
, color_reset
= hilight
.split(',', 1)
710 s
= s
.replace(m
, '%s%s%s' % (color_hilight
, m
, color_reset
))
713 # no need for findall() here
714 elif regexp
.search(s
):
717 def grep_file(file, head
, tail
, after_context
, before_context
, count
, regexp
, hilight
, exact
, invert
):
718 """Return a list of lines that match 'regexp' in 'file', if no regexp returns all lines."""
720 tail
= head
= after_context
= before_context
= False
723 before_context
= after_context
= False
727 #debug(' '.join(map(str, (file, head, tail, after_context, before_context))))
730 # define these locally as it makes the loop run slightly faster
731 append
= lines
.append
732 count_match
= lines
.count_match
733 separator
= lines
.append_separator
736 if check_string(s
, regexp
, hilight
, exact
):
741 check
= lambda s
: check_string(s
, regexp
, hilight
, exact
)
744 file_object
= open(file, 'r', errors
='ignore')
748 if tail
or before_context
:
749 # for these options, I need to seek in the file, but is slower and uses a good deal of
750 # memory if the log is too big, so we do this *only* for these options.
751 file_lines
= file_object
.readlines()
754 # instead of searching in the whole file and later pick the last few lines, we
755 # reverse the log, search until count reached and reverse it again, that way is a lot
758 # don't invert context switches
759 before_context
, after_context
= after_context
, before_context
762 before_context_range
= list(range(1, before_context
+ 1))
763 before_context_range
.reverse()
768 while line_idx
< len(file_lines
):
769 line
= file_lines
[line_idx
]
775 for id in before_context_range
:
777 context_line
= file_lines
[line_idx
- id]
778 if check(context_line
):
779 # match in before context, that means we appended these same lines in a
780 # previous match, so we delete them merging both paragraphs
782 del lines
[id - before_context
- 1:]
792 while id < after_context
+ offset
:
795 context_line
= file_lines
[line_idx
+ id]
796 _context_line
= check(context_line
)
799 context_line
= _context_line
# so match is hilighted with --hilight
806 if limit
and lines
.matches_count
>= limit
:
816 for line
in file_object
:
819 count
or append(line
)
823 while id < after_context
+ offset
:
826 context_line
= next(file_object
)
827 _context_line
= check(context_line
)
830 context_line
= _context_line
832 count
or append(context_line
)
833 except StopIteration:
836 if limit
and lines
.matches_count
>= limit
:
842 def grep_buffer(buffer, head
, tail
, after_context
, before_context
, count
, regexp
, hilight
, exact
,
844 """Return a list of lines that match 'regexp' in 'buffer', if no regexp returns all lines."""
847 tail
= head
= after_context
= before_context
= False
850 before_context
= after_context
= False
851 #debug(' '.join(map(str, (tail, head, after_context, before_context, count, exact, hilight))))
853 # Using /grep in grep's buffer can lead to some funny effects
854 # We should take measures if that's the case
855 def make_get_line_funcion():
856 """Returns a function for get lines from the infolist, depending if the buffer is grep's or
858 string_remove_color
= weechat
.string_remove_color
859 infolist_string
= weechat
.infolist_string
860 grep_buffer
= weechat
.buffer_search('python', SCRIPT_NAME
)
861 if grep_buffer
and buffer == grep_buffer
:
862 def function(infolist
):
863 prefix
= infolist_string(infolist
, 'prefix')
864 message
= infolist_string(infolist
, 'message')
865 if prefix
: # only our messages have prefix, ignore it
869 infolist_time
= weechat
.infolist_time
870 def function(infolist
):
871 prefix
= string_remove_color(infolist_string(infolist
, 'prefix'), '')
872 message
= string_remove_color(infolist_string(infolist
, 'message'), '')
873 date
= infolist_time(infolist
, 'date')
874 # since WeeChat 2.2, infolist_time returns a long integer
875 # instead of a string
876 if not isinstance(date
, str):
877 date
= time
.strftime('%F %T', time
.localtime(int(date
)))
878 return '%s\t%s\t%s' %(date
, prefix
, message
)
880 get_line
= make_get_line_funcion()
882 infolist
= weechat
.infolist_get('buffer_lines', buffer, '')
884 # like with grep_file() if we need the last few matching lines, we move the cursor to
885 # the end and search backwards
886 infolist_next
= weechat
.infolist_prev
887 infolist_prev
= weechat
.infolist_next
889 infolist_next
= weechat
.infolist_next
890 infolist_prev
= weechat
.infolist_prev
893 # define these locally as it makes the loop run slightly faster
894 append
= lines
.append
895 count_match
= lines
.count_match
896 separator
= lines
.append_separator
899 if check_string(s
, regexp
, hilight
, exact
):
904 check
= lambda s
: check_string(s
, regexp
, hilight
, exact
)
907 before_context_range
= reversed(range(1, before_context
+ 1))
909 while infolist_next(infolist
):
910 line
= get_line(infolist
)
911 if line
is None: continue
917 for id in before_context_range
:
918 if not infolist_prev(infolist
):
920 for id in before_context_range
:
921 context_line
= get_line(infolist
)
922 if check(context_line
):
924 del lines
[id - before_context
- 1:]
928 infolist_next(infolist
)
929 count
or append(line
)
933 while id < after_context
+ offset
:
935 if infolist_next(infolist
):
936 context_line
= get_line(infolist
)
937 _context_line
= check(context_line
)
939 context_line
= _context_line
944 # in the main loop infolist_next will start again an cause an infinite loop
946 infolist_next
= lambda x
: 0
948 if limit
and lines
.matches_count
>= limit
:
950 weechat
.infolist_free(infolist
)
956 ### this is our main grep function
957 hook_file_grep
= None
958 def show_matching_lines():
960 Greps buffers in search_in_buffers or files in search_in_files and updates grep buffer with the
963 global pattern
, matchcase
, number
, count
, exact
, hilight
, invert
964 global tail
, head
, after_context
, before_context
965 global search_in_files
, search_in_buffers
, matched_lines
, home_dir
967 matched_lines
= linesDict()
968 #debug('buffers:%s \nlogs:%s' %(search_in_buffers, search_in_files))
972 if search_in_buffers
:
973 regexp
= make_regexp(pattern
, matchcase
)
974 for buffer in search_in_buffers
:
975 buffer_name
= weechat
.buffer_get_string(buffer, 'name')
976 matched_lines
[buffer_name
] = grep_buffer(buffer, head
, tail
, after_context
,
977 before_context
, count
, regexp
, hilight
, exact
, invert
)
981 size_limit
= get_config_int('size_limit', allow_empty_string
=True)
983 if size_limit
or size_limit
== 0:
984 size
= sum(map(get_size
, search_in_files
))
985 if size
> size_limit
* 1024:
987 elif size_limit
== '':
990 regexp
= make_regexp(pattern
, matchcase
)
992 global grep_options
, log_pairs
993 grep_options
= (head
, tail
, after_context
, before_context
,
994 count
, regexp
, hilight
, exact
, invert
)
996 log_pairs
= [(strip_home(log
), log
) for log
in search_in_files
]
1000 for log_name
, log
in log_pairs
:
1001 matched_lines
[log_name
] = grep_file(log
, *grep_options
)
1004 global hook_file_grep
, grep_stdout
, grep_stderr
, pattern_tmpl
1005 grep_stdout
= grep_stderr
= b
''
1006 hook_file_grep
= weechat
.hook_process(
1007 'func:grep_process',
1008 get_config_int('timeout_secs') * 1000,
1013 buffer_create("Searching for '%s' in %s worth of data..." % (
1015 human_readable_size(size
)
1021 def grep_process(*args
):
1024 global grep_options
, log_pairs
1025 for log_name
, log
in log_pairs
:
1026 result
[log_name
] = grep_file(log
, *grep_options
)
1027 except Exception as e
:
1030 return pickle
.dumps(result
, 0)
1032 def grep_process_cb(data
, command
, return_code
, out
, err
):
1033 global grep_stdout
, grep_stderr
, matched_lines
, hook_file_grep
1035 if isinstance(out
, str):
1039 if isinstance(err
, str):
1043 def set_buffer_error(message
):
1045 grep_buffer
= buffer_create()
1046 title
= weechat
.buffer_get_string(grep_buffer
, 'title')
1047 title
= title
+ ' %serror' % color_title
1048 weechat
.buffer_set(grep_buffer
, 'title', title
)
1050 if return_code
== weechat
.WEECHAT_HOOK_PROCESS_ERROR
:
1051 set_buffer_error("Background grep timed out")
1052 hook_file_grep
= None
1053 return WEECHAT_RC_OK
1055 elif return_code
>= 0:
1056 hook_file_grep
= None
1058 set_buffer_error(grep_stderr
)
1059 return WEECHAT_RC_OK
1062 data
= pickle
.loads(grep_stdout
)
1063 if isinstance(data
, Exception):
1065 matched_lines
.update(data
)
1066 except Exception as e
:
1067 set_buffer_error(repr(e
))
1068 return WEECHAT_RC_OK
1072 return WEECHAT_RC_OK
1074 def get_grep_file_status():
1075 global search_in_files
, matched_lines
, time_start
1076 elapsed
= now() - time_start
1077 if len(search_in_files
) == 1:
1078 log
= '%s (%s)' %(strip_home(search_in_files
[0]),
1079 human_readable_size(get_size(search_in_files
[0])))
1081 size
= sum(map(get_size
, search_in_files
))
1082 log
= '%s log files (%s)' %(len(search_in_files
), human_readable_size(size
))
1083 return 'Searching in %s, running for %.4f seconds. Interrupt it with "/grep stop" or "stop"' \
1084 ' in grep buffer.' %(log
, elapsed
)
1087 def buffer_update():
1088 """Updates our buffer with new lines."""
1089 global pattern_tmpl
, matched_lines
, pattern
, count
, hilight
, invert
, exact
1092 buffer = buffer_create()
1093 if get_config_boolean('clear_buffer'):
1094 weechat
.buffer_clear(buffer)
1095 matched_lines
.strip_separator() # remove first and last separators of each list
1096 len_total_lines
= len(matched_lines
)
1097 max_lines
= get_config_int('max_lines')
1098 if not count
and len_total_lines
> max_lines
:
1099 weechat
.buffer_clear(buffer)
1101 def _make_summary(log
, lines
, note
):
1102 return '%s matches "%s%s%s"%s in %s%s%s%s' \
1103 %(lines
.matches_count
, color_summary
, pattern_tmpl
, color_info
,
1104 invert
and ' (inverted)' or '',
1105 color_summary
, log
, color_reset
, note
)
1108 make_summary
= lambda log
, lines
: _make_summary(log
, lines
, ' (not shown)')
1110 def make_summary(log
, lines
):
1111 if lines
.stripped_lines
:
1113 note
= ' (last %s lines shown)' %len(lines
)
1115 note
= ' (not shown)'
1118 return _make_summary(log
, lines
, note
)
1120 global weechat_format
1122 # we don't want colors if there's match highlighting
1123 format_line
= lambda s
: '%s %s %s' %split
_line
(s
)
1126 global nick_dict
, weechat_format
1127 date
, nick
, msg
= split_line(s
)
1130 nick
= nick_dict
[nick
]
1133 nick_c
= color_nick(nick
)
1134 nick_dict
[nick
] = nick_c
1136 return '%s%s %s%s %s' %(color_date
, date
, nick
, color_reset
, msg
)
1142 print_line('Search for "%s%s%s"%s in %s%s%s.' %(color_summary
, pattern_tmpl
, color_info
,
1143 invert
and ' (inverted)' or '', color_summary
, matched_lines
, color_reset
),
1145 # print last <max_lines> lines
1146 if matched_lines
.get_matches_count():
1148 # with count we sort by matches lines instead of just lines.
1149 matched_lines_items
= matched_lines
.items_count()
1151 matched_lines_items
= matched_lines
.items()
1153 matched_lines
.get_last_lines(max_lines
)
1154 for log
, lines
in matched_lines_items
:
1155 if lines
.matches_count
:
1159 weechat_format
= True
1164 if line
== linesList
._sep
:
1166 prnt(buffer, context_sep
)
1170 error("Found garbage in log '%s', maybe it's corrupted" %log
)
1171 line
= line
.replace('\x00', '')
1172 prnt_date_tags(buffer, 0, 'no_highlight', format_line(line
))
1175 if count
or get_config_boolean('show_summary'):
1176 summary
= make_summary(log
, lines
)
1177 print_line(summary
, buffer)
1180 if not count
and lines
:
1183 print_line('No matches found.', buffer)
1189 time_total
= time_end
- time_start
1190 # percent of the total time used for grepping
1191 time_grep_pct
= (time_grep
- time_start
)/time_total
*100
1192 #debug('time: %.4f seconds (%.2f%%)' %(time_total, time_grep_pct))
1193 if not count
and len_total_lines
> max_lines
:
1194 note
= ' (last %s lines shown)' %len(matched_lines
)
1197 title
= "'q': close buffer | Search in %s%s%s %s matches%s | pattern \"%s%s%s\"%s %s | %.4f seconds (%.2f%%)" \
1198 %(color_title
, matched_lines
, color_reset
, matched_lines
.get_matches_count(), note
,
1199 color_title
, pattern_tmpl
, color_reset
, invert
and ' (inverted)' or '', format_options(),
1200 time_total
, time_grep_pct
)
1201 weechat
.buffer_set(buffer, 'title', title
)
1203 if get_config_boolean('go_to_buffer'):
1204 weechat
.buffer_set(buffer, 'display', '1')
1206 # free matched_lines so it can be removed from memory
1210 """Splits log's line 's' in 3 parts, date, nick and msg."""
1211 global weechat_format
1212 if weechat_format
and s
.count('\t') >= 2:
1213 date
, nick
, msg
= s
.split('\t', 2) # date, nick, message
1215 # looks like log isn't in weechat's format
1216 weechat_format
= False # incoming lines won't be formatted
1217 date
, nick
, msg
= '', '', s
1220 msg
= msg
.replace('\t', ' ')
1221 return date
, nick
, msg
1223 def print_line(s
, buffer=None, display
=False):
1224 """Prints 's' in script's buffer as 'script_nick'. For displaying search summaries."""
1226 buffer = buffer_create()
1227 say('%s%s' %(color_info
, s
), buffer)
1228 if display
and get_config_boolean('go_to_buffer'):
1229 weechat
.buffer_set(buffer, 'display', '1')
1231 def format_options():
1232 global matchcase
, number
, count
, exact
, hilight
, invert
1233 global tail
, head
, after_context
, before_context
1235 append
= options
.append
1236 insert
= options
.insert
1238 for i
, flag
in enumerate((count
, hilight
, matchcase
, exact
, invert
)):
1243 n
= get_config_int('default_tail_head')
1257 if before_context
and after_context
and (before_context
== after_context
):
1259 append(before_context
)
1263 append(before_context
)
1266 append(after_context
)
1268 s
= ''.join(map(str, options
)).strip()
1269 if s
and s
[0] != '-':
1273 def buffer_create(title
=None):
1274 """Returns our buffer pointer, creates and cleans the buffer if needed."""
1275 buffer = weechat
.buffer_search('python', SCRIPT_NAME
)
1277 buffer = weechat
.buffer_new(SCRIPT_NAME
, 'buffer_input', '', '', '')
1278 weechat
.buffer_set(buffer, 'time_for_each_line', '0')
1279 weechat
.buffer_set(buffer, 'nicklist', '0')
1280 weechat
.buffer_set(buffer, 'title', title
or 'grep output buffer')
1281 weechat
.buffer_set(buffer, 'localvar_set_no_log', '1')
1283 weechat
.buffer_set(buffer, 'title', title
)
1286 def buffer_input(data
, buffer, input_data
):
1287 """Repeats last search with 'input_data' as regexp."""
1289 cmd_grep_stop(buffer, input_data
)
1291 return WEECHAT_RC_OK
1292 if input_data
in ('q', 'Q'):
1293 weechat
.buffer_close(buffer)
1294 return weechat
.WEECHAT_RC_OK
1296 global search_in_buffers
, search_in_files
1299 if pattern
and (search_in_files
or search_in_buffers
):
1300 # check if the buffer pointers are still valid
1301 for pointer
in search_in_buffers
:
1302 infolist
= weechat
.infolist_get('buffer', pointer
, '')
1304 del search_in_buffers
[search_in_buffers
.index(pointer
)]
1305 weechat
.infolist_free(infolist
)
1307 cmd_grep_parsing(input_data
)
1308 except Exception as e
:
1309 error('Argument error, %s' % e
, buffer=buffer)
1310 return WEECHAT_RC_OK
1312 show_matching_lines()
1313 except Exception as e
:
1316 error("There isn't any previous search to repeat.", buffer=buffer)
1317 return WEECHAT_RC_OK
1321 """Resets global vars."""
1322 global home_dir
, cache_dir
, nick_dict
1323 global pattern_tmpl
, pattern
, matchcase
, number
, count
, exact
, hilight
, invert
1324 global tail
, head
, after_context
, before_context
1326 head
= tail
= after_context
= before_context
= invert
= False
1327 matchcase
= count
= exact
= False
1328 pattern_tmpl
= pattern
= number
= None
1329 home_dir
= get_home()
1330 cache_dir
= {} # for avoid walking the dir tree more than once per command
1331 nick_dict
= {} # nick cache for don't calculate nick color every time
1333 def cmd_grep_parsing(args
):
1334 """Parses args for /grep and grep input buffer."""
1335 global pattern_tmpl
, pattern
, matchcase
, number
, count
, exact
, hilight
, invert
1336 global tail
, head
, after_context
, before_context
1337 global log_name
, buffer_name
, only_buffers
, all
1338 opts
, args
= getopt
.gnu_getopt(args
.split(), 'cmHeahtivn:bA:B:C:o', ['count', 'matchcase', 'hilight',
1339 'exact', 'all', 'head', 'tail', 'number=', 'buffer', 'after-context=', 'before-context=',
1340 'context=', 'invert', 'only-match'])
1341 #debug(opts, 'opts: '); debug(args, 'args: ')
1343 if args
[0] == 'log':
1345 log_name
= args
.pop(0)
1346 elif args
[0] == 'buffer':
1348 buffer_name
= args
.pop(0)
1350 def tmplReplacer(match
):
1351 """This function will replace templates with regexps"""
1352 s
= match
.groups()[0]
1353 tmpl_args
= s
.split()
1354 tmpl_key
, _
, tmpl_args
= s
.partition(' ')
1356 template
= templates
[tmpl_key
]
1357 if callable(template
):
1358 r
= template(tmpl_args
)
1360 error("Template %s returned empty string "\
1361 "(WeeChat doesn't have enough data)." %t
)
1368 args
= ' '.join(args
) # join pattern for keep spaces
1371 pattern
= _tmplRe
.sub(tmplReplacer
, args
)
1372 debug('Using regexp: %s', pattern
)
1374 raise Exception('No pattern for grep the logs.')
1376 def positive_number(opt
, val
):
1387 raise Exception("argument for %s must be a positive integer." % opt
)
1389 for opt
, val
in opts
:
1390 opt
= opt
.strip('-')
1391 if opt
in ('c', 'count'):
1393 elif opt
in ('m', 'matchcase'):
1394 matchcase
= not matchcase
1395 elif opt
in ('H', 'hilight'):
1396 # hilight must be always a string!
1400 hilight
= '%s,%s' %(color_hilight
, color_reset
)
1401 # we pass the colors in the variable itself because check_string() must not use
1402 # weechat's module when applying the colors (this is for grep in a hooked process)
1403 elif opt
in ('e', 'exact', 'o', 'only-match'):
1406 elif opt
in ('a', 'all'):
1408 elif opt
in ('h', 'head'):
1411 elif opt
in ('t', 'tail'):
1414 elif opt
in ('b', 'buffer'):
1416 elif opt
in ('n', 'number'):
1417 number
= positive_number(opt
, val
)
1418 elif opt
in ('C', 'context'):
1419 n
= positive_number(opt
, val
)
1422 elif opt
in ('A', 'after-context'):
1423 after_context
= positive_number(opt
, val
)
1424 elif opt
in ('B', 'before-context'):
1425 before_context
= positive_number(opt
, val
)
1426 elif opt
in ('i', 'v', 'invert'):
1430 if number
is not None:
1439 n
= get_config_int('default_tail_head')
1445 def cmd_grep_stop(buffer, args
):
1446 global hook_file_grep
, pattern
, matched_lines
1449 weechat
.unhook(hook_file_grep
)
1450 hook_file_grep
= None
1452 s
= 'Search for \'%s\' stopped.' % pattern
1454 grep_buffer
= weechat
.buffer_search('python', SCRIPT_NAME
)
1456 weechat
.buffer_set(grep_buffer
, 'title', s
)
1459 say(get_grep_file_status(), buffer)
1462 def cmd_grep(data
, buffer, args
):
1463 """Search in buffers and logs."""
1464 global pattern
, matchcase
, head
, tail
, number
, count
, exact
, hilight
1466 cmd_grep_stop(buffer, args
)
1468 return WEECHAT_RC_OK
1471 weechat
.command('', '/help %s' %SCRIPT_COMMAND
)
1472 return WEECHAT_RC_OK
1475 global log_name
, buffer_name
, only_buffers
, all
1476 log_name
= buffer_name
= ''
1477 only_buffers
= all
= False
1481 cmd_grep_parsing(args
)
1482 except Exception as e
:
1483 error('Argument error, %s' % e
)
1484 return WEECHAT_RC_OK
1487 log_file
= search_buffer
= None
1489 log_file
= get_file_by_pattern(log_name
, all
)
1491 error("Couldn't find any log for %s. Try /logs" %log_name
)
1492 return WEECHAT_RC_OK
1494 search_buffer
= get_all_buffers()
1496 search_buffer
= get_buffer_by_name(buffer_name
)
1497 if not search_buffer
:
1498 # there's no buffer, try in the logs
1499 log_file
= get_file_by_name(buffer_name
)
1501 error("Logs or buffer for '%s' not found." %buffer_name
)
1502 return WEECHAT_RC_OK
1504 search_buffer
= [search_buffer
]
1506 search_buffer
= [buffer]
1509 global search_in_files
, search_in_buffers
1510 search_in_files
= []
1511 search_in_buffers
= []
1513 search_in_files
= log_file
1514 elif not only_buffers
:
1515 #debug(search_buffer)
1516 for pointer
in search_buffer
:
1517 log
= get_file_by_buffer(pointer
)
1518 #debug('buffer %s log %s' %(pointer, log))
1520 search_in_files
.append(log
)
1522 search_in_buffers
.append(pointer
)
1524 search_in_buffers
= search_buffer
1528 show_matching_lines()
1529 except Exception as e
:
1531 return WEECHAT_RC_OK
1533 def cmd_logs(data
, buffer, args
):
1534 """List files in Weechat's log dir."""
1537 sort_by_size
= False
1541 opts
, args
= getopt
.gnu_getopt(args
.split(), 's', ['size'])
1544 for opt
, var
in opts
:
1545 opt
= opt
.strip('-')
1546 if opt
in ('size', 's'):
1548 except Exception as e
:
1549 error('Argument error, %s' % e
)
1550 return WEECHAT_RC_OK
1552 # is there's a filter, filter_excludes should be False
1553 file_list
= dir_list(home_dir
, filter, filter_excludes
=not filter)
1555 file_list
.sort(key
=get_size
)
1559 file_sizes
= map(lambda x
: human_readable_size(get_size(x
)), file_list
)
1560 # calculate column lenght
1565 column_len
= len(bigest
) + 3
1569 buffer = buffer_create()
1570 if get_config_boolean('clear_buffer'):
1571 weechat
.buffer_clear(buffer)
1572 file_list
= list(zip(file_list
, file_sizes
))
1573 msg
= 'Found %s logs.' %len(file_list
)
1575 print_line(msg
, buffer, display
=True)
1576 for file, size
in file_list
:
1577 separator
= column_len
and '.'*(column_len
- len(file))
1578 prnt(buffer, '%s %s %s' %(strip_home(file), separator
, size
))
1580 print_line(msg
, buffer)
1581 return WEECHAT_RC_OK
1585 def completion_log_files(data
, completion_item
, buffer, completion
):
1586 #debug('completion: %s' %', '.join((data, completion_item, buffer, completion)))
1589 completion_list_add
= weechat
.hook_completion_list_add
1590 WEECHAT_LIST_POS_END
= weechat
.WEECHAT_LIST_POS_END
1591 for log
in dir_list(home_dir
):
1592 completion_list_add(completion
, log
[l
:], 0, WEECHAT_LIST_POS_END
)
1593 return WEECHAT_RC_OK
1595 def completion_grep_args(data
, completion_item
, buffer, completion
):
1596 for arg
in ('count', 'all', 'matchcase', 'hilight', 'exact', 'head', 'tail', 'number', 'buffer',
1597 'after-context', 'before-context', 'context', 'invert', 'only-match'):
1598 weechat
.hook_completion_list_add(completion
, '--' + arg
, 0, weechat
.WEECHAT_LIST_POS_SORT
)
1599 for tmpl
in templates
:
1600 weechat
.hook_completion_list_add(completion
, '%{' + tmpl
, 0, weechat
.WEECHAT_LIST_POS_SORT
)
1601 return WEECHAT_RC_OK
1605 # template placeholder
1606 _tmplRe
= re
.compile(r
'%\{(\w+.*?)(?:\}|$)')
1607 # will match 999.999.999.999 but I don't care
1608 ipAddress
= r
'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
1609 domain
= r
'[\w-]{2,}(?:\.[\w-]{2,})*\.[a-z]{2,}'
1610 url
= r
'\w+://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?' % (domain
, ipAddress
)
1612 def make_url_regexp(args
):
1613 #debug('make url: %s', args)
1615 words
= r
'(?:%s)' %'|'.join(map(re
.escape
, args
.split()))
1616 return r
'(?:\w+://|www\.)[^\s]*%s[^\s]*(?:/[^\])>\s]*)?' %words
1620 def make_simple_regexp(pattern
):
1633 'url': make_url_regexp
,
1634 'escape': lambda s
: re
.escape(s
),
1635 'simple': make_simple_regexp
,
1640 def delete_bytecode():
1642 bytecode
= path
.join(script_path
, SCRIPT_NAME
+ '.pyc')
1643 if path
.isfile(bytecode
):
1645 return WEECHAT_RC_OK
1647 if __name__
== '__main__' and import_ok
and \
1648 weechat
.register(SCRIPT_NAME
, SCRIPT_AUTHOR
, SCRIPT_VERSION
, SCRIPT_LICENSE
, \
1649 SCRIPT_DESC
, 'delete_bytecode', ''):
1650 home_dir
= get_home()
1652 # for import ourselves
1654 script_path
= path
.dirname(__file__
)
1655 sys
.path
.append(script_path
)
1658 # check python version
1661 if sys
.version_info
> (2, 6):
1667 weechat
.hook_command(SCRIPT_COMMAND
, cmd_grep
.__doc
__,
1668 "[log <file> | buffer <name> | stop] [-a|--all] [-b|--buffer] [-c|--count] [-m|--matchcase] "
1669 "[-H|--hilight] [-o|--only-match] [-i|-v|--invert] [(-h|--head)|(-t|--tail) [-n|--number <n>]] "
1670 "[-A|--after-context <n>] [-B|--before-context <n>] [-C|--context <n> ] <expression>",
1673 log <file>: Search in one log that matches <file> in the logger path.
1674 Use '*' and '?' as wildcards.
1675 buffer <name>: Search in buffer <name>, if there's no buffer with <name> it will
1676 try to search for a log file.
1677 stop: Stops a currently running search.
1678 -a --all: Search in all open buffers.
1679 If used with 'log <file>' search in all logs that matches <file>.
1680 -b --buffer: Search only in buffers, not in file logs.
1681 -c --count: Just count the number of matched lines instead of showing them.
1682 -m --matchcase: Don't do case insensitive search.
1683 -H --hilight: Colour exact matches in output buffer.
1684 -o --only-match: Print only the matching part of the line (unique matches).
1685 -v -i --invert: Print lines that don't match the regular expression.
1686 -t --tail: Print the last 10 matching lines.
1687 -h --head: Print the first 10 matching lines.
1688 -n --number <n>: Overrides default number of lines for --tail or --head.
1689 -A --after-context <n>: Shows <n> lines of trailing context after matching lines.
1690 -B --before-context <n>: Shows <n> lines of leading context before matching lines.
1691 -C --context <n>: Same as using both --after-context and --before-context simultaneously.
1692 <expression>: Expression to search.
1695 Input line accepts most arguments of /grep, it'll repeat last search using the new
1696 arguments provided. You can't search in different logs from the buffer's input.
1697 Boolean arguments like --count, --tail, --head, --hilight, ... are toggleable
1699 Python regular expression syntax:
1700 See http://docs.python.org/lib/re-syntax.html
1703 %{url [text]}: Matches anything like an url, or an url with text.
1704 %{ip}: Matches anything that looks like an ip.
1705 %{domain}: Matches anything like a domain.
1706 %{escape text}: Escapes text in pattern.
1707 %{simple pattern}: Converts a pattern with '*' and '?' wildcards into a regexp.
1710 Search for urls with the word 'weechat' said by 'nick'
1711 /grep nick\\t.*%{url weechat}
1712 Search for '*.*' string
1715 # completion template
1716 "buffer %(buffers_names) %(grep_arguments)|%*"
1717 "||log %(grep_log_files) %(grep_arguments)|%*"
1719 "||%(grep_arguments)|%*",
1721 weechat
.hook_command('logs', cmd_logs
.__doc
__, "[-s|--size] [<filter>]",
1722 "-s --size: Sort logs by size.\n"
1723 " <filter>: Only show logs that match <filter>. Use '*' and '?' as wildcards.", '--size', 'cmd_logs', '')
1725 weechat
.hook_completion('grep_log_files', "list of log files",
1726 'completion_log_files', '')
1727 weechat
.hook_completion('grep_arguments', "list of arguments",
1728 'completion_grep_args', '')
1731 for opt
, val
in settings
.items():
1732 if not weechat
.config_is_set_plugin(opt
):
1733 weechat
.config_set_plugin(opt
, val
)
1736 color_date
= weechat
.color('brown')
1737 color_info
= weechat
.color('cyan')
1738 color_hilight
= weechat
.color('lightred')
1739 color_reset
= weechat
.color('reset')
1740 color_title
= weechat
.color('yellow')
1741 color_summary
= weechat
.color('lightcyan')
1742 color_delimiter
= weechat
.color('chat_delimiters')
1743 color_script_nick
= weechat
.color('chat_nick')
1746 script_nick
= '%s[%s%s%s]%s' %(color_delimiter
, color_script_nick
, SCRIPT_NAME
, color_delimiter
,
1748 script_nick_nocolor
= '[%s]' %SCRIPT_NAME
1749 # paragraph separator when using context options
1750 context_sep
= '%s\t%s--' %(script_nick
, color_info
)
1752 # -------------------------------------------------------------------------
1755 if weechat
.config_get_plugin('debug'):
1757 # custom debug module I use, allows me to inspect script's objects.
1759 debug
= pybuffer
.debugBuffer(globals(), '%s_debug' % SCRIPT_NAME
)
1761 def debug(s
, *args
):
1763 if not isinstance(s
, basestring
):
1769 prnt('', '%s\t%s' %(script_nick
, s
))
1774 # vim:set shiftwidth=4 tabstop=4 softtabstop=4 expandtab textwidth=100: