Read a configuration file

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

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

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



11l

Translation of: Python
F readconf(fname)
   [String = String] ret
   L(=line) File(fname).read_lines()
      line = line.trim((‘ ’, "\t", "\r", "\n"))
      I line == ‘’ | line.starts_with(‘#’)
         L.continue

      V boolval = 1B
      I line.starts_with(‘;’)
         line = line.ltrim(‘;’)

         I line.split_py().len != 1
            L.continue
         boolval = 0B

      V bits = line.split(‘ ’, 2, group_delimiters' 1B)
      String k, v
      I bits.len == 1
         k = bits[0]
         v = String(boolval)
      E
         (k, v) = bits
      ret[k.lowercase()] = v
   R ret

V conf = readconf(‘config.txt’)
L(k, v) sorted(conf.items())
   print(k‘ = ’v)
Output:
favouritefruit = banana
fullname = Foo Barber
needspeeling = 1B
otherfamily = Rhu Barber, Harry Barber
seedsremoved = 0B

Ada

Works with: Ada version 2005

Uses package Config available at SourceForge: https://sourceforge.net/projects/ini-files/

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;
Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = TRUE
seedsremoved = FALSE
otherfamily = Rhu Barber, Harry Barber

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");
}
Output:
FAVOURITEFRUIT: banana
FULLNAME: Foo Barber
NEEDSPEELING: true
SEEDSREMOVED: false
OTHERFAMILY: Rhu Barber, Harry Barber,

Arturo

parseConfig: function [f][
    lines: split.lines read f
    lines: select map lines 'line [strip replace line {/[#;].*/} ""]
                            'line [not? empty? line]
    result: #[]

    fields: loop lines 'line [
        field: first match line {/^[A-Z]+/}
        rest: strip replace line field ""
        parts: select map split.by:"," rest => strip 'part -> not? empty? part

        val: null
        case [(size parts)]
            when? [= 0] -> val: true
            when? [= 1] -> val: first parts
            else -> val: parts

        result\[lower field]: val
    ]

    return result
]

loop parseConfig relative "config.file" [k,v][
    if? block? v -> print [k "->" join.with:", " v]
    else -> print [k "->" v]
]
Output:
fullname -> Foo Barber 
favouritefruit -> banana 
needspeeling -> true 
otherfamily -> Rhu Barber, Harry Barber

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%

AWK

# 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)
}
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.

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

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

      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
Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily(1) = Rhu Barber
otherfamily(2) = Harry Barber

C

Library: libconfini

optimized

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <confini.h>

#define rosetta_uint8_t unsigned char

#define FALSE 0
#define TRUE 1

#define CONFIGS_TO_READ 5
#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;

}
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

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <boost/tokenizer.hpp>
#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;
}
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.

#include <iostream>
#include <iomanip>
#include <string>
#include <exception>
#include <fstream>
#include <vector>
#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);
}
Output:
Full Name: Foo Barber
Favourite Fruit: banana
Needs peeling?: True
Seeds removed?: False
Other family members: Rhu Barber, Harry Barber

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))))
Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily = Rhu Barber, Harry Barber

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
           .
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:

(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))
Output:
READ-CONFIG> (with-open-file (s "test.cfg") (parse-config s))
#<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

D

import std.stdio, std.string, std.conv, std.regex, std.getopt;

enum VarName(alias var) = var.stringof;

