fix docutils-test cleanup before installing new version
[docutils/kirr.git] / sandbox / ianb / wiki / Wiki.py
blobb7cfb3eefa5d753d89db5eb53d1ac57b4e91f7cd
1 """
2 The Wiki module primarily exports the `WikiPage` class:
3 """
5 import os, re, time
6 from docutils import core, io
7 from docutils import readers
9 __all__ = ['WikiPage', 'allPages', 'recentPages',
10 'searchTitles', 'search', 'css']
12 ## All the Wiki pages will be kept in this directory:
13 pageDir = '/usr/home/ianb/w/pypaper/pages/'
15 class WikiPage(object):
16 """
17 WikiPage is a class to represent one page in a WikiWikiWeb [#]_.
18 The page may or may not yet exist -- that is, it may not yet
19 have content.
21 .. [#] http://c2.com/cgi-bin/wiki
23 It has the following properties and methods:
25 `html`:
26 A read-only property giving the HTML for the page.
27 If the page does not yet have content the text
28 ``"This page has not yet been created"`` is returned.
29 `text`:
30 The text for the page. To save new text, simply
31 assign to this property.
32 `title`:
33 The title of the page.
34 `name`:
35 The name of the page -- a canonical identifier.
36 Related to the title, but not necessarily the
37 same.
38 .. ignore: html
39 .. ignore: text
40 .. ignore: setText
41 .. ignore: title
43 """
45 def __init__(self, pageName):
46 """
47 Each page has a name, which is a unique identifier, for example
48 ``"FrontPage"``, which identifies the page in the URL and
49 for linking.
50 """
51 self.name = pageName
53 def basePath(self):
54 """
55 :Ignore: yes
56 Returns the base path (sans extension) for this page
57 """
58 return _basePath(self.name)
60 basePath = property(basePath)
62 def exists(self):
63 """Does this page have content yet?"""
64 return _exists(self.name)
66 def html(self):
67 """Returns text of HTML for page (HTML fragment only)"""
68 if self.exists():
69 html = open(self.basePath + ".html").read()
70 html = self._subWikiLinks(html)
71 return html
72 else:
73 return 'This page has not yet been created.'
75 html = property(html)
77 def preview(self, text):
78 """Returns an HTML preview of the text"""
79 return self._subWikiLinks(self._convertText(text))
81 _wikiLinkRE = re.compile(r'(<a [^>]* href=")!(.*?)("[^>]*>)(.*?)(</a>)',
82 re.I+re.S)
84 def _subWikiLinks(self, text):
85 return self._wikiLinkRE.sub(self._subLink, ' %s ' % text)
87 def _subLink(self, match):
88 if _exists(match.group(2)):
89 return match.group(1) + match.group(2) + match.group(3) + match.group(4) + match.group(5)
90 else:
91 return '<span class="nowiki">%s%s%s%s?%s</span>' \
92 % (match.group(4), match.group(1), match.group(2),
93 match.group(3), match.group(5))
95 def text(self):
96 """
97 The text of the page. ReStructuredText is used, though the
98 parsing is internal to the module. You can assign to this
99 property to save new text for the page.
101 if self.exists():
102 return open(self.basePath + ".txt").read()
103 else:
104 return ''
106 def setText(self, text):
107 """Sets the text for the page (and updates cached HTML at the
108 same time)"""
109 f = open(self.basePath + ".txt", 'w')
110 f.write(text)
111 f.close()
112 f = open(self.basePath + ".html", 'w')
113 f.write(self._convertText(text))
114 f.close()
116 def _convertText(self, text):
117 return self._cleanHTML(core.publish_string(
118 source=text,
119 reader=Reader(),
120 parser_name='restructuredtext',
121 writer_name='html'))
123 def _cleanHTML(self, html):
124 return html[html.find('<body>'):html.find('</body>')]
126 text = property(text, setText)
128 def searchMatches(self, text):
130 :Ignore: yes
132 return self.searchTitleMatches(text) \
133 or self.text().lower().find(text.lower()) != -1
135 def searchTitleMatches(self, text):
137 :Ignore: yes
139 return self.title().lower().find(text.lower()) != -1
141 def modifiedDate(self):
142 """Date modified (integer timestamp)"""
143 return os.stat(self.basePath + ".txt").st_mtime
145 modifiedDate = property(modifiedDate)
147 def modifiedDateText(self):
148 """Text representation of modified date"""
149 return time.strftime("%a %m/%d/%y", time.gmtime(self.modifiedDate()))
151 modifiedDateText = property(modifiedDateText)
153 def title(self):
154 """Page title"""
155 return self.name
157 title = property(title)
160 Methods for searching the wiki pages:
163 def allPages():
164 """All pages with content in the system"""
165 return [WikiPage(page[:-4])
166 for page in os.listdir(pageDir)
167 if page.endswith('.txt')]
169 def recentPages():
170 """All pages, sorted by date modified, most recent first"""
171 pages = allPages()
172 pages.sort(lambda a, b: cmp(b.modifiedDate(), a.modifiedDate()))
173 return pages
175 def searchTitles(text):
176 """Search page titles for ``text``, returning list of pages"""
177 return [page for page in allPages()
178 if page.searchTitleMatches(text)]
180 def search(text):
181 """Search titles and bodies of pages for ``text``, returning list
182 of pages"""
183 return [page for page in allPages()
184 if page.searchMatches(text)]
187 def _basePath(name):
188 return os.path.join(pageDir, name)
190 def _exists(name):
191 return os.path.exists(_basePath(name) + ".html")
194 There is one module global to be printed at the top of
195 every Wiki page:
197 `css`:
198 The HTML to put the proper CSS at the top of the page. This
199 should be put in the ``<head>`` section of the page.
202 try:
203 f = open('default.css')
204 except IOError:
205 css = ""
206 else:
207 css = '<style type="text/css">\n%s</style>\n' % f.read()
208 f.close()
210 ########################################
211 ## reST-specific stuff
212 ########################################
215 from docutils import nodes
216 from docutils.readers import standalone
217 from docutils.transforms import Transform
219 class WikiLinkResolver(nodes.SparseNodeVisitor):
220 ":Ignore: yes"
222 def visit_reference(self, node):
223 if node.resolved or not node.hasattr('refname'):
224 return
225 refname = node['refname']
226 node.resolved = 1
227 node['class'] = 'wiki'
228 # I put a ! here to distinguish Wiki links from other
229 # links -- Wiki links have to be fixed up at view time,
230 # to distinguish between dangling and resolved Wiki
231 # links.
232 node['refuri'] = '!' + refname
233 del node['refname']
235 class WikiLink(Transform):
236 ":Ignore: yes"
238 default_priority = 800
240 def apply(self):
241 visitor = WikiLinkResolver(self.document)
242 self.document.walk(visitor)
244 class Reader(standalone.Reader):
245 ":Ignore: yes"
247 supported = standalone.Reader.supported + ('wiki',)
249 def get_transforms(self):
250 return standalone.Reader.get_transforms(self) + [WikiLink]