← Back to team overview

zim-wiki team mailing list archive

Re: CLI?

 

Hi,

I've been tuning and tweaking a bit to get things to sort of work.

I already had a working installation of DokuVimKi vim plugin on my
computer and decided to just copy those files to something I called
zimvimki.vim locally. Everything "doku" became "zim" both in lower and
upper case, and anything "DW" became "ZW" to distinguish zim from
dokuwiki.

Now I can open a zim notebook in ZimVimKi and edit files and pages not
containing whitespaces. I still have the old page naming rule from
dokuwiki in my plugin and need to root this out, and I can still not
save any changes I make to the pages I am able to edit.

>From my work, I see that the zimwikiclient needs to be put in the
general python path so that it is reachable by vim. That means it should
have a home in the general zim python install. Alternative is to extend
the sys.path 

I've done things easy for myself, and have that in mind when trying out
what I have done.

Put zimvimki.vim and my modified zimwikiclient.py in .vim/plugin and
zimvimki.txt in .vim/doc and give it a try. Edit .vimrc and add
let g:ZimVimKi_URL  = '/home/user/path/to/zim/Notes/'

Fire up vim, no errors should happen, and run :ZimVimKi

Remember that many features available in DokuVimKi is not yet available
or working as expected.

-- 
Svenn
"-----------------------------------------------------------------------------
" Copyright (C) 2010 Michael Klier <chi@xxxxxxxxxxx>
"
" 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 2, 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, write to the Free Software Foundation,
" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  
"
" Maintainer:   Michael Klier <chi@xxxxxxxxxxx>
" URL:          http://www.chimeric.de/projects/zimuwiki/zimvimki
"-----------------------------------------------------------------------------

if has('python') && version > 700
  command! -nargs=0 ZimVimKi exec('py zimvimki()')

  if !exists('g:ZimVimKi_INDEX_WINWIDTH')
    let g:ZimVimKi_INDEX_WINWIDTH=30
  endif

  if !exists('g:ZimVimKi_DEFAULT_SUM')
    let g:ZimVimKi_DEFAULT_SUM = '[xmlrpc zimvimki edit]'
  endif

  " Custom autocompletion function for wiki pages and media files
  " the global g:pages g:media variables are set/refreshed 
  " when the index is loaded
  fun! InsertModeComplete(findstart, base)
    if a:findstart
      " locate the start of the page/media link
      let line = getline('.')
      let start = col('.') - 1
      while start > 0 && line[start - 1] !~ '\([\|{\)'
        let start -= 1
      endwhile
      if line[start - 1] =~ "["
        let g:comp = "pages"
      elseif line[start - 1] =~ "{"
        let g:comp = "media"
      endif
      return start
    else
      " find matching pages/media
      let res = []
      if g:comp =~ "pages"
        for m in split(g:pages)
          if m =~ '^' . a:base
            call add(res, m)
          endif
        endfor
      elseif g:comp =~ "media"
        for m in split(g:media)
          if m =~ '^' . a:base
            call add(res, m)
          endif
        endfor
      endif
      return res
    endif
  endfun

  " Custom autocompletion function for namespaces and pages in
  " normal mode. Used with ZWedit
  fun! CmdModeComplete(ArgLead, CmdLine, CursorPos)
    let res = []
    for m in split(g:pages)
      if m =~ '^' . a:ArgLead
        call add(res, m)
      endif
    endfor
    return res
  endfun

  " Inserts a headline
  let g:headlines = ["======  ======", "=====  =====", "====  ====", "===  ===", "==  =="]
  fun! Headline()
    return g:headlines[g:lvl]
  endfun

  " Sets indentation/headline level
  let g:lvl = 0
  fun! SetLvl(lvl)
    let nlvl = g:lvl + a:lvl
    if nlvl >= 1 && nlvl <= 4
      let g:lvl = nlvl
    endif
    return ''
  endfun

python <<EOF
# -*- coding: utf-8 -*-

__version__ = '2010-07-01';
__author__  = 'Michael Klier <chi@xxxxxxxxxxx>'

import sys
import os
import re
import vim
import time

sys.path.append('/home/svenn/.vim/plugin')
try:
    import zimwikiclient
    has_zimwikiclient = True
except ImportError:
    print >>sys.stderr, 'ZimVimKi Error: The zimwikiclient python module is missing!'
    print os.getcwd()
    has_zimwikiclient = False



