]> git.r.bdr.sh - rbdr/dotfiles/blame - weechat/python/autoload/weeget.py
Merge branch 'master' of gitlab.com:rbdr/dotfiles
[rbdr/dotfiles] / weechat / python / autoload / weeget.py
CommitLineData
aca63701
BB
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2009-2012 Sebastien Helleu <flashcode@flashtux.org>
4#
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.
9#
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.
14#
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/>.
17#
18
19#
20# WeeChat scripts manager.
21# (this script requires WeeChat >= 0.3.0 and python >= 2.6)
22#
23# History:
24#
25# 2012-03-09, Sebastien Helleu <flashcode@flashtux.org>:
26# version 1.8: fix reload of config file
27# 2012-02-27, Sebastien Helleu <flashcode@flashtux.org>:
28# version 1.7: add support of scheme scripts
29# 2012-02-05, Sebastien Helleu <flashcode@flashtux.org>:
30# version 1.6: use URL transfer from API (for WeeChat >= 0.3.7)
31# 2012-01-03, Sebastien Helleu <flashcode@flashtux.org>:
32# version 1.5: make script compatible with Python 3.x
33# 2011-03-25, Sebastien Helleu <flashcode@flashtux.org>:
34# version 1.4: add completion with installed scripts for action "remove"
35# 2011-03-10, Sebastien Helleu <flashcode@flashtux.org>:
36# version 1.3: add script extension in script name completion and a new
37# completion with tags for actions "list" and "listinstalled"
38# 2011-02-13, Sebastien Helleu <flashcode@flashtux.org>:
39# version 1.2: use new help format for command arguments
40# 2010-11-08, Sebastien Helleu <flashcode@flashtux.org>:
41# version 1.1: get python 2.x binary for hook_process (fix problem
42# when python 3.x is default python version, requires
43# WeeChat >= 0.3.4)
44# 2010-02-22, Blake Winton <bwinton@latte.ca>:
45# version 1.0: add action "listinstalled" for command /weeget
46# 2010-01-25, Sebastien Helleu <flashcode@flashtux.org>:
47# version 0.9: fix "running" status of scripts with /weeget check
48# 2009-09-30, Sebastien Helleu <flashcode@flashtux.org>:
49# version 0.8: fix bugs and add missing info in "/weeget show",
50# display warning if url for plugins.xml.gz is old site
51# 2009-09-07, Sebastien Helleu <flashcode@flashtux.org>:
52# version 0.7: update weechat site with new URL
53# 2009-05-02, Sebastien Helleu <flashcode@flashtux.org>:
54# version 0.6: sync with last API changes
55# 2009-04-15, Sebastien Helleu <flashcode@flashtux.org>:
56# version 0.5: display missing module(s) when import failed
57# 2009-04-11, Sebastien Helleu <flashcode@flashtux.org>:
58# version 0.4: use new completion for command arguments
59# 2009-04-07, Sebastien Helleu <flashcode@flashtux.org>:
60# version 0.3: fix bug with install/upgrade when weeget is updated with
61# other scripts: ensure that weeget is always the last
62# installed script
63# 2009-04-07, Sebastien Helleu <flashcode@flashtux.org>:
64# version 0.2: add author's mail in script description
65# 2009-04-05, Sebastien Helleu <flashcode@flashtux.org>:
66# version 0.1: initial release
67#
68
69SCRIPT_NAME = "weeget"
70SCRIPT_AUTHOR = "Sebastien Helleu <flashcode@flashtux.org>"
71SCRIPT_VERSION = "1.8"
72SCRIPT_LICENSE = "GPL3"
73SCRIPT_DESC = "WeeChat scripts manager"
74
75SCRIPT_COMMAND = "weeget"
76
77import_ok = True
78
79try:
80 import weechat
81except ImportError:
82 print("This script must be run under WeeChat.")
83 print("Get WeeChat now at: http://www.weechat.org/")
84 import_ok = False
85
86try:
87 import sys, os, stat, time, gzip, hashlib, xml.dom.minidom
88except ImportError as message:
89 print("Missing package(s) for %s: %s" % (SCRIPT_NAME, message))
90 import_ok = False
91
92CONFIG_FILE_NAME = "wg"
93
94SCRIPT_EXTENSION = {
95 "perl" : "pl",
96 "python": "py",
97 "ruby" : "rb",
98 "lua" : "lua",
99 "tcl" : "tcl",
100 "guile" : "scm",
101}
102
103# timeout for download of plugins.xml.gz
104TIMEOUT_UPDATE = 60 * 1000
105
106# timeout for download of a script
107TIMEOUT_SCRIPT = 60 * 1000
108
109# config file and options
110wg_config_file = ""
111wg_config_option = {}
112
113# action (install, remove, ..) and arguments
114wg_action = ""
115wg_action_args = ""
116
117# loaded scripts
118wg_loaded_scripts = {}
119
120# hook process and stdout
121wg_hook_process = { "update": "", "script": "" }
122wg_stdout = { "update": "", "script": "" }
123
124# scripts read from plugins.xml.gz
125wg_scripts = {}
126
127# list of script to install, and script currently installing
128wg_scripts_to_install = []
129wg_current_script_install = {}
130
131# =================================[ config ]=================================
132
133def wg_config_init():
134 """
135 Initialization of configuration file.
136 Sections: color, scripts.
137 """
138 global wg_config_file, wg_config_option
139 wg_config_file = weechat.config_new(CONFIG_FILE_NAME,
140 "wg_config_reload_cb", "")
141 if wg_config_file == "":
142 return
143
144 # section "color"
145 section_color = weechat.config_new_section(
146 wg_config_file, "color", 0, 0, "", "", "", "", "", "", "", "", "", "")
147 if section_color == "":
148 weechat.config_free(wg_config_file)
149 return
150 wg_config_option["color_script"] = weechat.config_new_option(
151 wg_config_file, section_color,
152 "script", "color", "Color for script names", "", 0, 0,
153 "cyan", "cyan", 0, "", "", "", "", "", "")
154 wg_config_option["color_installed"] = weechat.config_new_option(
155 wg_config_file, section_color,
156 "installed", "color", "Color for \"installed\" indicator", "", 0, 0,
157 "yellow", "yellow", 0, "", "", "", "", "", "")
158 wg_config_option["color_running"] = weechat.config_new_option(
159 wg_config_file, section_color,
160 "running", "color", "Color for \"running\" indicator", "", 0, 0,
161 "lightgreen", "lightgreen", 0, "", "", "", "", "", "")
162 wg_config_option["color_obsolete"] = weechat.config_new_option(
163 wg_config_file, section_color,
164 "obsolete", "color", "Color for \"obsolete\" indicator", "", 0, 0,
165 "lightmagenta", "lightmagenta", 0, "", "", "", "", "", "")
166 wg_config_option["color_unknown"] = weechat.config_new_option(
167 wg_config_file, section_color,
168 "unknown", "color", "Color for \"unknown status\" indicator", "", 0, 0,
169 "lightred", "lightred", 0, "", "", "", "", "", "")
170 wg_config_option["color_language"] = weechat.config_new_option(
171 wg_config_file, section_color,
172 "language", "color", "Color for language names", "", 0, 0,
173 "lightblue", "lightblue", 0, "", "", "", "", "", "")
174
175 # section "scripts"
176 section_scripts = weechat.config_new_section(
177 wg_config_file, "scripts", 0, 0, "", "", "", "", "", "", "", "", "", "")
178 if section_scripts == "":
179 weechat.config_free(wg_config_file)
180 return
181 wg_config_option["scripts_url"] = weechat.config_new_option(
182 wg_config_file, section_scripts,
183 "url", "string", "URL for file with list of plugins", "", 0, 0,
184 "http://www.weechat.org/files/plugins.xml.gz",
185 "http://www.weechat.org/files/plugins.xml.gz", 0, "", "", "", "", "", "")
186 wg_config_option["scripts_dir"] = weechat.config_new_option(
187 wg_config_file, section_scripts,
188 "dir", "string", "Local cache directory for" + SCRIPT_NAME, "", 0, 0,
189 "%h/" + SCRIPT_NAME, "%h/" + SCRIPT_NAME, 0, "", "", "", "", "", "")
190 wg_config_option["scripts_cache_expire"] = weechat.config_new_option(
191 wg_config_file, section_scripts,
192 "cache_expire", "integer", "Local cache expiration time, in minutes "
193 "(-1 = never expires, 0 = always expires)", "",
194 -1, 60*24*365, "60", "60", 0, "", "", "", "", "", "")
195
196def wg_config_reload_cb(data, config_file):
197 """ Reload configuration file. """
198 return weechat.config_reload(config_file)
199
200def wg_config_read():
201 """ Read configuration file. """
202 global wg_config_file
203 return weechat.config_read(wg_config_file)
204
205def wg_config_write():
206 """ Write configuration file. """
207 global wg_config_file
208 return weechat.config_write(wg_config_file)
209
210def wg_config_color(color):
211 """ Get a color from configuration. """
212 global wg_config_option
213 option = wg_config_option.get("color_" + color, "")
214 if option == "":
215 return ""
216 return weechat.color(weechat.config_string(option))
217
218def wg_config_get_dir():
219 """ Return weeget directory, with expanded WeeChat home dir. """
220 global wg_config_option
221 return weechat.config_string(
222 wg_config_option["scripts_dir"]).replace("%h",
223 weechat.info_get("weechat_dir", ""))
224
225def wg_config_create_dir():
226 """ Create weeget directory. """
227 dir = wg_config_get_dir()
228 if not os.path.isdir(dir):
229 os.makedirs(dir, mode=0o700)
230
231def wg_config_get_cache_filename():
232 """ Get local cache filename, based on URL. """
233 global wg_config_option
234 return wg_config_get_dir() + os.sep + \
235 os.path.basename(weechat.config_string(wg_config_option["scripts_url"]))
236
237# =============================[ download file ]==============================
238
239def wg_download_file(url, filename, timeout, callback, callback_data):
240 """Download a file with an URL. Return hook_process created."""
241 version = weechat.info_get("version_number", "") or 0
242 if int(version) >= 0x00030700:
243 return weechat.hook_process_hashtable("url:%s" % url,
244 { "file_out": filename },
245 timeout,
246 callback, callback_data)
247 else:
248 script = [ "import sys",
249 "try:",
250 " if sys.version_info >= (3,):",
251 " import urllib.request",
252 " response = urllib.request.urlopen('%s')" % url,
253 " else:",
254 " import urllib2",
255 " response = urllib2.urlopen(urllib2.Request('%s'))" % url,
256 " f = open('%s', 'wb')" % filename,
257 " f.write(response.read())",
258 " response.close()",
259 " f.close()",
260 "except Exception as e:",
261 " print('error:' + str(e))" ]
262 return weechat.hook_process("python -c \"%s\"" % "\n".join(script),
263 timeout,
264 callback, callback_data)
265
266# ================================[ scripts ]=================================
267
268def wg_search_script_by_name(name):
269 """
270 Search a script in list by name.
271 Name can be short name ('weeget') or full name ('weeget.py').
272 """
273 global wg_scripts
274 for id, script in wg_scripts.items():
275 if script["name"] == name or script["full_name"] == name:
276 return script
277 return None
278
279def wg_get_loaded_scripts():
280 """
281 Get python dictionary with loaded scripts.
282 Keys are filenames and values are path to script, for example:
283 'weeget.py': '/home/xxx/.weechat/python/weeget.py'
284 """
285 global wg_loaded_scripts
286 wg_loaded_scripts = {}
287 for language in SCRIPT_EXTENSION.keys():
288 infolist = weechat.infolist_get(language + "_script", "", "")
289 while weechat.infolist_next(infolist):
290 filename = weechat.infolist_string(infolist, "filename")
291 if filename != "":
292 wg_loaded_scripts[os.path.basename(filename)] = filename
293 weechat.infolist_free(infolist)
294
295def wg_is_local_script_loaded(filename):
296 """ Check if a script filename (like 'python/weeget.py') is loaded. """
297 global wg_loaded_scripts
298 filename2 = filename
299 if filename2.startswith("autoload/"):
300 filename2 = filename2[9:]
301 for name, path in wg_loaded_scripts.items():
302 if path.endswith(filename) or path.endswith(filename2):
303 return True
304 return False
305
306def wg_get_local_script_status(script):
307 """
308 Check if a script is installed.
309 'script' is a dictionary retrieved from scripts xml list.
310 """
311 global wg_loaded_scripts
312 status = { "installed": "", "obsolete": "", "running": "" }
313 local_dir = weechat.info_get("weechat_dir", "") + os.sep + script["language"]
314 local_name = local_dir + os.sep + "autoload" + os.sep + script["full_name"]
315 if not os.path.isfile(local_name):
316 local_name = local_dir + os.sep + script["full_name"]
317 if os.path.isfile(local_name):
318 status["installed"] = "1"
319 f = open(local_name, "rb")
320 md5 = hashlib.md5()
321 md5.update(f.read())
322 f.close()
323 local_md5 = md5.hexdigest()
324 if local_md5 != script["md5sum"]:
325 status["obsolete"] = "1"
326 if script["full_name"] in wg_loaded_scripts.keys():
327 status["running"] = "1"
328 return status
329
330def wg_get_local_scripts():
331 """
332 Get list of all local scripts (in languages and autoload dirs).
333 Return a dictionary with language as key and list of paths as value,
334 with autoloaded scripts at beginning of list, for example:
335 { 'perl': [ 'autoload/buffers.pl',
336 'autoload/weetris.pl',
337 'beep.pl',
338 'launcher.pl' ],
339 'python': [ 'autoload/weeget.py',
340 'go.py',
341 'vdm.py' ]
342 }
343 """
344 files = {}
345 for language in SCRIPT_EXTENSION.keys():
346 files[language] = []
347 autoloaded_files = []
348 rootdir = weechat.info_get("weechat_dir", "") + os.sep + language
349 for root, dirs, listfiles in os.walk(rootdir):
350 if root == rootdir:
351 files[language] = listfiles
352 elif root == rootdir + os.sep + "autoload":
353 autoloaded_files = listfiles
354 for file in autoloaded_files:
355 if file in files[language]:
356 files[language].remove(file)
357 files[language].insert(0, "autoload" + os.sep + file)
358 return files
359
360def wg_get_local_scripts_status():
361 """
362 Return list of all local scripts with status (unknown/obsolete/running).
363 For example:
364 [ 'perl/weetris.pl': { 'unknown': '', 'obsolete': '1', 'running': '' },
365 'python/weeget.py': { 'unknown': '', 'obsolete': '', 'running': '1' }
366 ]
367 """
368 local_scripts_status = []
369 local_scripts = wg_get_local_scripts()
370 if len(local_scripts) > 0:
371 for language, files in local_scripts.items():
372 for file in files:
373 script_status = { "unknown": "", "obsolete": "", "running": "" }
374 name_with_ext = os.path.basename(file)
375 script = wg_search_script_by_name(os.path.basename(file))
376 if script == None:
377 script_status["unknown"] = "1"
378 else:
379 status = wg_get_local_script_status(script)
380 if status["obsolete"]:
381 script_status["obsolete"] = "1"
382 if wg_is_local_script_loaded(file):
383 script_status["running"] = "1"
384 local_scripts_status.append((language + os.sep + file,
385 script_status))
386 return local_scripts_status
387
388def wg_search_scripts(search):
389 """ Search word in scripts, return list of matching scripts. """
390 global wg_scripts
391 if search == "":
392 return wg_scripts
393 scripts_matching = {}
394 for id, script in wg_scripts.items():
395 if script["name"].lower().find(search) >= 0 \
396 or script["language"].lower().find(search) >= 0 \
397 or script["desc_en"].lower().find(search) >= 0 \
398 or script["desc_fr"].lower().find(search) >= 0 \
399 or script["tags"].lower().find(search) >= 0:
400 scripts_matching[id] = script
401 return scripts_matching
402
403def wg_list_scripts(search, installed=False):
404 """
405 List all scripts (with optional search string).
406 If installed == True, then list only installed scripts.
407 For each script, display status (installed/running/new version available),
408 name of script, language and description.
409 For example:
410 ir buffers pl Sidebar with list of buffers.
411 i N go py Quick jump to buffers.
412 i weetris pl Tetris-like game.
413 """
414 global wg_scripts
415 search = search.strip().lower()
416 scripts_matching = wg_search_scripts(search)
417 if len(scripts_matching) == 0:
418 weechat.prnt("", "%s: no script found" % SCRIPT_NAME)
419 else:
420 weechat.prnt("", "")
421 if search != "":
422 if installed:
423 weechat.prnt("", "Scripts installed matching \"%s\":" % search)
424 else:
425 weechat.prnt("", "Scripts for WeeChat %s matching \"%s\":"
426 % (weechat.info_get("version", ""),
427 search))
428 else:
429 if installed:
430 weechat.prnt("", "Scripts installed:")
431 else:
432 weechat.prnt("", "Scripts for WeeChat %s:"
433 % weechat.info_get("version", ""))
434 sorted_scripts = sorted(scripts_matching.items(),
435 key=lambda s: s[1]["name"])
436 length_max_name = 0
437 for item in sorted_scripts:
438 length = len(item[1]["name"])
439 if length > length_max_name:
440 length_max_name = length
441 str_format = "%%s%%s%%s%%s%%s%%s%%s %%s%%-%ds %%s%%-3s %%s%%s" \
442 % length_max_name
443 for item in sorted_scripts:
444 script = item[1]
445 str_installed = " "
446 str_running = " "
447 str_obsolete = " "
448 status = wg_get_local_script_status(script)
449 if installed and not status["installed"]:
450 continue
451 if status["installed"]:
452 str_installed = "i"
453 if status["running"]:
454 str_running = "r"
455 if status["obsolete"]:
456 str_obsolete = "N"
457 weechat.prnt("", str_format
458 % (wg_config_color("installed"),
459 str_installed,
460 wg_config_color("running"),
461 str_running,
462 wg_config_color("obsolete"),
463 str_obsolete,
464 weechat.color("chat"),
465 wg_config_color("script"),
466 script["name"],
467 wg_config_color("language"),
468 SCRIPT_EXTENSION[script["language"]],
469 weechat.color("chat"),
470 script["desc_en"]))
471
472def wg_show_script(name):
473 """
474 Show detailed info about a script (in repository).
475 For example:
476 Script: weeget.py, version 0.7, license: GPL3
477 Author: Sebastien Helleu <flashcode [at] flashtux [dot] org>
478 Status: installed, running
479 Date: added: 2009-04-05, updated: 2009-09-07
480 URL: http://www.weechat.org/files/scripts/weeget.py
481 MD5: 4b0458dd5cc5c9a09ba8078f89830869
482 Desc: Scripts manager.
483 Tags: scripts
484 Requires: python 2.5
485 Min: 0.3.0
486 """
487 if len(wg_scripts) == 0:
488 return
489 script = wg_search_script_by_name(name)
490 if script == None:
491 weechat.prnt("", "%s: script \"%s%s%s\" not found"
492 % (SCRIPT_NAME,
493 wg_config_color("script"),
494 name,
495 weechat.color("chat")))
496 else:
497 weechat.prnt("", "")
498 weechat.prnt("", " Script: %s%s%s, version %s, license: %s"
499 % (wg_config_color("script"),
500 script["full_name"],
501 weechat.color("chat"),
502 script["version"],
503 script["license"]))
504 weechat.prnt("", " Author: %s <%s>" % (script["author"], script["mail"]))
505 status = wg_get_local_script_status(script)
506 str_status = "not installed"
507 if status["installed"]:
508 str_status = "installed"
509 if status["running"]:
510 str_status += ", running"
511 else:
512 str_status += ", not running"
513 if status["obsolete"]:
514 str_status += " (new version available)"
515 weechat.prnt("", " Status: %s" % str_status)
516 date_added = script.get("added", "")[:10]
517 str_updated = script.get("updated", "")
518 if str_updated != "":
519 date_updated = script["updated"][:10]
520 if date_updated == "0000-00-00" or date_updated == date_added:
521 str_updated = ""
522 if str_updated != "":
523 weechat.prnt("", " Date: added: %s, updated: %s"
524 % (date_added, date_updated))
525 else:
526 weechat.prnt("", " Date: added: %s" % date_added)
527 weechat.prnt("", " URL: %s" % script.get("url", ""))
528 weechat.prnt("", " MD5: %s" % script.get("md5sum", ""))
529 weechat.prnt("", " Desc: %s" % script.get("desc_en", ""))
530 weechat.prnt("", " Tags: %s" % script.get("tags", ""))
531 str_requires = script.get("requirements", "")
532 if str_requires == "":
533 str_requires = "(nothing)"
534 weechat.prnt("", "Requires: %s" % str_requires)
535 vmin = script.get("min_weechat", "")
536 vmax = script.get("max_weechat", "")
537 if vmin != "":
538 weechat.prnt("", " Min: %s" % vmin)
539 if vmax != "":
540 weechat.prnt("", " Max: %s" % vmax)
541
542def wg_install_next_script():
543 """
544 Install first script in list wg_scripts_to_install and remove it from
545 list.
546 """
547 global wg_scripts, wg_scripts_to_install, wg_current_script_install
548 global wg_hook_process
549 if len(wg_scripts) == 0:
550 return
551 # be sure weeget is ALWAYS last script to install/update
552 # otherwise we'll lose end of list when weeget is unloaded by WeeChat
553 if SCRIPT_NAME in wg_scripts_to_install:
554 wg_scripts_to_install.remove(SCRIPT_NAME)
555 wg_scripts_to_install.append(SCRIPT_NAME)
556 # loop until a script is installed, or end if list is empty
557 while len(wg_scripts_to_install) > 0:
558 name = wg_scripts_to_install.pop(0)
559 script = wg_search_script_by_name(name)
560 if script == None:
561 weechat.prnt("", "%s: script \"%s%s%s\" not found"
562 % (SCRIPT_NAME,
563 wg_config_color("script"),
564 name,
565 weechat.color("chat")))
566 else:
567 status = wg_get_local_script_status(script)
568 if status["installed"] and not status["obsolete"]:
569 weechat.prnt("",
570 "%s: script \"%s%s%s\" is already "
571 "installed and up to date"
572 % (SCRIPT_NAME,
573 wg_config_color("script"),
574 script["full_name"],
575 weechat.color("chat")))
576 else:
577 weechat.prnt("", "%s: downloading \"%s%s%s\"..."
578 % (SCRIPT_NAME,
579 wg_config_color("script"),
580 script["full_name"],
581 weechat.color("chat")))
582 if wg_hook_process["script"] != "":
583 weechat.unhook(wg_hook_process["script"])
584 wg_hook_process["script"] = ""
585 wg_current_script_install = script
586 filename = wg_config_get_dir() + os.sep + script["full_name"]
587 wg_hook_process["script"] = wg_download_file(script["url"], filename, TIMEOUT_SCRIPT,
588 "wg_process_script_cb", "")
589 # this function will be called again when script will be
590 # downloaded
591 return
592
593def wg_install_scripts(names):
594 """ Install scripts. """
595 global wg_scripts_to_install
596 for name in names.split(" "):
597 wg_scripts_to_install.append(name)
598 wg_install_next_script()
599
600def wg_process_script_cb(data, command, rc, stdout, stderr):
601 """ Callback when reading a script from website. """
602 global wg_hook_process, wg_stdout, wg_current_script_install, wg_loaded_scripts
603 if stdout != "":
604 wg_stdout["script"] += stdout
605 if stderr != "":
606 wg_stdout["script"] += stderr
607 if int(rc) >= 0:
608 if wg_stdout["script"].startswith("error:"):
609 weechat.prnt("", "%s%s: error downloading script (%s)"
610 % (weechat.prefix("error"), SCRIPT_NAME,
611 wg_stdout["update"][6:].strip()))
612 else:
613 # ask C plugin to install/load script
614 weechat.hook_signal_send(wg_current_script_install["language"] + "_script_install",
615 weechat.WEECHAT_HOOK_SIGNAL_STRING,
616 wg_config_get_dir() + os.sep + wg_current_script_install["full_name"])
617 wg_hook_process["script"] = ""
618 wg_install_next_script()
619 return weechat.WEECHAT_RC_OK
620
621def wg_check_scripts():
622 """
623 Check status of local script(s).
624 For each script found, display status (unknown/running/new version available).
625 For example:
626 r python/autoload/vdm.py
627 ?r python/autoload/dummy.py
628 rN python/shell.py
629 perl/buffers.pl
630 """
631 local_scripts_status = wg_get_local_scripts_status()
632 if len(local_scripts_status) == 0:
633 return
634 weechat.prnt("", "")
635 weechat.prnt("", "Local scripts:")
636 for file, status in local_scripts_status:
637 str_unknown = " "
638 str_running = " "
639 str_obsolete = " "
640 if status["unknown"]:
641 str_unknown = "?"
642 if status["running"]:
643 str_running = "r"
644 if status["obsolete"]:
645 str_obsolete = "N"
646 weechat.prnt("", "%s%s%s%s%s%s%s %s%s%s%s"
647 % (wg_config_color("unknown"), str_unknown,
648 wg_config_color("running"), str_running,
649 wg_config_color("obsolete"), str_obsolete,
650 weechat.color("chat"),
651 os.path.dirname(file),
652 os.sep,
653 wg_config_color("script"),
654 os.path.basename(file)))
655
656def wg_upgrade_scripts():
657 """ Upgrade scripts. """
658 global wg_scripts, wg_scripts_to_install
659 if len(wg_scripts) == 0:
660 return
661 scripts_to_upgrade = []
662 for id, script in wg_scripts.items():
663 status = wg_get_local_script_status(script)
664 if status["installed"] and status["obsolete"]:
665 scripts_to_upgrade.append(script["name"])
666 if len(scripts_to_upgrade) == 0:
667 weechat.prnt("", "%s: all scripts are up to date" % SCRIPT_NAME)
668 else:
669 wg_scripts_to_install.extend(scripts_to_upgrade)
670 wg_install_next_script()
671
672def wg_remove_scripts(names):
673 """ Remove scripts. """
674 if len(wg_scripts) == 0:
675 return
676 list_names = names.split(" ")
677 scripts_to_remove = {}
678 for language in SCRIPT_EXTENSION.keys():
679 scripts_to_remove[language] = []
680 for name in list_names:
681 script = wg_search_script_by_name(name)
682 if script == None:
683 weechat.prnt("", "%s: script \"%s%s%s\" not found"
684 % (SCRIPT_NAME,
685 wg_config_color("script"),
686 name,
687 weechat.color("chat")))
688 else:
689 if script["full_name"] not in scripts_to_remove[script["language"]]:
690 scripts_to_remove[script["language"]].append(script["full_name"])
691 for language in SCRIPT_EXTENSION.keys():
692 if len(scripts_to_remove[language]) > 0:
693 # ask C plugin to remove script file(s)
694 weechat.hook_signal_send(language + "_script_remove",
695 weechat.WEECHAT_HOOK_SIGNAL_STRING,
696 ",".join(scripts_to_remove[language]))
697
698# ==================================[ xml ]===================================
699
700def wg_execute_action():
701 """ Execute action. """
702 global wg_action, wg_action_args, wg_loaded_scripts
703 if wg_action != "":
704 wg_get_loaded_scripts()
705 if wg_action == "list":
706 wg_list_scripts(wg_action_args)
707 elif wg_action == "listinstalled":
708 wg_list_scripts(wg_action_args, installed=True)
709 elif wg_action == "show":
710 wg_show_script(wg_action_args)
711 elif wg_action == "install":
712 wg_install_scripts(wg_action_args)
713 elif wg_action == "check":
714 wg_check_scripts()
715 elif wg_action == "upgrade":
716 wg_upgrade_scripts()
717 elif wg_action == "remove":
718 wg_remove_scripts(wg_action_args)
719 else:
720 weechat.prnt("", "%s%s: unknown action \"%s\""
721 % (weechat.prefix("error"), SCRIPT_NAME, wg_action))
722
723 # reset action
724 wg_action = ""
725 wg_action_args = ""
726 wg_loaded_scripts = {}
727
728def wg_check_version(script):
729 """ Check if a script is designed for current running WeeChat version."""
730 version = weechat.info_get("version", "")
731 version = version.split("-", 1)[0]
732 vmin = script.get("min_weechat", "")
733 vmax = script.get("max_weechat", "")
734 if vmin != "" and version < vmin:
735 return False
736 if vmax != "" and version > vmax:
737 return False
738 return True
739
740def wg_parse_xml():
741 """
742 Parse XML scripts list and return dictionary with list, with key 'id'.
743 Example of item return in dictionary :
744 '119': { 'name' : 'weeget',
745 'version' : '0.1',
746 'url' : 'http://www.weechat.org/files/scripts/weeget.py',
747 'language' : 'python',
748 'license' : 'GPL3',
749 'md5sum' : 'd500714fc19b0e10cc4e339e70739e4ad500714fc19b0e10cc4e339e70739e4a',
750 'tags' : 'scripts',
751 'desc_en' : 'Scripts manager.',
752 'desc_fr' : 'Gestionnaire de scripts.',
753 'requirements': 'python 2.5',
754 'min_weechat' : '0.3.0',
755 'max_weechat' : '',
756 'author' : 'FlashCode',
757 'mail' : 'flashcode [at] flashtux [dot] org',
758 'added' : '2009-04-05 22:39:18',
759 'updated' : '0000-00-00 00:00:00' }
760 """
761 global wg_scripts, wg_action, wg_action_args
762 wg_scripts = {}
763 try:
764 f = gzip.open(wg_config_get_cache_filename(), "rb")
765 string = f.read()
766 f.close()
767 except:
768 weechat.prnt("", "%s%s: unable to read xml file"
769 % (weechat.prefix("error"), SCRIPT_NAME))
770 else:
771 try:
772 dom = xml.dom.minidom.parseString(string)
773 except:
774 weechat.prnt("",
775 "%s%s: unable to parse xml list of scripts"
776 % (weechat.prefix("error"), SCRIPT_NAME))
777 # discard action
778 wg_action = ""
779 wg_action_args = ""
780 else:
781 for scriptNode in dom.getElementsByTagName("plugin"):
782 id = scriptNode.getAttribute("id")
783 script = {}
784 for node in scriptNode.childNodes:
785 if node.nodeType == node.ELEMENT_NODE:
786 if node.firstChild != None:
787 nodename = node.nodeName
788 value = node.firstChild.data
789 if sys.version_info < (3,):
790 # python 2.x: convert unicode to str (in python 3.x, id and text are already strings)
791 nodename = nodename.encode("utf-8")
792 value = value.encode("utf-8")
793 script[nodename] = value
794 if script["language"] in SCRIPT_EXTENSION:
795 script["full_name"] = script["name"] + "." + SCRIPT_EXTENSION[script["language"]]
796 if wg_check_version(script):
797 wg_scripts[id] = script
798 wg_execute_action()
799
800def wg_process_update_cb(data, command, rc, stdout, stderr):
801 """ Callback when reading XML cache file from website. """
802 global wg_hook_process, wg_stdout, wg_scripts
803 if stdout != "":
804 wg_stdout["update"] += stdout
805 if stderr != "":
806 wg_stdout["update"] += stderr
807 if int(rc) >= 0:
808 if wg_stdout["update"].startswith("error:"):
809 weechat.prnt("", "%s%s: error downloading scripts (%s)"
810 % (weechat.prefix("error"), SCRIPT_NAME,
811 wg_stdout["update"][6:].strip()))
812 else:
813 weechat.prnt("", "%s: scripts downloaded" % SCRIPT_NAME)
814 wg_parse_xml()
815 wg_hook_process["update"] = ""
816 return weechat.WEECHAT_RC_OK
817
818def wg_update_cache():
819 """ Download list of scripts and update local cache. """
820 global wg_config_option, wg_hook_process, wg_stdout
821 # get data from website, via hook_process
822 if wg_hook_process["update"] != "":
823 weechat.unhook(wg_hook_process["update"])
824 wg_hook_process["update"] = ""
825 weechat.prnt("", "%s: downloading list of scripts..." % SCRIPT_NAME)
826 wg_stdout["update"] = ""
827 wg_config_create_dir()
828 url = weechat.config_string(wg_config_option["scripts_url"])
829 filename = wg_config_get_cache_filename()
830 wg_hook_process["update"] = wg_download_file(url, filename, TIMEOUT_UPDATE,
831 "wg_process_update_cb", "")
832
833def wg_read_scripts(download_list=True):
834 """ Read scripts list (download list if needed and asked). """
835 global wg_scripts
836 cache_file = wg_config_get_cache_filename()
837 if os.path.isfile(cache_file):
838 # check if local cache file is too old
839 cache_expire = weechat.config_integer(wg_config_option["scripts_cache_expire"]) * 60
840 if cache_expire >= 0:
841 diff_time = time.time() - os.stat(cache_file)[stat.ST_MTIME]
842 if download_list and diff_time >= cache_expire:
843 os.unlink(cache_file)
844 wg_scripts.clear()
845 if len(wg_scripts) > 0:
846 wg_execute_action()
847 else:
848 if os.path.isfile(cache_file):
849 wg_parse_xml()
850 elif download_list:
851 wg_update_cache()
852
853# ================================[ command ]=================================
854
855def wg_cmd(data, buffer, args):
856 """ Callback for /weeget command. """
857 global wg_action, wg_action_args
858 if args == "":
859 weechat.command("", "/help %s" % SCRIPT_COMMAND)
860 return weechat.WEECHAT_RC_OK
861 argv = args.strip().split(" ", 1)
862 if len(argv) == 0:
863 return weechat.WEECHAT_RC_OK
864
865 wg_action = ""
866 wg_action_args = ""
867
868 # check arguments
869 if len(argv) < 2:
870 if argv[0] == "show" or \
871 argv[0] == "install" or \
872 argv[0] == "remove":
873 weechat.prnt("", "%s: too few arguments for action \"%s\""
874 % (SCRIPT_NAME, argv[0]))
875 return weechat.WEECHAT_RC_OK
876
877 # execute asked action
878 if argv[0] == "update":
879 wg_update_cache()
880 else:
881 wg_action = argv[0]
882 wg_action_args = ""
883 if len(argv) > 1:
884 wg_action_args = argv[1]
885 wg_read_scripts()
886
887 return weechat.WEECHAT_RC_OK
888
889def wg_completion_scripts_cb(data, completion_item, buffer, completion):
890 """ Complete with known script names, for command '/weeget'. """
891 global wg_scripts
892 wg_read_scripts(download_list=False)
893 if len(wg_scripts) > 0:
894 for id, script in wg_scripts.items():
895 weechat.hook_completion_list_add(completion, script["full_name"],
896 0, weechat.WEECHAT_LIST_POS_SORT)
897 return weechat.WEECHAT_RC_OK
898
899def wg_completion_scripts_installed_cb(data, completion_item, buffer, completion):
900 """ Complete with names of scripts installed, for command '/weeget'. """
901 global wg_scripts
902 wg_read_scripts(download_list=False)
903 if len(wg_scripts) > 0:
904 for id, script in wg_scripts.items():
905 status = wg_get_local_script_status(script)
906 if status["installed"]:
907 weechat.hook_completion_list_add(completion, script["full_name"],
908 0, weechat.WEECHAT_LIST_POS_SORT)
909 return weechat.WEECHAT_RC_OK
910
911def wg_completion_scripts_tags_cb(data, completion_item, buffer, completion):
912 """ Complete with known tags, for command '/weeget'. """
913 global wg_scripts
914 wg_read_scripts(download_list=False)
915 if len(wg_scripts) > 0:
916 for id, script in wg_scripts.items():
917 if script["tags"]:
918 for tag in script["tags"].split(","):
919 weechat.hook_completion_list_add(completion, tag,
920 0, weechat.WEECHAT_LIST_POS_SORT)
921 return weechat.WEECHAT_RC_OK
922
923# ==================================[ main ]==================================
924
925if __name__ == "__main__" and import_ok:
926 if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE,
927 SCRIPT_DESC, "wg_unload_script", ""):
928 wg_config_init()
929 wg_config_read()
930 if weechat.config_string(wg_config_option["scripts_url"]).find("weechat.flashtux.org") >= 0:
931 weechat.prnt("", "%sWarning: old site still used in URL for plugins.xml.gz, you should do: /unset wg.scripts.url"
932 % weechat.prefix("error"))
933 str_installed = wg_config_color("installed") + "i" + weechat.color("chat")
934 str_unknown = wg_config_color("unknown") + "?" + weechat.color("chat")
935 str_running = wg_config_color("running") + "r" + weechat.color("chat")
936 str_obsolete = wg_config_color("obsolete") + "N" + weechat.color("chat")
937 weechat.hook_command(SCRIPT_COMMAND,
938 "WeeChat scripts manager",
939 "list|listinstalled [<text>|<tag>] || show <script>"
940 " || install|remove <script> [<script>...] || check|update|upgrade",
941 " list: list scripts (search text if given)\n"
942 "listinstalled: list installed scripts (search text if given)\n"
943 " show: show detailed information about a script (in repository)\n"
944 " install: install/upgrade script(s)\n"
945 " check: check if local scripts needs upgrade\n"
946 " update: update local scripts cache\n"
947 " upgrade: upgrade all local scripts if they are obsolete\n"
948 " remove: remove script(s)\n\n"
949 "Indicators in lists (first column):\n"
950 " " + str_installed + " script is installed\n"
951 " " + str_unknown + " unknown script\n"
952 " " + str_running + " script is running (loaded)\n"
953 " " + str_obsolete + " script is obsolete (new version available)\n\n"
954 "Examples:\n"
955 " /" + SCRIPT_COMMAND + " list => list all scripts\n"
956 " /" + SCRIPT_COMMAND + " list game => list all scripts with text/tag \"game\"\n"
957 " /" + SCRIPT_COMMAND + " install beep.pl => install script beep.pl\n"
958 " /" + SCRIPT_COMMAND + " remove beep.pl => remove script beep.pl",
959 "list %(weeget_scripts_tags)"
960 " || listinstalled %(weeget_scripts_tags)"
961 " || show %(weeget_scripts)"
962 " || install %(weeget_scripts)|%*"
963 " || remove %(weeget_scripts_installed)|%*"
964 " || check"
965 " || update"
966 " || upgrade",
967 "wg_cmd", "")
968 weechat.hook_completion("weeget_scripts", "list of scripts in repository",
969 "wg_completion_scripts_cb", "")
970 weechat.hook_completion("weeget_scripts_installed", "list of scripts installed",
971 "wg_completion_scripts_installed_cb", "")
972 weechat.hook_completion("weeget_scripts_tags", "tags of scripts in repository",
973 "wg_completion_scripts_tags_cb", "")
974
975# ==================================[ end ]===================================
976
977def wg_unload_script():
978 """ Function called when script is unloaded. """
979 wg_config_write()
980 return weechat.WEECHAT_RC_OK