diff options
| author | Antti Ajanki <antti.ajanki@iki.fi> | 2010-07-23 20:55:11 +0300 |
|---|---|---|
| committer | Antti Ajanki <antti.ajanki@iki.fi> | 2010-07-23 20:55:11 +0300 |
| commit | 310743fb9ebbf68b253b923a309cc5f635da89a1 (patch) | |
| tree | 59c365db7459649344b4ab6d58fde1ceb362506d /src/webvicli | |
| download | vdr-plugin-webvideo-310743fb9ebbf68b253b923a309cc5f635da89a1.tar.gz vdr-plugin-webvideo-310743fb9ebbf68b253b923a309cc5f635da89a1.tar.bz2 | |
release 0.3.0
Diffstat (limited to 'src/webvicli')
| -rwxr-xr-x | src/webvicli/webvi | 22 | ||||
| -rw-r--r-- | src/webvicli/webvicli/__init__.py | 1 | ||||
| -rw-r--r-- | src/webvicli/webvicli/client.py | 729 | ||||
| -rw-r--r-- | src/webvicli/webvicli/menu.py | 171 |
4 files changed, 923 insertions, 0 deletions
diff --git a/src/webvicli/webvi b/src/webvicli/webvi new file mode 100755 index 0000000..b8fa190 --- /dev/null +++ b/src/webvicli/webvi @@ -0,0 +1,22 @@ +#!/usr/bin/python + +# menu.py - starter script for webvicli +# +# Copyright (c) 2010 Antti Ajanki <antti.ajanki@iki.fi> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +from webvicli import client +client.main(sys.argv[1:]) diff --git a/src/webvicli/webvicli/__init__.py b/src/webvicli/webvicli/__init__.py new file mode 100644 index 0000000..1cf59b7 --- /dev/null +++ b/src/webvicli/webvicli/__init__.py @@ -0,0 +1 @@ +__all__ = ['client', 'menu'] diff --git a/src/webvicli/webvicli/client.py b/src/webvicli/webvicli/client.py new file mode 100644 index 0000000..782c47c --- /dev/null +++ b/src/webvicli/webvicli/client.py @@ -0,0 +1,729 @@ +#!/usr/bin/env python + +# webvicli.py - webvi command line client +# +# Copyright (c) 2009, 2010 Antti Ajanki <antti.ajanki@iki.fi> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import cStringIO +import sys +import cmd +import mimetypes +import select +import os.path +import subprocess +import time +import re +import libxml2 +import webvi.api +import webvi.utils +from optparse import OptionParser +from ConfigParser import RawConfigParser +from webvi.constants import WebviRequestType, WebviErr, WebviOpt, WebviInfo, WebviSelectBitmask, WebviConfig +from . import menu + +VERSION = '0.3.0' + +# Default options +DEFAULT_PLAYERS = ['vlc --play-and-exit "%s"', + 'totem "%s"', + 'mplayer "%s"', + 'xine "%s"'] + +# These mimetypes are common but often missing +mimetypes.init() +mimetypes.add_type('video/flv', '.flv') +mimetypes.add_type('video/x-flv', '.flv') + +def safe_filename(name): + """Sanitize a filename. No paths (replace '/' -> '!') and no + names starting with a dot.""" + res = name.replace('/', '!').lstrip('.') + res = res.encode(sys.getfilesystemencoding(), 'ignore') + return res + +class DownloadData: + def __init__(self, handle, progressstream): + self.handle = handle + self.destfile = None + self.destfilename = '' + self.contentlength = -1 + self.bytes_downloaded = 0 + self.progress = ProgressMeter(progressstream) + +class ProgressMeter: + def __init__(self, stream): + self.last_update = None + self.samples = [] + self.total_bytes = 0 + self.stream = stream + self.progress_len = 0 + self.starttime = time.time() + + def pretty_bytes(self, bytes): + """Pretty print bytes as kB or MB.""" + if bytes < 1100: + return '%d B' % bytes + elif bytes < 1024*1024: + return '%.1f kB' % (float(bytes)/1024) + elif bytes < 1024*1024*1024: + return '%.1f MB' % (float(bytes)/1024/1024) + else: + return '%.1f GB' % (float(bytes)/1024/1024/1024) + + def pretty_time(self, seconds): + """Pretty print seconds as hour and minutes.""" + seconds = int(round(seconds)) + if seconds < 60: + return '%d s' % seconds + elif seconds < 60*60: + secs = seconds % 60 + mins = seconds/60 + return '%d min %d s' % (mins, secs) + else: + hours = seconds / (60*60) + mins = (seconds-60*60*hours) / 60 + return '%d hours %d min' % (hours, mins) + + def update(self, bytes): + """Update progress bar. + + Updates the estimates of download rate and remaining time. + Prints progress bar, if at least one second has passed since + the previous update. + """ + now = time.time() + + if self.total_bytes > 0: + percentage = float(bytes)/self.total_bytes * 100.0 + else: + percentage = 0 + + if self.total_bytes > 0 and bytes >= self.total_bytes: + self.stream.write('\r') + self.stream.write(' '*self.progress_len) + self.stream.write('\r') + self.stream.write('%3.f %% of %s downloaded in %s (%.1f kB/s)\n' % + (percentage, self.pretty_bytes(self.total_bytes), + self.pretty_time(now-self.starttime), + float(bytes)/(now-self.starttime)/1024.0)) + self.stream.flush() + return + + force_refresh = False + if self.last_update is None: + # This is a new progress meter + self.last_update = now + force_refresh = True + + if (not force_refresh) and (now <= self.last_update + 1): + # do not update too often + return + + self.last_update = now + + # Estimate bytes per second rate from the last 10 samples + self.samples.append((bytes, now)) + if len(self.samples) > 10: + self.samples.pop(0) + + bytes_old, time_old = self.samples[0] + if now > time_old: + rate = float(bytes-bytes_old)/(now-time_old) + else: + rate = 0 + + if self.total_bytes > 0: + remaining = self.total_bytes - bytes + + if rate > 0: + time_left = self.pretty_time(remaining/rate) + else: + time_left = '???' + + progress = '%3.f %% of %s (%.1f kB/s) %s remaining' % \ + (percentage, self.pretty_bytes(self.total_bytes), + rate/1024.0, time_left) + else: + progress = '%s downloaded (%.1f kB/s)' % \ + (self.pretty_bytes(bytes), rate/1024.0) + + new_progress_len = len(progress) + if new_progress_len < self.progress_len: + progress += ' '*(self.progress_len - new_progress_len) + self.progress_len = new_progress_len + + self.stream.write('\r') + self.stream.write(progress) + self.stream.flush() + + +class WVClient: + def __init__(self, streamplayers, downloadlimits, streamlimits): + self.streamplayers = streamplayers + self.history = [] + self.history_pointer = 0 + self.quality_limits = {'download': downloadlimits, + 'stream': streamlimits} + + def parse_page(self, page): + if page is None: + return None + try: + doc = libxml2.parseDoc(page) + except libxml2.parserError: + return None + + root = doc.getRootElement() + if root.name != 'wvmenu': + return None + queryitems = [] + menupage = menu.Menu() + node = root.children + while node: + if node.name == 'title': + menupage.title = webvi.utils.get_content_unicode(node) + elif node.name == 'link': + menuitem = self.parse_link(node) + menupage.add(menuitem) + elif node.name == 'textfield': + menuitem = self.parse_textfield(node) + menupage.add(menuitem) + queryitems.append(menuitem) + elif node.name == 'itemlist': + menuitem = self.parse_itemlist(node) + menupage.add(menuitem) + queryitems.append(menuitem) + elif node.name == 'textarea': + menuitem = self.parse_textarea(node) + menupage.add(menuitem) + elif node.name == 'button': + menuitem = self.parse_button(node, queryitems) + menupage.add(menuitem) + node = node.next + doc.freeDoc() + return menupage + + def parse_link(self, node): + label = '' + ref = None + stream = None + child = node.children + while child: + if child.name == 'label': + label = webvi.utils.get_content_unicode(child) + elif child.name == 'ref': + ref = webvi.utils.get_content_unicode(child) + elif child.name == 'stream': + stream = webvi.utils.get_content_unicode(child) + child = child.next + return menu.MenuItemLink(label, ref, stream) + + def parse_textfield(self, node): + label = '' + name = node.prop('name') + child = node.children + while child: + if child.name == 'label': + label = webvi.utils.get_content_unicode(child) + child = child.next + return menu.MenuItemTextField(label, name) + + def parse_textarea(self, node): + label = '' + child = node.children + while child: + if child.name == 'label': + label = webvi.utils.get_content_unicode(child) + child = child.next + return menu.MenuItemTextArea(label) + + def parse_itemlist(self, node): + label = '' + name = node.prop('name') + items = [] + values = [] + child = node.children + while child: + if child.name == 'label': + label = webvi.utils.get_content_unicode(child) + elif child.name == 'item': + items.append(webvi.utils.get_content_unicode(child)) + values.append(child.prop('value')) + child = child.next + return menu.MenuItemList(label, name, items, values, sys.stdout) + + def parse_button(self, node, queryitems): + label = '' + submission = None + child = node.children + while child: + if child.name == 'label': + label = webvi.utils.get_content_unicode(child) + elif child.name == 'submission': + submission = webvi.utils.get_content_unicode(child) + child = child.next + return menu.MenuItemSubmitButton(label, submission, queryitems) + + def guess_extension(self, mimetype, url): + ext = mimetypes.guess_extension(mimetype) + if (ext is None) or (mimetype == 'text/plain'): + # This function is only called for video files. Try to + # extract the extension from url because text/plain is + # clearly wrong. + lastcomponent = re.split(r'[?#]', url, 1)[0].split('/')[-1] + i = lastcomponent.rfind('.') + if i == -1: + ext = '' + else: + ext = lastcomponent[i:] + + return ext + + def execute_webvi(self, handle): + """Call webvi.api.perform until handle is finished.""" + while True: + rescode, readfds, writefds, excfds, maxfd = webvi.api.fdset() + if [] == readfds == writefds == excfds: + finished, status, errmsg, remaining = webvi.api.pop_message() + if finished == handle: + return (status, errmsg) + else: + return (501, 'No active sockets') + + readyread, readywrite, readyexc = select.select(readfds, writefds, excfds, 30.0) + + for fd in readyread: + webvi.api.perform(fd, WebviSelectBitmask.READ) + for fd in readywrite: + webvi.api.perform(fd, WebviSelectBitmask.WRITE) + + remaining = -1 + while remaining != 0: + finished, status, errmsg, remaining = webvi.api.pop_message() + if finished == handle: + return (status, errmsg) + + def collect_data(self, inp, inplen, dlbuffer): + """Callback that writes the downloaded data to dlbuffer. + """ + dlbuffer.write(inp) + return inplen + + def open_dest_file(self, inp, inplen, dldata): + """Initial download callback. This opens the destination file, + and reseats the callback to self.write_to_dest. The + destination file can not be opened until now, because the + stream title and final URL are not known before. + """ + title = webvi.api.get_info(dldata.handle, WebviInfo.STREAM_TITLE)[1] + contenttype = webvi.api.get_info(dldata.handle, WebviInfo.CONTENT_TYPE)[1] + contentlength = webvi.api.get_info(dldata.handle, WebviInfo.CONTENT_LENGTH)[1] + url = webvi.api.get_info(dldata.handle, WebviInfo.URL)[1] + ext = self.guess_extension(contenttype, url) + destfilename = self.next_available_file_name(safe_filename(title), ext) + + try: + destfile = open(destfilename, 'w') + except IOError, err: + print 'Failed to open the destination file %s: %s' % (destfilename, err.args[1]) + return -1 + + dldata.destfile = destfile + dldata.destfilename = destfilename + dldata.contentlength = contentlength + dldata.progress.total_bytes = contentlength + webvi.api.set_opt(dldata.handle, WebviOpt.WRITEFUNC, self.write_to_dest) + + return self.write_to_dest(inp, inplen, dldata) + + def write_to_dest(self, inp, inplen, dldata): + """Callback that writes downloaded data to self.destfile.""" + try: + dldata.destfile.write(inp) + except IOError, err: + print 'IOError while writing to %s: %s' % \ + (dldata.destfilename, err.args[1]) + return -1 + + dldata.bytes_downloaded += inplen + + dldata.progress.update(dldata.bytes_downloaded) + + return inplen + + def getmenu(self, ref): + dlbuffer = cStringIO.StringIO() + handle = webvi.api.new_request(ref, WebviRequestType.MENU) + if handle == -1: + print 'Failed to open handle' + return (-1, '', None) + + webvi.api.set_opt(handle, WebviOpt.WRITEFUNC, self.collect_data) + webvi.api.set_opt(handle, WebviOpt.WRITEDATA, dlbuffer) + webvi.api.start_handle(handle) + + status, err = self.execute_webvi(handle) + webvi.api.delete_handle(handle) + + if status != 0: + print 'Download failed:', err + return (status, err, None) + + return (status, err, self.parse_page(dlbuffer.getvalue())) + + def get_quality_params(self, videosite, streamtype): + params = [] + lim = self.quality_limits[streamtype].get(videosite, {}) + + if lim.has_key('min'): + params.append('minquality=' + lim['min']) + if lim.has_key('max'): + params.append('maxquality=' + lim['max']) + + return '&'.join(params) + + def download(self, stream): + m = re.match(r'wvt:///([^/]+)/', stream) + if m is not None: + stream += '&' + self.get_quality_params(m.group(1), 'download') + + handle = webvi.api.new_request(stream, WebviRequestType.FILE) + if handle == -1: + print 'Failed to open handle' + return False + + dldata = DownloadData(handle, sys.stdout) + + webvi.api.set_opt(handle, WebviOpt.WRITEFUNC, self.open_dest_file) + webvi.api.set_opt(handle, WebviOpt.WRITEDATA, dldata) + webvi.api.start_handle(handle) + + status, err = self.execute_webvi(handle) + if dldata.destfile is not None: + dldata.destfile.close() + + webvi.api.delete_handle(handle) + + if status not in (0, 504): + print 'Download failed:', err + return + + if dldata.contentlength != -1 and \ + dldata.bytes_downloaded != dldata.contentlength: + print 'Warning: the size of the file (%d) differs from expected (%d)' % \ + (dldata.bytes_downloaded, dldata.contentlength) + + print 'Saved to %s' % dldata.destfilename + + return True + + def play_stream(self, ref): + streamurl = self.get_stream_url(ref) + if streamurl == '': + print 'Did not find URL' + return False + + # Found url, now find a working media player + for player in self.streamplayers: + if '%s' not in player: + playcmd = player + ' ' + streamurl + else: + try: + playcmd = player % streamurl + except TypeError: + print 'Can\'t substitute URL in', player + continue + + try: + print 'Trying player: ' + playcmd + retcode = subprocess.call(playcmd, shell=True) + if retcode > 0: + print 'Player failed with returncode', retcode + else: + return True + except OSError, err: + print 'Execution failed:', err + + return False + + def get_stream_url(self, ref): + m = re.match(r'wvt:///([^/]+)/', ref) + if m is not None: + ref += '&' + self.get_quality_params(m.group(1), 'stream') + + handle = webvi.api.new_request(ref, WebviRequestType.STREAMURL) + if handle == -1: + print 'Failed to open handle' + return '' + + dlbuffer = cStringIO.StringIO() + webvi.api.set_opt(handle, WebviOpt.WRITEFUNC, self.collect_data) + webvi.api.set_opt(handle, WebviOpt.WRITEDATA, dlbuffer) + webvi.api.start_handle(handle) + status, err = self.execute_webvi(handle) + webvi.api.delete_handle(handle) + + if status != 0: + print 'Download failed:', err + return '' + + return dlbuffer.getvalue() + + def next_available_file_name(self, basename, ext): + fullname = basename + ext + if not os.path.exists(fullname): + return fullname + i = 1 + while os.path.exists('%s-%d%s' % (basename, i, ext)): + i += 1 + return '%s-%d%s' % (basename, i, ext) + + def get_current_menu(self): + if (self.history_pointer >= 0) and \ + (self.history_pointer < len(self.history)): + return self.history[self.history_pointer] + else: + return None + + def history_add(self, menupage): + if menupage is not None: + self.history = self.history[:(self.history_pointer+1)] + self.history.append(menupage) + self.history_pointer = len(self.history)-1 + + def history_back(self): + if self.history_pointer > 0: + self.history_pointer -= 1 + return self.get_current_menu() + + def history_forward(self): + if self.history_pointer < len(self.history)-1: + self.history_pointer += 1 + return self.get_current_menu() + + +class WVShell(cmd.Cmd): + def __init__(self, client, completekey='tab', stdin=None, stdout=None): + cmd.Cmd.__init__(self, completekey, stdin, stdout) + self.prompt = '> ' + self.client = client + + def preloop(self): + self.stdout.write('webvicli %s starting\n' % VERSION) + self.do_menu(None) + + def precmd(self, arg): + try: + int(arg) + return 'select ' + arg + except ValueError: + return arg + + def onecmd(self, c): + try: + return cmd.Cmd.onecmd(self, c) + except Exception: + import traceback + print 'Exception occured while handling command "' + c + '"' + print traceback.format_exc() + return False + + def emptyline(self): + pass + + def display_menu(self, menupage): + if menupage is not None: + self.stdout.write(unicode(menupage).encode(self.stdout.encoding, 'replace')) + + def _get_numbered_item(self, arg): + menupage = self.client.get_current_menu() + try: + v = int(arg)-1 + if (menupage is None) or (v < 0) or (v >= len(menupage)): + raise ValueError + except ValueError: + self.stdout.write('Invalid selection: %s\n' % arg) + return None + return menupage[v] + + def do_select(self, arg): + """select x +Select the link whose index is x. + """ + menuitem = self._get_numbered_item(arg) + if menuitem is None: + return False + ref = menuitem.activate() + if ref is not None: + status, statusmsg, menupage = self.client.getmenu(ref) + if menupage is not None: + self.client.history_add(menupage) + else: + self.stdout.write('Error: %d %s\n' % (status, statusmsg)) + else: + menupage = self.client.get_current_menu() + self.display_menu(menupage) + return False + + def do_download(self, arg): + """download x +Download media stream whose index is x to a file. Downloadable items +are the ones without brackets. + """ + menuitem = self._get_numbered_item(arg) + if menuitem is None: + return False + elif hasattr(menuitem, 'stream') and menuitem.stream is not None: + self.client.download(menuitem.stream) + else: + self.stdout.write('Not a stream\n') + return False + + def do_stream(self, arg): + """stream x +Play the media file whose index is x. Streams are the ones +without brackets. + """ + menuitem = self._get_numbered_item(arg) + if menuitem is None: + return False + elif hasattr(menuitem, 'stream') and menuitem.stream is not None: + self.client.play_stream(menuitem.stream) + else: + self.stdout.write('Not a stream\n') + return False + + def do_display(self, arg): + """Redisplay the current menu.""" + if not arg: + self.display_menu(self.client.get_current_menu()) + else: + self.stdout.write('Unknown parameter %s\n' % arg) + return False + + def do_menu(self, arg): + """Get back to the main menu.""" + status, statusmsg, menupage = self.client.getmenu('wvt:///?srcurl=mainmenu') + if menupage is not None: + self.client.history_add(menupage) + self.display_menu(menupage) + else: + self.stdout.write('Error: %d %s\n' % (status, statusmsg)) + return True + return False + + def do_back(self, arg): + """Go to the previous menu in the history.""" + menupage = self.client.history_back() + self.display_menu(menupage) + return False + + def do_forward(self, arg): + """Go to the next menu in the history.""" + menupage = self.client.history_forward() + self.display_menu(menupage) + return False + + def do_quit(self, arg): + """Quit the program.""" + return True + + def do_EOF(self, arg): + """Quit the program.""" + return True + + +def load_config(options): + """Load options from config files.""" + cfgprs = RawConfigParser() + cfgprs.read(['/etc/webvi.conf', os.path.expanduser('~/.webvi')]) + for sec in cfgprs.sections(): + if sec == 'webvi': + for opt, val in cfgprs.items('webvi'): + options[opt] = val + + elif sec.startswith('site-'): + sitename = sec[5:] + + if not options.has_key('download-limits'): + options['download-limits'] = {} + if not options.has_key('stream-limits'): + options['stream-limits'] = {} + options['download-limits'][sitename] = {} + options['stream-limits'][sitename] = {} + + for opt, val in cfgprs.items(sec): + if opt == 'download-min-quality': + options['download-limits'][sitename]['min'] = val + elif opt == 'download-max-quality': + options['download-limits'][sitename]['max'] = val + elif opt == 'stream-min-quality': + options['stream-limits'][sitename]['min'] = val + elif opt == 'stream-max-quality': + options['stream-limits'][sitename]['max'] = val + + return options + +def parse_command_line(cmdlineargs, options): + parser = OptionParser() + parser.add_option('-t', '--templatepath', type='string', + dest='templatepath', + help='read video site templates from DIR', metavar='DIR', + default=None) + cmdlineopt = parser.parse_args(cmdlineargs)[0] + + if cmdlineopt.templatepath is not None: + options['templatepath'] = cmdlineopt.templatepath + + return options + +def player_list(options): + """Return a sorted list of player commands extracted from options + dictionary.""" + # Load streamplayer items from the config file and sort them + # according to quality. + players = [] + for opt, val in options.iteritems(): + m = re.match(r'streamplayer([1-9])$', opt) + if m is not None: + players.append((int(m.group(1)), val)) + + players.sort() + ret = [] + for quality, playcmd in players: + ret.append(playcmd) + + # If the config file did not define any players use the default + # players + if not ret: + ret = list(DEFAULT_PLAYERS) + + return ret + +def main(argv): + options = load_config({}) + options = parse_command_line(argv, options) + + if options.has_key('templatepath'): + webvi.api.set_config(WebviConfig.TEMPLATE_PATH, options['templatepath']) + + shell = WVShell(WVClient(player_list(options), + options.get('download-limits', {}), + options.get('stream-limits', {}))) + shell.cmdloop() + +if __name__ == '__main__': + main([]) diff --git a/src/webvicli/webvicli/menu.py b/src/webvicli/webvicli/menu.py new file mode 100644 index 0000000..70ef6ea --- /dev/null +++ b/src/webvicli/webvicli/menu.py @@ -0,0 +1,171 @@ +# menu.py - menu elements for webvicli +# +# Copyright (c) 2009, 2010 Antti Ajanki <antti.ajanki@iki.fi> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import textwrap +import urllib + +LINEWIDTH = 72 + +class Menu: + def __init__(self): + self.title = None + self.items = [] + + def __str__(self): + s = u'' + if self.title: + s = self.title + '\n' + '='*len(self.title) + '\n' + for i, item in enumerate(self.items): + if isinstance(item, MenuItemTextArea): + num = ' ' + else: + num = '%d.' % (i+1) + + s += u'%s %s\n' % (num, unicode(item).replace('\n', '\n ')) + return s + + def __getitem__(self, i): + return self.items[i] + + def __len__(self): + return len(self.items) + + def add(self, menuitem): + self.items.append(menuitem) + + +class MenuItemLink: + def __init__(self, label, ref, stream): + self.label = label + if type(ref) == unicode: + self.ref = ref.encode('utf-8') + else: + self.ref = ref + self.stream = stream + + def __str__(self): + res = self.label + if not self.stream: + res = '[' + res + ']' + return res + + def activate(self): + return self.ref + + +class MenuItemTextField: + def __init__(self, label, name): + self.label = label + self.name = name + self.value = u'' + + def __str__(self): + return u'%s: %s' % (self.label, self.value) + + def get_query(self): + return {self.name: self.value} + + def activate(self): + self.value = unicode(raw_input('%s> ' % self.label), sys.stdin.encoding) + return None + + +class MenuItemTextArea: + def __init__(self, label): + self.label = label + + def __str__(self): + return textwrap.fill(self.label, width=LINEWIDTH) + + def activate(self): + return None + + +class MenuItemList: + def __init__(self, label, name, items, values, stdout): + self.label = label + self.name = name + assert len(items) == len(values) + self.items = items + self.values = values + self.current = 0 + self.stdout = stdout + + def __str__(self): + itemstrings = [] + for i, itemname in enumerate(self.items): + if i == self.current: + itemstrings.append('<' + itemname + '>') + else: + itemstrings.append(itemname) + + lab = self.label + ': ' + return textwrap.fill(u', '.join(itemstrings), width=LINEWIDTH, + initial_indent=lab, + subsequent_indent=' '*len(lab)) + + def get_query(self): + if (self.current >= 0) and (self.current < len(self.items)): + return {self.name: self.values[self.current]} + else: + return {} + + def activate(self): + itemstrings = [] + for i, itemname in enumerate(self.items): + itemstrings.append('%d. %s' % (i+1, itemname)) + + self.stdout.write(u'\n'.join(itemstrings).encode(self.stdout.encoding, 'replace')) + self.stdout.write('\n') + + tmp = raw_input('Select item (1-%d)> ' % len(self.items)) + try: + i = int(tmp) + if (i < 1) or (i > len(self.items)): + raise ValueError + self.current = i-1 + except ValueError: + self.stdout.write('Must be an integer in the range 1 - %d\n' % len(self.items)) + return None + + +class MenuItemSubmitButton: + def __init__(self, label, baseurl, subitems): + self.label = label + if type(baseurl) == unicode: + self.baseurl = baseurl.encode('utf-8') + else: + self.baseurl = baseurl + self.subitems = subitems + + def __str__(self): + return '[' + self.label + ']' + + def activate(self): + baseurl = self.baseurl + if baseurl.find('?') == -1: + baseurl += '?' + else: + baseurl += '&' + + parts = [] + for sub in self.subitems: + for key, val in sub.get_query().iteritems(): + parts.append('subst=' + urllib.quote_plus(key.encode('utf-8')) + ',' + urllib.quote_plus(val.encode('utf-8'))) + + return baseurl + '&'.join(parts) |
