1
# See bottom of file for license and copyright information
3
# This is a both parser for configuration declaration files, such as
4
# FoswikiCfg.spec, and a serialisation visitor for writing out changes
7
# The supported syntax in declaration files is as follows:
9
# cfg ::= ( setting | section | extension )* ;
10
# setting ::= BOL typespec EOL comment* BOL def ;
11
# typespec ::= "# **" id options "**" ;
12
# def ::= "$" ["Foswiki::"] "cfg" keys "=" value ";" ;
13
# keys ::= ( "{" id "}" )+ ;
14
# value is any perl value not including ";"
15
# comment ::= BOL "#" string EOL ;
16
# section ::= BOL "#--++" string EOL comment* ;
17
# extension ::= BOL " *" id "*"
19
# BOL ::= beginning of line
20
# id ::= a \w+ word (legal Perl bareword)
22
# * A *section* is simply a divider used to create foldable blocks. It can
23
# have varying depth depending on the number of + signs
24
# * A *setting* is the sugar required for the setting of a single
25
# configuration value.
26
# * An *extension* is a pluggable UI extension that supports some extra UI
27
# functionality, such as the menu of languages or the menu of plugins.
29
# Each *setting* has a *typespec* and a *def*.
31
# The typespec consists of a type id and some options. Types are loaded by
32
# type id from the Foswiki::Configure::Types hierachy - for example, type
33
# BOOLEAN is defined by Foswiki::Configure::Types::BOOLEAN. Each type is a
34
# subclass of Foswiki::Configure::Type - see that class for more details of
37
# A *def* is a specification of a field in the $Foswiki::cfg hash, together with
38
# a perl value for that hash. Each field can have an associated *Checker*
39
# which is loaded from the Foswiki::Configure::Checkers hierarchy. Checkers
40
# are responsible for specific checks on the value of that variable. For
41
# example, the checker for $Foswiki::cfg{Banana}{Republic} will be expected
42
# to be found in Foswiki::Configure::Checkers::Banana::Republic.
43
# Checkers are subclasses of Foswiki::Configure::Checker. See that class for
46
# An *extension* is a placeholder for a pluggable UI module.
48
package Foswiki::Configure::FoswikiCfg;
53
use Foswiki::Configure::Section;
54
use Foswiki::Configure::Value;
55
use Foswiki::Configure::Pluggable;
56
use Foswiki::Configure::Item;
58
# Used in saving, when we need a callback. Otherwise the methods here are
63
return bless( {}, $class );
66
# Load the configuration declarations. The core set is defined in
67
# Foswiki.spec, which must be found on the @INC path and is always loaded
68
# first. Then find all settings for extensions in their .spec files.
70
# This *only* reads type specifications, it *does not* read values.
72
# SEE ALSO Foswiki::Configure::Load::readDefaults
74
my ( $root, $haveLSC ) = @_;
76
my $file = Foswiki::findFileOnPath('Foswiki.spec');
78
_parse( $file, $root, $haveLSC );
82
foreach my $dir (@INC) {
83
_loadSpecsFrom( "$dir/Foswiki/Plugins", $root, \%read );
84
_loadSpecsFrom( "$dir/Foswiki/Contrib", $root, \%read );
85
_loadSpecsFrom( "$dir/TWiki/Plugins", $root, \%read );
86
_loadSpecsFrom( "$dir/TWiki/Contrib", $root, \%read );
92
my ( $dir, $root, $read ) = @_;
94
return unless opendir( D, $dir );
95
foreach my $extension ( grep { !/^\./ } readdir D ) {
96
next if $read->{$extension};
98
$extension = $1; # untaint
99
my $file = "$dir/$extension/Config.spec";
100
next unless -e $file;
101
_parse( $file, $root, 1 );
102
$read->{$extension} = $file;
107
###########################################################################
109
###########################################################################
112
# Inner class that represents section headings temporarily during the
113
# parse. They are expanded to section blocks at the end.
114
package SectionMarker;
116
use base 'Foswiki::Configure::Item';
119
my ( $class, $depth, $head ) = @_;
120
my $this = bless( {}, $class );
121
$this->{depth} = $depth + 1;
122
$this->{head} = $head;
126
sub getValueObject { return undef; }
129
# Process the config array and add section objects
130
sub _extractSections {
131
my ( $settings, $root ) = @_;
136
foreach my $item (@$settings) {
137
if ( $item->isa('SectionMarker') ) {
139
$root->getSectionObject( $item->{head}, $item->{depth} + 1 );
141
$depth = $item->{depth};
144
while ( $depth > $item->{depth} - 1 ) {
145
$section = $section->{parent};
148
while ( $depth < $item->{depth} - 1 ) {
149
my $ns = new Foswiki::Configure::Section('');
150
$section->addChild($ns);
154
$ns = new Foswiki::Configure::Section( $item->{head} );
155
$ns->{desc} = $item->{desc};
156
$section->addChild($ns);
161
elsif ( $item->isa('Foswiki::Configure::Value') ) {
163
# Skip it if we already have a settings object for these
164
# keys (first loaded always takes precedence, irrespective
165
# of which section it is in)
166
my $vo = $root->getValueObject( $item->getKeys() );
168
$section->addChild($item);
171
$section->addChild($item);
176
# See if we have already build a value object for these keys
177
sub _getValueObject {
178
my ( $keys, $settings ) = @_;
179
foreach my $item (@$settings) {
180
my $i = $item->getValueObject($keys);
186
# Parse the config declaration file and return a root node for the
187
# configuration it describes
189
my ( $file, $root, $haveLSC ) = @_;
191
open( F, "<$file" ) || return '';
197
foreach my $l (<F>) {
198
if ( $l =~ /^#\s*\*\*\s*([A-Z]+)\s*(.*?)\s*\*\*\s*$/ ) {
199
pusht( \@settings, $open ) if $open;
200
$open = new Foswiki::Configure::Value( typename => $1, opts => $2 );
203
elsif ( $l =~ /^#?\s*\$(?:(?:Fosw|TW)iki::)?cfg([^=\s]*)\s*=(.*)$/ ) {
205
my $tentativeVal = $2;
206
if ( $open && $open->isa('SectionMarker') ) {
207
pusht( \@settings, $open );
211
# If there is already a UI object for
212
# these keys, we don't need to add another. But if there
215
next if $root->getValueObject($keys);
216
next if ( _getValueObject( $keys, \@settings ) );
218
# This is an untyped value
219
$open = new Foswiki::Configure::Value();
221
$open->set( keys => $keys );
222
pusht( \@settings, $open );
226
elsif ( $l =~ /^#\s*\*([A-Z]+)\*/ ) {
228
my $p = Foswiki::Configure::Pluggable::load($pluggable);
230
pusht( \@settings, $open ) if $open;
235
$open->addToDesc($l);
239
elsif ( $l =~ /^#\s*---\+(\+*) *(.*?)$/ ) {
241
# Only load the first section if we don't have LocalSite.cfg
242
last if ( $sectionNum && !$haveLSC );
244
pusht( \@settings, $open ) if $open;
245
$open = new SectionMarker( length($1), $2 );
248
elsif ( $l =~ /^#\s?(.*)$/ ) {
249
$open->addToDesc($1) if $open;
253
pusht( \@settings, $open ) if $open;
254
_extractSections( \@settings, $root );
259
foreach my $v (@$a) {
260
Carp::confess "$n" if $v eq $n;
265
###########################################################################
267
###########################################################################
269
# Generate .cfg file format output
271
my ( $root, $valuer, $logger ) = @_;
273
# Object used to act as a visitor to hold the output
274
my $this = new Foswiki::Configure::FoswikiCfg();
275
$this->{logger} = $logger;
276
$this->{valuer} = $valuer;
277
$this->{root} = $root;
278
$this->{content} = '';
280
my $lsc = Foswiki::findFileOnPath('LocalSite.cfg');
283
# If not found on the path, park it beside Foswiki.spec
284
$lsc = Foswiki::findFileOnPath('Foswiki.spec') || '';
285
$lsc =~ s/Foswiki\.spec/LocalSite.cfg/;
288
if ( open( F, '<' . $lsc ) ) {
290
$this->{content} = <F>;
294
$this->{content} = <<'HERE';
295
# Local site settings for Foswiki. This file is managed by the 'configure'
296
# CGI script, though you can also make (careful!) manual changes with a
301
my $out = $this->_save();
302
open( F, '>' . $lsc )
303
|| die "Could not open $lsc for write: $!";
304
print F $this->{content};
313
$this->{content} =~ s/\s*1;\s*$/\n/sg;
314
$this->{root}->visit($this);
315
$this->{content} .= "1;\n";
318
# Visitor method called by node traversal during save. Incrementally modify
319
# values, unless a value is reverting to the default in which case remove it.
321
my ( $this, $visitee ) = @_;
323
if ( $visitee->isa('Foswiki::Configure::Value') ) {
324
my $keys = $visitee->getKeys();
325
my $warble = $this->{valuer}->currentValue($visitee);
326
return 1 unless defined $warble;
328
# For some reason Data::Dumper ignores the second parameter sometimes
329
# when -T is enabled, so have to do a substitution
330
my $txt = Data::Dumper->Dump( [$warble] );
331
$txt =~ s/VAR1/Foswiki::cfg$keys/;
332
if ( $this->{logger} ) {
333
$this->{logger}->logChange( $visitee->getKeys(), $txt );
336
# Substitute any existing value, or append if not there
337
unless ( $this->{content} =~ s/\$(Foswiki::)?cfg$keys\s*=.*?;\n/$txt/s )
339
$this->{content} .= $txt;
346
my ( $this, $visitee ) = @_;
354
# Foswiki - The Free and Open Source Wiki, http://foswiki.org/
356
# Copyright (C) 2008 Foswiki Contributors. All Rights Reserved.
357
# Foswiki Contributors are listed in the AUTHORS file in the root
358
# of this distribution. NOTE: Please extend that file, not this notice.
360
# Additional copyrights apply to some or all of the code in this
363
# Copyright (C) 2000-2006 TWiki Contributors. All Rights Reserved.
364
# TWiki Contributors are listed in the AUTHORS file in the root
365
# of this distribution. NOTE: Please extend that file, not this notice.
367
# This program is free software; you can redistribute it and/or
368
# modify it under the terms of the GNU General Public License
369
# as published by the Free Software Foundation; either version 2
370
# of the License, or (at your option) any later version. For
371
# more details read LICENSE in the root of this distribution.
373
# This program is distributed in the hope that it will be useful,
374
# but WITHOUT ANY WARRANTY; without even the implied warranty of
375
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
377
# As per the GPL, removal of this notice is prohibited.