Update a configuration file: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|Java}}: small changes)
(→‎{{header|REXX}}: added the REXX language.)
Line 1,650: Line 1,650:
(set! numberofstrawberries 62000)
(set! numberofstrawberries 62000)
</lang>
</lang>

=={{header|REXX}}==
This REXX program was written to be executed under the Microsoft version of DOS &nbsp; (with or without Windows).
<lang rexx>/*REXX pgm shows how to update a configuration file (4 specific tasks).*/
parse arg iFID . /*obtain optional input file─id. */
if iFID=='' | iFID==',' then iFID='UPDATECF.TXT' /*use the default? */
rec=0 /*set record count.*/
tFID='\temp\UPDATECF.$$$' /*define the tempory output file.*/
call lineout iFID; call lineout oFID /*close the input & output files.*/
@erase='ERASE' /*name of a "DOS" command used. */
@type ='TYPE' /*name of a "DOS" command used. */
@xcopy='XCOPY' /*name of a "DOS" command used. */
strawberries=0 /*flag if STRAWBERRY opt. exists.*/
@erase tFID "2> nul" /*erase a file (with no err MSGs)*/
changed=0 /*nothing changed in file so far.*/

do while lines(iFID)\==0; rec=rec+1 /*read entire file; bump rec ctr.*/
z=linein(iFID); zz=space(z) /*get rec; del extraneous blanks.*/
zzu=zz; upper zzu /*also, get an uppercase version.*/
say '───────── record:' z /*echo the record just read──►con*/
a=left(zz,1) /*obtain the 1st non─blank char. */
word1=word(translate(zz,,';'),1) /*get 1st (real) non─blank char. */
if zz=='' | a=='#' then do; call cpy; iterate; end /*blank or comment*/

/*─────────────────────────task 1, disable NEEDSPEELING opt.*/
if a\==';' then do 1 /* "1": for easy egress from DO*/
if word(zzu,2)\=='NEEDSPEELING' then leave /*wrong.*/
z=';' z /*prefix option with a semicolon.*/
changed=1 /*indicate the record has changed*/
end

/*─────────────────────────task 2, enable SEEDSREMOVED opt.*/
if a==';' then do 1 /* "1": for easy egress from DO*/
if word1\=='SEEDSREMOVED' then leave /*right option?*/
z=overlay(' ',z,pos(';',z)) /*overlay ; with a blank*/
changed=1 /*indicate the record has changed*/
end

/*─────────────────────────task 3, change NUMBEROFBANANAS. */
if a\==';' then do 1 /* "1": for easy egress from DO*/
if word1\=='NUMBEROFBANANAS' then leave /*right opt?*/
z=word1 1024 /*set 2nd word in option to 1024 */
changed=1 /*indicate the record has changed*/
end

/*─────────────────────────task 4, chng NUMBEROFSTRAWBERRIES*/
if a\==';' then do 1 /* "1": for easy egress from DO*/
if word1\=='NUMBEROFSTRAWBERRIES' then leave /*opt?*/
z=word1 62000 /*change 2nd word in option──►62k*/
strawberries=1 /*indicate that we processed opt.*/
changed=1 /*indicate the record has changed*/
end
call cpy /*write the Z record to output.*/
end /*rec*/

if \strawberries then do; z='NUMBEROFSTRAWBERRIES' 62000; call cpy
changed=1 /*indicate the record has changed*/
end
call lineout iFID; call lineout tFID /*close the input & output files.*/
if rec==0 then do; say e "the input file wasn't found: " iFID; exit; end
if changed then do /*possibly overwrite input file. */
@xcopy tFID iFID '/y /q > nul' /*noprompting, quietly.*/
say; say center('output file', 79, "▒") /*title.*/
@TYPE tFID /*display output file's content. */
end
@erase tFID "2> nul" /*erase a file (with no err msg)*/
exit /*stick a fork in it, we're done.*/
/*──────────────────────────────────CPY subroutine──────────────────────*/
cpy: call lineout tFID,z; return /*write one line of text───►tFID.*/</lang>
'''output''' when using the default input file and input options:
<pre>
───────── record: # This is a configuration file in standard configuration file format
───────── record: #
───────── record: # Lines begininning with a hash or a semicolon are ignored by the application
───────── record: # program. Blank lines are also ignored by the application program.
───────── record:
───────── record: # The first word on each non comment line is the configuration option.
───────── record: # Remaining words or numbers on the line are configuration parameter
───────── record: # data fields.
───────── record:
───────── record: # Note that configuration option names are not case sensitive. However,
───────── record: # configuration parameter data is case sensitive and the lettercase must
───────── record: # be preserved.
───────── record:
───────── record: # This is a favourite fruit
───────── record: FAVOURITEFRUIT banana
───────── record:
───────── record: # This is a boolean that should be set
───────── record: NEEDSPEELING
───────── record:
───────── record: # This boolean is commented out
───────── record: SEEDSREMOVED
───────── record:
───────── record: # How many bananas we have
───────── record: NUMBEROFBANANAS 1024
───────── record: NUMBEROFSTRAWBERRIES 62000

▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒output file▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
# 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 1024
NUMBEROFSTRAWBERRIES 62000
</pre>


=={{header|Ruby}}==
=={{header|Ruby}}==

Revision as of 05:13, 11 May 2015

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>

C

C with POSIX strcasecmp function for case-insensitive comparing. Substitute your toolchain's version.

<lang C>#include <stdio.h>

  1. include <stdlib.h>
  2. include <string.h>
  1. define strcomp(X, Y) strcasecmp(X, Y)

struct option { const char *name, *value;

 int flag; };

/* TODO: dynamically obtain these */ struct option updlist[] = { { "NEEDSPEELING", NULL },

 { "SEEDSREMOVED", "" },
 { "NUMBEROFBANANAS", "1024" },
 { "NUMBEROFSTRAWBERRIES", "62000" },
 { NULL, NULL } };

int output_opt(FILE *to, struct option *opt) { if (opt->value == NULL)

   return fprintf(to, "; %s\n", opt->name);
 else if (opt->value[0] == 0)
   return fprintf(to, "%s\n", opt->name);
 else 
   return fprintf(to, "%s %s\n", opt->name, opt->value); }

