11 my $backlinks_calculated=0;
13 sub calculate_backlinks () { #{{{
14 return if $backlinks_calculated;
16 foreach my $page (keys %links) {
17 foreach my $link (@{$links{$page}}) {
18 my $bestlink=bestlink($page, $link);
19 if (length $bestlink && $bestlink ne $page) {
20 $backlinks{$bestlink}{$page}=1;
24 $backlinks_calculated=1;
27 sub backlinks ($) { #{{{
30 calculate_backlinks();
33 foreach my $p (keys %{$backlinks{$page}}) {
34 my $href=urlto($p, $page);
36 # Trim common dir prefixes from both pages.
38 my $page_trimmed=$page;
40 1 while (($dir)=$page_trimmed=~m!^([^/]+/)!) &&
42 $p_trimmed=~s/^\Q$dir\E// &&
43 $page_trimmed=~s/^\Q$dir\E//;
45 push @links, { url => $href, page => pagetitle($p_trimmed) };
47 @links = sort { $a->{page} cmp $b->{page} } @links;
49 return \@links, [] if @links <= $config{numbacklinks} || ! $config{numbacklinks};
50 return [@links[0..$config{numbacklinks}-1]],
51 [@links[$config{numbacklinks}..$#links]];
54 sub parentlinks ($) { #{{{
60 my $title=$config{wikiname};
62 return if $page eq 'index'; # toplevel
63 foreach my $dir (split("/", $page)) {
64 push @ret, { url => urlto($path, $page), page => $title };
66 $title=pagetitle($dir);
71 sub genpage ($$$) { #{{{
76 my $template=template("page.tmpl", blind_cache => 1);
79 if (length $config{cgiurl}) {
80 $template->param(editurl => cgiurl(do => "edit", page => pagetitle($page, 1)));
81 $template->param(prefsurl => cgiurl(do => "prefs"));
83 $template->param(recentchangesurl => cgiurl(do => "recentchanges"));
88 if (length $config{historyurl}) {
89 my $u=$config{historyurl};
90 $u=~s/\[\[file\]\]/$pagesources{$page}/g;
91 $template->param(historyurl => $u);
94 if ($config{discussion}) {
95 my $discussionlink=gettext("discussion");
96 if ($page !~ /.*\/\Q$discussionlink\E$/ &&
97 (length $config{cgiurl} ||
98 exists $links{$page."/".$discussionlink})) {
99 $template->param(discussionlink => htmllink($page, $page, gettext("Discussion"), noimageinline => 1, forcesubpage => 1));
105 $template->param(have_actions => 1);
108 my ($backlinks, $more_backlinks)=backlinks($page);
111 title => $page eq 'index'
113 : pagetitle(basename($page)),
114 wikiname => $config{wikiname},
115 parentlinks => [parentlinks($page)],
117 backlinks => $backlinks,
118 more_backlinks => $more_backlinks,
119 mtime => displaytime($mtime),
120 baseurl => baseurl($page),
123 run_hooks(pagetemplate => sub {
124 shift->(page => $page, destpage => $page, template => $template);
127 $content=$template->output;
129 run_hooks(format => sub {
142 return (stat($file))[9];
148 my $type=pagetype($file);
150 my $srcfile=srcfile($file);
151 my $content=readfile($srcfile);
152 my $page=pagename($file);
153 will_render($page, htmlpage($page), 1);
155 # Always needs to be done, since filters might add links
157 $content=filter($page, $content);
160 while ($content =~ /(?<!\\)$config{wiki_link_regexp}/g) {
161 push @links, linkpage($2);
163 if ($config{discussion}) {
164 # Discussion links are a special case since they're
165 # not in the text of the page, but on its template.
166 push @links, $page."/".gettext("discussion");
168 $links{$page}=\@links;
170 # Preprocess in scan-only mode.
171 preprocess($page, $page, $content, 1);
174 will_render($file, $file, 1);
178 sub render ($) { #{{{
181 my $type=pagetype($file);
182 my $srcfile=srcfile($file);
184 my $content=readfile($srcfile);
185 my $page=pagename($file);
186 delete $depends{$page};
187 will_render($page, htmlpage($page), 1);
189 $content=filter($page, $content);
190 $content=preprocess($page, $page, $content);
191 $content=linkify($page, $page, $content);
192 $content=htmlize($page, $type, $content);
194 writefile(htmlpage($page), $config{destdir},
195 genpage($page, $content, mtime($srcfile)));
198 my $srcfd=readfile($srcfile, 1, 1);
199 delete $depends{$file};
200 will_render($file, $file, 1);
201 writefile($file, $config{destdir}, undef, 1, sub {
206 my ($len, $buf, $written);
207 while ($len = sysread $srcfd, $buf, $blksize) {
208 if (! defined $len) {
209 next if $! =~ /^Interrupted/;
210 error("failed to read $srcfile: $!", $cleanup);
214 defined($written = syswrite $destfd, $buf, $len, $offset)
215 or error("failed to write $file: $!", $cleanup);
228 my $dir=dirname($file);
229 while (rmdir($dir)) {
234 sub refresh () { #{{{
235 # find existing pages
238 eval q{use File::Find};
244 if (file_pruned($_, $config{srcdir})) {
245 $File::Find::prune=1;
247 elsif (! -d $_ && ! -l $_) {
248 my ($f)=/$config{wiki_file_regexp}/; # untaint
250 warn(sprintf(gettext("skipping bad filename %s"), $_)."\n");
253 $f=~s/^\Q$config{srcdir}\E\/?//;
255 $exists{pagename($f)}=1;
264 if (file_pruned($_, $config{underlaydir})) {
265 $File::Find::prune=1;
267 elsif (! -d $_ && ! -l $_) {
268 my ($f)=/$config{wiki_file_regexp}/; # untaint
270 warn(sprintf(gettext("skipping bad filename %s"), $_)."\n");
273 # Don't add pages that are in the
275 $f=~s/^\Q$config{underlaydir}\E\/?//;
276 if (! -e "$config{srcdir}/$f" &&
277 ! -l "$config{srcdir}/$f") {
278 my $page=pagename($f);
279 if (! $exists{$page}) {
287 }, $config{underlaydir});
291 # check for added or removed pages
293 foreach my $file (@files) {
294 my $page=pagename($file);
295 $pagesources{$page}=$file;
296 if (! $pagemtime{$page}) {
298 $pagecase{lc $page}=$page;
299 if ($config{getctime} && -e "$config{srcdir}/$file") {
300 $pagectime{$page}=rcs_getctime("$config{srcdir}/$file");
302 elsif (! exists $pagectime{$page}) {
303 $pagectime{$page}=mtime(srcfile($file));
308 foreach my $page (keys %pagemtime) {
309 if (! $exists{$page}) {
310 debug(sprintf(gettext("removing old page %s"), $page));
311 push @del, $pagesources{$page};
313 $renderedfiles{$page}=[];
315 prune($config{destdir}."/".$_)
316 foreach @{$oldrenderedfiles{$page}};
317 delete $pagesources{$page};
321 # scan changed and new files
323 foreach my $file (@files) {
324 my $page=pagename($file);
326 my $mtime=mtime(srcfile($file));
327 if (! exists $pagemtime{$page} ||
328 $mtime > $pagemtime{$page} ||
329 $forcerebuild{$page}) {
330 debug(sprintf(gettext("scanning %s"), $file));
331 $pagemtime{$page}=$mtime;
332 push @changed, $file;
336 calculate_backlinks();
338 # render changed and new pages
339 foreach my $file (@changed) {
340 debug(sprintf(gettext("rendering %s"), $file));
345 # rebuild pages that link to added or removed pages
347 foreach my $f (@add, @del) {
349 foreach my $page (keys %{$backlinks{$p}}) {
350 my $file=$pagesources{$page};
351 next if $rendered{$file};
352 debug(sprintf(gettext("rendering %s, which links to %s"), $file, $p));
359 if (%rendered || @del) {
360 # rebuild dependant pages
361 foreach my $f (@files) {
362 next if $rendered{$f};
364 if (exists $depends{$p}) {
365 foreach my $file (keys %rendered, @del) {
367 my $page=pagename($file);
368 if (pagespec_match($page, $depends{$p}, location => $p)) {
369 debug(sprintf(gettext("rendering %s, which depends on %s"), $f, $page));
378 # handle backlinks; if a page has added/removed links,
379 # update the pages it links to
381 foreach my $file (keys %rendered, @del) {
382 my $page=pagename($file);
384 if (exists $links{$page}) {
385 foreach my $link (map { bestlink($page, $_) } @{$links{$page}}) {
387 (! exists $oldlinks{$page} ||
388 ! grep { bestlink($page, $_) eq $link } @{$oldlinks{$page}})) {
389 $linkchanged{$link}=1;
393 if (exists $oldlinks{$page}) {
394 foreach my $link (map { bestlink($page, $_) } @{$oldlinks{$page}}) {
396 (! exists $links{$page} ||
397 ! grep { bestlink($page, $_) eq $link } @{$links{$page}})) {
398 $linkchanged{$link}=1;
403 foreach my $link (keys %linkchanged) {
404 my $linkfile=$pagesources{$link};
405 if (defined $linkfile) {
406 next if $rendered{$linkfile};
407 debug(sprintf(gettext("rendering %s, to update its backlinks"), $linkfile));
409 $rendered{$linkfile}=1;
414 # remove no longer rendered files
415 foreach my $src (keys %rendered) {
416 my $page=pagename($src);
417 foreach my $file (@{$oldrenderedfiles{$page}}) {
418 if (! grep { $_ eq $file } @{$renderedfiles{$page}}) {
419 debug(sprintf(gettext("removing %s, no longer rendered by %s"), $file, $page));
420 prune($config{destdir}."/".$file);
426 run_hooks(delete => sub { shift->(@del) });
429 run_hooks(change => sub { shift->(keys %rendered) });
433 sub commandline_render () { #{{{
440 my $srcfile=possibly_foolish_untaint($config{render});
442 $file=~s/\Q$config{srcdir}\E\/?//;
444 my $type=pagetype($file);
445 die sprintf(gettext("ikiwiki: cannot render %s"), $srcfile)."\n" unless defined $type;
446 my $content=readfile($srcfile);
447 my $page=pagename($file);
448 $pagesources{$page}=$file;
449 $content=filter($page, $content);
450 $content=preprocess($page, $page, $content);
451 $content=linkify($page, $page, $content);
452 $content=htmlize($page, $type, $content);
454 print genpage($page, $content, mtime($srcfile));