ZWiki/ 0040775 0000764 0000764 00000000000 10000524236 011155 5 ustar vahur vahur ZWiki/Extensions/ 0040775 0000764 0000764 00000000000 10000524010 013302 5 ustar vahur vahur ZWiki/Extensions/zwiki_mailin.py 0100775 0000764 0000764 00000010770 07644512335 016402 0 ustar vahur vahur # zwiki mailin - post an email message to a wiki page # # This method expects at least one argument, msg, an RFC822 email # message. This will be formatted as a wiki comment and appended to # the appropriate zwiki page. The page is selected as follows: # # - if called in a page context, use that page # - if called in a folder context, look for # 1. a zwiki page name appearing before @ in a recipient hdr (To,Cc,etc) # 2. or, the first page name appearing in the subject # 3. or, the defaultpage argument # 4. or, defaultpage hardcoded below (FrontPage) # # 1 & 2 may be turned off if you don't want them # # This is intended to receive email messages from a procmail recipe # like the following: # # # forward messages to a zwiki web # # assumes an entry for site in ~/.netrc # # processing only messages whose recipient contains 'wiki': # * ^TO.*wiki # * !^FROM_MAILER # :0 i # | curl -n -F 'msg=<-' http://site/wiki_folder/mailin # # todo: # in procmail, forward message+error to a human if post fails # support checking of sender against a member list # support size limits from types import * import re from DocumentTemplate.DT_Util import html_quote try: from Products.ZWiki.ZWikiRegexes import wikiname1,wikiname2,bracketedexpr except ImportError: # pre-0.9.4 ZWiki : from Products.ZWiki.ZWikiPage import wikiname1,wikiname2,bracketedexpr #wikinameexp = r'(%s|%s|%s)' % (wikiname1,wikiname2,bracketedexpr) wikinameexp = r'(%s|%s)' % (wikiname1,wikiname2) defaultpage = 'FrontPage' def mailin(self, msg, pagenameexp=wikinameexp, defaultpage=defaultpage, separator='\n\n', checkrecipient=1, checksubject=1): # extract essential fields # could use rfc822 ? # mo=rfc822.Message(mfile) # # hd={} # hd['to']=[] # for header in (mo.getaddrlist('to'), # mo.getaddrlist('cc'), # mo.getaddrlist('bcc')): # if not header: continue # for name, addr in header: # hd['to'].append(addr) # # hd['from']=mo.getaddr('from')[1] # hd['subject']=mo.getheader('subject') or '' author = re.search(r'(?m)^From: (.*)',msg) if author: author = author.group(1) else: author = '' date = re.search(r'(?m)^Date: (.*)',msg) if date: date = date.group(1) else: date = '' text = re.search(r'(?s)\n\n(.*)',msg) if text: text = text.group(1) else: text = '' # & do just a little html-prettification for web display. # If this comment gets sent out again to subscribers, zwiki # will try to strip this again. #comment = "
- structured text :: examples
- structured text '' quoted code
- html tags.
* append permission now works
* tweaked the anti-javascript hack
* tweaked the anti-decapitation kludge
* added plainhtmldtml mode (DTML + HTML, nothing else)
ZWiki 0.9.3 2001-05-02
* creating/editing/deleting pages with eg spaces in the name has
been broken for a while, it seems - made some fixes in this area
* made the edit conflict message more helpful
* relaxed edit conflict checking: if your username & ip address
match the last editor's, the timestamp will be ignored. In other
words, you can no longer have an edit conflict with yourself.
This means eg you can backtrack in your browser, edit and click
Change again. This change may disable conflict checking amongst
anonymous users coming through a proxy.
* renamed the "username" property to "last_editor", added
"last_editor_ip", made these read-only in the mgmt. interface.
Existing zwiki pages are upgraded when viewed. &dtml-username; is
still supported for backwards compatibility, but deprecated;
use &dtml-last_editor_or_ip; by preference.
* stx workaround: trailing blank lines no longer cause unwanted
headings
* stx workaround: initial word plus period no longer becomes a
numeric bullet
* stx workaround: whitespace after :: no longer prevents example
formatting
* stx headings on first lines were broken - fixed
* fix for a 0.9.1 bug: with hierarchy display enabled, creating a
new page from a top-level page gave "typeerror"
* gopher: urls are now recognized
* renamed {wiki,page}_path to {wiki,page}_url in 0.9.2
ZWiki 0.9.2 2001-04-26
* added a bunch of wiki_{page,base}_url variants for testing purposes
* about: urls are now recognized
* fixed a potential DeleteMe error message caused by incorrect parents
* allow standard_wiki_page to be defined as a folder property
* added plainhtml render mode - HTML only, no wiki-linking
* test suite updates, documentation
ZWiki 0.9.1 2001-04-24
* allow non-wiki paths in []
* image file names in [] are auto-inlined
* use uploaded image size to help "add file/image" decide whether to inline
* reset parents when they have become outdated/confused
* folder attribute "standard_page_type" overrides type of all new pages
* display new page name when creating a page
* record username when creating a page
* renamed wiki_page_url(), wiki_base_url() to page_path(),
wiki_path(). The old names, used throughout existing dtml code,
are supported but deprecated
* made remotewikilinks more careful to avoid trailing punctuation
* allow []-named pages in remote wiki links
* disable structured text's [] footnote linking to avoid conflicts
ZWiki 0.9.0 2001-04-23
* added append method
* simple email notification (PageSubscribers)
* made wikilinks absolute for greater robustness
* header/footer layout tweaks
* made last editor's authenticated username override username cookie
* proxy role tweak
* added file/image upload
* added more detailed permissions
* refactored edit()
* changed/reverted wiki_{page,base}_url as per Christian Scholz
for virtual hosting
* allowed https: urls
* allowed + and $ in remote wiki links
ZWiki 0.8.1 2001-01-04
* added (experimental) page deletion
* record IP address when username cookie is blank
* make disabled javascript tags visible
* added more unit tests
ZWiki 0.8.0rc1 2000-12-14
* log last editor's IP address if there is no username cookie
* wrapped a bunch of long lines
* unit tests - added support for ZUnit and DocTest, and a few
initial tests. http://zwiki.org/zwikidir/Makefile.sample contains
some useful recipes for automated testing.
ZWikiWebs.zexp: incorporated latest zwiki.org tweaks, namely:
* removed a spurious menu from the add zwiki web form
* added UserOptions to the default BookMarks and removed it from
the page footer
* bookmarks, quote, search box and hierarchy may all be turned on
or off in UserOptions
* the default home page is now a user option
* user options & search box tab ordering fixed
* always show the editform even for write-protected pages, with
appropriate header/footer color
* page history is now accessible
* simplified RecentChanges
ZWiki 0.7.1 2000-11-03
* fixed broken line-ending handling and non-rendering of initial
lines containing ":" in dtml modes
* return to the wiki page after clicking the reparent button
* added warning of incompatibility with old dtml methods to readme
ZWiki 0.7.0 2000-10-31 "Halloween!"
* cookie-based user options, including edit form size, timezone,
bookmarks and wikiwikiweb-style username (help from Phil Armstrong)
* ZWiki is now zope 2.2-compatible (Garth Kidd) and -requiring,
and benefits from the 2.2 security model. Executable dtml pages
now run with those permissions that are common to both the
page-viewing user and the wiki web's owner. Set the folder's
owner to limit the permissions of executable pages.
* incorporated & updated Chris Withers' product for creating wiki webs
* added streamlined "hierarchal2" wiki style & other layout tweaks
* wikiwikiweb-style late page creation
* added simple javascript-disabling code
* made paths work with virtual hosting again (Evan Simpson)
* fixed unreliable ! line protection in structuredtext modes
* fixed unreliable remote wiki links in classicwiki mode
* more permissive remote wiki link regexps (Geoff Gardiner)
* "with this" dtml kludge no longer needed
* added built-in defaults for all dtml methods
* simpler, more consistent urls & api
* code refactoring/cleanups, other misc. bugfixes
ZWiki 0.6.1 2000-05-03
* documented permission configuration in zwiki_examples/index_html
ZWiki 0.6 2000-05-02 "skins-structure-permissions"
* wikinames must now start on a word boundary
* added # and = to url regexp
* try allowing numbers in wikinames
* added utility methods wiki_base_url & wiki_page_url
* added KenManheimer's hierarchy & navigation code
* added JimFulton's edit conflict safety belts for http & ftp
* added jim's permission & validation patch
* add & change zwiki page permissions are now functional
* reorganized & expanded example content
* deemphasised DTML-enabled content where not needed -
changed pages to structuredtext where possible,
restricted permissions on the rest, changed the default
page type to structuredtext
ZWiki 0.5 2000-03-27 "simple"
* simplified the default wiki content & page layout
* disabled catalog support for the moment
ZWiki 0.4 2000-02-14
* new sample wiki, defaults to structuredtextdtml mode only
* bare urls are automatically hyperlinked, others should be left alone
* extensible markup modes - you can add your own render methods
* code cleanups
* tweaked markup modes for usability (see new TextFormattingRules)
* made validation of newly-edited DTML more accurate
* ZWikiPages are catalog-aware (mostly.. still some issues ?)
* added RemoteWikiLinks
* bracketed numbers are no longer wikilinks, so StructuredText's
footnotes can work
ZWiki 0.3 1999-11-14
* sample wiki: included latest DTML features from ZWikiWeb -
SearchPage, JumpTo, AnnoyingQuote, separate edit page, etc etc.
* multiple markup formats -
structured text
classic wiki (TresSeaver)
HTML/DTML
plain text
* better international character handling (AlexandreRatti)
* LFCR line-terminations are converted to LF
* tweaked editform layout
* source cleanup
* renamed default_wiki_page to standard_wiki_page for consistency
* ! at the beginning of a line protects it from wiki translation
ZWiki 0.2 1999-11-08
* now checks for valid DTML & reports errors
* no longer requires "view management screens" permission
* tweaked wikilink regexp
* new icon
* misc fixes
* standard_wiki_header, standard_wiki_footer & default_wiki_page
have built-in defaults; define as dtml methods to override
* sample wiki: AllPages is a zwikipage; pagenames with spaces
are listed properly
* sample wiki: added a RecentChanges page
* sample wiki: simplified
ZWiki 0.1 1999-11-05
* initial development release
ZWiki/Defaults.py 0100775 0000764 0000764 00000020043 07644512335 013316 0 ustar vahur vahur ######################################################################
# constants and built-in defaults for ZWiki
DEFAULT_PAGE_TYPE = 'classicwiki'
DISABLE_JAVASCRIPT = 1
LARGE_FILE_SIZE = 1024*1024
PERMPREFIX = '' # 'ZWiki:'
# built-in DTML defaults - to override these, define any of the
# following as DTML methods (python scripts, properties ?):
#
# standard_wiki_header
# standard_wiki_footer
# standard_wiki_page
# editform
# backlinks
# titleprefix
import DocumentTemplate
from DocumentTemplate import HTMLFile
#from Globals import HTMLFile
# ? the ramifications of when and where this dtml gets executed are
# not yet clear ...
# would like to move these to separate files.. how do I define
# these in such a way that "
|
''') default_wiki_footer = DocumentTemplate.HTML(source_string=\ '''
Last edited |
Edit this page |
|
''') default_backlinks = DocumentTemplate.HTML(source_string=\ '''
|
Mail subscription |
%s""" % (self.page_url(), (revA>=19 and '<< previous edit') # we only see 20 revs right now or '<< previous edit' %( self.page_url(), revA+1, revA), (revB==0 and 'next edit >>' % (self.page_url())) or 'next edit >>' % ( self.page_url(), revB, max(revB-1,0)), self.page_url(), t) def oldDiff(self,revA=1,revB=0): """ display a zope-page-history-style html-formatted diff between two revisions of this page, numbering back from the latest. """ revA, revB = int(revA), int(revB) a=split(self.lasttext(versionsBack=revA),'\n') b=split(self.lasttext(versionsBack=revB),'\n') cruncher=ndiff.SequenceMatcher(isjunk=split, a=a, b=b) r = ['
\n%s\n
\n%s\n
\n%s\n%s\n
\n%s\n%s\n
') #unpreexp = re.compile(r'') #citedexp = re.compile(r'^\s*>') ## Match group 1 is citation prefix, group 2 is leading whitespace: #cite_prefixexp = re.compile('([\s>]*>)?([\s]*)') ZWiki/SubscriberList.py 0100775 0000764 0000764 00000020440 07644512335 014507 0 ustar vahur vahur ###################################################################### # import string, re ########################################################################### # CLASS SubscriberList ########################################################################### #class ZMISubscriberList(SubscriberList, Persistent, Item, Implicit): #? class SubscriberList: """ RESPONSIBILITIES - manage a list of email subscribers to a ZWikiPage - expose the ZWikiPage's subscribers as a ZMI property - manage a list of email subscribers to a ZWikiPage's parent folder COLLABORATORS ZWikiPage ? NB for convenience a ZWikiPage has this mixed-in; for a Folder we could install a manageable subclass of this as a subobject. See if this makes sense. Bah too many complications. For now, overload the methods below to also manage a property in the parent folder. """ ###################################################################### # VARIABLES ###################################################################### subscribers = '' #subscribers = [] # NB this will hide the folder's subscribers property _properties=( {'id':'subscribers', 'type': 'string', 'mode': 'w'}, #{'id':'subscribers', 'type': 'lines', 'mode': 'w'}, ) ###################################################################### # METHOD CATEGORY: private ###################################################################### def _resetSubscribers(self, parent=0): """ Clear this page's subscriber list. With parent arg, manage the parent folder's subscriber list instead. """ if parent: self.aq_parent.subscribers = '' else: self.subscribers = '' def _setSubscribers(self, subscriberlist, parent=0): """ Set this page's email subscriber list. Currently a string of comma-separated email addresses. Change to a list. With parent arg, manage the parent folder's subscriber list instead. """ if parent: self.aq_parent.subscribers = subscriberlist else: self.subscribers = subscriberlist ###################################################################### # METHOD CATEGORY: page subscription ###################################################################### def subscriberList(self, parent=0): """ Return this page's subscriber list (as a string). With parent arg, manage the parent folder's subscriber list instead. """ # look for a line beginning "PageSubscribers:" within the text #match = re.search(pagesubscribers, self.read()) #if match: # return html_unquote(match.group(1)) if parent: if hasattr(self.aq_parent,'subscribers') and \ self.aq_parent.subscribers: return self.aq_parent.subscribers else: return '' else: # look for a "subscribers" property if hasattr(self,'subscribers') and self.subscribers: return self.subscribers else: return '' def subscriberCount(self, parent=0): """ Return the number of email addresses currently subscribed to this page With parent arg, manage the parent folder's subscriber list instead. """ s = self.subscriberList(parent) if s: return string.count(self.subscriberList(parent),',') + 1 else: return 0 def isSubscriber(self,email,parent=0): """ Is email currently subscribed to this page ? With parent arg, manage the parent folder's subscriber list instead. >>> zc.TestPage._setSubscribers('a, b') >>> zc.TestPage.isSubscriber('a') 1 >>> zc.TestPage.isSubscriber('c') 0 >>> zc.TestPage.isSubscriber('') 0 >>> zc.TestPage.isSubscriber(' ') 0 """ if (email and re.match(r'(?i).*\b%s\b.*' % (email),self.subscriberList(parent))): return 1 else: return 0 def subscribe(self, email, REQUEST=None, parent=0): """ Add email as a subscriber to this page. With parent arg, manage the parent folder's subscriber list instead. >>> zc.TestPage._setSubscribers('spamandeggs') >>> zc.TestPage.subscriberList() 'spamandeggs' >>> zc.TestPage._resetSubscribers() >>> zc.TestPage.subscriberList() '' >>> zc.TestPage.subscriberCount() 0 >>> zc.TestPage.subscribe('1@test.com') >>> zc.TestPage.subscriberList() '1@test.com' >>> zc.TestPage.subscriberCount() 1 >>> zc.TestPage.isSubscriber('1@test.com') 1 >>> zc.TestPage.isSubscriber('2@test.com') 0 >>> zc.TestPage.subscribe('2@test.com') >>> zc.TestPage.subscriberList() '1@test.com, 2@test.com' >>> zc.TestPage.subscriberCount() 2 >>> zc.TestPage.isSubscriber('2@test.com') 1 >>> zc.TestPage.subscribe('1@test.com') >>> zc.TestPage.subscriberList() '1@test.com, 2@test.com' >>> zc.TestPage.unsubscribe('1@test.com') >>> zc.TestPage.subscriberList() '2@test.com' >>> zc.TestPage.unsubscribe('1@test.com') >>> zc.TestPage.subscriberList() '2@test.com' >>> zc.TestPage.unsubscribe('2@TesT.cOm') >>> zc.TestPage.subscriberList() '' """ # do some email address validation if email: s = self.subscriberList(parent) if not s: self._setSubscribers(email,parent) else: if not self.isSubscriber(email,parent): self._setSubscribers(s + ', ' + email,parent) # redirect browser if needed if REQUEST: REQUEST.RESPONSE.redirect( REQUEST['URL1']+'/subscribeform?email='+email) def unsubscribe(self, email, REQUEST=None, parent=0): """ Remove an email address from this page's mail subscriber list. With parent arg, manage the parent folder's subscriber list instead. """ if self.isSubscriber(email,parent): s = self.subscriberList(parent) s = re.sub(r'(?i)%s(, )?' % (email), r'', s) if s[:2] == ', ': s = s[2:] if s[-2:] == ', ': s = s[:-2] self._setSubscribers(s,parent) # redirect browser if needed if REQUEST: REQUEST.RESPONSE.redirect( REQUEST['URL1']+'/subscribeform?email='+email) ###################################################################### # METHOD CATEGORY: parent folder subscription ###################################################################### def wikiSubscriberList(self): """ whole-wiki version of subscriberList """ return self.subscriberList(parent=1) def wikiSubscriberCount(self): """ whole-wiki version of subscriberCount """ return self.subscriberCount(parent=1) def isWikiSubscriber(self,email): """ whole-wiki version of isSubscriber """ return self.isSubscriber(email,parent=1) def wikiSubscribe(self, email, REQUEST=None): """ whole-wiki version of subscribe """ return self.subscribe(email,REQUEST,parent=1) def wikiUnsubscribe(self, email, REQUEST=None): """ whole-wiki version of unsubscribe """ return self.unsubscribe(email,REQUEST,parent=1) ###################################################################### # METHOD CATEGORY: misc ###################################################################### def allSubscribers(self): """ This page's subscribers + whole wiki subscribers. >>> zc.TestPage._resetSubscribers() >>> zc.TestPage._resetSubscribers(parent=1) >>> zc.TestPage.allSubscribers() '' """ s = self.subscriberList() + ', ' + self.wikiSubscriberList() if s[:2] == ', ': s = s[2:] if s[-2:] == ', ': s = s[:-2] return s ZWiki/TextFormatter.py 0100775 0000764 0000764 00000013012 07644512335 014355 0 ustar vahur vahur #=================================================================== #!/usr/bin/env python # File: TextFormatter.py # Author: Hamish B Lawson # Date: 19/11/1999 # from http://www.faqts.com/knowledge_base/view.phtml/aid/4517 """ Here is TextFormatter, a simple module for formatting text into columns of specified widths. It does multiline wrapping and supports left, center and right alignment. SKWM made filling & padding optional, tweaked some edge cases """ import string left = 0 center = centre = 1 right = 2 class TextFormatter: """ Formats text into columns. Constructor takes a list of dictionaries that each specify the properties for a column. Dictionary entries can be: width the width within which the text will be wrapped alignment left|center|right margin amount of space to prefix in front of column The compose() method takes a list of strings and returns a formatted string consisting of each string wrapped within its respective column. Example: formatter = TextFormatter( ( {'width': 10}, {'width': 12, 'margin': 4}, {'width': 20, 'margin': 8, 'alignment': right}, ) ) print formatter.compose( ( "A rather short paragraph", "Here is a paragraph containing a veryveryverylongwordindeed.", "And now for something on the right-hand side.", ) ) gives: A rather Here is a And now for short paragraph something on the paragraph containing a right-hand side. veryveryvery longwordinde ed. """ class Column: def __init__(self, width=75, alignment=left, margin=0, fill=1, pad=1): self.width = width self.alignment = alignment self.margin = margin self.fill = fill self.pad = pad self.lines = [] def align(self, line): if self.alignment == center: return string.center(line, self.width) elif self.alignment == right: return string.rjust(line, self.width) else: if self.pad: return string.ljust(line, self.width) else: return line def wrap(self, text): self.lines = [] words = [] if self.fill: # SKWM for word in string.split(text): if word <= self.width: # don't understand this words.append(word) else: for i in range(0, len(word), self.width): words.append(word[i:i+self.width]) else: for line in string.split(text,'\n'): for word in string.split(line): for i in range(0, len(word), self.width): words.append(word[i:i+self.width]) words.append('\n') if words[-1] == '\n': words.pop() if words: current = words.pop(0) for word in words: increment = 1 + len(word) if word == '\n': self.lines.append(self.align(current)) current = '' elif len(current) + increment > self.width: self.lines.append(self.align(current)) current = word else: if current: current = current + ' ' + word else: current = word if current: self.lines.append(self.align(current)) def getline(self, index): if index < len(self.lines): return ' '*self.margin + self.lines[index] else: if self.pad: return ' ' * (self.margin + self.width) else: return '' def numlines(self): return len(self.lines) def __init__(self, colspeclist): self.columns = [] for colspec in colspeclist: self.columns.append(apply(TextFormatter.Column, (), colspec)) def compose(self, textlist): numlines = 0 textlist = list(textlist) if len(textlist) != len(self.columns): raise IndexError, "Number of text items does not match columns" for text, column in map(None, textlist, self.columns): column.wrap(text) numlines = max(numlines, column.numlines()) complines = [''] * numlines for ln in range(numlines): for column in self.columns: complines[ln] = complines[ln] + column.getline(ln) #return string.join(complines, '\n') + '\n' return string.join(complines, '\n') def test(): formatter = TextFormatter( ( {'width': 10}, {'width': 12, 'margin': 4}, {'width': 20, 'margin': 8, 'alignment': right}, ) ) print formatter.compose( ( "A rather short paragraph", "Here is a paragraph containing a veryveryverylongwordindeed.", "And now for something on the right-hand side.", ) ) if __name__ == '__main__': test() ZWiki/ZWikiPage.py 0100775 0000764 0000764 00000246703 07776512355 013426 0 ustar vahur vahur ###################################################################### # main ZWiki source file """ ZWiki README This zope product allows you to build wiki webs in zope. To install: - unpack ZWiki-x.x.x.tgz in your zope Products directory - restart zope - in the ZMI, add a ZWiki Web. Please understand the security issues noted on that form before publishing your wiki. For documentation and assistance, please see http://zwiki.org, http://zwiki.org/Discussion, etc. All feedback, bugreports and help appreciated. (c) 1999,2000,2001 Simon Michael
\n" + html_quote(self.read()) + "\n\n" #return t footer = apply(self._renderHeaderOrFooter, ('footer',REQUEST,RESPONSE),kw) return header + t + footer # "just testing" def render_issuedtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): header = apply(self._renderHeaderOrFooter, ('header',REQUEST,RESPONSE),kw) # render an "issue" page # this is an ordinary zwiki page with some extra attributes # (properties, attributes or embedded fields ?) # (if properties, does that imply a zwikipage superclass ?) # (set via in-page dtml, dtml method/pythonscript, product method ?) # which we render as form fields # embedded form for changing issue properties issueform = DocumentTemplate.HTML (""" """) #dtml-evaluate both form and main page # not sure about this first one t = apply(issueform.__call__,(self, REQUEST),kw) + \ apply(DTMLDocument.__call__,(self, client, REQUEST, RESPONSE), kw) #wikilinks t = re.sub(protected_line, self._protect_line, t) t = self._structured_text(t) t = re.sub(interwikilink, thunk_substituter(self._interwikilink_replace, t, 1), t) t = re.sub(wikilink, thunk_substituter(self._wikilink_replace, t, 1), t) footer = apply(self._renderHeaderOrFooter, ('footer',REQUEST,RESPONSE),kw) return header + t + footer def changeProperties(self, REQUEST=None, **kw): """ identical to manage_changeProperties, except redirects back to the current page """ if REQUEST is None: props={} else: props=REQUEST if kw: for name, value in kw.items(): props[name]=value propdict=self.propdict() for name, value in props.items(): if self.hasProperty(name): if not 'w' in propdict[name].get('mode', 'wd'): raise 'BadRequest', '%s cannot be changed' % name self._updateProperty(name, value) self.reindex_object() # don't forget this if REQUEST: REQUEST.RESPONSE.redirect(self.page_url()) def wikilink(self, text): """ utility method for wiki-linking an arbitrary text snippet >>> zc.TestPage.wikilink('http://a.b.c/d') 'http://a.b.c/d' >>> zc.TestPage.wikilink('mailto://a@b.c') 'mailto://a@b.c' >>> zc.TestPage.wikilink('nolink') 'nolink' >>> zc.TestPage.wikilink('TestPage')[-23:] '/TestPage">TestPage' >>> zc.TestPage.wikilink('TestPage1234')[-43:] '/TestPage/editform?page=TestPage1234">?' >>> zc.TestPage.wikilink('!TestPage') 'TestPage' >>> zc.TestPage.wikilink('[nolink]')[-37:] '/TestPage/editform?page=nolink">?' a problem with escaping remote wiki links was reported >>> zc.TestPage.edit(text='RemoteWikiURL: URL/') >>> zc.TestPage.wikilink('TestPage:REMOTEPAGE') # should give: 'TestPage:REMOTEPAGE' >>> zc.TestPage.wikilink('!TestPage:REMOTEPAGE') # should give: 'TestPage:REMOTEPAGE' """ # ' for font-lock # could modify the render_ methods to do this instead t = re.sub(protected_line, self._protect_line, text) t = re.sub(interwikilink, thunk_substituter(self._interwikilink_replace, t, 1), t) t = re.sub(wikilink, thunk_substituter(self._wikilink_replace, t, 1), t) return t def htmlquote(self, text): # expose this darn thing for dtml programmers once and for all! return html_quote(text) def _wikilink_replace(self, match, allowed=0, state=None, text=''): # tasty spaghetti regexps! better suggestions welcome ? """ Replace an occurrence of the wikilink regexp or one of the special [] constructs with a suitable hyperlink To be used as a re.sub repl function *and* get a proper value for literal context, 'allowed', etc, enclose this function with the value using 'thunk_substituter'. """ # In a literal? if state is not None: if within_literal(match.start(1), match.end(1)-1, state, text): return match.group(1) # matches beginning with ! should be left alone if re.match('^!',match.group(0)): return match.group(1) m = match.group(1) # if it's a bracketed expression, if re.match(bracketedexpr,m): # strip the enclosing []'s m = re.sub(bracketedexpr, r'\1', m) # extract a (non-url) path if there is one pathmatch = re.match(r'(([^/]*/)+)([^/]+)',m) if pathmatch: path,id = pathmatch.group(1), pathmatch.group(3) else: path,id = '',m # if it looks like an image link, inline it if guess_content_type(id)[0][0:5] == 'image': return '' % (path,id) # or if there was a path assume it's to some non-wiki # object and skip the usual existence checking for # simplicity. Could also attempt to navigate the path in # zodb to learn more about the destination if path: return '%s' % (path,id,id) # otherwise fall through to normal link processing # if it's an ordinary url, link to it if re.match(url,m): # except, if preceded by " or = it should probably be left alone if re.match('^["=]',m): # " return m else: return '%s' % (m, m) # a wikiname - if a page (or something) of this name exists, link to it elif hasattr(self.aq_parent.aq_self, m): #return '%s' % (quote(m), m) # XXX make all wiki links absolute. this is a bit drastic!! # It's to make eg BookMarks more robust in editform & backlinks, # hopefully it won't break anything. I have no better ideas # right now so it's Do The Simplest Thing time return '%s' % (self.wiki_url(),quote(m), m) # otherwise, provide a "?" creation link else: # XXX see above #return '%s?' % (m, quote(self.id()), quote(m)) return '%s?' % (m, self.wiki_url(), quote(self.id()), quote(m)) def _interwikilink_replace(self, match, allowed=0, state=None, text=''): """ Replace an occurrence of interwikilink with a suitable hyperlink. To be used as a re.sub repl function *and* get a proper value for literal context, 'allowed', etc, enclose this function with the value using 'thunk_substituter'. """ # matches beginning with ! should be left alone #if re.match('^!',match.group(0)): return match.group(1) # NB this is a bit naughty, but: since we know this text will likely # be scanned with _wikilink_replace right after this pass, leave # the ! in place for it to find. Otherwise the localname will # get wiki-linked. if re.match('^!',match.group(0)): return match.group(0) localname = match.group('local') remotename = match.group('remote') # named groups come in handy here! # NB localname could be [bracketed] if re.match(bracketedexpr,localname): localname = re.sub(bracketedexpr, r'\1', localname) # look for a RemoteWikiURL definition if hasattr(self.aq_parent, localname): localpage = getattr(self.aq_parent,localname) # local page found - search for "RemoteWikiUrl: url" m = re.search(remotewikiurl, str(localpage)) if m is not None: remoteurl = html_unquote(m.group(1)) # NB: pages are # stored html-quoted # XXX eh ? they are ? # something's not # right # somewhere.. # I have lost my # grip on this # whole quoting # issue. # we have a valid inter-wiki link link = '%s:%s' % \ (remoteurl, remotename, localname, remotename) # protect it from any later wiki-izing passes return re.sub(wikilink, r'!\1', link) # otherwise, leave alone return match.group(0) def _protect_line(self, match): """protect an entire line from _wikilink_replace, by protecting all its wikilink occurrences """ return re.sub(wikilink, r'!\1', match.group(1)) def _structured_text(self, text): """ Render some text into html according to the structured text rules, massaging and tweaking slightly to work around stx issues and for our nefarious zwiki purposes. """ if ZOPEMAJORVERSION <= 2 and ZOPEMINORVERSION <= 3: # zope 2.2 - 2.3 # workarounds for classic structured text # strip trailing blank lines to avoid the famous last line # heading bug text = re.sub(r'(?m)\n[\n\s]*$', r'\n', text) # "A paragraph that begins with a sequence of sequences, where # each sequence is a sequence of digits or a sequence of # letters followed by a period, is treated as an ordered list # element." # This stx rule converts an initial single word plus period # into a numeric bullet. Don't understand why it exists. # Quickest workaround: prepend a harmless marker like # preserve indentation # now why did I need (?m) here ? seems backwards # hmm \s matches our line breaks # caution - our workaround will be visible eg in :: examples, # so we remove it after stx has done it's thing, below text = re.sub(r'(?m)^([ \t]*)([A-z]\w*\.)', r'\1\2', text) # "Sub-paragraphs of a paragraph that ends in the word example # or the word examples, or :: is treated as example code and # is output as is" # This fails if there is whitespace after the ::, so remove it # NB contrary to the docs "the word example or examples" is # not used, rightly I think text = re.sub(r'(?m)::[ \t]+$', r'::', text) # skip stx's [] footnote linking (by not calling # html_with_references) text = str(StructuredText.HTML(text,level=3)) # DTSTTCPW, DTSTTCPW text = re.sub(r'(?i)(<|<)!--STXKLUDGE--(>|>)', r'', text) else: # zope 2.4 - # interesting new workarounds for structured text NG # "Sub-paragraphs of a paragraph that ends in the word example # or the word examples, or :: is treated as example code and # is output as is" # This fails if there is whitespace after the ::, so remove it # still need this one text = re.sub(r'(?m)::[ \t]+$', r'::', text) # stxng is doing it's [] footnote linking but I can't # figure out how # so, another silly hack text = re.sub(r'(?m)\[',r'[',text) text = str(StructuredText.HTML(text,level=3)) text = re.sub(r'(<|<)!--STXKLUDGE--(>|>)', r'', text) return text ###################################################################### # METHOD CATEGORY: page editing & creation ###################################################################### def create(self, page, text=None, type=None, title='', REQUEST=None): """ Create a new wiki page and redirect there if appropriate; can upload a file at the same time. Normally edit() will call this for you. >>> # create a blank page >>> >>> zc.TestPage.create('TestPage1',text='') >>> assert hasattr(zc,'TestPage1') >>> zc.TestPage1.text() '' >>> >>> # create a classicwiki page with some text >>> >>> zc.TestPage.create('TestPage2',text='test page data',type='classicwiki') >>> assert hasattr(zc,'TestPage1') >>> zc.TestPage2.src() 'test page data' >>> zc.TestPage2.page_type 'classicwiki' >>> #>>> # add a file while creating a page #>>> # this capability broke - fix if ever needed #>>> # and the rest of this is tested in edit() I think #>>> #>>> import OFS.Image #>>> file = OFS.Image.Pdata('test file data') #>>> file.filename = 'test_file' #>>> zc.REQUEST.file = file #>>> zc.TestPage.create('TestPage3',text='test page data',REQUEST=zc.REQUEST) #>>> # the new file should exist #>>> assert hasattr(zc,'test_file') #>>> # with the right data #>>> str(zc['test_file']) #'test file data' #>>> # and a link should have been added to the new wiki page #>>> zc.TestPage3.src() #'test page data\\n\\ntest_file\\n' #>>> #>>> # ditto, with an image #>>> #>>> file.filename = 'test_image.gif' #>>> zc.REQUEST.file = file #>>> zc.TestPage1.create('TestPage4',text='test page data',REQUEST=zc.REQUEST) #>>> assert hasattr(zc,'test_image.gif') #>>> zc['test_image.gif'].content_type #'image/gif' #>>> zc.TestPage4.src() #'test page data\\n\\n\\n' #>>> #>>> # images should not be inlined if dontinline is set #>>> #>>> file.filename = 'test_image.JPG' #>>> zc.REQUEST.dontinline = 1 #>>> zc.TestPage1.create('TestPage5',text='',REQUEST=zc.REQUEST) #>>> zc.TestPage5.src() #'\\n\\ntest_image.JPG\\n' #>>> # cleanup #>>> zc.REQUEST.dontinline = None """ # do we have permission ? checkPermission = getSecurityManager().checkPermission if not checkPermission(Permissions.Add,self.aq_parent): raise 'Unauthorized', ( 'You are not authorized to add ZWiki Pages here.') # make a new (blank) page object, p = ZWikiPage(source_string='', __name__=page) p.title = title p.parents = [self.id()] p._set_last_editor(REQUEST) # inherit type from the previous (parent) page if not specified # or overridden via folder attribute, if hasattr(self.aq_parent,'standard_page_type'): p.page_type = self.aq_parent.standard_page_type elif type: p.page_type = type else: p.page_type = self.page_type # & situate it in the parent folder # NB newid might not be the same as page newid = self.aq_parent._setObject(page,p) # To help manage executable content, make sure the new page # acquires it's owner from the parent folder. p._deleteOwnershipAfterAdd() # choose initial page text and set it as edit() would, with # cleanups and dtml validation if text is not None: t = text elif hasattr(self.aq_parent, 'standard_wiki_page'): if callable(self.aq_parent.standard_wiki_page): t = self.aq_parent.standard_wiki_page(self,REQUEST) else: t = DocumentTemplate.HTML(self.aq_parent.standard_wiki_page) else: t = default_wiki_page(self,REQUEST) p._set_text(t,REQUEST) # if a file was submitted as well, handle that # we pass in aq_parent because the new-born p doesn't # have a proper acquisition context ? p._handleFileUpload(REQUEST, parent=self.aq_parent) # update catalog if present # DTMLDocumentExt indexed us after _setObject, but do it again # now that we have set the text # NB need the full wrapped object for this getattr(self.aq_parent,newid).index_object() # old stuff: #if hasattr(self, self.default_catalog): # url = join(split(self.url(),'/')[:-1],'/') + '/' + newid # !!?? # getattr(self, self.default_catalog).catalog_object(p, url) # redirect browser if needed if REQUEST is not None: u=REQUEST['URL2'] REQUEST.RESPONSE.redirect(u + '/' + quote(newid)) def append(self, text='', separator='\n\n', REQUEST=None): """ Appends some text to an existing zwiki page by calling edit; may result in mail notifications to subscribers. """ oldtext = self.read() text = str(text) if text: # cc comment to subscribers # temporary - # edit() is able to send mail notifications now, but in # this case I prefer to handle it myself so as to just # mail the comment text rather than a diff. # Done first because edit may not return. #try: # self.sendMailToSubscribers(text,REQUEST) #except: # pass # usability hack: scroll to bottom after adding a comment if REQUEST: REQUEST['URL1'] = REQUEST['URL1'] + '#bottom' #text = oldtext + separator #if comment_heading: # text = text + \ # '
"+REQUEST['L_ic_zwiki_nobody_modified']+"
\n" else: tulemus=tulemus+""+REQUEST['L_ic_name']+" | \n"+\ REQUEST['L_ic_date']+" | \n
---|---|
"+x+" | \n\n" tulemus=tulemus+self.get_printable_day(self.aq_self.kylastused[x], REQUEST)+" | \n
# Try editing the page again. # ''') return 1 else: return 0 ###################################################################### # METHOD CATEGORY: ftp/PUT/webdav handling ###################################################################### def manage_FTPget(self): "Get source for FTP download" return "Wiki-Safetybelt: %s\n\n%s" % ( self.timeStamp(), self.read()) def PUT(self, REQUEST, RESPONSE): # should this do what _set_text() does ? """Handle HTTP PUT requests.""" self.dav__init(REQUEST, RESPONSE) body=REQUEST.get('BODY', '') self._validateProxy(REQUEST) m=re.match(r'Wiki-Safetybelt: ([0-9]+[.][0-9]+)[\n][\n]?', body) if m: body = body[m.span()[1]:] if self.checkEditConflict(m.group(1), REQUEST): RESPONSE.setStatus(404) #XXX what should this be return RESPONSE self.munge(body) self._set_last_editor(REQUEST) RESPONSE.setStatus(204) return RESPONSE ###################################################################### # METHOD CATEGORY: wiki-mail integration ###################################################################### # see also SubscriberList mixin def sendMailToSubscribers(self, text, REQUEST, subjectSuffix=''): """ If a mailhost and mail_from property have been configured and there are subscribers to this page, email text to them """ recipients = self.allSubscribers() if recipients: self.sendMailTo(recipients,text,REQUEST,subjectSuffix) def sendMailTo(self, recipients, text, REQUEST, subjectSuffix=''): """ If a mailhost and mail_from property have been configured, email text to recipients """ # ugLy temp hack # strip out the message heading typically prepended on *Discussion pages mailouttext = re.sub(r'(?s)(
Someone else has saved this page while you were editing. To resolve the conflict, do this:
To discard your recent edit and start again, click OK. """, action=self.page_url()+'/editform') def backlinks(self, REQUEST=None): """ Display a default backlinks page. May be overridden by a DTML method of the same name. """ if hasattr(self.aq_parent, 'backlinks'): return self.aq_parent.backlinks(self,REQUEST) else: return default_backlinks(self,REQUEST) def subscribeform(self, REQUEST=None): """ Display a default mail subscription form. May be overridden by a DTML method of the same name. """ if hasattr(self.aq_parent, 'subscribeform'): return self.aq_parent.subscribeform(self,REQUEST) else: return default_subscribeform(self,REQUEST) ###################################################################### # METHOD CATEGORY: misc ###################################################################### zwiki_username_or_ip__roles__ = None def zwiki_username_or_ip(self, REQUEST=None): """ search REQUEST for an authenticated member or a zwiki_username cookie XXX added REQUEST arg at one point when sending mail notification in append() was troublesome - still needed ? """ if not REQUEST and hasattr(self,'REQUEST'): REQUEST = self.REQUEST username = None if REQUEST: user = REQUEST.AUTHENTICATED_USER username = user.getUserName() if not username or str(user.acl_users._nobody) == username: if hasattr(REQUEST, 'cookies') and \ REQUEST.cookies.has_key('zwiki_username') and \ REQUEST.cookies['zwiki_username']: username = REQUEST.cookies['zwiki_username'] else: username = REQUEST.REMOTE_ADDR return username or '' text__roles__ = None def text(self, REQUEST=None, RESPONSE=None): # see also backwards compatibility section # why permission-free ? """ return this page's raw text (a permission-free version of document_src) also fiddle the mime type for web browsing misc tests: ### ensure we don't lose first lines to DTML's decapitate() >>> zc.TestPage._set_text(r'first: line\\n\\nsecond line\\n') >>> zc.TestPage.text() 'first: line\\\\n\\\\nsecond line\\\\n' >>> zc.TestPage.text() 'first: line\\\\n\\\\nsecond line\\\\n' ### ensure none of these reveal the antidecapkludge >>> zc.TestPage.edit(type='htmldtml') >>> zc.TestPage._set_text('test text') >>> zc.TestPage.text() 'test text' >>> zc.TestPage.read() 'test text' >>> zc.TestPage.__str__() 'test text' """ if RESPONSE is not None: RESPONSE.setHeader('Content-Type', 'text/plain') #RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime)) return self.read() # antidecapitationkludge - see render_structuredtextdtml _old_read = DTMLDocument.read def read(self): return re.sub('\n\n?','', self._old_read()) def __repr__(self): return ("<%s %s at 0x%s>" % (self.__class__.__name__, `self.id()`, hex(id(self))[2:])) # methods for reliable dtml access to page & wiki url # these have been troublesome; they need to work with & without # virtual hosting. Tests needed. # Keep old versions around to help with debugging - see # http://zwiki.org/VirtualHostingSummary def page_url(self): """return the url path for this wiki page""" return self.page_url7() def wiki_url(self): """return the base url path for this wiki""" return self.wiki_url7() def page_url1(self): """return the url path for the current wiki page""" o = self url = [] while hasattr(o,'id'): url.insert(0,absattr(o.id)) o = getattr(o,'aq_parent', None) return quote('/' + join(url[1:],'/')) def wiki_url1(self): """return the base url path for the current wiki""" # this code is buried somewhere in cvs return '?' def page_url2(self): """return the url path for the current wiki page""" return quote('/' + self.absolute_url(relative=1)) def wiki_url2(self): """return the base url path for the current wiki""" return quote('/' + self.aq_inner.aq_parent.absolute_url(relative=1)) def page_url3(self): """return the url path for the current wiki page""" return self.REQUEST['BASE1'] + '/' + self.absolute_url(relative=1) def wiki_url3(self): """return the base url path for the current wiki""" return self.REQUEST['BASE1'] + '/' + self.aq_inner.aq_parent.absolute_url(relative=1) def page_url4(self): """return the url path for the current wiki page""" return self.absolute_url(relative=0) def wiki_url4(self): """return the base url path for the current wiki""" return self.aq_inner.aq_parent.absolute_url(relative=0) def page_url5(self): """return the url path for the current wiki page""" return self.absolute_url() def wiki_url5(self): """return the base url path for the current wiki""" return self.aq_inner.aq_parent.absolute_url() def page_url6(self): """return the url path for the current wiki page""" return '/' + self.absolute_url(relative=1) def wiki_url6(self): """return the base url path for the current wiki""" return '/' + self.aq_inner._my_folder().absolute_url(relative=1) def _my_folder(self): """Obtain parent folder, avoiding potential acquisition recursion.""" got = parent = self.aq_parent # Handle bizarred degenerate case - darnit, i've forgotten now where # i've seen this occur! while (got and hasattr(got, 'aq_parent') and got.aq_parent and (got is not got.aq_parent) and (aq_base(got) is aq_base(got.aq_parent))): parent = got got = got.aq_parent return parent def page_url7(self): """return the url path for the current wiki page""" return self.wiki_url() + '/' + quote(self.id()) def wiki_url7(self): """return the base url path for the current wiki""" return self.aq_inner.aq_parent.absolute_url() ###################################################################### # METHOD CATEGORY: backwards compatibility ###################################################################### def doLegacyFixups(self): """upgrade old zwikipage objects on the fly """ # can probably be simplified. # would this be better done offline by a script ? # Note that the objects don't get very far unpickling, some # by-hand adjustment via command-line interaction is necessary # to get them over the transition, sigh. --ken # not sure what this means --SM # Confused about what happens in the zodb when class # definitions change. I now think that all instances in the # zodb conform to the new class shape immediately on # refresh/restart. True ? Not sure what exactly happens to # deleted properties/attributes - discarded ? No I think they # lurk around. # add parents property if (not hasattr(self, 'parents')) or self.parents is None: self.parents = [] # username -> last_editor/last_editor_ip if not hasattr(self, 'last_editor'): self.last_editor = '' if not hasattr(self, 'last_editor_ip'): self.last_editor_ip = '' if (hasattr(self, 'username') and type(self.username) is StringType): if re.match(r'[0-9\.]+',self.username): self.last_editor_ip = self.username else: self.last_editor = self.username delattr(self,'username') self.last_editor_ip = '' self.last_editor = 'UpGrade' get_transaction().commit() # a bit confusing - here's what I believe I'm doing: # when we encounter a zwikipage with a username string # attribute (left over from the old username property), we # copy the info to the appropriate last_editor* property # and delete the old attribute. Once it's gone, the # username method below is revealed and allows old dtml to # work as before - handy. Also, do a commit to update the # page's timestamp immediately, otherwise the append form # will get displayed with an out of date timestamp. # upgrade page_type if not hasattr(self, 'page_type'): self.page_type = DEFAULT_PAGE_TYPE elif self.page_type == 'Structured Text': self.page_type = 'structuredtext' elif self.page_type == 'structuredtext_dtml': self.page_type = 'structuredtextdtml' elif self.page_type[0:4] == 'HTML': self.page_type = 'html' elif self.page_type == 'html_dtml': self.page_type = 'htmldtml' elif self.page_type == 'Classic Wiki': self.page_type = 'classicwiki' elif self.page_type == 'Plain Text': self.page_type = 'plaintext' # API fixups to help keep legacy DTML working def username(self): # backwards compatibility for dtml which tries to display the old # username property (doLegacyFixups may not have been called yet) for p in self._properties: if p['id'] == 'username': return username if hasattr(self,'last_editor') and self.last_editor: return self.last_editor if hasattr(self,'last_editor_ip') and self.last_editor_ip: return self.last_editor_ip return '' # actually the above is useful to keep around as last_editor_or_ip = username last_editor_or_ip__roles__ = None # text() was src() # revert to that name ? or use document_src() ? src = text src__roles__ = None # these were renamed # do these have __roles__=None just to get them published in # the absence of a docstring ? wiki_page_url = page_url wiki_page_url__roles__ = None wiki_base_url = wiki_url wiki_base_url__roles__ = None editTimestamp = timeStamp editTimestamp__roles__ = None checkEditTimeStamp = checkEditConflict checkEditTimeStamp__roles__ = None ###################################################################### # FUNCTION CATEGORY: rendering helper functions ###################################################################### def thunk_substituter(func, text, allowed): """Return a function which takes one arg and passes it with other args to passed-in func. thunk_substituter passes in the value of it's parameter, 'allowed', and a dictionary {'lastend': int, 'inpre': bool, 'intag': bool}. This is for use in a re.sub situation, to get the 'allowed' parameter and the state dict into the callback. (The technical term really is "thunk". Honest.-)""" state = {'lastend': 0, 'inpre': 0, 'incode': 0, 'intag': 0} return lambda arg, func=func, allowed=allowed, text=text, state=state: ( func(arg, allowed, state, text)) def within_literal(upto, after, state, text, rfind=rfind, lower=lower): """Check text from state['lastend'] to upto for literal context: - Within an enclosing '
' preformatted region '' - Within an enclosing '
' code fragment '
'
- Within a tag '<' body '>'
We also update the state dict accordingly."""
# XXX This breaks on badly nested angle brackets and , etc.
lastend, inpre, intag = state['lastend'], state['inpre'], state['intag']
lastend = state['lastend']
inpre, incode, intag = state['inpre'], state['incode'], state['intag']
newintag = newincode = newinpre = 0
text = lower(text)
# Check whether '' is currently (possibly, still) prevailing. opening = rfind(text, '', lastend, upto) if (opening != -1) or inpre: if opening != -1: opening = opening + 4 else: opening = lastend if -1 == rfind(text, '', opening, upto): newinpre = 1 state['inpre'] = newinpre # Check whether '' is currently (possibly, still) prevailing. opening = rfind(text, '
', lastend, upto) if (opening != -1) or incode: if opening != -1: opening = opening + 5 # We must already be incode, start at beginning of this segment: else: opening = lastend if -1 == rfind(text, '
', opening, upto): newincode = 1 state['incode'] = newincode # Determine whether we're (possibly, still) within a tag. opening = rfind(text, '<', lastend, upto) if (opening != -1) or intag: # May also be intag - either way, we skip past last: if opening != -1: opening = opening + 1 # We must already be intag, start at beginning of this segment: else: opening = lastend if -1 == rfind(text, '>', opening, upto): newintag = 1 state['intag'] = newintag state['lastend'] = after return newinpre or newincode or newintag # exact reverse of DT_Util.html_quote def html_unquote(v, name='(Unknown name)', md={}, character_entities=( (('&'), '&'), (('<'), '<' ), (('>'), '>' ), (('<'), '\213' ), (('>'), '\233' ), (('"'), '"'))): #" text=str(v) for re,name in character_entities: if find(text, re) >= 0: text=join(split(text,re),name) return text ###################################################################### # FUNCTION CATEGORY: page creation (ZMI) ###################################################################### # ZMI page creation form manage_addZWikiPageForm = HTMLFile('dtml/zwikiPageAdd', globals()) def manage_addZWikiPage(self, id, title='', file='', REQUEST=None, submit=None): """Add a ZWiki Page object with the contents of file. If 'file' is empty, default text is used. """ # refactor with create # don't bother with create's default text and dtml shenanigans right now text = file if type(text) is not StringType: text=text.read() if not text: text = '' # if hasattr(self, 'standard_wiki_page'): # if callable(self.aq_parent.standard_wiki_page): # text = self.standard_wiki_page(self,REQUEST) # else: # text = DocumentTemplate.HTML(self.standard_wiki_page) # else: # text = default_wiki_page ob=ZWikiPage(source_string=text, __name__=id) ob.page_type = DEFAULT_PAGE_TYPE ob.title=title id=self._setObject(id, ob) # 2.2-specific: the new page object is owned by the current # authenticated user, if any; not desirable for executable content. # Remove any such ownership so that the page will acquire it's # owner from the parent folder. ob._deleteOwnershipAfterAdd() #XXX or _owner=UnownableOwner ? # redirect browser if needed if REQUEST is not None: try: u=self.DestinationURL() except: u=REQUEST['URL1'] if submit==" Add and Edit ": u="%s/%s" % (u,quote(id)) REQUEST.RESPONSE.redirect(u+'/manage_main') return '' # ensure that doctest will test all ZWikiPage's private methods # eg _createFileOrImage # but, causes public methods to be tested twice ? # and worse, it's twice within one invocation of setup/teardown ? #__test__ = { "ZWikiPage": ZWikiPage, } ZWiki/ZWikiWeb.py 0100775 0000764 0000764 00000014762 07644512335 013255 0 ustar vahur vahur ###################################################################### # create wikis from templates from Globals import HTMLFile, package_home from OFS.Folder import Folder from OFS.DTMLMethod import DTMLMethod from Products.ZWiki.ZWikiPage import ZWikiPage from AccessControl import getSecurityManager from Defaults import DEFAULT_PAGE_TYPE import os, re, string # ZMI wiki creation form manage_addZWikiWebForm = HTMLFile('dtml/zwikiWebAdd', globals()) ###################################################################### # FUNCTION CATEGORY: wikiweb creation ###################################################################### def manage_addZWikiWeb(self, new_id, new_title='', wiki_type='zwikidotorg', REQUEST=None, enter=0): """ Create a new zwiki web of the specified type #>>> ZWiki.manage_addZWikiWeb('test') """ if wiki_type in self.listFsWikis(): self.addZWikiWebFromFs(new_id,new_title,wiki_type,REQUEST) elif wiki_type in self.listZodbWikis(): self.addZWikiWebFromZodb(new_id,new_title,wiki_type,REQUEST) else: messageDialog('unknown wiki type') if REQUEST is not None: if enter: # can't see why this doesn't work after addZWikiWebFromFs #REQUEST.RESPONSE.redirect(getattr(self,new_id).absolute_url()) REQUEST.RESPONSE.redirect(REQUEST['URL3']+'/'+new_id+'/') else: try: u=self.DestinationURL() except: u=REQUEST['URL1'] REQUEST.RESPONSE.redirect(u+'/manage_main?update_menu=1') #why do this ? #else: # return '' def addZWikiWebFromZodb(self,new_id, new_title='', wiki_type='zwikidotorg', REQUEST=None): """ Create a new zwiki web by cloning the specified template in /Control_Panel/Products/ZWiki """ # locate the specified wiki prototype # these are installed in /Control_Panel/Products/ZWiki prototype = self.getPhysicalRoot().Control_Panel.Products.ZWiki[wiki_type] # clone it self.manage_clone(prototype, new_id, REQUEST) wiki = getattr(self, new_id) wiki.manage_changeProperties(title=new_title) # could do stuff with ownership here # set it to low-privileged "nobody" by default ? def addZWikiWebFromFs(self, new_id, title='', wiki_type='zwikidotorg', REQUEST=None): """ Create a new zwiki web from the specified template on the filesystem """ ob = Folder() ob.id=str(new_id) ob.title=str(title) id = self._setObject(ob.id, ob) ob = getattr(self, id) p = package_home(globals()) + os.sep + 'wikis' + os.sep + wiki_type fnames = os.listdir(p) # hmm auto-cataloging is really slowing this down! for fname in fnames: if re.match(r'[Mm]akefile',fname): # I must be allowed my makefiles continue elif fname[-1] == '~': # and just in case continue elif re.match(r'[A-Z]',fname[0]): f = open(p + os.sep + fname, 'r') _addZWikiPage(ob, fname, title='', file=f.read()) elif re.match(r'[a-z]',fname[0]): f = open(p + os.sep + fname, 'r') _addDTMLMethod(ob, fname, title='', file=f.read()) else: continue def _addZWikiPage(self, id, title='', file=''): """ >>> ZWiki.manage_addZWikiWeb('test') >>> zc['test'].RecentChanges.page_type 'htmldtml' >>> zc['test'].RecentChanges.parents ['HelpPage'] """ id=str(id) title=str(title) # parse optional attributes # it's late - please do better m = re.match(r'(?si)(^#page_type:(.*?)\n)?(.*)',file) if m.group(2): page_type = string.strip(m.group(2)) else: page_type = DEFAULT_PAGE_TYPE file = m.group(3) m = re.match(r'(?si)(^#parents:(.*?)\n)?(.*)',file) if m.group(2): parents = string.split(string.strip(m.group(2)),',') else: parents = [] file = m.group(3) text = file #ob = makeZWikiPage(id, title, file) ob = ZWikiPage(source_string=text, __name__=id) ob.title = title ob.page_type = page_type ob.parents = parents username = getSecurityManager().getUser().getUserName() ob.manage_addLocalRoles(username, ['Owner']) # is that what we want ? how about what we do elsewhere # # 2.2-specific: the new page object is owned by the current # # authenticated user, if any; not desirable for executable content. # # Remove any such ownership so that the page will acquire it's # # owner from the parent folder. # ob._deleteOwnershipAfterAdd() # #XXX or _owner=UnownableOwner ? #ob.setSubOwner('both') #? self._setObject(id, ob) #def _makeZWikiPage(id, title, file): # ob = ZWikiPage(source_string=file, __name__=id) # ob.title = title # ob.parents = [] # username = getSecurityManager().getUser().getUserName() # ob.manage_addLocalRoles(username, ['Owner']) # #ob.setSubOwner('both') #? # return ob # other CMFWiki stuff # initPageMetadata(ob) # for name, perm in ob._perms.items(): # pseudoperm = default_perms[name] # local_roles_map = ob._local_roles_map # roles_map = ob._roles_map # roles = (local_roles_map[name],) + roles_map[pseudoperm] # ob.manage_permission(perm, roles=roles) def _addDTMLMethod(self, id, title='', file=''): id=str(id) title=str(title) ob = DTMLMethod(source_string=file, __name__=id) ob.title = title username = getSecurityManager().getUser().getUserName() ob.manage_addLocalRoles(username, ['Owner']) #ob.setSubOwner('both') #? self._setObject(id, ob) def listWikis(self): """ list all wiki templates available in the filesystem or zodb >>> ZWiki.listWikis() ['basic-0.9.5', 'zwikidotorg', 'zwikidotorg-0.9.6'] """ list = self.listFsWikis() for w in self.listZodbWikis(): if not w in list: list.append(w) list.sort() return list def listZodbWikis(self): """ list the wiki templates available in the ZODB >>> ZWiki.listZodbWikis() ['basic-0.9.5', 'zwikidotorg-0.9.6'] """ list = self.getPhysicalRoot().Control_Panel.Products.ZWiki.objectIds() list.remove('Help') return list def listFsWikis(self): """ list the wiki templates available in the filesystem >>> ZWiki.listFsWikis() ['zwikidotorg'] """ list = os.listdir(package_home(globals()) + os.sep + 'wikis') # temporary if 'CVS' in list: list.remove('DEFAULTS') if 'DEFAULTS' in list: list.remove('DEFAULTS') return list ZWiki/__init__.py 0100775 0000764 0000764 00000014424 07644512335 013314 0 ustar vahur vahur ###################################################################### # product initialization __doc__=""" ZWikiPage product """ __version__='0.9.8' import ZWikiPage, ZWikiWeb, OFS.Folder import os, re from Globals import package_home from OFS.ObjectManager import customImporters # "to be sure the permissions take effect you should add a # Globals.default__class_init__() call after any class that has an # __ac_permissions__ structure." import Globals; Globals.default__class_init__(ZWikiPage) ###################################################################### # FUNCTION CATEGORY: product initialization ###################################################################### def initialize(context): """Initialize the ZWiki product. """ try: # register classes associated with the product context.registerClass( ZWikiPage.ZWikiPage, constructors = ( ZWikiPage.manage_addZWikiPageForm, ZWikiPage.manage_addZWikiPage), icon = 'images/ZWikiPage_icon.gif' ) # allow zclass subclassing ? #context.createZClassForBase(ZWikiPage.ZWikiPage, # globals(), # nice_name=None) # not really registering Folder.. we are just adding # the "Add ZWiki Web" menu item context.registerClass( OFS.Folder.Folder, meta_type='ZWiki Web', #icon = 'images/AZWikiWeb_icon.gif' constructors = ( ZWikiWeb.manage_addZWikiWebForm, ZWikiWeb.manage_addZWikiWeb, ZWikiWeb.listWikis, ZWikiWeb.listZodbWikis, ZWikiWeb.listFsWikis, ZWikiWeb.addZWikiWebFromFs, ZWikiWeb.addZWikiWebFromZodb, ) ) # auto-import! install extra wiki templates to zodb #autoImport(context) except: import sys, traceback, string type, val, tb = sys.exc_info() sys.stderr.write(string.join(traceback.format_exception(type, val, tb), '')) del type, val, tb def autoImport(context): """Import any files in our import directory into /Control_Panel/Products/PRODUCT. Called at product startup and refresh. XXX auto-refresh too ? How to handle versions & upgrades of imported content nicely ? Don't want to overwrite anything the zope admin has in place. Try to KISS.. Here's the plan for zwiki: sample wiki zexp filenames will end in a version number corresponding to the zwiki release they shipped with (eg). The imported id is based on this so newer versions will import cleanly. The Add Zwiki Web form will use the latest version of each sample wiki that it finds. The admin can clean out old versions at will. """ importdir = package_home(globals()) + os.sep + 'import' if 0: #XXX not exists(importdir) return # sneaky, or dumb, way to get to the zodb obj = context.getProductHelp() productfolder = obj.getPhysicalRoot().Control_Panel.Products['ZWiki'] #XXX should find product name dynamically # based on manage_importObject # locate a valid connection connection=obj._p_jar while connection is None: obj=obj.aq_parent connection=obj._p_jar # try to import any and all files found # will fail if the object is already in the ZODB files = os.listdir(importdir) for filename in files: filepath = importdir + os.sep + filename # how do we get these to show in undo ? #get_transaction().begin() try: ob=connection.importFile(filepath, customImporters=customImporters) id = filename[:-5] # assume files are named something.zexp productfolder._setObject(id, ob, set_owner=0) # try to make ownership implicit if possible ob=productfolder._getOb(id) ob.manage_changeOwnershipType(explicit=0) #get_transaction().commit() #XXX log it except: #get_transaction().abort() #XXX log it pass # enable catalog awareness for common ZMI operations def manage_afterAdd(self, item, container): self.index_object() ZWikiPage.ZWikiPage.manage_afterAdd = manage_afterAdd def manage_afterClone(self, item): self.index_object() ZWikiPage.ZWikiPage.manage_afterClone = manage_afterClone def manage_beforeDelete(self, item, container): self.unindex_object() ZWikiPage.ZWikiPage.manage_beforeDelete = manage_beforeDelete original_edit = ZWikiPage.ZWikiPage.manage_edit def manage_edit(self,data,title,SUBMIT='Change',dtpref_cols='50', dtpref_rows='20',REQUEST=None): """Edit object an reindex""" r = original_edit(self,data,title,SUBMIT,dtpref_cols,dtpref_rows,REQUEST) self.reindex_object() return r ZWikiPage.ZWikiPage.manage_edit = manage_edit original_addProperty = ZWikiPage.ZWikiPage.manage_addProperty def manage_addProperty(self, id, value, type, REQUEST=None): """Add property and reindex""" r = original_addProperty(self,id,value,type,REQUEST) self.reindex_object() return r ZWikiPage.ZWikiPage.manage_addProperty = manage_addProperty original_delProperties = ZWikiPage.ZWikiPage.manage_delProperties def manage_delProperties(self, ids=None, REQUEST=None): """Delete properties and reindex""" r = original_delProperties(self, ids, REQUEST) self.reindex_object() return r ZWikiPage.ZWikiPage.manage_delProperties = manage_delProperties original_changeProperties = ZWikiPage.ZWikiPage.manage_changeProperties def manage_changeProperties(self, REQUEST=None, **kw): """Update properties and reindex""" r = original_changeProperties(self, REQUEST, kw=kw) self.reindex_object() return r ZWikiPage.ZWikiPage.manage_changeProperties = manage_changeProperties original_editProperties = ZWikiPage.ZWikiPage.manage_editProperties def manage_editProperties(self, REQUEST): """Edit Properties and reindex""" r = original_editProperties(self, REQUEST) self.reindex_object() return r ZWikiPage.ZWikiPage.manage_editProperties = manage_editProperties ZWiki/version.txt 0100775 0000764 0000764 00000000014 10000524236 013376 0 ustar vahur vahur ZWiki-0-9-8 ZWiki/refresh.txt 0100775 0000764 0000764 00000000000 10000524236 013342 0 ustar vahur vahur ZWiki/wwml.py 0100775 0000764 0000764 00000032225 07757704122 012544 0 ustar vahur vahur # WikiWikiWeb-style markup # contributed by Tres Seaver """ tests: >>> assert 2==2 """ import re import string import urllib name_pattern_1 = r'[A-Z]+[a-z]+[A-Z][A-Za-z]' name_pattern_2 = r'[A-Z][A-Z]+[a-z][A-Za-z]' bracket_pattern = r'\[[\\\w.:_ ]+\]' full_wikilink_pattern = re.compile( r'!?(%s*|%s*|%s)' % ( name_pattern_1 , name_pattern_2 , bracket_pattern ) ) abbrev_wikilink_pattern = re.compile( r'!?(%s*|%s*)' % ( name_pattern_1 , name_pattern_2 ) ) strip_bracket_pattern = re.compile( r'^\[(.*)\]$' ) def Wiki_ize( self, text='', full_pattern=0 ): """ Transform a html page into a wiki page by changing WikiNames into hyperlinks; pass full_pattern=1 to pick up square bracket links, too (default not to avoid fighting with StructuredText). tests: >>> assert 2==2 """ pattern = full_pattern and full_wikilink_pattern or abbrev_wikilink_pattern wikifier = _Wikifier( self ) text = pattern.sub( wikifier._wikilink_replace, text ) return text class _Wikifier : def __init__( self, other ) : self.other_ = other def _wikilink_replace( self, matchobj ): """replace an occurrence of wikilink_pattern with a suitable hyperlink """ # any matches preceded by ! should be left alone if re.match( '^!', matchobj.group( 0 ) ): return matchobj.group( 1 ) # discard enclosing [] if any wikiname = strip_bracket_pattern.sub( r'\1', matchobj.group( 1 ) ) # if something of this name exists, link to it; # otherwise, provide a "?" creation link if hasattr( self.other_.aq_parent.aq_self, wikiname ): return '' + wikiname + '' else: return '%s?' \ % ( wikiname , urllib.quote( self.other_.id ) , urllib.quote( wikiname ) ) class WMMLTranslator : """ |FSM/translator for texts marked up using WikiWikiMarkupLanguage, as | defined at http://www.c2.org/cgi/wiki?TextFormattingRules: | |Paragraphs | | Don't Indent paragraphs | | Words wrap and fill as needed | | Use blank lines as separators | | Four or more minus signs make a horizontal rule | |Lists | | tab-* for first level | | tab-tab-* for second level, etc. | | Use * for bullet lists, 1. for numbered lists (mix at will) | | tab-Term:-tab Definition for definition lists | | One line for each item | | Other leading whitespace signals preformatted text, changes font. | |Fonts | | Indent with one or more spaces to use a monospace font: | | This is in monospace # note it is off the margin | |This is not # it is on the left margin there | |Indented Paragraphs (Quotes) | | tab-space-:-tab -- often used (with emphasis) for quotations. | (See SimulatingQuoteBlocks) | |Emphasis | | Use doubled single-quotes ('') for emphasis (usually italics) | | Use tripled single-quotes (''') for strong emphasis (usually bold) | | Use five single-quotes ('), or triples within doubles, for some other | kind of emphasis (BoldItalicInWiki), but be careful about the bugs in | the Wiki emphasis logic... | | At most one per line. But as many as you need in a paragraph; just use | multiple lines without intervening blank lines. | | Don't cross line boundaries | |References | | JoinCapitalizedWords to make local references | | [1], [2], [3], [4] refer to remote references. Click EditLinks on the | edit form to enter URLs | | Or precede URLs with "http:", "ftp:" or "mailto:" to create links | automatically as in: http://c2.com/ | | URLs ending with .gif are inlined if inserted as a remote reference | | ISBN 0-13-748310-4 links to a bookseller. (The pattern is: | "ISBN", optional colon, space, ten digits with optional hypens, | the whole thing optionally in square brackets. The last digit can be | an "X".) We are an AmazonAssociate. | | I S B N: 0123456789 becomes ISBN 0123456789 | [I S B N 0123456789] becomes ISBN 0123456789 | [I S B N: 123-456-789-X] becomes ISBN 123-456-789-X """ # # Patterns used to parse markup. # blankLine = re.compile( '^$' ) anyGroup = "(.*)" notTick = "([^']*)" emphasis = re.compile( "%s''%s''" % ( (notTick,) * 2 ) ) strong = re.compile( "%s'''%s'''" % ( (notTick,) * 2 ) ) hrule = re.compile( '^----[-]*%s' % anyGroup ) nTabs = "([\t]*)" tabList = re.compile( '^%s%s' % ( nTabs, anyGroup ) ) bulletPrefix = '^(\*)' digitPrefix = '^([1-9][0-9]*[\.]?)' dictPrefix = "^([A-Za-z '-]*):" itemSuffix = "[ \t]+" + anyGroup bulletItem = re.compile( bulletPrefix + itemSuffix ) digitItem = re.compile( digitPrefix + itemSuffix ) dictItem = re.compile( dictPrefix + itemSuffix ) codePrefix = '^([ ]+)' codeLine = re.compile( codePrefix + anyGroup ) httpPrefix = '(http:)' ftpPrefix = '(ftp:)' mailtoPrefix = '(mailto:)' urlSuffix = '([^ ]+)' httpURL = re.compile( httpPrefix + urlSuffix ) ftpURL = re.compile( ftpPrefix + urlSuffix ) mailtoURL = re.compile( mailtoPrefix + urlSuffix ) # def __init__( self, other ) : def __init__( self ) : """ """ # self.other = other self.listStack = [] self.translatedLines = [] self.topList = self.translatedLines self.topCode = '' def nestingLevel( self ) : """ How deep is the nesting stack? """ return len( self.listStack ) def pushList( self, code ) : """ Start a new nested line list (e.g., for /
/
/
). """ parentList = self.topList self.topList = [] self.topCode = code self.listStack.append( ( code, self.topList, parentList ) ) def popList( self ) : """ Finish current nested line list. """ # First, pop the topmost record, and restore invariant (topList # points to the list member of topmost). oldLevel = self.nestingLevel() if not oldLevel : return code, lines, self.topList = self.listStack.pop() newLevel = self.nestingLevel() self.topCode = newLevel and self.listStack[ newLevel - 1 ][ 0 ] or '' # Next, insert popped record's lines as nested structure. if code == 'PRE' : indent = '' else : indent = ' ' self.topList.append( '%s<%s>' % ( indent, code ) ) for line in lines : self.topList.append( '%s%s%s' % ( indent, indent, line ) ) self.topList.append( '%s%s>' % ( indent, code ) ) def replaceEmphasis( self, matchObj ) : """ Replace ''foo'' with foo. """ return '%s%s' % ( matchObj.group(1) , matchObj.group(2) ) def replaceStrong( self, matchObj ) : """ Replace '''foo''' with foo. """ return '%s%s' % ( matchObj.group(1) , matchObj.group(2) ) def replaceInlineURL( self, matchObj ): """ Replace http://www.foo.com with http://www.foo.com (likewise ftp: and mailto: URL's). """ return '%s%s' % ( matchObj.group( 1 ) , matchObj.group( 2 ) , matchObj.group( 1 ) , matchObj.group( 2 ) ) def appendCodeLine( self, line ) : """ """ while self.nestingLevel() > 1 : self.popList() if self.topCode != 'PRE' : self.popList() self.pushList( 'PRE' ) self.topList.append( line ) def mungeLine( self, line ) : """ """ # Munge "simple" markup. # line = Wiki_ize( self.other, line, 1 ) line = self.blankLine.sub( '', line ) line = self.strong.sub( self.replaceStrong, line ) line = self.emphasis.sub( self.replaceEmphasis, line ) # SKWM let the main routines take care of this later #line = self.httpURL.sub( self.replaceInlineURL, line ) #line = self.ftpURL.sub( self.replaceInlineURL, line ) #line = self.mailtoURL.sub( self.replaceInlineURL, line ) line = self.hrule.sub( '
', line ) line = self.blankLine.sub( '', line ) return line def parseLine( self, line ) : # Break off leading tabs and count. tabMatch = self.tabList.match( line ) numTabs = len( tabMatch.group( 1 ) ) line = tabMatch.group( 2 ) # Store line and compute new state. self.newState( numTabs, line ) def replaceListItem( self, matchObj ) : """ """ return "
- %s" % matchObj.group( 2 ) def replaceDictItem( self, matchObj ) : """ """ return "
- %s
- %s" % ( matchObj.group( 1 ), matchObj.group( 2 ) ) def lexListLine( self, line ) : line, nsub = self.bulletItem.subn( self.replaceListItem, line ) if nsub : return 'UL', self.mungeLine( line ) line, nsub = self.digitItem.subn( self.replaceListItem, line ) if nsub : return 'OL', self.mungeLine( line ) line, nsub = self.dictItem.subn( self.replaceDictItem, line ) if nsub : return 'DL', self.mungeLine( line ) return '', self.mungeLine( line ) def newState( self, numTabs, line ) : """ Decode new state (i.e., adjust the nesting stack), based on current state and inputs. """ level = self.nestingLevel() if self.topCode == 'PRE' : self.popList() if numTabs : newCode, line = self.lexListLine( line ) else : newCode = '' line = self.mungeLine( line ) if level < numTabs : self.pushList( newCode ) elif level > numTabs : self.popList() if newCode != self.topCode : self.popList() if newCode : self.pushList( newCode ) self.topList.append( line ) def translate( self, lines ) : """ """ for line in lines : if self.codeLine.match( line ) : self.appendCodeLine( line ) else : self.parseLine( line ) return self.translatedLines __call__ = translate #def translate_WMML( self, text ) : # wt = WMMLTranslator( self ) def translate_WMML( text ) : wt = WMMLTranslator() lines = wt( string.split( str( text ), '\n' ) ) return string.join( lines, '\n' ) if __name__ == '__main__' : testString = \ """ This is the first paragraph. There should be a paragraph marker before it, and it should wrap nicely around. --------- That should have produced a horizontal rule. The last word should be ''emphasized''. The last word should be '''strong''' Can we do both ''emph'' and '''strong''' on the same line? How about two times: ''emph1'' followed by ''emph2''? * one bullet, with a WikiName * second bullet, with an UnknownWikiName 1. nested digit 2. nested digit 1. Top-level numbered list 2. Top-level numbered list * nested bullet * nested bullet 3. See if we keep the numbering here! Term: a definition ''Marked-up Term'': another definition, but with ''markup'' : a definition without a term This should be monospaced, and indented manually and now this is another normal paragraph. Here is some more pre-formatted text. * followed by a bullet Let's see if AutomaticURLLinking works yet: * http://www.palladion.com * ftp://www.neosoft.com/pub/users/t/tseaver * mailto:tseaver@palladion.com And [these words] should be linked, too. """ class FakeParent : def __init__( self, parent, **kw ) : self.aq_parent = parent for key, value in kw.items() : setattr( self, key, value ) grandparent = FakeParent( None, WikiName = 0 ) # print translate_WMML( FakeParent( grandparent, id = 'parent' ), testString ) print translate_WMML( testString ) ZWiki/dtml/ 0040775 0000764 0000764 00000000000 10000524010 012103 5 ustar vahur vahur ZWiki/dtml/zwikiPageAdd.dtml 0100775 0000764 0000764 00000002066 07644512335 015367 0 ustar vahur vahur
Add ZWiki Page Add ZWiki Page
You may create a new ZWiki Page object using the form below. You may also choose to upload an existing html file from your local computer by clicking the Browse button.
ZWiki/dtml/zwikiWebAdd.dtml 0100775 0000764 0000764 00000004511 07644512335 015225 0 ustar vahur vahurAdd a ZWiki web Add a ZWiki web..
How to configure permissions
Users will need 'View' and 'Access Contents Information' permissions on the folder to view the wiki. To append comments they will need 'Append to ZWiki Pages' permission. To edit, they will need 'Change ZWiki Page' permission. To create new pages they will need 'Add ZWiki Page' permission.Security issues
"You are responsible for security; zwiki may contain unforeseen risks."
ZWiki allows users to create content which will be executed when viewed by others (DTML, or at least javascript). If you will be allowing untrusted users to edit your wiki, you should understand the ServerSideTrojanIssue. Tip: to restrict the permissions available to pages at run-time, ensure that your zwikiweb folder is owned by a low-privileged user.About ZWiki
Here is the README. For more documentation and discussion, visit http://zwiki.org. ZWiki/images/ 0040775 0000764 0000764 00000000000 10000524010 012410 5 ustar vahur vahur ZWiki/images/ZWikiPage_icon.gif 0100775 0000764 0000764 00000000324 07644512335 015773 0 ustar vahur vahur GIF89a „ ÿ ÿ PPP€€€ÀÀÀÿ € @ @€€ €@ @ÿÿ ÿÿ€ €€@€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù , Q # diŠ¨#<ë®Bª¶n»>1zÓöãç3ž/@$ê|.$aЇŠ¡9B&•ÔÙJœ[`«·æI³aø1V!˾vú Ӽ”™Šú‹! ; ZWiki/tests/ 0040775 0000764 0000764 00000000000 10000524027 012315 5 ustar vahur vahur ZWiki/tests/__init__.py 0100775 0000764 0000764 00000006347 07644512335 014463 0 ustar vahur vahur """ Unit test package for ZWiki. As test suites are added, they should be added to the mega-test-suite below. NB: ZWiki tests are not necessarily comprehensive, consistent, well-structured. Maybe I should test at a lower level ? They may be more like a bunch of random spot checks, that's still darn useful. The CMF tests seem to support running from the command-line as well as in the zope context. I can't see that ever working for me so maybe I'll drop that code for clarity. simpletestrunner was a learning experience, and still has some problems. For god's sake get old zunit working again. Aha! a zunit 0.1 testrunner will happily run the test_suite below, and what's more it's giving a pretty streaming output I have never seen before. I'll TAKE it. old notes, test ideas: how to do authentication/permissions-related testing ? test functionality of all the different page types, with no, good & bad dtml test edit's various cases test leading & trailing newline preservation test initial http-like-header: preservation test whether stx heading on first line works test all the various kinds of links - mailto, http, wikiname, remotewikilink, stx, combinations.. test handling of large cookies, empty cookies, missing cookies by UserOptions, editform, standard_wiki_header... test rendering without dtml methods and with various other versions of the methods test with other zope versions (at least product startup). Test with/without ZWikiWebs; ZUnit; doctest; other ZWiki versions; other related products test zwiki_base,page_url with & without a SiteRoot test permissions the prerelease checklist on http://zwiki.org/ZWikiWebs check all version nos. are correct (special prerelease test ?) what's the best way to handle sometimes-applicable tests - like the above, or testing virtual hosting only if SiteAccess is found tests that would catch eg the deepappend bug that broke page creation from a top-level page, eg: visit both TestPage/editform?page=NewPage and TestPage/SubPage/editform?page=NewPage, in a basic wiki (simple title) and a zwikidotorg wiki (with show_hierarchy enabled) (SubPage should be a child of TestPage, add this to setup) """ from Products.ZWiki.tests import simpletestrunner from Products.ZWiki.tests import testDoctests from Products.ZWiki.tests import testZWikiPage from Products.ZWiki.tests import testZWikiWeb # need these froms ? def test_suite(): suite = simpletestrunner.TestSuite() suite.addTest(testDoctests.test_suite()) suite.addTest(testZWikiPage.test_suite()) suite.addTest(testZWikiWeb.test_suite()) return suite #def suite(): # modules_to_test = ('listtests', 'widgettests') # and so on # alltests = unittest.TestSuite() # for module in map(__import__, modules_to_test): # alltests.addTest(module.suite()) # return alltests def run(context=None): """ This is for running tests through the web, via an external method that looks like this: def run_tests(self): from Products.ZWiki import tests; return tests.run(context=self) But there are problems. Pointing ZUnit 0.1 at Products.ZWiki.tests.test_suite works better. """ return(simpletestrunner.simpleTestRunner(test_suite(), context)) ZWiki/tests/doctest.py 0100775 0000764 0000764 00000105511 07644512335 014362 0 ustar vahur vahur # Module doctest version 0.9.4 # Released to the public domain 27-Mar-1999, # by Tim Peters (tim_one@email.msn.com). # Provided as-is; use at your own risk; no warranty; no promises; enjoy! # this version hacked by SM to work with zope's extension classes """Module doctest -- a framework for running examples in docstrings. NORMAL USAGE In normal use, end each module M with: def _test(): import doctest, M # replace M with your module's name return doctest.testmod(M) # ditto if __name__ == "__main__": _test() Then running the module as a script will cause the examples in the docstrings to get executed and verified: python M.py This won't display anything unless an example fails, in which case the failing example(s) and the cause(s) of the failure(s) are printed to stdout (why not stderr? because stderr is a lame hack <0.2 wink>), and the final line of output is "Test failed.". Run it with the -v switch instead: python M.py -v and a detailed report of all examples tried is printed to stdout, along with assorted summaries at the end. You can force verbose mode by passing "verbose=1" to testmod, or prohibit it by passing "verbose=0". In either of those cases, sys.argv is not examined by testmod. In any case, testmod returns a 2-tuple of ints (f, t), where f is the number of docstring examples that failed and t is the total number of docstring examples attempted. WHICH DOCSTRINGS ARE EXAMINED? + M.__doc__. + f.__doc__ for all functions f in M.__dict__.values(), except those with private names. + C.__doc__ for all classes C in M.__dict__.values(), except those with private names. + If M.__test__ exists and "is true", it must be a dict, and each entry maps a (string) name to a function object, class object, or string. Function and class object docstrings found from M.__test__ are searched even if the name is private, and strings are searched directly as if they were docstrings. In output, a key K in M.__test__ appears with name.__test__.K Any classes found are recursively searched similarly, to test docstrings in their contained methods and nested classes. Private names reached from M's globals are skipped, but all names reached from M.__test__ are searched. By default, a name is considered to be private if it begins with an underscore (like "_my_func") but doesn't both begin and end with (at least) two underscores (like "__init__"). You can change the default by passing your own "isprivate" function to testmod. If you want to test docstrings in objects with private names too, stuff them into an M.__test__ dict, or see ADVANCED USAGE below (e.g., pass your own isprivate function to Tester's constructor, or call the rundoc method of a Tester instance). Warning: imports can cause trouble; e.g., if you do from XYZ import XYZclass then XYZclass is a name in M.__dict__ too, and doctest has no way to know that XYZclass wasn't *defined* in M. So it may try to execute the examples in XYZclass's docstring, and those in turn may require a different set of globals to work correctly. I prefer to do "import *"- friendly imports, a la import XYY _XYZclass = XYZ.XYZclass del XYZ and then the leading underscore stops testmod from going nuts. You may prefer the method in the next section. WHAT'S THE EXECUTION CONTEXT? By default, each time testmod finds a docstring to test, it uses a *copy* of M's globals (so that running tests on a module doesn't change the module's real globals, and so that one test in M can't leave behind crumbs that accidentally allow another test to work). This means examples can freely use any names defined at top-level in M. It also means that sloppy imports (see above) can cause examples in external docstrings to use globals inappropriate for them. You can force use of your own dict as the execution context by passing "globs=your_dict" to testmod instead. Presumably this would be a copy of M.__dict__ merged with the globals from other imported modules. WHAT IF I WANT TO TEST A WHOLE PACKAGE? Piece o' cake, provided the modules do their testing from docstrings. Here's the test.py I use for the world's most elaborate Rational/ floating-base-conversion pkg (which I'll distribute some day): from Rational import Cvt from Rational import Format from Rational import machprec from Rational import Rat from Rational import Round from Rational import utils modules = (Cvt, Format, machprec, Rat, Round, utils) def _test(): import doctest import sys verbose = "-v" in sys.argv for mod in modules: doctest.testmod(mod, verbose=verbose, report=0) doctest.master.summarize() if __name__ == "__main__": _test() IOW, it just runs testmod on all the pkg modules. testmod remembers the names and outcomes (# of failures, # of tries) for each item it's seen, and passing "report=0" prevents it from printing a summary in verbose mode. Instead, the summary is delayed until all modules have been tested, and then "doctest.master.summarize()" forces the summary at the end. So this is very nice in practice: each module can be tested individually with almost no work beyond writing up docstring examples, and collections of modules can be tested too as a unit with no more work than the above. WHAT ABOUT EXCEPTIONS? No problem, as long as the only output generated by the example is the traceback itself. For example: >>> 1/0 Traceback (innermost last): File " ", line 1, in ? ZeroDivisionError: integer division or modulo >>> Note that only the exception type and value are compared (specifically, only the last line in the traceback). ADVANCED USAGE doctest.testmod() captures the testing policy I find most useful most often. You may want other policies. testmod() actually creates a local instance of class doctest.Tester, runs appropriate methods of that class, and merges the results into global Tester instance doctest.master. You can create your own instances of doctest.Tester, and so build your own policies, or even run methods of doctest.master directly. See doctest.Tester.__doc__ for details. SO WHAT DOES A DOCSTRING EXAMPLE LOOK LIKE ALREADY!? Oh ya. It's easy! In most cases a copy-and-paste of an interactive console session works fine -- just make sure the leading whitespace is rigidly consistent (you can mix tabs and spaces if you're too lazy to do it right, but doctest is not in the business of guessing what you think a tab means). >>> # comments are ignored >>> x = 12 >>> x 12 >>> if x == 13: ... print "yes" ... else: ... print "no" ... print "NO" ... print "NO!!!" ... no NO NO!!! >>> Any expected output must immediately follow the final ">>>" or "..." line containing the code, and the expected output (if any) extends to the next ">>>" or all-whitespace line. That's it. Bummers: + Expected output cannot contain an all-whitespace line, since such a line is taken to signal the end of expected output. + Output to stdout is captured, but not output to stderr (exception tracebacks are captured via a different means). + If you continue a line via backslashing in an interactive session, or for any other reason use a backslash, you need to double the backslash in the docstring version. This is simply because you're in a string, and so the backslash must be escaped for it to survive intact. Like: >>> if "yes" == \\ ... "y" + \\ ... "es": # in the source code you'll see the doubled backslashes ... print 'yes' yes The starting column doesn't matter: >>> assert "Easy!" >>> import math >>> math.floor(1.9) 1.0 and as many leading whitespace characters are stripped from the expected output as appeared in the initial ">>>" line that triggered it. If you execute this very file, the examples above will be found and executed, leading to this output in verbose mode: Running doctest.__doc__ Trying: 1/0 Expecting: Traceback (innermost last): File " ", line 1, in ? ZeroDivisionError: integer division or modulo ok Trying: x = 12 Expecting: nothing ok Trying: x Expecting: 12 ok Trying: if x == 13: print "yes" else: print "no" print "NO" print "NO!!!" Expecting: no NO NO!!! ok ... and a bunch more like that, with this summary at the end: 5 items had no tests: doctest.Tester.__init__ doctest.Tester.run__test__ doctest.Tester.summarize doctest.run_docstring_examples doctest.testmod 12 items passed all tests: 8 tests in doctest 6 tests in doctest.Tester 10 tests in doctest.Tester.merge 7 tests in doctest.Tester.rundict 3 tests in doctest.Tester.rundoc 3 tests in doctest.Tester.runstring 2 tests in doctest.__test__._TestClass 2 tests in doctest.__test__._TestClass.__init__ 2 tests in doctest.__test__._TestClass.get 1 tests in doctest.__test__._TestClass.square 2 tests in doctest.__test__.string 7 tests in doctest.is_private 53 tests in 17 items. 53 passed and 0 failed. Test passed. """ # 0,0,1 06-Mar-1999 # initial version posted # 0,0,2 06-Mar-1999 # loosened parsing: # cater to stinkin' tabs # don't insist on a blank after PS2 prefix # so trailing "... " line from a compound stmt no longer # breaks if the file gets whitespace-trimmed # better error msgs for inconsistent leading whitespace # 0,9,1 08-Mar-1999 # exposed the Tester class and added client methods # plus docstring examples of their use (eww - head-twisting!) # fixed logic error in reporting total # of tests & failures # added __test__ support to testmod (a pale reflection of Christian # Tismer's vision ...) # removed the "deep" argument; fiddle __test__ instead # simplified endcase logic for extracting tests, and running them. # before, if no output was expected but some was produced # anyway via an eval'ed result, the discrepancy wasn't caught # made TestClass private and used __test__ to get at it # many doc updates # speed _SpoofOut for long expected outputs # 0,9,2 09-Mar-1999 # throw out comments from examples, enabling use of the much simpler # exec compile(... "single") ... # for simulating the runtime; that barfs on comment-only lines # used the traceback module to do a much better job of reporting # exceptions # run __doc__ values thru str(), "just in case" # privateness of names now determined by an overridable "isprivate" # function # by default a name now considered to be private iff it begins with # an underscore but doesn't both begin & end with two of 'em; so # e.g. Class.__init__ etc are searched now -- as they always # should have been # 0,9,3 18-Mar-1999 # added .flush stub to _SpoofOut (JPython buglet diagnosed by # Hugh Emberson) # repaired ridiculous docs about backslashes in examples # minor internal changes # changed source to Unix line-end conventions # moved __test__ logic into new Tester.run__test__ method # 0,9,4 27-Mar-1999 # report item name and line # in failing examples # 0,9,5 29-Jun-1999 # allow straightforward exceptions in examples - thanks to Mark Hammond! __version__ = 0, 9, 5 import types _FunctionType = types.FunctionType _ClassType = types.ClassType _ModuleType = types.ModuleType _StringType = types.StringType del types #SKWM import ExtensionClass _ExtensionClassType = type(ExtensionClass.Base) del ExtensionClass import string _string_find = string.find _string_join = string.join _string_split = string.split _string_rindex = string.rindex del string import re PS1 = ">>>" PS2 = "..." _isPS1 = re.compile(r"(\s*)" + re.escape(PS1)).match _isPS2 = re.compile(r"(\s*)" + re.escape(PS2)).match _isEmpty = re.compile(r"\s*$").match _isComment = re.compile(r"\s*#").match del re # Extract interactive examples from a string. Return a list of triples, # (source, outcome, lineno). "source" is the source code, and ends # with a newline iff the source spans more than one line. "outcome" is # the expected output if any, else an empty string. When not empty, # outcome always ends with a newline. "lineno" is the line number, # 0-based wrt the start of the string, of the first source line. def _extract_examples(s): isPS1, isPS2 = _isPS1, _isPS2 isEmpty, isComment = _isEmpty, _isComment examples = [] lines = _string_split(s, "\n") i, n = 0, len(lines) while i < n: line = lines[i] i = i + 1 m = isPS1(line) if m is None: continue j = m.end(0) # beyond the prompt if isEmpty(line, j) or isComment(line, j): # a bare prompt or comment -- not interesting continue lineno = i - 1 if line[j] != " ": raise ValueError("line " + `lineno` + " of docstring lacks " "blank after " + PS1 + ": " + line) j = j + 1 blanks = m.group(1) nblanks = len(blanks) # suck up this and following PS2 lines source = [] while 1: source.append(line[j:]) line = lines[i] m = isPS2(line) if m: if m.group(1) != blanks: raise ValueError("inconsistent leading whitespace " "in line " + `i` + " of docstring: " + line) i = i + 1 else: break if len(source) == 1: source = source[0] else: # get rid of useless null line from trailing empty "..." if source[-1] == "": del source[-1] source = _string_join(source, "\n") + "\n" # suck up response if isPS1(line) or isEmpty(line): expect = "" else: expect = [] while 1: if line[:nblanks] != blanks: raise ValueError("inconsistent leading whitespace " "in line " + `i` + " of docstring: " + line) expect.append(line[nblanks:]) i = i + 1 line = lines[i] if isPS1(line) or isEmpty(line): break expect = _string_join(expect, "\n") + "\n" examples.append( (source, expect, lineno) ) return examples # Capture stdout when running examples. class _SpoofOut: def __init__(self): self.clear() def write(self, s): self.buf.append(s) def get(self): return _string_join(self.buf, "") def clear(self): self.buf = [] def flush(self): # JPython calls flush pass # Display some tag-and-msg pairs nicely, keeping the tag and its msg # on the same line when that makes sense. def _tag_out(printer, *tag_msg_pairs): for tag, msg in tag_msg_pairs: printer(tag + ":") msg_has_nl = msg[-1:] == "\n" msg_has_two_nl = msg_has_nl and \ _string_find(msg, "\n") < len(msg) - 1 if len(tag) + len(msg) < 76 and not msg_has_two_nl: printer(" ") else: printer("\n") printer(msg) if not msg_has_nl: printer("\n") # Run list of examples, in context globs. "out" can be used to display # stuff to "the real" stdout, and fakeout is an instance of _SpoofOut # that captures the examples' std output. Return (#failures, #tries). def _run_examples_inner(out, fakeout, examples, globs, verbose, name): import sys, traceback OK, BOOM, FAIL = range(3) NADA = "nothing" stderr = _SpoofOut() failures = 0 for source, want, lineno in examples: if verbose: _tag_out(out, ("Trying", source), ("Expecting", want or NADA)) fakeout.clear() try: exec compile(source, " ", "single") in globs got = fakeout.get() state = OK except: # See whether the exception was expected. if _string_find(want, "Traceback (innermost last):\n") == 0: # Only compare exception type and value - the rest of # the traceback isn't necessary. want = _string_split(want, '\n')[-2] + '\n' exc_type, exc_val, exc_tb = sys.exc_info() got = traceback.format_exception_only(exc_type, exc_val)[0] state = OK else: # unexpected exception stderr.clear() traceback.print_exc(file=stderr) state = BOOM if state == OK: if got == want: if verbose: out("ok\n") continue state = FAIL assert state in (FAIL, BOOM) failures = failures + 1 out("*" * 65 + "\n") _tag_out(out, ("Failure in example", source)) out("from line #" + `lineno` + " of " + name + "\n") if state == FAIL: _tag_out(out, ("Expected", want or NADA), ("Got", got)) else: assert state == BOOM _tag_out(out, ("Exception raised", stderr.get())) return failures, len(examples) # Run list of examples, in context globs. Return (#failures, #tries). def _run_examples(examples, globs, verbose, name): import sys saveout = sys.stdout try: sys.stdout = fakeout = _SpoofOut() x = _run_examples_inner(saveout.write, fakeout, examples, globs, verbose, name) finally: sys.stdout = saveout return x def run_docstring_examples(f, globs, verbose=0, name="NoName"): """f, globs, verbose=0, name="NoName" -> run examples from f.__doc__. Use dict globs as the globals for execution. Return (#failures, #tries). If optional arg verbose is true, print stuff even if there are no failures. Use string name in failure msgs. """ try: doc = f.__doc__ if not doc: # docstring empty or None return 0, 0 # just in case CT invents a doc object that has to be forced # to look like a string <0.9 wink> doc = str(doc) except: return 0, 0 e = _extract_examples(doc) if not e: return 0, 0 return _run_examples(e, globs, verbose, name) def is_private(prefix, base): """prefix, base -> true iff name prefix + "." + base is "private". Prefix may be an empty string, and base does not contain a period. Prefix is ignored (although functions you write conforming to this protocol may make use of it). Return true iff base begins with an (at least one) underscore, but does not both begin and end with (at least) two underscores. >>> is_private("a.b", "my_func") 0 >>> is_private("____", "_my_func") 1 >>> is_private("someclass", "__init__") 0 >>> is_private("sometypo", "__init_") 1 >>> is_private("x.y.z", "_") 1 >>> is_private("_x.y.z", "__") 0 >>> is_private("", "") # senseless but consistent 0 """ return base[:1] == "_" and not base[:2] == "__" == base[-2:] class Tester: """Class Tester -- runs docstring examples and accumulates stats. In normal use, function doctest.testmod() hides all this from you, so use that if you can. Create your own instances of Tester to do fancier things. Methods: runstring(s, name) Search string s for examples to run; use name for logging. Return (#failures, #tries). rundoc(object, name=None) Search object.__doc__ for examples to run; use name (or object.__name__) for logging. Return (#failures, #tries). rundict(d, name) Search for examples in docstrings in all of d.values(); use name for logging. Return (#failures, #tries). run__test__(d, name) Treat dict d like module.__test__. Return (#failures, #tries). summarize(verbose=None) Display summary of testing results, to stdout. Return (#failures, #tries). merge(other) Merge in the test results from Tester instance "other". >>> from doctest import Tester >>> t = Tester(globs={'x': 42}, verbose=0) >>> t.runstring(r''' ... >>> x = x * 2 ... >>> print x ... 42 ... ''', 'XYZ') ***************************************************************** Failure in example: print x from line #2 of XYZ Expected: 42 Got: 84 (1, 2) >>> t.runstring(">>> x = x * 2\\n>>> print x\\n84\\n", 'example2') (0, 2) >>> t.summarize() 1 items had failures: 1 of 2 in XYZ ***Test Failed*** 1 failures. (1, 4) >>> t.summarize(verbose=1) 1 items passed all tests: 2 tests in example2 1 items had failures: 1 of 2 in XYZ 4 tests in 2 items. 3 passed and 1 failed. ***Test Failed*** 1 failures. (1, 4) >>> """ def __init__(self, mod=None, globs=None, verbose=None, isprivate=None): """mod=None, globs=None, verbose=None, isprivate=None See doctest.__doc__ for an overview. Optional keyword arg "mod" is a module, whose globals are used for executing examples. If not specified, globs must be specified. Optional keyword arg "globs" gives a dict to be used as the globals when executing examples; if not specified, use the globals from module mod. In either case, a copy of the dict is used for each docstring examined. Optional keyword arg "verbose" prints lots of stuff if true, only failures if false; by default, it's true iff "-v" is in sys.argv. Optional keyword arg "isprivate" specifies a function used to determine whether a name is private. The default function is doctest.is_private; see its docs for details. """ if mod is None and globs is None: raise TypeError("Tester.__init__: must specify mod or globs") if mod is not None and type(mod) is not _ModuleType: raise TypeError("Tester.__init__: mod must be a module; " + `mod`) if globs is None: globs = mod.__dict__ self.globs = globs if verbose is None: import sys verbose = "-v" in sys.argv self.verbose = verbose if isprivate is None: isprivate = is_private self.isprivate = isprivate self.name2ft = {} # map name to (#failures, #trials) pair def runstring(self, s, name): """ s, name -> search string s for examples to run, logging as name. Use string name as the key for logging the outcome. Return (#failures, #examples). >>> t = Tester(globs={}, verbose=1) >>> test = r''' ... # just an example ... >>> x = 1 + 2 ... >>> x ... 3 ... ''' >>> t.runstring(test, "Example") Running string Example Trying: x = 1 + 2 Expecting: nothing ok Trying: x Expecting: 3 ok 0 of 2 examples failed in string Example (0, 2) """ if self.verbose: print "Running string", name f = t = 0 e = _extract_examples(s) if e: f, t = _run_examples(e, self.globs.copy(), self.verbose, name) if self.verbose: print f, "of", t, "examples failed in string", name self.__record_outcome(name, f, t) return f, t def rundoc(self, object, name=None): """ object, name=None -> search object.__doc__ for examples to run. Use optional string name as the key for logging the outcome; by default use object.__name__. Return (#failures, #examples). If object is a class object, search recursively for method docstrings too. object.__doc__ is examined regardless of name, but if object is a class, whether private names reached from object are searched depends on the constructor's "isprivate" argument. >>> t = Tester(globs={}, verbose=0) >>> def _f(): ... '''Trivial docstring example. ... >>> assert 2 == 2 ... ''' ... return 32 ... >>> t.rundoc(_f) # expect 0 failures in 1 example (0, 1) """ if name is None: try: name = object.__name__ except AttributeError: raise ValueError("Tester.rundoc: name must be given " "when object.__name__ doesn't exist; " + `object`) if self.verbose: print "Running", name + ".__doc__" f, t = run_docstring_examples(object, self.globs.copy(), self.verbose, name) if self.verbose: print f, "of", t, "examples failed in", name + ".__doc__" self.__record_outcome(name, f, t) if type(object) is _ClassType or type(object) is _ExtensionClassType: # SKWM f2, t2 = self.rundict(object.__dict__, name) f = f + f2 t = t + t2 return f, t def rundict(self, d, name): """ d. name -> search for docstring examples in all of d.values(). For k, v in d.items() such that v is a function or class, do self.rundoc(v, name + "." + k). Whether this includes objects with private names depends on the constructor's "isprivate" argument. Return aggregate (#failures, #examples). >>> def _f(): ... '''>>> assert 1 == 1 ... ''' >>> def g(): ... '''>>> assert 2 != 1 ... ''' >>> d = {"_f": _f, "g": g} >>> t = Tester(globs={}, verbose=0) >>> t.rundict(d, "rundict_test") # _f is skipped (0, 1) >>> t = Tester(globs={}, verbose=0, isprivate=lambda x,y: 0) >>> t.rundict(d, "rundict_test_pvt") # both are searched (0, 2) """ if not hasattr(d, "items"): raise TypeError("Tester.rundict: d must support .items(); " + `d`) f = t = 0 for thisname, value in d.items(): if type(value) in (_FunctionType, _ClassType, _ExtensionClassType): # SKWM f2, t2 = self.__runone(value, name + "." + thisname) f = f + f2 t = t + t2 return f, t def run__test__(self, d, name): """d, name -> Treat dict d like module.__test__. Return (#failures, #tries). See testmod.__doc__ for details. """ failures = tries = 0 prefix = name + "." savepvt = self.isprivate try: self.isprivate = lambda *args: 0 for k, v in d.items(): thisname = prefix + k if type(v) is _StringType: f, t = self.runstring(v, thisname) elif type(v) in (_FunctionType, _ClassType, _ExtensionClassType): # SKWM f, t = self.rundoc(v, thisname) else: raise TypeError("Tester.run__test__: values in " "dict must be strings, functions " "or classes; " + `v`) failures = failures + f tries = tries + t finally: self.isprivate = savepvt return failures, tries def summarize(self, verbose=None): """ verbose=None -> summarize results, return (#failures, #tests). Print summary of test results to stdout. Optional arg 'verbose' controls how wordy this is. By default, use the verbose setting established by the constructor. """ if verbose is None: verbose = self.verbose notests = [] passed = [] failed = [] totalt = totalf = 0 for x in self.name2ft.items(): name, (f, t) = x assert f <= t totalt = totalt + t totalf = totalf + f if t == 0: notests.append(name) elif f == 0: passed.append( (name, t) ) else: failed.append(x) if verbose: if notests: print len(notests), "items had no tests:" notests.sort() for thing in notests: print " ", thing if passed: print len(passed), "items passed all tests:" passed.sort() for thing, count in passed: print " %3d tests in %s" % (count, thing) if failed: print len(failed), "items had failures:" failed.sort() for thing, (f, t) in failed: print " %3d of %3d in %s" % (f, t, thing) if verbose: print totalt, "tests in", len(self.name2ft), "items." print totalt - totalf, "passed and", totalf, "failed." if totalf: print "***Test Failed***", totalf, "failures." elif verbose: print "Test passed." return totalf, totalt def merge(self, other): """ other -> merge in test results from the other Tester instance. If self and other both have a test result for something with the same name, the (#failures, #tests) results are summed, and a warning is printed to stdout. >>> from doctest import Tester >>> t1 = Tester(globs={}, verbose=0) >>> t1.runstring(''' ... >>> x = 12 ... >>> print x ... 12 ... ''', "t1example") (0, 2) >>> >>> t2 = Tester(globs={}, verbose=0) >>> t2.runstring(''' ... >>> x = 13 ... >>> print x ... 13 ... ''', "t2example") (0, 2) >>> common = ">>> assert 1 + 2 == 3\\n" >>> t1.runstring(common, "common") (0, 1) >>> t2.runstring(common, "common") (0, 1) >>> t1.merge(t2) *** Tester.merge: 'common' in both testers; summing outcomes. >>> t1.summarize(1) 3 items passed all tests: 2 tests in common 2 tests in t1example 2 tests in t2example 6 tests in 3 items. 6 passed and 0 failed. Test passed. (0, 6) >>> """ d = self.name2ft for name, (f, t) in other.name2ft.items(): if d.has_key(name): print "*** Tester.merge: '" + name + "' in both" \ " testers; summing outcomes." f2, t2 = d[name] f = f + f2 t = t + t2 d[name] = f, t def __record_outcome(self, name, f, t): if self.name2ft.has_key(name): print "*** Warning: '" + name + "' was tested before;", \ "summing outcomes." f2, t2 = self.name2ft[name] f = f + f2 t = t + t2 self.name2ft[name] = f, t def __runone(self, target, name): if "." in name: i = _string_rindex(name, ".") prefix, base = name[:i], name[i+1:] else: prefix, base = "", base if self.isprivate(prefix, base): return 0, 0 return self.rundoc(target, name) master = None def testmod(m, name=None, globs=None, verbose=None, isprivate=None, report=1): """m, name=None, globs=None, verbose=None, isprivate=None, report=1 Test examples in docstrings in functions and classes reachable from module m, starting with m.__doc__. Private names are skipped. Also test examples reachable from dict m.__test__ if it exists and is not None. m.__dict__ maps names to functions, classes and strings; function and class docstrings are tested even if the name is private; strings are tested directly, as if they were docstrings. Return (#failures, #tests). See doctest.__doc__ for an overview. Optional keyword arg "name" gives the name of the module; by default use m.__name__. Optional keyword arg "globs" gives a dict to be used as the globals when executing examples; by default, use m.__dict__. A copy of this dict is actually used for each docstring, so that each docstring's examples start with a clean slate. Optional keyword arg "verbose" prints lots of stuff if true, prints only failures if false; by default, it's true iff "-v" is in sys.argv. Optional keyword arg "isprivate" specifies a function used to determine whether a name is private. The default function is doctest.is_private; see its docs for details. Optional keyword arg "report" prints a summary at the end when true, else prints nothing at the end. In verbose mode, the summary is detailed, else very brief (in fact, empty if all tests passed). Advanced tomfoolery: testmod runs methods of a local instance of class doctest.Tester, then merges the results into (or creates) global Tester instance doctest.master. Methods of doctest.master can be called directly too, if you want to do something unusual. Passing report=0 to testmod is especially useful then, to delay displaying a summary. Invoke doctest.master.summarize(verbose) when you're done fiddling. """ global master if type(m) is not _ModuleType: raise TypeError("testmod: module required; " + `m`) if name is None: name = m.__name__ tester = Tester(m, globs=globs, verbose=verbose, isprivate=isprivate) failures, tries = tester.rundoc(m, name) f, t = tester.rundict(m.__dict__, name) failures = failures + f tries = tries + t if hasattr(m, "__test__"): testdict = m.__test__ if testdict: if not hasattr(testdict, "items"): raise TypeError("testmod: module.__test__ must support " ".items(); " + `testdict`) f, t = tester.run__test__(testdict, name + ".__test__") failures = failures + f tries = tries + t if report: tester.summarize() if master is None: master = tester else: master.merge(tester) return failures, tries class _TestClass: """ A pointless class, for sanity-checking of docstring testing. Methods: square() get() >>> _TestClass(13).get() + _TestClass(-12).get() 1 >>> hex(_TestClass(13).square().get()) '0xa9' """ def __init__(self, val): """val -> _TestClass object with associated value val. >>> t = _TestClass(123) >>> print t.get() 123 """ self.val = val def square(self): """square() -> square TestClass's associated value >>> _TestClass(13).square().get() 169 """ self.val = self.val ** 2 return self def get(self): """get() -> return TestClass's associated value. >>> x = _TestClass(-42) >>> print x.get() -42 """ return self.val __test__ = {"_TestClass": _TestClass, "string": r""" Example of a string object, searched as-is. >>> x = 1; y = 2 >>> x + y, x * y (3, 2) """ } def _test(): import doctest return doctest.testmod(doctest) if __name__ == "__main__": _test() ZWiki/tests/simpletestrunner.py 0100775 0000764 0000764 00000003761 07644512335 016344 0 ustar vahur vahur """ a simple through-the-web test runner this is an alternative to using a zeo client or zunit. You can set up a simple external method to call this and view test results in your browser or via command-line tool. Results are not saved anywhere. If you use the makeSuite below to build your test suites, you can pass in the live zope context as zunit does. See ZWiki.tests.__init__ for an example of use. problems: tests run wherever we invoke this, and are affected by that environment tests are affected by the current user's permissions - same as zunit ? test activities affect the user's browser session. How does zunit avoid this ? """ # import a more recent copy of pyunit if available # no effect, try other modules try: from Products.ZWiki.tests import unittest except ImportError: import unittest from cStringIO import StringIO # as in zunit: class TestCase (unittest.TestCase): def setZopeContext (self, ctx): self.ZopeContext = ctx class TestSuite(unittest.TestSuite): def setZopeContext (self, ctx): self.ZopeContext = ctx for test in self._tests: test.setZopeContext (ctx) def makeSuite(testCaseClass, prefix='test', sortUsing=cmp): cases = map(testCaseClass, unittest.getTestCaseNames(testCaseClass, prefix, sortUsing)) return TestSuite(cases) ### def simpleTestRunner(suite, context=None): """ a simple through-the-web test runner. """ context.REQUEST.RESPONSE.setHeader ('Content-type', 'text/plain') if hasattr (suite, 'setZopeContext') and context: suite.setZopeContext(context) s = StringIO() unittest.JUnitTextTestRunner(stream=s).run(suite) results = s.getvalue() # try: # stream = StringAndHttpIO (ctx.REQUEST.RESPONSE) # except AttributeError: # stream = StringIO() # success = unittest.TextTestRunner (stream).run (suite).wasSuccessful() # results = stream.getvalue() s.close() return " TEST RESULTS:\n%s" % (results) ZWiki/tests/testDoctests.py 0100775 0000764 0000764 00000012464 07644512335 015411 0 ustar vahur vahur """ doctest support Doctests are represented as python snippets within docstrings, and appear throughout the zwiki code. See doctest.py for more info. Each of the tests below invokes doctest on a module and passes if all the doctests pass. notes: - we need to use a hacked version of doctest to see the ZWikiPage class, because it's based on an extension class. - the zope context is available in doctests as "zc" - exceptions in doctests don't work because zunit catches them first - when reporting the number of tests, doctest counts contiguous non-blank >>> lines as one test old todos: I suspect we're running the doctests twice, an undesirable slowdown it would be nice to make zunit count each doctest separately get zunit to leave exceptions for doctest ? get zunit to display line of code like doctest, instead of having to define assertion strings ? get doctest to print line numbers in such a way that emacs can jump there """ from Products.ZWiki.tests import simpletestrunner import unittest #import Zope #from Products.ZWiki.ZWikiPage import * from cStringIO import StringIO import sys from Products.ZWiki.tests import doctest class DocTests(simpletestrunner.TestCase): def setUp(self): # aborting the transaction breaks the DeleteMe tests # for some reason #get_transaction().begin() # create a dummy page for doctests to play with self.ZopeContext.manage_addProduct['ZWiki'].manage_addZWikiPage( 'TestPage', file='testing testing 1 2 3') def tearDown(self): self.ZopeContext.manage_delObjects(['TestPage']) #get_transaction().abort() def _runDoctest(self, module, verbose=0, report=0): """ helper method to invoke doctest on a module from within a zunit test """ doctest.master = None # to get rid of merge warnings saveout=sys.stdout sys.stdout=StringIO() # XXX some streaming here perhaps failures, tests = doctest.testmod( module, globs={'zc':self.ZopeContext, 'self':self.ZopeContext, 'ZWiki':self.ZopeContext.manage_addProduct['ZWiki'], }, verbose=verbose, report=report, isprivate=lambda x,y: 0 # test private things too ) sys.stdout.flush() output = sys.stdout.getvalue() sys.stdout=saveout assert failures == 0, 'module %s (%d doctests):\n%s' % \ (module.__name__,tests,output) return # run any doctests in the main modules # should scan for these automatically def testZWikiPageDoctests(self): "doctests in ZWikiPage.py" from Products.ZWiki import ZWikiPage self._runDoctest(ZWikiPage) # runs ZWikiPage.py's doctests a second time ? #def testInitDoctests(self): # "doctests in __init.py__" # from Products.ZWiki import __init__ # self._runDoctest(__init__) def testCatalogAwarenessDoctests(self): "doctests in CatalogAwareness.py" from Products.ZWiki import CatalogAwareness self._runDoctest(CatalogAwareness) def testDefaultsDoctests(self): "doctests in Defaults.py" from Products.ZWiki import Defaults self._runDoctest(Defaults) def testDiffDoctests(self): "doctests in Diff.py" from Products.ZWiki import Diff self._runDoctest(Diff) def testParentsDoctests(self): "doctests in Parents.py" from Products.ZWiki import Parents self._runDoctest(Parents) def testPermissionsDoctests(self): "doctests in Permissions.py" from Products.ZWiki import Permissions self._runDoctest(Permissions) def testRegexpsDoctests(self): "doctests in Regexps.py" from Products.ZWiki import Regexps self._runDoctest(Regexps) def testZWikiWebDoctests(self): "doctests in ZWikiWeb.py" from Products.ZWiki import ZWikiWeb #ack this is a pain.. ZWikiWeb imports ZWikiPage causing (a) those #tests to be run twice and (b) trouble #self._runDoctest(ZWikiWeb) pass def testWwmlDoctests(self): "doctests in wwml.py" from Products.ZWiki import wwml self._runDoctest(wwml) # some doctests may fit better down here in the test modules ? # getting some tests repeated # def test000TestDoctestsDoctests(self): # "doctests in tests/testDoctests.py" # from Products.ZWiki.tests import testDoctests # self._runDoctest(testDoctests) # yup, doctesting ourself # # def test001TestZWikiPageDoctests(self): # "doctests in tests/testZWikiPage.py" # from Products.ZWiki.tests import testZWikiPage # self._runDoctest(testZWikiPage) # # def test002TestZWikiWebDoctests(self): # "doctests in tests/testZWikiWeb.py" # from Products.ZWiki.tests import testZWikiWeb # self._runDoctest(testZWikiWeb) #quicktest1 = testTestZWikiPageDoctests #quicktest2 = testZWikiPageDoctests def test_suite(): ts = ( simpletestrunner.makeSuite(DocTests), #simpletestrunner.makeSuite(DocTests,'testZWikiWeb'), ) return simpletestrunner.TestSuite(ts) def main(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': main() ZWiki/tests/testZWikiPage.py 0100775 0000764 0000764 00000013553 07644512335 015453 0 ustar vahur vahur import unittest import simpletestrunner from Products.ZWiki import ZWikiPage class TestZWikiPageMisc(simpletestrunner.TestCase): def setUp( self ): get_transaction().begin() def tearDown( self ): del self.ZopeContext # needed ? get_transaction().abort() # not sure if this is useful # doesn't seem to keep leftovers # out of catalog, for instance def testZMIAddPageForm(self): "test the 'add zwiki page' form" zc = self.ZopeContext ZWikiPage.manage_addZWikiPageForm(client=zc, REQUEST=zc.REQUEST) def testPageCreation(self): """test basic zwiki page creation""" p = ZWikiPage.ZWikiPage() assert p.parents == [] assert p.page_type == ZWikiPage.DEFAULT_PAGE_TYPE def testPageAddDel(self): "test adding & deleting zwiki pages via the ZMI" zc = self.ZopeContext # sure this is the best way to get at manage_add ? zc.manage_addProduct['ZWiki'].manage_addZWikiPage('TestPageB', \ title='test page', \ file='blah blah') assert hasattr(zc,'TestPageB'), 'page not created' assert zc.TestPageB.text() == 'blah blah', 'wrong text' assert zc.TestPageB.page_type == 'structuredtext', \ 'wrong default page type' zc.manage_delObjects(['TestPageB']) assert not hasattr(zc,'TestPageB'), 'page not deleted' #from Products.ZCatalog import ZCatalog,Vocabulary #from Products.ZCatalog.Catalog import Catalog,CatalogError class TestZWikiPageCatalog(simpletestrunner.TestCase): # cf /usr/lib/zope/lib/python/Products/ZCatalog/tests/testCatalog.py # # we have various ZMI and zwiki methods # for adding, deleting, editing etc. # Easier to write doctests method by method ? # # these assume a default catalog is present and being used # (requires that the DTMLDocumentExt product be installed, # since I haven't get CatalogAwareness working) # beware of existing catalog data screwing things up, maybe # we can create our own (& set SITE_CATALOG) in zc def setUp(self): pass def tearDown(self): pass def testOkWithoutCatalog(self): """ """ pass def testOkWithoutSiteCatalogProperty(self): """ """ pass def testAutoCatalogingWithZMIMethods(self): """ shag this, let's go with doctest it's easier to see what we're actually getting even so there is a slight dilemma: - use assert and get meaningful messages, or - use the style below and always see what you got ? nice to find an economical way to do both now I tell you what might also be nice - the ability to inline doctests with real code, and have them executed along with the rest when python is run in debug mode. XXX not currently doc-tested - see CatalogAwareness.py >>> # the doctest setup/teardown creates & destroys TestPage - >>> # use another in case of confusion >>> # check everything is clean before we start >>> zc.Catalog(id='CatalogTestPage') [] >>> zc.Catalog(PrincipiaSearchSource="blah") [] >>> zc.Catalog(PrincipiaSearchSource="blob") [] >>> # add a page via ZMI >>> ZWiki.manage_addZWikiPage('CatalogTestPage',title='test page',\ file='blah') '' >>> zc.Catalog(id='CatalogTestPage')[0][0] 'CatalogTestPage' >>> zc.Catalog(PrincipiaSearchSource="blah")[0][0] 'CatalogTestPage' >>> zc.Catalog(PrincipiaSearchSource="blob") [] >>> # edit a page via ZMI >>> zc.CatalogTestPage.manage_edit('blob','') >>> zc.Catalog(PrincipiaSearchSource="blah") [] >>> zc.Catalog(PrincipiaSearchSource="blob")[0][0] 'CatalogTestPage' >>> # delete a page via ZMI >>> zc.manage_delObjects(['CatalogTestPage']) >>> zc.Catalog(id='CatalogTestPage') [] >>> # who cleans this up if we don't ? """ pass def testAutoCatalogingWithZWikiMethods(self): """ try this one in zunit as noted above, these really expect to start with a clean Catalog """ zc = self.ZopeContext assert not zc.Catalog(id='CatalogTestPage'), \ 'uncreated page found in the catalog' zc.manage_addProduct['ZWiki'].manage_addZWikiPage('CatalogTestPage1') zc.CatalogTestPage1.edit(page='CatalogTestPage',text='bleh') assert zc.Catalog(id='CatalogTestPage'), \ 'page not found in catalog after creation by edit' assert zc.Catalog(PrincipiaSearchSource="bleh"), \ 'page text not found in catalog after creation by edit' assert not zc.Catalog(PrincipiaSearchSource="blib"), \ 'dummy text found in catalog after creation by edit' zc.CatalogTestPage.append(text='blib') assert zc.Catalog(PrincipiaSearchSource="blib"), \ 'new text not found in catalog after append' # not working #zc.CatalogTestPage.edit(text='DeleteMe') #assert not hasattr(zc,'CatalogTestPage'), 'ack! page not deleted' #assert not zc.Catalog(id='CatalogTestPage'), \ # 'page still found in catalog after DeleteMe edit' def test_suite(): ts = ( simpletestrunner.makeSuite(TestZWikiPageMisc), simpletestrunner.makeSuite(TestZWikiPageCatalog) ) return simpletestrunner.TestSuite(ts) def main(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': main() ZWiki/tests/testZWikiWeb.py 0100775 0000764 0000764 00000014621 07644512335 015311 0 ustar vahur vahur import simpletestrunner import unittest from Products.ZWiki.ZWikiWeb import * class ZWikiWebsTests(simpletestrunner.TestCase): WIKI_TEMPLATES = ( # ('basic', # (('FrontPage', 'structuredtext'), # ('HelpPage', 'structuredtext'), # ('JumpSearch', 'htmldtml'), # ('RecentChanges', 'htmldtml'), # ('RemoteWikiURL', 'structuredtext'), # ('SearchPage', 'htmldtml'), # ('StructuredTextRules', 'structuredtext'), # ('TextFormattingRules', 'structuredtext'), # ('UserOptions', 'structuredtextdtml'), # ('ZWiki', 'structuredtext'), # )), # ('classic', # (('FrontPage', 'classicwiki'), # ('HelpPage', 'classicwiki'), # ('RecentChanges', 'htmldtml'), # ('SearchPage', 'htmldtml'), # ('UserOptions', 'structuredtextdtml'), # ('ZWiki', 'classicwiki'), # )), ('zwikidotorg', (('AnnoyingQuote', 'structuredtext'), ('BookMarks', 'structuredtext'), ('FrontPage', 'structuredtext'), ('HelpPage', 'structuredtext'), ('HierarchalStructure', 'structuredtext'), ('JumpSearch', 'htmldtml'), ('RecentChanges', 'htmldtml'), ('RemoteWikiLinks', 'structuredtext'), ('RemoteWikiURL', 'structuredtext'), ('SearchPage', 'htmldtml'), ('StructuredTextRules', 'structuredtext'), ('TextFormattingRules', 'structuredtext'), ('TimeZone', 'structuredtext'), ('UserName', 'structuredtext'), ('UserOptions', 'structuredtextdtml'), ('WikiName', 'structuredtext'), ('WikiWikiWeb', 'structuredtext'), ('ZWiki', 'structuredtext'), ('ZopeDotOrg', 'structuredtext'), )), ) # add parents # def setUp( self ): # pass # def tearDown( self ): # pass # the form can't see listWiki in this test.. how come ? # def testAddZwikiWebForm(self): # "test the 'add zwiki web' form" # # # the old zunit test simulated a user coming in through the web # # this should work now # zc = self.ZopeContext # root = zc.getPhysicalRoot() # PARENTS = [root] # manage_addZWikiWebForm(client=zc, # REQUEST=zc.REQUEST, # PARENTS=PARENTS) # # # this one assumes direct zodb manipulation, bypassing zpublisher # # is it possible/meaningful to test the form in this case ? # # fake REQUEST because the form expects it # #req = {} # #wikitype = 'basic' # #req['new_id'] = wikitype # #req['new_title'] = wikitype + ' wiki' # #req['wiki_type'] = wikitype # ## req['SERVER_URL'] required # #self.root.manage_addProduct['ZWikiWebs'].ZWikiWebAddForm( # # client=self.root,\ # # REQUEST=req) def SKIPtestWikiTemplates(self): """ test the sample wikis' content. #>>> ZWiki.manage_addZWikiWeb('test') #>>> zc['test'].RecentChanges.page_type #'htmldtml' #>>> zc['test'].RecentChanges.parents #['HelpPage'] """ # how would this look as a doctest ? as follows: #(would it give us a global report of all errors more easily ? no) # #>>> DEFAULT_PAGES = ( #... ('basic', #... (('JumpSearch','htmldtml'), #... ('SearchPage','spam'), #... )), #... ) #>>> product=zc.manage_addProduct['ZWikiWebs'] #>>> for wikitype, pages in DEFAULT_PAGES: #... zc.REQUEST['new_id'] = wikitype #... zc.REQUEST['new_title'] = wikitype + ' wiki' #... zc.REQUEST['wiki_type'] = wikitype #... product.ZWikiWebAdd(client=product,REQUEST=zc.REQUEST) #... assert hasattr(zc, wikitype), \ # wikitype+" wiki was not created" #... for page, type in pages: #... assert hasattr(zc[wikitype],page), \ # wikitype+"/"+page+" does not exist" #... assert zc[wikitype][page].page_type == type, \ # wikitype+"/"+page+"'s type is not "+type #... zc = self.ZopeContext # for each sample wikiweb, for wikitype, pages in self.WIKI_TEMPLATES: # create one # fake form input zc.REQUEST['new_id'] = wikitype zc.REQUEST['new_title'] = wikitype + ' wiki' zc.REQUEST['wiki_type'] = wikitype manage_addZWikiWeb(zc, wikitype, new_title=wikitype+' wiki', wiki_type=wikitype, REQUEST=zc.REQUEST) #Traceback (most recent call last): # File "/var/lib/zope/Products/ZWiki/tests/testZWikiWeb.py", line 124, in testWikiTemplates # File "/var/lib/zope/Products/ZWiki/ZWikiWeb.py", line 27, in manage_addZWikiWeb # self.addZWikiWebFromFs(new_id,new_title,wiki_type,REQUEST) #AttributeError: listFsWikis # verify that it exists and that all pages listed above # are present and correct assert hasattr(zc, wikitype), \ wikitype+" wiki was not created" for page, type in pages: assert hasattr(zc[wikitype],page), \ wikitype+"/"+page+" does not exist" assert zc[wikitype][page].page_type == type, \ wikitype+"/"+page+"'s type is not "+type # also: # XXX check dtml methods # XXX check for extraneous stuff that's not listed above # XXX other stuff on http://zwiki.org/ZWikiWebs checklist #def test_suite(): # suite = simpletestrunner.TestSuite() # suite.addTest(simpletestrunner.makeSuite(ZWikiWebsTests)) # return suite def test_suite(): ts = ( simpletestrunner.makeSuite(ZWikiWebsTests), ) return simpletestrunner.TestSuite(ts) def run(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': run() ZWiki/wikis/ 0040775 0000764 0000764 00000000000 10000524010 012271 5 ustar vahur vahur ZWiki/wikis/DEFAULTS/ 0040775 0000764 0000764 00000000000 10000524010 013500 5 ustar vahur vahur ZWiki/wikis/DEFAULTS/standard_wiki_page 0100775 0000764 0000764 00000000036 07644512440 017271 0 ustar vahur vahur Describehere ZWiki/wikis/zwikidotorg/ 0040775 0000764 0000764 00000000000 10000524010 014645 5 ustar vahur vahur ZWiki/wikis/zwikidotorg/AnnoyingQuote 0100775 0000764 0000764 00000000272 07644512336 017425 0 ustar vahur vahur #parents:FrontPage You can change the header quote by editing the last line of this page (or adding a comment). Don't add a final newline. Welcome to zwiki. Edit AnnoyingQuote to change ZWiki/wikis/zwikidotorg/FrontPage 0100775 0000764 0000764 00000000167 07644512336 016515 0 ustar vahur vahur **Welcome!** This is ZWiki's "zwikidotorg" wiki template. For more information, see: HelpPage, RecentChanges, ZWiki ZWiki/wikis/zwikidotorg/HelpPage 0100775 0000764 0000764 00000005253 07644512336 016316 0 ustar vahur vahur #parents:ZWiki What is this ? This is a kind of collaborative website, called a *wiki*. "Wiki wiki" means "quick" in hawaiian. You can edit any page on the spot, by clicking "Edit this page" or by using the "Add a comment" form. Several simple conventions are followed to make writing and linking easier. Here is the main one: each page has a WikiName - a short descriptive name made up of one or more capitalized words run together. These are used to form automatic hyperlinks between pages. How do I navigate ? To return to the site's front page at any time, click the logo at top left. To see a site map centered on the current page, click "contents" above. To see a list of pages which link to this one, click the page title. To jump to a page by name, or to search the site, enter text in the search field at top right and press enter. To find out more, see JumpSearch. To see a list of all pages in the site by modification date, visit RecentChanges. More about editing To add some text to any page, scroll to the bottom, enter some text into the "Add a comment" form and click the button. SandBox is a good place to practice this. Or, to make a more complex change, click the "Edit this page" link, edit the text and click the "Change.." button. Text will be formatted for you automatically. To begin with, just remember to use a blank line when you want to start a new paragraph. When you are ready, learn more about the TextFormattingRules. NB: site admins can help undo any editing mistakes & recover old versions if needed. How do I link and create new pages ? To create a link to another wiki page, just write it's WikiName. If the page does not yet exist, a ? will be displayed - click this when you are ready to create the new page. This is how pages are added to the site. To link to a page on another site, just type the url. Or, you can use HTML. Also, RemoteWikiLinks are a convenient notation for linking to pages on another site. What if I make a mistake ? If some good text gets lost by mistake, you might find it by backing up in your browser - copy and paste back it into the page. Otherwise, the site admin may be able to help undo the changes. Page hierarchy Zwiki keeps track of parent-child relationships between pages, which you may use or ignore; this information will be displayed at the top of the page if enabled in UserOptions. The parent of a new page is the page on which you clicked the ? link. You can change the parent(s) later, by clicking on the page title. For more help.. There are some additional features not described here.. see also: ZWiki, ZWiki:HelpPage, ZWiki:AdvancedEditOptions ZWiki/wikis/zwikidotorg/JumpSearch 0100775 0000764 0000764 00000000752 07644512336 016671 0 ustar vahur vahur #page_type:htmldtml #parents:HelpPage To jump directly to a page, enter the first few letters of its name in the JumpSearch field (usually at top right) and press enter. If more than one page matches, the alphabetically first is chosen. If no matches are found or if the expression begins with !, a search of all pages is done instead. This is case-insensitive and spaces are preserved. To list all pages alphabetically, leave the expression blank. See also RecentChanges, HowDoINavigate ZWiki/wikis/zwikidotorg/RecentChanges 0100775 0000764 0000764 00000001577 07644512336 017347 0 ustar vahur vahur #page_type:htmldtml #parents:HelpPage All pages in this wiki web, most recently changed at the top.
Page Size Last modified By [ ] not so important (
pages) ZWiki/wikis/zwikidotorg/RemoteWikiLinks 0100775 0000764 0000764 00000000415 07644512336 017704 0 ustar vahur vahur #parents:HelpPage A notation for linking to pages on other wiki servers. Here's an example: ZWiki:FrontPage that is, two WikiName(s) separated by **:** . The first must be a local page containing a RemoteWikiURL, the second is the name of the page on the remote site. ZWiki/wikis/zwikidotorg/RemoteWikiURL 0100775 0000764 0000764 00000000372 07644512336 017270 0 ustar vahur vahur #parents:RemoteWikiLinks To support RemoteWikiLinks, we need a local page which describes the remote server. At minimum it should contain the RemoteWikiURL keyword followed by the URL on the same line, like this: RemoteWikiURL: http://www.zwiki.org/ ZWiki/wikis/zwikidotorg/SearchPage 0100775 0000764 0000764 00000004273 07644512336 016634 0 ustar vahur vahur #page_type:htmldtml #parents:JumpSearch ZWiki/wikis/zwikidotorg/StructuredTextRules 0100775 0000764 0000764 00000007043 07644512336 020654 0 ustar vahur vahur #parents:TextFormattingRules "Structured text is text that uses indentation and simple symbology to indicate the structure of a document. A structured string consists of a sequence of paragraphs separated by one or more blank lines. Each paragraph has a level which is defined as the minimum indentation of the paragraph. A paragraph is a sub-paragraph of another paragraph if the other paragraph is the last preceding paragraph that has a lower level. Special symbology is used to indicate special constructs: - A single-line paragraph whose immediately succeeding paragraphs are lower level is treated as a header. - A paragraph that begins with a '-', '*', or 'o' is treated as an unordered list (bullet) element. - A paragraph that begins with a sequence of digits followed by a white-space character is treated as an ordered list element. - A paragraph that begins with a sequence of sequences, where each sequence is a sequence of digits or a sequence of letters followed by a period, is treated as an ordered list element. - A paragraph with a first line that contains some text, followed by some white-space and '--' is treated as a descriptive list element. The leading text is treated as the element title. - Sub-paragraphs of a paragraph that ends in the word 'example' or the word 'examples', or '::' is treated as example code and is output as is:: this is slow.. re-implement in python? we are doing a jump blank expr - jump to front page blank expr - let the search list all skip jumping, just do a search search for an id beginning with expr found one - jump there can't find url_quote no ids matched - fall through and do a search - Text enclosed single quotes (with white-space to the left of the first quote and whitespace or puctuation to the right of the second quote) is treated as example code. For example: '<dtml-var foo>'. - Text surrounded by '*' characters (with white-space to the left of the first '*' and whitespace or puctuation to the right of the second '*') is *emphasized*. - Text surrounded by '**' characters (with white-space to the left of the first '**' and whitespace or puctuation to the right of the second '**') is made **strong**. - Text surrounded by '_' underscore characters (with whitespace to the left and whitespace or punctuation to the right) is made _underlined_. - Text encloded by double quotes followed by a colon, a URL, and concluded by punctuation plus white space, *or* just white space, is treated as a hyper link. For example, '"Zope":http://www.zope.org/' is interpreted as "Zope":http://www.zope.org/ *Note: This works for relative as well as absolute URLs.* - Text enclosed by double quotes followed by a comma, one or more spaces, an absolute URL and concluded by punctuation plus white space, or just white space, is treated as a hyper link. For example: '"mail me", mailto:amos@digicool.com' is interpreted as "mail me", mailto:amos@digicool.com - Text enclosed in brackets which consists only of letters, digits, underscores and dashes is treated as hyper links within the document. For example: '"As demonstrated by Smith [12] this technique ..."' Is interpreted as: "As demonstrated by Smith [12] this technique" Together with the next rule this allows easy coding of references or end notes. - Text enclosed in brackets which is preceded by the start of a line, two periods and a space is treated as a named link. For example: '.. [12] "Effective Techniques" Smith, Joe ...' Is interpreted as .. [12] "Effective Techniques" Smith, Joe ... *Note: see the <A NAME="12"> in the HTML source.* Together with the previous rule this allows easy coding of references or end notes." See also: TextFormattingRules ZWiki/wikis/zwikidotorg/TextFormattingRules 0100775 0000764 0000764 00000001156 07644512336 020621 0 ustar vahur vahur #parents:HelpPage Your text is formatted as a web page according to some simple markup rules. These are intended to be convenient and unobtrusive. The StructuredRextRules are used by default. Briefly - * separate paragraphs with a blank line * a single-line paragraph followed by a more-indented paragraph makes a heading * for bulleted/numbered lists, use * or 0. followed by a space * for emphasis, use * ... * and ** ... ** Also - * WikiName(s), bare urls and words enclosed in square brackets are converted to hyperlinks (prepend the link or the line with ! to prevent this) * HTML & DTML tags may be used ZWiki/wikis/zwikidotorg/UserOptions 0100775 0000764 0000764 00000022365 07644512336 017126 0 ustar vahur vahur #page_type:structuredtextdtml #parents:HelpPage
Foo Let's be clear about it: this page is one giant hack. Latest version at http://zwiki.org/UserOptions this form calls itself. if we are setting or clearing options, handle it now NB use path=wiki_base_url() if you want per-wiki options ! *Cookies configured.* *Cookies removed.*
Set your options below - or, choose one of these presets:
ZWiki/wikis/zwikidotorg/WikiName 0100775 0000764 0000764 00000000313 07644512336 016325 0 ustar vahur vahur #parents:HelpPage Wiki page names are two or more CapitalizedWords joined together without spaces. They are easy to read and write while still being distinctive enough to be recognized by the computer. ZWiki/wikis/zwikidotorg/ZWiki 0100775 0000764 0000764 00000000431 07644512336 015657 0 ustar vahur vahur #parents:FrontPage The software which drives this wiki web. For more documentation & discussion, see http://zwiki.org . ZWiki is inspired by the original WikiWikiWeb (http://c2.com/cgi/wiki) and the zope web application server (http://zope.org). RemoteWikiURL: http://zwiki.org/ ZWiki/wikis/zwikidotorg/backlinks 0100775 0000764 0000764 00000003733 07644512336 016573 0 ustar vahur vahur> Backlinks and Parents
">set ">
Other parent:
Backlinks
Here are the pages which link to
(backlinks). Click a page name to go there.