~kosova/+junk/tuxfamily-twiki

« back to all changes in this revision

Viewing changes to foswiki/lib/Foswiki/Configure/FoswikiCfg.pm

  • Committer: James Michael DuPont
  • Date: 2009-07-18 19:58:49 UTC
  • Revision ID: jamesmikedupont@gmail.com-20090718195849-vgbmaht2ys791uo2
added foswiki

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# See bottom of file for license and copyright information
 
2
 
 
3
# This is a both parser for configuration declaration files, such as
 
4
# FoswikiCfg.spec, and a serialisation visitor for writing out changes
 
5
# to LocalSite.cfg
 
6
#
 
7
# The supported syntax in declaration files is as follows:
 
8
#
 
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 "*"
 
18
# EOL ::= end of line
 
19
# BOL ::= beginning of line
 
20
# id ::= a \w+ word (legal Perl bareword)
 
21
#
 
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.
 
28
#
 
29
# Each *setting* has a *typespec* and a *def*.
 
30
#
 
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
 
35
# what is supported.
 
36
#
 
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
 
44
# more details.
 
45
#
 
46
# An *extension* is a placeholder for a pluggable UI module.
 
47
#
 
48
package Foswiki::Configure::FoswikiCfg;
 
49
 
 
50
use strict;
 
51
use Data::Dumper;
 
52
 
 
53
use Foswiki::Configure::Section;
 
54
use Foswiki::Configure::Value;
 
55
use Foswiki::Configure::Pluggable;
 
56
use Foswiki::Configure::Item;
 
57
 
 
58
# Used in saving, when we need a callback. Otherwise the methods here are
 
59
# all static.
 
60
sub new {
 
61
    my $class = shift;
 
62
 
 
63
    return bless( {}, $class );
 
64
}
 
65
 
 
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.
 
69
#
 
70
# This *only* reads type specifications, it *does not* read values.
 
71
#
 
72
# SEE ALSO Foswiki::Configure::Load::readDefaults
 
73
sub load {
 
74
    my ( $root, $haveLSC ) = @_;
 
75
 
 
76
    my $file = Foswiki::findFileOnPath('Foswiki.spec');
 
77
    if ($file) {
 
78
        _parse( $file, $root, $haveLSC );
 
79
    }
 
80
    if ($haveLSC) {
 
81
        my %read;
 
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 );
 
87
        }
 
88
    }
 
89
}
 
90
 
 
91
sub _loadSpecsFrom {
 
92
    my ( $dir, $root, $read ) = @_;
 
93
 
 
94
    return unless opendir( D, $dir );
 
95
    foreach my $extension ( grep { !/^\./ } readdir D ) {
 
96
        next if $read->{$extension};
 
97
        $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;
 
103
    }
 
104
    closedir(D);
 
105
}
 
106
 
 
107
###########################################################################
 
108
## INPUT
 
109
###########################################################################
 
110
{
 
111
 
 
112
    # Inner class that represents section headings temporarily during the
 
113
    # parse. They are expanded to section blocks at the end.
 
114
    package SectionMarker;
 
115
 
 
116
    use base 'Foswiki::Configure::Item';
 
117
 
 
118
    sub new {
 
119
        my ( $class, $depth, $head ) = @_;
 
120
        my $this = bless( {}, $class );
 
121
        $this->{depth} = $depth + 1;
 
122
        $this->{head}  = $head;
 
123
        return $this;
 
124
    }
 
125
 
 
126
    sub getValueObject { return undef; }
 
127
}
 
128
 
 
129
# Process the config array and add section objects
 
130
sub _extractSections {
 
131
    my ( $settings, $root ) = @_;
 
132
 
 
133
    my $section = $root;
 
134
    my $depth   = 0;
 
135
 
 
136
    foreach my $item (@$settings) {
 
137
        if ( $item->isa('SectionMarker') ) {
 
138
            my $ns =
 
139
              $root->getSectionObject( $item->{head}, $item->{depth} + 1 );
 
140
            if ($ns) {
 
141
                $depth = $item->{depth};
 
142
            }
 
143
            else {
 
144
                while ( $depth > $item->{depth} - 1 ) {
 
145
                    $section = $section->{parent};
 
146
                    $depth--;
 
147
                }
 
148
                while ( $depth < $item->{depth} - 1 ) {
 
149
                    my $ns = new Foswiki::Configure::Section('');
 
150
                    $section->addChild($ns);
 
151
                    $section = $ns;
 
152
                    $depth++;
 
153
                }
 
154
                $ns = new Foswiki::Configure::Section( $item->{head} );
 
155
                $ns->{desc} = $item->{desc};
 
156
                $section->addChild($ns);
 
157
                $depth++;
 
158
            }
 
159
            $section = $ns;
 
160
        }
 
161
        elsif ( $item->isa('Foswiki::Configure::Value') ) {
 
162
 
 
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() );
 
167
            next if ($vo);
 
168
            $section->addChild($item);
 
169
        }
 
170
        else {
 
171
            $section->addChild($item);
 
172
        }
 
173
    }
 
174
}
 
175
 
 
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);
 
181
        return $i if $i;
 
182
    }
 
183
    return undef;
 
184
}
 
185
 
 
186
# Parse the config declaration file and return a root node for the
 
187
# configuration it describes
 