int update(FILE *from, FILE *to, struct option *updlist) { char line_buf[256], opt_name[128];

 int i;
 for (;;)
 { size_t len, space_span, span_to_hash;
   if (fgets(line_buf, sizeof line_buf, from) == NULL)
     break;
   len = strlen(line_buf);
   space_span = strspn(line_buf, "\t ");
   span_to_hash = strcspn(line_buf, "#");
   if (space_span == span_to_hash)
     goto line_out;
   if (space_span == len)
     goto line_out;
   if ((sscanf(line_buf, "; %127s", opt_name) == 1) ||
       (sscanf(line_buf, "%127s", opt_name) == 1))
   { int flag = 0;
     for (i = 0; updlist[i].name; i++)
     { if (strcomp(updlist[i].name, opt_name) == 0)
       { if (output_opt(to, &updlist[i]) < 0)
           return -1;
         updlist[i].flag = 1;
         flag = 1; } }
     if (flag == 0)
       goto line_out; }
   else
 line_out: 
     if (fprintf(to, "%s", line_buf) < 0)
       return -1;
   continue; }
 { for (i = 0; updlist[i].name; i++)
   { if (!updlist[i].flag)
       if (output_opt(to, &updlist[i]) < 0)
         return -1; } }
 return feof(from) ? 0 : -1; }

int main(void) { if (update(stdin, stdout, updlist) < 0)

 { fprintf(stderr, "failed\n");
   return (EXIT_FAILURE); }
 return 0; }</lang>

Run:

$ ./a.out  < configfile
# 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 1024
NUMBEROFSTRAWBERRIES 62000

D

This type of file is really not very suitable for automated management, so this code is very basic. <lang d>import std.stdio, std.file, std.string, std.regex, std.path,

      std.typecons;

final class Config {

   enum EntryType { empty, enabled, disabled, comment, ignore }
   static protected struct Entry {
       EntryType type;
       string name, value;
   }
   protected Entry[] entries;
   protected string path;
   this(in string path) {
       if (!isValidPath(path) || (exists(path) && !isFile(path)))
           throw new Exception("Invalid filename");
       this.path = path;
       if (!exists(path))
           return;
       auto r = regex(r"^(;*)\s*([A-Z0-9]+)\s*([A-Z0-9]*)", "i");
       auto f = File(path, "r");
       foreach (const buf; f.byLine()) {
           auto line = buf.strip().idup;
           if (!line.length)
               entries ~= Entry(EntryType.empty);
           else if (line[0] == '#')
               entries ~= Entry(EntryType.comment, line);
           else {
               line = line.removechars("^a-zA-Z0-9\x20;");
               auto m = match(line, r);
               if (!m.empty && m.front[2].length) {
                   EntryType t = EntryType.enabled;
                   if (m.front[1].length)
                       t = EntryType.disabled;
                   addOption(m.front[2], m.front[3], t);
               }
           }
       }
   }
   void enableOption(in string name) pure {
       immutable i = getOptionIndex(name);
       if (!i.isNull)
           entries[i].type = EntryType.enabled;
   }
   void disableOption(in string name) pure {
       immutable i = getOptionIndex(name);
       if (!i.isNull)
           entries[i].type = EntryType.disabled;
   }
   void setOption(in string name, in string value) pure {
       immutable i = getOptionIndex(name);
       if (!i.isNull)
           entries[i].value = value;
   }
   void addOption(in string name, in string val,
                  in EntryType t = EntryType.enabled) pure {
       entries ~= Entry(t, name.toUpper(), val);
   }
   void removeOption(in string name) pure {
       immutable i = getOptionIndex(name);
       if (!i.isNull)
           entries[i].type = EntryType.ignore;
   }
   Nullable!size_t getOptionIndex(in string name) const pure {
       foreach (immutable i, const ref e; entries) {
           if (e.type != EntryType.enabled &&
               e.type != EntryType.disabled)
               continue;
           if (e.name == name.toUpper())
               return typeof(return)(i);
       }
       return typeof(return).init;
   }
   void store() {
       auto f = File(path, "w+");
       foreach (immutable e; entries) {
           final switch (e.type) {
               case EntryType.empty:
                   f.writeln();
                   break;
               case EntryType.enabled:
                   f.writefln("%s %s", e.name, e.value);
                   break;
               case EntryType.disabled:
                   f.writefln("; %s %s", e.name, e.value);
                   break;
               case EntryType.comment:
                   f.writeln(e.name);
                   break;
               case EntryType.ignore:
                   continue;
           }
       }
   }

}

void main() {

   auto cfg = new Config("config.txt");
   cfg.enableOption("seedsremoved");
   cfg.disableOption("needspeeling");
   cfg.setOption("numberofbananas", "1024");
   cfg.addOption("numberofstrawberries", "62000");
   cfg.store();

}</lang> Input file:

# 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

Output file:

# 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 1024
NUMBEROFSTRAWBERRIES 62000

Erlang

Given the large number of very exact rules governing the update of this configuration file it is with some pleasure I add new options to the beginning of the file. <lang Erlang> -module( update_configuration_file ).

-export( [add/3, change/3, disable/2, enable/2, read/1, task/0, write/2] ).

add( Option, Value, Lines ) -> Upper = string:to_upper( Option ), [string:join( [Upper, Value], " " ) | Lines].

change( Option, Value, Lines ) -> Upper = string:to_upper( Option ), change_done( Option, Value, Lines, [change_option(Upper, Value, X) || X <- Lines] ).

disable( Option, Lines ) -> Upper = string:to_upper( Option ), [disable_option(Upper, X) || X <- Lines].

enable( Option, Lines ) -> Upper = string:to_upper( Option ), [enable_option(Upper, X) || X <- Lines].

