X-Git-Url: https://codewiz.org/gitweb?p=geekigeeki.git;a=blobdiff_plain;f=geekigeeki.py;h=c5b51096a1fbecfe242b189b8d84625b152cdb65;hp=b52d3522c88d0b5b11a5e6c46463b70a1f92ca52;hb=99fb949cf7923eaab08664706287c120b1a93d79;hpb=84d4dd63eb79f68bec2e6172a9c701575b502243 diff --git a/geekigeeki.py b/geekigeeki.py index b52d352..c5b5109 100755 --- a/geekigeeki.py +++ b/geekigeeki.py @@ -18,20 +18,22 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__version__ = '$Id$'[4:12] +__version__ = '4.0-' + '$Id$'[4:11] -from time import clock +from time import clock, localtime, gmtime, strftime start_time = clock() title_done = False import cgi, sys, os, re, errno, stat +image_ext = 'png|gif|jpg|jpeg|bmp|ico' +video_ext = "ogg|ogv|oga" # Not supported by Firefox 3.5: mkv|mpg|mpeg|mp4|avi|asf|flv|wmv|qt +image_re = re.compile(r".*\.(" + image_ext + "|" + video_ext + ")", re.IGNORECASE) +video_re = re.compile(r".*\.(" + video_ext + ")", re.IGNORECASE) # FIXME: we accept stuff like foo/../bar and we shouldn't -file_re = re.compile(r"^\b([A-Za-z0-9_\-][A-Za-z0-9_\.\-/]*)\b$") -img_re = re.compile(r"^.*\.(png|gif|jpg|jpeg|bmp|ico|ogm|ogg|mkv|mpg|mpeg|mp4|avi|asf|flv|wmv|qt)$", re.IGNORECASE) -video_re = re.compile(r"^.*\.(ogm|ogg|mkv|mpg|mpeg|mp4|avi|asf|flv|wmv|qt)$", re.IGNORECASE) -url_re = re.compile(r"^[a-z]{3,8}://[^\s'\"]+\S$") -ext_re = re.compile(r"\.([^\./]+)$") +file_re = re.compile(r"([A-Za-z0-9_\-][A-Za-z0-9_\.\-/]*)") +url_re = re.compile(r"[a-z]{3,8}://[^\s'\"]+\S") +ext_re = re.compile(r"\.([^\./]+)$") # CGI stuff --------------------------------------------------------- def script_name(): @@ -79,7 +81,7 @@ def permalink(s): return re.sub(' ', '-', re.sub('[^a-z0-9_ ]', '', s.lower()).strip()) def humanlink(s): - return re.search('([^:/\.]+)(?:\.[^/:]+|)$', s).group(1).replace('_', ' ') + return re.sub(r'(?:.*[/:]|)([^:/\.]+)(?:\.[^/:]+|)$', r'\1', s.replace('_', ' ')) # Split arg lists like "blah| blah blah| width=100 | align = center", # return a list containing anonymous arguments and a map containing the named arguments @@ -87,10 +89,10 @@ def parse_args(s): args = [] kwargs = {} for arg in s.strip('<[{}]>').split('|'): - try: - key, val = arg.split('=', 1) - kwargs[key.strip()] = val.strip() - except ValueError: + m = re.match('\s*(\w+)\s*=\s*(.+)\s*', arg) + if m is not None: + kwargs[m.group(1)] = m.group(2) + else: args.append(arg.strip()) return (args, kwargs) @@ -103,7 +105,9 @@ def url_args(kvargs): return '' # Formatting stuff -------------------------------------------------- -def emit_header(mime_type="text/html"): +def emit_header(mtime=None, mime_type="text/html"): + if mtime: + print("Last-Modified: " + strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime(mtime))) print("Content-type: " + mime_type + "; charset=utf-8\n") def send_guru(msg_text, msg_type): @@ -117,12 +121,12 @@ def send_guru(msg_text, msg_type): print('' \ % relative_url('sys/GuruMeditation.js')) -def send_title(name, text="Limbo", msg_text=None, msg_type='error', writable=False): +def send_title(name, text="Limbo", msg_text=None, msg_type='error', writable=False, mtime=None): global title_done if title_done: return # Head - emit_header() + emit_header(mtime) print('') print('') @@ -173,7 +177,7 @@ def send_title(name, text="Limbo", msg_text=None, msg_type='error', writable=Fal print(' | Page History') if name: - print(' | ' + link_tag('?raw=' + name, 'Raw Text', cssclass='navlink')) + print(' | ' + link_tag(name + '?a=raw', 'Raw Text', cssclass='navlink')) if privileged_url is not None: if writable: print(' | ' + link_tag('?a=edit&q=' + name, 'Edit', cssclass='navlink', privileged=True)) @@ -197,7 +201,7 @@ def send_httperror(status="403 Not Found", query=""): def link_tag(dest, text=None, privileged=False, **kvargs): if text is None: text = humanlink(dest) - elif img_re.match(text): + elif image_re.match(text): text = '' + text + '' link_class = kvargs.get('class', kvargs.get('cssclass', None)) @@ -222,7 +226,7 @@ def link_inline(name, descr=None, kvargs={}): url = relative_url(name) if video_re.match(name): return '' % url - elif img_re.match(name): + elif image_re.match(name): return '%s' % (url, url + url_args(kvargs), descr) elif file_re.match(name) and not ext_re.search(name): # FIXME: this guesses a wiki page return Page(name).send_naked() @@ -324,26 +328,27 @@ def make_index_key(): links = ['%s' % (ch, ch) for ch in 'abcdefghijklmnopqrstuvwxyz'] return '