void setOpt(alias Var)(in string line) {
    auto m = match(line, regex(`^(?i)` ~ VarName!Var ~ `(?-i)(\s*=?\s+(.*))?`));

    if (!m.empty) {
        static if (is(typeof(Var) == string[]))
            Var = m.captures.length > 2 ? m.captures[2].split(regex(`\s*,\s*`)) : [""];
        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;
    string[] otherFamily;
    bool needsPeeling, seedsRemoved; // Default false.

    auto f = "readcfg.conf".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("%s = %s", VarName!fullName, fullName);
    writefln("%s = %s", VarName!favouriteFruit, favouriteFruit);
    writefln("%s = %s", VarName!needsPeeling, needsPeeling);
    writefln("%s = %s", VarName!seedsRemoved, seedsRemoved);
    writefln("%s = %s", VarName!otherFamily, otherFamily);
}
Output:
fullName = Foo Barber
favouriteFruit = banana
needsPeeling = true
seedsRemoved = false
otherFamily = ["Rhu Barber", "Harry Barber", "John"]

Variant 2

Correct version with handling optional '=' sign. Config is assembled into one class.

import std.stdio, std.string, std.conv, std.regex, std.algorithm;

auto reNameValue = ctRegex!(`^(\w+)\s*=?\s*(\S.*)?`);// ctRegex creates regexp parser at compile time

// print Config members w/o hardcoding names
void PrintMembers(Config c)
{
    foreach(M; __traits(derivedMembers, Config))
        writeln(M ~ ` = `, __traits(getMember, c, M));
}

void main(in string[] args /* arg[0] is EXE name */) {

    auto cfg = new Config;
    auto f = args[1].File;// open config given in command line
    foreach (line; f.byLineCopy.map!(s => s.strip).filter!(s => !s.empty && s[0] != '#' && s[0] != ';')) {// free loop from unnecessary lines
        auto m = matchFirst(line, reNameValue);
        if (m.empty) { writeln(`Wrong config line: ` ~ line); continue; }

        switch(m[1].toUpper) {
        case `FULLNAME`:       cfg.FullName       = m[2]; break;
        case `FAVOURITEFRUIT`: cfg.FavouriteFruit = m[2]; break;
        case `NEEDSPEELING`:   cfg.needsPeeling   = (m[2].toUpper != `FALSE`); break;
        case `SEEDSREMOVED`:   cfg.seedsRemoved   = (m[2].toUpper != `FALSE`); break;
        case `OTHERFAMILY`:    cfg.otherFamily    = split(m[2], regex(`\s*,\s*`)); break;// regex allows to avoid 'strip' step
        default:
            writeln(`Unknown config variable: ` ~ m[1]);
        }
    }
    PrintMembers(cfg);
}

class Config
{
    string FullName;
    string FavouriteFruit;
    bool needsPeeling;
    bool seedsRemoved;
    string[] otherFamily;
}
Output:

On config:

# 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,   John

Output is:

FullName = Foo Barber
FavouriteFruit = banana
needsPeeling = true
seedsRemoved = false
otherFamily = ["Rhu Barber", "Harry Barber", "John"]

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

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: can''t 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.

Usage of unit:

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

(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")))
Output:
;; 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

Elixir

Translation of: Erlang
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
Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily(1) = Rhu Barber
otherfamily(2) = Harry Barber

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].
Output:
50> configuration_file:task().
fullname = "Foo Barber"
favouritefruit = "banana"
needspeeling = true
seedsremoved = false
otherfamily = ["Rhu Barber","Harry Barber"]

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(", "))
  }
}

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.

\ 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
' # alias ;                                                \ 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) $.  ;

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

Note that parsing a config file using the forth text interpreter this way is probably only safe if you are the only one that edits the config file, as it can execute any forth word.

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

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
Output:
Full name        = Foo Barber
Favourite fruit  = banana
Needs peeling    = true
Seeds removed    = false
Other family(0)  = Rhu Barber
Other family(1)  = Harry Barber

FutureBasic

local fn SaveConfiguration
  CFDictionaryRef defaults = @{¬
  @"FULLNAME"       : @"Foo Barber",¬
  @"FAVOURITEFRUIT" : @"banana",¬
  @"NEEDSPEELING"   : @YES,¬
  @"SEEDSREMOVED"   : @NO,¬
  @"OTHERFAMILY"    : @[@"Rhu Barber", @"Harry Barber"]}
  
  UserDefaultsRegisterDefaults( defaults )
end fn

