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.default_tail_head: 
  57 #     Config option for define default number of lines returned when using --head or --tail options. 
  58 #     Can be overriden in the command with --number option. 
  62 #   * try to figure out why hook_process chokes in long outputs (using a tempfile as a 
  64 #   * possibly add option for defining time intervals 
  71 #   '~' is now expaned to the home directory in the log file path so 
  72 #   paths like '~/logs/' should work. 
  75 #   version 0.7.4: make q work to quit grep buffer (requested by: gb) 
  77 #   2014-03-29, Felix Eckhofer <felix@tribut.de> 
  78 #   version 0.7.3: fix typo 
  81 #   version 0.7.2: bug fixes 
  85 #   * use TempFile so temporal files are guaranteed to be deleted. 
  86 #   * enable Archlinux workaround. 
  91 #   * using --only-match shows only unique strings. 
  92 #   * fixed bug that inverted -B -A switches when used with -t 
  95 #   version 0.6.8: by xt <xt@bash.no> 
  96 #   * supress highlights when printing in grep buffer 
  99 #   version 0.6.7: by xt <xt@bash.no>  
 100 #   * better temporary file: 
 101 #    use tempfile.mkstemp. to create a temp file in log dir,  
 102 #    makes it safer with regards to write permission and multi user 
 105 #   version 0.6.6: bug fixes 
 106 #   * use WEECHAT_LIST_POS_END in log file completion, makes completion faster 
 107 #   * disable bytecode if using python 2.6 
 108 #   * use single quotes in command string 
 109 #   * fix bug that could change buffer's title when using /grep stop 
 112 #   version 0.6.5: disable bytecode is a 2.6 feature, instead, resort to delete the bytecode manually 
 115 #   version 0.6.4: bug fix 
 116 #   version 0.6.3: added options --invert --only-match (replaces --exact, which is still available 
 117 #   but removed from help) 
 118 #   * use new 'irc_nick_color' info 
 119 #   * don't generate bytecode when spawning a new process 
 120 #   * show active options in buffer title 
 123 #   version 0.6.2: removed 2.6-ish code 
 124 #   version 0.6.1: fixed bug when grepping in grep's buffer 
 127 #   version 0.6.0: implemented grep in background 
 128 #   * improved context lines presentation. 
 129 #   * grepping for big (or many) log files runs in a weechat_process. 
 130 #   * added /grep stop. 
 131 #   * added 'size_limit' option 
 132 #   * fixed a infolist leak when grepping buffers 
 133 #   * added 'default_tail_head' option 
 134 #   * results are sort by line count 
 135 #   * don't die if log is corrupted (has NULL chars in it) 
 136 #   * changed presentation of /logs 
 137 #   * log path completion doesn't suck anymore 
 138 #   * removed all tabs, because I learned how to configure Vim so that spaces aren't annoying 
 139 #   anymore. This was the script's original policy. 
 142 #   version 0.5.5: rename script to 'grep.py' (FlashCode <flashcode@flashtux.org>). 
 145 #   version 0.5.4.1: fix index error when using --after/before-context options. 
 148 #   version 0.5.4: new features 
 149 #   * added --after-context and --before-context options. 
 150 #   * added --context as a shortcut for using both -A -B options. 
 153 #   version 0.5.3: improvements for long grep output 
 154 #   * grep buffer input accepts the same flags as /grep for repeat a search with different 
 156 #   * tweaks in grep's output. 
 157 #   * max_lines option added for limit grep's output. 
 158 #   * code in update_buffer() optimized. 
 159 #   * time stats in buffer title. 
 160 #   * added go_to_buffer config option. 
 161 #   * added --buffer for search only in buffers. 
 165 #   version 0.5.2: made it python-2.4.x compliant 
 168 #   version 0.5.1: some refactoring, show_summary option added. 
 171 #   version 0.5: rewritten from xt's grep.py 
 172 #   * fixed searching in non weechat logs, for cases like, if you're 
 173 #     switching from irssi and rename and copy your irssi logs to %h/logs 
 174 #   * fixed "timestamp rainbow" when you /grep in grep's buffer 
 175 #   * allow to search in other buffers other than current or in logs 
 176 #     of currently closed buffers with cmd 'buffer' 
 177 #   * allow to search in any log file in %h/logs with cmd 'log' 
 178 #   * added --count for return the number of matched lines 
 179 #   * added --matchcase for case sensible search 
 180 #   * added --hilight for color matches 
 181 #   * added --head and --tail options, and --number 
 182 #   * added command /logs for list files in %h/logs 
 183 #   * added config option for clear the buffer before a search 
 184 #   * added config option for filter logs we don't want to grep 
 185 #   * added the posibility to repeat last search with another regexp by writing 
 186 #     it in grep's buffer 
 187 #   * changed spaces for tabs in the code, which is my preference 
 192 import sys
, getopt
, time
, os
, re
, tempfile
 
 196     from weechat 
import WEECHAT_RC_OK
, prnt
, prnt_date_tags
 
 202 SCRIPT_AUTHOR  
= "Elián Hanisch <lambdae2@gmail.com>" 
 203 SCRIPT_VERSION 
= "0.7.5" 
 204 SCRIPT_LICENSE 
= "GPL3" 
 205 SCRIPT_DESC    
= "Search in buffers and logs" 
 206 SCRIPT_COMMAND 
= "grep" 
 208 ### Default Settings ### 
 210 'clear_buffer'      : 'off', 
 212 'go_to_buffer'      : 'on', 
 213 'max_lines'         : '4000', 
 214 'show_summary'      : 'on', 
 215 'size_limit'        : '2048', 
 216 'default_tail_head' : '10', 
 219 ### Class definitions ### 
 220 class linesDict(dict): 
 222     Class for handling matched lines in more than one buffer. 
 223     linesDict[buffer_name] = matched_lines_list 
 225     def __setitem__(self
, key
, value
): 
 226         assert isinstance(value
, list) 
 228             dict.__setitem
__(self
, key
, value
) 
 230             dict.__getitem
__(self
, key
).extend(value
) 
 232     def get_matches_count(self
): 
 233         """Return the sum of total matches stored.""" 
 234         if dict.__len
__(self
): 
 235             return sum(map(lambda L
: L
.matches_count
, self
.itervalues())) 
 240         """Return the sum of total lines stored.""" 
 241         if dict.__len
__(self
): 
 242             return sum(map(len, self
.itervalues())) 
 247         """Returns buffer count or buffer name if there's just one stored.""" 
 250             return self
.keys()[0] 
 257         """Returns a list of items sorted by line count.""" 
 258         items 
= dict.items(self
) 
 259         items
.sort(key
=lambda i
: len(i
[1])) 
 262     def items_count(self
): 
 263         """Returns a list of items sorted by match count.""" 
 264         items 
= dict.items(self
) 
 265         items
.sort(key
=lambda i
: i
[1].matches_count
) 
 268     def strip_separator(self
): 
 269         for L 
in self
.itervalues(): 
 272     def get_last_lines(self
, n
): 
 273         total_lines 