188
sub _parse {
 
189
    my ( $file, $root, $haveLSC ) = @_;
 
190
 
 
191
    open( F, "<$file" ) || return '';
 
192
    local $/ = "\n";
 
193
    my $open = undef;
 
194
    my @settings;
 
195
    my $sectionNum = 0;
 
196
 
 
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 );
 
201
        }
 
202
 
 
203
        elsif ( $l =~ /^#?\s*\$(?:(?:Fosw|TW)iki::)?cfg([^=\s]*)\s*=(.*)$/ ) {
 
204
            my $keys         = $1;
 
205
            my $tentativeVal = $2;
 
206
            if ( $open && $open->isa('SectionMarker') ) {
 
207
                pusht( \@settings, $open );
 
208
                $open = undef;
 
209
            }
 
210
 
 
211
            # If there is already a UI object for
 
212
            # these keys, we don't need to add another. But if there
 
213
            # isn't, we do.
 
214
            if ( !$open ) {
 
215
                next if $root->getValueObject($keys);
 
216
                next if ( _getValueObject( $keys, \@settings ) );
 
217
 
 
218
                # This is an untyped value
 
219
                $open = new Foswiki::Configure::Value();
 
220
            }
 
221
            $open->set( keys => $keys );
 
222
            pusht( \@settings, $open );
 
223
            $open = undef;
 
224
        }
 
225
 
 
226
        elsif ( $l =~ /^#\s*\*([A-Z]+)\*/ ) {
 
227
            my $pluggable = $1;
 
228
            my $p         = Foswiki::Configure::Pluggable::load($pluggable);
 
229
            if ($p) {
 
230
                pusht( \@settings, $open ) if $open;
 
231
                $open = $p;
 
232
            }
 
233
            elsif ($open) {
 
234
                $l =~ s/^#\s?//;
 
235
                $open->addToDesc($l);
 
236
            }
 
237
        }
 
238
 
 
239
        elsif ( $l =~ /^#\s*---\+(\+*) *(.*?)$/ ) {
 
240
 
 
241
            # Only load the first section if we don't have LocalSite.cfg
 
242
            last if ( $sectionNum && !$haveLSC );
 
243
            $sectionNum++;
 
244
            pusht( \@settings, $open ) if $open;
 
245
            $open = new SectionMarker( length($1), $2 );
 
246
        }
 
247
 
 
248
        elsif ( $l =~ /^#\s?(.*)$/ ) {
 
249
            $open->addToDesc($1) if $open;
 
250
        }
 
251
    }
 
252
    close(F);
 
253
    pusht( \@settings, $open ) if $open;
 
254
    _extractSections( \@settings, $root );
 
255
}
 
256
 
 
257
sub pusht {
 
258
    my ( $a, $n ) = @_;
 
259
    foreach my $v (@$a) {
 
260
        Carp::confess "$n" if $v eq $n;
 
261
    }
 
262
    push( @$a, $n );
 
263
}
 
264
 
 
265
###########################################################################
 
266
## OUTPUT
 
267
###########################################################################
 
268
 
 
269
# Generate .cfg file format output
 
270
sub save {
 
271
    my ( $root, $valuer, $logger ) = @_;
 
272
 
 
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} = '';
 
279
 
 
280
    my $lsc = Foswiki::findFileOnPath('LocalSite.cfg');
 
281
    unless ($lsc) {
 
282
 
 
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/;
 
286
    }
 
287
 
 
288
    if ( open( F, '<' . $lsc ) ) {
 
289
        local $/ = undef;
 
290
        $this->{content} = <F>;
 
291
        close(F);
 
292
    }
 
293
    else {
 
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
 
297
# text editor.
 
298
HERE
 
299
    }
 
300
 
 
301
    my $out = $this->_save();
 
302
    open( F, '>' . $lsc )
 
303
      || die "Could not open $lsc for write: $!";
 
304
    print F $this->{content};
 
305
    close(F);
 
306
 
 
307
    return '';
 
308
}
 
309
 
 
310
sub _save {
 
311
    my $this = shift;
 
312
 
 
313
    $this->{content} =~ s/\s*1;\s*$/\n/sg;
 
314
    $this->{root}->visit($this);
 
315
    $this->{content} .= "1;\n";
 
316
}
 
317
 
 
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.
 
320
sub startVisit {
 
321
    my ( $this, $visitee ) = @_;
 
322
 
 
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;
 
327
 
 
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 );
 
334
        }
 
335
 
 
336
        # Substitute any existing value, or append if not there
 
337
        unless ( $this->{content} =~ s/\$(Foswiki::)?cfg$keys\s*=.*?;\n/$txt/s )
 
338
        {
 
339
            $this->{content} .= $txt;
 
340
        }
 
341
    }
 
342
    return 1;
 
343
}
 
344
 
 
345
sub endVisit {
 
346
    my ( $this, $visitee ) = @_;
 
347
 
 
348
    return 1;
 
349
}
 
350
 
 
351
1;
 
352
__DATA__
 
353
#
 
354
# Foswiki - The Free and Open Source Wiki, http://foswiki.org/
 
355
#
 
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.
 
359
#
 
360
# Additional copyrights apply to some or all of the code in this
 
361
# file as follows:
 
362
#
 
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.
 
366
#
 
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.
 
372
#
 
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.
 
376
#
 
377
# As per the GPL, removal of this notice is prohibited.
 
378
#