local fn ReadConfiguration
  CFStringRef tempStr
  
  CFStringRef       fullname = fn UserDefaultsString( @"FULLNAME"       )
  CFStringRef favouritefruit = fn UserDefaultsString( @"FAVOURITEFRUIT" )
  BOOL          needspeeling = fn UserDefaultsBool(   @"NEEDSPEELING"   )
  BOOL          seedsremoved = fn UserDefaultsBool(   @"SEEDSREMOVED"   )
  CFArrayRef     otherfamily = fn UserDefaultsArray(  @"OTHERFAMILY"    )
  
  printf @"Saved configuration:\n"
  printf @"FULLNAME:       %@", fullname
  printf @"FAVOURITEFRUIT: %@", favouritefruit
  if needspeeling == YES then tempStr = @"TRUE" else tempStr = @"FALSE"
  printf @"NEEDSPEELING:   %@", tempStr
  if seedsremoved == YES then tempStr = @"TRUE" else tempStr = @"FALSE"
  printf @"SEEDSREMOVED:   %@", @"(undefined)"
  printf @"OTHERFAMILY:    %@, %@", otherfamily[0], otherfamily[1]
end fn

fn SaveConfiguration
fn ReadConfiguration

HandleEvents
Output:
Saved configuration:

FULLNAME:       Foo Barber
FAVOURITEFRUIT: banana
NEEDSPEELING:   TRUE
SEEDSREMOVED:   (undefined)
OTHERFAMILY:    Rhu Barber, Harry Barber

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

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.

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
}

Usage example:

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)
}

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]
            }
        }
    }
}

Testing:

loadConfig new File('config.ini')
config.each { println it }
Output:
FULLNAME=Foo Barber
FAVOURITEFRUIT=banana
NEEDSPEELING=true
SEEDSREMOVED=false
OTHERFAMILY(1)=Rhu Barber
OTHERFAMILY(2)=Harry Barber

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 []

Or, use Data.Configfile:

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"

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:

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

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

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
)

Example use:

   taskCfg 'fruit.conf'
   (,' = ',]&.do)&>;: 'fullname favouritefruit needspeeling seedsremoved'
fullname = Foo Barber  
favouritefruit = banana
needspeeling = 1       
seedsremoved = 0

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).

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 ) );
        }
    }
}
Output:
{otherfamily=[Rhu Barber, Harry Barber], favouritefruit=banana, seedsremoved=false, needspeeling=true, fullname=Foo Barber}

A more functional and concise approach using Java 8:

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;
    }
}
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.

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;
}

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

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

jq

Works with: jq version 1.5

In the following, in the case of collisions, the last-most specification prevails.

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

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

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])
}

Ksh

#!/bin/ksh

# Read a configuration file

#	# Variables:
#

#	# The configuration file (below) could be read in from a file
#	  But this method keeps everything together.
# e.g. config=$(< /path/to/config_file)

integer config_num=0
config='# 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'

isComment='#|;'
paraDelim=' |='
boolean="SEEDSREMOVED|NEEDSPEELING"

typeset -T Config_t=(
	typeset -h		'Full name'				fullname
	typeset -h		'Favorite fruit'		favouritefruit
	typeset -h		'Boolean NEEDSPEELING'	needspeeling=false
	typeset -h		'Boolean SEEDSREMOVED'	seedsremoved=false
	typeset -a -h	'Other family'			otherfamily

	function set_name {
		typeset fn ; fn=$(echo $1)	# Strip any leading/trailing white space
		_.fullname="${fn}"
	}

	function set_fruit {
		typeset fruit ; fruit=$(echo $1)
		_.favouritefruit="${fruit}"
	}

	function set_bool {
		typeset bool ; typeset -u bool=$1

		case ${bool} in
			NEEDSPEELING) _.needspeeling=true ;;
			SEEDSREMOVED) _.seedsremoved=true ;;
		esac
	}

	function set_family {
		typeset ofam ; ofam=$(echo $1)
		typeset farr i ; typeset -a farr ; integer i

		oldIFS="$IFS" ; IFS=',' ; farr=( ${ofam} ) ; IFS="${oldIFS}"
		for ((i=0; i<${#farr[*]}; i++)); do
			_.otherfamily[i]=$(echo ${farr[i]})
		done
	}
)

#	# Functions:
#

#	# Function _parseconf(config) - Parse uncommented lines 
#
function _parseconf {
	typeset _cfg ; _cfg="$1"
	typeset _conf ; nameref _conf="$2"

	echo "${_cfg}" |	\
	while read; do
		[[ $REPLY == @(${isComment})* ]] || [[ $REPLY == '' ]] && continue
		_parseline "$REPLY" _conf
	done
}

function _parseline {
	typeset _line ; _line=$(echo $1)
	typeset _conf ; nameref _conf="$2"
	typeset _param _value ; typeset -u _param

	_param=${_line%%+(${paraDelim})*}
	_value=${_line#*+(${paraDelim})}

	if [[ ${_param} == @(${boolean}) ]]; then
		_conf.set_bool ${_param}
	else
		case ${_param} in
			FULLNAME)		_conf.set_name "${_value}" ;;
			FAVOURITEFRUIT)	_conf.set_fruit ${_value} ;;
			OTHERFAMILY)	_conf.set_family "${_value}" ;;
		esac
	fi
}
 ######