= len(self
) 
 274         #debug('total: %s n: %s' %(total_lines, n)) 
 278         for k
, v 
in reversed(self
.items()): 
 283                     v
.stripped_lines 
= l
-n
 
 289 class linesList(list): 
 290     """Class for list of matches, since sometimes I need to add lines that aren't matches, I need an 
 291     independent counter.""" 
 293     def __init__(self
, *args
): 
 294         list.__init
__(self
, *args
) 
 295         self
.matches_count 
= 0 
 296         self
.stripped_lines 
= 0 
 298     def append(self
, item
): 
 299         """Append lines, can be a string or a list with strings.""" 
 300         if isinstance(item
, str): 
 301             list.append(self
, item
) 
 305     def append_separator(self
): 
 306         """adds a separator into the list, makes sure it doen't add two together.""" 
 308         if (self 
and self
[-1] != s
) or not self
: 
 316     def count_match(self
, item
=None): 
 317         if item 
is None or isinstance(item
, str): 
 318             self
.matches_count 
+= 1 
 320             self
.matches_count 
+= len(item
) 
 322     def strip_separator(self
): 
 323         """removes separators if there are first or/and last in the list.""" 
 331 ### Misc functions ### 
 335         return os
.stat(f
).st_size
 
 339 sizeDict 
= {0:'b', 1:'KiB', 2:'MiB', 3:'GiB', 4:'TiB'}
 
 340 def human_readable_size(size
): 
 345     return '%.2f %s' %(size
, sizeDict
.get(power
, '')) 
 347 def color_nick(nick
): 
 348     """Returns coloured nick, with coloured mode if any.""" 
 349     if not nick
: return '' 
 350     wcolor 
= weechat
.color
 
 351     config_string 
= lambda s 
: weechat
.config_string(weechat
.config_get(s
)) 
 352     config_int 
= lambda s 
: weechat
.config_integer(weechat
.config_get(s
)) 
 354     prefix 
= config_string('irc.look.nick_prefix') 
 355     suffix 
= config_string('irc.look.nick_suffix') 
 356     prefix_c 
= suffix_c 
= wcolor(config_string('weechat.color.chat_delimiters')) 
 357     if nick
[0] == prefix
: 
 360         prefix 
= prefix_c 
= '' 
 361     if nick
[-1] == suffix
: 
 363         suffix 
= wcolor(color_delimiter
) + suffix
 
 365         suffix 
= suffix_c 
= '' 
 369         mode
, nick 
= nick
[0], nick
[1:] 
 370         mode_color 
= wcolor(config_string('weechat.color.nicklist_prefix%d' \
 
 371             %(modes
.find(mode
) + 1))) 
 373         mode 
= mode_color 
= '' 
 375     nick_color 
= weechat
.info_get('irc_nick_color', nick
) 
 377         # probably we're in WeeChat 0.3.0 
 378         #debug('no irc_nick_color') 
 379         color_nicks_number 
= config_int('weechat.look.color_nicks_number') 
 380         idx 
= (sum(map(ord, nick
))%color
_nicks
_number
) + 1 
 381         nick_color 
= wcolor(config_string('weechat.color.chat_nick_color%02d' %idx)) 
 382     return ''.join((prefix_c
, prefix
, mode_color
, mode
, nick_color
, nick
, suffix_c
, suffix
)) 
 384 ### Config and value validation ### 
 385 boolDict 
= {'on':True, 'off':False}
 
 386 def get_config_boolean(config
): 
 387     value 
= weechat
.config_get_plugin(config
) 
 389         return boolDict
[value
] 
 391         default 
= settings
[config
] 
 392         error("Error while fetching config '%s'. Using default value '%s'." %(config
, default
)) 
 393         error("'%s' is invalid, allowed: 'on', 'off'" %value
) 
 394         return boolDict
[default
] 
 396 def get_config_int(config
, allow_empty_string
=False): 
 397     value 
= weechat
.config_get_plugin(config
) 
 401         if value 
== '' and allow_empty_string
: 
 403         default 
= settings
[config
] 
 404         error("Error while fetching config '%s'. Using default value '%s'." %(config
, default
)) 
 405         error("'%s' is not a number." %value
) 
 408 def get_config_log_filter(): 
 409     filter = weechat
.config_get_plugin('log_filter') 
 411         return filter.split(',') 
 416     home 
= weechat
.config_string(weechat
.config_get('logger.file.path')) 
 417     home 
= path
.abspath(path
.expanduser(home
)) 
 418     return home
.replace('%h', weechat
.info_get('weechat_dir', '')) 
 420 def strip_home(s
, dir=''): 
 421     """Strips home dir from the begging of the log path, this makes them sorter.""" 
 431 script_nick 
= SCRIPT_NAME
 
 432 def error(s
, buffer=''): 
 434     prnt(buffer, '%s%s %s' %(weechat
.prefix('error'), script_nick
, s
)) 
 435     if weechat
.config_get_plugin('debug'): 
 437         if traceback
.sys
.exc_type
: 
 438             trace 
= traceback
.format_exc() 
 441 def say(s
, buffer=''): 
 443     prnt_date_tags(buffer, 0, 'no_highlight', '%s\t%s' %(script_nick
, s
)) 
 447 ### Log files and buffers ### 
 448 cache_dir 
= {} # note: don't remove, needed for completion if the script was loaded recently 
 449 def dir_list(dir, filter_list
=(), filter_excludes
=True, include_dir
=False): 
 450     """Returns a list of files in 'dir' and its subdirs.""" 
 453     from fnmatch 
import fnmatch
 
 454     #debug('dir_list: listing in %s' %dir) 
 455     key 
= (dir, include_dir
) 
 457         return cache_dir
[key
] 
 461     filter_list 
= filter_list 
or get_config_log_filter() 
 465             file = file[dir_len
:] # pattern shouldn't match home dir 
 466             for pattern 
in filter_list
: 
 467                 if fnmatch(file, pattern
): 
 468                     return filter_excludes
 
 469             return not filter_excludes
 
 471         filter = lambda f 
: not filter_excludes
 
 474     extend 
= file_list
.extend
 
 477         for basedir
, subdirs
, files 
in walk(dir): 
 479             #    subdirs = map(lambda s : join(s, ''), subdirs) 
 480             #    files.extend(subdirs) 
 481             files_path 
= map(lambda f 
: join(basedir
, f
), files
) 
 482             files_path 
= [ file for file in files_path 
if not filter(file) ] 
 486     cache_dir
[key
] = file_list
 
 487     #debug('dir_list: got %s' %str(file_list)) 
 490 def get_file_by_pattern(pattern
, all=False): 
 491     """Returns the first log whose path matches 'pattern', 
 492     if all is True returns all logs that matches.""" 
 493     if not pattern
: return [] 
 494     #debug('get_file_by_filename: searching for %s.' %pattern) 
 495     # do envvar expandsion and check file 
 496     file = path
.expanduser(pattern
) 
 497     file = path
.expandvars(file) 
 498     if path
.isfile(file): 
 500     # lets see if there's a matching log 
 502     file = path
