# Copyright (C) 2006 Samuel Abels, http://debain.org # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2, as # published by the Free Software Foundation. # # 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 import os import re import sys import difflib import Warehouse from cgi import escape from genshi import Markup from WikiMarkup import Wiki2Html from WikiPage import WikiPage class Wiki(object): def __init__(self, db, **kwargs): self.db = db self.warehouse = Warehouse.DB(db) self.__class__.differ = difflib.Differ() self.__class__.matcher = difflib.SequenceMatcher() self.wiki2html = Wiki2Html() if kwargs.has_key('directory'): self.warehouse.set_directory(kwargs.get('directory')) if kwargs.has_key('wiki_word_handler'): self.wiki2html.set_wiki_word_handler(kwargs.get('wiki_word_handler')) if kwargs.has_key('wiki_url_handler'): self.wiki2html.set_url_handler(kwargs.get('wiki_url_handler')) def set_directory(self, directory): self.warehouse.set_directory(directory) def set_wiki_word_handler(self, handler): self.wiki2html.set_wiki_word_handler(handler) def set_url_handler(self, handler): self.wiki2html.set_url_handler(handler) def __markup_diff_line(self, line_from, line_to): #print "IN_FROM:", line_from #print "IN_TO: ", line_to self.__class__.matcher.set_seq1(line_from) self.__class__.matcher.set_seq2(line_to) line_from_new = '' line_to_new = '' for opcode in self.__class__.matcher.get_opcodes(): if opcode[0] == 'equal': line_from_new += escape(line_from[opcode[1]:opcode[2]]) line_to_new += escape(line_to[opcode[3]:opcode[4]]) elif opcode[0] == 'replace': line_from_new += '' line_to_new += '' line_from_new += escape(line_from[opcode[1]:opcode[2]]) line_to_new += escape(line_to[opcode[3]:opcode[4]]) line_from_new += '' line_to_new += '' elif opcode[0] == 'delete': line_from_new += '' line_from_new += escape(line_from[opcode[1]:opcode[2]]) line_from_new += '' elif opcode[0] == 'insert': line_to_new += '' line_to_new += escape(line_to[opcode[3]:opcode[4]]) line_to_new += '' else: print opcode[0] assert False # Unknown opcode in diff. return (line_from_new, line_to_new) def __markup_diff_block(self, deleted, inserted): #print "__markup_diff_block():", len(deleted), len(inserted) if len(deleted) == 1 and len(inserted) == 1: result = self.__markup_diff_line(deleted[0], inserted[0]) return ([Markup(unicode(result[0], 'utf-8'))], [Markup(unicode(result[1], 'utf-8'))]) deleted_new = [] inserted_new = [] for line in deleted: deleted_line = '' deleted_line += escape(line) deleted_line += '' deleted_new.append(Markup(unicode(deleted_line, 'utf-8'))) for line in inserted: inserted_line = '' inserted_line += escape(line) inserted_line += '' inserted_new.append(Markup(unicode(inserted_line, 'utf-8'))) return (deleted_new, inserted_new) def __markup_diff(self, content_from, content_to): # Start by comparing line wise. lines = self.__class__.differ.compare(content_from.splitlines(1), content_to.splitlines(1)) # Now compare the lines in more details. diff = [] deleted = [] inserted = [] line_number = 0 last_operator = None for line in lines: operator = line[0] if line[-1] == '\n': line = line[2:-1] else: line = line[2:] # That's junk we don't care about. if operator == '?': continue # Count the line number in the "from" content. elif operator == ' ' or operator == '-': line_number += 1 # Collect all lines of the current block of '+' and '-' lines. if operator == '+': inserted.append(line) # Lines that are equal need not be examined any further. elif operator == ' ': line = Markup(unicode(line, 'utf-8')) diff.append((line_number, 'equal', [line], [line])) # When a block is complete, pass it to the diff function about. if (len(deleted) + len(inserted) > 0 and (operator == '+' or operator == ' ' or (operator == '-' and last_operator == '-'))): (block1, block2) = self.__markup_diff_block(deleted, inserted) # Determine the type of change that this block makes. if len(block1) == 0: type = 'insert' elif len(block2) == 0: type = 'delete' elif len(block1) > 0 and len(block2) > 0: type = 'replace' # Store both blocks in the diff. diff.append((line_number, type, block1, block2)) deleted = [] inserted = [] last_operator = operator if operator == '-': deleted.append(line) # We still need to clear the buffer containing the current block. (block1, block2) = self.__markup_diff_block(deleted, inserted) if len(block1) == 0: type = 'insert' elif len(block2) == 0: type = 'delete' elif len(block1) > 0 and len(block2) > 0: type = 'replace' if len(block1) != 0 or len(block2) != 0: diff.append((line_number, type, block1, block2)) return diff def __get_page_item(self, alias, revision = None): if revision is not None: item = self.warehouse.get_file_from_alias(alias, int(revision)) if item is not None: return item return self.warehouse.get_file_from_alias(alias) def has_page(self, alias): """ @rtype: bool @return: True if a page with the given alias exists, False otherwise. """ return self.warehouse.get_file_from_alias(alias) is not None def get_page(self, alias, revision = None): """ Returns the page with the given alias and the given revision. If a revision was not given, the most recent version is returned. @rtype: WikiPage @return: The page with the given alias, ot None if it does not exist. """ item = self.__get_page_item(alias, revision) assert item.get_filename() is not None assert len(item.get_filename()) > 0 return WikiPage(self, None, item = item) def save_page(self, page): """ Saves the given page in the database. @type page: WikiPage @param page: The page to be saved. @rtype: bool @return: True on success, False otherwise. """ return self.warehouse.add_file(page.item) def get_revision_list(self, alias, offset = 0, limit = 0): """ Returns the list of all revisions of the given page in the database. The result may be restricted by the given offset and limit. @type alias: string @param alias: The alias of the page. @type offset: int @param offset: The offset of the first result. @type limit: int @param limit: The maximum number of results. @rtype: list[WikiPage] @return: A list of wiki pages. """ result = [] for item in self.warehouse.get_file_list_from_alias(alias, True, offset, limit): result.append(WikiPage(self, '', item = item)) return result def get_diff(self, alias, revision1, revision2): item1 = self.warehouse.get_file_from_alias(alias, int(revision1)) item2 = self.warehouse.get_file_from_alias(alias, int(revision2)) assert item1 is not None assert item2 is not None content_from = item1.get_content() content_to = item2.get_content() return self.__markup_diff(content_from, content_to)