# main #
 ######

typeset -a configuration		# Indexed array of configurations
Config_t configuration[config_num]
_parseconf "${config}" configuration[config_num]

for cnum in ${!configuration[*]}; do
	printf "fullname = %s\n" "${configuration[cnum].fullname}"
	printf "favouritefruit = %s\n" ${configuration[cnum].favouritefruit}
	printf "needspeeling = %s\n" ${configuration[cnum].needspeeling}
	printf "seedsremoved = %s\n" ${configuration[cnum].seedsremoved}
	for ((i=0; i<${#configuration[cnum].otherfamily[*]}; i++)); do
		print "otherfamily($((i+1))) = ${configuration[cnum].otherfamily[i]}"
	done
done
Output:

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

Lasso

local(config = '# 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
')
// 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)
)

#fullname
'<br />'
#favorite
'<br />'
#sedsremoved
'<br />'
#needspeel
'<br />'
#otherfamily
'<br />'
Output:
Foo Barber
banana
false
true
array(Rhu Barber, Harry Barber)

Liberty BASIC

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
Output:
Full name: Foo Barber
likes: banana
needs peeling: 1
seeds removed: 0
other family:
1. Rhu Barber
2. Harry Barber

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

M2000 Interpreter

The congiguration.txt is in a zip file in Encode64 Binary part We can export to disk or use it as is, through the buffer

to make it I use this:

Declare zip compressor
a$=str$(a$)
Method zip, "AddFromMemory",a$, "configuration.txt" as ok
Method zip,"CreateZipBuffer" as buf1
clipboard String$(eval$(buf1) as Encode64)

//where a$ defined (before)
a$={text here from first line
until last line
}


To do Declare Zip Nothing is optional (for User forms isn't though)


module check(a$, id as list){
	Document Export$	
	nl$={
	}
	dim L$() : L$()=piece$(a$,nl$)
	if len(L$())=0 then exit
	for i=0 to len(L$())-1
		a$=trim$(L$(i))
		b$=left$(a$, 1)
		select case b$
		case ";"
			examineValue(true)
		case >"#"
			examineValue(false)
		end select
	next
	Report Export$  // result or Clipboard Export$
	Sub examineValue(NotUsed as boolean)
		local i
		if NotUsed then
			a$=trim$(mid$(a$,2))+" "
			b$=leftpart$(a$," ")
		else
			a$+=" "
			b$=leftpart$(a$," ")
		end if
		a$=trim$(rightpart$(a$," "))
		// optional = removed
		if left$(a$,1)="=" then a$=trim$(mid$(a$,2))
		// if not exist ignore it
		if exist(id,ucase$(b$)) then
			if len(a$)=0 then  // we have a boolean
				Export$=b$+" = "+if$(NotUsed->"false", "true")+nl$
			else.if instr(a$,",")>0 then // multiple value
				local a$()
				a$()=piece$(a$,",")
				for i=0 to len(a$())-1
					Export$=format$("{0}({1}) = {2}",b$,i+1, trim$(a$(i)))+nl$
				next
			else
				Export$=b$+" = "+a$+nl$
			end if
		end if
	End Sub
}
valid=list:="FULLNAME", "FAVOURITEFRUIT", "NEEDSPEELING", "SEEDSREMOVED", "OTHERFAMILY"
binary{
UEsDBBQAAAgIAO8FflU2rdfqSAIAANQDAAARAAAAY29uZmlndXJhdGlvbi50eHRT
VgjJyCxWyCxWSFRIzs9Ly0wvLUosyczPU0jLzElVyMxTKC5JzEtJLErBJp2WX5Sb
WMLLpczLpazgk5mXWqyQlJqemZeXmZeuUJ5ZkqGQqJCRWJyhkF+kkKhQnJqbmZyf
k5+nkFiUqpCZnpdflJqikFSpUJKRqpBYUJCTmQw2G2RYQVF+elFirp6CU05iXrZC
DthskLbEnOJ8PHrhGnm5QMbAPAdSlVaak5OXmJuqUJBYlJibWpJaxMvlFurj4+fo
66rglp+v4JRYlAQSRNaYqJCWWJZfWpRZkqqQVlSaWcLL5eYY5h8a5Bni6hYU6hmi
kJSYl5iXiK4rKT8/JzUxT6EkI7FEoTgjvzQnRSEpVaE4tYSXy8/V1SU4wNXVx9PP
HUkfTEtmsUJyfm5ual5JaopCfmkJL5e1QjBIS5Crr3+YqwtEizNKbOQXgCmQ9yDB
lJdfopCcWAyyMa84sySzLFVHIam0BC0SkUJCWSElsSQRbDmKNoXEvBSF3MRKkOsL
ilKLU4vKiAl4R5ibEnMUUgtLE3OKFYoz0/MUkhPzQCaVFqemKJTkKxSngpxQkorL
XWBHgcxLK8rPBVuJ5FM9eHinFOUXFCCcVZBYVJxapKcAdQqa4VATQH4qScxOVcgt
zSnJLMhBShfFcHeBjQTFRmKxHjiNpyamgNI2KFBKihIzc8AJPSOzJLW4IDE5VSGx
KL80LwXJ/dAYQREDB3RaZmpOSjHITPyZASVc/UM8XIPcHH09fSIVgjJKoSlWR8Ej
saioEp5+AVBLAQItABQAAAgIAO8FflU2rdfqSAIAANQDAAARAAAAAAAAAAAAIAAA
AAAAAABjb25maWd1cmF0aW9uLnR4dFBLBQYAAAAAAQABAD8AAAB3AgAAAAA=} as zip1
Declare zip compressor
method zip,"OpenZipBuf", zip1
method zip, "ExtractOneToBuffer", "configuration.txt" as buf
If true then
         // save buf to file, the load to document as ANSI 1033
	open "configuration.txt" for output as #f
	put #f,buf, 1
	close #f
	document b$ : Load.doc b$, "configuration.txt", 1033
	check b$, valid
else
	check chr$(eval$(buf)), valid
end if
Output:
FULLNAME = Foo Barber
FAVOURITEFRUIT = banana
NEEDSPEELING = true
SEEDSREMOVED = false
OTHERFAMILY(1) = Rhu Barber
OTHERFAMILY(2) = Harry Barber

Mathematica/Wolfram Language

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]

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.

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

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])
Output:
[[FULLNAME : Foo Barber], [FAVOURITEFRUIT : banana], [NEEDSPEELING : true], [OTHERFAMILY : [Rhu Barber, Harry Barber]]]

Nim

import re, strformat, strutils, tables

var configs: OrderedTable[string, seq[string]]
var parsed: seq[string]

for line in "demo.config".lines():
  let line = line.strip()
  if line != "" and not line.startswith(re"#|;"):
    parsed = line.split(re"\s*=\s*|\s+", 1)
    configs[parsed[0].toLower()] = if len(parsed) > 1: parsed[1].split(re"\s*,\s*") else: @[]

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}")
Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily(1) = Rhu Barber
otherfamily(2) = Harry Barber

