Read a configuration file

Revision as of 15:14, 13 February 2021 by MaiconSoft (talk | contribs) (Added Delphi example)

The task is to read a configuration file in standard configuration file format, and set variables accordingly.

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

For this task, we have a configuration file as follows:

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

# This is the fullname parameter
FULLNAME Foo Barber

# This is a favourite fruit
FAVOURITEFRUIT banana

# This is a boolean that should be set
NEEDSPEELING

# This boolean is commented out
; SEEDSREMOVED

# Configuration option names are not case sensitive, but configuration parameter
# data is case sensitive and may be preserved by the application program.

# An optional equals sign can be used to separate configuration parameter data
# from the option name. This is dropped by the parser. 

# A configuration option may take multiple parameters separated by commas.
# Leading and trailing whitespace around parameter names and parameter data fields
# are ignored by the application program.

OTHERFAMILY Rhu Barber, Harry Barber


For the task we need to set four variables according to the configuration entries as follows:

  • fullname = Foo Barber
  • favouritefruit = banana
  • needspeeling = true
  • seedsremoved = false


We also have an option that contains multiple parameters. These may be stored in an array.

  • otherfamily(1) = Rhu Barber
  • otherfamily(2) = Harry Barber


Related tasks



Ada

Works with: Ada version 2005

Uses package Config available at SourceForge: https://sourceforge.net/projects/ini-files/ <lang Ada>with Config; use Config; with Ada.Text_IO; use Ada.Text_IO;

procedure Rosetta_Read_Cfg is

 cfg: Configuration:= Init("rosetta_read.cfg", Case_Sensitive => False, Variable_Terminator => ' ');
 fullname       : String  := cfg.Value_Of("*", "fullname");
 favouritefruit : String  := cfg.Value_Of("*", "favouritefruit");
 needspeeling   : Boolean := cfg.Is_Set("*", "needspeeling");
 seedsremoved   : Boolean := cfg.Is_Set("*", "seedsremoved");
 otherfamily    : String  := cfg.Value_Of("*", "otherfamily");