.join(home_dir
, pattern
) 
 503     if path
.isfile(file): 
 506         from fnmatch 
import fnmatch
 
 508         file_list 
= dir_list(home_dir
) 
 510         for log 
in file_list
: 
 512             if fnmatch(basename
, pattern
): 
 514         #debug('get_file_by_filename: got %s.' %file) 
 520 def get_file_by_buffer(buffer): 
 521     """Given buffer pointer, finds log's path or returns None.""" 
 522     #debug('get_file_by_buffer: searching for %s' %buffer) 
 523     infolist 
= weechat
.infolist_get('logger_buffer', '', '') 
 524     if not infolist
: return 
 526         while weechat
.infolist_next(infolist
): 
 527             pointer 
= weechat
.infolist_pointer(infolist
, 'buffer') 
 528             if pointer 
== buffer: 
 529                 file = weechat
.infolist_string(infolist
, 'log_filename') 
 530                 if weechat
.infolist_integer(infolist
, 'log_enabled'): 
 531                     #debug('get_file_by_buffer: got %s' %file) 
 534                 #    debug('get_file_by_buffer: got %s but log not enabled' %file) 
 536         #debug('infolist gets freed') 
 537         weechat
.infolist_free(infolist
) 
 539 def get_file_by_name(buffer_name
): 
 540     """Given a buffer name, returns its log path or None. buffer_name should be in 'server.#channel' 
 541     or '#channel' format.""" 
 542     #debug('get_file_by_name: searching for %s' %buffer_name) 
 543     # common mask options 
 544     config_masks 
= ('logger.mask.irc', 'logger.file.mask') 
 545     # since there's no buffer pointer, we try to replace some local vars in mask, like $channel and 
 546     # $server, then replace the local vars left with '*', and use it as a mask for get the path with 
 547     # get_file_by_pattern 
 548     for config 
in config_masks
: 
 549         mask 
= weechat
.config_string(weechat
.config_get(config
)) 
 550         #debug('get_file_by_name: mask: %s' %mask) 
 552             mask 
= mask
.replace('$name', buffer_name
) 
 553         elif '$channel' in mask 
or '$server' in mask
: 
 554             if '.' in buffer_name 
and \
 
 555                     '#' not in buffer_name
[:buffer_name
.find('.')]: # the dot isn't part of the channel name 
 556                 #    ^ I'm asuming channel starts with #, i'm lazy. 
 557                 server
, channel 
= buffer_name
.split('.', 1) 
 559                 server
, channel 
= '*', buffer_name
 
 560             if '$channel' in mask
: 
 561                 mask 
= mask
.replace('$channel', channel
) 
 562             if '$server' in mask
: 
 563                 mask 
= mask
.replace('$server', server
) 
 564         # change the unreplaced vars by '*' 
 565         from string 
import letters
 
 567             # vars for time formatting 
 568             mask 
= mask
.replace('%', '$') 
 570             masks 
= mask
.split('$') 
 571             masks 
= map(lambda s
: s
.lstrip(letters
), masks
) 
 572             mask 
= '*'.join(masks
) 
 575         #debug('get_file_by_name: using mask %s' %mask) 
 576         file = get_file_by_pattern(mask
) 
 577         #debug('get_file_by_name: got file %s' %file) 
 582 def get_buffer_by_name(buffer_name
): 
 583     """Given a buffer name returns its buffer pointer or None.""" 
 584     #debug('get_buffer_by_name: searching for %s' %buffer_name) 
 585     pointer 
= weechat
.buffer_search('', buffer_name
) 
 588             infolist 
= weechat
.infolist_get('buffer', '', '') 
 589             while weechat
.infolist_next(infolist
): 
 590                 short_name 
= weechat
.infolist_string(infolist
, 'short_name') 
 591                 name 
= weechat
.infolist_string(infolist
, 'name') 
 592                 if buffer_name 
in (short_name
, name
): 
 593                     #debug('get_buffer_by_name: found %s' %name) 
 594                     pointer 
= weechat
.buffer_search('', name
) 
 597             weechat
.infolist_free(infolist
) 
 598     #debug('get_buffer_by_name: got %s' %pointer) 
 601 def get_all_buffers(): 
 602     """Returns list with pointers of all open buffers.""" 
 604     infolist 
= weechat
.infolist_get('buffer', '', '') 
 605     while weechat
.infolist_next(infolist
): 
 606         buffers
.append(weechat
.infolist_pointer(infolist
, 'pointer')) 
 607     weechat
.infolist_free(infolist
) 
 608     grep_buffer 
= weechat
.buffer_search('python', SCRIPT_NAME
) 
 609     if grep_buffer 
and grep_buffer 
in buffers
: 
 610         # remove it from list 
 611         del buffers
[buffers
.index(grep_buffer
)] 
 615 def make_regexp(pattern
, matchcase
=False): 
 616     """Returns a compiled regexp.""" 
 617     if pattern 
in ('.', '.*', '.?', '.+'): 
 618         # because I don't need to use a regexp if we're going to match all lines 
 620     # matching takes a lot more time if pattern starts or ends with .* and it isn't needed. 
 621     if pattern
[:2] == '.*': 
 622         pattern 
= pattern
[2:] 
 623     if pattern
[-2:] == '.*': 
 624         pattern 
= pattern
[:-2] 
 627             regexp 
= re
.compile(pattern
, re
.IGNORECASE
) 
 629             regexp 
= re
.compile(pattern
) 
 631         raise Exception, 'Bad pattern, %s' %e 
 634 def check_string(s
, regexp
, hilight
='', exact
=False): 
 635     """Checks 's' with a regexp and returns it if is a match.""" 
 640         matchlist 
= regexp
.findall(s
) 
 642             if isinstance(matchlist
[0], tuple): 
 643                 # join tuples (when there's more than one match group in regexp) 
 644                 return [ ' '.join(t
) for t 
in matchlist 
] 
 648         matchlist 
= regexp
.findall(s
) 
 650             if isinstance(matchlist
[0], tuple): 
 652                 matchlist 
= [ item 
for L 
in matchlist 
for item 
in L 
if item 
] 
 653             matchlist 
= list(set(matchlist
)) # remove duplicates if any 
 655             color_hilight
, color_reset 
= hilight
.split(',', 1) 
 657                 s 
= s
.replace(m
, '%s%s%s' % (color_hilight
, m
, color_reset
)) 
 660     # no need for findall() here 
 661     elif regexp
.search(s
): 
 664 def grep_file(file, head
, tail
, after_context
, before_context
, count
, regexp
, hilight
, exact
, invert
): 
 665     """Return a list of lines that match 'regexp' in 'file', if no regexp returns all lines.""" 
 667         tail 
= head 
= after_context 
= before_context 
= False 
 670         before_context 
= after_context 
= False 
 674     #debug(' '.join(map(str, (file, head, tail, after_context, before_context)))) 
 677     # define these locally as it makes the loop run slightly faster 
 678     append 
= lines
.append
 
 679     count_match 
= lines
.count_match
 
 680     separator 