OCaml

Using the library ocaml-inifiles:

#use "topfind"
#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;
;;

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:

#!/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
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.

#!/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.
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.

<@ 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</@>

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.)

my $fullname;
my $favouritefruit;
my $needspeeling;
my $seedsremoved;
my @otherfamily;

# 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";
}

# read_conf_file:  Given a file handle opened for reading and a configuration definition,
# read the file.
# If the configuration file doesn't match the definition, raise an exception with "die".
# The configuration definition is (a reference to) an associative array
# where the keys are the configuration variable names in all lower case
# and the values are references to arrays.
# The first element of each of these arrays is the expected type:  'boolean', 'string', or 'array';
# 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)";
        }
    }
}

Phix

Normally I would recommend IupConfig, but the "standard" file format in the task description isn't even close (no [Section] headers, no '=').

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)
Output:
{"FAVOURITEFRUIT","banana"}
{"FULLNAME","Foo Barber"}
{"NEEDSPEELING",1}
{"OTHERFAMILY",{"Rhu Barber","Harry Barber"}}
"banana"

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
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()

<?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;
Output:
Full name       = Foo Barber
Favourite fruit = banana
Need spelling   = true
Seeds removed   = false
Other family    = Array
(
    [0] => Rhu Barber
    [1] => Harry Barber
)

