]>
Commit | Line | Data |
---|---|---|
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 | ||
69 | SCRIPT_NAME = "weeget" | |
70 | SCRIPT_AUTHOR = "Sebastien Helleu <flashcode@flashtux.org>" | |
71 | SCRIPT_VERSION = "1.8" | |
72 | SCRIPT_LICENSE = "GPL3" | |
73 | SCRIPT_DESC = "WeeChat scripts manager" | |
74 | ||
75 | SCRIPT_COMMAND = "weeget" | |
76 | ||
77 | import_ok = True | |
78 | ||
79 | try: | |
80 | import weechat | |
81 | except 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 | ||
86 | try: | |
87 | import sys, os, stat, time, gzip, hashlib, xml.dom.minidom | |
88 | except ImportError as message: | |
89 | print("Missing package(s) for %s: %s" % (SCRIPT_NAME, message)) | |
90 | import_ok = False | |
91 | ||
92 | CONFIG_FILE_NAME = "wg" | |
93 | ||
94 | SCRIPT_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 | |
104 | TIMEOUT_UPDATE = 60 * 1000 | |
105 | ||
106 | # timeout for download of a script | |
107 | TIMEOUT_SCRIPT = 60 * 1000 | |
108 | ||
109 | # config file and options | |
110 | wg_config_file = "" | |
111 | wg_config_option = {} | |
112 | ||
113 | # action (install, remove, ..) and arguments | |
114 | wg_action = "" | |
115 | wg_action_args = "" | |
116 | ||
117 | # loaded scripts | |
118 | wg_loaded_scripts = {} | |
119 | ||
120 | # hook process and stdout | |
121 | wg_hook_process = { "update": "", "script": "" } | |
122 | wg_stdout = { "update": "", "script": "" } | |
123 | ||
124 | # scripts read from plugins.xml.gz | |
125 | wg_scripts = {} | |
126 | ||
127 | # list of script to install, and script currently installing | |
128 | wg_scripts_to_install = [] | |
129 | wg_current_script_install = {} | |
130 | ||
131 | # =================================[ config ]================================= | |
132 | ||
133 | def 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 | ||
196 | def wg_config_reload_cb(data, config_file): | |
197 | """ Reload configuration file. """ | |
198 | return weechat.config_reload(config_file) | |
199 | ||
200 | def wg_config_read(): | |
201 | """ Read configuration file. """ | |
202 | global wg_config_file | |
203 | return weechat.config_read(wg_config_file) | |
204 | ||
205 | def wg_config_write(): | |
206 | """ Write configuration file. """ | |
207 | global wg_config_file | |
208 | return weechat.config_write(wg_config_file) | |
209 | ||
210 | def 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 | ||
218 | def 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 | ||
225 | def 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 | ||
231 | def 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 | ||
239 | def 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 | ||
268 | def 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 | ||
279 | def 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 | ||
295 | def 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 | ||
306 | def 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 | ||
330 | def 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 | ||
360 | def 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 | ||
388 | def 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 | ||
403 | def 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 | ||
472 | def 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 | ||
542 | def 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 | ||
593 | def 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 | ||
600 | def 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 | ||
621 | def 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 | ||
656 | def 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 | ||
672 | def 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 | ||
700 | def 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 | ||
728 | def 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 | ||
740 | def 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 | ||
800 | def 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 | ||
818 | def 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 | ||
833 | def 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 | ||
855 | def 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 | ||
889 | def 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 | ||
899 | def 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 | ||
911 | def 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 | ||
925 | if __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 | ||
977 | def wg_unload_script(): | |
978 | """ Function called when script is unloaded. """ | |
979 | wg_config_write() | |
980 | return weechat.WEECHAT_RC_OK |