= lines
.append_separator
 
 683             if check_string(s
, regexp
, hilight
, exact
): 
 688         check 
= lambda s
: check_string(s
, regexp
, hilight
, exact
) 
 691         file_object 
= open(file, 'r') 
 695     if tail 
or before_context
: 
 696         # for these options, I need to seek in the file, but is slower and uses a good deal of 
 697         # memory if the log is too big, so we do this *only* for these options. 
 698         file_lines 
= file_object
.readlines() 
 701             # instead of searching in the whole file and later pick the last few lines, we 
 702             # reverse the log, search until count reached and reverse it again, that way is a lot 
 705             # don't invert context switches 
 706             before_context
, after_context 
= after_context
, before_context
 
 709             before_context_range 
= range(1, before_context 
+ 1) 
 710             before_context_range
.reverse() 
 715         while line_idx 
< len(file_lines
): 
 716             line 
= file_lines
[line_idx
] 
 722                     for id in before_context_range
: 
 724                             context_line 
= file_lines
[line_idx 
- id] 
 725                             if check(context_line
): 
 726                                 # match in before context, that means we appended these same lines in a 
 727                                 # previous match, so we delete them merging both paragraphs 
 729                                     del lines
[id - before_context 
- 1:] 
 739                     while id < after_context 
+ offset
: 
 742                             context_line 
= file_lines
[line_idx 
+ id] 
 743                             _context_line 
= check(context_line
) 
 746                                 context_line 
= _context_line 
# so match is hilighted with --hilight 
 753                 if limit 
and lines
.matches_count 
>= limit
: 
 763         for line 
in file_object
: 
 766                 count 
or append(line
) 
 770                     while id < after_context 
+ offset
: 
 773                             context_line 
= file_object
.next() 
 774                             _context_line 
= check(context_line
) 
 777                                 context_line 
= _context_line
 
 779                             count 
or append(context_line
) 
 780                         except StopIteration: 
 783                 if limit 
