#!/usr/bin/env python # # radwikiedit - edit wiki pages from the command line # Copyright (C) 2006 RADLogic Pty Ltd. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # # This library 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 # Lesser General Public License for more details. # # See http://www.fsf.org/licensing/licenses/lgpl.txt for full license text. """Edit a wiki page via the command line. This is designed to be called called from emacs as well as the command line. This currently only supports MoinMoin. This uses form-scraping because the moin xmlrpc interface is not quite up to scratch at the moment. Use cases: - Create a new wiki page Get new data. Check that the new page is empty. Send data to page. (Note: Barf if existing page not empty. Beware of losing data.) - Add a block of text the top/bottom of an existing wiki page Get new data. Send data to page. Create if it doesn't exist. - Pull the text from an existing page, edit it, and push it back Grab existing data. Create if it doesn't exist. Present it to the editor. Get new data from editor. Send data to page. - Attach a file to a wiki page Requirements: - Python 2.2 or greater - ClientForm ( http://wwwsearch.sourceforge.net/ClientForm/ ) - ClientCookie ( http://wwwsearch.sourceforge.net/ClientCookie/ ) Todo: - make this work with mediawiki as well as moinmoin (this should be quite easy) """ __author__ = 'Tim Wegener ' __date__ = '$Date: 2007/03/27 04:26:53 $' __version__ = '$Revision: 0.4 $' import sys import optparse import ClientForm import ClientCookie try: True, False except NameError: True, False = 1, not 1 class Error(Exception): pass class WikiEditor: """Interface for editing wiki pages""" page_prefix = '/' edit_command = '?action=edit' attach_command = '?action=AttachFile' # ClientForm does not support selecting forms by name. ## form_name = 'editor' edit_form_index = 0 # i-th form on page wiki_text_field = 'savetext' summary_field = 'comment' attach_form_index = 3 # i-th form on page file_field = 'file' rename_field = 'rename' def __init__(self, wiki_url, cookie_file=None): self.wiki_url = wiki_url cookie_jar = ClientCookie.MozillaCookieJar() if cookie_file is not None: cookie_jar.load(cookie_file) opener = ClientCookie.build_opener( ClientCookie.HTTPCookieProcessor(cookie_jar)) self.opener = opener def read_form(self, page, action='edit'): """Return a object representing the first form found on the page. Arguments: page -- name of wiki page containing a form action -- 'edit'|'attach' """ if action == 'edit': command = self.edit_command form_index = self.edit_form_index elif action == 'attach': command = self.attach_command form_index = self.attach_form_index form_url = '%s%s%s%s' % (self.wiki_url, self.page_prefix, page, command) try: response = self.opener.open(form_url) except ClientForm.urllib2.HTTPError, exc: raise Error('%s ( %s )' % (exc, form_url)) forms = ClientForm.ParseResponse(response) response.close() form = forms[form_index] return form def get_wiki_text(self, page, cancel=True): """Return the wiki text for the specified page.""" form = self.read_form(page) text = form[self.wiki_text_field] if cancel: request = form.click('button_cancel') try: response = self.opener.open(request) except ClientForm.urllib2.HTTPError, exc: # This error will occur if it is a new page. # It can be safely ignored. pass return text def set_wiki_text(self, page, text, summary='', form=None): """Set the specified text for the given wiki page. This will replace any existing text on the page. """ if form is None: form = self.read_form(page) form[self.wiki_text_field] = text form[self.summary_field] = summary # todo: Make the button name a class variable. request = form.click('button_save') response = self.opener.open(request) # todo: Check response. def add_wiki_text(self, page, text, summary='', bottom=False): """Add the specified text to the wiki page. If the page does not exist it will be created. If the bottom argument is set to True, the text will be added to the bottom of the page instead of the top. """ form = self.read_form(page) existing_text = form[self.wiki_text_field] if not bottom: new_text = text + existing_text else: new_text = existing_text + text self.set_wiki_text(page, new_text, summary=summary, form=form) def attach_file(self, page, filename, rename=''): """Add a file attachment to a wiki page. (NOT YET IMPLEMENTED)""" form = self.read_form(page, action='attach') form.add_file(open(filename, 'rb'), filename=filename) form[self.rename_field] = rename # todo: Make the button name a class variable. request = form.click(type='submit') response = self.opener.open(request) def read_text(filename): """Return text read from specified filename or use '-' for stdin.""" if filename == '-': f = sys.stdin else: f = open(filename, 'r') text = f.read() f.close() return text def main(): """Command-line front-end""" # Handle command line options and arguments. usage = ( """ %prog [options] commands: get -- get the existing page data set -- set the page data to the specified data (replace) add -- add new data to the top of a wiki page attach -- attach a file to a wiki page """ ) version = ("%prog " + __version__.split()[1] + " Copyright (C) 2006 RADLogic Pty Ltd. All rights Reserved.") parser = optparse.OptionParser(usage=usage, version=version) parser.add_option('-f', '--file', action='store', metavar='FILE', default='-', help='specify the file containing the new text' ' (Use - for stdout)') parser.add_option('-p', '--page', action='store', help='specify the wiki page name') parser.add_option('-s', '--summary', action='store', help='specify the edit summary (comment)') parser.add_option('-c', '--cookie-file', action='store', metavar='FILE', help='specify the cookie jar filename') parser.add_option('-u', '--url', action='store', help='specify the wiki url') parser.add_option('-r', '--rename', action='store', default='', help= 'specify the destination filename for an attachment') parser.add_option('-q', '--quiet', action='store_true', help='inhibit acknowledgment message') (options, args) = parser.parse_args() try: command = args[0] except IndexError: command = None if command not in ['get', 'set', 'add', 'attach']: sys.stderr.write( "Error: Must specify command ('get'|'set'|'add'|'attach')\n") sys.exit(2) # Do the work. wiki = WikiEditor(wiki_url=options.url, cookie_file=options.cookie_file) page = options.page try: if command == 'get': text = wiki.get_wiki_text(page) sys.stdout.write(text) message = "Successfully retrieved page: '%s'" % page elif command == 'set': text = read_text(options.file) wiki.set_wiki_text(page, text, summary=options.summary) message = "Successfully set text for page: '%s'" % page elif command == 'add': text = read_text(options.file) wiki.add_wiki_text(page, text, summary=options.summary) message = "Successfully added text to page: '%s'" % page elif command == 'attach': wiki.attach_file(page, options.file, options.rename) message = "Successfully attached file to page: '%s'" % page except Error, exc: sys.stderr.write('%s\n' % exc) sys.exit(1) if not options.quiet: sys.stderr.write('%s\n' % message) if __name__ == '__main__': main()