begin

 Put_Line("fullname = "       & fullname);
 Put_Line("favouritefruit = " & favouritefruit);
 Put_Line("needspeeling = "   & Boolean'Image(needspeeling));
 Put_Line("seedsremoved = "   & Boolean'Image(seedsremoved));
 Put_Line("otherfamily = "    & otherfamily);

end;</lang>

Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = TRUE
seedsremoved = FALSE
otherfamily = Rhu Barber, Harry Barber

Aime

<lang aime>record r, s; integer c; file f; list l; text an, d, k;

an = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

f.affix("tmp/config");

while ((c = f.peek) ^ -1) {

   integer removed;
   f.side(" \t\r");
   c = f.peek;
   removed = c == ';';
   if (removed) {
       f.pick;
       f.side(" \t\r");
       c = f.peek;
   }
   c = place(an, c);
   if (-1 < c && c < 52) {
       f.near(an, k);
       if (removed) {
           r[k] = "false";
       } else {
           f.side(" \t\r");
           if (f.peek == '=') {
               f.pick;
               f.side(" \t\r");
           }
           f.ever(",#\n", d);
           d = bb_drop(d, " \r\t");
           if (f.peek != ',') {
               r[k] = ~d ? d : "true";
           } else {
               f.news(l, 0, 0, ",");
               lf_push(l, d);
               for (c, d in l) {
                   l[c] = bb_drop(d, " \r\t").bf_drop(" \r\t").string;
               }
               s.put(k, l);
               f.seek(-1, SEEK_CURRENT);
           }
       }
   }
   f.slip;

}

r.wcall(o_, 0, 2, ": ", "\n");

for (k, l in s) {

   o_(k, ": ");
   l.ucall(o_, 0, ", ");
   o_("\n");

}</lang>

Output:
FAVOURITEFRUIT: banana
FULLNAME: Foo Barber
NEEDSPEELING: true
SEEDSREMOVED: false
OTHERFAMILY: Rhu Barber, Harry Barber,

AutoHotkey

<lang AutoHotkey>

Author
AlephX, Aug 18 2011

data = %A_scriptdir%\rosettaconfig.txt comma := ","

Loop, Read, %data% { if NOT (instr(A_LoopReadLine, "#") == 1 OR A_LoopReadLine == "")

{ if instr(A_LoopReadLine, ";") == 1 { parameter := RegExReplace(Substr(A_LoopReadLine,2), "^[ \s]+|[ \s]+$", "") %parameter% = "1" } else { parameter := RegExReplace(A_LoopReadLine, "^[ \s]+|[ \s]+$", "")

if instr(parameter, A_Space) { value := substr(parameter, instr(parameter, A_Space)+1,999) parameter := substr(parameter, 1, instr(parameter, A_Space)-1)

if (instr(value, ",") <> 0) { Loop, Parse, value, %comma% ,%A_Space% %parameter%%A_Index% := A_Loopfield } else %parameter% = %value% } else %parameter% = "0" } } } msgbox, FULLNAME %fullname%`nFAVOURITEFRUIT %FAVOURITEFRUIT%`nNEEDSPEELING %NEEDSPEELING%`nSEEDSREMOVED %SEEDSREMOVED%`nOTHERFAMILY %OTHERFAMILY1% + %OTHERFAMILY2% </lang>

AWK

<lang AWK>

  1. syntax: GAWK -f READ_A_CONFIGURATION_FILE.AWK

BEGIN {

   fullname = favouritefruit = ""
   needspeeling = seedsremoved = "false"
   fn = "READ_A_CONFIGURATION_FILE.INI"
   while (getline rec <fn > 0) {
     tmp = tolower(rec)
     if (tmp ~ /^ *fullname/) { fullname = extract(rec) }
     else if (tmp ~ /^ *favouritefruit/) { favouritefruit = extract(rec) }
     else if (tmp ~ /^ *needspeeling/) { needspeeling = "true" }
     else if (tmp ~ /^ *seedsremoved/) { seedsremoved = "true" }
     else if (tmp ~ /^ *otherfamily/) { split(extract(rec),otherfamily,",") }
   }
   close(fn)
   printf("fullname=%s\n",fullname)
   printf("favouritefruit=%s\n",favouritefruit)
   printf("needspeeling=%s\n",needspeeling)
   printf("seedsremoved=%s\n",seedsremoved)
   for (i=1; i<=length(otherfamily); i++) {
     sub(/^ +/,"",otherfamily[i]) # remove leading spaces
     sub(/ +$/,"",otherfamily[i]) # remove trailing spaces
     printf("otherfamily(%d)=%s\n",i,otherfamily[i])
   }
   exit(0)

} function extract(rec, pos,str) {

   sub(/^ +/,"",rec)       # remove leading spaces before parameter name
   pos = match(rec,/[= ]/) # determine where data begins
   str = substr(rec,pos)   # extract the data
   gsub(/^[= ]+/,"",str)   # remove leading "=" and spaces
   sub(/ +$/,"",str)       # remove trailing spaces
   return(str)

} </lang>

Output:
fullname=Foo Barber
favouritefruit=banana
needspeeling=true
seedsremoved=false
otherfamily(1)=Rhu Barber
otherfamily(2)=Harry Barber

BASIC

Works with: QBasic version 1.1
Works with: QuickBasic version 4.5
Works with: VB-DOS version 1.0
Works with: QB64 version 1.1

This is a fully functional program with generic reading of a configuration file with variable names up to 20 characters and values up to 30 characters. Both limits can be expanded as needed but remember how quick it can eat RAM. It can read configuration files with variables separated of values through spaces or equal sign (=), so it will find "Variable = Value" or "Variable Value". Values can be separated by commas in configuration file and it will create a virtual array. This program will omit lines begining with #, ; or null. <lang qbasic> ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' Read a Configuration File V1.0 ' ' ' ' Developed by A. David Garza Marín in VB-DOS for ' ' RosettaCode. December 2, 2016. ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '

OPTION EXPLICIT ' For VB-DOS, PDS 7.1 ' OPTION _EXPLICIT ' For QB64

' SUBs and FUNCTIONs DECLARE FUNCTION ErrorMessage$ (WhichError AS INTEGER) DECLARE FUNCTION YorN$ () DECLARE FUNCTION FileExists% (WhichFile AS STRING) DECLARE FUNCTION ReadConfFile% (NameOfConfFile AS STRING) DECLARE FUNCTION getVariable$ (WhichVariable AS STRING) DECLARE FUNCTION getArrayVariable$ (WhichVariable AS STRING, WhichIndex AS INTEGER)

' Register for values located TYPE regVarValue

 VarName AS STRING * 20
 VarType AS INTEGER ' 1=String, 2=Integer, 3=Real
 VarValue AS STRING * 30

END TYPE

' Var DIM rVarValue() AS regVarValue, iErr AS INTEGER, i AS INTEGER, iHMV AS INTEGER DIM otherfamily(1 TO 2) AS STRING DIM fullname AS STRING, favouritefruit AS STRING, needspeeling AS INTEGER, seedsremoved AS INTEGER CONST ConfFileName = "config.fil"

' ------------------- Main Program ------------------------ CLS PRINT "This program reads a configuration file and shows the result." PRINT PRINT "Default file name: "; ConfFileName PRINT iErr = ReadConfFile(ConfFileName) IF iErr = 0 THEN

 iHMV = UBOUND(rVarValue)
 PRINT "Variables found in file:"
 FOR i = 1 TO iHMV
   PRINT RTRIM$(rVarValue(i).VarName); " = "; RTRIM$(rVarValue(i).VarValue); " (";
   SELECT CASE rVarValue(i).VarType
     CASE 0: PRINT "Undefined";
     CASE 1: PRINT "String";
     CASE 2: PRINT "Integer";
     CASE 3: PRINT "Real";
   END SELECT
   PRINT ")"
 NEXT i
 PRINT
 ' Sets required variables
 fullname = getVariable$("FullName")
 favouritefruit = getVariable$("FavouriteFruit")
 needspeeling = VAL(getVariable$("NeedSpeeling"))
 seedsremoved = VAL(getVariable$("SeedsRemoved"))
 FOR i = 1 TO 2
   otherfamily(i) = getArrayVariable$("OtherFamily", i)
 NEXT i
 PRINT "Variables requested to set values:"
 PRINT "fullname = "; fullname
 PRINT "favouritefruit = "; favouritefruit
 PRINT "needspeeling = ";
 IF needspeeling = 0 THEN PRINT "false" ELSE PRINT "true"
 PRINT "seedsremoved = ";
 IF seedsremoved = 0 THEN PRINT "false" ELSE PRINT "true"
 FOR i = 1 TO 2
   PRINT "otherfamily("; i; ") = "; otherfamily(i)
 NEXT i

ELSE

 PRINT ErrorMessage$(iErr)

END IF ' --------- End of Main Program -----------------------

END

FileError:

 iErr = ERR

RESUME NEXT

FUNCTION ErrorMessage$ (WhichError AS INTEGER)

   ' Var
   DIM sError AS STRING
   SELECT CASE WhichError
     CASE 0: sError = "Everything went ok."
     CASE 1: sError = "Configuration file doesn't exist."
     CASE 2: sError = "There are no variables in the given file."
   END SELECT
   ErrorMessage$ = sError

END FUNCTION

FUNCTION FileExists% (WhichFile AS STRING)

   ' Var
   DIM iFile AS INTEGER
   DIM iItExists AS INTEGER
   SHARED iErr AS INTEGER
   ON ERROR GOTO FileError
   iFile = FREEFILE
   iErr = 0
   OPEN WhichFile FOR BINARY AS #iFile
   IF iErr = 0 THEN
       iItExists = LOF(iFile) > 0
       CLOSE #iFile
       IF NOT iItExists THEN
           KILL WhichFile
       END IF
   END IF
   ON ERROR GOTO 0
   FileExists% = iItExists

END FUNCTION

FUNCTION getArrayVariable$ (WhichVariable AS STRING, WhichIndex AS INTEGER)

 ' Var
 DIM i AS INTEGER, iHMV AS INTEGER, iCount AS INTEGER
 DIM sVar AS STRING, sVal AS STRING, sWV AS STRING
 SHARED rVarValue() AS regVarValue
 ' Looks for a variable name and returns its value
 iHMV = UBOUND(rVarValue)
 sWV = UCASE$(LTRIM$(RTRIM$(WhichVariable)))
 sVal = ""
 DO
   i = i + 1
   sVar = UCASE$(RTRIM$(rVarValue(i).VarName))
   IF sVar = sWV THEN
     iCount = iCount + 1
     IF iCount = WhichIndex THEN
       sVal = LTRIM$(RTRIM$(rVarValue(i).VarValue))
     END IF
   END IF
 LOOP UNTIL i >= iHMV OR sVal <> ""
 ' Found it or not, it will return the result.
 ' If the result is "" then it didn't found the requested variable.
 getArrayVariable$ = sVal

END FUNCTION

FUNCTION getVariable$ (WhichVariable AS STRING)

 ' Var
 DIM i AS INTEGER, iHMV AS INTEGER
 DIM sVal AS STRING
 ' For a single variable, looks in the first (and only)
 '   element of the array that contains the name requested.
 sVal = getArrayVariable$(WhichVariable, 1)
 getVariable$ = sVal

END FUNCTION

FUNCTION ReadConfFile% (NameOfConfFile AS STRING)

 ' Var
 DIM iFile AS INTEGER, iType AS INTEGER, iVar AS INTEGER, iHMV AS INTEGER
 DIM iVal AS INTEGER, iCurVar AS INTEGER, i AS INTEGER, iErr AS INTEGER
 DIM dValue AS DOUBLE
 DIM sLine AS STRING, sVar AS STRING, sValue  AS STRING
 SHARED rVarValue() AS regVarValue
 ' This procedure reads a configuration file with variables
 '  and values separated by the equal sign (=) or a space.
 '  It needs the FileExists% function.
 '  Lines begining with # or blank will be ignored.
 IF FileExists%(NameOfConfFile) THEN
   iFile = FREEFILE
   REDIM rVarValue(1 TO 10) AS regVarValue
   OPEN NameOfConfFile FOR INPUT AS #iFile
     WHILE NOT EOF(iFile)
       LINE INPUT #iFile, sLine
       sLine = RTRIM$(LTRIM$(sLine))
       IF LEN(sLine) > 0 THEN ' Does it have any content?
         IF LEFT$(sLine, 1) <> "#" THEN  ' Is not a comment?
           IF LEFT$(sLine,1) = ";" THEN  ' It is a commented variable
             sLine = LTRIM$(MID$(sLine, 2))
           END IF
           iVar = INSTR(sLine, "=")  ' Is there an equal sign?
           IF iVar = 0 THEN iVar = INSTR(sLine, " ") ' if not then is there a space?
           GOSUB AddASpaceForAVariable
           iCurVar = iHMV
           IF iVar > 0 THEN  ' Is a variable and a value
             rVarValue(iHMV).VarName = LEFT$(sLine, iVar - 1)
           ELSE              ' Is just a variable name
             rVarValue(iHMV).VarName = sLine
             rVarValue(iHMV).VarValue = ""
           END IF
           IF iVar > 0 THEN  ' Get the value(s)
             sLine = LTRIM$(MID$(sLine, iVar + 1))
             DO  ' Look for commas
               iVal = INSTR(sLine, ",")
               IF iVal > 0 THEN  ' There is a comma
                 rVarValue(iHMV).VarValue = RTRIM$(LEFT$(sLine, iVal - 1))
                 GOSUB AddASpaceForAVariable
                 rVarValue(iHMV).VarName = rVarValue(iHMV - 1).VarName  ' Repeats the variable name
                 sLine = LTRIM$(MID$(sLine, iVal + 1))
               END IF
             LOOP UNTIL iVal = 0
             rVarValue(iHMV).VarValue = sLine
             ' Determine the variable type of each variable found in this step
             FOR i = iCurVar TO iHMV
               GOSUB DetermineVariableType
             NEXT i
           END IF
         END IF
       END IF
     WEND
   CLOSE iFile
   IF iHMV > 0 THEN
     REDIM PRESERVE rVarValue(1 TO iHMV) AS regVarValue
     iErr = 0 ' Everything ran ok.
   ELSE
     REDIM rVarValue(1 TO 1) AS regVarValue
     iErr = 2 ' No variables found in configuration file
   END IF
 ELSE
   iErr = 1 ' File doesn't exist
 END IF
 ReadConfFile = iErr

EXIT FUNCTION

AddASpaceForAVariable:

 iHMV = iHMV + 1
 IF UBOUND(rVarValue) < iHMV THEN  ' Are there space for a new one?
   REDIM PRESERVE rVarValue(1 TO iHMV + 9) AS regVarValue
 END IF

RETURN

DetermineVariableType:

 sValue = RTRIM$(rVarValue(i).VarValue)
 IF ASC(LEFT$(sValue, 1)) < 48 OR ASC(LEFT$(sValue, 1)) > 57 THEN
   rVarValue(i).VarType = 1  ' String
 ELSE
   dValue = VAL(sValue)
   IF CLNG(dValue) = dValue THEN
     rVarValue(i).VarType = 2 ' Integer
   ELSE
     rVarValue(i).VarType = 3 ' Real
   END IF
 END IF

RETURN

END FUNCTION </lang> Run

This program reads a configuration file and shows the result.

Default file name: config.fil

Variables found in file:
FULLNAME = Foo Barber (String)
FAVOURITEFRUIT = banana (String)
NEEDSPEELING = (Undefined)
SEEDSREMOVED = (Undefined)
OTHERFAMILY = Rhu Barber (String)
OTHERFAMILY = Harry Barber (String)

Variables requested to set values:
fullname = Foo Barber
favouritefruit = banana
needspeeling = false
seedsremoved = false
otherfamily( 1 ) = Rhu Barber
otherfamily( 2 ) = Harry Barber

BBC BASIC

<lang bbcbasic> BOOL = 1

     NAME = 2
     ARRAY = 3
     
     optfile$ = "options.cfg"
     
     fullname$ = FNoption(optfile$, "FULLNAME", NAME)
     favouritefruit$ = FNoption(optfile$, "FAVOURITEFRUIT", NAME)
     needspeeling% = FNoption(optfile$, "NEEDSPEELING", BOOL)
     seedsremoved% = FNoption(optfile$, "SEEDSREMOVED", BOOL)
     !^otherfamily$() = FNoption(optfile$, "OTHERFAMILY", ARRAY)
     
     PRINT "fullname = " fullname$
     PRINT "favouritefruit = " favouritefruit$
     PRINT "needspeeling = "; : IF needspeeling% PRINT "true" ELSE PRINT "false"
     PRINT "seedsremoved = "; : IF seedsremoved% PRINT "true" ELSE PRINT "false"
     PRINT "otherfamily(1) = " otherfamily$(1)
     PRINT "otherfamily(2) = " otherfamily$(2)
     END
     
     DEF FNoption(file$, key$, type%)
     LOCAL file%, opt$, comma%, bool%, name$, size%, !^array$()
     file% = OPENIN(file$)
     IF file% = 0 THEN = 0
     WHILE NOT EOF#file%
       opt$ = GET$#file%
       WHILE RIGHT$(opt$) = " " opt$ = LEFT$(opt$) : ENDWHILE
       IF opt$ = key$ OR LEFT$(opt$, LEN(key$)+1) = key$ + " " THEN
         opt$ = MID$(opt$, LEN(key$) + 1)
         WHILE LEFT$(opt$,1) = " " opt$ = MID$(opt$,2) : ENDWHILE
         CASE type% OF
           WHEN BOOL: bool% = TRUE : EXIT WHILE
           WHEN NAME: name$ = opt$ : EXIT WHILE
           WHEN ARRAY:
             REPEAT
               comma% = INSTR(opt$, ",", comma%+1)
               IF comma% size% += 1
             UNTIL comma% = 0
             DIM array$(size% + 1)
             size% = 0
             REPEAT
               comma% = INSTR(opt$, ",")
               IF comma% THEN
                 size% += 1
                 array$(size%) = LEFT$(opt$, comma%-1)
                 opt$ = MID$(opt$, comma%+1)
                 WHILE LEFT$(opt$,1) = " " opt$ = MID$(opt$,2) : ENDWHILE
               ENDIF
             UNTIL comma% = 0
             array$(size% + 1) = opt$
             EXIT WHILE
         ENDCASE
       ENDIF
     ENDWHILE
     CLOSE #file%
     CASE type% OF
       WHEN BOOL: = bool%
       WHEN NAME: = name$
       WHEN ARRAY: = !^array$()
     ENDCASE
     = 0</lang>
Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily(1) = Rhu Barber
otherfamily(2) = Harry Barber

C

Library: libconfini

optimized

<lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. include <string.h>
  3. include <confini.h>
  1. define rosetta_uint8_t unsigned char
  1. define FALSE 0
  2. define TRUE 1
  1. define CONFIGS_TO_READ 5
  2. define INI_ARRAY_DELIMITER ','

/* Assume that the config file represent a struct containing all the parameters to load */ struct configs { char *fullname; char *favouritefruit; rosetta_uint8_t needspeeling; rosetta_uint8_t seedsremoved; char **otherfamily; size_t otherfamily_len; size_t _configs_left_; };

static char ** make_array (size_t * arrlen, const char * src, const size_t buffsize, IniFormat ini_format) {

/* Allocate a new array of strings and populate it from the stringified source */ *arrlen = ini_array_get_length(src, INI_ARRAY_DELIMITER, ini_format); char ** const dest = *arrlen ? (char **) malloc(*arrlen * sizeof(char *) + buffsize) : NULL; if (!dest) { return NULL; } memcpy(dest + *arrlen, src, buffsize); char * iter = (char *) (dest + *arrlen); for (size_t idx = 0; idx < *arrlen; idx++) { dest[idx] = ini_array_release(&iter, INI_ARRAY_DELIMITER, ini_format); ini_string_parse(dest[idx], ini_format); } return dest;

}

static int configs_member_handler (IniDispatch *this, void *v_confs) {

struct configs *confs = (struct configs *) v_confs;

if (this->type != INI_KEY) {

return 0;

}

if (ini_string_match_si("FULLNAME", this->data, this->format)) {

if (confs->fullname) { return 0; } this->v_len = ini_string_parse(this->value, this->format); /* Remove all quotes, if any */ confs->fullname = strndup(this->value, this->v_len); confs->_configs_left_--;

} else if (ini_string_match_si("FAVOURITEFRUIT", this->data, this->format)) {

if (confs->favouritefruit) { return 0; } this->v_len = ini_string_parse(this->value, this->format); /* Remove all quotes, if any */ confs->favouritefruit = strndup(this->value, this->v_len); confs->_configs_left_--;

} else if (ini_string_match_si("NEEDSPEELING", this->data, this->format)) {

if (~confs->needspeeling & 0x80) { return 0; } confs->needspeeling = ini_get_bool(this->value, TRUE); confs->_configs_left_--;

} else if (ini_string_match_si("SEEDSREMOVED", this->data, this->format)) {

if (~confs->seedsremoved & 0x80) { return 0; } confs->seedsremoved = ini_get_bool(this->value, TRUE); confs->_configs_left_--;

} else if (!confs->otherfamily && ini_string_match_si("OTHERFAMILY", this->data, this->format)) {

if (confs->otherfamily) { return 0; } this->v_len = ini_array_collapse(this->value, INI_ARRAY_DELIMITER, this->format); /* Save memory (not strictly needed) */ confs->otherfamily = make_array(&confs->otherfamily_len, this->value, this->v_len + 1, this->format); confs->_configs_left_--;

}

/* Optimization: stop reading the INI file when we have all we need */ return !confs->_configs_left_;

}

static int populate_configs (struct configs * confs) {

/* Define the format of the configuration file */ IniFormat config_format = { .delimiter_symbol = INI_ANY_SPACE, .case_sensitive = FALSE, .semicolon_marker = INI_IGNORE, .hash_marker = INI_IGNORE, .multiline_nodes = INI_NO_MULTILINE, .section_paths = INI_NO_SECTIONS, .no_single_quotes = FALSE, .no_double_quotes = FALSE, .no_spaces_in_names = TRUE, .implicit_is_not_empty = TRUE, .do_not_collapse_values = FALSE, .preserve_empty_quotes = FALSE, .disabled_after_space = TRUE, .disabled_can_be_implicit = FALSE };

*confs = (struct configs) { NULL, NULL, 0x80, 0x80, NULL, 0, CONFIGS_TO_READ };

if (load_ini_path("rosetta.conf", config_format, NULL, configs_member_handler, confs) & CONFINI_ERROR) {

fprintf(stderr, "Sorry, something went wrong :-(\n"); return 1;

}

confs->needspeeling &= 0x7F; confs->seedsremoved &= 0x7F;

return 0;

}

int main () {

struct configs confs;

ini_global_set_implicit_value("YES", 0);

if (populate_configs(&confs)) {

return 1;

}

/* Print the configurations parsed */

printf(

"Full name: %s\n" "Favorite fruit: %s\n" "Need spelling: %s\n" "Seeds removed: %s\n",

confs.fullname, confs.favouritefruit, confs.needspeeling ? "True" : "False", confs.seedsremoved ? "True" : "False"

);

for (size_t idx = 0; idx < confs.otherfamily_len; idx++) {

printf("Other family[%d]: %s\n", idx, confs.otherfamily[idx]);

}

/* Free the allocated memory */

#define FREE_NON_NULL(PTR) if (PTR) { free(PTR); }

FREE_NON_NULL(confs.fullname); FREE_NON_NULL(confs.favouritefruit); FREE_NON_NULL(confs.otherfamily);

return 0;

}</lang>

Output:
Full name: Foo Barber
Favorite fruit: banana
Need spelling: True
Seeds removed: False
Other family[0]: Rhu Barber
Other family[1]: Harry Barber

C++

Library: Boost
Works with: Visual Studio version 2005

unoptimized

<lang cpp>#include "stdafx.h"

  1. include <iostream>
  2. include <fstream>
  3. include <vector>
  4. include <string>
  5. include <boost/tokenizer.hpp>
  6. include <boost/algorithm/string/case_conv.hpp>

using namespace std; using namespace boost;

typedef boost::tokenizer<boost::char_separator<char> > Tokenizer; static const char_separator<char> sep(" ","#;,");

//Assume that the config file represent a struct containing all the parameters to load struct configs{ string fullname; string favoritefruit; bool needspelling; bool seedsremoved; vector<string> otherfamily; } conf;

void parseLine(const string &line, configs &conf) { if (line[0] == '#' || line.empty()) return; Tokenizer tokenizer(line, sep); vector<string> tokens; for (Tokenizer::iterator iter = tokenizer.begin(); iter != tokenizer.end(); iter++) tokens.push_back(*iter); if (tokens[0] == ";"){ algorithm::to_lower(tokens[1]); if (tokens[1] == "needspeeling") conf.needspelling = false; if (tokens[1] == "seedsremoved") conf.seedsremoved = false; } algorithm::to_lower(tokens[0]); if (tokens[0] == "needspeeling") conf.needspelling = true; if (tokens[0] == "seedsremoved") conf.seedsremoved = true; if (tokens[0] == "fullname"){ for (unsigned int i=1; i<tokens.size(); i++) conf.fullname += tokens[i] + " "; conf.fullname.erase(conf.fullname.size() -1, 1); } if (tokens[0] == "favouritefruit") for (unsigned int i=1; i<tokens.size(); i++) conf.favoritefruit += tokens[i]; if (tokens[0] == "otherfamily"){ unsigned int i=1; string tmp; while (i<=tokens.size()){ if ( i == tokens.size() || tokens[i] ==","){ tmp.erase(tmp.size()-1, 1); conf.otherfamily.push_back(tmp); tmp = ""; i++; } else{ tmp += tokens[i]; tmp += " "; i++; } } } }

int _tmain(int argc, TCHAR* argv[]) { if (argc != 2) { wstring tmp = argv[0]; wcout << L"Usage: " << tmp << L" <configfile.ini>" << endl; return -1; } ifstream file (argv[1]);

if (file.is_open()) while(file.good()) { char line[255]; file.getline(line, 255); string linestring(line); parseLine(linestring, conf); } else { cout << "Unable to open the file" << endl; return -2; }

cout << "Fullname= " << conf.fullname << endl; cout << "Favorite Fruit= " << conf.favoritefruit << endl; cout << "Need Spelling= " << (conf.needspelling?"True":"False") << endl; cout << "Seed Removed= " << (conf.seedsremoved?"True":"False") << endl; string otherFamily; for (unsigned int i = 0; i < conf.otherfamily.size(); i++) otherFamily += conf.otherfamily[i] + ", "; otherFamily.erase(otherFamily.size()-2, 2); cout << "Other Family= " << otherFamily << endl;

return 0; } </lang>

Output:
Fullname= Foo Barber
Favorite Fruit= banana
Need Spelling= True
Seed Removed= False
Other Family= Rhu Barber, Harry Barber

Solution without Boost libraries. No optimisation. <lang cpp>#include <iostream>

  1. include <iomanip>
  2. include <string>
  3. include <exception>
  4. include <fstream>
  5. include <vector>
  6. include <algorithm>

struct confi { std::string fullname; std::string favouritefruit; bool needspeeling; bool seedsremoved; std::vector<std::string> otherfamily; };

void read_config(std::ifstream& in, confi& out) { in.open("Config.txt"); std::string str; out.needspeeling = false; out.seedsremoved = false; while(!in.eof()) { while(getline(in,str)) { std::string::size_type begin = str.find_first_not_of(" \f\t\v"); //Skips blank lines if(begin == std::string::npos) continue; //Skips # if(std::string("#").find(str[begin]) != std::string::npos) continue; std::string firstWord; try { firstWord = str.substr(0,str.find(" ")); } catch(std::exception& e) { firstWord = str.erase(str.find_first_of(" "),str.find_first_not_of(" ")); } std::transform(firstWord.begin(),firstWord.end(),firstWord.begin(), ::toupper); if(firstWord == "FULLNAME") out.fullname = str.substr(str.find(" ")+1,str.length()); if(firstWord == "FAVOURITEFRUIT") out.favouritefruit = str.substr(str.find(" ")+1,str.length()); if(firstWord == "NEEDSPEELING") out.needspeeling = true; if(firstWord == "SEEDSREMOVED") out.seedsremoved = true; if(firstWord == "OTHERFAMILY") { size_t found = str.find(","); if(found != std::string::npos) { out.otherfamily.push_back(str.substr(str.find_first_of(" ")+1,found-str.find_first_of(" ")-1)); out.otherfamily.push_back(str.substr(found+2,str.length())); } }

} } std::cout << "Full Name: " << out.fullname << std::endl; std::cout << "Favourite Fruit: " << out.favouritefruit << std::endl; std::cout << "Needs peeling?: "; if(out.needspeeling == true) std::cout << "True" << std::endl; else std::cout << "False" << std::endl; std::cout << "Seeds removed?: "; if(out.seedsremoved == true) std::cout << "True" << std::endl; else std::cout << "False" << std::endl; std::cout << "Other family members: " << out.otherfamily[0] << ", " << out.otherfamily[1] << std::endl; } int main() { std::ifstream inp; confi outp; read_config(inp,outp); } </lang>

Output:
Full Name: Foo Barber
Favourite Fruit: banana
Needs peeling?: True
Seeds removed?: False
Other family members: Rhu Barber, Harry Barber

Clojure

<lang clojure>(ns read-conf-file.core

 (:require [clojure.java.io :as io]
           [clojure.string :as str])
 (:gen-class))

(def conf-keys ["fullname"

               "favouritefruit"
               "needspeeling"
               "seedsremoved"
               "otherfamily"])

(defn get-lines

 "Read file returning vec of lines."
 [file]
 (try 
   (with-open [rdr (io/reader file)]
     (into [] (line-seq rdr)))
   (catch Exception e (.getMessage e))))

(defn parse-line

 "Parse passed line returning vec: token, vec of values."
 [line]
 (if-let [[_ k v] (re-matches #"(?i)^\s*([a-z]+)(?:\s+|=)?(.+)?$" line)]
   (let [k (str/lower-case k)]
     (if v
       [k (str/split v #",\s*")]
       [k [true]]))))

(defn mk-conf

 "Build configuration map from lines."
 [lines]
 (->> (map parse-line lines)
      (filter (comp not nil?))
      (reduce (fn
                [m [k v]]
                (assoc m k v)) {})))

(defn output

 [conf-keys conf]
 (doseq [k conf-keys]
   (let [v (get conf k)]
     (if v
       (println (format "%s = %s" k (str/join ", " v)))
       (println (format "%s = %s" k "false"))))))

(defn -main

 [filename]
 (output conf-keys (mk-conf (get-lines filename))))</lang>
Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily = Rhu Barber, Harry Barber

COBOL

<lang cobol>

      identification division.
      program-id. ReadConfiguration.
      environment division.
      configuration section.
      repository.
          function all intrinsic.
      input-output section.
      file-control.
          select config-file     assign to "Configuration.txt"
                                 organization line sequential.
      data division.
      file section.
      fd  config-file.
      01  config-record          pic is x(128).
      working-storage section.
      77  idx                    pic 9(3).
      77  pos                    pic 9(3).
      77  last-pos               pic 9(3).
      77  config-key             pic x(32).
      77  config-value           pic x(64).
      77  multi-value            pic x(64).
      77  full-name              pic x(64).
      77  favourite-fruit        pic x(64).
      77  other-family           pic x(64) occurs 10.
      77  need-speeling          pic x(5) value "false".
      77  seeds-removed          pic x(5) value "false".
      procedure division.
      main.
          open input config-file
          perform until exit
             read config-file
                at end
                   exit perform
             end-read  
             move trim(config-record) to config-record
             if config-record(1:1) = "#" or ";" or spaces
                exit perform cycle
             end-if
             unstring config-record delimited by spaces into config-key
             move trim(config-record(length(trim(config-key)) + 1:)) to config-value
             if config-value(1:1) = "="
                move trim(config-value(2:)) to config-value
             end-if
             evaluate upper-case(config-key)
                when "FULLNAME"
                   move config-value to full-name
                when "FAVOURITEFRUIT"
                   move config-value to favourite-fruit
                when "NEEDSPEELING"
                   if config-value = spaces
                      move "true" to config-value
                   end-if
                   if config-value = "true" or "false"
                      move config-value to need-speeling
                   end-if
                when "SEEDSREMOVED"
                   if config-value = spaces
                      move "true" to config-value
                   end-if,
                   if config-value = "true" or "false"
                      move config-value to seeds-removed
                   end-if
                when "OTHERFAMILY"
                   move 1 to idx, pos
                   perform until exit
                      unstring config-value delimited by "," into multi-value with pointer pos
                         on overflow
                            move trim(multi-value) to other-family(idx)
                            move pos to last-pos
                         not on overflow
                            if config-value(last-pos:) <> spaces
                               move trim(config-value(last-pos:)) to other-family(idx)
                            end-if,
                            exit perform
                      end-unstring
                      add 1 to idx
                   end-perform
             end-evaluate
          end-perform
          close config-file
          display "fullname = " full-name
          display "favouritefruit = " favourite-fruit
          display "needspeeling = " need-speeling
          display "seedsremoved = " seeds-removed
          perform varying idx from 1 by 1 until idx > 10
             if other-family(idx) <> low-values
                display "otherfamily(" idx ") = " other-family(idx)
             end-if
          end-perform
          .

</lang>

Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily(001) = Rhu Barber
otherfamily(002) = Harry Barber

Common Lisp

Using parser-combinators available in quicklisp: <lang lisp>(ql:quickload :parser-combinators)

(defpackage :read-config

 (:use :cl :parser-combinators))

(in-package :read-config)

(defun trim-space (string)

 (string-trim '(#\space #\tab) string))

(defun any-but1? (except)

 (named-seq? (<- res (many1? (except? (item) except)))
             (coerce res 'string)))

(defun values? ()

 (named-seq? (<- values (sepby? (any-but1? #\,) #\,))
             (mapcar 'trim-space values)))

(defun key-values? ()

 (named-seq? (<- key (word?))
             (opt? (many? (whitespace?)))
             (opt? #\=)
             (<- values (values?))
             (cons key (or (if (cdr values) values (car values)) t))))

(defun parse-line (line)

 (setf line (trim-space line))
 (if (or (string= line "") (member (char line 0) '(#\# #\;)))
     :comment
     (parse-string* (key-values?) line)))

(defun parse-config (stream)

 (let ((hash (make-hash-table :test 'equal)))
   (loop for line = (read-line stream nil nil)
      while line
      do (let ((parsed (parse-line line)))
           (cond ((eq parsed :comment))
                 ((eq parsed nil) (error "config parser error: ~a" line))
                 (t (setf (gethash (car parsed) hash) (cdr parsed))))))
   hash))</lang>
Output:

<lang lisp>READ-CONFIG> (with-open-file (s "test.cfg") (parse-config s))

  1. <HASH-TABLE :TEST EQUAL :COUNT 4 {100BD25B43}>

READ-CONFIG> (maphash (lambda (k v) (print (list k v))) *)

("FULLNAME" "Foo Barber") ("FAVOURITEFRUIT" "banana") ("NEEDSPEELING" T) ("OTHERFAMILY" ("Rhu Barber" "Harry Barber")) NIL READ-CONFIG> (gethash "SEEDSREMOVED" **) NIL NIL </lang>

D

<lang d>import std.stdio, std.string, std.conv, std.regex, std.getopt;

enum VarName(alias var) = var.stringof.toUpper;

void setOpt(alias Var)(in string line) {

   auto m = match(line, regex(`^` ~ VarName!Var ~ `(\s+(.*))?`));
   if (!m.empty) {
       static if (is(typeof(Var) == string))
           Var = m.captures.length > 2 ? m.captures[2] : "";
       static if (is(typeof(Var) == bool))
           Var = true;
       static if (is(typeof(Var) == int))
           Var = m.captures.length > 2 ? to!int(m.captures[2]) : 0;
   }

}

void main(in string[] args) {

   string fullName, favouriteFruit, otherFamily;
   bool needsPeeling, seedsRemoved; // Default false.
   auto f = "readcfg.txt".File;
   foreach (line; f.byLine) {
       auto opt = line.strip.idup;
       setOpt!fullName(opt);
       setOpt!favouriteFruit(opt);
       setOpt!needsPeeling(opt);
       setOpt!seedsRemoved(opt);
       setOpt!otherFamily(opt);
   }
   writefln("%14s = %s", VarName!fullName, fullName);
   writefln("%14s = %s", VarName!favouriteFruit, favouriteFruit);
   writefln("%14s = %s", VarName!needsPeeling, needsPeeling);
   writefln("%14s = %s", VarName!seedsRemoved, seedsRemoved);
   writefln("%14s = %s", VarName!otherFamily, otherFamily);

}</lang>

Output:
     FULLNAME = Foo Barber
AVOURITEFRUIT = banana
 NEEDSPEELING = true
 SEEDSREMOVED = false
  OTHERFAMILY = Rhu Barber, Harry Barber

DCL

<lang DCL>$ open input config.ini $ loop: $ read /end_of_file = done input line $ line = f$edit( line, "trim" )  ! removes leading and trailing spaces or tabs $ if f$length( line ) .eq. 0 then $ goto loop $ first_character = f$extract( 0, 1, line ) $ if first_character .eqs. "#" .or. first_character .eqs. ";" then $ goto loop $ equal_sign_offset = f$locate( "=", line ) $ length_of_line = f$length( line ) $ if equal_sign_offset .ne. length_of_line then $ line = f$extract( 0, equal_sign_offset, line ) + " " + f$extract( equal_sign_offset + 1, length_of_line, line ) $ option_name = f$element( 0, " ", line ) $ parameter_data = line - option_name - " " $ if parameter_data .eqs. "" then $ parameter_data = "true" $ 'option_name = parameter_data $ show symbol 'option_name $ goto loop $ done: $ close input</lang>

Output:
$ @read_a_configuration_file
  FULLNAME = "Foo Barber"
  FAVOURITEFRUIT = "banana"
  NEEDSPEELING = "true"

Delphi

Library: uSettings

Unit for manager config files, used in Update a configuration file. <lang Delphi> unit uSettings;

interface

uses

 System.SysUtils, System.IoUtils, System.Generics.Collections, System.Variants;

type

 TVariable = record
   value: variant;
   function ToString: string;
   class operator Implicit(a: variant): TVariable;
   class operator Implicit(a: TVariable): TArray<string>;
   class operator Implicit(a: TVariable): string;
 end;
 TSettings = class(TDictionary<string, TVariable>)
 private
   function GetVariable(key: string): TVariable;
   procedure SetVariable(key: string; const Value: TVariable);
   function GetKey(line: string; var key: string; var value: variant; var
     disable: boolean): boolean;
   function GetAllKeys: TList<string>;
 public
   procedure LoadFromFile(Filename: TfileName);
   procedure SaveToFile(Filename: TfileName);
   property Variable[key: string]: TVariable read GetVariable write SetVariable; default;
 end;

implementation

{ TVariable }

class operator TVariable.Implicit(a: variant): TVariable; begin

 Result.value := a;

end;

class operator TVariable.Implicit(a: TVariable): TArray<string>; begin

 if VarIsType(a.value, varArray or varOleStr) then
   Result := a.value
 else
   raise Exception.Create('Error: cant convert this type data in array');

end;

class operator TVariable.Implicit(a: TVariable): string; begin

 Result := a.ToString;

end;

function TVariable.ToString: string; var

 arr: TArray<string>;

begin

 if VarIsType(value, varArray or varOleStr) then
 begin
   arr := value;
   Result := string.Join(', ', arr).Trim;
 end
 else
   Result := value;
 Result := Result.Trim;

end;

{ TSettings }

function TSettings.GetAllKeys: TList<string>; var

 key: string;

begin

 Result := TList<string>.Create;
 for key in Keys do
   Result.Add(key);

end;

function TSettings.GetKey(line: string; var key: string; var value: variant; var

 disable: boolean): boolean;

var

 line_: string;
 j: integer;

begin

 line_ := line.Trim;
 Result := not (line_.IsEmpty or (line_[1] = '#'));
 if not Result then
   exit;
 disable := (line_[1] = ';');
 if disable then
   delete(line_, 1, 1);
 var data := line_.Split([' '], TStringSplitOptions.ExcludeEmpty);
 case length(data) of
   1: //Boolean
     begin
       key := data[0].ToUpper;
       value := True;
     end;
   2: //Single String
     begin
       key := data[0].ToUpper;
       value := data[1].Trim;
     end;
 else // Mult String value or Array of value
   begin
     key := data[0];
     delete(line_, 1, key.Length);
     if line_.IndexOf(',') > -1 then
     begin
       data := line_.Trim.Split([','], TStringSplitOptions.ExcludeEmpty);
       for j := 0 to High(data) do
         data[j] := data[j].Trim;
       value := data;
     end
     else
       value := line_.Trim;
   end;
 end;
 Result := true;

end;

function TSettings.GetVariable(key: string): TVariable; begin

 key := key.Trim.ToUpper;
 if not ContainsKey(key) then
   add(key, false);
 result := Items[key];

end;

procedure TSettings.LoadFromFile(Filename: TfileName); var

 key, line: string;
 value: variant;
 disabled: boolean;
 Lines: TArray<string>;

begin

 if not FileExists(Filename) then
   exit;
 Clear;
 Lines := TFile.ReadAllLines(Filename);
 for line in Lines do
 begin
   if GetKey(line, key, value, disabled) then
   begin
     if disabled then
       AddOrSetValue(key, False)
     else
       AddOrSetValue(key, value)
   end;
 end;

end;

procedure TSettings.SaveToFile(Filename: TfileName); var

 key, line: string;
 value: variant;
 disabled: boolean;
 Lines: TArray<string>;
 i: Integer;
 All_kyes: TList<string>;

begin

 All_kyes := GetAllKeys();
 SetLength(Lines, 0);
 i := 0;
 if FileExists(Filename) then
 begin
   Lines := TFile.ReadAllLines(Filename);
   for i := high(Lines) downto 0 do
   begin
     if GetKey(Lines[i], key, value, disabled) then
     begin
       if not ContainsKey(key) then
       begin
         Lines[i] := '; ' + Lines[i];
         Continue;
       end;
       All_kyes.Remove(key);
       disabled := VarIsType(Variable[key].value, varBoolean) and (Variable[key].value
         = false);
       if not disabled then
       begin
         if VarIsType(Variable[key].value, varBoolean) then
           Lines[i] := key
         else
           Lines[i] := format('%s %s', [key, Variable[key].ToString])
       end
       else
         Lines[i] := '; ' + key;
     end;
   end;
 end;
 // new keys
 i := high(Lines) + 1;
 SetLength(Lines, Length(Lines) + All_kyes.Count);
 for key in All_kyes do
 begin
   Lines[i] := format('%s %s', [key, Variable[key].ToString]);
   inc(i);
 end;
 Tfile.WriteAllLines(Filename, Lines);
 All_kyes.Free;

end;

procedure TSettings.SetVariable(key: string; const Value: TVariable); begin

 AddOrSetValue(key.Trim.ToUpper, Value);

end; end. </lang> Usage of unit: <lang Delphi> program ReadAConfigFile;

{$APPTYPE CONSOLE}

uses

 System.SysUtils,
 uSettings;

const

 FileName = 'Config.txt';

var

 Settings: TSettings;

procedure show(key: string; value: string); begin

 writeln(format('%14s = %s', [key, value]));

end;

begin

 Settings := TSettings.Create;
 Settings.LoadFromFile(FileName);
 for var k in Settings.Keys do
   show(k, Settings[k]);
 Settings.Free;
 Readln;

end.</lang>

Output:
FAVOURITEFRUIT = banana
      FULLNAME = Foo Barber
  NEEDSPEELING = True
  SEEDSREMOVED = False
   OTHERFAMILY = Rhu Barber, Harry Barber

EchoLisp

This example is incorrect. Please fix the code and remove this message.

Details: Makes no attempt to parse the configuration file of the task description.

There is no 'config file' in EchoLisp, but a (preferences) function which is automatically loaded and evaluated at boot-time, and automatically saved after modification. This function can set global parameters, or call other functions, or load libraries. <lang lisp> (edit 'preferences)

current contents to edit is displayed in the input box

(define (preferences) (define-syntax-rule (++ n) (begin (set! n (1+ n)) n)) (define-syntax-rule (% a b) (modulo a b))

(lib 'gloops)

(lib 'timer))

enter new preferences

(define (preferences)

   (define FULLNAME "Foo Barber")
   (define FAVOURITEFRUIT 'banana)
   (define NEEDSPELLING #t)
SEEDSREMOVED
   (define OTHERFAMILY '("Rhu Barber" "Harry Barber")))

</lang>

Output:

<lang lisp>

press F5 or COMMAND-R to reload

EchoLisp - 2.13.12 📗 local-db: db.version: 13

enter parameters names

NEEDSPELLING → #t FAVOURITEFRUIT → banana SEEDSREMOVED 😡 error: #|user| : unbound variable : SEEDSREMOVED </lang>

Elixir

Translation of: Erlang

<lang elixir>defmodule Configuration_file do

 def read(file) do
   File.read!(file)
   |> String.split(~r/\n|\r\n|\r/, trim: true)
   |> Enum.reject(fn line -> String.starts_with?(line, ["#", ";"]) end)
   |> Enum.map(fn line ->
        case String.split(line, ~r/\s/, parts: 2) do
          [option]         -> {to_atom(option), true}
          [option, values] -> {to_atom(option), separate(values)}
        end
      end)
 end
 
 def task do
   defaults = [fullname: "Kalle", favouritefruit: "apple", needspeeling: false, seedsremoved: false]
   options = read("configuration_file") ++ defaults
   [:fullname, :favouritefruit, :needspeeling, :seedsremoved, :otherfamily]
   |> Enum.each(fn x ->
        values = options[x]
        if is_boolean(values) or length(values)==1 do
          IO.puts "#{x} = #{values}"
        else
          Enum.with_index(values) |> Enum.each(fn {value,i} ->
            IO.puts "#{x}(#{i+1}) = #{value}"
          end)
        end
      end)
 end
 
 defp to_atom(option), do: String.downcase(option) |> String.to_atom
 
 defp separate(values), do: String.split(values, ",") |> Enum.map(&String.strip/1)

end

Configuration_file.task</lang>

Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily(1) = Rhu Barber
otherfamily(2) = Harry Barber

Erlang

<lang Erlang> -module( configuration_file ).

-export( [read/1, task/0] ).

read( File ) ->

   {ok, Binary} = file:read_file( File ),
   Lines = [X || <<First:8, _T/binary>> = X <- binary:split(Binary, <<"\n">>, [global]), First =/= $#, First =/= $;],
   [option_from_binaries(binary:split(X, <<" ">>)) || X <- Lines].

task() ->

   Defaults = [{fullname, "Kalle"}, {favouritefruit, "apple"}, {needspeeling, false}, {seedsremoved, false}],
   Options = read( "configuration_file" ) ++ Defaults,
   [io:fwrite("~p = ~p~n", [X, proplists:get_value(X, Options)]) || X <- [fullname, favouritefruit, needspeeling, seedsremoved, otherfamily]].


option_from_binaries( [Option] ) -> {erlang:list_to_atom(string:to_lower(erlang:binary_to_list(Option))), true}; option_from_binaries( [Option, Values] ) -> {erlang:list_to_atom(string:to_lower(erlang:binary_to_list(Option))), option_from_binaries_value(binary:split(Values, <<", ">>))}.

option_from_binaries_value( [Value] ) -> erlang:binary_to_list(Value); option_from_binaries_value( Values ) -> [erlang:binary_to_list(X) || X <- Values]. </lang>

Output:
50> configuration_file:task().
fullname = "Foo Barber"
favouritefruit = "banana"
needspeeling = true
seedsremoved = false
otherfamily = ["Rhu Barber","Harry Barber"]

Fantom

<lang fantom> class Main {

 // remove the given key and an optional '=' from start of line
 Str removeKey (Str key, Str line)
 {
   remainder := line[key.size..-1].trim
   if (remainder.startsWith("="))
   {
     remainder = remainder.replace("=", "").trim
   }
   return remainder
 }
 Void main ()
 {
   // define the variables which need configuring
   fullname := ""
   favouritefruit := "" 
   needspeeling := false 
   seedsremoved := false
   Str[] otherfamily := [,]
   // loop through the file, setting variables as needed
   File(`config.dat`).eachLine |Str line|
   {
     line = line.trim
     if (line.isEmpty || line.startsWith("#") || line.startsWith(";")) 
     { 
       // do nothing for empty and comment lines
     }
     else if (line.upper.startsWith("FULLNAME"))
     {
       fullname = removeKey("FULLNAME", line)
     }
     else if (line.upper.startsWith("FAVOURITEFRUIT"))
     {
       favouritefruit = removeKey("FAVOURITEFRUIT", line)
     }
     else if (line.upper.startsWith("NEEDSPEELING"))
     {
       needspeeling = true
     }
     else if (line.upper.startsWith("SEEDSREMOVED"))
     {
       seedsremoved = true
     }
     else if (line.upper.startsWith("OTHERFAMILY"))
     {
       otherfamily = removeKey("OTHERFAMILY", line).split(',')
     }
   }
   // report results
   echo ("Full name is $fullname")
   echo ("Favourite fruit is $favouritefruit")
   echo ("Needs peeling is $needspeeling")
   echo ("Seeds removed is $seedsremoved")
   echo ("Other family is " + otherfamily.join(", "))
 }

} </lang>

Forth

Forth is one of the unique languages that provides the programmer with both an extendable interpreter and an extendable compiler. This demonstration starts from the assumption that competent Forth programmers would not write an entire interpreter just to read a config file but would extend the Forth Interpreter to do the job. This approach is more representative of how Forth is used to create small domain specific languages for specific purposes.

As a result of taking this approach some liberties have been taken here from the original task. This code creates three operators for the Forth interpreter (SET, RESET, =) and uses these operators to assign values to actual variables in the Forth system. So the "=" sign here is not optional, it is required. The "=" operator parses an entire line of text and assigns it to a string variable. Set and reset assign 0 or -1 (all bits set) to an integer variable. Using the Forth interpreter "as is" also means that all script symbols are separated by a minimum of 1 space character however this is not a great hardship.

A more conventional version could of course be created if absolutely mandated however it would not be created in a few lines of code as this one is.

Something worth noting is that the FORTH interpreter will halt on a syntax error in the config.txt file. If this was not the proscribed behavior for the application then the FORTH error handler would need modification. This is possible in most systems by using the system error words (abort, catch, throw) appropriately while interpreting the config file. <lang>\ declare the configuration variables in the FORTH app FORTH DEFINITIONS

32 CONSTANT $SIZE

VARIABLE FULLNAME $SIZE ALLOT VARIABLE FAVOURITEFRUIT $SIZE ALLOT VARIABLE NEEDSPEELING VARIABLE SEEDSREMOVED VARIABLE OTHERFAMILY(1) $SIZE ALLOT VARIABLE OTHERFAMILY(2) $SIZE ALLOT

-leading ( addr len -- addr' len' )
           begin over c@ bl = while 1 /string repeat ;   \ remove leading blanks
trim ( addr len -- addr len) -leading -trailing ; \ remove blanks both ends

\ create the config file interpreter ------- VOCABULARY CONFIG \ create a namespace CONFIG DEFINITIONS \ put things in the namespace

SET ( addr --) true swap ! ;
RESET ( addr --) false swap ! ;
# ( -- ) 1 PARSE 2DROP ; \ parse line and throw away
= ( addr --) 1 PARSE trim ROT PLACE ; \ string assignment operator

synonym ; # \ 2nd comment operator is simple

FORTH DEFINITIONS \ this command reads and interprets the config.txt file

CONFIGURE ( -- ) CONFIG s" CONFIG.TXT" INCLUDED FORTH ;

\ config file interpreter ends ------

\ tools to validate the CONFIG interpreter

$. ( str --) count type ;
BOOL. ( ? --) @ IF ." ON" ELSE ." OFF" THEN ;
.CONFIG CR ." Fullname  : " FULLNAME $.
               CR  ." Favourite fruit: " FAVOURITEFRUIT $.
               CR  ." Needs peeling  : " NEEDSPEELING bool.
               CR  ." Seeds removed  : " SEEDSREMOVED bool.
               CR  ." Family:"
               CR   otherfamily(1) $.
               CR   otherfamily(2) $.  ;</lang>

The config file would look like this

# READ this file from within FORTH with the command CONFIGURE
FULLNAME = Foo Barber
FAVOURITEFRUIT = banana

# This is a boolean that should be set
NEEDSPEELING SET

# This boolean is commented out
; SEEDSREMOVED SET

OTHERFAMILY(1) = Rhu Barber
OTHERFAMILY(2) = Harry Barber

Usage and result

CONFIGURE 
Compiling config.txt ok
.config
Fullname       : Foo Barber
Favourite fruit: banana
Needs peeling  : ON
Seeds removed  : OFF
Family:
Rhu Barber
Harry Barber ok

Fortran

<lang Fortran> program readconfig

 implicit none
 integer, parameter    :: strlen = 100
 logical               :: needspeeling = .false., seedsremoved =.false.
 character(len=strlen) :: favouritefruit = "", fullname = "", fst, snd
 character(len=strlen), allocatable :: otherfamily(:), tmp(:)
 character(len=1000)   :: line
 integer               :: lun, stat,  j, j0, j1, ii = 1, z
 integer, parameter    :: state_begin=1, state_in_fst=2, state_in_sep=3
 open(newunit=lun, file="config.ini", status="old")
 
 do 
   read(lun, "(a)", iostat=stat) line
   if (stat<0) exit
   if ((line(1:1) == "#") .or. &
       (line(1:1) == ";") .or. &
       (len_trim(line)==0)) then
     cycle
   end if
   z = state_begin
   do j = 1, len_trim(line)
     if (z == state_begin) then
       if (line(j:j)/=" ") then
         j0 = j
         z = state_in_fst
       end if
     elseif (z == state_in_fst) then
       if (index("= ",line(j:j))>0) then
         fst = lower(line(j0:j-1))
         z = state_in_sep
       end if
     elseif (z == state_in_sep) then
       if (index(" =",line(j:j)) == 0) then
         snd = line(j:)
         exit
       end if
     else
        stop "not possible to be here"
     end if
   end do
   if (z == state_in_fst) then
     fst = lower(line(j0:))
   elseif (z == state_begin) then
     cycle
   end if
   if (fst=="fullname") then
     read(snd,"(a)") fullname
   elseif (fst=="favouritefruit") then
     read(snd,"(a)") favouritefruit
   elseif (fst=="seedsremoved") then
     seedsremoved = .true.
   elseif (fst=="needspeeling") then
     needspeeling = .true.
   elseif (fst=="otherfamily") then
     j = 1; ii = 1
     do while (len_trim(snd(j:)) >0)
       j1  = index(snd(j:),",")
       if (j1==0) then
         j1 = len_trim(snd)
       else
         j1 = j + j1 - 2
       end if
       do 
         if (j>len_trim(snd)) exit
         if (snd(j:j) /= " ") exit
         j = j +1
       end do
       allocate(tmp(ii)) 
       tmp(1:ii-1) = otherfamily
       call move_alloc(tmp, otherfamily)
       read(snd(j:j1),"(a)"), otherfamily(ii)
       j = j1 + 2 
       ii = ii + 1
     end do
   else 
     print *, "unknown option '"//trim(fst)//"'"; stop
   end if
 end do
 close(lun)
 print "(a,a)","fullname = ",       trim(fullname)
 print "(a,a)","favouritefruit = ", trim(favouritefruit)
 print "(a,l)","needspeeling = ",   needspeeling
 print "(a,l)","seedsremoved = ",   seedsremoved
 print "(a,*(a,:,', '))", "otherfamily = ", &
        (trim(otherfamily(j)), j=1,size(otherfamily))

contains

pure function lower (str) result (string)

   implicit none
   character(*), intent(In) :: str
   character(len(str))      :: string
   Integer :: ic, i
   character(26), parameter :: cap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
   character(26), parameter :: low = 'abcdefghijklmnopqrstuvwxyz'
   string = str
   do i = 1, len_trim(str)
       ic = index(cap, str(i:i))
       if (ic > 0) string(i:i) = low(ic:ic)
   end do

end function

end program </lang>

FreeBASIC

<lang freebasic>' FB 1.05.0 Win64

Sub split (s As Const String, sepList As Const String, result() As String)

 If s = "" OrElse sepList = "" Then 
    Redim result(0)
    result(0) = s
    Return
 End If
 Dim As Integer i, j, count = 0, empty = 0, length
 Dim As Integer position(Len(s) + 1)
 position(0) = 0

 For i = 0 To len(s) - 1
   For j = 0 to Len(sepList) - 1
     If s[i] = sepList[j] Then 
       count += 1
       position(count) = i + 1       
     End If
   Next j
 Next i

 Redim result(count)
 If count  = 0 Then
   result(0) = s
   Return
 End If

 position(count + 1) = len(s) + 1

 For i = 1 To count + 1  
   length = position(i) - position(i - 1) - 1 
   result(i - 1) = Mid(s, position(i - 1) + 1, length)
 Next

End Sub

Type ConfigData

 fullName As String
 favouriteFruit As String
 needsPeeling As Boolean
 seedsRemoved As Boolean
 otherFamily(Any) As String

End Type

Sub readConfigData(fileName As String, cData As ConfigData)

 Dim fileNum As Integer = FreeFile
 Open fileName For Input As #fileNum
 If err > 0 Then
   Print "File could not be opened"
   Sleep
   End
 End If
 Dim ln As String
 While Not Eof(fileNum)
   Line Input #fileNum, ln
   If ln = "" OrElse Left(ln, 1) = "#" OrElse Left(ln, 1) = ";" Then Continue While
   If UCase(Left(ln, 8)) = "FULLNAME" Then 
     cData.fullName = Trim(Mid(ln, 9), Any " =")
   ElseIf UCase(Left(ln, 14)) = "FAVOURITEFRUIT" Then
     cData.favouriteFruit = Trim(Mid(ln, 15), Any " =")
   ElseIf UCase(Left(ln, 12)) = "NEEDSPEELING" Then
     Dim s As String = Trim(Mid(ln, 13), Any " =")
     If s = ""  OrElse UCase(s) = "TRUE" Then 
       cData.needsPeeling = True
     Else
       cData.needsPeeling = False
     End If
   ElseIf UCase(Left(ln, 12)) = "SEEDSREMOVED" Then
     Dim s As String = Trim(Mid(ln, 13), Any " =")
     If s = ""  OrElse UCase(s) = "TRUE" Then 
       cData.seedsRemoved = True
     Else
       cData.seedsRemoved = False
     End If
   ElseIf UCase(Left(ln, 11)) = "OTHERFAMILY" Then
      split Mid(ln, 12), ",", cData.otherFamily()
      For i As Integer = LBound(cData.otherFamily) To UBound(cData.otherFamily)
        cData.otherFamily(i) = Trim(cData.otherFamily(i), Any " =")
      Next
   End If
 Wend
 Close #fileNum

End Sub

Dim fileName As String = "config.txt" Dim cData As ConfigData readConfigData fileName, cData Print "Full name = "; cData.fullName Print "Favourite fruit = "; cData.favouriteFruit Print "Needs peeling = "; cData.needsPeeling Print "Seeds removed = "; cData.seedsRemoved For i As Integer = LBound(cData.otherFamily) To UBound(cData.otherFamily)

 Print "Other family("; Str(i); ")  = "; cData.otherFamily(i)

Next Print Print "Press any key to quit" Sleep</lang>

Output:
Full name        = Foo Barber
Favourite fruit  = banana
Needs peeling    = true
Seeds removed    = false
Other family(0)  = Rhu Barber
Other family(1)  = Harry Barber

Gambas

<lang gambas>Public Sub Form_Open() Dim fullname As String = Settings["fullname", "Foo Barber"] 'If fullname is empty then use the default "Foo Barber" Dim favouritefruit As String = Settings["favouritefruit", "banana"] Dim needspeeling As String = Settings["needspeling", True] Dim seedsremoved As String = Settings["seedsremoved", False] Dim otherfamily As String[] = Settings["otherfamily", ["Rhu Barber", "Harry Barber"]]

Print fullname

'To save Settings["fullname"] = "John Smith" fullname = Settings["fullname"]

Print fullname

End</lang> Output:

Foo Barber
John Smith

Go

This make assumptions about the way the config file is supposed to be structured similar to the ones made by the Python solution. <lang go>package config

import ( "errors" "io" "fmt" "bytes" "strings" "io/ioutil" )

var ( ENONE = errors.New("Requested value does not exist") EBADTYPE = errors.New("Requested type and actual type do not match") EBADVAL = errors.New("Value and type do not match") )

type varError struct { err error n string t VarType }

func (err *varError) Error() string { return fmt.Sprintf("%v: (%q, %v)", err.err, err.n, err.t) }

type VarType int

const ( Bool VarType = 1 + iota Array String )

func (t VarType) String() string { switch t { case Bool: return "Bool" case Array: return "Array" case String: return "String" }

panic("Unknown VarType") }

type confvar struct { Type VarType Val interface{} }

type Config struct { m map[string]confvar }

func Parse(r io.Reader) (c *Config, err error) { c = new(Config) c.m = make(map[string]confvar)

buf, err := ioutil.ReadAll(r) if err != nil { return }

lines := bytes.Split(buf, []byte{'\n'})

for _, line := range lines { line = bytes.TrimSpace(line) if len(line) == 0 { continue } switch line[0] { case '#', ';': continue }

parts := bytes.SplitN(line, []byte{' '}, 2) nam := string(bytes.ToLower(parts[0]))

if len(parts) == 1 { c.m[nam] = confvar{Bool, true} continue }

if strings.Contains(string(parts[1]), ",") { tmpB := bytes.Split(parts[1], []byte{','}) for i := range tmpB { tmpB[i] = bytes.TrimSpace(tmpB[i]) } tmpS := make([]string, 0, len(tmpB)) for i := range tmpB { tmpS = append(tmpS, string(tmpB[i])) }

c.m[nam] = confvar{Array, tmpS} continue }

c.m[nam] = confvar{String, string(bytes.TrimSpace(parts[1]))} }

return }

func (c *Config) Bool(name string) (bool, error) { name = strings.ToLower(name)

if _, ok := c.m[name]; !ok { return false, nil }

if c.m[name].Type != Bool { return false, &varError{EBADTYPE, name, Bool} }

v, ok := c.m[name].Val.(bool) if !ok { return false, &varError{EBADVAL, name, Bool} } return v, nil }

func (c *Config) Array(name string) ([]string, error) { name = strings.ToLower(name)

if _, ok := c.m[name]; !ok { return nil, &varError{ENONE, name, Array} }

if c.m[name].Type != Array { return nil, &varError{EBADTYPE, name, Array} }

v, ok := c.m[name].Val.([]string) if !ok { return nil, &varError{EBADVAL, name, Array} } return v, nil }

func (c *Config) String(name string) (string, error) { name = strings.ToLower(name)

if _, ok := c.m[name]; !ok { return "", &varError{ENONE, name, String} }

if c.m[name].Type != String { return "", &varError{EBADTYPE, name, String} }

v, ok := c.m[name].Val.(string) if !ok { return "", &varError{EBADVAL, name, String} }

return v, nil }</lang>

Usage example: <lang go>package main

import ( "os" "fmt" "config" )

func main() { if len(os.Args) != 2 { fmt.Printf("Usage: %v <configfile>\n", os.Args[0]) os.Exit(1) }

file, err := os.Open(os.Args[1]) if err != nil { fmt.Println(err) os.Exit(1) } defer file.Close()

conf, err := config.Parse(file) if err != nil { fmt.Println(err) os.Exit(1) }

fullname, err := conf.String("fullname") if err != nil { fmt.Println(err) os.Exit(1) }

favouritefruit, err := conf.String("favouritefruit") if err != nil { fmt.Println(err) os.Exit(1) }

needspeeling, err := conf.Bool("needspeeling") if err != nil { fmt.Println(err) os.Exit(1) }

seedsremoved, err := conf.Bool("seedsremoved") if err != nil { fmt.Println(err) os.Exit(1) }

otherfamily, err := conf.Array("otherfamily") if err != nil { fmt.Println(err) os.Exit(1) }

fmt.Printf("FULLNAME: %q\n", fullname) fmt.Printf("FAVOURITEFRUIT: %q\n", favouritefruit) fmt.Printf("NEEDSPEELING: %q\n", needspeeling) fmt.Printf("SEEDSREMOVED: %q\n", seedsremoved) fmt.Printf("OTHERFAMILY: %q\n", otherfamily) }</lang>

Groovy

<lang groovy>def config = [:] def loadConfig = { File file ->

   String regex = /^(;{0,1})\s*(\S+)\s*(.*)$/
   file.eachLine { line ->
       (line =~ regex).each { matcher, invert, key, value ->
           if (key ==  || key.startsWith("#")) return
           parts = value ? value.split(/\s*,\s*/) : (invert ? [false] : [true])
           if (parts.size() > 1) {
               parts.eachWithIndex{ part, int i -> config["$key(${i + 1})"] = part}
           } else {
               config[key] = parts[0]
           }
       }
   }

}</lang> Testing: <lang groovy>loadConfig new File('config.ini') config.each { println it }</lang>

Output:
FULLNAME=Foo Barber
FAVOURITEFRUIT=banana
NEEDSPEELING=true
SEEDSREMOVED=false
OTHERFAMILY(1)=Rhu Barber
OTHERFAMILY(2)=Harry Barber

Haskell

<lang haskell> import Data.Char import Data.List import Data.List.Split

main :: IO () main = readFile "config" >>= (print . parseConfig)

parseConfig :: String -> Config parseConfig = foldr addConfigValue defaultConfig . clean . lines

   where clean = filter (not . flip any ["#", ";", "", " "] . (==) . take 1)
         

addConfigValue :: String -> Config -> Config addConfigValue raw config = case key of

   "fullname"       -> config {fullName      = values}
   "favouritefruit" -> config {favoriteFruit = values}
   "needspeeling"   -> config {needsPeeling  = True}
   "seedsremoved"   -> config {seedsRemoved  = True}
   "otherfamily"    -> config {otherFamily   = splitOn "," values}
   _                -> config
   where (k, vs) = span (/= ' ') raw
         key = map toLower k
         values = tail vs

data Config = Config

   { fullName      :: String
   , favoriteFruit :: String
   , needsPeeling  :: Bool
   , seedsRemoved  :: Bool
   , otherFamily   :: [String]
   } deriving (Show)

defaultConfig :: Config defaultConfig = Config "" "" False False [] </lang>

Or, use Data.Configfile:

<lang haskell> import Data.ConfigFile import Data.Either.Utils

getSetting cp x = forceEither $ get cp "Default" x

cp <- return . forceEither =<< readfile emptyCP "name_of_configuration_file" let username = getSetting cp "username"

   password = getSetting cp "password"

</lang> This works with configuration files in standard format, i.e.,

# this is a comment
username  = myname

Icon and Unicon

The following works in both languages. However boolean values don't exist in either language. So a variable whose value in other languages is false simply has no value in Icon and Unicon. If it contains any value then it can considered as being equivalent to true:

<lang unicon>procedure main(A)

   ws := ' \t'
   vars := table()
   every line := !&input do {
       line ? {
           tab(many(ws))
           if any('#;') | pos(0) then next
           vars[map(tab(upto(ws)\1|0))] := getValue()
           }
       }
   show(vars)

end

procedure getValue()

   ws := ' \t'
   a := []
   while not pos(0) do {
       tab(many(ws))
       put(a, trim(tab(upto(',')|0)))
       move(1)
       }
   return a

end

procedure show(t)

   every pair := !sort(t) do {
       every (s := pair[1]||" = ") ||:= !pair[2] || ", "
       write(s[1:-2])
       }

end</lang>

Sample run on above input:

->rcf <rcf.in
favouritefruit = banana
fullname = Foo Barber
needspeeling 
otherfamily = Rhu Barber, Harry Barber
->

Note that seedsremoved doesn't exist.

J

<lang j>require'regex' set=:4 :'(x)=:y'

cfgString=:4 :0

 y set 
 (1;&,~'(?i:',y,')\s*(.*)') y&set rxapply x

)

cfgBoolean=:4 :0

 y set 0
 (1;&,~'(?i:',y,')\s*(.*)') y&set rxapply x
 if.-.0-:y do.y set 1 end.

)

taskCfg=:3 :0

 cfg=: ('[#;].*';) rxrplc 1!:1<y
 cfg cfgString 'fullname'
 cfg cfgString 'favouritefruit'
 cfg cfgBoolean 'needspeeling'
 cfg cfgBoolean 'seedsremoved'
 i.0 0

)</lang>

Example use:

<lang j> taskCfg 'fruit.conf'

  (,' = ',]&.do)&>;: 'fullname favouritefruit needspeeling seedsremoved'

fullname = Foo Barber favouritefruit = banana needspeeling = 1 seedsremoved = 0 </lang>

Java

A more natural way to do this in Java would be Properties.load(InputStream) but the example data is not in the format expected by that method (equals signs are optional). <lang Java>import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern;

public class ConfigReader {

   private static final Pattern             LINE_PATTERN = Pattern.compile( "([^ =]+)[ =]?(.*)" );
   private static final Map<String, Object> DEFAULTS     = new HashMap<String, Object>() {{
       put( "needspeeling", false );
       put( "seedsremoved", false );
   }};
   public static void main( final String[] args ) {
       System.out.println( parseFile( args[ 0 ] ) );
   }
   public static Map<String, Object> parseFile( final String fileName ) {
       final Map<String, Object> result = new HashMap<String, Object>( DEFAULTS );
       /*v*/ BufferedReader      reader = null;
       try {
           reader = new BufferedReader( new FileReader( fileName ) );
           for ( String line; null != ( line = reader.readLine() );  ) {
               parseLine( line, result );
           }
       } catch ( final IOException x ) {
           throw new RuntimeException( "Oops: " + x, x );
       } finally {
           if ( null != reader ) try {
               reader.close();
           } catch ( final IOException x2 ) {
               System.err.println( "Could not close " + fileName + " - " + x2 );
           }
       }
       return result;
   }
   private static void parseLine( final String line, final Map<String, Object> map ) {
       if ( "".equals( line.trim() ) || line.startsWith( "#" ) || line.startsWith( ";" ) )
           return;
       final Matcher matcher = LINE_PATTERN.matcher( line );
       if ( ! matcher.matches() ) {
           System.err.println( "Bad config line: " + line );
           return;
       }
       final String key   = matcher.group( 1 ).trim().toLowerCase();
       final String value = matcher.group( 2 ).trim();
       if ( "".equals( value ) ) {
           map.put( key, true );
       } else if ( -1 == value.indexOf( ',' ) ) {
           map.put( key, value );
       } else {
           final String[] values = value.split( "," );
           for ( int i = 0; i < values.length; i++ ) {
               values[ i ] = values[ i ].trim();
           }
           map.put( key, Arrays.asList( values ) );
       }
   }

}</lang>

Output:
{otherfamily=[Rhu Barber, Harry Barber], favouritefruit=banana, seedsremoved=false, needspeeling=true, fullname=Foo Barber}

A more functional and concise approach using Java 8:

<lang Java>import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors;

public class ConfigReader {

   private static final Pattern LINE_PATTERN = Pattern.compile("([^ =]+)[ =]?(.*)");
   public static void main(final String[] args) throws IOException {
       System.out.println(parseFile(args[0]));
   }
   public static Map<String, Object> parseFile(final String fileName) throws IOException {
       final Map<String, Object> result = new HashMap<>();
       result.put("needspeeling", false);
       result.put("seedsremoved", false);
       try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
           result.putAll(reader.lines()
               .filter(line -> !"".equals(line.trim()) && !line.startsWith("#") && !line.startsWith(";"))
               .map(LINE_PATTERN::matcher)
               .filter(Matcher::matches)
               .collect(Collectors.toMap(matcher -> matcher.group(1).trim().toLowerCase(), matcher -> {
                   final String value = matcher.group(2).trim();
                   if ("".equals(value)) {
                       return true;
                   } else if (-1 == value.indexOf(',')) {
                       return value;
                   }
                   return Arrays.asList(value.split(",")).stream().map(String::trim).collect(Collectors.toList());
               }))
           );
       }
       return result;
   }

}</lang>

Output:
{seedsremoved=false, otherfamily=[Rhu Barber, Harry Barber], needspeeling=true, fullname=Foo Barber, favouritefruit=banana}

JavaScript

In JavaScript using an object makes more sense than local variables. This function takes our config file in plain text as the parameter.

<lang javascript>function parseConfig(config) {

   // this expression matches a line starting with an all capital word, 
   // and anything after it
   var regex = /^([A-Z]+)(.*)$/mg;
   var configObject = {};
   
   // loop until regex.exec returns null
   var match;
   while (match = regex.exec(config)) {
       // values will typically be an array with one element
       // unless we want an array
       // match[0] is the whole match, match[1] is the first group (all caps word), 
       // and match[2] is the second (everything through the end of line)
       var key = match[1], values = match[2].split(",");
       if (values.length === 1) {
           configObject[key] = values[0];
       }
       else {
           configObject[key] = values.map(function(value){
               return value.trim();
           });
       }
   }
   
   return configObject;

} </lang>

The result is an object, which can be represented with this JSON.

<lang javascript>{

 "FULLNAME": " Foo Barber",
 "FAVOURITEFRUIT": " banana",
 "NEEDSPEELING": "",
 "OTHERFAMILY": [
   "Rhu Barber",
   "Harry Barber"
 ]

} </lang>

jq

Works with: jq version 1.5

In the following, in the case of collisions, the last-most specification prevails. <lang jq>def parse:

 def uc: .name | ascii_upcase;
 
 def parse_boolean:
   capture( "(?<name>^[^ ] *$)" )
   | { (uc) : true };
 def parse_var_value:
   capture( "(?<name>^[^ ]+)[ =] *(?<value>[^,]+ *$)" )
   | { (uc) : .value };
 def parse_var_array:
   capture( "(?<name>^[^ ]+)[ =] *(?<value>.*)" )
   | { (uc) : (.value | sub(" +$";"") | [splits(", *")]) };
 reduce inputs as $i ({};
   if $i|length == 0 or test("^[#;]") then .
   else . + ($i | ( parse_boolean // parse_var_value // parse_var_array // {} ))
   end);

parse</lang>

Invocation

   $ jq -n -R -f parse.jq config.txt
Output:
{
  "FULLNAME": "Foo Barber",
  "FAVOURITEFRUIT": "banana",
  "NEEDSPEELING": true,
  "OTHERFAMILY": [
    "Rhu Barber",
    "Harry Barber"
  ]
}

Julia

Works with: Julia version 0.6

<lang julia>function readconf(file)

   vars = Dict()
   for line in eachline(file)
       line = strip(line)
       if !isempty(line) && !startswith(line, '#') && !startswith(line, ';')
           fspace  = searchindex(line, " ")
           if fspace == 0
               vars[Symbol(lowercase(line))] = true
           else
               vname, line = Symbol(lowercase(line[1:fspace-1])), line[fspace+1:end]
               value = ',' ∈ line ? strip.(split(line, ',')) : line
               vars[vname] = value
           end
       end
   end
   for (vname, value) in vars
       eval(:($vname = $value))
   end
   return vars

end

readconf("test.conf")

@show fullname favouritefruit needspeeling otherfamily</lang>

Output:
fullname = "Foo Barber"
favouritefruit = "banana"
needspeeling = true
otherfamily = SubString{String}["Rhu Barber", "Harry Barber"]

Kotlin

Works with: Kotlin version 1.0.6

This example is more verbose than it has to be because of increased effort in providing immutability to the configuration class. <lang scala>import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Paths

data class Configuration(val map: Map<String, Any?>) {

   val fullName: String by map
   val favoriteFruit: String by map
   val needsPeeling: Boolean by map
   val otherFamily: List<String> by map

}

fun main(args: Array<String>) {

   val lines = Files.readAllLines(Paths.get("src/configuration.txt"), StandardCharsets.UTF_8)
   val keyValuePairs = lines.map{ it.trim() }
           .filterNot { it.isEmpty() }
           .filterNot(::commentedOut)
           .map(::toKeyValuePair)
   val configurationMap = hashMapOf<String, Any>("needsPeeling" to false)
   for (pair in keyValuePairs) {
       val (key, value) = pair
       when (key) {
           "FULLNAME"       -> configurationMap.put("fullName", value)
           "FAVOURITEFRUIT" -> configurationMap.put("favoriteFruit", value)
           "NEEDSPEELING"   -> configurationMap.put("needsPeeling", true)
           "OTHERFAMILY"    -> configurationMap.put("otherFamily", value.split(" , ").map { it.trim() })
           else             -> println("Encountered unexpected key $key=$value")
       }
   }
   println(Configuration(configurationMap))

}

private fun commentedOut(line: String) = line.startsWith("#") || line.startsWith(";")

private fun toKeyValuePair(line: String) = line.split(Regex(" "), 2).let {

   Pair(it[0], if (it.size == 1) "" else it[1])

}</lang>

Lasso

<lang Lasso>local(config = '# This is a configuration file in standard configuration file format

  1. Lines beginning with a hash or a semicolon are ignored by the application
  2. program. Blank lines are also ignored by the application program.
  1. This is the fullname parameter

FULLNAME Foo Barber

  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. Configuration option names are not case sensitive, but configuration parameter
  2. data is case sensitive and may be preserved by the application program.
  1. An optional equals sign can be used to separate configuration parameter data
  2. from the option name. This is dropped by the parser.
  1. A configuration option may take multiple parameters separated by commas.
  2. Leading and trailing whitespace around parameter names and parameter data fields
  3. are ignored by the application program.

OTHERFAMILY Rhu Barber, Harry Barber ') // if config is in a file collect it like this //local(config = file('path/and/file.name') -> readstring)

define getconfig(term::string, config::string) => {

local( regexp = regexp(-find = `(?m)^` + #term + `($|\s*=\s*|\s+)(.*)$`, -input = #config, -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

}

local( fullname = getconfig('FULLNAME', #config), favorite = getconfig('FAVOURITEFRUIT', #config), sedsremoved = getconfig('SEEDSREMOVED', #config), needspeel = getconfig('NEEDSPEELING', #config), otherfamily = getconfig('OTHERFAMILY', #config) )

  1. fullname

'
'

  1. favorite

'
'

  1. sedsremoved

'
'

  1. needspeel

'
'

  1. otherfamily

'
'</lang>

Output:
Foo Barber
banana
false
true
array(Rhu Barber, Harry Barber)

Liberty BASIC

<lang lb> dim confKeys$(100) dim confValues$(100)

optionCount = ParseConfiguration("a.txt")

fullName$ = GetOption$( "FULLNAME", optionCount) favouriteFruit$ = GetOption$( "FAVOURITEFRUIT", optionCount) needsPeeling = HasOption("NEEDSPEELING", optionCount) seedsRemoved = HasOption("SEEDSREMOVED", optionCount) otherFamily$ = GetOption$( "OTHERFAMILY", optionCount) 'it's easier to keep the comma-separated list as a string

print "Full name: "; fullName$ print "likes: "; favouriteFruit$ print "needs peeling: "; needsPeeling print "seeds removed: "; seedsRemoved

print "other family:" otherFamily$ = GetOption$( "OTHERFAMILY", optionCount) counter = 1 while word$(otherFamily$, counter, ",") <> ""

   print counter; ". "; trim$(word$(otherFamily$, counter, ","))
   counter = counter + 1

wend end

'parses the configuration file, stores the uppercase keys in array confKeys$ and corresponding values in confValues$ 'returns the number of key-value pairs found function ParseConfiguration(fileName$)

   count = 0
   open fileName$ for input as #f
   while not(eof(#f))
       line input #f, s$
       if not(Left$(s$,1) = "#" or Left$( s$,1) = ";" or trim$(s$) = "") then  'ignore empty and comment lines
           s$ = trim$(s$)
           key$ = ParseKey$(s$)
           value$ = trim$(Mid$(s$,len(key$) + 1))
           if Left$( value$,1) = "=" then value$ = trim$(Mid$(value$,2))  'optional =
           count = count + 1
           confKeys$(count) = upper$(key$)
           confValues$(count) = value$
       end if
   wend
   close #f
   ParseConfiguration = count

end function

function ParseKey$(s$)

   'key is the first word in s$, delimited by whitespace or =
   s$ = word$(s$, 1)
   ParseKey$ = trim$(word$(s$, 1, "="))

end function


function GetOption$( key$, optionCount)

   index = Find.confKeys( 1, optionCount, key$)
   if index > 0 then GetOption$ =(confValues$(index))

end function


function HasOption(key$, optionCount)

   HasOption = Find.confKeys( 1, optionCount, key$) > 0

end function

function Find.confKeys( Start, Finish, value$)

   Find.confKeys = -1
   for i = Start to Finish
       if confKeys$(i) = value$ then Find.confKeys = i : exit for
   next i

end function </lang>

Output:
Full name: Foo Barber
likes: banana
needs peeling: 1
seeds removed: 0
other family:
1. Rhu Barber
2. Harry Barber

Lua

<lang lua>conf = {}

fp = io.open( "conf.txt", "r" )

for line in fp:lines() do

   line = line:match( "%s*(.+)" )
   if line and line:sub( 1, 1 ) ~= "#" and line:sub( 1, 1 ) ~= ";" then
	option = line:match( "%S+" ):lower()

value = line:match( "%S*%s*(.*)" )

if not value then

	    conf[option] = true

else if not value:find( "," ) then conf[option] = value else value = value .. "," conf[option] = {} for entry in value:gmatch( "%s*(.-)," ) do conf[option][#conf[option]+1] = entry end end end

   end

end

fp:close()


print( "fullname = ", conf["fullname"] ) print( "favouritefruit = ", conf["favouritefruit"] ) if conf["needspeeling"] then print( "needspeeling = true" ) else print( "needspeeling = false" ) end if conf["seedsremoved"] then print( "seedsremoved = true" ) else print( "seedsremoved = false" ) end if conf["otherfamily"] then

   print "otherfamily:"
   for _, entry in pairs( conf["otherfamily"] ) do

print( "", entry )

   end

end</lang>

Mathematica

<lang Mathematica> ClearAll[CreateVar, ImportConfig]; CreateVar[x_, y_String: "True"] := Module[{},

 If[StringFreeQ[y, ","]
  ,
  ToExpression[x <> "=" <> y]
  ,
  ToExpression[x <> "={" <> StringJoin@Riffle[StringSplit[y, ","], ","] <> "}"]
  ]
 ]

ImportConfig[configfile_String] := Module[{data},

 (*data = ImportString[configfile, "List", "Numeric" -> False];*)
 data=Import[configfile,"List","Numeric"\[Rule]False];
 data = StringTrim /@ data;
 data = Select[data, # =!= "" &];
 data = Select[data, ! StringMatchQ[#, "#" | ";" ~~ ___] &];
 data = If[! StringFreeQ[#, " "], StringSplit[#, " ", 2], {#}] & /@ data;
 CreateVar @@@ data;
]

ImportConfig[file] </lang>

MATLAB / Octave

This is defined as a function, parameters are returned as part of a struct. When the first line, and the assignment to return values are removed, it is a script that stores the parameters in the local workspace.

<lang MATLAB>function R = readconf(configfile) % READCONF reads configuration file. % % The value of boolean parameters can be tested with % exist(parameter,'var')

if nargin<1,

  configfile = 'q.conf';

end;

fid = fopen(configfile); if fid<0, error('cannot open file %s\n',a); end;

while ~feof(fid)

   line = strtrim(fgetl(fid));
   if isempty(line) || all(isspace(line)) || strncmp(line,'#',1) || strncmp(line,';',1),

; % no operation

   else 

[var,tok] = strtok(line,' \t='); var = upper(var); if any(tok==','), k = 1; while (1) [val, tok]=strtok(tok,','); R.(var){k} = strtrim(val); % return value of function eval(sprintf('%s{%i}=%s;',var,k,strtrim(val)));  % stores variable in local workspace if isempty(tok), break; end; k=k+1; end; else tok = strtrim(tok); R.(var) = tok; % return value of function eval(sprintf('%s=%s; ',var,tok));  % stores variable in local workspace end;

   end;

end; fclose(fid); whos,  % shows the parameter in the local workspace

</lang>
R=readconf('file.conf')
Variables in the current scope:

  Attr Name                Size                     Bytes  Class
  ==== ====                ====                     =====  ===== 
       FAVOURITEFRUIT      1x6                          6  char
       FULLNAME            1x10                        10  char
       NEEDSPEELING        0x0                          0  char
       OTHERFAMILY         1x2                         22  cell
   f   R                   1x1                         38  struct
   f   configfile          1x6                          6  char
       fid                 1x1                          8  double
       k                   1x1                          8  double
       line                0x0                          0  char
       tok                 0x0                          0  char
       val                 1x13                        13  char
       var                 1x11                        11  char

Total is 51 elements using 122 bytes

R =

  scalar structure containing the fields:

    FULLNAME = Foo Barber
    FAVOURITEFRUIT = banana
    NEEDSPEELING = 
    OTHERFAMILY = 
    {
      [1,1] = Rhu Barber
      [1,2] = Harry Barber
    }

Nanoquery

<lang nanoquery>import Nanoquery.IO import dict

def get_config(fname)

       f = new(File).open(fname)
       lines = split(f.readAll(), "\n")
       values = new(Dict)
       for line in lines
               line = trim(line)
               if len(line) > 0
                       if not (line .startswith. "#") or (line .startswith. ";")
                               tokens = split(line, " ")
                               if len(tokens) = 1
                                       values.add(upper(tokens[0]), true)
                               else
                                       parameters = list()
                                       parameter = ""
                                       for i in range(1, len(tokens) - 1)
                                               parameter += tokens[i] + " "
                                               if parameter .endswith. ", "
                                                       parameter = parameter.substring(0, len(parameter) - 2)
                                                       parameters.append(trim(parameter))
                                                       parameter = ""
                                               end
                                       end
                                       parameters.append(trim(parameter))
                                       if len(parameters) > 1
                                               values.add(upper(tokens[0]), parameters)
                                       else
                                               values.add(upper(tokens[0]), parameters[0])
                                       end
                               end
                       end
               end
       end
       return values

end

println get_config(args[2])</lang>

Output:
[[FULLNAME : Foo Barber], [FAVOURITEFRUIT : banana], [NEEDSPEELING : true], [OTHERFAMILY : [Rhu Barber, Harry Barber]]]

Nim

<lang Nim>import re, strformat, strutils, tables

var configs = newOrderedTable[string, seq[string]]() var f = open("demo.config") ## shown above in description of the task var parsed: seq[string]

for line in f.lines():

 if not (line.startswith(re"#|;") or line.strip() == ""):
   parsed = line.strip().split(re"\s*=\s*|\s+", 1)
   if len(parsed) == 1:
     configs[parsed[0].toLower()] = @[]
   else:
     configs[parsed[0].toLower()] = parsed[1].split(re"\s*,\s*")

f.close() for key in ["fullname", "favouritefruit", "needspeeling", "seedsremoved",

   "otherfamily"]:
 if not configs.hasKey(key):
   echo(&"{key} = false")
 else:
   case len(configs[key])
   of 0:
     echo(&"{key} = true")
   of 1:
     echo(&"{key} = {configs[key][0]}")
   else:
     for i, v in configs[key].pairs():
       echo(&"{key}({i+1}) = {v}")

</lang>

Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily(1) = Rhu Barber
otherfamily(2) = Harry Barber

OCaml

Using the library ocaml-inifiles:

<lang ocaml>#use "topfind"

  1. require "inifiles"

open Inifiles

let print_field ini (label, field) =

 try
   let v = ini#getval "params" field in
   Printf.printf "%s: %s\n" label v
 with Invalid_element _ ->
   Printf.printf "%s: not defined\n" label

let () =

 let ini = new inifile "./conf.ini" in
 let lst = [
   "Full name", "FULLNAME";
   "likes", "FAVOURITEFRUIT";
   "needs peeling", "NEEDSPEELING";
   "seeds removed", "SEEDSREMOVED";
 ] in
 List.iter (print_field ini) lst;
 let v = ini#getaval "params" "OTHERFAMILY" in
 print_endline "other family:";
 List.iter (Printf.printf "- %s\n") v;
</lang>

The file "conf.ini":

# This is a configuration file
#
# Lines begininning with a hash are ignored
# Blank lines are also ignored

# Leading and trailing whitespace around parameter names and
# parameter data fields are ignored

# ocaml-inifiles needs at least one section name
[params]

# This is the fullname parameter
FULLNAME = Foo Barber

# This is a favourite fruit
FAVOURITEFRUIT = banana

# This is a boolean that should be set
NEEDSPEELING = true

# This boolean is commented out
#SEEDSREMOVED = false

# A configuration option may take multiple values
# these values will be returned as a list
OTHERFAMILY = Rhu Barber
OTHERFAMILY = Harry Barber
Output:
$ ocaml conf.ml 
Full name: Foo Barber
likes: banana
needs peeling: true
seeds removed: not defined
other family:
- Harry Barber
- Rhu Barber

ooRexx

Here's another way of doing this, which stores the values in a Rexx stem (array), and stores each value of a multivalued variable as a separate item: <lang ooRexx>

  1. !/usr/bin/rexx

/*.----------------------------------------------------------------------.*/ /*|readconfig: Read keyword value pairs from a configuration file into |*/ /*| Rexx variables. |*/ /*| |*/ /*|Usage: |*/ /*| .-~/rosetta.conf-. |*/ /*|>>-readconfig-+----------------+------------------------------------><|*/ /*| |-configfilename-| |*/ /*| |-- -? ----------| |*/ /*| |-- -h ----------| |*/ /*| '- --help -------' |*/ /*| |*/ /*|where |*/ /*| configfilename |*/ /*| is the name of the configuration file to be processed. if not|*/ /*| specified, ~/rosetta.conf is used. |*/ /*| |*/ /*|All values retrieved from the configuration file are stored in |*/ /*|compound variables with the stem config. Variables with multiple |*/ /*|values have a numeric index appended, and the highest index number |*/ /*|is stored in the variable with index 0; e.g. if CONFIG.OTHERFAMILY.1 |*/ /*|and CONFIG.OTHERFAMILY.2 have values assigned, CONFIG.OTHERFAMILY.0 = |*/ /*|2. |*/ /*|-?, -h or --help all cause this documentation to be displayed. |*/ /*| |*/ /*|This program was tested using Open Object Rexx 4.1.1. It should work |*/ /*|with most other dialects as well. |*/ /*'----------------------------------------------------------------------'*/

 call usage arg(1)
 trace normal
 signal on any name error

/* Prepare for processing the configuration file. */

 keywords = 'FULLNAME FAVOURITEFRUIT NEEDSPEELING SEEDSREMOVED OTHERFAMILY'

/* Set default values for configuration variables here */

 config_single?. = 1
 config. = 
 config.NEEDSPEELING = 0
 config.SEEDSREMOVED = 1

/* Validate command line inputs. */

 parse arg configfilename
 if length(configfilename) = 0 then
   configfilename = '~/rosetta.conf'
 configfile = stream(configfilename,'COMMAND','QUERY EXISTS')
 if length(configfile) = 0 then
   do
     say configfilename 'was not found.'
     exit 28
   end
 signal on notready                               /* if an I/O error occurs. */

/* Open the configuration file. */

 response = stream(configfile,'COMMAND','OPEN READ SHARED')

/* Parse the contents of the configuration file into variables. */

 do while lines(configfile) > 0
   statement = linein(configfile)
   select
     when left(statement,1) = '#',
        | left(statement,1) = ';',
        | length(strip(statement)) = 0,
     then                                      /* a comment or a blank line. */
       nop                                                       /* skip it. */
     otherwise
       do
         if pos('=',word(statement,1)) > 0,
          | left(word(statement,2),1) = '=',
         then                        /* a keyword/value pair with = between. */
           parse var statement keyword '=' value
         else                             /* a keyword/value pair with no =. */
           parse var statement keyword value
         keyword = translate(strip(keyword))           /* make it uppercase. */
         single? = pos(',',value) = 0 /* a single value, or multiple values? */
         call value 'CONFIG_single?.'keyword,single?          /* remember. */
         if single? then
           do
             if length(value) > 0 then
               call value 'CONFIG.'keyword,strip(value)
           end                      /* strip keeps internal whitespace only. */
         else                            /* store each value with its index. */
           do v = 1 by 1 while length(value) > 0
             parse var value value1 ',' value
             if length(value1) > 0 then
               do
                 call value 'CONFIG.'keyword'.'v,strip(value1)
                 call value 'CONFIG.'keyword'.0',v         /* remember this. */
               end
           end
       end
   end
 end

/* Display the values of the configuration variables. */

 say 'Values associated with configuration file' configfilename':'
 say
 do while words(keywords) > 0
   parse var keywords keyword keywords
   if value('CONFIG_single?.'keyword) then
     say right(keyword,20) '= "'value('CONFIG.'keyword)'"'
   else
     do
       lastv = value('CONFIG.'keyword'.0')
       do v = 1 to lastv
         say right(keyword,20-(length(v)+2))'['v'] = "'value('CONFIG.'keyword'.'v)'"'
       end
     end
 end
 say

notready: /* I/O errors come here. */

 filestatus = stream(configfile,'STATE')
 if filestatus \= 'READY' then
   say 'An I/O error occurred; the file status is' filestatus'.'
 response = stream(configfile,'COMMAND','CLOSE')

error: /*? = sysdumpvariables() */ /* see everything Rexx used. */ exit

usage: procedure

 trace normal
 if arg(1) = '-h',
  | arg(1) = '-?',
  | arg(1) = '--help'
 then
   do
     line = '/*|'
     say
     do l = 3 by 1 while line~left(3) = '/*|'
       line = sourceline(l)
       parse var line . '/*|' text '|*/' .
       say text
     end
     say
     exit 0
   end

return </lang>

Output:
$ readconfig.rex
Values associated with configuration file ~/rosetta.conf:

            FULLNAME = "Foo Barber"
      FAVOURITEFRUIT = "banana"
        NEEDSPEELING = "0"
        SEEDSREMOVED = "1"
      OTHERFAMILY[1] = "Rhu Barber"
      OTHERFAMILY[2] = "Harry Barber"


Leslie

Pascal

Works with: Free_Pascal
Library: SysUtils

This solution makes use of FCL-STL package shipped with FPC >= 2.6.0, moreover it can be run directly like a script (just chmod +x) using the new instantfpc feature.

<lang Pascal>#!/usr/bin/instantfpc

{$if not defined(fpc) or (fpc_fullversion < 20600)}

 {$error FPC 2.6.0 or greater required}

{$endif}

{$mode objfpc}{$H+}

uses

 Classes,SysUtils,gvector,ghashmap;

type

 TStrHashCaseInsensitive = class
   class function hash(s: String; n: Integer): Integer;
 end;

class function TStrHashCaseInsensitive.hash(s: String; n: Integer): Integer; var

 x: Integer;
 c: Char;

begin

 x := 0;
 for c in UpCase(s) do Inc(x,Ord(c));
 Result := x mod n;

end;

type

 TConfigValues  = specialize TVector<String>;
 TConfigStorage = class(specialize THashMap<String,TConfigValues,TStrHashCaseInsensitive>)
   destructor Destroy; override;
 end;

destructor TConfigStorage.Destroy; var

 It: TIterator;

begin

 if Size > 0 then begin
   It := Iterator;
   repeat
     It.Value.Free;
   until not It.Next;
   It.Free;
 end;
 inherited Destroy;

end;

var

 ConfigStrings,ConfigValues: TStrings;
 ConfigStorage: TConfigStorage;
 ConfigLine,ConfigName,ConfigValue: String;
 SeparatorPos: Integer;

begin

 ConfigStrings := TStringList.Create;
 ConfigValues  := TStringList.Create;
 ConfigValues.Delimiter := ',';
 ConfigValues.StrictDelimiter := true;
 ConfigStorage := TConfigStorage.Create;
 ConfigStrings.LoadFromFile('config.test');
 for ConfigLine in ConfigStrings do begin
   if Length(ConfigLine) > 0 then begin
     case ConfigLine[1] of
       '#',';': ; // ignore
       else begin
         // look for = first
         SeparatorPos := Pos('=',ConfigLine);
         // if not found, then look for space
         if SeparatorPos = 0 then begin
           SeparatorPos := Pos(' ',ConfigLine);
         end;
         // found space
         if SeparatorPos <> 0 then begin
           ConfigName := UpCase(Copy(ConfigLine,1,SeparatorPos - 1));
           ConfigValues.DelimitedText := Copy(ConfigLine,SeparatorPos + 1,Length(ConfigLine) - SeparatorPos);
         // no = or space found, take the whole line as a key name
         end else begin
           ConfigName := UpCase(Trim(ConfigLine));
         end;
         if not ConfigStorage.Contains(ConfigName) then begin
           ConfigStorage[ConfigName] := TConfigValues.Create;
         end;
         for ConfigValue in ConfigValues do begin
           ConfigStorage[ConfigName].PushBack(Trim(ConfigValue));
         end;
       end;
     end;
   end;
 end;
 WriteLn('FULLNAME = ' + ConfigStorage['FULLNAME'][0]);
 WriteLn('FAVOURITEFRUIT = ' + ConfigStorage['FAVOURITEFRUIT'][0]);
 WriteLn('NEEDSPEELING = ' + BoolToStr(ConfigStorage.Contains('NEEDSPEELING'),true));
 WriteLn('SEEDSREMOVED = ' + BoolToStr(ConfigStorage.Contains('SEEDSREMOVED'),true));
 WriteLn('OTHERFAMILY(1) = ' + ConfigStorage['OTHERFAMILY'][0]);
 WriteLn('OTHERFAMILY(2) = ' + ConfigStorage['OTHERFAMILY'][1]);
 ConfigStorage.Free;
 ConfigValues.Free;
 ConfigStrings.Free;

end.</lang>

Output:
FULLNAME = Foo Barber
FAVOURITEFRUIT = banana
NEEDSPEELING = True
SEEDSREMOVED = False
OTHERFAMILY(1) = Rhu Barber
OTHERFAMILY(2) = Harry Barber

Peloton

Despite the discussion, this task is still a bit ambiguous. I've taken it that

   * blank lines and lines beginning with # and ; should be ignored. 
   * an all uppercase word defines a configuration symbol
   * no tail means a boolean true
   * a tail including a comma means a list 
   * any other kind of tail is a string

What we end up with after processing rosetta.config are three VARs and a LST, named FAVOURITEFRUIT, FULLNAME, NEEDSPEELING and OTHERFAMILY respectively.

<lang sgml><@ DEFUDRLIT>__ReadConfigurationFile| <@ LETSCPPNTPARSRC>Data|1</@><@ OMT> read file into locally scope variable</@> <@ LETCGDLSTLLOSCP>List|Data</@><@ OMT> split Data into a list of lines </@> <@ OMT> Remove comment lines, and blank lines </@> <@ ACTOVRBEFLSTLIT>List|;</@> <@ ACTOVRBEFLSTLIT>List|#</@> <@ ACTRMELST>List</@> <@ OMT> Iterate over the lines of the list </@> <@ ITEENULSTLit>List| <@ LETVARUPTVALLSTLIT>key|...| </@> <@ LETVARAFTVALLSTLIT>val|...| </@> <@ OMT> test for an empty key (in the case of a boolean) </@> <@ TSTVARLIT>key|</@> <@ IFE><@ LETPNTVARVARLIT>val|__True</@></@> <@ ELS> <@ TSTGT0ATBVARLIT>val|,</@> <@ IFE><@ ACTEXEEMMCAP><&prot; LETCNDLSTLITLIT>&key;&pipe;&val;&pipe;, </&prot;></@></@> <@ ELS><@ LETPNTVARVARVAR>key|val</@></@> </@> </@> </@>

<@ ACTUDRLIT>__ReadConfigurationFile|c:\rosetta.config</@> <@ SAYVAR>FAVOURITEFRUIT</@> <@ SAYVAR>FULLNAME</@> <@ SAYVAR>NEEDSPEELING</@> <@ SAYDMPLST>OTHERFAMILY</@> </lang>

Perl

This is an all-singing, all-dancing version that checks the configuration file syntax and contents and raises exceptions if it fails. (It is intentionally over-commented for pedagogical purposes.)

<lang perl>my $fullname; my $favouritefruit; my $needspeeling; my $seedsremoved; my @otherfamily;

  1. configuration file definition. See read_conf_file below for explanation.

my $conf_definition = {

   'fullname'          => [ 'string', \$fullname ],
   'favouritefruit'    => [ 'string', \$favouritefruit ],
   'needspeeling'      => [ 'boolean', \$needspeeling ],
   'seedsremoved'      => [ 'boolean', \$seedsremoved ],
   'otherfamily'       => [ 'array', \@otherfamily ],

};

my $arg = shift; # take the configuration file name from the command line

                              # (or first subroutine argument if this were in a sub)

my $file; # this is going to be a file handle reference open $file, $arg or die "Can't open configuration file '$arg': $!";

read_conf_file($file, $conf_definition);

print "fullname = $fullname\n"; print "favouritefruit = $favouritefruit\n"; print "needspeeling = ", ($needspeeling ? 'true' : 'false'), "\n"; print "seedsremoved = ", ($seedsremoved ? 'true' : 'false'), "\n"; for (my $i = 0; $i < @otherfamily; ++$i) {

   print "otherfamily(", $i + 1, ") = ", $otherfamily[$i], "\n";

}

  1. read_conf_file: Given a file handle opened for reading and a configuration definition,
  2. read the file.
  3. If the configuration file doesn't match the definition, raise an exception with "die".
  4. The configuration definition is (a reference to) an associative array
  5. where the keys are the configuration variable names in all lower case
  6. and the values are references to arrays.
  7. The first element of each of these arrays is the expected type: 'boolean', 'string', or 'array';
  8. the second element is a reference to the variable that should be assigned the data.

sub read_conf_file {

   my ($fh, $def) = @_;        # copy parameters
   local $_;                   # avoid interfering with use of $_ in main program
   while (<$fh>) {             # read a line from $fh into $_ until end of file
       next if /^#/;           # skip "#" comments
       next if /^;/;           # skip ";" comments
       next if /^$/;           # skip blank lines
       chomp;                  # strip final newline
       $_ =~ /^\s*(\w+)\s*(.*)$/i or die "Syntax error";
       my $key = $1;
       my $rest = $2;
       $key =~ tr/[A-Z]/[a-z]/; # convert keyword to lower case
       if (!exists $def->{$key}) {
           die "Unknown keyword: '$key'";
       }
       if ($def->{$key}[0] eq 'boolean') {
           if ($rest) {
               die "Syntax error:  extra data following boolean '$key'";
           }
           ${$def->{$key}[1]} = 1;
           next;                # done with this line, go back to "while"
       }
       $rest =~ s/\s*$//;       # drop trailing whitespace
       $rest =~ s/^=\s*//;      # drop equals sign if present
       if ($def->{$key}[0] eq 'string') {
           ${$def->{$key}[1]} = $rest;
       } elsif ($def->{$key}[0] eq 'array') {
           @{$def->{$key}[1]} = split /\s*,\s*/, $rest;
       } else {
           die "Internal error (unknown type in configuration definition)";
       }
   }

} </lang>

Phix

Normally I would recommend IupConfig, but the "standard" file format in the task description isn't even close (no [Section] headers, no '='). <lang Phix>integer fn = open("RCTEST.INI","r") sequence lines = get_text(fn,GT_LF_STRIPPED) close(fn) constant dini = new_dict() for i=1 to length(lines) do

   string li = trim(lines[i])
   if length(li)
   and not find(li[1],"#;") then
       integer k = find(' ',li)
       if k!=0 then
           string rest = li[k+1..$]
           li = li[1..k-1] -- (may want upper())
           if find(',',rest) then
               sequence a = split(rest,',')
               for j=1 to length(a) do a[j]=trim(a[j]) end for
               putd(li,a,dini)
           else
               putd(li,rest,dini)
           end if
       else
           putd(li,1,dini) -- ""
       end if
   end if

end for

function visitor(object key, object data, object /*user_data*/)

   ?{key,data}
   return 1

end function traverse_dict(routine_id("visitor"),0,dini) ?getd("FAVOURITEFRUIT",dini)</lang>

Output:
{"FAVOURITEFRUIT","banana"}
{"FULLNAME","Foo Barber"}
{"NEEDSPEELING",1}
{"OTHERFAMILY",{"Rhu Barber","Harry Barber"}}
"banana"

Phixmonti

<lang Phixmonti>def optionValue

   2 get "," find
   if
       " " "-" subst "," " " subst split
       len for
           var i
           i get "-" " " subst
           rot 1 get "(" chain print i print ") = " print swap trim print nl swap 
       endfor
       drop drop
   else
       swap 1 get print " = " print swap print nl
   endif

enddef

0 tolist

"rosetta_read.cfg" "r" fopen var file

file 0 > if

   true
   while
       file fgets
       dup -1 != if
           trim
           len 0 > if
               1 get '#' != if
                   " " find var pos
                   pos if
                       1 pos 1 - slice
                       swap len pos - pos 1 + swap slice
                       nip 2 tolist 
                   else
                       "" 2 tolist
                   endif
                   0 put
               else
                   drop
               endif
           else
               drop
           endif
           true
       else
           drop
           file fclose
           false
       endif
   endwhile
   len for
       get
       1 get ";" == if 2 get print " = false" print nl
       else 2 get "" == if 1 get print " = true" print nl
           else optionValue
           endif
       endif
       drop
   endfor

endif</lang>

Output:
FULLNAME = Foo Barber
FAVOURITEFRUIT = banana
NEEDSPEELING = true
SEEDSREMOVED = false
OTHERFAMILY(1) = Rhu Barber
OTHERFAMILY(2) = Harry Barber

PHP

Slightly modify the format of the configuration file before passing it to the internal function parse_ini_string()

<lang PHP><?php

$conf = file_get_contents('parse-conf-file.txt');

// Add an "=" after entry name $conf = preg_replace('/^([a-z]+)/mi', '$1 =', $conf);

// Replace multiple parameters separated by commas : // name = value1, value2 // by multiple lines : // name[] = value1 // name[] = value2 $conf = preg_replace_callback(

   '/^([a-z]+)\s*=((?=.*\,.*).*)$/mi',
   function ($matches) {
       $r = ;
       foreach (explode(',', $matches[2]) AS $val) {
           $r .= $matches[1] . '[] = ' . trim($val) . PHP_EOL;
       }
       return $r;
   },
   $conf

);

// Replace empty values by "true" $conf = preg_replace('/^([a-z]+)\s*=$/mi', '$1 = true', $conf);

// Parse configuration file $ini = parse_ini_string($conf);

echo 'Full name = ', $ini['FULLNAME'], PHP_EOL; echo 'Favourite fruit = ', $ini['FAVOURITEFRUIT'], PHP_EOL; echo 'Need spelling = ', (empty($ini['NEEDSPEELING']) ? 'false' : 'true'), PHP_EOL; echo 'Seeds removed = ', (empty($ini['SEEDSREMOVED']) ? 'false' : 'true'), PHP_EOL; echo 'Other family = ', print_r($ini['OTHERFAMILY'], true), PHP_EOL;</lang>

Output:
Full name       = Foo Barber
Favourite fruit = banana
Need spelling   = true
Seeds removed   = false
Other family    = Array
(
    [0] => Rhu Barber
    [1] => Harry Barber
)

PicoLisp

<lang PicoLisp>(de rdConf (File)

  (in File
     (while (read)
        (set @ (or (pack (clip (line))) T)) ) ) )

(rdConf "conf.txt") (println FULLNAME FAVOURITEFRUIT NEEDSPEELING SEEDSREMOVED OTHERFAMILY) (bye)</lang>

Output:
"Foo Barber" "banana" T NIL "Rhu Barber, Harry Barber"

PL/I

<lang PL/I> set: procedure options (main);

  declare text character (100) varying;
  declare (fullname, favouritefruit) character (100) varying initial ();
  declare needspeeling bit (1) static initial ('0'b);
  declare seedsremoved bit (1) static initial ('0'b);
  declare otherfamily(10) character (100) varying;
  declare (i, j) fixed binary;
  declare in file;
  open file (in) title ( '/RD-CON.DAT,TYPE(TEXT),RECSIZE(200)' );
  on endfile (in) go to variables;
  otherfamily = ; j = 0;
  do forever;
     get file (in) edit (text) (L);
     text = trim(text);
     if length(text) = 0 then iterate;
     if substr(text, 1, 1) = ';' then iterate;
     if substr(text, 1, 1) = '#' then iterate;
     if length(text) >= 9 then
        if substr(text, 1, 9) = 'FULLNAME ' then
           fullname = trim( substr(text, 9) );
     if length(text) >= 15 then
        if substr(text, 1, 15) = 'FAVOURITEFRUIT ' then
           favouritefruit = trim( substr(text, 15) );
     if length(text) >= 12 then
        if text = 'NEEDSPEELING' then needspeeling = '1'b;
     if length(text) >= 12 then
        if text = 'SEEDSREMOVED' then seedsremoved = '1'b;
     if length(text) >= 12 then
      if substr(text, 1, 12) = 'OTHERFAMILY ' then
        do;
                 text = trim(substr(text, 12) );
                 i = index(text, ',');
                 do while (i > 0);
                    j = j + 1;
                    otherfamily(j) = substr(text, 1, i-1);
                    text = trim(substr(text, i+1));
                    i = index(text, ',');
                 end;           
                 j = j + 1;
                 otherfamily(j) = trim(text);
        end;
  end;

variables:

     put skip data (fullname);
     put skip data (favouritefruit);
     put skip data (needspeeling);
     put skip data (seedsremoved);
     do i = 1 to j;
        put skip data (otherfamily(i));
     end;

end set; </lang>

Output:

using the given text file as input

-
FULLNAME='Foo Barber';
FAVOURITEFRUIT='banana';
NEEDSPEELING='1'B;
SEEDSREMOVED='0'B;
OTHERFAMILY(1)='Rhu Barber';
OTHERFAMILY(2)='Harry Barber';

PowerShell

<lang PowerShell> function Read-ConfigurationFile {

   [CmdletBinding()]
   Param
   (
       # Path to the configuration file.  Default is "C:\ConfigurationFile.cfg"
       [Parameter(Mandatory=$false, Position=0)]
       [string]
       $Path = "C:\ConfigurationFile.cfg"
   )
   [string]$script:fullName = ""
   [string]$script:favouriteFruit = "" 
   [bool]$script:needsPeeling = $false 
   [bool]$script:seedsRemoved = $false
   [string[]]$script:otherFamily = @()
   function Get-Value ([string]$Line)
   {
       if ($Line -match "=")
       {
           [string]$value = $Line.Split("=",2).Trim()[1]
       }
       elseif ($Line -match " ")
       {
           [string]$value = $Line.Split(" ",2).Trim()[1]
       }
       $value
   }
   # Process each line in file that is not a comment.
   Get-Content $Path | Select-String -Pattern "^[^#;]" | ForEach-Object {
       [string]$line = $_.Line.Trim()
       if ($line -eq [String]::Empty)
       {
           # do nothing for empty lines
       }
       elseif ($line.ToUpper().StartsWith("FULLNAME"))
       {
           $script:fullName = Get-Value $line
       }
       elseif ($line.ToUpper().StartsWith("FAVOURITEFRUIT"))
       {
           $script:favouriteFruit = Get-Value $line
       }
       elseif ($line.ToUpper().StartsWith("NEEDSPEELING"))
       {
           $script:needsPeeling = $true
       }
       elseif ($line.ToUpper().StartsWith("SEEDSREMOVED"))
       {
           $script:seedsRemoved = $true
       }
       elseif ($line.ToUpper().StartsWith("OTHERFAMILY"))
       {
           $script:otherFamily = (Get-Value $line).Split(',').Trim()
       }
   }
   Write-Verbose -Message ("{0,-15}= {1}" -f "FULLNAME", $script:fullName)
   Write-Verbose -Message ("{0,-15}= {1}" -f "FAVOURITEFRUIT", $script:favouriteFruit)
   Write-Verbose -Message ("{0,-15}= {1}" -f "NEEDSPEELING", $script:needsPeeling)
   Write-Verbose -Message ("{0,-15}= {1}" -f "SEEDSREMOVED", $script:seedsRemoved)
   Write-Verbose -Message ("{0,-15}= {1}" -f "OTHERFAMILY", ($script:otherFamily -join ", "))

} </lang> I stored the file in ".\temp.txt" and there is no output unless the -Verbose switch is used: <lang PowerShell> Read-ConfigurationFile -Path .\temp.txt -Verbose </lang>

Output:
VERBOSE: FULLNAME       = Foo Barber
VERBOSE: FAVOURITEFRUIT = banana
VERBOSE: NEEDSPEELING   = True
VERBOSE: SEEDSREMOVED   = False
VERBOSE: OTHERFAMILY    = Rhu Barber, Harry Barber

Test if the variables are set: <lang PowerShell> Get-Variable -Name fullName, favouriteFruit, needsPeeling, seedsRemoved, otherFamily </lang>

Output:
Name                           Value
----                           -----
fullName                       Foo Barber
favouriteFruit                 banana
needsPeeling                   True
seedsRemoved                   False
otherFamily                    {Rhu Barber, Harry Barber}

Using Switch -Regex

<lang PowerShell> Function Read-ConfigurationFile {

  [CmdletBinding()]
  [OutputType([Collections.Specialized.OrderedDictionary])]
  Param (
  [Parameter(
     Mandatory=$true,
     Position=0
     )
  ]
  [Alias('LiteralPath')]
  [ValidateScript({
     Test-Path -LiteralPath $PSItem -PathType 'Leaf'
     })
  ]
  [String]
  $_LiteralPath
  )
  Begin {
     Function Houdini-Value ([String]$_Text) {
        $__Aux = $_Text.Trim()
        If ($__Aux.Length -eq 0) {
           $__Aux = $true
        } ElseIf ($__Aux.Contains(',')) {
           $__Aux = $__Aux.Split(',') |
              ForEach-Object {
                 If ($PSItem.Trim().Length -ne 0) {
                    $PSItem.Trim()
                 }
              }
        }
        Return $__Aux
     }
  }
  
  Process {
     $__Configuration = [Ordered]@{}
     # Config equivalent pattern
     # Select-String -Pattern '^\s*[^\s;#=]+.*\s*$' -LiteralPath '.\filename.cfg'
     Switch -Regex -File $_LiteralPath {
        '^\s*[;#=]|^\s*$'  {
           Write-Verbose -Message "v$(' '*20)ignored"
           Write-Verbose -Message $Matches[0]
           Continue
        }
        '^([^=]+)=(.*)$' {
           Write-Verbose -Message '↓← ← ← ← ← ← ← ← ← ← equal pattern'
           Write-Verbose -Message $Matches[0]
           $__Name,$__Value = $Matches[1..2]
           $__Configuration[$__Name.Trim()] = Houdini-Value($__Value)
           Continue
        }
        '^\s*([^\s;#=]+)(.*)(\s*)$' {
           Write-Verbose -Message '↓← ← ← ← ← ← ← ← ← ← space or tab pattern or only name'
           Write-Verbose -Message $Matches[0]
           $__Name,$__Value = $Matches[1..2]
           $__Configuration[$__Name.Trim()] = Houdini-Value($__Value)
           Continue
        }
     }
     Return $__Configuration
  }

}

Function Show-Value ([Collections.Specialized.OrderedDictionary]$_Dictionary, $_Index, $_SubIndex) {

  $__Aux = $_Index + ' = '
  If ($_Dictionary[$_Index] -eq $null) {
     $__Aux += $false
  } ElseIf ($_Dictionary[$_Index].Count -gt 1) {
     If ($_SubIndex -eq $null) {
        $__Aux += $_Dictionary[$_Index] -join ','
     } Else {
        $__Aux = $_Index + '(' + $_SubIndex + ') = '
        If ($_Dictionary[$_Index][$_SubIndex] -eq $null) {
           $__Aux += $false		 
        } Else {
           $__Aux += $_Dictionary[$_Index][$_SubIndex]
        }
     }
  } Else {
     $__Aux += $_Dictionary[$_Index]
  }
  Return $__Aux

} </lang> Setting variable <lang PowerShell> $Configuration = Read-ConfigurationFile -LiteralPath '.\config.cfg' </lang>

Show variable <lang PowerShell> $Configuration </lang>

Output:
Name                           Value
----                           -----
FULLNAME                       Foo Barber
FAVOURITEFRUIT                 banana
NEEDSPEELING                   True
OTHERFAMILY                    {Rhu Barber, Harry Barber}

Using customize function <lang PowerShell> Show-Value $Configuration 'fullname' Show-Value $Configuration 'favouritefruit' Show-Value $Configuration 'needspeeling' Show-Value $Configuration 'seedsremoved' Show-Value $Configuration 'otherfamily' Show-Value $Configuration 'otherfamily' 0 Show-Value $Configuration 'otherfamily' 1 Show-Value $Configuration 'otherfamily' 2 </lang>

Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = True
seedsremoved = False
otherfamily = Rhu Barber,Harry Barber
otherfamily(0) = Rhu Barber
otherfamily(1) = Harry Barber
otherfamily(2) = False

Using index variable <lang PowerShell> '$Configuration[fullname]' $Configuration['fullname'] '$Configuration.fullname' $Configuration.'fullname' '$Configuration.Item(fullname)' $Configuration.Item('fullname') '$Configuration[0]' $Configuration[0] '$Configuration.Item(0)' $Configuration.Item(0) ' ' '=== $Configuration[otherfamily] ===' $Configuration['otherfamily'] '=== $Configuration[otherfamily][0] ===' $Configuration['otherfamily'][0] '=== $Configuration[otherfamily][1] ===' $Configuration['otherfamily'][1] ' ' '=== $Configuration.otherfamily ===' $Configuration.'otherfamily' '=== $Configuration.otherfamily[0] ===' $Configuration.'otherfamily'[0] '=== $Configuration.otherfamily[1] ===' $Configuration.'otherfamily'[1] ' ' '=== $Configuration.Item(otherfamily) ===' $Configuration.Item('otherfamily') '=== $Configuration.Item(otherfamily)[0] ===' $Configuration.Item('otherfamily')[0] '=== $Configuration.Item(otherfamily)[1] ===' $Configuration.Item('otherfamily')[1] ' ' '=== $Configuration[3] ===' $Configuration[3] '=== $Configuration[3][0] ===' $Configuration[3][0] '=== $Configuration[3][1] ===' $Configuration[3][1] ' ' '=== $Configuration.Item(3) ===' $Configuration.Item(3) '=== $Configuration.Item(3).Item(0) ===' $Configuration.Item(3).Item(0) '=== $Configuration.Item(3).Item(1) ===' $Configuration.Item(3).Item(1) </lang>

Output:
$Configuration['fullname']
Foo Barber
$Configuration.'fullname'
Foo Barber
$Configuration.Item('fullname')
Foo Barber
$Configuration[0]
Foo Barber
$Configuration.Item(0)
Foo Barber

=== $Configuration['otherfamily'] ===
Rhu Barber
Harry Barber
=== $Configuration['otherfamily'][0] ===
Rhu Barber
=== $Configuration['otherfamily'][1] ===
Harry Barber

=== $Configuration.'otherfamily' ===
Rhu Barber
Harry Barber
=== $Configuration.'otherfamily'[0] ===
Rhu Barber
=== $Configuration.'otherfamily'[1] ===
Harry Barber

=== $Configuration.Item('otherfamily') ===
Rhu Barber
Harry Barber
=== $Configuration.Item('otherfamily')[0] ===
Rhu Barber
=== $Configuration.Item('otherfamily')[1] ===
Harry Barber

=== $Configuration[3] ===
Rhu Barber
Harry Barber
=== $Configuration[3][0] ===
Rhu Barber
=== $Configuration[3][1] ===
Harry Barber

=== $Configuration.Item(3) ===
Rhu Barber
Harry Barber
=== $Configuration.Item(3).Item(0) ===
Rhu Barber
=== $Configuration.Item(3).Item(1) ===
Harry Barber

Python

This task is not well-defined, so we have to make some assumptions (see comments in code). <lang python>def readconf(fn):

   ret = {}
   with file(fn) as fp:
       for line in fp:
           # Assume whitespace is ignorable
           line = line.strip()
           if not line or line.startswith('#'): continue
           
           boolval = True
           # Assume leading ";" means a false boolean
           if line.startswith(';'):
               # Remove one or more leading semicolons
               line = line.lstrip(';')
               # If more than just one word, not a valid boolean
               if len(line.split()) != 1: continue
               boolval = False
           
           bits = line.split(None, 1)
           if len(bits) == 1:
               # Assume booleans are just one standalone word
               k = bits[0]
               v = boolval
           else:
               # Assume more than one word is a string value
               k, v = bits
           ret[k.lower()] = v
   return ret


if __name__ == '__main__':

   import sys
   conf = readconf(sys.argv[1])
   for k, v in sorted(conf.items()):
       print k, '=', v</lang>

Racket

Use the shared Racket/Options code.

<lang Racket>

  1. lang racket

(require "options.rkt")

(read-options "options-file") (define-options fullname favouritefruit needspeeling seedsremoved otherfamily) (printf "fullname = ~s\n" fullname) (printf "favouritefruit = ~s\n" favouritefruit) (printf "needspeeling = ~s\n" needspeeling) (printf "seedsremoved = ~s\n" seedsremoved) (printf "otherfamily = ~s\n" otherfamily) </lang>

Output:
fullname       = "Foo Barber"
favouritefruit = "banana"
needspeeling   = #t
seedsremoved   = #f
otherfamily    = ("Rhu Barber" "Harry Barber")

Raku

(formerly Perl 6)

Works with: Rakudo version 2020.08.1

This demonstrates several interesting features of Raku, including full grammar support, derived grammars, alternation split across derivations, and longest-token matching that works across derivations. It also shows off Raku's greatly cleaned up regex syntax. <lang perl6>my $fullname; my $favouritefruit; my $needspeeling = False; my $seedsremoved = False; my @otherfamily;

grammar ConfFile {

   token TOP {

:my $*linenum = 0; ^ <fullline>* [$ || (\N*) { die "Parse failed at $0" } ]

   }
   token fullline {

<?before .> { ++$*linenum } <line> [ \n || { die "Parse failed at line $*linenum" } ]

   }
   proto token line() {*}
   token line:misc  { {} (\S+) { die "Unrecognized word: $0" } }
   token line:sym<comment> { ^^ [ ';' | '#' ] \N* }
   token line:sym<blank>   { ^^ \h* $$ }
   token line:sym<fullname>       {:i fullname»       <rest> { $fullname = $<rest>.trim } }
   token line:sym<favouritefruit> {:i favouritefruit» <rest> { $favouritefruit = $<rest>.trim } }
   token line:sym<needspeeling>   {:i needspeeling»    <yes> { $needspeeling = defined $<yes> } }
   token rest { \h* '='? (\N*) }
   token yes { :i \h* '='? \h*
   	[

|| ([yes|true|1]) || [no|false|0] || (<?>) ] \h*

   }

}

grammar MyConfFile is ConfFile {

   token line:sym<otherfamily>    {:i otherfamily»    <rest> { @otherfamily = $<rest>.split(',')».trim } }

}

MyConfFile.parsefile('file.cfg');

say "fullname: $fullname"; say "favouritefruit: $favouritefruit"; say "needspeeling: $needspeeling"; say "seedsremoved: $seedsremoved"; print "otherfamily: "; say @otherfamily.raku;</lang>

Output:
fullname: Foo Barber
favouritefruit: banana
needspeeling: True
seedsremoved: False
otherfamily: ["Rhu Barber", "Harry Barber"]

RapidQ

<lang vb> type TSettings extends QObject

   FullName as string
   FavouriteFruit as string
   NeedSpelling as integer
   SeedsRemoved as integer
   OtherFamily as QStringlist
   
   Constructor
       FullName = ""
       FavouriteFruit = ""
       NeedSpelling = 0
       SeedsRemoved = 0
       OtherFamily.clear
   end constructor

end type

Dim Settings as TSettings dim ConfigList as QStringList dim x as integer dim StrLine as string dim StrPara as string dim StrData as string

function Trim$(Expr as string) as string

   Result = Rtrim$(Ltrim$(Expr))

end function

Sub ConfigOption(PData as string)

   dim x as integer
   for x = 1 to tally(PData, ",") +1
       Settings.OtherFamily.AddItems Trim$(field$(PData, "," ,x))
   next

end sub

Function ConfigBoolean(PData as string) as integer

   PData = Trim$(PData)
   Result = iif(lcase$(PData)="true" or PData="1" or PData="", 1, 0)

end function

sub ReadSettings

   ConfigList.LoadFromFile("Rosetta.cfg")
   ConfigList.text = REPLACESUBSTR$(ConfigList.text,"="," ")
   for x = 0 to ConfigList.ItemCount -1
       StrLine = Trim$(ConfigList.item(x))
       StrPara = Trim$(field$(StrLine," ",1))
       StrData = Trim$(lTrim$(StrLine - StrPara))  
   
       Select case UCase$(StrPara)
       case "FULLNAME"       : Settings.FullName = StrData 
       case "FAVOURITEFRUIT" : Settings.FavouriteFruit = StrData 
       case "NEEDSPEELING"   : Settings.NeedSpelling = ConfigBoolean(StrData)
       case "SEEDSREMOVED"   : Settings.SeedsRemoved = ConfigBoolean(StrData)
       case "OTHERFAMILY"    : Call ConfigOption(StrData)
       end select
   next

end sub

Call ReadSettings </lang>

Output:
Settings.FullName            = Foo Barber
Settings.FavouriteFruit      = banana
Settings.NeedSpelling        = 1
Settings.SeedsRemoved        = 0
Settings.OtherFamily.Item(0) = Rhu Barber
Settings.OtherFamily.Item(1) = Harry Barber

Red

<lang Rebol>Red ["Read a config file"]

remove-each l lines: read/lines %file.conf [any [empty? l #"#" = l/1]] foreach line lines [ foo: parse line [collect [keep to [" " | end] skip keep to end]] either foo/1 = #";" [set to-word foo/2 false][ set to-word foo/1 any [ all [find foo/2 #"," split foo/2 ", "] foo/2 true ] ] ] foreach w [fullname favouritefruit needspeeling seedsremoved otherfamily][ prin [pad w 15 ": "] probe get w ]</lang>

Output:
fullname        : "Foo Barber"
favouritefruit  : "banana"
needspeeling    : true
seedsremoved    : false
otherfamily     : ["Rhu Barber" "Harry Barber"]

REXX

No assumptions were made about what variables are (or aren't) in the configuration file.
Code was written to make the postmortem report as readable as possible. <lang rexx>/*REXX program reads a config (configuration) file and assigns VARs as found within. */ signal on syntax; signal on novalue /*handle REXX source program errors. */ parse arg cFID _ . /*cFID: is the CONFIG file to be read.*/ if cFID== then cFID='CONFIG.DAT' /*Not specified? Then use the default.*/ bad= /*this will contain all the bad VARs. */ varList= /* " " " " " good " */ maxLenV=0; blanks=0; hashes=0; semics=0; badVar=0 /*zero all these variables.*/

  do j=0  while lines(cFID)\==0                 /*J:   it counts the lines in the file.*/
  txt=strip(linein(cFID))                       /*read a line (record) from the file,  */
                                                /*  ··· & strip leading/trailing blanks*/
  if      txt    =    then do; blanks=blanks+1; iterate; end   /*count # blank lines.*/
  if left(txt,1)=='#'   then do; hashes=hashes+1; iterate; end   /*  "   " lines with #*/
  if left(txt,1)==';'   then do; semics=semics+1; iterate; end   /*  "   "   "     "  ;*/
  eqS=pos('=',txt)                              /*we can't use the   TRANSLATE   BIF.  */
  if eqS\==0  then txt=overlay(' ',txt,eqS)     /*replace the first  '='  with a blank.*/
  parse var txt xxx value;  upper xxx           /*get the variable name and it's value.*/
  value=strip(value)                            /*strip leading and trailing blanks.   */
  if value= then value='true'                 /*if no value,  then use   "true".     */
  if symbol(xxx)=='BAD'  then do                /*can REXX utilize the variable name ? */
                              badVar=badVar+1;  bad=bad xxx;  iterate  /*append to list*/
                              end
  varList=varList xxx                           /*add it to the list of good variables.*/
  call value xxx,value                          /*now,  use VALUE to set the variable. */
  maxLenV=max(maxLenV,length(value))            /*maxLen of varNames,  pretty display. */
  end   /*j*/

vars=words(varList); @ig= 'ignored that began with a'

                   say #(j)       'record's(j) "were read from file: " cFID

if blanks\==0 then say #(blanks) 'blank record's(blanks) "were read." if hashes\==0 then say #(hashes) 'record's(hashes) @ig "# (hash)." if semics\==0 then say #(semics) 'record's(semics) @ig "; (semicolon)." if badVar\==0 then say #(badVar) 'bad variable name's(badVar) 'detected:' bad say; say 'The list of' vars "variable"s(vars) 'and' s(vars,'their',"it's"),

                                  "value"s(vars)       'follows:'

say; do k=1 for vars

             v=word(varList,k)
             say  right(v,maxLenV) '=' value(v)
             end   /*k*/

say; exit /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ s: if arg(1)==1 then return arg(3); return word(arg(2) 's',1)

  1. return right(arg(1),length(j)+11) /*right justify a number & also indent.*/

err: do j=1 for arg(); say '***error*** ' arg(j); say; end /*j*/; exit 13 novalue: syntax: call err 'REXX program' condition('C') "error",,

        condition('D'),'REXX source statement (line' sigl"):",sourceline(sigl)

</lang> output   when using the input (file) as specified in the task description:

           28 records were read from file:  CONFIG.DAT
            8 blank records were read.
           15 records ignored that began with a # (hash).
            1 record ignored that began with a ; (semicolon).

The list of 4 variables and their values follows:

                FULLNAME = Foo Barber
          FAVOURITEFRUIT = banana
            NEEDSPEELING = true
             OTHERFAMILY = Rhu Barber, Harry Barber

Ruby

<lang ruby>fullname = favouritefruit = "" needspeeling = seedsremoved = false otherfamily = []

IO.foreach("config.file") do |line|

 line.chomp!
 key, value = line.split(nil, 2)
 case key
 when /^([#;]|$)/; # ignore line
 when "FULLNAME"; fullname = value
 when "FAVOURITEFRUIT"; favouritefruit = value
 when "NEEDSPEELING"; needspeeling = true
 when "SEEDSREMOVED"; seedsremoved = true
 when "OTHERFAMILY"; otherfamily = value.split(",").map(&:strip)
 when /^./; puts "#{key}: unknown key"
 end

end

puts "fullname = #{fullname}" puts "favouritefruit = #{favouritefruit}" puts "needspeeling = #{needspeeling}" puts "seedsremoved = #{seedsremoved}" otherfamily.each_with_index do |name, i|

 puts "otherfamily(#{i+1}) = #{name}"

end</lang>

Output:
fullname       = Foo Barber
favouritefruit = banana
needspeeling   = true
seedsremoved   = false
otherfamily(1) = Rhu Barber
otherfamily(2) = Harry Barber

Run BASIC

<lang Runbasic>dim param$(6) dim paramVal$(6) param$(1) = "fullname" param$(2) = "favouritefruit" param$(3) = "needspeeling" param$(4) = "seedsremoved" param$(5) = "otherfamily" for i = 1 to 6

paramVal$(i) = "false"

next i

open DefaultDir$ + "\public\a.txt" for binary as #f while not(eof(#f)) line input #f, a$ a$ = trim$(a$) if instr("#;",left$(a$,1)) = 0 and a$ <> "" then thisParam$ = lower$(word$(a$,1," ")) for i = 1 to 5 if param$(i) = thisParam$ then paramVal$(i) = "true" aa$ = trim$(mid$(a$,len(thisParam$)+2)) if aa$ <> "" then paramVal$(i) = aa$ end if next i end if wend close #f for i = 1 to 5

 if instr(paramVal$(i),",") > 0 then
  for j = 1 to 2
    print param$(i);"(";j;")";chr$(9);trim$(word$(paramVal$(i),j,","))
  next j
 else
  print param$(i);chr$(9);paramVal$(i)
 end if

next i</lang>

Output:
fullname	Foo Barber
favouritefruit	banana
needspeeling	true
seedsremoved	false
otherfamily(1)	Rhu Barber
otherfamily(2)	Harry Barber

Rust

<lang Rust>use std::fs::File; use std::io::BufRead; use std::io::BufReader; use std::iter::FromIterator; use std::path::Path;

fn main() {

   let path = String::from("file.conf");
   let cfg = config_from_file(path);
   println!("{:?}", cfg);

}

fn config_from_file(path: String) -> Config {

   let path = Path::new(&path);
   let file = File::open(path).expect("File not found or cannot be opened");
   let content = BufReader::new(&file);
   let mut cfg = Config::new();
   for line in content.lines() {
       let line = line.expect("Could not read the line");
       // Remove whitespaces at the beginning and end
       let line = line.trim();
       // Ignore comments and empty lines
       if line.starts_with("#") || line.starts_with(";") || line.is_empty() {
           continue;
       }
       // Split line into parameter name and rest tokens
       let tokens = Vec::from_iter(line.split_whitespace()); 
       let name = tokens.first().unwrap();
       let tokens = tokens.get(1..).unwrap();
       // Remove the equal signs
       let tokens = tokens.iter().filter(|t| !t.starts_with("="));
       // Remove comment after the parameters
       let tokens = tokens.take_while(|t| !t.starts_with("#") && !t.starts_with(";"));
       
       // Concat back the parameters into one string to split for separated parameters
       let mut parameters = String::new();
       tokens.for_each(|t| { parameters.push_str(t); parameters.push(' '); });
       // Splits the parameters and trims
       let parameters = parameters.split(',').map(|s| s.trim());
       // Converts them from Vec<&str> into Vec<String>
       let parameters: Vec<String> = parameters.map(|s| s.to_string()).collect();
       
       // Setting the config parameters
       match name.to_lowercase().as_str() {
           "fullname" => cfg.full_name = parameters.get(0).cloned(),
           "favouritefruit" => cfg.favourite_fruit = parameters.get(0).cloned(),
           "needspeeling" => cfg.needs_peeling = true,
           "seedsremoved" => cfg.seeds_removed = true,
           "otherfamily" => cfg.other_family = Some(parameters),
           _ => (),
       }
   }
   cfg

}

  1. [derive(Clone, Debug)]

struct Config {

   full_name: Option<String>,
   favourite_fruit: Option<String>,
   needs_peeling: bool,
   seeds_removed: bool,
   other_family: Option<Vec<String>>,

}

impl Config {

   fn new() -> Config {
       Config {
           full_name: None,
           favourite_fruit: None,
           needs_peeling: false,
           seeds_removed: false,
           other_family: None,
       }
   }

}</lang>

Output:
Config { full_name: Some("Foo Barber"), favourite_fruit: Some("banana"), needs_peeling: true, seeds_removed: false, other_family: Some(["Rhu Barber", "Harry Barber"]) }

Scala

A "one liner" version which:

  • Filters out empty and comment lines
  • Splits field name and value(s)
  • Adds "true" to value-less fields
  • Converts values to (k, List(values)
  • Converts the entire collection to a Map

<lang Scala>val conf = scala.io.Source.fromFile("config.file").

 getLines.
 toList.
 filter(_.trim.size > 0).
 filterNot("#;" contains _(0)).
 map(_ split(" ", 2) toList).
 map(_ :+ "true" take 2).
 map {
   s:List[String] => (s(0).toLowerCase, s(1).split(",").map(_.trim).toList)
 }.toMap</lang>

Test code: <lang Scala> for ((k,v) <- conf) {

 if (v.size == 1)
   println("%s: %s" format (k, v(0)))
 else
   for (i <- 0 until v.size)
     println("%s(%s): %s" format (k, i+1, v(i)))

} </lang>

Output:
fullname: Foo Barber
favouritefruit: banana
needspeeling: true
otherfamily(1): Rhu Barber
otherfamily(2): Harry Barber

Seed7

The Seed7 library scanfile.s7i, can be used to scan a file with functions like getWord, skipSpace and getLine.

<lang seed7>$ include "seed7_05.s7i";

 include "scanfile.s7i";

var string: fullname is ""; var string: favouritefruit is ""; var boolean: needspeeling is FALSE; var boolean: seedsremoved is FALSE; var array string: otherfamily is 0 times "";

const proc: main is func

 local
   var file: configFile is STD_NULL;
   var string: symbol is "";
   var integer: index is 0;
 begin
   configFile := open("readcfg.txt", "r");
   configFile.bufferChar := getc(configFile);
   symbol := lower(getWord(configFile));
   while symbol <> "" do
     skipSpace(configFile);
     if symbol = "#" or symbol = ";" then
       skipLine(configFile);
     elsif symbol = "fullname" then
       fullname := getLine(configFile);
     elsif symbol = "favouritefruit" then
       favouritefruit := getLine(configFile);
     elsif symbol = "needspeeling" then
       needspeeling := TRUE;
     elsif symbol = "seedsremoved" then
       seedsremoved := TRUE;
     elsif symbol = "otherfamily" then
       otherfamily := split(getLine(configFile), ",");
       for key index range otherfamily do
         otherfamily[index] := trim(otherfamily[index]);
       end for;
     else
       writeln(" *** Illegal line " <& literal(getLine(configFile)));
     end if;
     symbol := lower(getWord(configFile));
   end while;
   close(configFile);
   writeln("fullname:       " <& fullname);
   writeln("favouritefruit: " <& favouritefruit);
   writeln("needspeeling:   " <& needspeeling);
   writeln("seedsremoved:   " <& seedsremoved);
   for key index range otherfamily do
     writeln(("otherfamily[" <& index <& "]:") rpad 16 <& otherfamily[index]);
   end for;
 end func;</lang>
Output:
fullname:       Foo Barber
favouritefruit: banana
needspeeling:   TRUE
seedsremoved:   FALSE
otherfamily[1]: Rhu Barber
otherfamily[2]: Harry Barber

SenseTalk

<lang sensetalk> // read the configuration file and get a list of just the interesting lines set lines to each line of file "config.txt" where char 1 of each isn't in ("#", ";", "")

set the listFormat's quotes to quote -- be sure to quote values for evaluating

repeat with each configLine in lines put word 1 of configLine into varName insert varName into variableNames -- make a list of all config variables

put (words 2 to last of configLine) split by comma into values put trim of each item of values into values -- trim any leading/trailing spaces if values is empty then set values to true -- no value means boolean true do "set" && varName && "to" && values -- assign value to variable end repeat

repeat with each name in variableNames put "Variable" && name && "is" && value(name) end repeat </lang> Output: <lang sensetalk> Variable FULLNAME is Foo Barber Variable FAVOURITEFRUIT is banana Variable NEEDSPEELING is True Variable OTHERFAMILY is ("Rhu Barber","Harry Barber") </lang>

Sidef

<lang ruby>var fullname = (var favouritefruit = ""); var needspeeling = (var seedsremoved = false); var otherfamily = [];

ARGF.each { |line|

   var(key, value) = line.strip.split(/\h+/, 2)...;
   given(key) {
       when (nil)              { }
       when (/^([#;]|\h*$)/)   { }
       when ("FULLNAME")       { fullname = value }
       when ("FAVOURITEFRUIT") { favouritefruit = value }
       when ("NEEDSPEELING")   { needspeeling = true }
       when ("SEEDSREMOVED")   { seedsremoved = true }
       when ("OTHERFAMILY")    { otherfamily = value.split(',')»strip»() }
       default                 { say "#{key}: unknown key" }
   }

}

say "fullname = #{fullname}"; say "favouritefruit = #{favouritefruit}"; say "needspeeling = #{needspeeling}"; say "seedsremoved = #{seedsremoved}";

otherfamily.each_kv {|i, name|

   say "otherfamily(#{i+1}) = #{name}";

}</lang>

Output:
fullname       = Foo Barber
favouritefruit = banana
needspeeling   = true
seedsremoved   = false
otherfamily(1) = Rhu Barber
otherfamily(2) = Harry Barber

Smalltalk

Works with: Smalltalk/X

This code retrieves the configuration values as a Dictionary; code to set an object's instance variables follows below: <lang smalltalk>dict := Dictionary new. configFile asFilename readingLinesDo:[:line |

   (line isEmpty or:[ line startsWithAnyOf:#('#' ';') ]) ifFalse:[
       s := line readStream.
       (s skipSeparators; atEnd) ifFalse:[
           |optionName values|
           optionName := s upToSeparator.
           values := (s upToEnd asCollectionOfSubstringsSeparatedBy:$,) 
                        collect:[:each | each withoutSeparators]
                        thenSelect:[:vals | vals notEmpty].
           dict at:optionName asLowercase put:(values isEmpty 
                                               ifTrue:[true] 
                                               ifFalse:[
                                                  values size == 1
                                                    ifTrue:[values first]
                                                    ifFalse:[values]]).    
       ]
   ].

]</lang> gives us in dict Dictionary ('fullname'->'Foo Barber' 'needspeeling'->true 'favouritefruit'->'banana' 'otherfamily'->OrderedCollection('Rhu Barber' 'Harry Barber'))

assuming that the target object has setters for each option name, we could write: <lang smalltalk>dict keysAndValuesDo:[:eachOption :eachValue |

  someObject 
      perform:(eachOption,':') asSymbol with:eachValue
      ifNotUnderstood: [ self error: 'unknown option: ', eachOption ]

]</lang> or assign variables with: <lang smalltalk>fullname := dict at: 'fullname' ifAbsent:false. needspeeling := dict at: 'needspeeling' ifAbsent:false. favouritefruit := dict at: 'favouritefruit' ifAbsent:false. otherfamily := dict at: 'otherfamily' ifAbsent:false. seedsremoved := dict at: 'seedsremoved' ifAbsent:false. </lang>

Tcl

This code stores the configuration values in a global array (cfg) so they can't pollute the global namespace in unexpected ways. <lang tcl>proc readConfig {filename {defaults {}}} {

   global cfg
   # Read the file in
   set f [open $filename]
   set contents [read $f]
   close $f
   # Set up the defaults, if supplied
   foreach {var defaultValue} $defaults {

set cfg($var) $defaultValue

   }
   # Parse the file's contents
   foreach line [split $contents "\n"] {

set line [string trim $line] # Skip comments if {[string match "#*" $line] || [string match ";*" $line]} continue # Skip blanks if {$line eq ""} continue

if {[regexp {^\w+$} $line]} { # Boolean case set cfg([string tolower $line]) true } elseif {[regexp {^(\w+)\s+([^,]+)$} $line -> var value]} { # Simple value case set cfg([string tolower $var]) $value } elseif {[regexp {^(\w+)\s+(.+)$} $line -> var listValue]} { # List value case set cfg([string tolower $var]) {} foreach value [split $listValue ","] { lappend cfg([string tolower $var]) [string trim $value] } } else { error "malformatted config file: $filename" }

   }

}

  1. Need to supply some default values due to config file grammar ambiguities

readConfig "fruit.cfg" {

   needspeeling false
   seedsremoved false

} puts "Full name: $cfg(fullname)" puts "Favourite: $cfg(favouritefruit)" puts "Peeling? $cfg(needspeeling)" puts "Unseeded? $cfg(seedsremoved)" puts "Family: $cfg(otherfamily)"</lang>

TXR

Prove the logic by transliterating to a different syntax: <lang txr>@(collect) @ (cases)

  1. @/.*/

@ (or)

@/.*/

@ (or) @{IDENT /[A-Z_][A-Z_0-9]+/}@/ */ @(bind VAL ("true")) @ (or) @{IDENT /[A-Z_][A-Z_0-9]+/}@(coll)@/ */@{VAL /[^,]+/}@/ */@(end) @ (or) @{IDENT /[A-Z_][A-Z_0-9]+/}@(coll)@/ */@{VAL /[^, ]+/}@/,? */@(end) @(flatten VAL) @ (or) @/ */ @ (or) @ (throw error "bad configuration syntax") @ (end) @(end) @(output) @ (repeat) @IDENT = @(rep)@VAL, @(first){ @VAL, @(last)@VAL };@(single)@VAL;@(end) @ (end) @(end) </lang>

Sample run:

$ txr  configfile.txr  configfile
FULLNAME = Foo Barber;
FAVOURITEFRUIT = banana;
NEEDSPEELING = true;
OTHERFAMILY = { Rhu Barber, Harry Barber };

UNIX Shell

No effort is made to make an exception for SEEDSREMOVED: lines beginning with a semi-colon are treated as comments. No expectations are made about what variables will be seen in the config file.

Works with: bash

<lang bash>readconfig() (

   # redirect stdin to read from the given filename
   exec 0<"$1"
   declare -l varname
   while IFS=$' =\t' read -ra words; do
       # is it a comment or blank line?
       if [[ ${#words[@]} -eq 0 || ${words[0]} == ["#;"]* ]]; then
           continue
       fi
       # get the variable name
       varname=${words[0]}
       # split all the other words by comma
       value="${words[*]:1}"
       oldIFS=$IFS IFS=,
       values=( $value )
       IFS=$oldIFS
       # assign the other words to a "scalar" variable or array
       case ${#values[@]} in
           0) printf '%s=true\n' "$varname" ;;
           1) printf '%s=%q\n' "$varname" "${values[0]}" ;;
           *) n=0
              for value in "${values[@]}"; do
                  value=${value# }
                  printf '%s[%d]=%q\n' "$varname" $((n++)) "${value% }"
              done
              ;;
       esac
   done

)

  1. parse the config file and evaluate the output in the current shell

source <( readconfig config.file )

echo "fullname = $fullname" echo "favouritefruit = $favouritefruit" echo "needspeeling = $needspeeling" echo "seedsremoved = $seedsremoved" for i in "${!otherfamily[@]}"; do

   echo "otherfamily[$i] = ${otherfamily[i]}"

done</lang>

Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = 
otherfamily[0] = Rhu Barber
otherfamily[1] = Harry Barber

VBScript

<lang vb> Set ofso = CreateObject("Scripting.FileSystemObject") Set config = ofso.OpenTextFile(ofso.GetParentFolderName(WScript.ScriptFullName)&"\config.txt",1)

config_out = ""

Do Until config.AtEndOfStream line = config.ReadLine If Left(line,1) <> "#" And Len(line) <> 0 Then config_out = config_out & parse_var(line) & vbCrLf End If Loop

WScript.Echo config_out

Function parse_var(s) 'boolean false If InStr(s,";") Then parse_var = Mid(s,InStr(1,s,";")+2,Len(s)-InStr(1,s,";")+2) & " = FALSE" 'boolean true ElseIf UBound(Split(s," ")) = 0 Then parse_var = s & " = TRUE" 'multiple parameters ElseIf InStr(s,",") Then var = Left(s,InStr(1,s," ")-1) params = Split(Mid(s,InStr(1,s," ")+1,Len(s)-InStr(1,s," ")+1),",") n = 1 : tmp = "" For i = 0 To UBound(params) parse_var = parse_var & var & "(" & n & ") = " & LTrim(params(i)) & vbCrLf n = n + 1 Next 'single var and paramater Else parse_var = Left(s,InStr(1,s," ")-1) & " = " & Mid(s,InStr(1,s," ")+1,Len(s)-InStr(1,s," ")+1) End If End Function

config.Close Set ofso = Nothing </lang>

Output:
FULLNAME = Foo Barber
FAVOURITEFRUIT = banana
NEEDSPEELING = TRUE
SEEDSREMOVED = FALSE
OTHERFAMILY(1) = Rhu Barber
OTHERFAMILY(2) = Harry Barber

Vedit macro language

<lang vedit>#11 = 0 // needspeeling = FALSE

  1. 12 = 0 // seedsremoved = FALSE

Reg_Empty(21) // fullname Reg_Empty(22) // favouritefruit Reg_Empty(23) // otherfamily

File_Open("|(PATH_ONLY)\example.cfg")

if (Search("|<FULLNAME|s", BEGIN+ADVANCE+NOERR)) {

   Match("=", ADVANCE)         // skip optional '='
   Reg_Copy_Block(21, CP, EOL_pos)

} if (Search("|<FAVOURITEFRUIT|s", BEGIN+ADVANCE+NOERR)) {

   Match("=", ADVANCE)
   Reg_Copy_Block(22, CP, EOL_pos)

} if (Search("|<OTHERFAMILY|s", BEGIN+ADVANCE+NOERR)) {

   Match("=", ADVANCE)
   Reg_Copy_Block(23, CP, EOL_pos)

} if (Search("|<NEEDSPEELING|s", BEGIN+ADVANCE+NOERR)) {

   #11 = 1

} if (Search("|<SEEDSREMOVED|s", BEGIN+ADVANCE+NOERR)) {

   #12 = 1

}

Buf_Quit(OK) // close .cfg file

// Display the variables Message("needspeeling = ") Num_Type(#11, LEFT) Message("seedsremoved = ") Num_Type(#12, LEFT) Message("fullname = ") Reg_Type(21) TN Message("favouritefruit = ") Reg_Type(22) TN Message("otherfamily = ") Reg_Type(23) TN</lang>

Output:
needspeeling   = 1
seedsremoved   = 0
fullname       = Foo Barber
favouritefruit = banana
otherfamily    = Rhu Barber, Harry Barber

Visual Basic

<lang vb>' Configuration file parser routines. ' ' (c) Copyright 1993 - 2011 Mark Hobley ' ' This configuration parser contains code ported from an application program ' written in Microsoft Quickbasic ' ' This code can be redistributed or modified under the terms of version 1.2 of ' the GNU Free Documentation Licence as published by the Free Software Foundation.

Sub readini()

 var.filename = btrim$(var.winpath) & ini.inifile
 var.filebuffersize = ini.inimaxlinelength
 Call openfileread
 If flg.error = "Y" Then
   flg.abort = "Y"
   Exit Sub
 End If
 If flg.exists <> "Y" Then
   flg.abort = "Y"
   Exit Sub
 End If
 var.inistream = var.stream

readinilabela:

 Call readlinefromfile
 If flg.error = "Y" Then
   flg.abort = "Y"
   Call closestream
   flg.error = "Y"
   Exit Sub
 End If
 If flg.endoffile <> "Y" Then
   iniline$ = message$
   If iniline$ <> "" Then
     If Left$(iniline$, 1) <> ini.commentchar AND Left$(iniline$, 1) <> ini.ignorechar Then
       endofinicommand% = 0
       For l% = 1 To Len(iniline$)
         If Mid$(iniline$, l%, 1) < " " Then
           endofinicommand% = l%
         End If
         If Not (endofinicommand%) Then
           If Mid$(iniline$, l%, 1) = " " Then
             endofinicommand% = l%
           End If
         End If
         If endofinicommand% Then
           l% = Len(iniline$)
         End If
       Next l%
       iniarg$ = ""
       If endofinicommand% Then
         If endofinicommand% <> Len(iniline$) Then
           iniarg$ = btrim$(Mid$(iniline$, endofinicommand% + 1))
           If iniarg$ = "" Then
             GoTo readinilabelb
           End If
           inicommand$ = Left$(iniline$, endofinicommand% - 1)
         End If
       Else
         inicommand$ = btrim$(iniline$)
       End If

readinilabelb:

       'interpret command
       inicommand$ = UCase$(inicommand$)
       Select Case inicommand$
         Case "FULLNAME"
           If iniarg$ <> "" Then
             ini.fullname = iniarg$
           End If
         Case "FAVOURITEFRUIT"
           If iniarg$ <> "" Then
             ini.favouritefruit = iniarg$
           End If
         Case "NEEDSPEELING"
           ini.needspeeling = "Y"
         Case "SEEDSREMOVED"
           ini.seedsremoved = "Y"
         Case "OTHERFAMILY"
           If iniarg$ <> "" Then
             ini.otherfamily = iniarg$
             CALL familyparser
           End If
         Case Else
           '!! error handling required
       End Select
     End If
   End If
   GoTo readinilabela
 End If
 Call closestream
 Exit Sub

readinierror:

End Sub

Sub openfileread()

 flg.streamopen = "N"
 Call checkfileexists
 If flg.error = "Y" Then Exit Sub
 If flg.exists <> "Y" Then Exit Sub
 Call getfreestream
 If flg.error = "Y" Then Exit Sub
 var.errorsection = "Opening File"
 var.errordevice = var.filename
 If ini.errortrap = "Y" Then
   On Local Error GoTo openfilereaderror
 End If
 flg.endoffile = "N"
 Open var.filename For Input As #var.stream Len = var.filebuffersize
 flg.streamopen = "Y"
 Exit Sub

openfilereaderror:

 var.errorcode = Err
 Call errorhandler
 resume '!!

End Sub

Public Sub checkfileexists()

 var.errorsection = "Checking File Exists"
 var.errordevice = var.filename
 If ini.errortrap = "Y" Then
   On Local Error GoTo checkfileexistserror
 End If
 flg.exists = "N"
 If Dir$(var.filename, 0) <> "" Then
   flg.exists = "Y"
 End If
 Exit Sub

checkfileexistserror:

 var.errorcode = Err
 Call errorhandler

End Sub

Public Sub getfreestream()

 var.errorsection = "Opening Free Data Stream"
 var.errordevice = ""
 If ini.errortrap = "Y" Then
   On Local Error GoTo getfreestreamerror
 End If
 var.stream = FreeFile
 Exit Sub

getfreestreamerror:

 var.errorcode = Err
 Call errorhandler
 resume '!!

End Sub

Sub closestream()

 If ini.errortrap = "Y" Then
   On Local Error GoTo closestreamerror
 End If
 var.errorsection = "Closing Stream"
 var.errordevice = ""
 flg.resumenext = "Y"
 Close #var.stream
 If flg.error = "Y" Then
   flg.error = "N"
   '!! Call unexpectederror
 End If
 flg.streamopen = "N"
 Exit Sub

closestreamerror:

 var.errorcode = Err
 Call errorhandler
 resume next

End Sub

Sub readlinefromfile()

 If ini.errortrap = "Y" Then
   On Local Error GoTo readlinefromfileerror
 End If
 If EOF(var.stream) Then
   flg.endoffile = "Y"
   Exit Sub
 End If
 Line Input #var.stream, tmp$
 message$ = tmp$
 Exit Sub

readlinefromfileerror:

 var.errorcode = Err
 Call errorhandler
 resume '!!

End Sub

Public Sub errorhandler()

 tmp$ = btrim$(var.errorsection)
 tmp2$ = btrim$(var.errordevice)
 If tmp2$ <> "" Then
   tmp$ = tmp$ + " (" + tmp2$ + ")"
 End If
 tmp$ = tmp$ + " : " + Str$(var.errorcode)
 tmp1% = MsgBox(tmp$, 0, "Error!")
 flg.error = "Y"
 If flg.resumenext = "Y" Then
   flg.resumenext = "N"

' Resume Next

 Else
   flg.error = "N"

' Resume

 End If

End Sub

Public Function btrim$(arg$)

 btrim$ = LTrim$(RTrim$(arg$))

End Function</lang>

Yabasic

<lang Yabasic>a = open("rosetta_read.cfg")

while(not eof(#a))

   FLAG = true : REMARK = false
   line input #a line$
   line$ = trim$(line$)
   ll = len(line$)
   c$ = left$(line$, 1)
   switch(c$)
       case "": case "#": REMARK = true : break
       case ";": FLAG = false : line$ = trim$(right$(line$, ll - 1)) : break
       default: MULTI = instr(line$, ",")
   end switch  
   if not REMARK then
       GAP = instr(line$, "=") : if not GAP GAP = instr(line$, " ")
       if not GAP then
           print line$, " = ";
           if FLAG then print "true" else print "false" end if
       else
           if MULTI then
               count = 1 : SG = GAP
               repeat
                   print left$(line$, GAP - 1), "(", count, ") = ", trim$(mid$(line$, SG + 1, MULTI - SG - 1))
                   count = count + 1
                   SG = MULTI + 1 : MULTI = instr(line$, ",", SG)
                   if not MULTI MULTI = ll + 1
               until(SG > ll)
           else
               print left$(line$, GAP - 1), " = ", trim$(right$(line$, ll - GAP))
           end if
       end if
   end if

wend

close #a</lang>

zkl

First, a simple parser that knows nothing about contents, just format. <lang zkl>fcn readConfigFile(config){ //--> read only dictionary

  conf:=Dictionary();
  foreach line in (config){
     line=line.strip();
     if (not line or "#"==line[0] or ";"==line[0]) continue;
     line=line.replace("\t"," ");
     n:=line.find(" ");
     if (Void==n) conf[line.toLower()]=True;  // eg NEEDSPEELING
     else{

key:=line[0,n].toLower(); line=line[n,*]; n=line.find(","); if (Void!=n) conf[key]=line.split(",").apply("strip").filter(); else conf[key]=line;

     }
  }
  conf.makeReadOnly();

}

conf:=readConfigFile(File("foo.conf"));</lang> Which may be good enough; query the hash table to get a set option or a default if it wasn't set: <lang zkl>foreach k,v in (conf){ println(k," : ",v) } println("Value of seedsremoved = ",conf.find("seedsremoved",False));</lang>

Output:
needspeeling : True
otherfamily : L("Rhu Barber","Harry Barber")
favouritefruit :  banana
fullname :  Foo Barber
Value of seedsremoved = False

If your program actually wants to use the options as variables, the following sets variables to values in the config file, ignoring misspellings, or values you don't care about. You are not allowed to create variables "on the fly". <lang zkl>var needspeeling,otherfamily,favouritefruit,fullname,seedsremoved; foreach k,v in (conf){ try{ setVar(k,v) }catch{} }; foreach k,v in (vars){ println(k," : ",v) } println("Value of seedsremoved = ",seedsremoved);</lang>

Output:
favouritefruit :  banana
fullname :  Foo Barber
needspeeling : True
otherfamily : L("Rhu Barber","Harry Barber")
seedsremoved : Void
Value of seedsremoved = Void