class ZimVimKi:
    """
    Provides all necessary functionality to interface between the ZimWiki
    XMLRPC API and vim.
    """


    def __init__(self):
        """
        Instantiates special buffers, setup the xmlrpc connection and loads the
        page index and displays the recent changes of the last 7 days.
        """

        if sys.version_info < (2,4):
            print >>sys.stderr, "ZimVimKi requires at least python Version 2.4 or greater!"
            return

        if not has_zimwikiclient:
            print >>sys.stderr, "zimwikiclient python module missing!"
            return

        if self.xmlrpc_init():

            vim.command("command! -complete=customlist,CmdModeComplete -nargs=1 ZWedit exec('py zimvimki.edit(<f-args>)')")
            vim.command("command! -complete=customlist,CmdModeComplete -nargs=* ZWcd exec('py zimvimki.cd(<f-args>)')")
            vim.command("command! -nargs=? ZWsave exec('py zimvimki.save(<f-args>)')")
            vim.command("command! -nargs=? ZWsearch exec('py zimvimki.search(\"page\", <f-args>)')")
            vim.command("command! -nargs=? ZWmediasearch exec('py zimvimki.search(\"media\", <f-args>)')")
            vim.command("command! -complete=customlist,CmdModeComplete -nargs=* ZWrevisions exec('py zimvimki.revisions(<f-args>)')")
            vim.command("command! -complete=customlist,CmdModeComplete -nargs=? ZWbacklinks exec('py zimvimki.backlinks(<f-args>)')")
            vim.command("command! -nargs=? ZWchanges exec('py zimvimki.changes(<f-args>)')")
            vim.command("command! -nargs=0 -bang ZWclose exec('py zimvimki.close(\"<bang>\")')")
            vim.command("command! -nargs=0 ZWdiffclose exec('py zimvimki.diff_close()')")
            vim.command("command! -complete=file -bang -nargs=1 ZWupload exec('py zimvimki.upload(<f-args>,\"<bang>\")')")
            vim.command("command! -nargs=0 ZWhelp exec('py zimvimki.help()')")
            vim.command("command! -nargs=0 -bang ZWquit exec('py zimvimki.quit(\"<bang>\")')")

            self.buffers = {}
            self.buffers['search']    = Buffer('search', 'nofile')
            self.buffers['backlinks'] = Buffer('backlinks', 'nofile')
            self.buffers['revisions'] = Buffer('revisions', 'nofile')
            self.buffers['changes']   = Buffer('changes', 'nofile')
            self.buffers['index']     = Buffer('index', 'nofile')
            self.buffers['media']     = Buffer('media', 'nofile')
            self.buffers['help']      = Buffer('help', 'nofile')

            self.needs_refresh = False
            self.diffmode      = False

            self.cur_ns = ''
            self.pages  = []

            self.default_sum   = vim.eval('g:ZimVimKi_DEFAULT_SUM')

            self.index_winwith = vim.eval('g:ZimVimKi_INDEX_WINWIDTH')
            self.index(self.cur_ns, True)

            vim.command('set laststatus=2')
            vim.command('silent! ' + self.index_winwith + 'vsplit')
            self.help()

            vim.command("command! -nargs=0 ZimVimKi echo 'ZimVimKi is already running!'")
        

    def xmlrpc_init(self):
        """
        Establishes the xmlrpc connection to the remote wiki.
        """

        try:
            self.dw_user = vim.eval('g:ZimVimKi_USER')
            self.dw_pass = vim.eval('g:ZimVimKi_PASS')
            self.dw_url  = vim.eval('g:ZimVimKi_URL')
        except vim.error, err:
            print >>sys.stderr, "Something went wrong during initialization. Please check your configuration settings."
            return False

        try:
            #self.xmlrpc = zimwikiclient.ZimWikiClient(self.dw_url, self.dw_user, self.dw_pass)
            self.xmlrpc = zimwikiclient.ZimWikiClient(self.dw_url)
            print >>sys.stdout, 'Connection to ' + vim.eval('g:ZimVimKi_URL') + ' established!'
            return True
        except vim.error, err:
            print >>sys.stderr, err
            return False


    def edit(self, wp, rev=''):
        """
        Opens a given wiki page, or a given revision of a wiki page for
        editing or switches to the correct buffer if the is open already.
        """

        wp = wp.strip()
        if wp.find(' ') != -1:
            print >>sys.stderr, "Pagenames cannot contain whitespace. Please use valid pagenames only!\nSee http://zimuwiki.org/pagename"; 
            return

        if self.diffmode:
            self.diff_close()

        self.focus(2)

        if wp.find(':') == -1:
            wp = self.cur_ns + wp

        if not self.buffers.has_key(wp):

            perm = int(self.xmlrpc.acl_check(wp))

            if perm >= 1:
                try:
                    if rev:
                        text = self.xmlrpc.page(wp, int(rev))
                    else:
                        text = self.xmlrpc.page(wp)
                except zimwikiclient.ZimWikiXMLRPCError, err:
                    print >>sys.stdout, err

                if text: 
                    if perm == 1:
                        print >>sys.stderr, "You don't have permission to edit %s. Opening readonly!" % wp
                        self.buffers[wp] = Buffer(wp, 'nowrite', True)
                        lines = text.split("\n")
                        self.buffers[wp].buf[:] = map(lambda x: x.encode('utf-8'), lines)
                        vim.command('setlocal nomodifiable')
                        vim.command('setlocal readonly')

                    if perm >= 2:
                        if not self.lock(wp):
                            return

                        print >>sys.stdout, "Opening %s for editing ..." % wp
                        self.buffers[wp] = Buffer(wp, 'acwrite', True)
                        lines = text.split("\n")
                        self.buffers[wp].page[:] = map(lambda x: x.encode('utf-8'), lines)
                        self.buffers[wp].buf[:]  = self.buffers[wp].page

                        vim.command('set nomodified')
                        vim.command('autocmd! BufWriteCmd <buffer> py zimvimki.save()')
                        vim.command('autocmd! FileWriteCmd <buffer> py zimvimki.save()')
                        vim.command('autocmd! FileAppendCmd <buffer> py zimvimki.save()')

                if not text and perm >= 4:
                    print >>sys.stdout, "Creating new page: %s" % wp
                    self.buffers[wp]   = Buffer(wp, 'acwrite', True)
                    self.needs_refresh = True

                    vim.command('set nomodified')
                    vim.command('autocmd! BufWriteCmd <buffer> py zimvimki.save()')
                    vim.command('autocmd! FileWriteCmd <buffer> py zimvimki.save()')
                    vim.command('autocmd! FileAppendCmd <buffer> py zimvimki.save()')
        
                self.buffer_setup()

            else:
                print >>sys.stderr, "You don't have permissions to read/edit/create %s" % wp
                return

        else:
            self.needs_refresh = False
            vim.command('silent! buffer! ' + self.buffers[wp].num)


    def diff(self, revline):
        """
        Opens a page and a given revision in diff mode.
        """

        data = revline.split()
        wp   = data[0]
        rev  = data[2]
        date = time.strftime('%Y-%m-%d@%Hh%mm%Ss', time.localtime(float(rev)))

        if not self.buffers.has_key(wp):
            self.edit(wp)

        if not self.buffers[wp].diff.has_key(rev):
            text = self.xmlrpc.page(wp, int(rev))
            if text:
                self.buffers[wp].diff[rev] = Buffer(wp + '_' + date, 'nofile')
                lines = text.split("\n")
                self.buffers[wp].diff[rev].page[:] = map(lambda x: x.encode('utf-8'), lines)
            else:
                print >>sys.stdout, "Error, couldn't load revision for diffing."
                return

        self.focus(2)
        vim.command('silent! buffer! ' + self.buffers[wp].num)
        vim.command('vertical diffsplit')
        self.focus(3)
        vim.command('silent! buffer! ' + self.buffers[wp].diff[rev].num)
        vim.command('setlocal modifiable')
        vim.command('abbr <buffer> close ZWdiffclose')
        vim.command('abbr <buffer> ZWclose ZWdiffclose')
        self.buffers[wp].diff[rev].buf[:]  = self.buffers[wp].diff[rev].page
        vim.command('setlocal nomodifiable')
        self.buffer_setup()
        vim.command('diffthis')
        self.focus(2)
        self.diffmode = True


    def diff_close(self):
        """
        Closes the diff window.
        """

        self.focus(3)
        vim.command('diffoff')
        vim.command('close')
        self.diffmode = False
        self.focus(2)
        vim.command('vertical resize')


    def save(self, sum='', minor=0):
        """
        Saves the current buffer. Works only if the buffer is a wiki page.
        Deleting wiki pages works like using the web interface, just delete all
        text and save.
        """
        
        wp = vim.current.buffer.name.rsplit('/', 1)[1]
        try:
            if not self.buffers[wp].iswp: 
                print >>sys.stderr, "Error: Current buffer %s is not a wiki page or not writeable!" % wp
            elif self.buffers[wp].type == 'nowrite':
                print >>sys.stderr, "Error: Current buffer %s is readonly!" % wp
            else:
                text = "\n".join(self.buffers[wp].buf)
                if text and not self.ismodified(wp):
                    print >>sys.stdout, "No unsaved changes in current buffer."
                elif not text and not wp in self.pages:
                    print >>sys.stdout, "Can't save new empty page %s." % wp
                else:
                    if not sum and text:
                        sum   = self.default_sum
                        minor = 1

                    try:
                        self.xmlrpc.put_page(wp, text, sum, minor)
                        self.buffers[wp].page[:]   = self.buffers[wp].buf
                        self.buffers[wp].need_save = False

                        if text:
                            vim.command('silent! buffer! ' + self.buffers[wp].num)
                            vim.command('set nomodified')
                            print >>sys.stdout, 'Page %s written!' % wp

                            if self.needs_refresh:
                                self.index(self.cur_ns, True)
                                self.needs_refresh = False
                                self.focus(2)
                        else:
                            print >>sys.stdout, 'Page %s removed!' % wp
                            self.close(False)
                            self.index(self.cur_ns, True)
                            self.focus(2)

                    except zimwikiclient.ZimWikiXMLRPCError, err:
                        print >>sys.stderr, 'ZimVimKi Error: %s' % err
        except KeyError, err:
            print >>sys.stderr, "Error: Current buffer %s is not handled by ZWsave!" % wp


    def upload(self, file, overwrite=False):
        """
        Uploads a file to the remote wiki.
        """

        path = os.path.realpath(file)
        fname = os.path.basename(path)

        if os.path.isfile(path):
            try:
                fh = open(path, 'r')
                data = fh.read()
                file_id = self.cur_ns + fname
                try:
                    self.xmlrpc.put_file(file_id, data, overwrite)
                    print >>sys.stdout, "Uploaded %s successfully." % fname
                    self.refresh()
                except zimwikiclient.ZimWikiXMLRPCError, err:
                    print >>sys.stderr, err
            except IOError, err:
                print >>sys.stderr, err
        else:
            print >>sys.stderr, '%s is not a file' % path


    def cd(self, query=''):
        """
        Changes into the given namespace.
        """
      
        if query and query[-1] != ':':
            query += ':'

        self.index(query)
  
    
    def index(self, query='', refresh=False):
        """
        Build the index used to navigate the remote wiki.
        """

        index = []
        pages = []
        dirs  = []

        self.focus(1)
        vim.command('set winwidth=' + self.index_winwith)
        vim.command('set winminwidth=' + self.index_winwith)

        vim.command('silent! buffer! ' + self.buffers['index'].num)
        vim.command('setlocal modifiable')
        vim.command('setlocal nonumber')
        vim.command('syn match ZimVimKi_NS /^.*\//')
        vim.command('syn match ZimVimKi_CURNS /^ns:/')

        vim.command('hi ZimVimKi_NS term=bold cterm=bold ctermfg=LightBlue gui=bold guifg=LightBlue')
        vim.command('hi ZimVimKi_CURNS term=bold cterm=bold ctermfg=Yellow gui=bold guifg=Yellow')

        if refresh:
            self.refresh()

        if query and query[-1] != ':':
            self.edit(query)
            return
        else:
            self.cur_ns = query

        if self.pages:
            for page in self.pages:
                if not query:
                    if page.find(':', 0) == -1:
                        pages.append(page)
                    else:
                        ns = page.split(':', 1)[0] + '/'
                        if ns not in dirs:
                            dirs.append(ns)
                else:
                    if re.search('^' + query, page):
                        page = page.replace(query, '')
                        if page.find(':') == -1:
                            if page not in index:
                                pages.append(page)
                        else:
                            ns = page.split(':', 1)[0] + '/'
                            if ns not in dirs:
                                dirs.append(ns)


            index.append('ns: ' + self.cur_ns)

            if query:
                index.append('.. (up a namespace)')

            index.append('')

            pages.sort()
            dirs.sort()
            index = index + dirs + pages

            self.buffers['index'].buf[:] = index

            vim.command('map <silent> <buffer> <enter> :py zimvimki.cmd("index")<CR>')
            vim.command('map <silent> <buffer> r :py zimvimki.cmd("revisions")<CR>')
            vim.command('map <silent> <buffer> b :py zimvimki.cmd("backlinks")<CR>')

            vim.command('setlocal nomodifiable')
            vim.command('2')


    def changes(self, timeframe=False):
        """
        Shows the last changes on the remote wiki.
        """
        
        if self.diffmode:
            self.diff_close()

        self.focus(2)

        vim.command('silent! buffer! ' + self.buffers['changes'].num)
        vim.command('setlocal modifiable')

        if not timeframe:
            timestamp = int(time.time()) - (60*60*24*7)
        else:
            m = re.match(r'(?P<num>\d+)(?P<type>[dw]{1})', timeframe)
            if m:
                argv = m.groupdict()

                if argv['type'] == 'd':
                    timestamp = int(time.time()) - (60*60*24*int(argv['num']))
                elif argv['type'] == 'w':
                    timestamp = int(time.time()) - (60*60*24*(int(argv['num'])*7))
                else:
                    print >>sys.stderr, "Wrong timeframe format %s." % timeframe
                    return
            else:
                print >>sys.stderr, "Wrong timeframe format %s." % timeframe
                return

        try:
            changes = self.xmlrpc.recent_changes(timestamp)
            lines = []
            if len(changes) > 0:
                maxlen = 0;
                for change in changes:
                    changelen = len(change['name'])
                    if changelen > maxlen:
                        maxlen = changelen

                for change in changes:
                    change['name'] = change['name'] + ' ' * (maxlen - len(change['name']))
                    line = "\t".join(map(lambda x: str(change[x]), ['name', 'lastModified', 'version', 'author']))
                    lines.append(line)
                
                lines.reverse()
                self.buffers['changes'].buf[:] = lines
                vim.command('syn match ZimVimKi_REV_PAGE /^\(\w\|:\)*/')
                vim.command('syn match ZimVimKi_REV_TS /\s\d*\s/')

                vim.command('hi ZimVimKi_REV_PAGE cterm=bold ctermfg=Yellow gui=bold guifg=Yellow')
                vim.command('hi ZimVimKi_REV_TS cterm=bold ctermfg=Yellow gui=bold guifg=Yellow')

                vim.command('setlocal nomodifiable')
                vim.command('map <silent> <buffer> <enter> :py zimvimki.rev_edit()<CR>')

            else:
                print >>sys.stderr, 'ZimVimKi Error: No changes'

        except zimwikiclient.ZimWikiXMLRPCError, err:
            print >>sys.stderr, err


    def revisions(self, wp='', first=0):
        """
        Display revisions for a certain page if any.
        """

        if self.diffmode:
            self.diff_close()

        if not wp or wp[-1] == ':':
            return

        try:
            self.focus(2)

            vim.command('silent! buffer! ' + self.buffers['revisions'].num)
            vim.command('setlocal modifiable')

            revs = self.xmlrpc.page_versions(wp, int(first))
            lines = []
            if len(revs) > 0:
                for rev in revs:
                    line = wp + "\t" + "\t".join(map(lambda x: str(rev[x]), ['modified', 'version', 'ip', 'type', 'user', 'sum']))
                    lines.append(line)
                
                self.buffers['revisions'].buf[:] = lines
                print >>sys.stdout, "loaded revisions for :%s" % wp
                vim.command('map <silent> <buffer> <enter> :py zimvimki.rev_edit()<CR>')

                vim.command('syn match ZimVimKi_REV_PAGE /^\(\w\|:\)*/')
                vim.command('syn match ZimVimKi_REV_TS /\s\d*\s/')
                vim.command('syn match ZimVimKi_REV_CHANGE /\s\w\{1}\s/')

                vim.command('hi ZimVimKi_REV_PAGE term=bold cterm=bold ctermfg=Yellow gui=bold guifg=Yellow')
                vim.command('hi ZimVimKi_REV_TS term=bold cterm=bold ctermfg=Yellow gui=bold guifg=Yellow')
                vim.command('hi ZimVimKi_REV_CHANGE term=bold cterm=bold ctermfg=Yellow gui=bold guifg=Yellow')

                vim.command('setlocal nomodifiable')
                vim.command('map <silent> <buffer> d :py zimvimki.cmd("diff")<CR>')

            else:
                print >>sys.stderr, 'ZimVimKi Error: No revisions found for page: %s' % wp

        except zimwikiclient.ZimWikiXMLRPCError, err:
            print >>sys.stderr, 'ZimVimKi XML-RPC Error: %s' % err


    def backlinks(self, wp=''):
        """
        Display backlinks for a certain page if any.
        """

        if self.diffmode:
            self.diff_close()

        if not wp or wp[-1] == ':':
            return

        try:
            self.focus(2)

            vim.command('silent! buffer! ' + self.buffers['backlinks'].num)
            vim.command('setlocal modifiable')

            blinks = self.xmlrpc.backlinks(wp)

            if len(blinks) > 0:
                for link in blinks:
                    self.buffers['backlinks'].buf[:] = map(str, blinks)
                vim.command('map <buffer> <enter> :py zimvimki.cmd("edit")<CR>')
            else:
                print >>sys.stderr, 'ZimVimKi Error: No backlinks found for page: %s' % wp
        
            vim.command('setlocal nomodifiable')

        except zimwikiclient.ZimWikiXMLRPCError, err:
            print >>sys.stderr, 'ZimVimKi XML-RPC Error: %s' % err


    def search(self, type='', pattern=''):
        """
        Search the page list for matching pages and display them for editing.
        """

        if self.diffmode:
            self.diff_close()

        self.focus(2)

        try:
            if type == 'page':
                vim.command('silent! buffer! ' + self.buffers['search'].num)
                vim.command('setlocal modifiable')

                if pattern:
                    p = re.compile(pattern)
                    result = filter(p.search, self.pages)
                else:
                    result = self.pages

                if len(result) > 0:
                    self.buffers['search'].buf[:] = result
                    vim.command('map <buffer> <enter> :py zimvimki.cmd("edit")<CR>')
                else:
                    print >>sys.stderr, 'ZimVimKi Error: No matching pages found!'

            elif type == 'media':
                vim.command('silent! buffer! ' + self.buffers['media'].num)
                vim.command('setlocal modifiable')

                if pattern:
                    p = re.compile(pattern)
                    result = filter(p.search, self.media)
                else:
                    result = self.media

                if len(result) > 0:
                    self.buffers['media'].buf[:] = result
                else:
                    print >>sys.stderr, 'ZimVimKi Error: No matching media files found!'

            vim.command('setlocal nomodifiable')

        except:
            pass

    
    def close(self, bang):
        """
        Closes the current buffer. Works only if the current buffer is a wiki
        page.  The buffer is also removed from the buffer stack.
        """

        if self.diffmode:
            self.diff_close()
            return

        try:
            buffer = vim.current.buffer.name.rsplit('/', 1)[1]
            if self.buffers[buffer].iswp: 
                if not bang and self.ismodified(buffer):
                    print >>sys.stderr, "Warning: %s contains unsaved changes! Use ZWclose!." % buffer
                    return

                vim.command('bp!')
                vim.command('bdel! ' + self.buffers[buffer].num)
                if self.buffers[buffer].type == 'acwrite':
                    self.unlock(buffer)
                del self.buffers[buffer]
            else:
                print >>sys.stderr, 'You cannot close special buffer "%s"!' % buffer

        except KeyError, err:
            print >>sys.stderr, 'You cannot use ZWclose on non wiki page "%s"!' % buffer


    def quit(self, bang):
        """
        Quits the current session. 
        """

        unsaved = []

        for buffer in self.buffers.keys():
            if self.buffers[buffer].iswp:
                if not self.ismodified(buffer):
                    vim.command('silent! buffer! ' + self.buffers[buffer].num)
                    self.close(False)
                elif self.ismodified(buffer) and bang:
                    vim.command('silent! buffer! ' + self.buffers[buffer].num)
                    self.close(True)
                else:
                    unsaved.append(buffer)

        if len(unsaved) == 0:
            vim.command('silent! quitall')
        else:
            print >>sys.stderr, "Some buffers contain unsaved changes. Use ZWquit! if you really want to quit."


    def help(self):
        """
        Shows the plugin help.
        """

        if self.diffmode:
            self.diff_close()

        self.focus(2)
        vim.command('silent! buffer! ' + self.buffers['help'].num)
        vim.command('silent! set buftype=help')

        # generate help tags just in case 
        vim.command('helptags ~/.vim/doc')
        vim.command('help zimvimki')
        vim.command("setlocal statusline=%{'[help]'}")


    def ismodified(self, buffer):
        """
        Checks whether the current buffer or a given buffer is modified or not.
        """

        if self.buffers[buffer].need_save:
            return True
        elif "\n".join(self.buffers[buffer].page).strip() != "\n".join(self.buffers[buffer].buf).strip():
            return True
        else:
            return False
        

    def rev_edit(self):
        """
        Special mapping for editing revisions from the revisions listing.
        """

        row, col = vim.current.window.cursor
        wp  = vim.current.buffer[row-1].split("\t")[0].strip()
        rev = vim.current.buffer[row-1].split("\t")[2].strip()
        self.edit(wp, rev)


    def focus(self, winnr):
        """
        Convenience function to switch the current window focus.
        """

        if int(vim.eval('winnr()')) != winnr:
            vim.command(str(winnr) + 'wincmd w')

    
    def refresh(self):
        """
        Refreshes the page index by retrieving a fresh list of all pages on the
        remote server and updating the completion dictionary.
        """

        self.pages = []
        self.media = []

        try:
            print >>sys.stdout, "Refreshing page index!"
            data = self.xmlrpc.all_pages()

            if data:
                for page in data:
                    page = page['id'].encode('utf-8')
                    ns   = page.rsplit(':', 1)[0] + ':'
                    self.pages.append(page)
                    if not ns in self.pages:
                        self.pages.append(ns)
                        self.media.append(ns)
                    

            self.pages.sort()
            vim.command('let g:pages = "' + " ".join(self.pages) + '"')

            print >>sys.stdout, "Refreshing media index!"
            data = self.xmlrpc.list_files(':', True)

            if data:
                for media in data:
                    self.media.append(media['id'].encode('utf-8'))

            self.media.sort()
            vim.command('let g:media = "' + " ".join(self.media) + '"')

        except zimwikiclient.ZimWikiXMLRPCError, err:
            print >>sys.stderr, "Failed to fetch page list. Please check your configuration\n%s" % err


    def lock(self, wp):
        """
        Tries to obtain a lock given wiki page.
        """

        locks = {}
        locks['lock']   = [ wp ]
        locks['unlock'] = []

        result = self.set_locks(locks)

        if locks['lock'] == result['locked']:
            print >>sys.stdout, "Locked page %s for editing." % wp
            return True
        else:
            print >>sys.stderr, 'The page "%s" appears to be locked for editing. You have to wait until the lock expires.' % wp
            return False

    
    def unlock(self, wp):
        """
        Tries to unlock a given wiki page.
        """

        locks = {}
        locks['lock']   = []
        locks['unlock'] = [ wp ]

        result = self.set_locks(locks)
        if locks['unlock'] == result['unlocked']:
            return True
        else:
            return False


    def set_locks(self, locks):
        """
        Locks unlocks a given set of pages.
        """

        try:
            return self.xmlrpc.set_locks(locks)
        except zimwikiclient.ZimWikiXMLRPCError, err:
            print >>sys.stderr, err


    def id_lookup(self):
        """
        When editing pages, hiting enter while over a wiki link will open the
        page. This functions tries to guess the correct wiki page.
        """
        line = vim.current.line
        row, col = vim.current.window.cursor

        # get namespace from current page
        wp = vim.current.buffer.name.rsplit('/', 1)[1]
        ns = wp.rsplit(':', 1)[0]
        if ns == wp:
            ns = ''

        # look for link syntax on the left and right from the current curser position
        reL = re.compile('\[{2}[^]]*$') # opening link syntax
        reR = re.compile('^[^\[]*]{2}') # closing link syntax

        L = reL.search(line[:col])
        R = reR.search(line[col:])

        # if both matched we probably have a link
        if L and R:

            # sanitize match remove anchors and everything after '|'
            id = (L.group() + R.group()).strip('[]').split('|')[0].split('#')[0]

            # check if it's not and external/interwiki/share link
            if id.find('>') == -1 and id.find('://') == -1 and id.find('\\') == -1:

                # check if useshlash is used
                if id.find('/'):
                    id = id.replace('/', ':')

                # this is _almost_ a rip off of ZimWikis resolve_id() function
                if id[0] == '.':
                    re_sanitize = re.compile('(\.(?=[^:\.]))')
                    id = re_sanitize.sub('.:', id)
                    id = ns + ':' + id
                    path = id.split(':')

                    result = []
                    for dir in path:
                        if dir == '..':
                            try:
                                if result[-1] == '..':
                                    result.append('..')
                                elif not result.pop():
                                    result.append('..')
                            except IndexError:
                                pass
                        elif dir and dir != '.' and not len(dir.split('.')) > 2:
                            result.append(dir)

                    id = ':'.join(result)

                elif ns and id[0] != ':' and id.find(':', 0) == -1:
                    id = ns + ':' + id

                # we're done, open the page for editing
                print >>sys.stdout, id
                self.edit(id)


    def cmd(self, cmd):
        """
        Callback function to provides various functionality for the page index
        (like open namespaces or triggering edit showing backlinks etc).
        """

        row, col = vim.current.window.cursor
        line = vim.current.buffer[row-1]

        # first line triggers nothing in index buffer
        if row == 1 and line.find('ns: ') != -1: 
            return

        if line.find('..') == -1:
            if line.find('/') == -1:
                if not line:
                    print >>sys.stdout, "meh"
                else:
                    line = self.cur_ns + line
            else:
                line = self.cur_ns + line.replace('/', ':')
        else:
            line = self.cur_ns.rsplit(':', 2)[0] + ':'
            if line == ":" or line == self.cur_ns:
                line = ''

        callback = getattr(self, cmd)
        callback(line)


    def buffer_enter(self, wp):
        """
        Loads the buffer on enter.
        """

        self.buffers[wp].buf[:] = self.buffers[wp].page
        vim.command('setlocal nomodified')
        self.buffer_setup()


    def buffer_leave(self, wp):
        if "\n".join(self.buffers[wp].buf).strip() != "\n".join(self.buffers[wp].page).strip():
            self.buffers[wp].page[:] = self.buffers[wp].buf
            self.buffers[wp].need_save = True


    def buffer_setup(self):
        """
        Setup edit environment.
        """

        vim.command('setlocal textwidth=0')
        vim.command('setlocal wrap')
        vim.command('setlocal linebreak')
        vim.command('setlocal syntax=zimuwiki')
        vim.command('setlocal filetype=zimuwiki')
        vim.command('setlocal tabstop=2')
        vim.command('setlocal expandtab')
        vim.command('setlocal shiftwidth=2')
        vim.command('setlocal encoding=utf-8')
        vim.command('setlocal completefunc=InsertModeComplete')
        vim.command('setlocal omnifunc=InsertModeComplete')
        vim.command('map <buffer> <silent> e :py zimvimki.id_lookup()<CR>')
        vim.command('imap <buffer> <silent> <C-D><C-B> ****<ESC>1hi')
        vim.command('imap <buffer> <silent> <C-D><C-I> ////<ESC>1hi')
        vim.command('imap <buffer> <silent> <C-D><C-U> ____<ESC>1hi')
        vim.command('imap <buffer> <silent> <C-D><C-L> [[]]<ESC>1hi')
        vim.command('imap <buffer> <silent> <C-D><C-M> {{}}<ESC>1hi')
        vim.command('imap <buffer> <silent> <C-D><C-K> <code><CR><CR></code><ESC>ki')
        vim.command('imap <buffer> <silent> <C-D><C-F> <file><CR><CR></file><ESC>ki')
        vim.command('imap <buffer> <silent> <expr> <C-D><C-H> Headline()')
        vim.command('imap <buffer> <silent> <expr> <C-D><C-P> SetLvl(+1)')
        vim.command('imap <buffer> <silent> <expr> <C-D><C-D> SetLvl(-1)')