read( Name ) -> {ok, Binary} = file:read_file( Name ), Lines = [binary:bin_to_list(X) || X <- binary:split( Binary, <<"\n">>, [global] )], Lines_no_white = [string:strip(X) || X <- Lines], Lines_no_control = [strip_control(X) || X <- Lines_no_white], Lines_no_consecutive_space = [string:join(string:tokens(X, " "), " ") || X <- Lines_no_control], Lines_no_consecutive_semicolon = [strip_semicolon(X) || X <- Lines_no_consecutive_space], Lines_no_empty = lists:filter( fun strip_empty/1, Lines_no_consecutive_semicolon ), Lines_upper = [to_upper(X) || X <- Lines_no_empty], lists:reverse( lists:foldl(fun remove_duplicates/2, [], Lines_upper) ).

task() -> Lines = read( "priv/configuration_file2" ), Disabled_lines = disable( "needspeeling", Lines ), Enabled_lines = enable( "SEEDSREMOVED", Disabled_lines ), Changed_lines1 = change( "NUMBEROFBANANAS", "1024", Enabled_lines ), Changed_lines2 = change( "numberofstrawberries", "62000", Changed_lines1 ), write( "configuration_file", Changed_lines2 ), [io:fwrite( "Wrote this line: ~s~n", [X]) || X <- Changed_lines2].

write( Name, Lines ) -> file:write_file( Name, binary:list_to_bin(string:join(Lines, "\n")) ).


change_done( Option, Value, Lines, Lines ) -> add( Option, Value, Lines ); change_done( _Option, _Value, _Lines, New_lines ) -> New_lines.

change_option( Option, Value, String ) -> change_option_same( string:str(String, Option), Value, String ).

change_option_same( 1, Value, String ) -> [Option | _T] = string:tokens( String, " " ), string:join( [Option, Value], " " ); change_option_same( _N, _Value, String ) -> String.

disable_option( Option, String ) -> disable_option_same( string:str(String, Option), String ).

disable_option_same( 1, String ) -> "; " ++ String; disable_option_same( _N, String ) -> String.

enable_option( Option, String ) -> enable_option_same( string:str(String, "; " ++ Option), String ).

enable_option_same( 1, "; " ++ String ) -> String; enable_option_same( _N, String ) -> String.

is_semicolon( $; ) -> true; is_semicolon( _C ) -> false.

remove_duplicates( "#" ++_T=Line, Lines ) -> [Line | Lines]; remove_duplicates( Line, Lines ) -> Duplicates = [X || X <-Lines, 1 =:= string:str(Line, X)], remove_duplicates( Duplicates, Line, Lines ).

remove_duplicates( [], Line, Lines ) -> [Line | Lines]; remove_duplicates( _Duplicates, _Line, Lines ) -> Lines.

strip_control( "" ) -> ""; strip_control( ";" ++ _T=String ) -> lists:filter( fun strip_control_codes:is_not_control_code_nor_extended_character/1, String ); strip_control( String ) -> String.

strip_empty( ";" ) -> false; strip_empty( _String ) -> true.


strip_semicolon( ";" ++ _T=String ) -> ";" ++ lists:dropwhile( fun is_semicolon/1, String ); strip_semicolon( String ) -> String.

to_upper( "" ) -> ""; to_upper( "#" ++ _T=String ) -> String; to_upper( "; " ++ _T=String ) -> [";", Option | T] = string:tokens( String, " " ), string:join( [";", string:to_upper(Option) | T], " " ); to_upper( String ) -> [Option | T] = string:tokens( String, " " ), string:join( [string:to_upper(Option) | T], " " ). </lang>

Output:
51> update_configuration_file:task().
Wrote this line: NUMBEROFSTRAWBERRIES 62000
Wrote this line: # This is a configuration file in standard configuration file format
Wrote this line: #
Wrote this line: # Lines begininning with a hash or a semicolon are ignored by the application
Wrote this line: # program. Blank lines are also ignored by the application program.
Wrote this line: 
Wrote this line: # The first word on each non comment line is the configuration option.
Wrote this line: # Remaining words or numbers on the line are configuration parameter
Wrote this line: # data fields.
Wrote this line: 
Wrote this line: # Note that configuration option names are not case sensitive. However,
Wrote this line: # configuration parameter data is case sensitive and the lettercase must
Wrote this line: # be preserved.
Wrote this line: 
Wrote this line: # This is a favourite fruit
Wrote this line: FAVOURITEFRUIT banana
Wrote this line: 
Wrote this line: # This is a boolean that should be set
Wrote this line: ; NEEDSPEELING
Wrote this line: 
Wrote this line: # This boolean is commented out
Wrote this line: SEEDSREMOVED
Wrote this line: 
Wrote this line: # How many bananas we have
Wrote this line: NUMBEROFBANANAS 1024

Perl