and lines
.matches_count 
>= limit
: 
 789 def grep_buffer(buffer, head
, tail
, after_context
, before_context
, count
, regexp
, hilight
, exact
, 
 791     """Return a list of lines that match 'regexp' in 'buffer', if no regexp returns all lines.""" 
 794         tail 
= head 
= after_context 
= before_context 
= False 
 797         before_context 
= after_context 
= False 
 798     #debug(' '.join(map(str, (tail, head, after_context, before_context, count, exact, hilight)))) 
 800     # Using /grep in grep's buffer can lead to some funny effects 
 801     # We should take measures if that's the case 
 802     def make_get_line_funcion(): 
 803         """Returns a function for get lines from the infolist, depending if the buffer is grep's or 
 805         string_remove_color 
= weechat
.string_remove_color
 
 806         infolist_string 
= weechat
.infolist_string
 
 807         grep_buffer 
= weechat
.buffer_search('python', SCRIPT_NAME
) 
 808         if grep_buffer 
and buffer == grep_buffer
: 
 809             def function(infolist
): 
 810                 prefix 
= infolist_string(infolist
, 'prefix') 
 811                 message 
= infolist_string(infolist
, 'message') 
 812                 if prefix
: # only our messages have prefix, ignore it 
 816             infolist_time 
= weechat
.infolist_time
 
 817             def function(infolist
): 
 818                 prefix 
= string_remove_color(infolist_string(infolist
, 'prefix'), '') 
 819                 message 
= string_remove_color(infolist_string(infolist
, 'message'), '') 
 820                 date 
= infolist_time(infolist
, 'date') 
 821                 return '%s\t%s\t%s' %(date
, prefix
, message
) 
 823     get_line 
= make_get_line_funcion() 
 825     infolist 
= weechat
.infolist_get('buffer_lines', buffer, '') 
 827         # like with grep_file() if we need the last few matching lines, we move the cursor to 
 828         # the end and search backwards 
 829         infolist_next 
= weechat
.infolist_prev
 
 830         infolist_prev 
= weechat
.infolist_next
 
 832         infolist_next 
= weechat
.infolist_next
 
 833         infolist_prev 
= weechat
.infolist_prev
 
 836     # define these locally as it makes the loop run slightly faster 
 837     append 
= lines
.append
 
 838     count_match 
= lines
.count_match
 
 839     separator 
= lines
.append_separator
 
 842             if check_string(s
, regexp
, hilight
, exact
): 
 847         check 
= lambda s
: check_string(s
, regexp
, hilight
, exact
) 
 850         before_context_range 
= range(1, before_context 
+ 1) 
 851         before_context_range
.reverse() 
 853     while infolist_next(infolist
): 
 854         line 
= get_line(infolist
) 
 855         if line 
is None: continue 
 861                 for id in before_context_range
: 
 862                     if not infolist_prev(infolist
): 
 864                 for id in before_context_range
: 
 865                     context_line 
= get_line(infolist
) 
 866                     if check(context_line
): 
 868                             del lines
[id - before_context 
- 1:] 
 872                     infolist_next(infolist
) 
 873             count 
or append(line
) 
 877                 while id < after_context 
+ offset
: 
 879                     if infolist_next(infolist
): 
 880                         context_line 
= get_line(infolist
) 
 881                         _context_line 
= check(context_line
) 
 883                             context_line 
= _context_line
 
 888                         # in the main loop infolist_next will start again an cause an infinite loop 
 890                         infolist_next 
= lambda x
: 0 
 892             if limit 
and lines
.matches_count 
>= limit
: 
 894     weechat
.infolist_free(infolist
) 
 900 ### this is our main grep function 
 901 hook_file_grep 
= None 
 902 def show_matching_lines(): 
 904     Greps buffers in search_in_buffers or files in search_in_files and updates grep buffer with the 
 907     global pattern
, matchcase
, number
, count
, exact
, hilight
, invert
 
 908     global tail
, head
, after_context
, before_context
 
 909     global search_in_files
, search_in_buffers
, matched_lines
, home_dir
 
 911     matched_lines 
= linesDict() 
 912     #debug('buffers:%s \nlogs:%s' %(search_in_buffers, search_in_files)) 
 916     if search_in_buffers
: 
 917         regexp 
= make_regexp(pattern
, matchcase
) 
 918         for buffer in search_in_buffers
: 
 919             buffer_name 
= weechat
.buffer_get_string(buffer, 'name') 
 920             matched_lines
[buffer_name
] = grep_buffer(buffer, head
, tail
, after_context
, 
 921                     before_context
, count
, regexp
, hilight
, exact
, invert
) 
 925         size_limit 
= get_config_int('size_limit', allow_empty_string
=True) 
 927         if size_limit 
or size_limit 
== 0: 
 928             size 
= sum(map(get_size
, search_in_files
)) 
 929             if size 
> size_limit 
* 1024: 
 931         elif size_limit 
== '': 
 936             regexp 
= make_regexp(pattern
, matchcase
) 
 937             for log 
in search_in_files
: 
 938                 log_name 
= strip_home(log
) 
 939                 matched_lines
[log_name
] = grep_file(log
, head
, tail
, after_context
, before_context
, 
 940                         count
, regexp
, hilight
, exact
, invert
) 
 943             # we hook a process so grepping runs in background. 
 944             #debug('on background') 
 945             global hook_file_grep
, script_path
, bytecode
 
 946             timeout 
= 1000*60*5 # 5 min 
 948             quotify 
= lambda s
: '"%s"' %s 
 949             files_string 
= ', '.join(map(quotify
, search_in_files
)) 
 952             # we keep the file descriptor as a global var so it isn't deleted until next grep 
 953             tmpFile 
= tempfile
.NamedTemporaryFile(prefix
=SCRIPT_NAME
, 
 954                     dir=weechat
.info_get('weechat_dir', '')) 
 955             cmd 
= grep_process_cmd 
%dict
(logs
=files_string
, head
=head
, pattern
=pattern
, tail
=tail
, 
 956                     hilight
=hilight
, after_context
=after_context
, before_context
=before_context
, 
 957                     exact
=exact
, matchcase
=matchcase
, home_dir
=home_dir
, script_path
=script_path
, 
 958                     count
=count
, invert
=invert
, bytecode
=bytecode
, filename
=tmpFile
.name
, 
 959                     python
=weechat
.info_get('python2_bin', '') or 'python') 
 962             hook_file_grep 
= weechat
.hook_process(cmd
, timeout
, 'grep_file_callback', tmpFile
.name
) 
 965                 buffer_create("Searching for '%s' in %s worth of data..." %(pattern_tmpl
, 
 966                     human_readable_size(size
))) 
 970 # defined here for commodity 
 971 grep_process_cmd 
= """%(python)s -%(bytecode)sc ' 
 972 import sys, cPickle, os 
 973 sys.path.append("%(script_path)s") # add WeeChat script dir so we can import grep 
 974 from grep import make_regexp, grep_file, strip_home 
 977     regexp = make_regexp("%(pattern)s", %(matchcase)s) 
 980         log_name = strip_home(log, "%(home_dir)s") 
 981         lines = grep_file(log, %(head)s, %(tail)s, %(after_context)s, %(before_context)s, 
 982         %(count)s, regexp, "%(hilight)s", %(exact)s, %(invert)s) 
 984     fd = open("%(filename)s", "wb") 
 985     cPickle.dump(d, fd, -1) 
 988     print >> sys.stderr, e' 
 991 grep_stdout 
= grep_stderr 
= '' 
 992 def grep_file_callback(filename
, command
, rc
, stdout
, stderr
): 
 993     global hook_file_grep
, grep_stderr
,  grep_stdout
 
 995     #debug("rc: %s\nstderr: %s\nstdout: %s" %(rc, repr(stderr), repr(stdout))) 
 997         grep_stdout 
+= stdout
 
 999         grep_stderr 
+= stderr
 
1002         def set_buffer_error(): 
1003             grep_buffer 
= buffer_create() 
1004             title 
= weechat
.buffer_get_string(grep_buffer
, 'title') 
1005             title 
= title 
+ ' %serror' %color
_title
 
1006             weechat
.buffer_set(grep_buffer
, 'title', title
) 
1014             elif path
.exists(filename
): 
1018                     fd 
= open(filename
, 'rb') 
1019                     d 
= cPickle
.load(fd
) 
1020                     matched_lines
.update(d
) 
1022                 except Exception, e
: 
1030             grep_stdout 
= grep_stderr 
= '' 
1031             hook_file_grep 
= None 
1032     return WEECHAT_RC_OK
 
1034 def get_grep_file_status(): 
1035     global search_in_files
, matched_lines
, time_start
 
1036     elapsed 
= now() - time_start
 
1037     if len(search_in_files
) == 1: 
1038         log 
= '%s (%s)' %(strip_home(search_in_files
[0]), 
1039                 human_readable_size(get_size(search_in_files
[0]))) 
1041         size 
= sum(map(get_size
, search_in_files
)) 
1042         log 
= '%s log files (%s)' %(len(search_in_files
), human_readable_size(size
)) 
1043     return 'Searching in %s, running for %.4f seconds. Interrupt it with "/grep stop" or "stop"' \
 
1044         ' in grep buffer.' %(log
, elapsed
) 
1047 def buffer_update(): 
1048     """Updates our buffer with new lines.""" 
1049     global pattern_tmpl
, matched_lines
, pattern
, count
, hilight
, invert
, exact
 
1052     buffer = buffer_create() 
1053     if get_config_boolean('clear_buffer'): 
1054         weechat
.buffer_clear(buffer) 
1055     matched_lines
.strip_separator() # remove first and last separators of each list 
1056     len_total_lines 
= len(matched_lines
) 
1057     max_lines 
= get_config_int('max_lines') 
1058     if not count 
and len_total_lines 
> max_lines
: 
1059         weechat
.buffer_clear(buffer) 
1061     def _make_summary(log
, lines
, note
): 
1062         return '%s matches "%s%s%s"%s in %s%s%s%s' \
 
1063                 %(lines
.matches_count
, color_summary
, pattern_tmpl
, color_info
, 
1064                   invert 
and ' (inverted)' or '', 
1065                   color_summary
, log
, color_reset
, note
) 
1068         make_summary 
= lambda log
, lines 
: _make_summary(log
, lines
, ' (not shown)') 
1070         def make_summary(log
, lines
): 
1071             if lines
.stripped_lines
: 
1073                     note 
= ' (last %s lines shown)' %len(lines
) 
1075                     note 
= ' (not shown)' 
1078             return _make_summary(log
, lines
, note
) 
1080     global weechat_format
 
1082         # we don't want colors if there's match highlighting 
1083         format_line 
= lambda s 
: '%s %s %s' %split
_line
(s
) 
1086             global nick_dict
, weechat_format
 
1087             date
, nick
, msg 
= split_line(s
) 
1090                     nick 
= nick_dict
[nick
] 
1093                     nick_c 
= color_nick(nick
) 
1094                     nick_dict
[nick
] = nick_c
 
1096                 return '%s%s %s%s %s' %(color_date
, date
, nick
, color_reset
, msg
) 
1102     print_line('Search for "%s%s%s"%s in %s%s%s.' %(color_summary
, pattern_tmpl
, color_info
, 
1103         invert 
and ' (inverted)' or '', color_summary
, matched_lines
, color_reset
), 
1105     # print last <max_lines> lines 
1106     if matched_lines
.get_matches_count(): 
1108             # with count we sort by matches lines instead of just lines. 
1109             matched_lines_items 
= matched_lines
.items_count() 
1111             matched_lines_items 
= matched_lines
.items() 
1113         matched_lines
.get_last_lines(max_lines
) 
1114         for log
, lines 
in matched_lines_items
: 
1115             if lines
.matches_count
: 
1119                     weechat_format 
= True 
1124                         if line 
== linesList
._sep
: 
1126                             prnt(buffer, context_sep
) 
1130                                 error("Found garbage in log '%s', maybe it's corrupted" %log
) 
1131                                 line 
= line
.replace('\x00', '') 
1132                             prnt_date_tags(buffer, 0, 'no_highlight', format_line(line
)) 
1135                 if count 
or get_config_boolean('show_summary'): 
1136                     summary 
= make_summary(log
, lines
) 
1137                     print_line(summary
, buffer) 
1140             if not count 
and lines
: 
1143         print_line('No matches found.', buffer) 
1149     time_total 
= time_end 
- time_start
 
1150     # percent of the total time used for grepping 
1151     time_grep_pct 
= (time_grep 
- time_start
)/time_total
*100 
1152     #debug('time: %.4f seconds (%.2f%%)' %(time_total, time_grep_pct)) 
1153     if not count 
and len_total_lines 
> max_lines
: 
1154         note 
= ' (last %s lines shown)' %len(matched_lines
) 
1157     title 
= "'q': close buffer | Search in %s%s%s %s matches%s | pattern \"%s%s%s\"%s %s | %.4f seconds (%.2f%%)" \
 
1158             %(color_title
, matched_lines
, color_reset
, matched_lines
.get_matches_count(), note
, 
1159               color_title
, pattern_tmpl
, color_reset
, invert 
and ' (inverted)' or '', format_options(), 
1160               time_total
, time_grep_pct
) 
1161     weechat
.buffer_set(buffer, 'title', title
) 
1163     if get_config_boolean('go_to_buffer'): 
1164         weechat
.buffer_set(buffer, 'display', '1') 
1166     # free matched_lines so it can be removed from memory 
1170     """Splits log's line 's' in 3 parts, date, nick and msg.""" 
1171     global weechat_format
 
1172     if weechat_format 
and s
.count('\t') >= 2: 
1173         date
, nick
, msg 
= s
.split('\t', 2) # date, nick, message 
1175         # looks like log isn't in weechat's format 
1176         weechat_format 
= False # incoming lines won't be formatted 
1177         date
, nick
, msg 
= '', '', s
 
1180         msg 
= msg
.replace('\t', '    ') 
1181     return date
, nick
, msg
 
1183 def print_line(s
, buffer=None, display
=False): 
1184     """Prints 's' in script's buffer as 'script_nick'. For displaying search summaries.""" 
1186         buffer = buffer_create() 
1187     say('%s%s' %(color_info
, s
), buffer) 
1188     if display 
and get_config_boolean('go_to_buffer'): 
1189         weechat
.buffer_set(buffer, 'display', '1') 
1191 def format_options(): 
1192     global matchcase
, number
, count
, exact
, hilight
, invert
 
1193     global tail
, head
, after_context
, before_context
 
1195     append 
= options
.append
 
1196     insert 
= options
.insert
 
1198     for i
, flag 
in enumerate((count
, hilight
, matchcase
, exact
, invert
)): 
1203         n 
= get_config_int('default_tail_head') 
1217     if before_context 
and after_context 
and (before_context 
== after_context
): 
1219         append(before_context
) 
1223             append(before_context
) 
1226             append(after_context
) 
1228     s 
= ''.join(map(str, options
)).strip() 
1229     if s 
and s
[0] != '-': 
1233 def buffer_create(title
=None): 
1234     """Returns our buffer pointer, creates and cleans the buffer if needed.""" 
1235     buffer = weechat
.buffer_search('python', SCRIPT_NAME
) 
1237         buffer = weechat
.buffer_new(SCRIPT_NAME
, 'buffer_input', '', '', '') 
1238         weechat
.buffer_set(buffer, 'time_for_each_line', '0') 
1239         weechat
.buffer_set(buffer, 'nicklist', '0') 
1240         weechat
.buffer_set(buffer, 'title', title 
or 'grep output buffer') 
1241         weechat
.buffer_set(buffer, 'localvar_set_no_log', '1') 
1243         weechat
.buffer_set(buffer, 'title', title
) 
1246 def buffer_input(data
, buffer, input_data
): 
1247     """Repeats last search with 'input_data' as regexp.""" 
1249         cmd_grep_stop(buffer, input_data
) 
1251         return WEECHAT_RC_OK
 
1252     if input_data 
in ('q', 'Q'): 
1253         weechat
.buffer_close(buffer) 
1254         return weechat
.WEECHAT_RC_OK
 
1256     global search_in_buffers
, search_in_files
 
1259         if pattern 
and (search_in_files 
or search_in_buffers
): 
1260             # check if the buffer pointers are still valid 
1261             for pointer 
in search_in_buffers
: 
1262                 infolist 
= weechat
.infolist_get('buffer', pointer
, '') 
1264                     del search_in_buffers
[search_in_buffers
.index(pointer
)] 
1265                 weechat
.infolist_free(infolist
) 
1267                 cmd_grep_parsing(input_data
) 
1268             except Exception, e
: 
1269                 error('Argument error, %s' %e, buffer=buffer) 
1270                 return WEECHAT_RC_OK
 
1272                 show_matching_lines() 
1273             except Exception, e
: 
1276         error("There isn't any previous search to repeat.", buffer=buffer) 
1277     return WEECHAT_RC_OK
 
1281     """Resets global vars.""" 
1282     global home_dir
, cache_dir
, nick_dict
 
1283     global pattern_tmpl
, pattern
, matchcase
, number
, count
, exact
, hilight
, invert
 
1284     global tail
, head
, after_context
, before_context
 
1286     head 
= tail 
= after_context 
= before_context 
= invert 
= False 
1287     matchcase 
= count 
= exact 
= False 
1288     pattern_tmpl 
= pattern 
= number 
= None 
1289     home_dir 
= get_home() 
1290     cache_dir 
= {} # for avoid walking the dir tree more than once per command 
1291     nick_dict 
= {} # nick cache for don't calculate nick color every time 
1293 def cmd_grep_parsing(args
): 
1294     """Parses args for /grep and grep input buffer.""" 
1295     global pattern_tmpl
, pattern
, matchcase
, number
, count
, exact
, hilight
, invert
 
1296     global tail
, head
, after_context
, before_context
 
1297     global log_name
, buffer_name
, only_buffers
, all 
1298     opts
, args 
= getopt
.gnu_getopt(args
.split(), 'cmHeahtivn:bA:B:C:o', ['count', 'matchcase', 'hilight', 
1299         'exact', 'all', 'head', 'tail', 'number=', 'buffer', 'after-context=', 'before-context=', 
1300         'context=', 'invert', 'only-match']) 
1301     #debug(opts, 'opts: '); debug(args, 'args: ') 
1303         if args
[0] == 'log': 
1305             log_name 
= args
.pop(0) 
1306         elif args
[0] == 'buffer': 
1308             buffer_name 
= args
.pop(0) 
1310     def tmplReplacer(match): 
1311         """This function will replace templates with regexps""" 
1312         s 
= match.groups()[0] 
1313         tmpl_args 
= s
.split() 
1314         tmpl_key
, _
, tmpl_args 
= s
.partition(' ') 
1316             template 
= templates
[tmpl_key
] 
1317             if callable(template
): 
1318                 r 
= template(tmpl_args
) 
1320                     error("Template %s returned empty string "\
 
1321                           "(WeeChat doesn't have enough data)." %t
) 
1328     args 
= ' '.join(args
) # join pattern for keep spaces 
1331         pattern 
= _tmplRe
.sub(tmplReplacer
, args
) 
1332         debug('Using regexp: %s', pattern
) 
1334         raise Exception, 'No pattern for grep the logs.' 
1336     def positive_number(opt
, val
): 
1347             raise Exception, "argument for %s must be a positive integer." %opt
 
1349     for opt
, val 
in opts
: 
1350         opt 
= opt
.strip('-') 
1351         if opt 
in ('c', 'count'): 
1353         elif opt 
in ('m', 'matchcase'): 
1354             matchcase 
= not matchcase
 
1355         elif opt 
in ('H', 'hilight'): 
1356             # hilight must be always a string! 
1360                 hilight 
= '%s,%s' %(color_hilight
, color_reset
) 
1361             # we pass the colors in the variable itself because check_string() must not use 
1362             # weechat's module when applying the colors (this is for grep in a hooked process) 
1363         elif opt 
in ('e', 'exact', 'o', 'only-match'): 
1366         elif opt 
in ('a', 'all'): 
1368         elif opt 
in ('h', 'head'): 
1371         elif opt 
in ('t', 'tail'): 
1374         elif opt 
in ('b', 'buffer'): 
1376         elif opt 
in ('n', 'number'): 
1377             number 
= positive_number(opt
, val
) 
1378         elif opt 
in ('C', 'context'): 
1379             n 
= positive_number(opt
, val
) 
1382         elif opt 
in ('A', 'after-context'): 
1383             after_context 
= positive_number(opt
, val
) 
1384         elif opt 
in ('B', 'before-context'): 
1385             before_context 
= positive_number(opt
, val
) 
1386         elif opt 
in ('i', 'v', 'invert'): 
1390     if number 
is not None: 
1399         n 
= get_config_int('default_tail_head') 
1405 def cmd_grep_stop(buffer, args
): 
1406     global hook_file_grep
, pattern
, matched_lines
, tmpFile
 
1409             weechat
.unhook(hook_file_grep
) 
1410             hook_file_grep 
= None 
1411             s 
= 'Search for \'%s\' stopped.' %pattern
 
1413             grep_buffer 
= weechat
.buffer_search('python', SCRIPT_NAME
) 
1415                 weechat
.buffer_set(grep_buffer
, 'title', s
) 
1419             say(get_grep_file_status(), buffer) 
1422 def cmd_grep(data
, buffer, args
): 
1423     """Search in buffers and logs.""" 
1424     global pattern
, matchcase
, head
, tail
, number
, count
, exact
, hilight
 
1426         cmd_grep_stop(buffer, args
) 
1428         return WEECHAT_RC_OK
 
1431         weechat
.command('', '/help %s' %SCRIPT_COMMAND
) 
1432         return WEECHAT_RC_OK
 
1435     global log_name
, buffer_name
, only_buffers
, all 
1436     log_name 
= buffer_name 
= '' 
1437     only_buffers 
= all = False 
1441         cmd_grep_parsing(args
) 
1442     except Exception, e
: 
1443         error('Argument error, %s' %e) 
1444         return WEECHAT_RC_OK
 
1447     log_file 
= search_buffer 
= None 
1449         log_file 
= get_file_by_pattern(log_name
, all) 
1451             error("Couldn't find any log for %s. Try /logs" %log_name
) 
1452             return WEECHAT_RC_OK
 
1454         search_buffer 
= get_all_buffers() 
1456         search_buffer 
= get_buffer_by_name(buffer_name
) 
1457         if not search_buffer
: 
1458             # there's no buffer, try in the logs 
1459             log_file 
= get_file_by_name(buffer_name
) 
1461                 error("Logs or buffer for '%s' not found." %buffer_name
) 
1462                 return WEECHAT_RC_OK
 
1464             search_buffer 
= [search_buffer
] 
1466         search_buffer 
= [buffer] 
1469     global search_in_files
, search_in_buffers
 
1470     search_in_files 
= [] 
1471     search_in_buffers 
= [] 
1473         search_in_files 
= log_file
 
1474     elif not only_buffers
: 
1475         #debug(search_buffer) 
1476         for pointer 
in search_buffer
: 
1477             log 
= get_file_by_buffer(pointer
) 
1478             #debug('buffer %s log %s' %(pointer, log)) 
1480                 search_in_files
.append(log
) 
1482                 search_in_buffers
.append(pointer
) 
1484         search_in_buffers 
= search_buffer
 
1488         show_matching_lines() 
1489     except Exception, e
: 
1491     return WEECHAT_RC_OK
 
1493 def cmd_logs(data
, buffer, args
): 
1494     """List files in Weechat's log dir.""" 
1497     sort_by_size 
= False 
1501         opts
, args 
= getopt
.gnu_getopt(args
.split(), 's', ['size']) 
1504         for opt
, var 
in opts
: 
1505             opt 
= opt
.strip('-') 
1506             if opt 
in ('size', 's'): 
1508     except Exception, e
: 
1509         error('Argument error, %s' %e) 
1510         return WEECHAT_RC_OK
 
1512     # is there's a filter, filter_excludes should be False 
1513     file_list 
= dir_list(home_dir
, filter, filter_excludes
=not filter) 
1515         file_list
.sort(key
=get_size
) 
1519     file_sizes 
= map(lambda x
: human_readable_size(get_size(x
)), file_list
) 
1520     # calculate column lenght 
1525         column_len 
= len(bigest
) + 3 
1529     buffer = buffer_create() 
1530     if get_config_boolean('clear_buffer'): 
1531         weechat
.buffer_clear(buffer) 
1532     file_list 
= zip(file_list
, file_sizes
) 
1533     msg 
= 'Found %s logs.' %len(file_list
) 
1535     print_line(msg
, buffer, display
=True) 
1536     for file, size 
in file_list
: 
1537         separator 
= column_len 
and '.'*(column_len 
- len(file)) 
1538         prnt(buffer, '%s %s %s' %(strip_home(file), separator
, size
)) 
1540         print_line(msg
, buffer) 
1541     return WEECHAT_RC_OK
 
1545 def completion_log_files(data
, completion_item
, buffer, completion
): 
1546     #debug('completion: %s' %', '.join((data, completion_item, buffer, completion))) 
1549     completion_list_add 
= weechat
.hook_completion_list_add
 
1550     WEECHAT_LIST_POS_END 
= weechat
.WEECHAT_LIST_POS_END
 
1551     for log 
in dir_list(home_dir
): 
1552         completion_list_add(completion
, log
[l
:], 0, WEECHAT_LIST_POS_END
) 
1553     return WEECHAT_RC_OK
 
1555 def completion_grep_args(data
, completion_item
, buffer, completion
): 
1556     for arg 
in ('count', 'all', 'matchcase', 'hilight', 'exact', 'head', 'tail', 'number', 'buffer', 
1557             'after-context', 'before-context', 'context', 'invert', 'only-match'): 
1558         weechat
.hook_completion_list_add(completion
, '--' + arg
, 0, weechat
.WEECHAT_LIST_POS_SORT
) 
1559     for tmpl 
in templates
: 
1560         weechat
.hook_completion_list_add(completion
, '%{' + tmpl
, 0, weechat
.WEECHAT_LIST_POS_SORT
) 
1561     return WEECHAT_RC_OK
 
1565 # template placeholder 
1566 _tmplRe 
= re
.compile(r
'%\{(\w+.*?)(?:\}|$)') 
1567 # will match 999.999.999.999 but I don't care 
1568 ipAddress 
= r
'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' 
1569 domain 
= r
'[\w-]{2,}(?:\.[\w-]{2,})*\.[a-z]{2,}' 
1570 url 
= r
'\w+://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?' % (domain
, ipAddress
) 
1572 def make_url_regexp(args
): 
1573     #debug('make url: %s', args) 
1575         words 
= r
'(?:%s)' %'|'.join(map(re
.escape
, args
.split())) 
1576         return r
'(?:\w+://|www\.)[^\s]*%s[^\s]*(?:/[^\])>\s]*)?' %words
 
1580 def make_simple_regexp(pattern
): 
1593            'url': make_url_regexp
, 
1594         'escape': lambda s
: re
.escape(s
), 
1595         'simple': make_simple_regexp
, 
1600 def delete_bytecode(): 
1602     bytecode 
= path
.join(script_path
, SCRIPT_NAME 
+ '.pyc') 
1603     if path
.isfile(bytecode
): 
1605     return WEECHAT_RC_OK
 
1607 if __name__ 
== '__main__' and import_ok 
and \
 
1608         weechat
.register(SCRIPT_NAME
, SCRIPT_AUTHOR
, SCRIPT_VERSION
, SCRIPT_LICENSE
, \
 
1609         SCRIPT_DESC
, 'delete_bytecode', ''): 
1610     home_dir 
= get_home() 
1612     # for import ourselves 
1614     script_path 
= path
.dirname(__file__
) 
1615     sys
.path
.append(script_path
) 
1618     # check python version 
1621     if sys
.version_info 
> (2, 6): 
1627     weechat
.hook_command(SCRIPT_COMMAND
, cmd_grep
.__doc
__, 
1628             "[log <file> | buffer <name> | stop] [-a|--all] [-b|--buffer] [-c|--count] [-m|--matchcase] " 
1629             "[-H|--hilight] [-o|--only-match] [-i|-v|--invert] [(-h|--head)|(-t|--tail) [-n|--number <n>]] " 
1630             "[-A|--after-context <n>] [-B|--before-context <n>] [-C|--context <n> ] <expression>", 
1633      log <file>: Search in one log that matches <file> in the logger path. 
1634                  Use '*' and '?' as wildcards. 
1635   buffer <name>: Search in buffer <name>, if there's no buffer with <name> it will 
1636                  try to search for a log file. 
1637            stop: Stops a currently running search. 
1638        -a --all: Search in all open buffers. 
1639                  If used with 'log <file>' search in all logs that matches <file>. 
1640     -b --buffer: Search only in buffers, not in file logs. 
1641      -c --count: Just count the number of matched lines instead of showing them. 
1642  -m --matchcase: Don't do case insensible search. 
1643    -H --hilight: Colour exact matches in output buffer. 
1644 -o --only-match: Print only the matching part of the line (unique matches). 
1645  -v -i --invert: Print lines that don't match the regular expression. 
1646       -t --tail: Print the last 10 matching lines. 
1647       -h --head: Print the first 10 matching lines. 
1648 -n --number <n>: Overrides default number of lines for --tail or --head. 
1649 -A --after-context <n>: Shows <n> lines of trailing context after matching lines. 
1650 -B --before-context <n>: Shows <n> lines of leading context before matching lines. 
1651 -C --context <n>: Same as using both --after-context and --before-context simultaneously. 
1652   <expression>: Expression to search. 
1655   Input line accepts most arguments of /grep, it'll repeat last search using the new 
1656   arguments provided. You can't search in different logs from the buffer's input. 
1657   Boolean arguments like --count, --tail, --head, --hilight, ... are toggleable 
1659 Python regular expression syntax: 
1660   See http://docs.python.org/lib/re-syntax.html 
1663      %{url [text]}: Matches anything like an url, or an url with text. 
1664              %{ip}: Matches anything that looks like an ip. 
1665          %{domain}: Matches anything like a domain. 
1666     %{escape text}: Escapes text in pattern. 
1667  %{simple pattern}: Converts a pattern with '*' and '?' wildcards into a regexp. 
1670   Search for urls with the word 'weechat' said by 'nick' 
1671     /grep nick\\t.*%{url weechat} 
1672   Search for '*.*' string 
1675             # completion template 
1676             "buffer %(buffers_names) %(grep_arguments)|%*" 
1677             "||log %(grep_log_files) %(grep_arguments)|%*" 
1679             "||%(grep_arguments)|%*", 
1681     weechat
.hook_command('logs', cmd_logs
.__doc
__, "[-s|--size] [<filter>]", 
1682             "-s --size: Sort logs by size.\n" 
1683             " <filter>: Only show logs that match <filter>. Use '*' and '?' as wildcards.", '--size', 'cmd_logs', '') 
1685     weechat
.hook_completion('grep_log_files', "list of log files", 
1686             'completion_log_files', '') 
1687     weechat
.hook_completion('grep_arguments', "list of arguments", 
1688             'completion_grep_args', '') 
1691     for opt
, val 
in settings
.iteritems(): 
1692         if not weechat
.config_is_set_plugin(opt
): 
1693             weechat
.config_set_plugin(opt
, val
) 
1696     color_date        
= weechat
.color('brown') 
1697     color_info        
= weechat
.color('cyan') 
1698     color_hilight     
= weechat
.color('lightred') 
1699     color_reset       
= weechat
.color('reset') 
1700     color_title       
= weechat
.color('yellow') 
1701     color_summary     
= weechat
.color('lightcyan') 
1702     color_delimiter   
= weechat
.color('chat_delimiters') 
1703     color_script_nick 
= weechat
.color('chat_nick') 
1706     script_nick 
= '%s[%s%s%s]%s' %(color_delimiter
, color_script_nick
, SCRIPT_NAME
, color_delimiter
, 
1708     script_nick_nocolor 
= '[%s]' %SCRIPT_NAME
 
1709     # paragraph separator when using context options 
1710     context_sep 
= '%s\t%s--' %(script_nick
, color_info
) 
1712     # ------------------------------------------------------------------------- 
1715     if weechat
.config_get_plugin('debug'): 
1717             # custom debug module I use, allows me to inspect script's objects. 
1719             debug 
= pybuffer
.debugBuffer(globals(), '%s_debug' % SCRIPT_NAME
) 
1721             def debug(s
, *args
): 
1722                 if not isinstance(s
, basestring
): 
1726                 prnt('', '%s\t%s' %(script_nick
, s
)) 
1731 # vim:set shiftwidth=4 tabstop=4 softtabstop=4 expandtab textwidth=100: