Update a configuration file: Difference between revisions

From Rosetta Code
Content added Content deleted
mNo edit summary
mNo edit summary
Line 164: Line 164:
sub config {
sub config {
my (@config) = <DATA>;
my (@config) = <DATA>;
push @config, "NUMBEROFSTRAWBERRIES $numberofstrawberries\n" unless grep { /^NUMBEROFSTRAWBERRIES\b/; } @config;
push @config, "NUMBEROFSTRAWBERRIES $numberofstrawberries\n" unless grep { /^;*[ \t]*NUMBEROFSTRAWBERRIES\b/; } @config;


foreach my $line (@config) {
foreach my $line (@config) {

Revision as of 06:42, 6 October 2011

Task
Update a configuration file
You are encouraged to solve this task according to the task description, using any language you may know.

We have a configuration file as follows:

# This is a configuration file in standard configuration file format
#
# Lines begininning with a hash or a semicolon are ignored by the application
# program. Blank lines are also ignored by the application program.

# The first word on each non comment line is the configuration option.
# Remaining words or numbers on the line are configuration parameter
# data fields.

# Note that configuration option names are not case sensitive. However,
# configuration parameter data is case sensitive and the lettercase must
# be preserved.

# This is a favourite fruit
FAVOURITEFRUIT banana

# This is a boolean that should be set
NEEDSPEELING

# This boolean is commented out
; SEEDSREMOVED

# How many bananas we have
NUMBEROFBANANAS 48

The task is to manipulate the configuration file as follows:

  • Disable the needspeeling option (using a semicolon prefix)
  • Enable the seedsremoved option by removing the semicolon and any leading whitespace
  • Change the numberofbananas parameter to 1024
  • Enable (or create if it does not exist in the file) a parameter for numberofstrawberries with a value of 62000

Note that configuration option names are not case sensitive. This means that changes should be effected, regardless of the case.

Options should always be disabled by prefixing them with a semicolon.

Lines beginning with hash symbols should not be manipulated and left unchanged in the revised file.

If a configuration option does not exist within the file (in either enabled or disabled form), it should be added during this update. Duplicate configuration option names in the file should be removed, leaving just the first entry.

For the purpose of this task, the revised file should contain appropriate entries, whether enabled or not for needspeeling,seedsremoved,numberofbananas and numberofstrawberries.)

The update should rewrite configuration option names in capital letters. However lines beginning with hashes and any parameter data must not be altered (eg the banana for favourite fruit must not become capitalized). The update process should also replace double semicolon prefixes with just a single semicolon (unless it is uncommenting the option, in which case it should remove all leading semicolons).

Any lines beginning with a semicolon or groups of semicolons, but no following option should be removed, as should any leading or trailing whitespace on the lines. Whitespace between the option and paramters should consist only of a single space, and any non ascii extended characters, tabs characters, or control codes (other than end of line markers), should also be removed.

See also:

AutoHotkey

<lang AutoHotkey>; Author: AlephX, Aug 17 2011 data = %A_scriptdir%\rosettaconfig.txt outdata = %A_scriptdir%\rosettaconfig.tmp FileDelete, %outdata%

NUMBEROFBANANAS := 1024 numberofstrawberries := 560 NEEDSPEELING = "0" FAVOURITEFRUIT := "bananas" SEEDSREMOVED = "1" BOOL0 = "0" BOOL1 = "1" NUMBER1 := 1 number0 := 0 STRINGA := "string here"

parameters = bool0|bool1|NUMBER1|number0|stringa|NEEDSPEELING|seedsremoved|numberofbananas|numberofstrawberries

Loop, Read, %data%, %outdata% { if (instr(A_LoopReadLine, "#") == 1 OR A_LoopReadLine == "") { Line := A_LoopReadLine } else { if instr(A_LoopReadLine, ";") == 1 { parameter := RegExReplace(Substr(A_LoopReadLine,2), "^[ \s]+|[ \s]+$", "")

parametervalue = %parameter% value := %parametervalue% if value == 0 Line := A_loopReadLine else Line := Parameter } else { parameter := RegExReplace(A_LoopReadLine, "^[ \s]+|[ \s]+$", "") if instr(parameter, A_Space) parameter := substr(parameter, 1, instr(parameter, A_Space)-1)

if instr(parameters, parameter) > 0 { parametervalue = %parameter% value := %parametervalue%

if (value = chr(34) . "0" . chr(34)) Line := "; " . Parameter else { if (value = chr(34) . "1" . chr(34)) Line := Parameter else Line = %parametervalue% %value% } } else Line := A_LoopReadLine }

} StringReplace, parameters, parameters, %parametervalue%,, StringReplace, parameters, parameters,||,|

FileAppend, %Line%`n }

Loop, parse, parameters,| { if (A_Loopfield <> "") { StringUpper, parameter, A_LoopField parametervalue = %parameter% value := %parametervalue%

if (value = chr(34) . "0" . chr(34)) Line := "; " . parameter else { if (value = chr(34) . "1" . chr(34)) Line := parameter else Line = %parametervalue% %value% }

FileAppend, %Line%`n, %outdata% } }

FileCopy, %A_scriptdir%\rosettaconfig.tmp, %A_scriptdir%\rosettaconfig.txt, 1</lang>

Perl

<lang Perl>#!/usr/bin/perl

use warnings; use strict;

my $needspeeling = 0; my $seedsremoved = 1; my $numberofstrawberries = 62000; my $numberofbananas = 1024; my $favouritefruit = 'bananas';

my @out;

sub config {

   my (@config) = ;
   push @config, "NUMBEROFSTRAWBERRIES $numberofstrawberries\n" unless grep { /^;*[ \t]*NUMBEROFSTRAWBERRIES\b/; } @config;
   foreach my $line (@config) {
       next if $line =~ /^[;\t ]+$/;
       if (substr($line, 0, 1) eq '#') {
           push @out, $line;
           next;
       }
       my ($option, $option_name);
       if ($line =~ /^([A-Z]+[0-9]*)/) {
           $option_name = lc $1;
           $option      = eval "\\\$$option_name";
           my $value = eval "\${$option_name}";
           if ($value) {
               $$option = $value;
           }
           else {
               $line    = '; ' . $line;
               $$option = undef;
           }
       }
       elsif ($line =~ /^;\s*([A-Z]+[0-9]*)/) {
           $option_name = lc $1;
           $option      = eval "\\\$$option_name";
           my $value = eval "\${$option_name}";
           if ($value) {
               $line =~ s/^;+\s*//;
               $$option = $value == 1 ?  : $value;
           }
           else {
               $$option = undef;
           }
       }
       if (defined $$option) {
           push @out, "\U$option_name\E $$option\n" unless grep { /^\U$option_name\E\b/; } @out;
       }
       else {
           $line =~ s/\s+$//;
           $line =~ s/[^[:ascii:]]+$//;
           $line =~ s/cntrl:+$//;
           push(@out, "$line\n");
       }
   }

}

config();

my $file = join(, @out); $file =~ s/\n{3,}/\n\n/g;

print $file;

__DATA__

  1. This is a configuration file in standard configuration file format
  2. Lines begininning with a hash or a semicolon are ignored by the application
  3. program. Blank lines are also ignored by the application program.
  1. The first word on each non comment line is the configuration option.
  2. Remaining words or numbers on the line are configuration parameter
  3. data fields.
  1. Note that configuration option names are not case sensitive. However,
  2. configuration parameter data is case sensitive and the lettercase must
  3. be preserved.
  1. This is a favourite fruit

FAVOURITEFRUIT banana

  1. This is a boolean that should be set

NEEDSPEELING

  1. This boolean is commented out
SEEDSREMOVED
  1. How many bananas we have

NUMBEROFBANANAS 48</lang>

PicoLisp

<lang PicoLisp>(let Data # Read all data

  (in "config"
     (make
        (until (eof)
           (link (trim (split (line) " "))) ) ) )
  (setq Data  # Fix comments
     (mapcar
        '((L)
           (while (head '(";" ";") (car L))
              (pop L) )
           (if (= '(";") (car L))
              L
              (cons NIL L) ) )
        Data ) )
  (let (Need NIL  Seed NIL  NBan NIL  NStr NIL  Favo NIL)
     (map
        '((L)
           (let D (mapcar uppc (cadar L))
              (cond
                 ((= '`(chop "NEEDSPEELING") D)
                    (if Need
                       (set L)
                       (on Need)
                       (unless (caar L)
                          (set (car L) '(";")) ) ) )
                 ((= '`(chop "SEEDSREMOVED") D)
                    (if Seed
                       (set L)
                       (on Seed)
                       (when (caar L)
                          (set (car L)) ) ) )
                 ((= '`(chop "NUMBEROFBANANAS") D)
                    (if NBan
                       (set L)
                       (on NBan)
                       (set (cddar L) 1024) ) )
                 ((= '`(chop "NUMBEROFSTRAWBERRIES") D)
                    (if NStr
                       (set L)
                       (on NStr) ) )
                 ((= '`(chop "FAVOURITEFRUIT") D)
                    (if Favo
                       (set L)
                       (on Favo) ) ) ) ) )
        Data )
     (unless Need
        (conc Data (cons (list NIL "NEEDSPEELING"))) )
     (unless Seed
        (conc Data (cons (list NIL "SEEDSREMOVED"))) )
     (unless NBan
        (conc Data (cons (list NIL "NUMBEROFBANANAS" 1024))) )
     (unless NStr
        (conc Data (cons (list NIL "NUMBEROFSTRAWBERRIES" 62000))) ) )
  (out "config"
     (for L Data
        (prinl (glue " " (if (car L) L (cdr L)))) ) ) )</lang>

Tcl

Creating this to be a general solution: <lang tcl>package require Tcl 8.6 oo::class create Config {

   variable filename contents
   constructor fileName {

set filename $fileName set contents {} try { set f [open $filename] ### Sanitize during input foreach line [split [read $f] \n] { if {[string match "#*" $line]} { lappend contents $line continue } if {[regexp {^;\W*$} $line]} continue set line [string trim [regsub -all {[^\u0020-\u007e]} $line {}]] if {[regexp {^(\W*)(\w+)(.*)$} $line -> a b c]} { set line "[regsub -all {^;+} $a {;}][string toupper $b]$c" } lappend contents $line } } finally { if {[info exists f]} { close $f } }

   }
   method save {} {

set f [open $filename w] puts $f [join $contents \n] close $f

   }
   # Utility methods (not exposed API)
   method Transform {pattern vars replacement} {

set matched 0 set line -1 set RE "(?i)^$pattern$" foreach l $contents { incr line if {[uplevel 1 [list regexp $RE $l -> {*}$vars]]} { if {$matched} { set contents [lreplace $contents $line $line] incr line -1 } else { lset contents $line [uplevel 1 [list subst $replacement]] } set matched 1 } } return $matched

   }
   method Format {k v} {

set v " [string trimleft $v]" return "[string toupper $k][string trimright $v]"

   }
   # Public API for modifying options
   method enable {option} {

if {![my Transform ";?\\s*($option)\\M\s*(.*)" {k v} \ {[my Format $k $v]}]} { lappend contents [my Format $option ""] }

   }
   method disable {option} {

if {![my Transform ";?\\s*($option)\\M\s*(.*)" {k v} \ {; [my Format $k $v]}]} { lappend contents "; [my Format $option ""]" }

   }
   method set {option {value ""}} {

if {![my Transform ";?\\s*($option)\\M.*" k {[my Format $k $value]}]} { lappend contents [my Format $option $value] }

   }

}</lang> Applying to the task at hand (assuming a file in the current directory called sample.cfg): <lang tcl>set cfg [Config new "sample.cfg"] $cfg disable needspeeling $cfg enable seedsremoved $cfg set numberofbananas 1024 $cfg set numberofstrawberries 62000 $cfg save</lang>