Compare commits

...

46 Commits

Author SHA1 Message Date
Alex Schroeder
c7db436f00 umlaut.t: environment variable must be encoded
The environment variable must be in "bytes", ie. we need to call
utf8::encode.
2016-06-17 16:50:47 +02:00
Alex Schroeder
14c33c5a9a Remove extra test for $DataDir
We already call CreateDir($DataDir) in this sub.
2016-06-17 16:49:46 +02:00
Alex Schroeder
0bd2afe4e1 Added basic tests to check for UTF-8 in $DataDir
basic.t tests normal operations; umlaut-t tests normal operations when
$DataDir contains an (UTF8 encoded) character.
2016-06-17 14:37:01 +02:00
Alex Schroeder
0a9ff1b723 Skip pygmentize if the binary is not found 2016-06-17 10:52:35 +02:00
Alex Schroeder
2dab06f905 Tests rely on English output
Set environment variable to en_US.UTF-8.
2016-06-17 10:52:35 +02:00
Alex Schroeder
8c143393e3 test.pl: no warning about killing the server 2016-06-17 10:52:35 +02:00
Alex Schroeder
8255303d95 Make server.pl compatible with Alexine 2016-06-17 10:52:34 +02:00
Alex Schroeder
cb00e7e969 Prevent warning by using 127.0.0.1
Using localhost leads to a warning on my Debian Wheezy system.
2016-06-12 22:29:49 +02:00
Alex Schroeder
1b2fe0d713 A test for Mojolicious + Namespaces
Currently this fails if the namespace contains non-ASCII characters.
This failing test has been wrapped in a TODO.
2016-06-12 22:24:23 +02:00
Alex Schroeder
8e73f6f0dd Use /wiki in $ScriptName for Mojolicious test 2016-06-12 21:26:18 +02:00
Alex Schroeder
d3c7b45ad9 Simplify Mojolicious server setup
Make sure the wiki log is written into the data directory.
2016-06-12 21:25:28 +02:00
Alex Schroeder
fee15fd880 Simplify namespaces.pl initialization 2016-06-12 21:24:43 +02:00
Alex Schroeder
196b960b47 Add test for Mojolicious server 2016-06-12 17:32:20 +02:00
Alex Schroeder
241a88ef48 Move server starting code from atom.t to test.pl 2016-06-11 20:08:04 +02:00
Alex Schroeder
fdf0c2711b Added a test for stuff/server.pl 2016-06-11 14:15:36 +02:00
Alex Schroeder
239a95e683 Fix typo: substring → substr 2016-06-05 18:00:01 +02:00
Alex Schroeder
d3205d2425 INS is italic, not red 2016-05-28 21:09:55 +02:00
Alex Schroeder
681ba8068c Add Spanish national days 2016-05-28 13:58:57 +02:00
Alex Schroeder
d5429d276f load-lang.pl: Fix code and add test
If you're accept-language settings included something for which no file
exists (such as "de-ch"), then the test for the file would
succeed (testing for the existence of "$LoadLanguageDir/") even though
no actual file will be loaded. This is fixed. Also: implemented actual
testing.
2016-05-28 09:30:59 +02:00
Alex Schroeder
746b10be81 Some fixes for Oddmuse Mode (Emacs) 2016-05-28 09:23:36 +02:00
Alex Schroeder
b9aa27e406 How to replace all your pictures from Flickr with local copies 2016-05-13 17:11:48 +02:00
Alex Schroeder
00cf277156 How to replace all your pictures from Flickr with local copies 2016-05-13 17:10:28 +02:00
Alex Schroeder
75ce7d745e expire-bans.pl is new 2016-03-03 15:04:48 +01:00
Alex Schroeder
ee1bbca5c9 Don't use both $wiki and $1 2016-02-16 17:39:24 +01:00
Alex Schroeder
0effc86620 added delete.sh
I've been using this to clear out wikis on campaignwiki.org
2016-02-16 16:42:37 +01:00
Aleks-Daniel Jakimenko-Aleksejev
8a36970b24 static-copy.pl: Fix non-ascii links
It seems like it is operating on url-encoded strings, so all we have to
do is to decode it. This, however, does not mean that we should print
decoded strings. ‘href’ attribute still has to be encoded (or so it seems).
2016-02-02 20:22:06 +02:00
Alex Schroeder
8be87ede99 light.css: change summory not bold 2016-01-29 14:16:11 +01:00
Alex Schroeder
d61dd71627 New CSS für alexschroeder.ch 2016-01-17 22:29:51 +01:00
Alex Schroeder
755f742088 sidebar.t: writing more tests
Trying to find the problem I have on one of the sites.
2016-01-02 12:21:54 +01:00
Aleks-Daniel Jakimenko-Aleksejev
0107e41123 load-lang.pl: Add Estonian translation 2015-12-30 15:45:41 +02:00
Ain Laidoja
e58c8c2192 Estonian translation 2015-12-30 15:11:57 +02:00
Alex Schroeder
ee4518da9e release: clean up and checkout master 2015-12-19 12:01:04 +01:00
Alex Schroeder
64e7183896 stuff/release is new 2015-12-19 11:54:19 +01:00
Alex Schroeder
bd2715a35e DoSearch: Add link to actually DO the replacement 2015-12-16 18:19:33 +01:00
Alex Schroeder
986e4fc65f Replace: no pagination
The old code would only call the the function provided
if pagination indicated that it was OK to do so. Thus,
only the first ten pages got replaced. This has been
fixed and tests have been added.
2015-12-16 15:50:35 +01:00
Alex Schroeder
908cecffb9 atom.t: fix warning 2015-12-16 10:04:56 +01:00
IngoBelka
07226ae7a1 typo 2015-11-29 10:25:15 +01:00
IngoBelka
c90258ef4b supplementing the German transltion 2015-11-29 10:10:47 +01:00
Aleks-Daniel Jakimenko-Aleksejev
f500092a6a Alexine IRC bot 2015-11-19 23:25:18 +02:00
Alex Schroeder
d1f1f65c9b green.css: no dashed line between sister sites 2015-11-18 09:58:20 +01:00
Alex Schroeder
aae0cb6379 light.css: no bold summary for recent changes 2015-11-15 21:58:58 +01:00
Alex Schroeder
2b0a0d9a14 word-count.pl: fix regular expression for dates 2015-11-15 21:58:58 +01:00
Aleks-Daniel Jakimenko-Aleksejev
69fcb9646b Fix tests for default stylesheet link
Also, get rid of redundant “www”.
2015-11-03 03:21:27 +02:00
Aleks-Daniel Jakimenko-Aleksejev
a0bf615960 Alexine: some successful commits are major
If previous commit had some failing tests then Alexine will announce that on
the wiki. When the problem is solved we probably don't want to see scary
messages in Recent Changes, so next successful commit should be announced (it
should not be a minor edit).
2015-11-03 03:03:13 +02:00
Aleks-Daniel Jakimenko-Aleksejev
c024f553fd Default css should be retrieved over secure connection
Everything on oddmuse.org now redirects to https, which means that every wiki
that is using default style sheet requires two requests to get the css file.
2015-11-02 20:57:49 +02:00
Aleks-Daniel Jakimenko-Aleksejev
c97d6a576f wiki.css: We are no longer using these fonts
There is no need to define fonts that we do not use anyway.
2015-11-02 20:50:52 +02:00
38 changed files with 3312 additions and 224 deletions

View File

@@ -19,6 +19,10 @@ build:
clean:
rm -rf build
prove t/setup.pl
release:
perl stuff/release ~/oddmuse.org
build/wiki.pl: wiki.pl
perl -lne "s/(\\\$$q->a\({-href=>'http:\/\/www.oddmuse.org\/'}, 'Oddmuse'\))/\\\$$q->a({-href=>'http:\/\/git.savannah.gnu.org\/cgit\/oddmuse.git\/tag\/?id=$(VERSION_NO)'}, 'wiki.pl') . ' ($(VERSION_NO)), see ' . \$$1/; print" < $< > $@

14
contrib/campaignwiki/delete.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
if test -z "$2" -o ! -z "$3"; then
echo "Usage: delete.sh USERNAME WIKI"
exit 1
fi
username=$1
wiki=$2
for p in $(curl "https://campaignwiki.org/wiki/$wiki?action=index;raw=1"); do
echo "Deleting: $p"
curl -F frodo=1 -F "title=$p" -F text=DeletedPage -F summary=Deleted -F username="$username" "https://campaignwiki.org/wiki/$wiki"
sleep 5
done

131
contrib/no-flickr.pl Normal file
View File