class Buffer:
    """
    Representates a vim buffer object. Used to manage keep track of all opened
    pages and to handle the zimvimki special buffers.

        self.num    = buffer number (starts at 1)
        self.id     = buffer id (starts at 0)
        self.buf    = vim buffer object
        self.name   = buffer name   
        self.iswp   = True if buffer represents a wiki page
    """

    id     = None
    num    = None
    name   = None
    buf    = None

    def __init__(self, name, type, iswp=False):
        """
        Instanziates a new buffer.
        """
        vim.command('badd ' + name)
        self.num  = vim.eval('bufnr("' + name + '")')
        self.id   = int(self.num) - 1
        self.buf  = vim.buffers[self.id]
        self.name = name
        self.iswp = iswp
        self.type = type
        self.page = []
        vim.command('silent! buffer! ' + self.num)
        vim.command('setlocal buftype=' + type)
        vim.command('abbr <silent> close ZWclose')
        vim.command('abbr <silent> close! ZWclose!')
        vim.command('abbr <silent> quit ZWquit')
        vim.command('abbr <silent> quit! ZWquit!')
        vim.command('abbr <silent> q ZWquit')
        vim.command('abbr <silent> q! ZWquit!')
        vim.command('abbr <silent> qa ZWquit')
        vim.command('abbr <silent> qa! ZWquit!')
        

        if type == 'nofile':
            vim.command('setlocal nobuflisted')
            vim.command('setlocal nomodifiable')
            vim.command('setlocal noswapfile')
            vim.command("setlocal statusline=%{'[" + self.name + "]'}")

        if type == 'acwrite':
            self.diff = {}
            self.need_save = False
            vim.command('autocmd! BufEnter <buffer> py zimvimki.buffer_enter("' + self.name + '")')
            vim.command('autocmd! BufLeave <buffer> py zimvimki.buffer_leave("' + self.name + '")')
            vim.command("setlocal statusline=%{'[wp]\ " + self.name + "'}\ %r\ [%c,%l][%p]")

        if type == 'nowrite':
            self.diff = {}
            vim.command("setlocal statusline=%{'[wp]\ " + self.name + "'}\ %r\ [%c,%l][%p%%]")



def zimvimki():
    """
    Creates the global zimvimki instance.
    """
    global zimvimki
    zimvimki = ZimVimKi()


EOF
  else
    command! -nargs=0 ZimVimKi echoerr "ZimVimKi disabled! Python support missing or vim version not supported."
endif
" vim:ts=4:sw=4:et:
*zimvimki*   Plugin for editing DokuWiki pages via XML-RPC for Vim > 6.0
                          For Vim > 7.0

                        By Michael Klier
                        <chi@xxxxxxxxxxx>
                                                            *zimvimki-plugin*

------------------------------------------------------------------------------
OVERVIEW                                                  *zimvimki-overview*

|zimvimki-installation|        Installation instructions

|zimvimki-configuration|       How to configure zimvimki

|zimvimki-commands|            Brief description of the available commands

|zimvimki-buffer-mappings|     Description of the mappings available in the 
                                special buffers

|zimvimki-bugs|                Bug reports are always welcome ;-)

------------------------------------------------------------------------------
UPDATES                                                    *zimvimki-updates*

The official releases can be found at:

    git: http://github.com/chimeric/zimvimki

------------------------------------------------------------------------------
INSTALLATION                                          *zimvimki-installation*

Just unpack the tarball in your ~/.vim/ directory. Then edit your .vimrc and
add your settings (see CONFIGURATION). You'll also need to install the python
xmlrpclib module and the dokuwikixmlrpc module in order to use this plugin!
For details about installing those please refer to the documentation specific
for your OS.

------------------------------------------------------------------------------
CONFIGURATION                                        *zimvimki-configuration*

The following variables have to be present in your ~/.vimrc in order to get
DokuVimKi working.

g:DokuVimKi_USER            The username used to login into the remote wiki.

g:DokuVimKi_PASS            The password used to login into the remote wiki.

g:DokuVimKi_URL             The URL of the remote wiki (no trailing slash!).

g:DokuVimKi_INDEX_WINWIDTH  The width of the index window (default 30).

g:DokuVimKi_DEFAULT_SUM     Default summary used if no summary is given on
                            save. Defaults to [xmlrpc zimvimki edit]. 
                            If you save pages using :w this is used as well
                            and will result in a minor edit.

------------------------------------------------------------------------------
COMMANDS                                                  *zimvimki-commands*

:DWedit <page>              Opens the given wiki page in the edit buffer. If
                            the page does not exist on the remote wiki it will
                            be created once you issue :DWSave. You can use 
                            <TAB> to autocomplete pages. If the page doesn't
                            contain any ':' the page will be created in the 
                            current namespace the index is showing.

:DWcd <namespace>           Change into a given namespace.

:DWsave <summary>           Save the wiki page in the edit buffer to the
                            remote wiki. If no edit summary is given it 
                            will be saved as minor edit. You can also use :w
                            but it will not allow to specify a edit summary.