Picat

go =>
  Vars = ["fullname","favouritefruit","needspeeling","seedsremoved","otherfamily"],
  Config = read_config("read_a_configuration_file_config.cfg"),
  foreach(Key in Vars)
    printf("%w = %w\n", Key, Config.get(Key,false))
  end,
  nl.

% Read configuration file
read_config(File) = Config =>
  Config = new_map(),
  Lines = [Line : Line in read_file_lines(File), Line != [], not membchk(Line[1],"#;")],
  foreach(Line in Lines)
    Line := strip(Line),
    once( append(Key,[' '|Value],Line) ; Key = Line, Value = true),
    if find(Value,",",_,_) then
      Value := [strip(Val) : Val in split(Value,",")]
    end,
    Key := strip(to_lowercase(Key)),
    Config.put(Key,Value)
  end.
Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = false
otherfamily = [Rhu Barber,Harry Barber]


PicoLisp

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

(rdConf "conf.txt")
(println FULLNAME FAVOURITEFRUIT NEEDSPEELING SEEDSREMOVED OTHERFAMILY)
(bye)
Output:
"Foo Barber" "banana" T NIL "Rhu Barber, Harry Barber"

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

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 ", "))
}

I stored the file in ".\temp.txt" and there is no output unless the -Verbose switch is used:

Read-ConfigurationFile -Path .\temp.txt -Verbose
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:

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

Using Switch -Regex

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
}

Setting variable

$Configuration = Read-ConfigurationFile -LiteralPath '.\config.cfg'

Show variable

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

Using customize function

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

'$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)
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).

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

Racket

Use the shared Racket/Options code.

#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)
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.

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;
Output:
fullname: Foo Barber
favouritefruit: banana
needspeeling: True
seedsremoved: False
otherfamily: ["Rhu Barber", "Harry Barber"]

RapidQ

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

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
]
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.

/*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)
#:       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)

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

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
Output:
fullname       = Foo Barber
favouritefruit = banana
needspeeling   = true
seedsremoved   = false
otherfamily(1) = Rhu Barber
otherfamily(2) = Harry Barber

Run BASIC

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
Output:
fullname	Foo Barber
favouritefruit	banana
needspeeling	true
seedsremoved	false
otherfamily(1)	Rhu Barber
otherfamily(2)	Harry Barber

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
}

#[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,
        }
    }
}
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
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

Test code:

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

}
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.

$ 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;
Output:
fullname:       Foo Barber
favouritefruit: banana
needspeeling:   TRUE
seedsremoved:   FALSE
otherfamily[1]: Rhu Barber
otherfamily[2]: Harry Barber

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

Output:

Variable FULLNAME is Foo Barber
Variable FAVOURITEFRUIT is banana
Variable NEEDSPEELING is True
Variable OTHERFAMILY is ("Rhu Barber","Harry Barber")

Sidef

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}";
}
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:

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]]).    
        ]
    ].
]

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:

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

or assign variables with:

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.

Tcl

This code stores the configuration values in a global array (cfg) so they can't pollute the global namespace in unexpected ways.

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"
	}
    }
}

# 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)"

TXR

Prove the logic by transliterating to a different syntax:

@(collect)
@  (cases)
#@/.*/
@  (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)

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

