4 # wikiri - A single-file wiki engine.
5 # Alberto Bertogli (albertito@blitiri.com.ar)
8 # Configuration section
10 # You can edit these values, or create a file named "config.py" and put them
11 # there to make updating easier. The ones in config.py take precedence.
14 # Directory where entries are stored
17 # Path where templates are stored. Use an empty string for the built-in
18 # default templates. If they're not found, the built-in ones will be used.
19 templates_path
= "templates/"
21 # URL to the wiki, including the name. Can be a full URL or just the path.
22 wiki_url
= "/wiki/wikiri.cgi"
24 # Style sheet (CSS) URL. Can be relative or absolute. To use the built-in
25 # default, set it to wiki_url + "/style".
26 css_url
= wiki_url
+ "/style"
29 title
= "I like wikis"
31 # History backend. Can be one of:
32 # - "none" for no history
33 # - "git" to use git (needs git and the data path to be a repository)
34 # - "darcs" to use darcs (needs darcs and the data path to be a repository)
35 # - "auto" to select automatically (looks if the data path is a repository)
40 # End of configuration
41 # DO *NOT* EDIT ANYTHING PAST HERE
52 # Load the config file, if there is one
59 # Find out our URL, just in case the templates need it (the default templates
60 # do not use it at all)
62 n
= os
.environ
['SERVER_NAME']
63 p
= os
.environ
['SERVER_PORT']
64 s
= os
.environ
['SCRIPT_NAME']
67 full_url
= 'http://%s%s%s' % (n
, p
, s
)
69 full_url
= 'Not needed'
77 import docutils
.parsers
.rst
79 from docutils
.core
import publish_parts
81 def _wiki_link_role(role
, rawtext
, text
, lineno
, inliner
,
82 options
= {}, content
= []):
84 ref
= wiki_url
+ '/' + \
85 urllib
.quote_plus(text
.encode('utf8'), safe
= '')
87 node
= docutils
.nodes
.reference(rawtext
, text
, refuri
= ref
,
91 def content2html(content
):
93 'input_encoding': 'utf8',
94 'output_encoding': 'utf8',
97 docutils
.parsers
.rst
.roles
.register_canonical_role('wikilink',
99 docutils
.parsers
.rst
.roles
.DEFAULT_INTERPRETED_ROLE
= 'wikilink'
101 parts
= publish_parts(content
,
102 settings_overrides
= settings
,
103 writer_name
= "html")
104 return parts
['body'].encode('utf8')
107 from xml
.sax
.saxutils
import escape
109 s
= '<div class="diff">'
110 for l
in diff
.split('\n'):
112 if l
.startswith("+") and not l
.startswith("+++"):
114 elif l
.startswith("-") and not l
.startswith("---"):
116 elif l
.startswith(" "):
118 elif l
.startswith("@@"):
120 elif l
.startswith("diff"):
124 s
+= '<span class="%s">' % c
+ escape(l
) + '</span>\n'
125 # note there's no need to put <br/>s because the div.diff has
126 # "white-space: pre" in the css
138 default_article_content
= """\
141 This page does *not* exist yet.
144 default_main_header
= """
145 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
149 <link href="%(css_url)s" rel="stylesheet" type="text/css" />
150 <title>%(title)s</title>
155 <h1><a href="%(url)s">%(title)s</a></h1>
157 <div class="content">
160 default_main_footer
= """
168 default_article_header
= """
169 <div class="article">
170 <h2><a href="%(url)s/%(artqname)s">%(arttitle)s</a></h2>
171 <span class="artinfo">
172 updated on %(uyear)04d-%(umonth)02d-%(uday)02d
173 %(uhour)02d:%(uminute)02d<br/>
175 <div class="artbody">
178 default_article_footer
= """
183 <a href="%(url)s/about">about</a> |
184 <a href="%(url)s/help">help</a> |
185 <a href="%(url)s/%(artqname)s/log">view history</a> |
186 <a href="%(url)s/%(artqname)s/edit">edit this page</a><br/>
191 default_edit_page
= """
192 <h2>Edit <a href="%(url)s/%(artqname)s">%(artname)s</a></h2>
195 <div class="editpage">
196 <form method="POST" action="save">
198 <label for="newtitle">Title: </label>
199 <input id="newtitle" type="text" name="newtitle"
200 value="%(arttitle)s"/><p/>
202 <textarea name="newcontent" cols="76" rows="25">
206 <label for="comment">Change summary: </label>
207 <input id="comment" type="text" name="comment"
208 size="50" value="%(comment)s"/><p/>
210 <button name="submit" type="submit" value="submit">Save</button>
211 <button name="preview" type="submit" value="preview">Preview</button>
216 <div class="quickhelp">
220 <li>Paragraphs are split by an empty line.</li>
221 <li>Lines that are underlined with dashes ("----") are a title.</li>
222 <li>Lines beginning with an "*" form a list.</li>
223 <li>To create new links, put them between backticks, <tt>`like
228 <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">reST
229 quick reference</a>.<p/>
235 default_removed_page
= """
236 <div class="article">
237 <h2><a href="%(url)s/%(artqname)s">%(artname)s</a></h2>
238 <div class="artbody">
240 This page has been successfuly removed.<p/>
246 default_diff_header
= """
247 <h2>Modification to <a href="%(url)s/%(artqname)s">%(arttitle)s</a></h2>
249 This page displays the modifications performed by the selected change. Green
250 lines are additions, red lines are removals.<p/>
253 default_diff_footer
= """
256 <a href="%(url)s/about">about</a> |
257 <a href="%(url)s/help">help</a> |
258 <a href="%(url)s/%(artqname)s/log">view history</a> |
259 <a href="%(url)s/%(artqname)s/edit">edit this page</a><br/>
264 default_help_page
= """
265 <div class="article">
267 <div class="artbody">
270 This wiki uses <a href="http://docutils.sf.net/rst.html">reStructuredText</a>
272 Here is a quick syntax summary.<p/>
275 <li>Paragraphs are split by an empty line.</li>
276 <li>Lines that are underlined with dashes ("----") are a title.</li>
277 <li>Lines beginning with an "*" form a list.</li>
278 <li>To create new links, put them between backticks, <tt>`like
282 If you want more information, see the
283 <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">reST
284 quick reference</a>.<p/>
290 default_about_page
= """
291 <div class="article">
293 <div class="artbody">
296 This wiki is powered by
297 <a href="http://blitiri.com.ar/p/misc.html">wikiri</a>, a single-file
298 blog engine written in <a href="http://python.org">Python</a>, and uses
299 <a href="http://docutils.sf.net/rst.html">reStructuredText</a>
302 If you have any questions or comments, please send an email to
303 <a href="mailto:albertito@blitiri.com.ar">Alberto Bertogli</a>.<p/>
309 default_log_header
= """
310 <h2>Change history for <a href="%(url)s/%(artqname)s">%(artname)s</a></h2>
316 default_log_entry
= """
319 %(date_y)04d-%(date_m)02d-%(date_d)02d
320 %(date_H)02d:%(date_M)02d:%(date_S)02d
322 <td class="summary">%(summary)s</td>
324 <a href="%(url)s/logview/%(commitid)s/%(artqname)s">view</a> |
325 <a href="%(url)s/restore/%(commitid)s/%(artqname)s">restore</a> |
326 <a href="%(url)s/diff/%(commitid)s/%(artqname)s">diff</a>
331 default_log_footer
= """
338 font-family: sans-serif;
347 border-bottom: 2px solid #99F;
356 border-bottom: 1px solid #99C;
360 text-decoration: none;
369 text-decoration: none;
373 span.artinfo a:hover {
374 text-decoration: none;
387 /* hack to properly align the hr */
393 background-color: #99F;
402 text-decoration: none;
406 border-right: 1px solid #6666cc;
407 border-collapse: collapse;
412 border-left: 1px solid #6666cc;
421 table.log td.summary {
430 /* Articles are enclosed in <div class="section"> */
436 border-bottom: 1px dotted #99C;
444 border-bottom: 1px dotted #DDD;
449 font-family: monospace;
459 div.diff span.remove {
463 div.diff span.unchanged {
466 div.diff span.position {
467 background-color: #E5E5FF;
470 div.diff span.header {
471 background-color: #CCF;
475 div.diff span.other {
480 class Templates (object):
482 self
.tpath
= templates_path
483 now
= datetime
.datetime
.now()
495 def art_vars(self
, article
):
496 avars
= self
.vars.copy()
498 'arttitle': article
.title
,
499 'artname': article
.name
,
500 'artqname': article
.qname
,
501 'updated': article
.updated
.isoformat(' '),
503 'uyear': article
.updated
.year
,
504 'umonth': article
.updated
.month
,
505 'uday': article
.updated
.day
,
506 'uhour': article
.updated
.hour
,
507 'uminute': article
.updated
.minute
,
508 'usecond': article
.updated
.second
,
512 def get_main_header(self
):
513 p
= self
.tpath
+ '/header.html'
514 if os
.path
.isfile(p
):
515 return open(p
).read() % self
.vars
516 return default_main_header
% self
.vars
518 def get_default_article_content(self
, artname
):
519 avars
= self
.vars.copy()
522 'artqname': urllib
.quote_plus(artname
, safe
= ''),
524 p
= self
.tpath
+ '/default_article_content.wiki'
525 if os
.path
.isfile(p
):
526 return open(p
).read() % avars
527 return default_article_content
% avars
529 def get_main_footer(self
):
530 p
= self
.tpath
+ '/footer.html'
531 if os
.path
.isfile(p
):
532 return open(p
).read() % self
.vars
533 return default_main_footer
% self
.vars
535 def get_article_header(self
, article
):
536 avars
= self
.art_vars(article
)
537 p
= self
.tpath
+ '/art_header.html'
538 if os
.path
.isfile(p
):
539 return open(p
).read() % avars
540 return default_article_header
% avars
542 def get_article_footer(self
, article
):
543 avars
= self
.art_vars(article
)
544 p
= self
.tpath
+ '/art_footer.html'
545 if os
.path
.isfile(p
):
546 return open(p
).read() % avars
547 return default_article_footer
% avars
549 def get_edit_page(self
, article
, comment
):
550 avars
= self
.art_vars(article
)
552 'content': article
.raw_content
,
555 p
= self
.tpath
+ '/edit_page.html'
556 if os
.path
.isfile(p
):
557 return open(p
).read() % avars
558 return default_edit_page
% avars
560 def get_removed_page(self
, artname
):
561 avars
= self
.vars.copy()
564 'artqname': urllib
.quote_plus(artname
, safe
= ''),
566 p
= self
.tpath
+ '/removed_page.html'
567 if os
.path
.isfile(p
):
568 return open(p
).read() % avars
569 return default_removed_page
% avars
571 def get_diff_header(self
, article
, commitid
):
572 avars
= self
.art_vars(article
)
574 'commitid': commitid
,
576 p
= self
.tpath
+ '/diff_header.html'
577 if os
.path
.isfile(p
):
578 return open(p
).read() % avars
579 return default_diff_header
% avars
581 def get_diff_footer(self
, article
, commitid
):
582 avars
= self
.art_vars(article
)
584 'commitid': commitid
,
586 p
= self
.tpath
+ '/diff_footer.html'
587 if os
.path
.isfile(p
):
588 return open(p
).read() % avars
589 return default_diff_footer
% avars
591 def get_help_page(self
):
592 p
= self
.tpath
+ '/help_page.html'
593 if os
.path
.isfile(p
):
594 return open(p
).read() % self
.vars
595 return default_help_page
% self
.vars
597 def get_about_page(self
):
598 p
= self
.tpath
+ '/about_page.html'
599 if os
.path
.isfile(p
):
600 return open(p
).read() % self
.vars
601 return default_about_page
% self
.vars
603 def get_log_header(self
, artname
):
604 avars
= self
.vars.copy()
607 'artqname': urllib
.quote_plus(artname
, safe
= ''),
609 p
= self
.tpath
+ '/log_header.html'
610 if os
.path
.isfile(p
):
611 return open(p
).read() % avars
612 return default_log_header
% avars
614 def get_log_entry(self
, artname
, commit
):
615 avars
= self
.vars.copy()
618 'artqname': urllib
.quote_plus(artname
, safe
= ''),
619 'summary': commit
['msg'],
620 'commitid': commit
['commit'],
621 'date_y': commit
['atime'].year
,
622 'date_m': commit
['atime'].month
,
623 'date_d': commit
['atime'].day
,
624 'date_H': commit
['atime'].hour
,
625 'date_M': commit
['atime'].minute
,
626 'date_S': commit
['atime'].second
,
628 p
= self
.tpath
+ '/log_entry.html'
629 if os
.path
.isfile(p
):
630 return open(p
).read() % avars
631 return default_log_entry
% avars
633 def get_log_footer(self
, artname
):
634 avars
= self
.vars.copy()
637 'artqname': urllib
.quote_plus(artname
, safe
= ''),
639 p
= self
.tpath
+ '/log_footer.html'
640 if os
.path
.isfile(p
):
641 return open(p
).read() % avars
642 return default_log_footer
% avars
650 class Article (object):
651 def __init__(self
, name
, title
= None, content
= None,
654 self
.qname
= urllib
.quote_plus(name
, safe
= "")
659 self
.preloaded_title
= title
660 self
.preloaded_content
= content
661 self
.has_header
= has_header
665 self
._raw
_content
= ''
667 def get_raw_content(self
):
670 return self
._raw
_content
671 raw_content
= property(fget
= get_raw_content
)
676 # use the name by default
677 return self
.attrs
.get('title', self
.name
)
678 title
= property(fget
= get_title
)
682 if self
.preloaded_content
:
683 raw
= self
.preloaded_content
684 raw
= [ s
+ '\n' for s
in raw
.split('\n') ]
685 self
.updated
= datetime
.datetime
.now()
687 fd
= open(data_path
+ '/' + self
.qname
)
689 stat
= os
.fstat(fd
.fileno())
690 self
.updated
= datetime
.datetime
.fromtimestamp(stat
.st_mtime
)
693 raw
= t
.get_default_article_content(self
.name
)
694 raw
= [ s
+ '\n' for s
in raw
.split('\n') ]
695 self
.updated
= datetime
.datetime
.now()
701 name
, value
= l
.split(':', 1)
702 name
= name
.lower().strip()
703 self
.attrs
[name
] = value
.strip()
706 elif l
== '\n' or l
== '\r\n':
711 self
._raw
_content
= ''.join(raw
[hdr_lines
:])
714 if self
.preloaded_title
:
715 self
.attrs
['title'] = self
.preloaded_title
717 def save(self
, newtitle
, newcontent
, raw
= False):
718 fd
= open(data_path
+ '/' + self
.qname
, 'w+')
722 fd
.write('title: %s\n\n' % newtitle
)
723 fd
.write(newcontent
.rstrip() + '\n')
726 # invalidate our information
731 os
.unlink(data_path
+ '/' + self
.qname
)
732 except OSError, errno
.ENOENT
:
736 return content2html(self
.raw_content
)
742 # At the moment we support none, git and darcs
744 class HistoryError (Exception):
749 if history
== 'auto':
750 if os
.path
.isdir(data_path
+ '.git'):
751 self
.be
= GitBackend(data_path
)
752 elif os
.path
.isdir(data_path
+ '_darcs'):
753 self
.be
= DarcsBackend(data_path
)
755 self
.be
= NoneBackend(data_path
)
756 elif history
== 'git':
757 self
.be
= GitBackend(data_path
)
758 elif history
== 'darcs':
759 self
.be
= DarcsBackend(data_path
)
761 self
.be
= NoneBackend(data_path
)
763 def commit(self
, msg
, author
= 'Wikiri <somebody@wikiri>'):
764 self
.be
.commit(msg
, author
= author
)
766 def log(self
, fname
):
767 # log() yields commits of the form:
769 # 'commit': commit id
770 # 'author': author of the commit
771 # 'committer': committer
772 # 'atime': time of the change itself
773 # 'ctime': time of the commit
774 # 'msg': commit msg (one line)
776 return self
.be
.log(file = fname
)
778 def add(self
, *files
):
779 return self
.be
.add(*files
)
781 def remove(self
, *files
):
782 return self
.be
.remove(*files
)
784 def get_content(self
, fname
, cid
):
785 return self
.be
.get_content(fname
, cid
)
787 def get_commit(self
, cid
):
788 return self
.be
.get_commit(cid
)
790 def get_diff(self
, cid
):
791 # get_diff() returns the diff in unified format
792 return self
.be
.get_diff(cid
)
795 def __init__(self
, repopath
):
798 def commit(self
, *args
, **kwargs
):
801 def log(self
, *args
, **kwargs
):
804 def add(self
, *files
):
807 def remove(self
, *files
):
810 def get_content(self
, fname
, cid
):
811 return "Not supported."
813 def get_commit(self
, cid
):
814 return { 'commit': cid
}
816 def get_diff(self
, cid
):
820 def __init__(self
, repopath
):
825 self
.prevdir
= os
.getcwd()
829 os
.chdir(self
.prevdir
)
831 def git(self
, *args
):
832 # delay the import to avoid the hit on a regular page view
835 cmd
= subprocess
.Popen( ['git'] + list(args
),
836 stdin
= subprocess
.PIPE
,
837 stdout
= subprocess
.PIPE
,
838 stderr
= sys
.stderr
)
842 def gitq(self
, *args
):
843 cmd
= self
.git(*args
)
844 stdout
, stderr
= cmd
.communicate()
845 return cmd
.returncode
847 def commit(self
, msg
, author
= None):
849 author
= "Unknown <unknown@example.com>"
851 # see if we have something to commit; if not, just return
852 self
.gitq('update-index', '--refresh')
853 r
= self
.gitq('diff-index', '--exit-code', '--quiet', 'HEAD')
857 r
= self
.gitq('commit',
861 raise HistoryError
, r
863 def log(self
, file = None, files
= None):
869 cmd
= self
.git("rev-list",
870 "--all", "--pretty=raw",
871 "HEAD", "--", *files
)
874 commit
= { 'msg': '' }
876 l
= cmd
.stdout
.readline()
878 if l
!= '\n' and not l
.startswith(' '):
879 name
, value
= l
[:-1].split(' ', 1)
883 # the previous commit has ended
886 commit
= { 'msg': '' }
889 # continue reusing the line
894 if l
.startswith(' '):
898 l
= cmd
.stdout
.readline()
900 # the last commit, if there is one
906 if cmd
.returncode
!= 0:
907 raise HistoryError
, cmd
.returncode
909 def add(self
, *files
):
910 r
= self
.gitq('add', "--", *files
)
912 raise HistoryError
, r
914 def remove(self
, *files
):
915 r
= self
.gitq('rm', '-f', '--', *files
)
917 raise HistoryError
, r
919 def get_content(self
, fname
, commitid
):
920 cmd
= self
.git("show", "%s:%s" % (commitid
, fname
))
921 content
= cmd
.stdout
.read()
925 def get_commit(self
, cid
):
926 cmd
= self
.git("rev-list", "-n1", "--pretty=raw", cid
)
927 out
= cmd
.stdout
.readlines()
929 commit
= { 'msg': '' }
931 if l
!= '\n' and not l
.startswith(' '):
932 name
, value
= l
[:-1].split(' ', 1)
939 def get_diff(self
, cid
):
940 cmd
= self
.git("diff", cid
+ "^.." + cid
)
941 out
= cmd
.stdout
.read()
945 def _add_times(commit
):
946 if 'author' in commit
:
947 author
, epoch
, tz
= commit
['author'].rsplit(' ', 2)
949 commit
['author'] = author
950 commit
['atime'] = datetime
.datetime
.fromtimestamp(epoch
)
952 if 'committer' in commit
:
953 committer
, epoch
, tz
= commit
['committer'].rsplit(' ', 2)
955 commit
['committer'] = committer
956 commit
['ctime'] = datetime
.datetime
.fromtimestamp(epoch
)
959 def __init__(self
, repopath
):
964 self
.prevdir
= os
.getcwd()
968 os
.chdir(self
.prevdir
)
970 def darcs(self
, *args
):
971 # delay the import to avoid the hit on a regular page view
974 cmd
= subprocess
.Popen( ['darcs'] + list(args
),
975 stdin
= subprocess
.PIPE
,
976 stdout
= subprocess
.PIPE
,
977 stderr
= sys
.stderr
)
981 def darcsq(self
, *args
):
982 cmd
= self
.darcs(*args
)
983 stdout
, stderr
= cmd
.communicate()
984 return cmd
.returncode
986 def commit(self
, msg
, author
= None):
988 author
= "Unknown <unknown@example.com>"
990 # see if we have something to commit; if not, just return
991 if self
.darcsq('whatsnew') != 0:
994 r
= self
.darcsq('record',
999 raise HistoryError
, r
1001 def log(self
, file = None, files
= None):
1002 import xml
.dom
.minidom
as minidom
1009 cmd
= self
.darcs("changes",
1014 xml
= minidom
.parse(cmd
.stdout
)
1017 if cmd
.returncode
!= 0:
1018 raise HistoryError
, cmd
.returncode
1020 for p
in xml
.getElementsByTagName('patch'):
1021 # ignore patches not directly inside <changelog> (they
1022 # can be, for instance, inside <created_as>, which we
1024 if p
.parentNode
.nodeName
!= 'changelog':
1027 cid
= p
.getAttribute("hash")
1028 author
= p
.getAttribute('author')
1029 atime
= p
.getAttribute('date')
1030 atime
= datetime
.datetime
.strptime(atime
,
1032 msg
= p
.getElementsByTagName('name')[0]
1033 msg
= msg
.childNodes
[0].data
1034 msg
= msg
.split('\n')[0].strip()
1037 'commit': cid
.encode('utf8'),
1038 'author': author
.encode('utf8'),
1039 'committer': author
.encode('utf8'),
1042 'msg': msg
.encode('utf8'),
1046 def add(self
, *files
):
1047 r
= self
.darcsq('add', "--", *files
)
1048 # 0 means success, 2 means the file was already there (which
1049 # is ok because we always add the files)
1050 if r
!= 0 and r
!= 2:
1051 raise HistoryError
, r
1053 def remove(self
, *files
):
1054 r
= self
.darcsq('remove', '--', *files
)
1056 raise HistoryError
, r
1058 def get_content(self
, fname
, commitid
):
1059 cmd
= self
.darcs("show", "contents",
1060 "--match", "hash %s" % commitid
,
1062 content
= cmd
.stdout
.read()
1066 def get_commit(self
, cid
):
1067 import xml
.dom
.minidom
as minidom
1069 cmd
= self
.darcs("changes",
1071 "--match", "hash %s" % cid
)
1074 xml
= minidom
.parse(cmd
.stdout
)
1077 if cmd
.returncode
!= 0:
1078 raise HistoryError
, cmd
.returncode
1081 p
= xml
.getElementsByTagName('patch')[0]
1083 raise HistoryError
, "not such patch"
1085 cid
= p
.getAttribute("hash")
1086 author
= p
.getAttribute('author')
1087 atime
= p
.getAttribute('date')
1088 atime
= datetime
.datetime
.strptime(atime
,
1090 msg
= p
.getElementsByTagName('name')[0]
1091 msg
= msg
.childNodes
[0].data
1092 msg
= msg
.split('\n')[0].strip()
1095 'commit': cid
.encode('utf8'),
1096 'author': author
.encode('utf8'),
1097 'committer': author
.encode('utf8'),
1100 'msg': msg
.encode('utf8'),
1104 def get_diff(self
, cid
):
1114 def render_article(art
):
1115 template
= Templates()
1116 print 'Content-type: text/html; charset=utf-8\n'
1117 print template
.get_main_header()
1118 print template
.get_article_header(art
)
1120 print template
.get_article_footer(art
)
1121 print template
.get_main_footer()
1123 def render_edit(art
, preview
= False, comment
= ""):
1124 template
= Templates()
1125 print 'Content-type: text/html; charset=utf-8\n'
1126 print template
.get_main_header()
1128 print template
.get_article_header(art
)
1130 print template
.get_article_footer(art
)
1131 print template
.get_edit_page(art
, comment
)
1132 print template
.get_main_footer()
1134 def render_removed(artname
):
1135 template
= Templates()
1136 print 'Content-type: text/html; charset=utf-8\n'
1137 print template
.get_main_header()
1138 print template
.get_removed_page(artname
)
1139 print template
.get_main_footer()
1141 def render_log(artname
, log
):
1142 template
= Templates()
1143 print 'Content-type: text/html; charset=utf-8\n'
1144 print template
.get_main_header()
1145 print template
.get_log_header(artname
)
1147 print template
.get_log_entry(artname
, commit
)
1148 print template
.get_log_footer(artname
)
1149 print template
.get_main_footer()
1151 def render_diff(article
, cid
, diff
):
1152 template
= Templates()
1153 print 'Content-type: text/html; charset=utf-8\n'
1154 print template
.get_main_header()
1155 print template
.get_diff_header(article
, cid
)
1156 print diff2html(diff
)
1157 print template
.get_diff_footer(article
, cid
)
1158 print template
.get_main_footer()
1161 template
= Templates()
1162 print 'Content-type: text/html; charset=utf-8\n'
1163 print template
.get_main_header()
1164 print template
.get_help_page()
1165 print template
.get_main_footer()
1168 template
= Templates()
1169 print 'Content-type: text/html; charset=utf-8\n'
1170 print template
.get_main_header()
1171 print template
.get_about_page()
1172 print template
.get_main_footer()
1175 print 'Content-type: text/css\n'
1178 def redirect(artname
):
1179 print 'Status: 303 See Other\r\n',
1180 print 'Location: %s/%s\r\n' % (wiki_url
, artname
),
1184 import cgitb
; cgitb
.enable()
1186 form
= cgi
.FieldStorage()
1193 newcontent
= form
.getfirst("newcontent", '')
1194 newtitle
= form
.getfirst("newtitle", '').strip()
1195 preview
= form
.getfirst("preview", '').strip()
1196 comment
= form
.getfirst("comment", '').strip()
1198 remoteip
= os
.environ
.get("REMOTE_ADDR", "unknownip")
1199 author
= "Somebody <%s@wikiri>" % remoteip
1201 if os
.environ
.has_key('PATH_INFO'):
1202 path_info
= os
.environ
['PATH_INFO']
1203 path_info
= os
.path
.normpath(path_info
)
1205 edit
= path_info
.endswith('/edit')
1206 save
= path_info
.endswith('/save')
1207 log
= path_info
.endswith('/log')
1209 if edit
or save
or log
:
1210 artname
= path_info
[1:].rsplit('/', 1)[0]
1212 artname
= path_info
[1:]
1214 if artname
== '' or artname
== '/':
1217 if save
and not os
.environ
.get('REQUEST_METHOD', 'GET') == 'POST':
1218 # only allow saves if the request is a post to prevent people
1219 # from accidentally performing a GET .../save, which would
1220 # result in an empty save, with the following page removal
1223 artname
= urllib
.unquote_plus(artname
)
1225 if artname
== 'style':
1227 elif artname
== 'help':
1229 elif artname
== 'about':
1231 elif edit
or (save
and preview
):
1233 art
= Article(artname
,
1235 content
= newcontent
,
1237 render_edit(art
, preview
= True, comment
= comment
)
1239 render_edit(Article(artname
))
1242 comment
= "No comment"
1244 a
= Article(artname
)
1245 if newcontent
.strip():
1246 a
.save(newtitle
, newcontent
)
1248 h
.commit(msg
= comment
, author
= author
)
1253 h
.commit(msg
= comment
, author
= author
)
1256 a
= Article(artname
)
1257 render_log(a
.name
, History().log(a
.qname
))
1258 elif artname
.startswith("logview/"):
1259 unused
, cid
, artname
= artname
.split('/', 2)
1260 artname
= urllib
.unquote_plus(artname
)
1261 oldcontent
= History().get_content(Article(artname
).qname
, cid
)
1262 render_article(Article(artname
, content
= oldcontent
))
1263 elif artname
.startswith("restore/"):
1264 unused
, cid
, artname
= artname
.split('/', 2)
1265 artname
= urllib
.unquote_plus(artname
)
1268 a
= Article(artname
)
1269 oldcontent
= h
.get_content(a
.qname
, cid
)
1271 a
.save(None, oldcontent
, raw
= True)
1273 ctime
= h
.get_commit(cid
)['atime'].strftime("%Y-%m-%d %H:%M:%S")
1274 h
.commit(msg
= 'Restored ' + ctime
, author
= author
)
1277 elif artname
.startswith("diff/"):
1278 unused
, cid
, artname
= artname
.split('/', 2)
1279 artname
= urllib
.unquote_plus(artname
)
1280 diff
= History().get_diff(cid
)
1281 render_diff(Article(artname
), cid
, diff
)
1283 render_article(Article(artname
))
1287 print "This is a CGI application."
1288 print "It only runs inside a web server."
1292 if os
.environ
.has_key('GATEWAY_INTERFACE'):
1295 sys
.exit(handle_cmd())