:DWbackLinks <page>         Loads a list of pages which link back to the given
                            wiki page into the edit buffer. If you are already
                            editing a page you can use the command without
                            supplying a wiki page and it will load a list of
                            backlinks to the page loaded in the edit buffer.
                            You can use <TAB> to autocomplete pages.

:DWrevisions <page> N       Lists the available revisions of a wiki page. You 
                            can use an offset (integer) to view earlier 
                            revisions. The number of shown revisions depends 
                            on the $conf['recent'] setting of the remote wiki. 
                            You can use <TAB> to autocomplete pages.

:DWsearch <pattern>         Searches for matching pages. You can use regular
                            expressions!

:DWmediasearch <pattern>    Searches for matching media files. You can use
                            regular expressions.

:DWchanges <timeframe>      Lists the recent changes of the remote wiki.
                            You can specify a timeframe:

                                Nd      show changes of the last N days
                                Nw      show changes of the last N weeks

:DWclose                    Closes the current edit buffer (removing edit 
:DWclose!                   locks on the remote wiki etc.) - if the buffer
                            contains changes which haven't been synced back
                            to the remote wiki this command will issue a
                            warning.  You can also use :close which is setup
                            as abbreviation.

:DWdiffclose                Closes diff mode

:DWupload <file>            Allows to upload a file in the current namespace.
:DWupload! <file>

:DWquit                     Quits the current session and quits vim. This will
:DWquit!                    fail if there are unsaved changes.

:DWhelp                     Displays the DokuVimKi help.

------------------------------------------------------------------------------
EDIT-MAPPINGS                                        *zimvimki-edit-mappings*