# 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
Output:
fullname = Foo Barber
favouritefruit = banana
needspeeling = true
seedsremoved = 
otherfamily[0] = Rhu Barber
otherfamily[1] = Harry Barber

VBScript

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
Output:
FULLNAME = Foo Barber
FAVOURITEFRUIT = banana
NEEDSPEELING = TRUE
SEEDSREMOVED = FALSE
OTHERFAMILY(1) = Rhu Barber
OTHERFAMILY(2) = Harry Barber

Vedit macro language

#11 = 0                 // needspeeling = FALSE
#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
Output:
needspeeling   = 1
seedsremoved   = 0
fullname       = Foo Barber
favouritefruit = banana
otherfamily    = Rhu Barber, Harry Barber

Visual Basic

' 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

Wren

Translation of: Kotlin
Library: Wren-ioutil

Includes 'seeds removed' in the map (with a default value of false) even though it's commented out of the configuration file.

import "io" for File
import "./ioutil" for FileUtil

class Configuration {
    construct new(map) {
        _fullName       = map["fullName"]
        _favouriteFruit = map["favouriteFruit"]
        _needsPeeling   = map["needsPeeling"]
        _seedsRemoved   = map["seedsRemoved"]
        _otherFamily    = map["otherFamily"]
    }

    toString {
        return [
            "Full name       = %(_fullName)",
            "Favourite fruit = %(_favouriteFruit)",
            "Needs peeling   = %(_needsPeeling)",
            "Seeds removed   = %(_seedsRemoved)",
            "Other family    = %(_otherFamily)"
        ].join("\n")
    }
}

var commentedOut = Fn.new { |line| line.startsWith("#") || line.startsWith(";") }

var toMapEntry = Fn.new { |line|
    var ix = line.indexOf(" ")
    if (ix == -1) return MapEntry.new(line, "")
    return MapEntry.new(line[0...ix], line[ix+1..-1])
}

var fileName = "configuration.txt"
var lines = File.read(fileName).trimEnd().split(FileUtil.lineBreak)
var mapEntries = lines.map   { |line| line.trim() }.
                       where { |line| line != "" }.
                       where { |line| !commentedOut.call(line) }.
                       map   { |line| toMapEntry.call(line) }
var configurationMap = { "needsPeeling": false, "seedsRemoved": false }
for (me in mapEntries) {
    if (me.key == "FULLNAME") {
        configurationMap["fullName"] = me.value
    } else if (me.key == "FAVOURITEFRUIT") {
        configurationMap["favouriteFruit"] = me.value
    } else if (me.key == "NEEDSPEELING") {
        configurationMap["needsPeeling"] = true
    } else if (me.key == "OTHERFAMILY") {
        configurationMap["otherFamily"] = me.value.split(" , ").map { |s| s.trim() }.toList
    } else if (me.key == "SEEDSREMOVED") {
        configurationMap["seedsRemoved"] = true
    } else {
        System.print("Encountered unexpected key %(me.key)=%(me.value)")
    }
}
System.print(Configuration.new(configurationMap))
Output:
Full name       = Foo Barber
Favourite fruit = banana
Needs peeling   = true
Seeds removed   = false
Other family    = [Rhu Barber, Harry Barber]

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

zkl

First, a simple parser that knows nothing about contents, just format.

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"));

Which may be good enough; query the hash table to get a set option or a default if it wasn't set:

foreach k,v in (conf){ println(k," : ",v) }
println("Value of seedsremoved = ",conf.find("seedsremoved",False));
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".

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);
Output:
favouritefruit :  banana
fullname :  Foo Barber
needspeeling : True
otherfamily : L("Rhu Barber","Harry Barber")
seedsremoved : Void
Value of seedsremoved = Void