summaryrefslogtreecommitdiff
path: root/IkiWiki/Setup.pm
blob: 7af744f6ab18fe2bc0132891ce7c4fc3a461040f (plain)
  1. #!/usr/bin/perl
  2. # Ikiwiki setup files can be perl files that 'use IkiWiki::Setup::foo',
  3. # passing it some sort of configuration data. Or, they can contain
  4. # the module name at the top, without the 'use', and the whole file is
  5. # then fed into that module.
  6. package IkiWiki::Setup;
  7. use warnings;
  8. use strict;
  9. use IkiWiki;
  10. use open qw{:utf8 :std};
  11. use File::Spec;
  12. sub load ($;$) {
  13. my $file=IkiWiki::possibly_foolish_untaint(shift);
  14. my $safemode=shift;
  15. $config{setupfile}=File::Spec->rel2abs($file);
  16. #translators: The first parameter is a filename, and the second
  17. #translators: is a (probably not translated) error message.
  18. open (IN, $file) || error(sprintf(gettext("cannot read %s: %s"), $file, $!));
  19. my $content;
  20. {
  21. local $/=undef;
  22. $content=<IN> || error("$file: $!");
  23. }
  24. close IN;
  25. if ($content=~/((?:use|require)\s+)?IkiWiki::Setup::(\w+)/) {
  26. $config{setuptype}=$2;
  27. if ($1) {
  28. error sprintf(gettext("cannot load %s in safe mode"), $file)
  29. if $safemode;
  30. no warnings;
  31. eval IkiWiki::possibly_foolish_untaint($content);
  32. error("$file: ".$@) if $@;
  33. }
  34. else {
  35. eval qq{require IkiWiki::Setup::$config{setuptype}};
  36. error $@ if $@;
  37. "IkiWiki::Setup::$config{setuptype}"->loaddump(IkiWiki::possibly_foolish_untaint($content));
  38. }
  39. }
  40. else {
  41. error sprintf(gettext("failed to parse %s"), $file);
  42. }
  43. }
  44. sub dump ($) {
  45. my $file=IkiWiki::possibly_foolish_untaint(shift);
  46. my @header=(
  47. "Setup file for ikiwiki.",
  48. "",
  49. "Passing this to ikiwiki --setup will make ikiwiki generate",
  50. "wrappers and build the wiki.",
  51. "",
  52. "Remember to re-run ikiwiki --setup any time you edit this file.",
  53. );
  54. # Fork because dumping setup requires loading all plugins.
  55. my $pid=fork();
  56. if ($pid == 0) {
  57. eval qq{require IkiWiki::Setup::$config{setuptype}};
  58. error $@ if $@;
  59. my @dump="IkiWiki::Setup::$config{setuptype}"->gendump(@header);
  60. open (OUT, ">", $file) || die "$file: $!";
  61. print OUT "$_\n" foreach @dump;
  62. close OUT;
  63. exit 0;
  64. }
  65. else {
  66. waitpid $pid, 0;
  67. exit($? >> 8) if $? >> 8;
  68. exit(1) if $?;
  69. }
  70. }
  71. sub merge ($) {
  72. # Merge setup into existing config and untaint.
  73. my %setup=%{shift()};
  74. if (exists $setup{add_plugins} && exists $config{add_plugins}) {
  75. push @{$setup{add_plugins}}, @{$config{add_plugins}};
  76. }
  77. if (exists $setup{exclude}) {
  78. push @{$config{wiki_file_prune_regexps}}, $setup{exclude};
  79. }
  80. foreach my $c (keys %setup) {
  81. if (defined $setup{$c}) {
  82. if (! ref $setup{$c} || ref $setup{$c} eq 'Regexp') {
  83. $config{$c}=IkiWiki::possibly_foolish_untaint($setup{$c});
  84. }
  85. elsif (ref $setup{$c} eq 'ARRAY') {
  86. if ($c eq 'wrappers') {
  87. # backwards compatability code
  88. $config{$c}=$setup{$c};
  89. }
  90. else {
  91. $config{$c}=[map { IkiWiki::possibly_foolish_untaint($_) } @{$setup{$c}}]
  92. }
  93. }
  94. elsif (ref $setup{$c} eq 'HASH') {
  95. foreach my $key (keys %{$setup{$c}}) {
  96. $config{$c}{$key}=IkiWiki::possibly_foolish_untaint($setup{$c}{$key});
  97. }
  98. }
  99. }
  100. else {
  101. $config{$c}=undef;
  102. }
  103. }
  104. if (length $config{cgi_wrapper}) {
  105. push @{$config{wrappers}}, {
  106. cgi => 1,
  107. wrapper => $config{cgi_wrapper},
  108. wrappermode => (defined $config{cgi_wrappermode} ? $config{cgi_wrappermode} : "06755"),
  109. };
  110. }
  111. }
  112. sub getsetup () {
  113. # Gets all available setup data from all plugins. Returns an
  114. # ordered list of [plugin, setup] pairs.
  115. # disable logging to syslog while dumping, broken plugins may
  116. # whine when loaded
  117. my $syslog=$config{syslog};
  118. $config{syslog}=undef;
  119. # Load all plugins, so that all setup options are available.
  120. my @plugins=IkiWiki::listplugins();
  121. foreach my $plugin (@plugins) {
  122. eval { IkiWiki::loadplugin($plugin, 1) };
  123. if (exists $IkiWiki::hooks{checkconfig}{$plugin}{call}) {
  124. my @s=eval { $IkiWiki::hooks{checkconfig}{$plugin}{call}->() };
  125. }
  126. }
  127. my %sections;
  128. foreach my $plugin (@plugins) {
  129. if (exists $IkiWiki::hooks{getsetup}{$plugin}{call}) {
  130. # use an array rather than a hash, to preserve order
  131. my @s=eval { $IkiWiki::hooks{getsetup}{$plugin}{call}->() };
  132. next unless @s;
  133. # set default section value (note use of shared
  134. # hashref between array and hash)
  135. my %s=@s;
  136. if (! exists $s{plugin} || ! $s{plugin}->{section}) {
  137. $s{plugin}->{section}="other";
  138. }
  139. # only the selected rcs plugin is included
  140. if ($config{rcs} && $plugin eq $config{rcs}) {
  141. $s{plugin}->{section}="core";
  142. }
  143. elsif ($s{plugin}->{section} eq "rcs") {
  144. next;
  145. }
  146. push @{$sections{$s{plugin}->{section}}}, [ $plugin, \@s ];
  147. }
  148. }
  149. $config{syslog}=$syslog;
  150. return map { sort { $a->[0] cmp $b->[0] } @{$sections{$_}} }
  151. sort { # core first, other last, otherwise alphabetical
  152. ($b eq "core") <=> ($a eq "core")
  153. ||
  154. ($a eq "other") <=> ($b eq "other")
  155. ||
  156. $a cmp $b
  157. } keys %sections;
  158. }
  159. sub commented_dump ($$) {
  160. my $dumpline=shift;
  161. my $indent=shift;
  162. my %setup=(%config);
  163. my @ret;
  164. # disable logging to syslog while dumping
  165. $config{syslog}=undef;
  166. eval q{use Text::Wrap};
  167. die $@ if $@;
  168. my %section_plugins;
  169. push @ret, commented_dumpvalues($dumpline, $indent, \%setup, IkiWiki::getsetup());
  170. foreach my $pair (IkiWiki::Setup::getsetup()) {
  171. my $plugin=$pair->[0];
  172. my $setup=$pair->[1];
  173. my %s=@{$setup};
  174. my $section=$s{plugin}->{section};
  175. push @{$section_plugins{$section}}, $plugin;
  176. if (@{$section_plugins{$section}} == 1) {
  177. push @ret, "", $indent.("#" x 70), "$indent# $section plugins",
  178. sub {
  179. wrap("$indent# (", "$indent# ",
  180. join(", ", @{$section_plugins{$section}})).")"
  181. },
  182. $indent.("#" x 70);
  183. }
  184. my @values=commented_dumpvalues($dumpline, $indent, \%setup, @{$setup});
  185. if (@values) {
  186. push @ret, "", "$indent# $plugin plugin", @values;
  187. }
  188. }
  189. return map { ref $_ ? $_->() : $_ } @ret;
  190. }
  191. sub commented_dumpvalues ($$$@) {
  192. my $dumpline=shift;
  193. my $indent=shift;
  194. my $setup=shift;
  195. my @ret;
  196. while (@_) {
  197. my $key=shift;
  198. my %info=%{shift()};
  199. next if $key eq "plugin" || $info{type} eq "internal";
  200. push @ret, "$indent# ".$info{description} if exists $info{description};
  201. if (exists $setup->{$key} && defined $setup->{$key}) {
  202. push @ret, $dumpline->($key, $setup->{$key}, $info{type}, "");
  203. delete $setup->{$key};
  204. }
  205. elsif (exists $info{example}) {
  206. push @ret, $dumpline->($key, $info{example}, $info{type}, "#");
  207. }
  208. else {
  209. push @ret, $dumpline->($key, "", $info{type}, "#");
  210. }
  211. }
  212. return @ret;
  213. }
  214. 1