@@ -0,0 +1,131 @@
#! /usr/bin/perl -w
# Copyright (C) 2005-2016 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
use Modern::Perl;
use LWP::UserAgent;
use utf8;
binmode(STDOUT, ":utf8");
my $ua = LWP::UserAgent->new;
sub url_encode {
my $str = shift;
return '' unless $str;
utf8::encode($str); # turn to byte string
my @letters = split(//, $str);
my %safe = map {$_ => 1} ('a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '_', '.', '!', '~', '*', "'", '(', ')', '#');
foreach my $letter (@letters) {
$letter = sprintf("%%%02x", ord($letter)) unless $safe{$letter};
}
return join('', @letters);
}
sub get_raw {
my $uri = shift;
my $response = $ua->get($uri);
return $response->content if $response->is_success;
}
sub get_wiki_page {
my ($wiki, $id, $password) = @_;
my $parameters = [
pwd => $password,
action => 'browse',
id => $id,
raw => 1,
];
my $response = $ua->post($wiki, $parameters);
return $response->decoded_content if $response->is_success;
die "Getting $id returned " . $response->status_line;
}
sub get_wiki_index {
my $wiki = shift;
my $parameters = [
search => "flickr.com",
context => 0,
raw => 1,
];
my $response = $ua->post($wiki, $parameters);
return $response->decoded_content if $response->is_success;
die "Getting the index returned " . $response->status_line;
}
sub post_wiki_page {
my ($wiki, $id, $username, $password, $text) = @_;
my $parameters = [
username => $username,
pwd => $password,
recent_edit => 'on',
text => $text,
title => $id,
];
my $response = $ua->post($wiki, $parameters);
die "Posting to $id returned " . $response->status_line unless $response->code == 302;
}
my %seen = ();
sub write_flickr {
my ($id, $flickr, $dir, $file) = @_;
say "Found $flickr";
warn "$file was seen before: " . $seen{$file} if $seen{$file};
die "$file contains unknown characters" if $file =~ /[^a-z0-9_.]/;
$seen{$file} = "$id used $flickr";
my $bytes = get_raw($flickr) or die("No data for $id");
open(my $fh, '>', "$dir/$file") or die "Cannot write $dir/$file";
binmode($fh);
print $fh $bytes;
close($fh);
}
sub convert_page {
my ($wiki, $pics, $dir, $username, $password, $id) = @_;
say $id;
my $text = get_wiki_page($wiki, $id, $password);
my $is_changed = 0;
while ($text =~ m!(https://[a-z0-9.]+.flickr.com/(?:[a-z0-9.]+/)?([a-z0-9_]+\.(?:jpg|png)))!) {
my $flickr = $1;
my $file = $2;
write_flickr($id, $flickr, $dir, $file);
$is_changed = 1;
my $re = quotemeta($flickr);
$text =~ s!$flickr!$pics/$file!g;
}
if ($is_changed) {
post_wiki_page($wiki, $id, $username, $password, $text);
} else {
# die "$id has no flickr matches?\n$text";
}
sleep(5);
}
sub convert_site {
my ($wiki, $pics, $dir, $username, $password) = @_;
my @ids = split(/\n/, get_wiki_index($wiki));
for my $id (@ids) {
convert_page($wiki, $pics, $dir, $username, $password, $id);
}
}
our $AdminPass;
do "/home/alex/password.pl";
convert_site('https://alexschroeder.ch/wiki',
'https://alexschroeder.ch/pics',
'/home/alex/alexschroeder.ch/pics',
'Alex Schroeder',
$AdminPass);

View File

@@ -38,9 +38,10 @@
;;; Code:
(eval-when-compile
(require 'cl)
(require 'sgml-mode)
(require 'skeleton))
'(progn
(require 'cl)
(require 'sgml-mode)
(require 'skeleton)))
(require 'goto-addr); URL regexp
(require 'info); link face
@@ -257,24 +258,6 @@ Example:
(defvar oddmuse-revision nil
"A variable to bind dynamically when calling `oddmuse-format-command'.")
(defun oddmuse-revision-put (wiki page rev)
"Store REV for WIKI and PAGE in `oddmuse-revisions'."
(let ((w (assoc wiki oddmuse-revisions)))
(unless w
(setq w (list wiki)
oddmuse-revisions (cons w oddmuse-revisions)))
(let ((p (assoc page w)))
(unless p
(setq p (list page))
(setcdr w (cons p (cdr w))))
(setcdr p rev))))
(defun oddmuse-revision-get (wiki page)
"Get revision for WIKI and PAGE in `oddmuse-revisions'."
(let ((w (assoc wiki oddmuse-revisions)))
(when w
(cdr (assoc page w)))))
;;; Helpers
(defsubst oddmuse-page-name (file)
@@ -300,7 +283,7 @@ Example:
(defun oddmuse-url (wiki pagename)
"Get the URL of oddmuse wiki."
(condition-case v
(concat (or (cadr (assoc wiki oddmuse-wikis)) (error)) "/"
(concat (or (cadr (assoc wiki oddmuse-wikis)) (error "Wiki not found in `oddmuse-wikis'")) "/"
(url-hexify-string pagename))
(error nil)))
@@ -534,7 +517,7 @@ as well."
((string-match "<title>Error</title>" status)
(if (string-match "<h1>\\(.*\\)</h1>" status)
(error "Error %s: %s" mesg (match-string 1 status))
(error "Error %s: Cause unknown")))
(error "Error %s: Cause unknown" status)))
(t
(message "%s...done" mesg))))))
@@ -736,7 +719,7 @@ Font-locking is controlled by `oddmuse-markup-functions'.
(set (make-local-variable 'sgml-tag-alist)
`(("b") ("code") ("em") ("i") ("strong") ("nowiki")
("pre" \n) ("tt") ("u")))
(set (make-local-variable 'skeleton-transformation) 'identity)
(set (make-local-variable 'skeleton-transformation-function) 'identity)
(make-local-variable 'oddmuse-wiki)
(make-local-variable 'oddmuse-page-name)
@@ -854,11 +837,8 @@ people have been editing the wiki in the mean time."
(set-buffer (get-buffer-create name))
(erase-buffer); in case of current-prefix-arg
(oddmuse-run "Loading" oddmuse-get-command wiki pagename)
(oddmuse-revision-put wiki pagename (oddmuse-get-latest-revision wiki pagename))
;; fix mode-line for VC in the new buffer because this is not a vc-checkout
(setq buffer-file-name (concat oddmuse-directory "/" wiki "/" pagename))
(vc-mode-line buffer-file-name 'oddmuse)
(pop-to-buffer (current-buffer))
(vc-working-revision buffer-file-name 'oddmuse)
;; check for a diff (this ends with display-buffer) and bury the
;; buffer if there are no hunks
(when (file-exists-p buffer-file-name)
@@ -869,7 +849,9 @@ people have been editing the wiki in the mean time."
;; this also changes the buffer name
(basic-save-buffer)
;; this makes sure that the buffer name is set correctly
(oddmuse-mode))))
(oddmuse-mode)
;; fix mode-line for VC in the new buffer because this is not a vc-checkout
(vc-mode-line buffer-file-name 'oddmuse))))
(defalias 'oddmuse-go 'oddmuse-edit)
@@ -909,8 +891,11 @@ Use a prefix argument to override this."
(and buffer-file-name (basic-save-buffer))
(oddmuse-run "Posting" oddmuse-post-command nil nil
(get-buffer-create " *oddmuse-response*") t 302)
(oddmuse-revision-put oddmuse-wiki oddmuse-page-name
(oddmuse-get-latest-revision oddmuse-wiki oddmuse-page-name)))
;; force reload
(vc-file-setprop buffer-file-name 'vc-working-revision
(oddmuse-get-latest-revision oddmuse-wiki oddmuse-page-name))
;; fix mode-line for VC in the new buffer because this is not a vc-checkout
(vc-mode-line buffer-file-name 'oddmuse))
;;;###autoload
(defun oddmuse-preview (&optional arg)

View File

@@ -47,7 +47,8 @@ For a list of possible values, see `vc-state'."
(defun vc-oddmuse-working-revision (file)
"The current revision based on `oddmuse-revisions'."
(oddmuse-revision-get oddmuse-wiki oddmuse-page-name))
(with-oddmuse-file
(oddmuse-get-latest-revision wiki pagename)))
(defun vc-oddmuse-checkout-model (files)
"No locking."
@@ -59,10 +60,6 @@ For a list of possible values, see `vc-state'."
(defun vc-oddmuse-register (files &optional rev comment)
"This always works.")
(defun vc-oddmuse-revert (file &optional contents-done)
"No idea"
nil)
(defvar vc-oddmuse-log-command
(concat "curl --silent %w"
" --form action=rc"
@@ -149,7 +146,7 @@ a version backup."
(with-oddmuse-file file
(let ((command (oddmuse-format-command vc-oddmuse-get-revision-command)))
(with-temp-buffer
(oddmuse-run "Loading" command)
(oddmuse-run "Loading" command wiki pagename)
(write-file file))))))
(defun vc-oddmuse-checkin (files rev comment)

536
css/alex-2016.css Normal file
View File

@@ -0,0 +1,536 @@
/* This file is in the public domain. */
html{ text-align: center; }
body, rss {
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
font-style: normal;
font-size: 14pt;
padding: 1em 3em;
max-width: 72ex;
display: inline-block;
text-align: left;
color: #000;
background-color: #fff;
}
@media print {
body {
font-size: 12pt;
}
/* hide all the crap */
div.diff, div.diff+hr, div.refer, div.near, div.definition, div.sister,
div.cal, div.footer, span.specialdays, span.gotobar, a.edit, a.number span,
div.rc form, form.tiny, p.comment, p#plus1, div.g-plusone, div.content a.feed {
display:none;
}
div.content a.book,
div.content a.movie {
text-decoration: none;
}
a cite {
font-style: italic;
}
img[alt="RSS"] { display: none }
a.rss { font-size: 8pt }
}
/* headings: we can use larger sizes if we use a lighter color.
we cannot inherit the font-family because header and footer use a narrow font. */
h1, h2, h3, title {
font-family: inherit;
font-weight: normal;
}
h1, channel title {
font-size: 32pt;
margin: 1em 0 0.5em 0;
padding: 0.4em 0;
}
h2 {
font-size: 18pt;
margin: 2em 0 0 0;
padding: 0;
}
h3 {
font-size: inherit;
font-weight: bold;
padding: 0;
margin: 1em 0 0 0;
clear: both;
}
/* headers in the journal are smaller */
div.journal h1, item title {
font-size: inherit;
padding: 0;
clear: both;
border-bottom: 1px solid #000;
}
div.journal h2 {
font-family: inherit;
font-size: inherit;
}
div.journal h3 {
font-family: inherit;
font-size: inherit;
font-weight: inherit;
font-style: italic;
}
div.journal hr {
visibility: hidden;
}
p.more {
margin-top: 3em;
}
/* Links in headings appear on journal pages. */
h1 a, h2 a, h3 a {
color:inherit;
text-decoration:none;
font-weight: normal;
}
h1 a:visited, h2 a:visited, h3 a:visited {
color: inherit;
}
/* for download buttons and the like */
.button {
display: inline-block;
font-size: 120%;
cursor: pointer;
padding: 0.4em 0.6em;
text-shadow: 0px -1px 0px #ccc;
background-color: #cfa;
border: 1px solid #9d8;
border-radius: 5px;
box-shadow: 0px 1px 3px white inset, 0px 1px 3px black;
}
.button .icon {
color: #363;
text-shadow: 0px -1px 1px white, 0px 1px 3px #666;
}
.button a {
text-decoration: none;
font-weight: normal;
}
/* links */
a.pencil {
padding-left: 1ex;
text-decoration: none;
color: inherit;
visibility: hidden;
transition: visibility 0s 1s, opacity 1s linear;
opacity: 0;
}
*:hover > a.pencil {
visibility: visible;
transition: opacity .5s linear;
opacity: 1;
}
@media print {
a.pencil {
display: none;
}
}
a.number {
text-decoration: none;
}
/* stop floating content from flowing over the footer */
hr {
clear: both;
}
/* the distance between links in the navigation bars */
span.bar a {
margin-right: 1ex;
}
a img {
border: none;
}
/* search box in the top bar */
.header form, .header p {
display: inline;
white-space: nowrap;
}
label[for="searchlang"], #searchlang, .header input[type="submit"] {
/* don't use display: none! http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
visibility: hidden; position: absolute;
}
/* wrap on the iphone */
@media only screen and (max-device-width: 480px) {
}
.header input {
width: 10ex;
}
/* other form fields */
input[type="text"] {
padding: 0;
font-size: 80%;
line-height: 125%;
}
/* code */
textarea, pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, "Symbola";
font-size: 80%;
}
pre {
overflow:hidden;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
/* styling for divs that will be invisible when printing
when printing. */
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 14pt;
}
@media print {
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 8pt;
}
}
div.footer form.search {
display: none;
}
div.rc li + li {
margin-top: 1em;
}
div.rc li strong, table.history strong, strong.description {
font-family: inherit;
font-weight: inherit;
}
div.diff {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
color: #000;
}
div.old {
background-color: #ffffaf;
}
div.new {
background-color: #cfffcf;
}
div.refer {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
}
div.message {
background-color:#fee;
color:#000;
}
img.xml {
border:none;
padding:1px;
}
a.small img {
max-width:300px;
}
a.large img {
max-width:600px;
}
div.sister {
margin-right:1ex;
background-color:inherit;
}
div.sister p {
margin-top:0;
}
div.sister hr {
display:none;
}
div.sister img {
border:none;
}
div.near, div.definition {
background-color:#efe;
}
div.sidebar {
float:right;
border:1px dotted #000;
padding:0 1em;
}
div.sidebar ul {
padding-left:1em;
}
/* replacements, features */
ins {
font-style: italic;
text-decoration: none;
}
acronym, abbr {
letter-spacing:0.1em;
font-variant:small-caps;
}
/* Interlink prefix not shown */
a .site, a .separator {
display: none;
}
a cite { font:inherit; }
/* browser borkage */
textarea[name="text"] { width:97%; height:80%; }
textarea[name="summary"] { width:97%; height:3em; }
/* comments */
textarea[name="aftertext"] { width:97%; height:10em; }
div.commentshown {
font-size: 12pt;
padding: 2em 0;
}
div.commenthidden {
display:none;
}
div.commentshown {
display:block;
}
p.comment {
margin-bottom: 0;
}
div.comment {
font-size: 14pt;
}
div.comment h2 {
margin-top: 5em;
}
/* comment pages with username, homepage, and email subscription */
.comment form span { display: block; }
.comment form span label { display: inline-block; width: 10em; }
/* IE sucks */
.comment input#username,
.comment input#homepage,
.comment input#mail { width: 20em; }
/* cal */
div.month { padding:0; margin:0 2ex; }
body > div.month {
float:right;
background-color: inherit;
border:solid thin;
padding:0 1ex;
}
.year > .month {
float:left;
}
.footer {
clear:both;
}
.month .title a.local {
background-color: inherit;
}
.month a.local {
background-color: #ddf;
}
.month a.today {
background-color: #fdd;
}
.month a {
color:inherit;
font-weight:inherit;
text-decoration: none;
background-color: #eee;
}
/* history tables and other tables */
table.history {
border: none;
}
td.history {
border: none;
}
table.user {
border: none;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
padding: 1em;
margin: 1em 2em;
}
table.user tr td, table.user tr th {
border: none;
padding: 0.2em 0.5em;
vertical-align: top;
}
table.arab tr th {
font-weight:normal;
text-align:left;
vertical-align:top;
}
table.arab, table.arab tr th, table.arab tr td {
border:none;
}
th.nobreak {
white-space:nowrap;
}
table.full { width:99%; margin-left:1px; }
table.j td, table.j th, table tr td.j, table tr th.j, .j { text-align:justify; }
table.l td, table.l th, table tr td.l, table tr th.l, .l { text-align:left; }
table.r td, table.r th, table tr td.r, table tr th.r, .r { text-align:right; }
table.c td, table.c th, table tr td.c, table tr th.c, .c { text-align:center; }
table.t td { vertical-align: top; }
td.half { width:50%; }
td.third { width:33%; }
form table td { padding:5px; }
/* lists */
dd { padding-bottom:0.5ex; }
dl.inside dt { float:left; }
/* search */
div.search span.result { font-size:larger; }
div.search span.info { font-size:smaller; font-style:italic; }
div.search p.result { display:none; }
img.logo {
float: right;
margin: 0 0 0 1ex;
padding: 0;
border: 1px solid #000;
opacity: 0.3;
background-color:#ffe;
}
/* images */
div.content a.feed img, div.journal a.feed img,
div.content a img.smiley, div.journal a img.smiley, img.smiley,
div.content a.inline img, div.journal a.inline img,
div.content li a.image img, div.journal li a.image img {
margin: 0; padding: 0; border: none;
}
div.image a img {
margin-bottom: 0;
}
div.image span.caption {
margin: 0 1em;
}
img {
max-width: 100%;
}
.left { float:left; margin-right: 1em; }
.right { float:right; margin-left: 1em; }
.half img { height: 50%; width: 50%; }
.face img { width: 200px; }
div.left .left, div.right .right {
float:none;
}
.center { text-align:center; }
table.aside {
float:right;
width:40%;
margin-left: 1em;
padding: 1ex;
border: 1px dotted #666;
}
table.aside td {
text-align:left;
}
div.sidebar {
float:right; width: 250px;
text-align: right;
border: none;
margin: 1ex;
}
.bigsidebar {
float:right;
width: 500px;
border: none;
margin-left: 1ex;
font-size: 80%;
}
dl.irc dt { width:20ex; float:left; text-align:right; clear:left; }
dl.irc dt span.time { float:left; }
dl.irc dd { margin-left:22ex; }
/* portrait */
div.footer, div.comment, hr { clear: both; }
.portrait { float: left; font-size: small; margin-right: 1em; }
.portrait a { color: #999; }
div.left { float:left; margin:1em; padding: 0.5em; }
div.left p { display:table-cell; }
div.left p + p { display:table-caption; caption-side:bottom; }
p.table a { float:left; width:20ex; }
p.table + p { clear:both; }
/* rss */
channel * { display: block; }
channel title {
margin-top: 30pt;
}
copyright {
font-size: 14pt;
margin-top: 1em;
}
channel > link:before {
font-size: 18pt;
display: block;
margin: 1em;
padding: 0.5em;
content: "This is an RSS feed, designed to be read in a feed reader.";
color: red;
border: 1px solid red;
}
link, license {
font-size: 11pt;
margin-bottom: 9pt;
}
username:before { content: "Last edited by "; }
username:after { content: "."; }
generator:before { content: "Feed generated by "; }
generator:after { content: "."; }
channel description {
font-weight: bold;
}
item description {
font-style: italic;
font-weight: normal;
margin-bottom: 1em;
}
docs, language,
pubDate, lastBuildDate, ttl, guid, category, comments,
docs, image title, image link,
status, version, diff, history, importance {
display: none;
}

View File

@@ -321,7 +321,6 @@ div.sister {
float:left;
margin-right:1ex;
padding-right:1ex;
border-right:1px dashed;
}
div.sister p { padding:1ex; margin:0; }
div.sister hr { display:none; }

View File

@@ -47,8 +47,8 @@ textarea, pre, code, tt {
.browse { min-height: 3em; }
.header form, .header p { margin: 0; }
/* hide the buttons but don't use display:none because of
http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
.header input[type="submit"] { position: absolute; visibility: hidden; }
http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form
.header input[type="submit"] { position: absolute; visibility: hidden; } */
.header input { width: 5em; font-size: 80%; }
.footer { clear:both; font-size: 90%; }
.content input { font-size: 80%; line-height: 125%; }
@@ -201,10 +201,12 @@ div.message {
}
table.history { border-style:none; }
td.history { border-style:none; }
div.history span.dash + strong { font-weight: normal; }
span.result { font-size:larger; }
span.info { font-size:smaller; font-style:italic; }
div.rc hr { display: none; }
div.rc li { padding-bottom: 0.5em; }
div.rc li strong { font-weight: normal; }
/* Tables */
table.user {

View File

@@ -219,50 +219,3 @@ code {
background: #eee;
white-space: pre-wrap;
}
@font-face {
font-family: 'Gentium Basic';
font-style: normal;
font-weight: 700;
src: local('Gentium Basic Bold'), local('GentiumBasic-Bold'), url(/fonts/GenBasB.woff) format('woff');
}
@font-face {
font-family: 'Gentium Basic';
font-style: italic;
font-weight: 400;
src: local('Gentium Basic Italic'), local('GentiumBasic-Italic'), url(/fonts/GenBasI.woff) format('woff');
}
@font-face {
font-family: 'Gentium Basic';
font-style: italic;
font-weight: 700;
src: local('Gentium Basic Bold Italic'), local('GentiumBasic-BoldItalic'), url(/fonts/GenBasBI.woff) format('woff');
}
@font-face {
font-family: 'Gentium Basic';
font-style: normal;
font-weight: 400;
src: local('Gentium Basic'), local('GentiumBasic'), url(/fonts/GenBasR.woff) format('woff');
}
@font-face {
font-family: 'Gentium Plus';
font-style: normal;
font-weight: 400;
src: local('Gentium Plus'), local('GentiumPlus'), url(/fonts/GentiumPlus-R.woff) format('woff');
}
@font-face {
font-family: 'Gentium Plus';
font-style: italic;
font-weight: 400;
src: local('Gentium Plus Italic'), local('GentiumPlus-Italic'), url(/fonts/GentiumPlus-I.woff) format('woff');
}
@font-face {
font-family: 'Symbola';
src: local('Symbola'), url('/fonts/Symbola.woff') format('woff') url('/fonts/Symbola.ttf') format('truetype');
}

View File

@@ -29,6 +29,7 @@ our %TranslationsLibrary = (
'bg' => 'bulgarian-utf8.pl',
'ca' => 'catalan-utf8.pl',
'de' => 'german-utf8.pl',
'et' => 'estonian-utf8.pl',
'es' => 'spanish-utf8.pl',
'fi' => 'finnish-utf8.pl',
'fr' => 'french-utf8.pl',
@@ -72,6 +73,7 @@ sub LoadLanguage {
foreach (@prefs) {
last if $Lang{$_} eq 'en'; # the default
my $file = $TranslationsLibrary{$Lang{$_}};
next unless $file; # file is not listed, eg. there is no file for "de-ch"
$file = "$LoadLanguageDir/$file" if defined $LoadLanguageDir;
if (-r $file) {
do $file;

View File

@@ -137,13 +137,8 @@ sub NamespacesInitVariables {
$StaticUrl .= UrlEncode($NamespaceCurrent) . '/'
if substr($StaticUrl,-1) eq '/'; # from static-copy.pl
$WikiDescription .= "<p>Current namespace: $NamespaceCurrent</p>";
# override LastUpdate
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks)
= stat($IndexFile);
$LastUpdate = $mtime;
CreateDir($DataDir); # Create directory if it doesn't exist
ReportError(Ts('Cannot create %s', $DataDir) . ": $!", '500 INTERNAL SERVER ERROR')
unless -d $DataDir;
$LastUpdate = (stat($IndexFile))[9];
CreateDir($DataDir);
}
$Namespaces{$NamespacesSelf} = $ScriptName . '?';
# reinitialize

View File

@@ -154,7 +154,7 @@ sub PageContentToTitle {
$title =~ s!\s+! !g;
$title =~ s!^ !!;
$title =~ s! $!!;
$title = substring($title, 0, $RefererTitleLimit) . "..."
$title = substr($title, 0, $RefererTitleLimit) . "..."
if length($title) > $RefererTitleLimit;
return $title;
}

View File

@@ -81,7 +81,7 @@ sub StaticScriptLink {
my %params;
if ($action !~ /=/) {
# the page might not exist, eg. if called via GetAuthorLink
$params{-href} = StaticFileName($action) if $IndexHash{$action};
$params{'-href'} = StaticFileName($action) if $IndexHash{UrlDecode($action)};
}
$params{'-class'} = $class if $class;
$params{'-name'} = UrlEncode($name) if $name;

File diff suppressed because it is too large Load Diff

View File

@@ -89,9 +89,9 @@ Ungültige Seite %s (Darf nicht mit .lck enden)
Invalid Page %s
Ungültige Seite %s
There are no comments, yet. Be the first to leave a comment!
Es gibt noch keine Kommentare, sei der Erste der einen hinterlässt!
Welcome!
Willkommen!
This page does not exist, but you can %s.
Diese Seite gibt es nicht, aber du kannst %s.
create it now
@@ -343,9 +343,9 @@ Die Sperre wurde %s gesetzt.
Maybe the user running this script is no longer allowed to remove the lock directory?
Vielleicht darf der user, welcher dieses script ausführt, das Sperr-Verzeichnis nicht löschen?
Sometimes locks are left behind if a job crashes.
Manchmal bleiben Sperren erhalten, wenn ein Prozess abbricht.
After ten minutes, you could try to unlock the wiki.
Nach zehn Minuten kann versucht werden, das Wiki zu entsperren.
This operation may take several seconds...
Das könnte einige Sekunden dauern...
Forced unlock of %s lock.
@@ -435,7 +435,7 @@ Grund unbekannt.
%s pages found.
%s Seiten gefunden.
Preview: %s
Vorschau: %s
Replaced: %s
Ersetzt: %s
Search for: %s
@@ -786,7 +786,7 @@ Kommentar hinzufügen
ordinary changes
normale Änderungen
%s days
%s Tage
################################################################################
# modules/edit-paragraphs.pl
################################################################################
@@ -1398,9 +1398,9 @@ Portrait
# modules/preview.pl
################################################################################
Pages with changed HTML
Seiten mit geändertem HTML
Preview changes in HTML output
Vorschau der Änderungen der HTML-Ausgabe
################################################################################
# modules/private-pages.pl
################################################################################

View File

@@ -0,0 +1,162 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
#
# The source for the data is unknown.
use utf8;
use strict;
AddModuleDescription('national-days-de.pl', 'Special Days') if defined &AddModuleDescription;
our %SpecialDays = (
'1-1' => 'Sudán, Día Nacional, Haití, Día de Independencia, Cuba, Día de Liberación',
'1-4' => 'Myanmar, Día de Independencia',
'1-9' => 'Islas de Mariana del Norte, Día Nacional',
'1-26' => 'Australia, Día de Australia',
'1-31' => 'Nauru, Día Nacional',
'2-4' => 'Sri Lanka, Día de Conmemoración de la Independencia',
'2-6' => 'Nueva Zelanda, Día de Waitangi',
'2-7' => 'Grenada, Día de Independencia',
'2-11' => 'Irán, Victoria de la Revolución Islámica en Irán',
'2-16' => 'Lituania, Día de Independencia',
'2-18' => 'Gambia, Día Nacional',
'2-22' => 'Santa Lucía, Día Nacional',
'2-23' => 'Guyana, Día Nacional, Brunei Darussalam, Día Nacional',
'2-24' => 'Estonia, Día de Independencia',
'2-25' => 'Kuwait, Día Nacional',
'2-27' => 'República Dominicana, Día de Independencia',
'3-3' => 'Bulgaria, Día Nacional',
'3-6' => 'Ghana, Día de Independencia',
'3-12' => 'Mauritius, Día Nacional',
'3-17' => 'Irlanda, Día de San Patricio',
'3-20' => 'Tunisia, Aniversario de la Independencia de la República de Tunisia',
'3-21' => 'Namibia, Día de Independencia',
'3-23' => 'Pakistán, Día Nacional',
'3-25' => 'Grecia, Día de Independencia',
'3-26' => 'Bangladesh, Día de Independencia',
'4-4' => 'Senegal, Día de Independencia',
'4-16' => 'Dinamarca, Cumpleaños de la Reina',
'4-17' => 'Syria, Día Nacional',
'4-18' => 'Zimbabwe, Día Nacional',
'4-19' => 'Sierra Leona, Día de la República',
'4-26' => 'Tanzania, Día de la Unión, Israel, Día de Independencia',
'4-27' => 'República Federal de Yugoslavia, Día Nacional, Togo, Togolais Día Nacional, Suráfrica, Día de la Libertad',
'4-30' => 'Los Países Bajos, Cumpleaños Oficial de Su Majestad la Reina Beatriz',
'5-1' => 'Islas Marshall, Día Nacional',
'5-3' => 'Polonia, Día Nacional',
'5-9' => 'Unión Europea, Día de Europa',
'5-14' => 'Paraguay, Día Nacional',
'5-17' => 'Noruega, Día de la Constitución',
'5-20' => 'Camerún, Día Nacional',
'5-22' => 'Yemen, Día Nacional',
'5-24' => 'Eritrea, Día de Independencia',
'5-25' => 'Jordania, Día de Independencia, Argentina, Día Nacional',
'5-26' => 'Georgia, Día Nacional',
'5-28' => 'Etiopía, Día Nacional, Azerbaijan, Día Nacional',
'6-1' => 'Samoa, Día de Independencia',
'6-2' => 'Italia, Fundación de la República',
'6-4' => 'Tonga, Día de Emancipación',
'6-6' => 'Suecia, Día Nacional',
'6-10' => 'Portugal, Día de Portugal, Día de Camões y Día de las Comunidades Portuguesas',
'6-12' => 'Filipinas, Día de Independencia, Rusia, Día Nacional',
'6-17' => 'Islandia, Día Nacional',
'6-18' => 'Seychelles, Día Nacional',
'6-23' => 'Luxemburgo, Día Nacional y Cumpleaños Oficial de H.R.H. el Gran Duque',
'6-25' => 'Croacia, Día Nacional, Eslovenia, Día Nacional, Mozambique, Día de Independencia',
'6-26' => 'Madagascar, Día Nacional',
'6-27' => 'Djibouti, Día de Independencia',
'6-30' => 'República Democrática del Congo, Día de Independencia',
'7-1' => 'Burundi, Día Nacional, Canadá, Día de Canadá',
'7-3' => 'Belarus, Día Nacional',
'7-4' => 'Estados Unidos de América, Día de Independencia',
'7-5' => 'Rwanda, Día de Liberación, Cape Verde, Día Nacional, Venezuela, Día Nacional',
'7-6' => 'Malawi, Día Nacional, Comoros, Día Nacional',
'7-7' => 'Nepal, Cumpleaños del Rey y Día Nacional, Islas Solomon, Día Nacional',
'7-10' => 'Bahamas, Día de Independencia',
'7-11' => 'Mongolia, Aniversario de la Revolución de las Gentes de Mongolia',
'7-12' => 'Sao Tome & Principe, Día Nacional, Kiribati, Día Nacional',
'7-14' => 'Francia, Día de Bastillas',
'7-17' => 'Irak, Día Nacional',
'7-20' => 'Colombia, Día Nacional',
'7-21' => 'Bélgica, Ascenso del Rey Leopoldo I (1831)',
'7-23' => 'Egipto, Aniversario de la Revolución',
'7-26' => 'Liberia, Día Nacional, Maldives, Día Nacional',
'7-28' => 'Perú, Día de Independencia',
'7-30' => 'Vanuatu, Día de Independencia, Marruecos, Festival del Trono',
'8-1' => 'Benin, Día Nacional, Suiza, Fundación de la Confederación Suiza',
'8-4' => 'Islas Cook, Día Nacional',
'8-5' => 'Jamaica, Día Nacional',
'8-6' => 'Bolivia, Día de Independencia',
'8-7' => 'Cote D\'Ivoire, Día Nacional',
'8-9' => 'Singapur, Día Nacional',
'8-10' => 'Ecuador, Día Nacional',
'8-11' => 'Chad, Día Nacional',
'8-15' => 'República de Korea, Día Nacional, Liechtenstein, Día Nacional, India, Día Nacional, República del Congo, Día de Independencia',
'8-17' => 'Indonesia, Proclamación de Independencia, Gabon, Día Nacional',
'8-19' => 'Afganistán, Día de Independencia',
'8-20' => 'Hungaria, Día Nacional',
'8-24' => 'Ucrania, Día Nacional',
'8-25' => 'Uruguay, Día de Independencia',
'8-27' => 'Moldova, Día Nacional',
'8-31' => 'Kyrgyzstan, Día Nacional, Trinidad y Tobago, Día Nacional, Malasia, Día Nacional',
'9-1' => 'Uzbekistán, Día Nacional, Eslovakia, Día de la Constitución',
'9-2' => 'Vietnam, Día Nacional, Libyan Arab Jamahiriya, Día Nacional',
'9-3' => 'Qatar, Día Nacional, San Marino, Día de Fundación Nacional',
'9-6' => 'Swaziland, Día Nacional',
'9-7' => 'Brazil, Día de Independencia',
'9-8' => 'Andorra, Día Nacional, Antigua República de Macedonia Yugoslava, Día de Independencia',
'9-9' => 'República Democrática de Korea, Día Nacional, Tajikistán, Día Nacional',
'9-15' => 'Guatemala, Día de Independencia, Honduras, Día de Independencia, El Salvador, Día de Independencia, Costa Rica, Día de Independencia, Nicaragua, Día de Independencia',
'9-16' => 'Papua Nueva Guinea, Día de Independencia, Méjico, Proclamación de Independencia',
'9-18' => 'Chile, Día Nacional',
'9-19' => 'St Kitts Nevis, Día de Independencia',
'9-21' => 'Belize, Día Nacional, Malta, Día de Independencia, Armenia, Día Nacional',
'9-22' => 'República de Mali, Proclamación de Independencia',
'9-23' => 'Arabia Saudita, Día Nacional',
'9-24' => 'Guinea Bissau, Día de Independencia',
'9-30' => 'Botswana, Día Nacional',
'10-1' => 'Guinea, Día de Independencia, China, Día Nacional, Palau, Día Nacional, Tuvalu, Día de Independencia, Cyprus, Día Nacional, Nigeria, Día Nacional',
'10-3' => 'Alemania, Día Nacional',
'10-4' => 'Lesotho, Día Nacional',
'10-9' => 'Uganda, Día de Independencia',
'10-10' => 'Fiji, Día Nacional',
'10-12' => 'Guinea Ecuatorial, Día de Independencia, España, Día Nacional',
'10-19' => 'Niue, Día Nacional',
'10-22' => 'Holy See, Aniversario, Ministerio Pontífice de Su Santidad el Papa Juan Pablo II',
'10-24' => 'Zambia, Día de Independencia',
'10-26' => 'Austria, Día Nacional',
'10-27' => 'Turkmenistán, Día Nacional, San Vincente y las Granadinas, Día Nacional',
'10-28' => 'República Checa, Día Nacional',
'10-29' => 'Turquía, Día de la República',
'11-1' => 'Antigua y Barbuda, Día Nacional, Algeria, Aniversario de la Revolución',
'11-3' => 'Panamá, Día Nacional, Dominica, Día Nacional, Estados Federados de Micronesia, Día Nacional',
'11-9' => 'Cambodia, Día de Independencia',
'11-11' => 'Angola, Día Nacional',
'11-18' => 'Omán, Día Nacional, Latvia, Proclamación de Independencia',
'11-19' => 'Mónaco, Día Nacional',
'11-22' => 'Lebanon, Día de Independencia',
'11-25' => 'Bosnia y Herzegovina, Día del Estado, Surinam, Día Nacional',
'11-28' => 'Mauritania, Día Nacional, Albania, Día Nacional',
'11-30' => 'Barbados, Día de Independencia',
'12-1' => 'Rumania, Día Nacional, República de África Central, Día Nacional',
'12-2' => 'Emiratos Árabes Unidos, Día Nacional, Laos, Día Nacional',
'12-5' => 'Tailandia, Cumpleaños del Rey',
'12-6' => 'Finlandia, Día de Independencia',
'12-11' => 'Burkina Faso, Día Nacional',
'12-12' => 'Kenya, Día de Jamhuri',
'12-16' => 'Bahrain, Día Nacional, Kazakhstán, Día Nacional',
'12-17' => 'Bután, Día Nacional',
'12-18' => 'Nigeria, Día Nacional',
'12-23' => 'Japón, Cumpleaños del Emperador',
);

View File

@@ -691,11 +691,6 @@ Clustermap
Pages without a Cluster
################################################################################
# modules/comment-div-wrapper.pl
################################################################################
Comments:
################################################################################
# modules/commentcount.pl
################################################################################
@@ -703,6 +698,11 @@ Comments on
Comment on
################################################################################
# modules/comment-div-wrapper.pl
################################################################################
Comments:
################################################################################
# modules/compilation.pl
################################################################################
@@ -1239,7 +1239,7 @@ Test / Always enabled / Always disabled
Start
Bisection proccess is already active.
Bisecting proccess is already active.
Stop
@@ -1280,6 +1280,11 @@ You linked more than %s times to the same domain. It would seem that only a spam
Namespaces
################################################################################
# modules/nearlink-create.pl
################################################################################
(create locally)
################################################################################
# modules/near-links.pl
################################################################################
@@ -1299,11 +1304,6 @@ EditNearLinks
The same page on other sites:
################################################################################
# modules/nearlink-create.pl
################################################################################
(create locally)
################################################################################
# modules/no-question-mark.pl
################################################################################
@@ -1662,4 +1662,5 @@ Edit %s.
################################################################################
Tags:
#
END_OF_TRANSLATION

188
scripts/alexine/bot.p6 Executable file
View File

@@ -0,0 +1,188 @@
#!/usr/bin/env perl6
use Net::IRC::Bot;
use Net::IRC::Modules::Autoident;
use Net::IRC::Modules::Tell;
use Net::IRC::CommandHandler;
sub wikiLink($page is copy) {
$page ~~ s:g/\s/_/; # quick and dirty
returnhttps://oddmuse.org/wiki/$page;
}
class Intermap {
has $.intermapLink is rw = https://oddmuse.org/wiki/Local_Intermap?raw=1;
has %!intermap;
method update {
# TODO https breaks HTTP::UserAgent, workaround with curl
my $proc = run(curl, $!intermapLink, :out);
my $text = $proc.out.slurp-rest;
$proc.out.close; # RT #126561
return False unless $proc;
for $text ~~ m:global ^^ \h+ $<name>=\S+ \s+ $<value>=.+? $$ {
%!intermap{~$_<name>} = ~$_<value>; # TODO map!
}
return True;
}
method said ($e) {
self.update if not %!intermap or $e.what ~~ / update intermap /; # lazy init
for $e.what ~~ m:global $<name>=<-[\s :]>+ : $<value>=\S+ { # quick and dirty
next unless %!intermap{.<name>}:exists;
my $link = %!intermap{~.<name>};
my $replacement = $_<value>;
$link ~~ s{ \%s | $ } = $replacement;
$e.msg: $link;
}
}
}
class Pages {
method said ($e) {
for $e.what ~~ m:global [[ $<page>=<-[ \] ]>+ ]] { # quick and dirty
$e.msg: wikiLink ~.<page>;
}
}
}
class Sorry {
has $.answers is rw = « I'm so sorry! Please forgive me!
I should have done better!
I promise that it won't happen again! »;
method said ($e) {
if $e.what ~~ / ^ "{ $e.bot.nick }" [:|,] / {
$e.msg: $!answers.pick;
}
}
}
class RecentChanges {
has $.delay is rw = 30;
has $.url is rw = https://oddmuse.org/wiki?action=rss;all=0;showedit=0;rollback=1;from=;
has $!last = time;
method joined ($e) {
start loop {
sleep $!delay;
self.process: $e;
}
}
method process ($e) {
my $newLast = time;
# TODO https breaks HTTP::UserAgent, workaround with curl
my $proc = run(curl, $!url ~ $!last, :out);
my $xml = $proc.out.slurp-rest;
$proc.out.close; # RT #126561
return False unless $proc;
$!last = $newLast;
use XML;
for from-xml($xml).elements(:TAG<item>, :RECURSE) {
my $title = ~.elements(:TAG<title>, :SINGLE).contents;
my $desc = ~.elements(:TAG<description>, :SINGLE).contents;
my $author = ~.elements(:TAG<dc:contributor>, :SINGLE).contents;
$e.msg:Wiki: [$title] <$author> $desc ({wikiLink $title});
}
return True;
}
}
class RecentCommits {
has $.delay is rw = 30;
has $.url = https://github.com/kensanata/oddmuse.git;
has $.repo = repo;
method joined ($e) {
start {
if $!repo.IO !~~ :e {
fail unless run(git, clone, $!url, $!repo);
}
loop {
sleep $!delay;
self.process: $e;
}
}
}
method process ($e) {
my $proc1 = run(git, --git-dir, $!repo ~ /.git, fetch) ;
return False unless $proc1;
my $proc2 = run(git, --git-dir, $!repo ~ /.git, log,
--pretty=format:Commit: %s (https://github.com/kensanata/oddmuse/commit/%h),
...origin, :out);
$e.msg: $_ for $proc2.out;
$proc2.out.close; # RT #126561
return False unless $proc2;
run(git, --git-dir, $!repo ~ /.git, merge, -q);
return True;
}
}
class Backlog does Net::IRC::CommandHandler {
has $.limit is rw = 60 * 60 * 48;
has $.delay is rw = 30; # seconds before file deletion
has $.path is rw = backlogs/;
has $.link is rw = http://alexine.oddmuse.org/backlogs/; # TODO https
has %.messages = ();
multi method said ($e) {
%!messages{$e.where} = [] unless %!messages{$e.where}:exists;
%!messages{$e.where}.push: { when => time, who => $e.who<nick>, what => $e.what };
self.clean;
}
method clean {
for %!messages.values -> $value { # each channel
for $value.kv -> $index, $elem { # each message
last if time - $elem<when> < $!limit;
LAST { $value.splice(0, $index) } # at least one message will be kept
}
}
}
method backlog ($e, $match) is cmd {
self.clean;
mkdir $!path unless $!path.IO ~~ :d;
my $name = ^2**128 .pick.base(36);
my $fh = open$!path/$name, :w;
$fh.say(<{.<who>}> {.<what>}) for @(%!messages{$e.where});
$fh.close;
$e.msg:$!link$name;
Promise.in($!delay).then: { unlink$!path/$name};
}
method forget ($e, $match) is cmd {
%!messages{$e.where} = [];
$e.msg: OK, we didn't have this conversation.;
}
}
sub MAIN(Str :$nick = alexine, Str :$password is copy = , Str :$channel = #oddmuse) {
$password = prompt Nickserv password: unless $password;
Net::IRC::Bot.new(
nick => $nick,
username => $nick,
realname => $nick,
server => irc.freenode.org,
channels => [ $channel ],
debug => True,
modules => (
Intermap.new(),
Pages.new(),
Sorry.new(),
RecentChanges.new(),
#RecentCommits.new(),
Backlog.new(prefix => .),
Net::IRC::Modules::Tell.new(prefix => .),
Net::IRC::Modules::Autoident.new(password => $password),
),
).run;
}

View File

@@ -21,6 +21,7 @@ TEST_LOG="$WORKING_DIRECTORY/log"
ODDMUSE_TEST_LOCATION="$WORKING_DIRECTORY/oddmuse-for-tests/"
GIT_LOCATION="$WORKING_DIRECTORY/"
LAST_COMMIT_FILE="$WORKING_DIRECTORY/last_commit"
LAST_STATUS_FILE="$WORKING_DIRECTORY/last_status"
FIRST_TESTABLE_COMMIT='1c0801bd6ca23de71c7c360a18a648c2b953f1da'
RESULT_FILE="$WORKING_DIRECTORY/output"
WIKIPUT='../config/oddmuse/scripts/cli/wikiput'
@@ -49,7 +50,9 @@ while :; do
"${git[@]}" reset --hard origin/master # starting our search from the last commit
[[ -f $LAST_COMMIT_FILE ]] || echo "$FIRST_TESTABLE_COMMIT" > "$LAST_COMMIT_FILE"
[[ -f $LAST_STATUS_FILE ]] || echo 0 > "$LAST_STATUS_FILE"
lastCommit=$(< "$LAST_COMMIT_FILE")
lastStatus=$(< "$LAST_STATUS_FILE")
logOutput=$("${git[@]}" log --topo-order --pretty=oneline | grep --before 1 -m 1 "^$lastCommit")
(($(wc -l <<< "$logOutput") < 2)) && exit 0 # No more commits to process, good!
@@ -65,14 +68,16 @@ while :; do
printf "%s\n" "$output" > "$RESULT_FILE"
# echo "Duration: $((duration/60))m$((duration%60))s Status: $status" >> "$RESULT_FILE"
printf "%s\n" "$currentCommit" > "$LAST_COMMIT_FILE"
printf "%s\n" "$status" > "$LAST_STATUS_FILE"
"${gitRepo[@]}" add -- "$(readlink -m -- "$RESULT_FILE")" "$(readlink -m -- "$LAST_COMMIT_FILE")"
"${gitRepo[@]}" add -- "$(readlink -m -- "$RESULT_FILE")" "$(readlink -m -- "$LAST_COMMIT_FILE")" "$(readlink -m -- "$LAST_STATUS_FILE")"
"${gitRepo[@]}" commit -m "Test status at $currentCommit (automated commit)"
"${gitRepo[@]}" push
if (( status == 0 )); then
"$WIKIPUT" -m -u "$USER_NAME" -s 'Tests PASSED' -z 'ham' "$WIKI_LOCATION/$STATUS_PAGE" <<< $'TEST STATUS **OK**\n\n'"Commit:${currentCommit:0:7} see [[$OUT_PAGE|test log]]"
(( lastStatus == 0 )) && minor='-m' || minor='' # we will use unquoted variable on purpose
"$WIKIPUT" $minor -u "$USER_NAME" -s 'Tests PASSED' -z 'ham' "$WIKI_LOCATION/$STATUS_PAGE" <<< $'TEST STATUS **OK**\n\n'"Commit:${currentCommit:0:7} see [[$OUT_PAGE|test log]]"
else
"$WIKIPUT" -u "$USER_NAME" -s 'Tests FAILED' -z 'ham' "$WIKI_LOCATION/$STATUS_PAGE" <<< $'TEST STATUS **FAIL**\n\n'"Commit:${currentCommit:0:7} see [[$OUT_PAGE|test log]]"
fi

View File

@@ -85,7 +85,7 @@ Examples
--------
Journal pages only:
$0 --match '^\d{4}-\d{2}-\d{2}'
$0 --match '^\\d{4}-\\d{2}-\\d{2}'
Skip comment pages:
$0 --match '^(?!Comments_on_)'

View File

@@ -1,36 +1,49 @@
#! /usr/bin/env perl
#!/usr/bin/env perl
# This script only works with a version of Mojolicious::Plugin::CGI better than
# the official 0.23. One version would be my fork:
# https://github.com/kensanata/mojolicious-plugin-cgi
# Copyright (C) 2015-2016 Alex Schroeder <alex@gnu.org>
# If you use the fork, you might want to simply add its lib directory to your
# libraries instead of installing it?
# use lib '/Users/alex/src/mojolicious-plugin-cgi/lib';
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
use Mojolicious::Lite;
# This needs to be in a different section, sometimes?
plugin CGI => {
support_semicolon_in_query_string => 1,
};
plugin CGI => {
route => '/wiki',
# We need this for older versions of Mojolicious::Plugin::CGI
script => 'wiki.pl',
run => \&OddMuse::DoWikiRequest,
before => sub {
no warnings;
$OddMuse::RunCGI = 0;
$OddMuse::DataDir = '/tmp/oddmuse';
# The default data directory is determined by the environment variable
# WikiDataDir and falls back to the following
# $OddMuse::DataDir = '/tmp/oddmuse';
use warnings;
require 'wiki.pl' unless defined &OddMuse::DoWikiRequest;
},
env => {},
errlog => 'wiki.log', # path to where STDERR from cgi script goes
# path to where STDERR from cgi script goes
errlog => ($ENV{WikiDataDir} || '/tmp/oddmuse')
. "/wiki.log",
};
get '/' => sub {
my $self = shift;
$self->redirect_to('/wiki');
};
app->start;

63
stuff/expire-bans.pl Executable file
View File

@@ -0,0 +1,63 @@
#! /usr/bin/perl
my $usage = q{expire-pans.pl
Usage: this script expects to be run in a directory with a spammer.log file as
produced by the LogBannedContent module.
<https://oddmuse.org/wiki/LogBannedContent_Module>
In the same directory, it expects at least one of BannedContent, BannedHosts or
BannedRegexps. It will work on all three, though. These must be the raw text
files of the wiki.
Here's how you might get them from Emacs Wiki, for example.
wget https://www.emacswiki.org/spammer.log
wget https://www.emacswiki.org/emacs/raw/BannedContent
wget https://www.emacswiki.org/emacs/raw/BannedHosts
wget https://www.emacswiki.org/emacs/raw/BannedRegexps
};
die $usage if ! -f 'spammer.log'
|| !(-f 'BannedContent' || -f 'BannedHosts' || -f 'BannedRegexps');
my $fh;
my @bans;
warn "Reading spammer.log...\n";
open($fh, '<:utf8', 'spammer.log') or die "Cannot read spammer.log: $!";
for my $line (<$fh>) {
push(@bans, $line);
}
close($fh);
for my $file (qw(BannedContent BannedHosts BannedRegexps)) {
warn "Reading $file...\n";
if (open($fh, '<:utf8', $file)) {
my $count = 0;
my $used = 0;
my @out;
for my $line (<$fh>) {
if ($line =~ m/^\s*([^#]+?)\s*(#\s*(\d\d\d\d-\d\d-\d\d\s*)?(.*))?$/) {
$count++;
my ($regexp, $comment) = ($1, $4);
foreach my $ban (@bans) {
if (index($ban, $regexp) > -1) {
$used++;
push(@out, $line);
last;
}
}
} else {
push(@out, $line);
}
}
close ($fh);
warn "$count regular expressions checked\n";
warn "$used regular expressions were used\n";
warn "Writing $file-new...\n";
open ($fh, '>:utf8', "$file-new")
or die "Cannot write $file-new: $!";
print $fh join("", @out);
close $fh;
}
}

63
stuff/release Normal file
View File

@@ -0,0 +1,63 @@
#!/bin/env perl
# Copyright (C) 2015 Alex Schroeder <alex@gnu.org>
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
use strict;
use warnings;
use version;
my $dir = shift;
unless (-d $dir) {
die <<"EOT";
Usage: $0 DIR [RELEASE]
DIR is the directory where the tarballs for each tag are created.
It must already exist.
If an optional RELEASE such as 2.3.0 is provided, then only tags
equal or greater than 2.3.0 will be considered. The default is 2.3.0.
EOT
}
my $min = version->parse(shift || "2.3.0");
my @tags = grep { /\d+\.\d+\.\d+/ and version->parse($_) > $min }
split(/\n/, qx{git tag --list});
unless (@tags) {
die "git tag --list produced no list of tags\n";
}
for my $tag (@tags) {
my $fname = "$dir/oddmuse-$tag.tar.gz";
unless (-f $fname) {
system("git", "checkout", $tag) == 0
or die "Failed to git checkout $tag\n";
system("make", "prepare") == 0
or die "Failed to run make prepare for tag $tag\n";
system("mv", "build", "oddmuse-$tag") == 0
or die "Failed to rename the build directory to oddmuse-$tag\n";
system("tar", "czf", "oddmuse-$tag.tar.gz", "oddmuse-$tag") == 0
or die "Failed to build tarball oddmuse-$tag.tar.gz\n";
system("mv", "oddmuse-$tag.tar.gz", $fname) == 0
or die "Failed to move the tarball oddmuse-$tag.tar.gz\n";
system("rm", "-rf", "oddmuse-$tag") == 0
or die "Failed to remove the directory oddmuse-$tag\n";
}
}
system("git", "checkout", "master") == 0
or die "Failed to git checkout master\n";

View File

@@ -22,45 +22,9 @@ use XML::Atom::Client;
use XML::Atom::Entry;
use XML::Atom::Person;
sub random_port {
use Errno qw( EADDRINUSE );
use Socket qw( PF_INET SOCK_STREAM INADDR_ANY sockaddr_in );
my $family = PF_INET;
my $type = SOCK_STREAM;
my $proto = getprotobyname('tcp') or die "getprotobyname: $!";
my $host = INADDR_ANY; # Use inet_aton for a specific interface
for my $i (1..3) {
my $port = 1024 + int(rand(65535 - 1024));
socket(my $sock, $family, $type, $proto) or die "socket: $!";
my $name = sockaddr_in($port, $host) or die "sockaddr_in: $!";
setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, 1);
bind($sock, $name)
and close($sock)
and return $port;
die "bind: $!" if $! != EADDRINUSE;
print "Port $port in use, retrying...\n";
}
die "Tried 3 random ports and failed.\n"
}
my $port = random_port();
$ScriptName = "http://localhost:$port";
AppendStringToFile($ConfigFile, "\$ScriptName = $ScriptName;\n");
add_module('atom.pl');
# Fork a test server with the new config file and the module
my $pid = fork();
if (!defined $pid) {
die "Cannot fork: $!";
} elsif ($pid == 0) {
use Config;
my $secure_perl_path = $Config{perlpath};
exec($secure_perl_path, "stuff/server.pl", "wiki.pl", $port) or die "Cannot exec: $!";
}
start_server();
# Give the child time to start
sleep 1;
@@ -69,7 +33,7 @@ sleep 1;
my $ua = LWP::UserAgent->new;
my $response = $ua->get("$ScriptName?action=version");
ok($response->is_success, "There is a wiki running at $ScriptName");
like($response->content, qr/\batom\.pl/, "The has the atom extension installed");
like($response->content, qr/\batom\.pl/, "The server has the atom extension installed");
# Testing the Atom API
my $api = XML::Atom::Client->new;
@@ -179,8 +143,3 @@ sub trim {
}
ok(trim($result->content->body) eq ("<p>" . trim($content) . '</p>'), 'verify content');
ok($result->author->name eq $username, 'verify author');
END {
# kill server
kill 'KILL', $pid;
}

25
t/basic.t Normal file
View File

@@ -0,0 +1,25 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 3;
# editing pages
test_page(get_page('Test'),
'<title>Wiki: Test</title>',
'Status: 404 NOT FOUND');
test_page(update_page('Test', 'Muuu!', 'first edit', undef, undef,
'username=Alex'),
'<p>Muuu!</p>');

10
t/css.t
View File

@@ -20,13 +20,13 @@ AppendStringToFile($ConfigFile, "\$StyleSheetPage = 'css';\n");
# Default
xpath_test(get_page('HomePage'),
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]');
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]');
# StyleSheetPage
update_page('css', "em { font-weight: bold; }", 'some css', 0, 1);
$page = get_page('HomePage');
negative_xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]');
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]');
xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://localhost/wiki.pl?action=browse;id=css;raw=1;mime-type=text/css"]');
@@ -34,7 +34,7 @@ xpath_test($page,
AppendStringToFile($ConfigFile, "\$StyleSheet = 'http://example.org/test.css';\n");
$page = get_page('HomePage');
negative_xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://localhost/wiki.pl?action=browse;id=css;raw=1;mime-type=text/css"]');
xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://example.org/test.css"]');
@@ -43,7 +43,7 @@ xpath_test($page,
AppendStringToFile($ConfigFile, "\$StyleSheet = ['http://example.org/test.css', 'http://example.org/another.css'];\n");
$page = get_page('HomePage');
negative_xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://localhost/wiki.pl?action=browse;id=css;raw=1;mime-type=text/css"]');
xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://example.org/test.css"]');
@@ -53,7 +53,7 @@ xpath_test($page,
# Parameter
$page = get_page('action=browse id=HomePage css=http://example.org/my.css');
negative_xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://localhost/wiki.pl?action=browse;id=css;raw=1;mime-type=text/css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://example.org/test.css"]');
xpath_test($page,

View File

@@ -23,6 +23,8 @@ SKIP: {
add_module('git.pl');
$ENV{LANG} = "en_US.UTF-8"; # test relies on English output
if (qx($GitBinary --version) !~ /git version/) {
skip "$GitBinary not found", 16;
}

View File

@@ -1,4 +1,5 @@
# Copyright (C) 2015 Alex-Daniel Jakimenko <alex.jakimenko@gmail.com>
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
@@ -14,7 +15,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 1;
use Test::More tests => 6;
use File::Basename;
@@ -23,9 +24,10 @@ require("$ModuleDir/load-lang.pl");
my %choosable_translations = reverse %TranslationsLibrary;
my @missing = (); # missing in load-lang.pl
my $count = 0;
foreach (bsd_glob("modules/translations/*.p[ml]")) {
my $filename = fileparse($_);
$count++;
next if exists $choosable_translations{$filename};
next if $filename eq 'new-utf8.pl'; # placeholder
next if $filename =~ /^month-names/; # month names are located in translations/ for whatever reason
@@ -37,4 +39,20 @@ unless (ok(@missing == 0, 'All translations are listed')) {
diag("$_ is not listed in load-lang.pl") for @missing;
}
# TODO test the module itself
unless (ok($count > 0, "$count translations were found")) {
diag("if 0 then the \$LoadLanguageDir was not set correctly");
}
test_page(get_page('Test'), 'Edit this page');
CreateDir($LoadLanguageDir);
WriteStringToFile("$LoadLanguageDir/german-utf8.pl", ReadFileOrDie("modules/translations/german-utf8.pl"));
# AppendStringToFile("$LoadLanguageDir/german-utf8.pl", "warn 'reading german-utf8.pl';\n");
$ENV{'HTTP_ACCEPT_LANGUAGE'} = 'de-ch,de;q=0.7,en;q=0.3';
test_page(get_page('action=version'), 'load-lang.pl');
my $page = get_page('Test');
test_page($page, 'Diese Seite bearbeiten');
test_page_negative($page, 'Edit this page');

View File

@@ -0,0 +1,69 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package OddMuse;
use Test::More;
use Test::Mojo;
require 't/test.pl';
add_module('namespaces.pl');
start_mojolicious_server();
sleep(1);
my $t = Test::Mojo->new;
# Installation worked
$t->get_ok("$ScriptName?action=version")
->content_like(qr/namespaces\.pl/);
# Edit a page in the Main namespace
$t->post_ok("$ScriptName"
=> form => {title => 'Some_Page',
text => 'This is the Main namespace.'})
->status_is(302);
$t->get_ok("$ScriptName/Some_Page")
->status_is(200)
->content_like(qr/This is the Main namespace/);
# Edit a page in the Five Winds namespace
$t->post_ok("$ScriptName/FiveWinds"
=> form => {title => 'Some_Page',
text => 'This is the Five Winds namespace.'})
->status_is(302);
$t->get_ok("$ScriptName/FiveWinds/Some_Page")
->status_is(200)
->content_like(qr/This is the Five Winds namespace/);
# This didn't overwrite the Main namespace.
$t->get_ok("$ScriptName/Some_Page")
->content_like(qr/This is the Main namespace/);
TODO: {
local $TODO = "Some bug in namespaces.pl remains";
diag "Waiting for the lock dir in RefreshIndex...";
# Umlauts
$t->post_ok("$ScriptName/F%C3%BCnfWinde"
=> form => {title => 'Some_Page',
text => 'Wir sind im Namensraum Fünf Winde.'})
->status_is(302);
$t->get_ok("$ScriptName/F%C3%BCnfWinde/Some_Page")
->status_is(200)
->content_like(qr/Wir sind im Namensraum Fünf Winde/);
}
done_testing();

35
t/mojolicious.t Normal file
View File

@@ -0,0 +1,35 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package OddMuse;
use Test::More;
use Test::Mojo;
require 't/test.pl';
start_mojolicious_server();
sleep(1);
my $t = Test::Mojo->new;
$t->get_ok("$ScriptName")->status_is(404)->content_like(qr/Welcome!/);
$t->get_ok("$ScriptName?action=admin")->status_is(200);
$t->post_ok("$ScriptName"
=> form => {title => 'HomePage', text => 'This is a test.'})
->status_is(302);
$t->get_ok("$ScriptName")->status_is(200)->content_like(qr/This is a test/);
done_testing();

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2015-2016 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
@@ -18,8 +18,14 @@ use Test::More tests => 2;
add_module('pygmentize.pl');
$ENV{PATH} = '.'; # pygmentize is not installed in the current directory
$page = apply_rules(newlines('{{{\ntest\n}}}\n'));
test_page($page,
'\bsh\b.*\bpygmentize\b.*\bnot found\b',
'<pre>test</pre>');
SKIP: {
if (qx(pygmentize -V) !~ /Pygments version/) {
skip "pygmentize not found", 2;
}
$ENV{PATH} = '.'; # pygmentize is not installed in the current directory
$page = apply_rules(newlines('{{{\ntest\n}}}\n'));
test_page($page,
'\bsh\b.*\bpygmentize\b.*\bnot found\b',
'<pre>test</pre>');
}

View File

@@ -15,7 +15,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 72;
use Test::More tests => 89;
use utf8; # tests contain UTF-8 characters and it matters
add_module('mac.pl');
@@ -116,6 +116,17 @@ xpath_test($page, '//a[@class="more"][@href="http://localhost/wiki.pl?search=Som
$page = get_page('search=Something preview=1 offset=10 num=10 replace=Other pwd=foo');
test_page($page, map { "Page_$_" } ('K' .. 'M'));
# Now do the replacement
$page = get_page('search=Something replace=Other pwd=foo');
test_page($page, 'Replaced: Something &#x2192; Other', '13 pages found',
map { "Page_$_" } ('A' .. 'M'));
# Verify that the change has been made
test_page(get_page('search=Other'), 'Search for: Other', '13 pages found');
# Replace with backreferences, where the replacement pattern is no longer found.
# Take 'fuuz and barz.' and replace ([a-z]+)z with x$1 results in 'xfuu and xbar.'
test_page(get_page('"search=([a-z]%2b)z" replace=x%241 pwd=foo'), '1 pages found');

33
t/server.t Normal file
View File

@@ -0,0 +1,33 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 4;
use LWP::UserAgent;
start_server();
# Give the child time to start
sleep 1;
# Check whether the child is up and running
my $ua = LWP::UserAgent->new;
my $response = $ua->get("$ScriptName?action=version");
ok($response->is_success, "There is a wiki running at $ScriptName");
like($response->content, qr/Oddmuse/, "It self-identifies as Oddmuse");
ok($ua->get("$ScriptName?title=Test;text=Testing")->is_success, "Page saved");
like($ua->get("$ScriptName/Test")->content, qr/Testing/, "Content verified");

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env perl
# Copyright (C) 2006, 2007 Alex Schroeder <alex@emacswiki.org>
# Copyright (C) 20062016 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,19 +16,71 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 6;
use Test::More tests => 9;
# basic test
add_module('sidebar.pl');
test_page(update_page($SidebarName, 'mu'), '<div class="sidebar"><p>mu</p></div>');
test_page(get_page('HomePage'), '<div class="sidebar"><p>mu</p></div>');
#FIXME: Due to the recent refactoring of the Table of Contents module, the
#Sidebar module is now known **not** to work as expected with that module.
#This would appear to be an unavoidable consequence of that refactoring... The
#Sidebar module, as currently implemented, **cannot** be made to work with the
#Table of Contents module. As such, we disable all prior tests against the
#Table of Contents module. It's hardly ideal. (But then, what is?)
# with images
add_module('image.pl');
# enable uploads
AppendStringToFile($ConfigFile, "\$UploadAllowed = 1;\n");
update_page('pic', "#FILE image/png\niVBORw0KGgoAAAA");
xpath_test(update_page($SidebarName, '[[image:pic|picture|Target]]'),
'//div[@class="sidebar"]/p/a[@class="image"][@href="http://localhost/wiki.pl/Target"]/img[@class="upload"][@src="http://localhost/wiki.pl/download/pic"][@alt="picture"]');
# with static-copy
add_module('static-copy.pl');
AppendStringToFile($ConfigFile, q{
$StaticAlways = 1;
$StaticDir = $DataDir . '/static';
$StaticUrl = '/static/';
%StaticMimeTypes = ('image/png' => 'png', );
@UploadTypes = ('image/png', );
});
update_page('pic', "DeletedPage");
update_page('pic', "#FILE image/png\niVBORw0KGgoAAAA", undef, 0, 1);
ok(-f "$DataDir/static/pic.png", "$DataDir/static/pic.png exists");
xpath_test(update_page($SidebarName, '[[image:pic|a picture|Target]]'),
'//div[@class="sidebar"]/p/a[@class="image"][@href="http://localhost/wiki.pl/Target"]/img[@class="upload"][@src="/static/pic.png"][@alt="a picture"]');
# with forms
add_module('forms.pl');
# Markup the sidebar page prior to locking the sidebar page. This should ensure
# that forms on that page are not interpreted.
test_page(update_page($SidebarName, '<form><h1>mu</h1></form>'),
'<div class="sidebar"><p>&lt;form&gt;&lt;h1&gt;mu&lt;/h1&gt;&lt;/form&gt;</p></div>');
# Lock the sidebar page, mark it up again, and ensure that forms on that page
# are now interpreted.
xpath_test(get_page("action=pagelock id=$SidebarName set=1 pwd=foo"),
'//p/text()[string()="Lock for "]/following-sibling::a[@href="http://localhost/wiki.pl/SideBar"][@class="local"][text()="SideBar"]/following-sibling::text()[string()=" created."]');
test_page(get_page("action=browse id=$SidebarName cache=0"), #update_page($SidebarName, '<form><h1>mu</h1></form>'),
'<div class="sidebar"><form><h1>mu</h1></form></div>');
# While rendering the SideBar as part of the HomePage, it should still
# be considered "locked", and therefore the form should render
# correctly.
test_page(get_page('HomePage'),
'<div class="sidebar"><form><h1>mu</h1></form></div>');
# FIXME: Due to the recent refactoring of the Table of Contents module, the
# Sidebar module is now known **not** to work as expected with that module.
# This would appear to be an unavoidable consequence of that refactoring... The
# Sidebar module, as currently implemented, **cannot** be made to work with the
# Table of Contents module. As such, we disable all prior tests against the
# Table of Contents module. It's hardly ideal. (But then, what is?)
# with toc
@@ -72,24 +124,3 @@ test_page(get_page('HomePage'), '<div class="sidebar"><p>mu</p></div>');
# remove_rule(\&TocRule);
# remove_rule(\&UsemodRule);
# with forms
add_module('forms.pl');
# Markup the sidebar page prior to locking the sidebar page. This should ensure
# that forms on that page are not interpreted.
test_page(update_page($SidebarName, '<form><h1>mu</h1></form>'),
'<div class="sidebar"><p>&lt;form&gt;&lt;h1&gt;mu&lt;/h1&gt;&lt;/form&gt;</p></div>');
# Lock the sidebar page, mark it up again, and ensure that forms on that page
# are now interpreted.
xpath_test(get_page("action=pagelock id=$SidebarName set=1 pwd=foo"),
'//p/text()[string()="Lock for "]/following-sibling::a[@href="http://localhost/wiki.pl/SideBar"][@class="local"][text()="SideBar"]/following-sibling::text()[string()=" created."]');
test_page(get_page("action=browse id=$SidebarName cache=0"), #update_page($SidebarName, '<form><h1>mu</h1></form>'),
'<div class="sidebar"><form><h1>mu</h1></form></div>');
# While rendering the SideBar as part of the HomePage, it should still
# be considered "locked", and therefore the form should render
# correctly.
test_page(get_page('HomePage'),
'<div class="sidebar"><form><h1>mu</h1></form></div>');

View File

@@ -16,7 +16,7 @@
require 't/test.pl';
package OddMuse;
use utf8;
use Test::More tests => 36;
use Test::More tests => 38;
add_module('static-copy.pl');
@@ -167,3 +167,17 @@ xpath_test(update_page('test_image', '[[image/right:bar baz]]'),
# Next, using a real page. The image type is used appropriately.
xpath_test(update_page('test_image', '[[image/right:Logo]]'),
'//a[@class="image right"][@href="http://localhost/wiki.pl/Logo"]/img[@class="upload"][@src="/static/Logo.png"][@alt="Logo"]');
my $weirdPage = 'Ï_lövé_¥ǫµnĩçȯḑë';
update_page($weirdPage, 'Some text');
update_page('Unicode', '[[' . $weirdPage . ']]');
get_page('action=static raw=1 pwd=foo html=1'); # generate static files
my ($status, $data) = ReadFile("$DataDir/static/Unicode.html");
xpath_test(get_page('Unicode'),
'//a[@class="local"]' . '[@href="http://localhost/wiki.pl/' . UrlEncode($weirdPage) . '"]');
xpath_test($data,
'//a[@class="local"]' . '[@href="' . UrlEncode($weirdPage) . '.html"]');

View File

@@ -353,6 +353,73 @@ sub clear_pages {
write_config_file();
}
# Find an unused port
sub random_port {
use Errno qw( EADDRINUSE );
use Socket qw( PF_INET SOCK_STREAM INADDR_ANY sockaddr_in );
my $family = PF_INET;
my $type = SOCK_STREAM;
my $proto = getprotobyname('tcp') or die "getprotobyname: $!";
my $host = INADDR_ANY; # Use inet_aton for a specific interface
for my $i (1..3) {
my $port = 1024 + int(rand(65535 - 1024));
socket(my $sock, $family, $type, $proto) or die "socket: $!";
my $name = sockaddr_in($port, $host) or die "sockaddr_in: $!";
setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, 1);
bind($sock, $name)
and close($sock)
and return $port;
die "bind: $!" if $! != EADDRINUSE;
print "Port $port in use, retrying...\n";
}
die "Tried 3 random ports and failed.\n"
}
my $pid;
# Fork a simple test server
sub start_server {
die "A server already exists: $pid\n" if $pid;
my $port = random_port();
$ScriptName = "http://localhost:$port";
AppendStringToFile($ConfigFile, "\$ScriptName = '$ScriptName';\n");
$pid = fork();
if (!defined $pid) {
die "Cannot fork: $!";
} elsif ($pid == 0) {
use Config;
my $secure_perl_path = $Config{perlpath};
exec($secure_perl_path, "stuff/server.pl", "wiki.pl", $port) or die "Cannot exec: $!";
}
}
# Fork a Mojolicious server
sub start_mojolicious_server {
die "A server already exists: $pid\n" if $pid;
my $port = random_port();
my $listen = "http://127.0.0.1:$port";
$ScriptName = "http://127.0.0.1:$port/wiki";
AppendStringToFile($ConfigFile, "\$ScriptName = '$ScriptName';\n");
$pid = fork();
if (!defined $pid) {
die "Cannot fork: $!";
} elsif ($pid == 0) {
use Config;
my $secure_perl_path = $Config{perlpath};
exec($secure_perl_path, "server.pl", "daemon", "-l", $listen)
or die "Cannot exec: $!";
}
}
END {
# kill server
if ($pid) {
kill 'KILL', $pid or warn "Could not kill server $pid";
}
}
sub RunAndTerminate { # runs a command for 1 second and then sends SIGTERM
my $pid = fork();
if (not $pid) { # child
@@ -374,3 +441,4 @@ sub AppendToConfig {
}
1;

31
t/umlaut.t Normal file
View File

@@ -0,0 +1,31 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 3;
use utf8;
my $dir = $ENV{WikiDataDir};
$dir .= "-ä";
utf8::encode($dir); # bytes
$ENV{WikiDataDir} = $dir;
# editing pages
test_page(get_page('Test'),
'<title>Wiki: Test</title>',
'Status: 404 NOT FOUND');
test_page(update_page('Test', 'Muuu!', 'first edit', undef, undef,
'username=Alex'),
'<p>Muuu!</p>');

16
wiki.pl
View File

@@ -305,7 +305,6 @@ sub InitVariables { # Init global session variables for mod_perl!
$LastUpdate = $ts;
unshift(@MyRules, \&MyRules) if defined(&MyRules) && (not @MyRules or $MyRules[0] != \&MyRules);
@MyRules = sort {$RuleOrder{$a} <=> $RuleOrder{$b}} @MyRules; # default is 0
ReportError(Ts('Cannot create %s', $DataDir) . ": $!", '500 INTERNAL SERVER ERROR') unless -d $DataDir;
@IndexOptions = (['pages', T('Include normal pages'), 1, \&AllPagesList]);
foreach my $sub (@MyInitVariables) {
my $result = $sub->();
@@ -2377,7 +2376,7 @@ sub GetCss { # prevent javascript injection
if ($IndexHash{$StyleSheetPage} and not @css) {
push (@css, "$ScriptName?action=browse;id=" . UrlEncode($StyleSheetPage) . ";raw=1;mime-type=text/css")
}
push (@css, 'http://www.oddmuse.org/default.css') unless @css;
push (@css, 'https://oddmuse.org/default.css') unless @css;
return join('', map { qq(<link type="text/css" rel="stylesheet" href="$_" />) } @css);
}
@@ -3409,6 +3408,11 @@ sub DoSearch {
if (GetParam('preview', '')) { # Preview button was used
print GetHeader('', Ts('Preview: %s', $string . " &#x2192; " . $replacement));
print $q->start_div({-class=>'content replacement'});
print GetFormStart(undef, 'post', 'replace');
print GetHiddenValue('search', $string);
print GetHiddenValue('replace', $replacement);
print GetHiddenValue('delete', GetParam('delete', 0));
print $q->submit(-value=>T('Go!')) . $q->end_form();
@results = ReplaceAndDiff($re, UnquoteHtml($replacement));
} else {
print GetHeader('', Ts('Replaced: %s', $string . " &#x2192; " . $replacement));
@@ -3580,7 +3584,7 @@ sub SearchExtract {
sub ReplaceAndSave {
my ($from, $to) = @_;
RequestLockOrError(); # fatal
my @result = Replace($from, $to, sub {
my @result = Replace($from, $to, 1, sub {
my ($id, $new) = @_;
Save($id, $new, $from . ' → ' . $to, 1, ($Page{host} ne $q->remote_addr()));
});
@@ -3590,7 +3594,7 @@ sub ReplaceAndSave {
sub ReplaceAndDiff {
my ($from, $to) = @_;
my @found = Replace($from, $to, sub {
my @found = Replace($from, $to, 0, sub {
my ($id, $new) = @_;
print $q->h2(GetPageLink($id)), $q->div({-class=>'diff'}, ImproveDiff(DoDiff($Page{text}, $new)));
});
@@ -3606,7 +3610,7 @@ sub ReplaceAndDiff {
}
sub Replace {
my ($from, $to, $func) = @_; # $func takes $id and $new text
my ($from, $to, $all, $func) = @_; # $func takes $id and $new text
my $lang = GetParam('lang', '');
my $num = GetParam('num', 10);
my $offset = GetParam('offset', 0);
@@ -3626,7 +3630,7 @@ sub Replace {
};
if (s/$from/$replacement->()/egi) { # allows use of backreferences
push (@result, $id);
$func->($id, $_) if @result > $offset and @result <= $offset + $num;
$func->($id, $_) if $all or @result > $offset and @result <= $offset + $num;
}
}
return @result;