' + ' | '.join(links) + '

' -def page_list(dirname = None, re = None): +def page_list(dirname=None, re=None): if re is None: # FIXME: WikiWord is too restrictive now! re = re.compile(r"^\b((([A-Z][a-z0-9]+){2,}/)*([A-Z][a-z0-9]+){2,})\b$") return sorted(filter(re.match, os.listdir(dirname or data_dir))) -def send_footer(mod_string=None): +def send_footer(mtime=None): if globals().get('debug_cgi', False): cgi.print_arguments() cgi.print_form(form) cgi.print_environ() + #FIXME link_inline("sys/footer") print(''' ') class WikiFormatter: @@ -365,6 +370,7 @@ class WikiFormatter: "**": ["b", False], "##": ["tt", False], "__": ["u", False], + "--": ["del", False], "^^": ["sup", False], ",,": ["sub", False], "''": ["em", False], # LEGACY @@ -377,6 +383,9 @@ class WikiFormatter: style[1] = not style[1] return ['' + def _glyph_repl(self, word): + return '—' + def _tit_repl(self, word): if self.h_level: result = '

\n' % self.h_level @@ -532,29 +541,30 @@ class WikiFormatter: print('

') scan_re = re.compile(r"""(?: - # Styles and formatting - (?P \*\*|'''|//|''|\#\#|``|__|\^\^|,,) + # Styles and formatting ("--" must cling to a word to disambiguate it from the dash) + (?P \*\* | // | \#\# | __ | --\b | \b-- | \^\^ | ,, | ''' | '' | `` ) | (?P \={2,6}) | (?P
\\\\) | (?P ^-{3,}) | (?P \b( FIXME | TODO | DONE )\b ) + | (?P --) # Links | (?P \<\<([^\s\|\>]+)(?:\s*\|\s*([^\>]+)|)\>\>) | (?P \[\[([^\s\|]+)(?:\s*\|\s*([^\]]+)|)\]\]) # Inline HTML - | (?P <(br|hr|div|span|form|iframe|input|textarea|a|img|h[1-5])\b ) + | (?P <(br|hr|div|span|form|iframe|input|textarea|a|img|h[1-5])\b ) | (?P ( /\s*> | ) ) | (?P [<>&] ) # Auto links (LEGACY) - | (?P \b[a-zA-Z0-9_/-]+\.(png|gif|jpg|jpeg|bmp|ico|ogm|ogg|mkv|mpg|mpeg|mp4|avi|asf|flv|wmv|qt)) + | (?P \b[a-zA-Z0-9_/-]+\.(""" + image_ext + "|" + video_ext + r""")) | (?P \b(?:[A-Z][a-z]+){2,}\b) | (?P (http|https|ftp|mailto)\:[^\s'\"]+\S) | (?P [-\w._+]+\@[\w.-]+) - # Lists, divs, spans + # Lists, divs, spans and inline objects | (?P

  • ^\s+[\*\#]\s+) | (?P
       \{\{\{|\s*\}\}\})
                 | (?P   \{\{([^\s\|]+)(?:\s*\|\s*([^\]]+)|)\}\})
    @@ -619,22 +629,26 @@ class Page:
         def _tmp_filename(self):
             return os.path.join(data_dir, ('#' + self.page_name.replace('/','_') + '.' + str(os.getpid()) + '#'))
     
    -    def exists(self):
    +    def _mtime(self):
             try:
    -            os.stat(self._filename())
    -            return True
    -        except OSError as err:
    +            return os.stat(self._filename()).st_mtime
    +        except OSError, err:
                 if err.errno == errno.ENOENT:
    -                return False
    +                return None
                 raise err
     
    +    def exists(self):
    +        if self._mtime():
    +            return True
    +        return False
    +
         def get_raw_body(self, default=None):
             try:
                 return open(self._filename(), 'rb').read()
    -        except IOError as err:
    +        except IOError, err:
                 if err.errno == errno.ENOENT:
                     if default is None:
    -                    default = '//[[%s|Describe %s|action=edit]]//' % (self.page_name, self.page_name)
    +                    default = '//[[?a=edit&q=%s|Describe %s]]//' % (self.page_name, self.page_name)
                     return default
                 if err.errno == errno.EISDIR:
                     return self.format_dir()
    @@ -643,13 +657,13 @@ class Page:
         def format_dir(self):
             out = '== '
             pathname = ''
    -        for dirname in self.page_name.split('/'):
    +        for dirname in self.page_name.strip('/').split('/'):
                 pathname = (pathname + '/' + dirname) if pathname else dirname
                 out += '[[' + pathname + '|' + dirname + ']]/'
             out += ' ==\n'
      
             for filename in page_list(self._filename(), file_re):
    -            if img_re.match(filename):
    +            if image_re.match(filename):
                     if image_maxwidth:
                         maxwidth_arg = ' | maxwidth=' + str(image_maxwidth)
                     out += '{{' + self.page_name + '/' + filename + ' | ' + humanlink(filename) + maxwidth_arg + ' | class=thumbleft}}\n'
    @@ -669,7 +683,7 @@ class Page:
                             break
                         self._pragmas[m.group(1)] = m.group(2).strip()
                         #print "bernie: pragmas[" + m.group(1) + "] = " + m.group(2) + "
    \n" - except IOError as err: + except IOError, err: if err.errno != errno.ENOENT and err.errno != errno.EISDIR: raise er return self._pragmas @@ -712,19 +726,9 @@ class Page: link_urls += [ [ "stylesheet", value ] ] send_title(self.page_name, self.split_title(), - msg_text=self.msg_text, msg_type=self.msg_type, writable=self.can_write()) + msg_text=self.msg_text, msg_type=self.msg_type, writable=self.can_write(), mtime=self._mtime()) self.send_naked() - send_footer(self._last_modified()) - - def _last_modified(self): - try: - from time import localtime, strftime - modtime = localtime(os.stat(self._filename())[stat.ST_MTIME]) - except OSError as err: - if err.errno != errno.ENOENT: - raise err - return None - return strftime(datetime_fmt, modtime) + send_footer(mtime=self._mtime()) def send_editor(self, preview=None): send_title(None, 'Edit ' + self.split_title(), msg_text=self.msg_text, msg_type=self.msg_type) @@ -741,7 +745,7 @@ class Page: + ' from ' + cgi.escape(get_hostname(remote_host())) + '

    ')) print('
    ' % relative_url(self.page_name)) - print('' % (self.page_name)) + print('') print('
    ' % (self.page_name)) print('' \ % cgi.escape(preview or self.get_raw_body(default=''))) @@ -769,18 +773,17 @@ class Page: def send_raw(self, mimetype='text/plain', args=[]): if not self.can_read(): - send_title(None, msg_text='Read access denied by ACLs', msg_type='notice') + send_title(None, msg_text='Read access denied by ACLs', msg_type='notice', mtime=self._mtime()) return + emit_header(self._mtime(), mimetype) if 'maxwidth' in args: import subprocess - emit_header(mimetype) sys.stdout.flush() subprocess.check_call(['gm', 'convert', self._filename(), '-scale', args['maxwidth'].value + ' >', '-']) else: body = self.get_raw_body() - emit_header(mimetype) print(body) def _write_file(self, data): @@ -791,8 +794,11 @@ class Page: # Bad Bill! POSIX rename ought to replace. :-( try: os.remove(name) - except OSError as err: + except OSError, err: if err.errno != errno.ENOENT: raise err + path = os.path.split(name)[0] + if not os.path.exists(path): + os.makedirs(path) os.rename(tmp_filename, name) def save(self, newdata, changelog):