DokuVimKi mimics the functionality of DokuWikis edit toolbar to make it easier
to insert/edit wiki syntax. Unless otherwise noted all those mappings work
only in insert mode.

    <C-D><C-B>  inserts bold syntax

    <C-D><C-I>  inserts italic syntax

    <C-D><C-U>  inserts underline syntax

    <C-D><C-L>  inserts link syntax

    <C-D><C-M>  inserts media syntax

    <C-D><C-K>  inserts code block syntax

    <C-D><C-F>  inserts file block syntax

    <C-D><C-H>  inserts a headline

    <C-I><C-P>  increases the current headline level by one

    <C-I><C-D>  decreases the current headline level by one

    e           opens a link under the current cursor position for editing
                (this mappings only works in normal mode)

------------------------------------------------------------------------------
BUFFER-MAPPINGS                                    *zimvimki-buffer-mappings*

DokuVimKi comes with some specials mappings you can use in the special buffers
like the index or the recent changes view.


INDEX

    <ENTER>     Opens the page for editing, or lists the contents of the
                namespace under the cursor.

    b           Shows a list of the page linking back to the page under the
                cursor.

    r           Shows the revisions of page under the cursor.


REVISIONS

    <ENTER>     Opens the page revision for editing.

    d           Opens the diff view for the page and the revision under the
                cursor.


CHANGES

    <ENTER>     Opens the page revision under the cursor for editing.


SEARCH

    <ENTER>     Opens the page under the cursor for editing.


BACKLINKS

    <ENTER>     Opens the page under the cursor for editing.

------------------------------------------------------------------------------
BUGS                                                          *zimvimki-bugs*

Please use the official bug tracker for feature requests of bug reports:
>
    http://github.com/chimeric/zimvimki/issus
    

If the bug is reproducible, then it will be of great help if a short
description of the events leading to the bug are also given ;-).

vim:tw=78:et:ts=4:ft=help:norl:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# Zim client with API that is compatible with dokuwikixmlrpc.py

# Copyright 2012 Jaap Karssenberg <jaap.karssenberg@xxxxxxxxx>
# Copyright 2008 by Michael Klier <chi@xxxxxxxxxxx>

# This code is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 2 only, as published by
# the Free Software Foundation.
#
# This code 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 version 3 for more
# details (a copy is included in the LICENSE file that accompanied this code).
#
# You should have received a copy of the GNU General Public License version 3
# along with dokuwikixmlrpc. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
# for a copy of the GPLv2 License.


# TODO
# allow to overwrite useragent?

import StringIO

import zim
from zim.errors import Error


READ = 1
WRITE = 2
CREATE = 4

class ZimWikiError(Exception):
    """ZimWikiError base class."""
    pass


class ZimWikiXMLRPCError(ZimWikiError):
    """Triggered on XMLRPC faults."""

    def __init__(self, obj):
        """Initalize and call anchestor __init__()."""
        ZimWikiError.__init__(self)
        if isinstance(obj, xmlrpclib.Fault):
            self.page_id = obj.faultCode
            self.message = obj.faultString
        else:
            self.page_id = 0
            self.message = obj

    def __str__(self):
        """Format returned error message."""
        return '<%s %s: \'%s\'>' % (self.__class__.__name__, 
                                    self.page_id, 
                                    self.message)
class ZimWikiClient(object):

	# Incompatibilities:
	# - In dokywiki version revisions are of type 'int', we use strings for identifiers
	#   background is that not all backends have integer revision IDs
	# - In links() we only list internal links

	# TODO revisions not yet supported - need to hooks versioncontrol plugin

	def __init__(self, notebook):
		self.interface = zim.NotebookInterface(notebook)
		self.notebook = self.interface.notebook
		self.notebook.index.update()

	def rpc_version_supported(self):
		return zim.__version__

	def _get_page(self, name):
		path = self.notebook.resolve_path(name)
		page = self.notebook.get_page(path)
		assert hasattr(page, 'source') and page.source
		return page

	def page(self, page_id, revision=None):
		"""Return the raw Wiki text of a given Wiki page.

		Optionally return the information of a Wiki page version (see
		page_versions())
		"""
		page = self._get_page(page_id)
		if revision:
			assert False, 'TODO, versioncontrol'
		else:
			return page.source.read()

	def page_versions(self, page_id, offset=0):
		"""Return a list of available versions for a Wiki page."""
		assert False, 'TODO, versioncontrol'
		# we get list of
		# (rev, data, user, msg)
		#
		# needs to return list of 
		# {
		#	'user': # user that made the change
		#	'ip':	# ip adres of the user
		#	'type': #type of change
		#	'sum': # summary of change - first line of msg ?
		#	'modified': # UTC tiemstamp
		#	'version': # page version timestamp
		# }

	def page_info(self, page_id, revision=None):
		"""Return information about a given Wiki page.

		Optionally return the information of a Wiki page version (see
		page_versions())
		
		"""
		page = self._get_page(page_id)
		if revision:
			assert False, 'TODO, versioncontrol'
		else:
			info = {
				'name': page.name, # basename ?
				'lastModified': 0, # FIXME UTC timestamp
				'author': '',
				'version': '',
			}
		return info

	def page_html(self, page_id, revision=None):
		"""Return the (X)HTML body of a Wiki page.

		Optionally return the (X)HTML body of a given Wiki page version (see
		page_versions())
		
		"""
		page = self._get_page(page_id)
		if revision:
			assert False, 'TODO, versioncontrol'
		else:
			exporter = zim.exporter.Exporter(self.notebook, 'html', template=None)
			fh = StringIO.StringIO()
			exporter.export_page_to_fh(fh, page)
			return fh.getvalue()

	def put_page(self, page_id, text, summary='', minor=False):
		"""Send a Wiki page to the remote Wiki.

		Keyword arguments:
		page_id -- valpage_id Wiki page page_id
		text -- raw Wiki text (UTF-8 encoded)
		sum -- summary
		minor -- mark as minor edit
		
		"""
		page = self._get_page(page_id)
		page.parse(text, 'wiki')
		self.notebook.store_page(page)

	def pagelist(self, namespace):
		"""Lists all pages within a given namespace."""
		path = self.notebook.resolve_path(namespace)
		return self._page_list(self.notebook.get_pagelist(path))

	def all_pages(self):
		"""List all pages of the remote Wiki."""
		return self._page_list(self.notebook.walk())

	def _page_list(self, pages):
		pageinfo = []
		for page in pages:
			pageinfo.append({
				'id': page.name, # id of the page
				'perms': self._permissions(page), # integer denoting the permissions on the page
				'size': 0, # FIXME size in bytes
				'lastModified': 0, # FIXME dateTime object of last modification date
			})
		return pageinfo

	def backlinks(self, page_id):
		"""Return a list of pages that link back to a Wiki page."""
		# FIXME Looks like this should be list of names, although expected
		# same as for links() -- double check
		from zim.index import LINK_DIR_BACKWARD
		links = []
		path = self.notebook.resolve_path(page_id)
		for link in self.notebook.index.list_links(path, LINK_DIR_BACKWARD):
			links.append(link.source.name)
		return links

	def links(self, page_id):
		"""Return a list of links contained in a Wiki page."""
		links = []
		path = self.notebook.resolve_path(page_id)
		for link in self.notebook.index.list_links(path):
			links.append({
				'type': 'local', # local/extern
				'page': link.href.name, # the wiki page (or the complete URL if extern)
				'href': None, # the complete URL - FIXME ?
			})
		return links


	def recent_changes(self, timestamp):
		"""Return the recent changes since a given timestampe (UTC)."""
		# return list of
		# {
		# 	'name' # page id
		# 	'lastModified' # modification date as UTC timestamp
		# 	'author' # author
		# 	'version' # page version as timestamp
		# }
		return [] # TODO

	def acl_check(self, page_id):
		"""Return the permissions of a Wiki page."""
		# Returns integer combination of READ | WRITE | CREATE
		page = self._get_page(page_id)
		return self._permissions(page)

	def _permissions(self, page):
		if self.notebook.readonly or page.readonly:
			return READ
		else:
			return READ | WRITE | CREATE

	def get_file(self, file_id):
		"""Download a file from a remote Wiki."""
		pass # TODO

	def put_file(self, file_id, data, overwrite = False):
		"""Upload a file to a remote Wiki."""
		pass # TODO

	def delete_file(self, file_id):
		"""Delete a file from a remote wiki."""
		pass # TODO

	def file_info(self, file_id):
		"""Return information about a given file."""
		pass # TODO

	def list_files(self, namespace, recursive = False, pattern = None):
		"""List files in a Wiki namespace."""
		pass # TODO

	def set_locks(self, locks):
		"""
		Lock/unlock a set of files. Locks must be a dictionary which contains
		list of ids to lock/unlock:

			locks =  { 'lock' : [], 'unlock' : [] }
		"""
		# Zim does not support this mechanism, so fake it to 
		# always agree to what is asked
		return {
			'locked': locks['lock'],
			'unlocked': locks['unlock'],
			'lockfail': [],
			'unlockfail': [],
		}