<lang 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) {
       if (substr($line, 0, 1) eq '#') {
           push @out, $line;
           next;
       }
       next if $line =~ /^[;\t ]+$/;
       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*[^[:ascii:]]+\s*$//;
           $line =~ s/[[:cntrl:]\s]+$//;
           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>

Go

<lang go>package main

import ( "bufio" "fmt" "io" "log" "os" "strings" "unicode" )

// line represents a single line in the configuration file. type line struct { kind lineKind option string value string disabled bool }

// lineKind represents the different kinds of configuration line. type lineKind int

const ( _ lineKind = iota ignore parseError comment blank value )

func (l line) String() string { switch l.kind { case ignore, parseError, comment, blank: return l.value case value: s := l.option if l.disabled { s = "; " + s } if l.value != "" { s += " " + l.value } return s } panic("unexpected line kind") }

func removeDross(s string) string { return strings.Map(func(r rune) rune { if r < 32 || r > 0x7f || unicode.IsControl(r) { return -1 } return r }, s) }

func parseLine(s string) line { if s == "" { return line{kind: blank} } if s[0] == '#' { return line{kind: comment, value: s} } s = removeDross(s) fields := strings.Fields(s) if len(fields) == 0 { return line{kind: blank} } // Strip leading semicolons (but record that we found them) semi := false for len(fields[0]) > 0 && fields[0][0] == ';' { semi = true fields[0] = fields[0][1:] } // Lose the first field if it was all semicolons if fields[0] == "" { fields = fields[1:] } switch len(fields) { case 0: // This can only happen if the line starts // with a semicolon but has no other information return line{kind: ignore} case 1: return line{ kind: value, option: strings.ToUpper(fields[0]), disabled: semi, } case 2: return line{ kind: value, option: strings.ToUpper(fields[0]), value: fields[1], disabled: semi, } } return line{kind: parseError, value: s} }

// Config represents a "standard" configuration file. type Config struct { options map[string]int // index of each option in lines. lines []line }

// index returns the index of the given option in // c.lines, or -1 if not found. func (c *Config) index(option string) int { if i, ok := c.options[option]; ok { return i } return -1 }

// addLine adds a line to the config, ignoring // duplicate options and "ignore" lines. func (c *Config) addLine(l line) { switch l.kind { case ignore: return case value: if c.index(l.option) >= 0 { return } c.options[l.option] = len(c.lines) c.lines = append(c.lines, l) default: c.lines = append(c.lines, l) } }

// ReadConfig reads a configuration file from path and returns it. func ReadConfig(path string) (*Config, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() r := bufio.NewReader(f) c := &Config{options: make(map[string]int)} for { s, err := r.ReadString('\n') if s != "" { if err == nil { // strip newline unless we encountered an error without finding one. s = s[:len(s)-1] } c.addLine(parseLine(s)) } if err == io.EOF { break } if err != nil { return nil, err } } return c, nil }

// Set sets an option to a value, adding the option if necessary. If // the option was previously disabled, it will be enabled. func (c *Config) Set(option string, val string) { if i := c.index(option); i >= 0 { line := &c.lines[i] line.disabled = false line.value = val return } c.addLine(line{ kind: value, option: option, value: val, }) }

// Enable sets the enabled status of an option. It is // ignored if the option does not already exist. func (c *Config) Enable(option string, enabled bool) { if i := c.index(option); i >= 0 { c.lines[i].disabled = !enabled } }

// Write writes the configuration file to the writer. func (c *Config) Write(w io.Writer) { for _, line := range c.lines { fmt.Println(line) } }

func main() { c, err := ReadConfig("/tmp/cfg") if err != nil { log.Fatalln(err) } c.Enable("NEEDSPEELING", false) c.Set("SEEDSREMOVED", "") c.Set("NUMBEROFBANANAS", "1024") c.Set("NUMBEROFSTRAWBERRIES", "62000") c.Write(os.Stdout) }</lang>

J

Since the task does not specify the line end character, we assume that the last character in the file is the line end character.

<lang J>require 'regex strings'

normalize=:3 :0

 seen=. a:
 eol=. {:;y
 t=. 
 for_line.<;._2;y do. lin=. deb line=.>line
   if. '#'={.line do. t=.t,line,eol
   elseif. -:lin do. t =. t,eol
   elseif. do.
     line=. 1 u:([-.-.)&(32+i.95)&.(3&u:) line
     base=. ('^ *;;* *';) rxrplc line
     nm=. ;name=. {.;:toupper base
     if. -. name e. seen do.
       seen=. seen, name
       t=. t,eol,~dtb ('; '#~';'={.lin),(('^(?i) *',nm,'\b *');(nm,' ')) rxrplc base
     end.
   end.
 end.t

)

enable=:1 :0

 (<m) 1!:2~ normalize (,y,{:);<@rxrplc~&(y;~'^; *(?i)',y,'\b');.2]1!:1<m

)

disable=:1 :0

 (<m) 1!:2~ normalize (,'; ',y,{:);<@rxrplc~&(('; ',y);~'^ *(?i)',y,'\b');.2]1!:1<m

)

set=:1 :0

 t=. 1!:1<m
 pat=. '^ *(?i)',y,'\b.*'
 upd=. y,' ',":x
 (<m) 1!:2~ normalize (,upd,{:);<@rxrplc~&(pat;upd) t

)</lang>

Note that, aside from the line end issue, the task is ambiguous because it specifies a set of operations rather than a file format. If the consequences of these ambiguities are troubling, you might prefer to replace normalize with normalize^:_

Example use:

<lang J> 'example.file' disable 'needspeeling'

  'example.file' enable 'seedsremoved'
  1024 'example.file' set 'numberofbananas'
  62000 'example.file' set 'numberofstrawberries'</lang>

Here's how the file specified in the task description looks after these steps have been executed:

# 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 1024

NUMBEROFSTRAWBERRIES 62000

Java

Translation of: D
Works with: Java version 7

<lang java>import java.io.*; import java.util.*; import java.util.regex.*;

public class UpdateConfig {

   public static void main(String[] args) {
       if (args[0] == null) {
           System.out.println("filename required");
       } else if (readConfig(args[0])) {
           enableOption("seedsremoved");
           disableOption("needspeeling");
           setOption("numberofbananas", "1024");
           addOption("numberofstrawberries", "62000");
           store();
       }
   }
   private enum EntryType {
       EMPTY, ENABLED, DISABLED, COMMENT
   }
   private static class Entry {
       EntryType type;
       String name, value;
       Entry(EntryType t, String n, String v) {
           type = t;
           name = n;
           value = v;
       }
   }
   private static Map<String, Entry> entries = new LinkedHashMap<>();
   private static String path;
   private static boolean readConfig(String p) {
       path = p;
       File f = new File(path);
       if (!f.exists() || f.isDirectory())
           return false;
       String regexString = "^(;*)\\s*([A-Za-z0-9]+)\\s*([A-Za-z0-9]*)";
       Pattern regex = Pattern.compile(regexString);
       try (Scanner sc = new Scanner(new FileReader(f))){
           int emptyLines = 0;
           String line;
           while (sc.hasNext()) {
               line = sc.nextLine().trim();
               if (line.isEmpty()) {
                   addOption("" + emptyLines++, null, EntryType.EMPTY);
               } else if (line.charAt(0) == '#') {
                   entries.put(line, new Entry(EntryType.COMMENT, line, null));
               } else {
                   line = line.replaceAll("[^a-zA-Z0-9\\x20;]", "");
                   Matcher m = regex.matcher(line);
                   if (m.find() && !m.group(2).isEmpty()) {
                       EntryType t = EntryType.ENABLED;
                       if (!m.group(1).isEmpty())
                           t = EntryType.DISABLED;
                       addOption(m.group(2), m.group(3), t);
                   }
               }
           }
       } catch (IOException e) {
           System.out.println(e);
       }
       return true;
   }
   private static void addOption(String name, String value) {
       addOption(name, value, EntryType.ENABLED);
   }
   private static void addOption(String name, String value, EntryType t) {
       name = name.toUpperCase();
       entries.put(name, new Entry(t, name, value));
   }
   private static void enableOption(String name) {
       Entry e = entries.get(name.toUpperCase());
       if (e != null)
           e.type = EntryType.ENABLED;
   }
   private static void disableOption(String name) {
       Entry e = entries.get(name.toUpperCase());
       if (e != null)
           e.type = EntryType.DISABLED;
   }
   private static void setOption(String name, String value) {
       Entry e = entries.get(name.toUpperCase());
       if (e != null)
           e.value = value;
   }
   private static void store() {
       try (PrintWriter pw = new PrintWriter(path)) {
           for (Entry e : entries.values()) {
               switch (e.type) {
                   case EMPTY:
                       pw.println();
                       break;
                   case ENABLED:
                       pw.format("%s %s%n", e.name, e.value);
                       break;
                   case DISABLED:
                       pw.format("; %s %s%n", e.name, e.value);
                       break;
                   case COMMENT:
                       pw.println(e.name);
                       break;
                   default:
                       break;
               }
           }
           if (pw.checkError()) {
               throw new IOException("writing to file failed");
           }
       } catch (IOException e) {
           System.out.println(e);
       }
   }

}</lang>

Input file:

# 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

Output:

# 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 1024
NUMBEROFSTRAWBERRIES 62000

Lasso

Config type definition <lang Lasso>#!/usr/bin/lasso9

define config => type {

data public configtxt, public path

public oncreate( path::string = 'testing/configuration.txt' ) => { .configtxt = file(#path) -> readstring .path = #path }

public get(term::string) => { .clean local( regexp = regexp(-find = `(?m)^` + #term + `($|\s*=\s*|\s+)(.*)$`, -input = .configtxt, -ignorecase), result )

while(#regexp -> find) => { #result = (#regexp -> groupcount > 1 ? (#regexp -> matchString(2) -> trim& || true)) if(#result -> asstring >> ',') => { #result = #result -> split(',') #result -> foreach => {#1 -> trim} } return #result } return false }

public set(term::string, value) => { if(#value === false) => { .disable(#term) return } .enable(#term) if(#value -> isanyof(::string, ::integer, ::decimal)) => { .configtxt = regexp(-find = `(?m)^(` + #term + `) ?(.*?)$`, -replace = `$1 ` + #value, -input = .configtxt, -ignorecase) -> replaceall } }

public disable(term::string) => { .clean local(regexp = regexp(-find = `(?m)^(` + #term + `)`, -replace = `; $1`, -input = .configtxt, -ignorecase)) .configtxt = #regexp -> replaceall }

public enable(term::string, -comment::string = '# Added ' + date) => { .clean local(regexp = regexp(-find = `(?m)^(; )?(` + #term + `)`, -replace = `$2`, -input = .configtxt, -ignorecase)) if(#regexp -> find) => { .configtxt = #regexp -> replaceall else .configtxt -> append('\n' + (not #comment -> beginswith('#') ? '# ') + #comment + '\n' + string_uppercase(#term) + '\n' ) } }

public write => { local(config = file(.path)) #config -> opentruncate #config -> dowithclose => { #config -> writestring(.configtxt) }

}

public clean => {

local( cleaned = array, regexp = regexp(-find = `^(;+)\W*$`) )

with line in .configtxt -> split('\n') do { #line -> trim #regexp -> input = #line

if(#line -> beginswith('#') or #line == ) => { #cleaned -> insert(#line) else(not (#regexp -> find)) if(#line -> beginswith(';')) => { #line -> replace(regexp(`^;+ *`), `; `) else #line -> replace(regexp(`^(.*?) +(.*?)`), `$1 $2`) } #line -> replace(regexp(`\t`)) #cleaned -> insert(#line) } }

.configtxt = #cleaned -> join('\n')

}

}</lang>

How to call it: <lang Lasso> local( config = config, )

stdoutnl(#config -> get('FAVOURITEFRUIT')) stdoutnl(#config -> get('SEEDSREMOVED')) stdoutnl(#config -> get('NUMBEROFBANANAS'))

  1. config -> enable('seedsremoved')
  2. config -> enable('PARAMWITHCOMMENT', -comment = 'This param was added to demonstrate the possibility to also have comments associated with it')
  3. config -> disable('needspeeling')
  4. config -> set('numberofstrawberries', 62000)
  5. config -> set('numberofbananas', 1024)
  1. config -> write</lang>

Initial config file:

# 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  

# This boolean is commented out
 ;  HEEDSREMOVED

# Another option 
;; OOPS Remove 	the double ;
;
;; 
  
# How many bananas we have
NUMBEROFBANANAS 48  

Final config file:

# 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

# This boolean is commented out
; HEEDSREMOVED

# Another option
; OOPS Remove the double ;

# How many bananas we have
NUMBEROFBANANAS 1024

# This param was added to demonstrate the possibility to also have comments associated with it
PARAMWITHCOMMENT

# Added 2013-12-01 00:32:00
NUMBEROFSTRAWBERRIES 62000

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>

Python

<lang Python>#!/usr/bin/env python

  1. ----------------------------------------------------------------------------
  2. STANDARD MODULES
  3. ----------------------------------------------------------------------------

import re import string


  1. ----------------------------------------------------------------------------
  2. GLOBAL: VARIABLES
  3. ----------------------------------------------------------------------------

DISABLED_PREFIX = ';'


  1. ----------------------------------------------------------------------------
  2. CLASS Option
  3. ----------------------------------------------------------------------------

class Option(object):

   """An option, characterized by its name and its (optional) value. and by
      its status, which can be enabled or disabled.
      If its value is None, it is regarded to as a boolean option with a
      value of true.
   """
   #------------------------------------------------------------------------
   def __init__(self, name, value=None, disabled=False,
                disabled_prefix=DISABLED_PREFIX):
       """Create an Option instance, setting its name to 'name' (always
          converted to a string) and its value to 'value'. If 'disabled' is
          True, the option is considered disabled, otherwise enabled.
          The string 'disabled_prefix' is used as a prefix when generating the
          string representation of the option.
       """
       self.name = str(name)
       self.value = value
       self.disabled = bool(disabled)
       self.disabled_prefix = disabled_prefix
   #------------------------------------------------------------------------
   def __str__(self):
       """Return a string representation of the Option instance.
          This always includes the option name, followed by a space and the
          option value (if it is not None). If the option is disabled, the
          whole string is preprendend by the string stored in the instance
          attribute 'disabled_prefix' and a space.
       """
       disabled = (, '%s ' % self.disabled_prefix)[self.disabled]
       value = (' %s' % self.value, )[self.value is None]
       return .join((disabled, self.name, value))
   #------------------------------------------------------------------------
   def get(self):
       """Return the option value.
          If the stored value is None, the option is regarded to as a
          boolean one and its enabled status is returned. Othrwise its value
          is returned.
       """
       enabled = not bool(self.disabled)
       if self.value is None:
           value = enabled
       else:
           value = enabled and self.value
       return value


  1. ----------------------------------------------------------------------------
  2. CLASS Config
  3. ----------------------------------------------------------------------------

class Config(object):

   """A set of configuration options and comment strings.
   """
   # Regular expression matching a valid option line.
   reOPTION = r'^\s*(?P<disabled>%s*)\s*(?P<name>\w+)(?:\s+(?P<value>.+?))?\s*$'
   #------------------------------------------------------------------------
   def __init__(self, fname=None, disabled_prefix=DISABLED_PREFIX):
       """Initialize a Config instance, optionally reading the contents of
          the configuration file 'fname'.
          The string 'disabled_prefix' is used as a prefix when generating the
          string representation of the options.
       """
       self.disabled_prefix = disabled_prefix
       self.contents = []          # Sequence of strings and Option instances.
       self.options = {}           # Map an option name to an Option instance.
       self.creOPTION = re.compile(self.reOPTION % self.disabled_prefix)
       if fname:
           self.parse_file(fname)
   #------------------------------------------------------------------------
   def __str__(self):
       """Return a string representation of the Config instance.
          This is just the concatenation of all the items stored in the
          attribute 'contents'.
       """
       return '\n'.join(map(str, self.contents))
   #------------------------------------------------------------------------
   def parse_file(self, fname):
       """Parse all the lines of file 'fname' by applying the method
          'parser_lines' on the file contents.
       """
       with open(fname) as f:
           self.parse_lines(f)
       return self
   #------------------------------------------------------------------------
   def parse_lines(self, lines):
       """Parse all the lines of iterable 'lines' by invoking the method
          'parse_line' for each line in 'lines'.
       """
       for line in lines:
           self.parse_line(line)
       return self
   #------------------------------------------------------------------------
   def parse_line(self, line):
       """Parse the line 'line', looking for options.
          If an option line is found, spaces are stripped from the start and
          the end of 'line' and any non-printable character is removed as well.
          Only the first occurrence of an option is processed, all the other
          occurrences are ignored. A valid option is added to the instance
          attribute 'contents' (in order to preserve its position among the
          other lines). It is also added to the mapping stored in the instance
          attribute 'options'.
          Any non-option string is added the the instance attribute 'contents',
          except those lines starting with the string stored into the instance
          attribute 'disabled_prefix' which are not followed by any option
          name.
       """
       s = .join(c for c in line.strip() if c in string.printable) 
       moOPTION = self.creOPTION.match(s)
       if moOPTION:
           name = moOPTION.group('name').upper()
           if not name in self.options:
               self.add_option(name, moOPTION.group('value'),
                               moOPTION.group('disabled'))
       else:
           if not s.startswith(self.disabled_prefix):
               self.contents.append(line.rstrip())
       return self
   #------------------------------------------------------------------------
   def add_option(self, name, value=None, disabled=False):
       """Create a new Option instance, named 'name' (always converted to
          uppercase) with value 'value' and set its disabled status to
          'disabled'.
          The Option instance is added to the instance attribute 'contents'.
          It is also added to the mapping stored in the instance attribute
          'options'.
       """
       name = name.upper()
       opt = Option(name, value, disabled)
       self.options[name] = opt
       self.contents.append(opt)
       return opt
   #------------------------------------------------------------------------
   def set_option(self, name, value=None, disabled=False):
       """Look for an option named 'name' (always converted to
          uppercase) among the options stored in the instance
          attribute 'options'.
          If it is not found, a new Option instance is created.
          In any case its value is set to 'value' and its disabled
          status to 'disabled'.
       """
       name = name.upper()
       opt = self.options.get(name)
       if opt:
           opt.value = value
           opt.disabled = disabled
       else:
           opt = self.add_option(name, value, disabled)
       return opt
   #------------------------------------------------------------------------
   def enable_option(self, name, value=None):
       """Enable the option named 'name' (always converted to
          uppercase) and set its value to 'value'.
          If the option is not found, it is created and added to the
          end of the instance attribute 'contents'.
       """
       return self.set_option(name, value, False)
   #------------------------------------------------------------------------
   def disable_option(self, name, value=None):
       """Disable the option named 'name' (always converted to
          uppercase) and set its value to 'value'.
          If the option is not found, it is created and added to the
          end of the instance attribute 'contents'.
       """
       return self.set_option(name, value, True)
   #------------------------------------------------------------------------
   def get_option(self, name):
       """Return the value of the option named 'name' (always
          converted to uppercase).
          If the option is not found in the instance attribute
          'options', None is returned. If the stored value is None,
          it is regarded to as a boolean option and its enable status
          is returned. Otherwise its value is returned.
       """
       opt = self.options.get(name.upper())
       value = opt.get() if opt else None
       return value


  1. ----------------------------------------------------------------------------
  2. MAIN
  3. ----------------------------------------------------------------------------

if __name__ == '__main__':

   import sys
   cfg = Config(sys.argv[1] if len(sys.argv) > 1 else None)
   cfg.disable_option('needspeeling')
   cfg.enable_option('seedsremoved')
   cfg.enable_option('numberofbananas', 1024)
   cfg.enable_option('numberofstrawberries', 62000)
   print cfg

</lang>

Output:

# 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 1024

NUMBEROFSTRAWBERRIES 62000

Racket

Use the shared options.rkt code.

<lang Racket>

  1. lang racket

(require "options.rkt")


(read-options "options-file") (define-options needspeeling seedsremoved numberofbananas numberofstrawberries)

Disable the needspeeling option (using a semicolon prefix)

(set! needspeeling #f)

Enable the seedsremoved option by removing the semicolon and any
leading whitespace

(set! seedsremoved ENABLE)

Change the numberofbananas parameter to 1024

(set! numberofbananas 1024)

Enable (or create if it does not exist in the file) a parameter for
numberofstrawberries with a value of 62000

(set! numberofstrawberries 62000) </lang>

REXX

This REXX program was written to be executed under the Microsoft version of DOS   (with or without Windows). <lang rexx>/*REXX pgm shows how to update a configuration file (4 specific tasks).*/ parse arg iFID . /*obtain optional input file─id. */ if iFID== | iFID==',' then iFID='UPDATECF.TXT' /*use the default? */ rec=0 /*set record count.*/ tFID='\temp\UPDATECF.$$$' /*define the tempory output file.*/ call lineout iFID; call lineout oFID /*close the input & output files.*/ @erase='ERASE' /*name of a "DOS" command used. */ @type ='TYPE' /*name of a "DOS" command used. */ @xcopy='XCOPY' /*name of a "DOS" command used. */ strawberries=0 /*flag if STRAWBERRY opt. exists.*/ @erase tFID "2> nul" /*erase a file (with no err MSGs)*/ changed=0 /*nothing changed in file so far.*/

 do  while lines(iFID)\==0; rec=rec+1 /*read entire file; bump rec ctr.*/
 z=linein(iFID);          zz=space(z) /*get rec; del extraneous blanks.*/
 zzu=zz;            upper zzu         /*also, get an uppercase version.*/
 say '───────── record:'  z           /*echo the record just read──►con*/
 a=left(zz,1)                         /*obtain the 1st non─blank char. */
 word1=word(translate(zz,,';'),1)     /*get 1st (real) non─blank char. */
 if zz== | a=='#'  then do; call cpy; iterate; end /*blank or comment*/
           /*─────────────────────────task 1, disable NEEDSPEELING opt.*/
 if a\==';'  then do 1                /* "1":   for easy egress from DO*/
                  if word(zzu,2)\=='NEEDSPEELING'  then leave  /*wrong.*/
                  z=';' z             /*prefix option with a semicolon.*/
                  changed=1           /*indicate the record has changed*/
                  end
           /*─────────────────────────task 2, enable  SEEDSREMOVED opt.*/
 if a==';'  then do 1                 /* "1":   for easy egress from DO*/
                 if word1\=='SEEDSREMOVED'  then leave  /*right option?*/
                 z=overlay(' ',z,pos(';',z))   /*overlay ; with a blank*/
                 changed=1            /*indicate the record has changed*/
                 end
           /*─────────────────────────task 3, change NUMBEROFBANANAS.  */
 if a\==';'  then do 1                /* "1":   for easy egress from DO*/
                  if word1\=='NUMBEROFBANANAS'  then leave /*right opt?*/
                  z=word1 1024        /*set 2nd word in option to 1024 */
                  changed=1           /*indicate the record has changed*/
                  end
           /*─────────────────────────task 4, chng NUMBEROFSTRAWBERRIES*/
 if a\==';'  then do 1                /* "1":   for easy egress from DO*/
                  if word1\=='NUMBEROFSTRAWBERRIES'  then leave  /*opt?*/
                  z=word1 62000       /*change 2nd word in option──►62k*/
                  strawberries=1      /*indicate that we processed opt.*/
                  changed=1           /*indicate the record has changed*/
                  end
 call cpy                             /*write the  Z  record to output.*/
 end   /*rec*/

if \strawberries then do; z='NUMBEROFSTRAWBERRIES' 62000; call cpy

                          changed=1   /*indicate the record has changed*/
                     end

call lineout iFID; call lineout tFID /*close the input & output files.*/ if rec==0 then do; say e "the input file wasn't found: " iFID; exit; end if changed then do /*possibly overwrite input file. */

                @xcopy tFID iFID '/y /q > nul'  /*noprompting, quietly.*/
                say;   say center('output file', 79, "▒")      /*title.*/
                @TYPE  tFID           /*display output file's content. */
                end

@erase tFID "2> nul" /*erase a file (with no err msg)*/ exit /*stick a fork in it, we're done.*/ /*──────────────────────────────────CPY subroutine──────────────────────*/ cpy: call lineout tFID,z; return /*write one line of text───►tFID.*/</lang> output when using the default input file and input options:

───────── record: # This is a configuration file in standard configuration file format
───────── record: #
───────── record: # Lines begininning with a hash or a semicolon are ignored by the application
───────── record: # program. Blank lines are also ignored by the application program.
───────── record:
───────── record: # The first word on each non comment line is the configuration option.
───────── record: # Remaining words or numbers on the line are configuration parameter
───────── record: # data fields.
───────── record:
───────── record: # Note that configuration option names are not case sensitive. However,
───────── record: # configuration parameter data is case sensitive and the lettercase must
───────── record: # be preserved.
───────── record:
───────── record: # This is a favourite fruit
───────── record: FAVOURITEFRUIT banana
───────── record:
───────── record: # This is a boolean that should be set
───────── record: NEEDSPEELING
───────── record:
───────── record: # This boolean is commented out
───────── record:   SEEDSREMOVED
───────── record:
───────── record: # How many bananas we have
───────── record: NUMBEROFBANANAS 1024
───────── record: NUMBEROFSTRAWBERRIES 62000

▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒output file▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
# 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 1024
NUMBEROFSTRAWBERRIES 62000

Ruby

<lang ruby>require 'stringio'

class ConfigFile

 # create a ConfigFile object from a file
 def self.file(filename)
   fh = File.open(filename)
   obj = self.new(fh)
   obj.filename = filename
   fh.close
   obj
 end
 # create a ConfigFile object from a string
 def self.data(string)
   fh = StringIO.new(string)
   obj = self.new(fh)
   fh.close
   obj
 end
 def initialize(filehandle)
   @lines = filehandle.readlines
   @filename = nil
   tidy_file
 end
 attr :filename
 def save()
   if @filename
     File.open(@filename, "w") {|f| f.write(self)}
   end
 end
 def tidy_file()
   @lines.map! do |line|
     # remove leading whitespace
     line.lstrip!
     if line.match(/^#/)
       # Lines beginning with hash symbols should not be manipulated and left
       # unchanged in the revised file.  
       line
     else
       # replace double semicolon prefixes with just a single semicolon  
       line.sub!(/^;+\s+/, "; ")
     
       if line.match(/^; \s*$/) 
         # Any lines beginning with a semicolon or groups of semicolons, but no
         # following option should be removed 
         line = ""
       else
         # remove ... any trailing whitespace on the lines 
         line = line.rstrip + "\n"
         # Whitespace between the option and paramters should consist only of a
         # single space 
         if m = line.match(/^(; )?(upper:+)\s+(.*)/)
           line = (m[1].nil? ? "" : m[1]) + format_line(m[2], m[3])
         end
       end
       line
     end
   end
 end
 def format_line(option, value)
   "%s%s\n" % [option.upcase.strip, value.nil? ? "" : " " + value.to_s.strip]
 end
 # returns the index of the option, or nil if not found
 def find_option(option)
   @lines.find_index {|line| line.match(/^#{option.upcase.strip}\b/)}
 end
 # uncomments a disabled option
 def enable_option(option)
   if idx = find_option("; " + option)
     @lines[idx][/^; /] = ""
   end
 end
 # comment a line with a semi-colon
 def disable_option(option)
   if idx = find_option(option)
     @lines[idx][/^/] = "; "
   end
 end
 # add an option, or change the value of an existing option. 
 # use nil for the value to set a boolean option
 def set_value(option, value)
   if idx = find_option(option)
     @lines[idx] = format_line(option, value)
   else
     @lines << format_line(option, value) 
   end
 end
 def to_s
   @lines.join()
 end

end


config = ConfigFile.data(DATA.read) config.disable_option('needspeeling') config.enable_option('seedsremoved') config.set_value('numberofbananas', 1024) config.set_value('numberofstrawberries', 62000) puts config


__END__

  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> outputs

# 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 1024

NUMBEROFSTRAWBERRIES 62000

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>

TXR

This is a general solution which implements a command-line tool for updating the config file. Omitted are the trivial steps for writing the configuration back into the same file; the final result is output on standard output.

The first argument is the name of the config file. The remaining arguments are of this form:

  VAR      # define or update VAR as a true-valued boolean
  VAR=     # ensure "; VAR" in the config file.
  VAR=VAL  # ensure "VAR VAL" in the config file

This works by reading the configuration into a variable, and then making multiple passes over it, using the same constructs that normally operate on files or pipes. The first 30% of the script deals with reading the configuration file and parsing each command line argument, and converting its syntax into configuration syntax, stored in new_opt_line. For each argument, the configuration is then scanned and filtered from config to new_config, using the same syntax which could be used to do the same job with temporary files. When the interesting variable is encountered in the config, using one of the applicable pattern matches, then the prepared configuration line is substituted for it. While this is going on, the encountered variable names (bindings for var_other) are also being collected into a list. This list is then later used to check via the directive @(bind opt_there option) to determine whether the option occurred in the configuration or not. The bind construct will not only check whether the left and right hand side are equal, but if nested lists are involved, it checks whether either side occurs in the other as a subtree. option binds with opt_other if it matches one of the option names in opt_other. Finally, the updated config is regurgitated.

<lang txr>@(next :args) @configfile @(maybe) @ (next configfile) @ (collect :vars (config)) @config @ (end) @(end) @(collect) @ (cases) @option= @ (output :into new_opt_line :filter :upcase)

@option

@ (end) @ (or) @option=@val @ (output :into new_opt_line :filter :upcase) @option @val @ (end) @ (or) @option @ (output :into new_opt_line :filter :upcase) @option @ (end) @ (end) @ (next :var config) @ (local new_config) @ (bind new_config ()) @ (collect :vars ((opt_there ""))) @ (block) @ (cases) @ (cases) @{line /[ \t]*/} @ (or) @{line /#.*/} @ (end) @ (output :append :into new_config) @line @ (end) @ (accept) @ (or) @ (maybe)

@opt_there

@ (or) @opt_there @(skip) @ (or) @opt_there @ (or) @original_line @ (end) @ (end) @ (cases) @ (bind opt_there option :filter :upcase) @ (output :append :into new_config) @new_opt_line @ (end) @ (or) @ (output :append :into new_config) @original_line @ (end) @ (end) @ (end) @ (cases) @ (bind opt_there option :filter :upcase) @ (or) @ (output :append :into new_config) @new_opt_line @ (end) @ (end) @ (set config new_config) @(end) @(output) @ (repeat) @config @ (end) @(end)</lang>

Sample invocation:

$ txr configfile2.txr configfile NEEDSPEELING= seedsREMOVED NUMBEROFBANANAS=1024 NUMBEROFSTRAWBERRIES=62000
# 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 1024
NUMBEROFSTRAWBERRIES 62000

Test run on empty input:

$ echo -n | txr configfile2.txr - NEEDSPEELING= SEEDSREMOVED NUMBEROFBANANAS=1024 NUMBEROFSTRAWBERRIES=62000
; NEEDSPEELING
SEEDSREMOVED
NUMBEROFBANANAS 1024
NUMBEROFSTRAWBERRIES 62000

Test run on empty input with no arguments

$ echo -n | txr configfile2.txr -
[ no output ]