class Callback(object):
	"""Callback class used by the option parser.

	Instantiates a new ZimWikiClient. It retrieves and outputs the data for
	the specified callback. The callback is specified in the option parser. The
	option destination has to match a ZimWikiClient method.
	
	"""
	def __init__(self, option, opt_str, value, parser):
		"""Initalize callback object."""
		if parser.values.wiki:
			try:
				self.zimwiki = ZimWikiClient(parser.values.wiki)
			except Error, error:
				parser.error(error)

			self._parser = parser
			(data, output_format) = self.dispatch(option.dest)

			if data:
				if output_format == 'plain':
					print data

				elif output_format == 'list':
					for item in data:
						print item

				elif output_format == 'dict':
					if type(data) == type([]):
						for item in data:
							for key in item.keys():
								print '%s: %s' % (key, item[key])
							print "\n"
					else:
						for key in data.keys():
							print '%s: %s' % (key, data[key])


		else:
			parser.print_usage()


	def _get_page_id(self):
		"""Check if the additional arguments contain a Wiki page id."""
		try:
			return self._parser.rargs.pop()
		except IndexError:
			self._parser.error('You have to specify a Wiki page.')


	def dispatch(self, option):
		"""Dispatch the provided callback."""

		callback = self.zimwiki.__getattribute__(option)

		if option == 'page' or option == 'page_html':
			page_id = self._get_page_id()

			timestamp = self._parser.values.timestamp

			if not timestamp:
				return (callback(page_id), 'plain')
			else:
				return (callback(page_id, timestamp), 'plain')

		elif option == 'backlinks':
			page_id = self._get_page_id()
			return (callback(page_id), 'list')

		elif option == 'page_info' or option == 'page_versions' or option == 'links':
			page_id = self._get_page_id()
			return (callback(page_id), 'dict')

		elif option == 'all_pages':
			return (callback(), 'list')

		elif option == 'recent_changes':
			from time import time
			timestamp = self._parser.values.timestamp
			if not timestamp:
				timestamp = int(time())
			return (callback(timestamp), 'dict')


def main():
	"""Main function. Invoked when called as script.

	The module can also be used as simple command line client to query a remote
	Wiki. It provides all methods supported by ZimWikis XML-RPC interface. The
	retrieved data is slightly formatted when output.
	
	"""
	from optparse import OptionParser
	
	parser = OptionParser(version = '%prog ' + zim.__version__)

	#parser.set_usage('%prog -u <username> -w <wikiurl> -p <passwd> [options] [wiki:page]')
	parser.set_usage('%prog -w <wikiurl> [options] [wiki:page]')

	#parser.add_option('-u', '--user', 
	#		dest = 'user', 
	#		help = 'Username to use when authenticating at the remote Wiki.')

	parser.add_option('-w', '--wiki',
			dest = 'wiki',
			help = 'The remote wiki.')

	#parser.add_option('-p', '--passwd',
	#		dest = 'passwd',
	#		help = 'The user password.')

	parser.add_option('--raw',
			dest = 'page',
			action = 'callback',
			callback = Callback,
			help = 'Return the raw Wiki text of a Wiki page.')

	parser.add_option('--html',
			dest = 'page_html',
			action = 'callback',
			callback = Callback,
			help = 'Return the HTML body of a Wiki page.')

	parser.add_option('--info',
			dest = 'page_info',
			action = 'callback',
			callback = Callback,
			help = 'Return some information about a Wiki page.')

	#parser.add_option('--changes',
	#		dest = 'recent_changes',
	#		action = 'callback',
	#		callback = Callback,
	#		help = 'List recent changes of the Wiki since timestamp.')

	parser.add_option('--revisions',
			dest = 'page_versions',
			action = 'callback',
			callback = Callback,
			help = 'Liste page revisions since timestamp.')

	parser.add_option('--backlinks',
			dest = 'backlinks',
			action = 'callback',
			callback = Callback,
			help = 'Return a list of pages that link back to a Wiki page.')

	parser.add_option('--allpages',
			dest = 'all_pages',
			action = 'callback',
			callback = Callback,
			help = 'List all pages in the remote Wiki.')

	parser.add_option('--links',
			dest = 'links',
			action = 'callback',
			callback = Callback,
			help = 'Return a list of links contained in a Wiki page.')

	parser.add_option('--time',
			dest = 'timestamp',
			type = 'int',
			help = 'Revision timestamp.')
	
	#parser.add_option('--http-basic-auth',
	#		dest = 'http_basic_auth',
	#		action = 'store_true',
	#		help = 'Use HTTP Basic Authentication.',
	#		default=False)

	parser.parse_args()


if __name__ == '__main__':
	main()



References