Read a configuration file

From Rosetta Code
Jump to: navigation, search
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, 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

See also:

Contents

[edit] Ada

Works with: Ada version 2005

Uses package config_file_parser described in Ada in Denmak Wiki http://wiki.ada-dk.org/simple_configuration_file_reader_-_using_a_generic

with Ada.Strings.Unbounded;
with Config_File_Parser;
pragma Elaborate_All (Config_File_Parser);
 
package Config is
 
function TUS (S : String) return Ada.Strings.Unbounded.Unbounded_String
renames Ada.Strings.Unbounded.To_Unbounded_String;
-- Convenience rename. TUS is much shorter than To_Unbounded_String.
 
type Keys is (
FULLNAME,
FAVOURITEFRUIT,
NEEDSPEELING,
SEEDSREMOVED,
OTHERFAMILY);
-- These are the valid configuration keys.
 
type Defaults_Array is
array (Keys) of Ada.Strings.Unbounded.Unbounded_String;
-- The array type we'll use to hold our default configuration settings.
 
Defaults_Conf : Defaults_Array :=
(FULLNAME => TUS ("John Doe"),
FAVOURITEFRUIT => TUS ("blackberry"),
NEEDSPEELING => TUS ("False"),
SEEDSREMOVED => TUS ("False"),
OTHERFAMILY => TUS ("Daniel Defoe, Ada Byron"));
-- Default values for the Program object. These can be overwritten by
-- the contents of the rosetta.cfg file(see below).
 
package Rosetta_Config is new Config_File_Parser (
Keys => Keys,
Defaults_Array => Defaults_Array,
Defaults => Defaults_Conf,
Config_File => "rosetta.cfg");
-- Instantiate the Config configuration object.
 
end Config;

Main program :

 
with Ada.Text_IO;
with Config; use Config;
 
procedure Read_Config is
use Ada.Text_IO;
use Rosetta_Config;
 
begin
New_Line;
Put_Line ("Reading Configuration File.");
Put_Line ("Fullname  := " & Get (Key => FULLNAME));
Put_Line ("Favorite Fruit := " & Get (Key => FAVOURITEFRUIT));
Put_Line ("Other Family  := " & Get (Key => OTHERFAMILY));
if Has_Value (Key => NEEDSPEELING) then
Put_Line ("NEEDSPEELLING  := " & Get (Key => NEEDSPEELING));
else
Put_Line ("NEEDSPEELLING  := True");
end if;
if Has_Value (Key => SEEDSREMOVED) then
Put_Line ("SEEDSREMOVED  := " & Get (Key => SEEDSREMOVED));
else
Put_Line ("SEEDSREMOVED  := True");
end if;
end Read_Config;

output

Reading Configuration File.
Fullname       := Foo Barber
Favorite Fruit := banana
Other Family   := Rhu Barber, Harry Barber
NEEDSPEELLING  := True
SEEDSREMOVED   := False

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


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

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

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

[edit] D

import std.stdio, std.string, std.conv, std.regex, std.getopt;
 
enum VarName(alias var) = var.stringof.toUpper;
 
void setOpt(alias Var)(in string line) {
auto m = match(line, regex(`^` ~ VarName!Var ~ `(\s+(.*))?`));
if (!m.empty) {
static if (is(typeof(Var) == string))
Var = m.captures.length > 2 ? m.captures[2] : "";
static if (is(typeof(Var) == bool))
Var = true;
static if (is(typeof(Var) == int))
Var = m.captures.length > 2 ? to!int(m.captures[2]) : 0;
}
}
 
void main(in string[] args) {
string fullName, favouriteFruit, otherFamily;
bool needsPeeling, seedsRemoved; // Default false.
auto f = "readcfg.txt".File;
 
foreach (line; f.byLine) {
auto opt = line.strip.idup;
setOpt!fullName(opt);
setOpt!favouriteFruit(opt);
setOpt!needsPeeling(opt);
setOpt!seedsRemoved(opt);
setOpt!otherFamily(opt);
}
 
writefln("%14s = %s", VarName!fullName, fullName);
writefln("%14s = %s", VarName!favouriteFruit, favouriteFruit);
writefln("%14s = %s", VarName!needsPeeling, needsPeeling);
writefln("%14s = %s", VarName!seedsRemoved, seedsRemoved);
writefln("%14s = %s", VarName!otherFamily, otherFamily);
}
Output:
     FULLNAME = Foo Barber
AVOURITEFRUIT = banana
 NEEDSPEELING = true
 SEEDSREMOVED = false
  OTHERFAMILY = Rhu Barber, Harry Barber

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

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

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

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

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

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

[edit] 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==http://rosettacode.org/mw/skins/common/images/button_nowiki.png

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

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

[edit] 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"
]
}
 

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

[edit] Kotlin

Works with: Kotlin version 6.2

This example is more verbose than it has to because of increased effort in providing immutability to the configuration class. Hopefully an adequate demonstration of Kotlin's features.

data class Configuration(val map: Map<String, Any?>) {
val fullName: String by Delegates.mapVal(map)
val favoriteFruit: String by Delegates.mapVal(map)
val needsPeeling: Boolean by Delegates.mapVal(map)
val otherFamily: List<String> by Delegates.mapVal(map)
}
 
fun main(args: Array<String>) {
val configurationPath = Paths.get(args[0])!!
 
val configurables = Files.readAllLines(configurationPath, StandardCharsets.UTF_8)
.map { it.trim() }
.filterNot { it.isEmpty() }
.filterNot(::commentedOut)
.map(::toKeyValuePairs)
 
val configurationMap: MutableMap<String, Any?> = hashMapOf("needsPeeling" to false)
for (configurable in configurables) {
val (key, value) = configurable
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}")
}
}
 
val configuration = Configuration(configurationMap)
}
 
private fun commentedOut(line: String): Boolean = (line.indexOf("#") == 0 || line.indexOf(";") == 0)
 
private fun toKeyValuePairs(line: String): Pair<String, String> {
return line.split(" ", 2).let {
Pair(it[0], if (it.size == 1) "" else it[1])
}
}

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

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

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

[edit] Mathematica

 
ClearAll[CreateVar, ImportConfig];
CreateVar[x_, y_String: "True"] := Module[{},
If[StringFreeQ[y, ","]
,
ToExpression[x <> "=" <> y]
,
ToExpression[x <> "={" <> StringJoin@Riffle[StringSplit[y, ","], ","] <> "}"]
]
]
ImportConfig[configfile_String] := Module[{data},
(*data = ImportString[configfile, "List", "Numeric" -> False];*)
data=Import[configfile,"List","Numeric"\[Rule]False];
 
data = StringTrim /@ data;
data = Select[data, # =!= "" &];
data = Select[data, ! StringMatchQ[#, "#" | ";" ~~ ___] &];
data = If[! StringFreeQ[#, " "], StringSplit[#, " ", 2], {#}] & /@ data;
 
CreateVar @@@ data;
]
ImportConfig[file]
 

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

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

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


It produces the following 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

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

[edit] 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)";
}
}
}
 

[edit] Perl 6

This demonstrates several interesting features of Perl 6, including full grammar support, derived grammars, alternation split across derivations, and longest-token matching that works across derivations. It also shows off Perl 6'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>[0].trim } }
token line:sym<favouritefruit> {:i favouritefruit» <rest> { $favouritefruit = $<rest>[0].trim } }
token line:sym<needspeeling> {:i needspeeling» <yes> { $needspeeling = defined $<yes>[0] } }
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» <many> { @otherfamily = $<many>[0]».trim} }
token many { \h*'='? ([ <![,]> \N ]*) ** ',' }
}
 
MyConfFile.parsefile('file.cfg');
 
.perl.say for
:$fullname,
:$favouritefruit,
:$needspeeling,
:$seedsremoved,
:@otherfamily;

Output:

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

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

[edit] 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';

[edit] Protium

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

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

[edit] 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")

[edit] 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 to read a config file and assign VARs as found within.   */
signal on syntax; signal on novalue /*handle REXX program errors. */
parse arg cFID _ . /*cFID = config file to be read. */
if cFID=='' then cFID='CONFIG.DAT' /*Not specified? Use the default*/
bad= /*this will contain all bad VARs.*/
varList= /*this will contain all the VARs.*/
maxLenV=0; blanks=0; hashes=0; semics=0; badVar=0 /*zero 'em.*/
 
do j=0 while lines(cFID)\==0 /*J count's the file's lines. */
txt=strip(linein(cFID)) /*read a line (record) from file,*/
/*& strip leading/trailing blanks*/
if txt ='' then do; blanks=blanks+1; iterate; end
if left(txt,1)=='#' then do; hashes=hashes+1; iterate; end
if left(txt,1)==';' then do; semics=semics+1; iterate; end
eqS=pos('=',txt) /*can't use the TRANSLATE bif. */
if eqS\==0 then txt=overlay(' ',txt,eqS) /*replace 1st '=' with blank*/
parse var txt xxx value; upper xxx /*get the variableName and value.*/
value=strip(value) /*strip leading & trailing blanks*/
if value='' then value='true' /*if no value, then use "true". */
if symbol(xxx)=='BAD' then do /*can REXX use the variable name?*/
badVar=badVar+1; bad=bad xxx; iterate
end
varList=varList xxx /*add it to the list of variables*/
call value xxx,value /*now, use VALUE to set the var. */
maxLenV=max(maxLenV,length(value)) /*maxLen of varNames, pretty disp*/
end /*j*/
 
vars=words(varList)
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) "ignored that began with a # (hash)."
if semics\==0 then say #(semics) 'record's(semics) "ignored that began with a ; (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*/
exit /*stick a fork in it, we're done.*/
/*───────────────────────────────error handling subroutines and others.─*/
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 +indent*/
err: say; say; say center(' error! ', max(40, linesize()%2), "*"); say
do j=1 for arg(); say arg(j); say; end; say; exit 13
novalue: syntax: call err 'REXX program' condition('C') "error",,
condition('D'),'REXX source statement (line' sigl"):",sourceline(sigl)

This REXX program makes use of   LINESIZE   REXX program (or BIF) which is used to determine the screen width (or linesize) of the terminal (console).
The   LINESIZE.REX   REXX program is included here ──► LINESIZE.REX.

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

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


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

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

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

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

[edit] 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)"

[edit] 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 };

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

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

[edit] zkl

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

fcn readConfigFile(config){  //--> read only dictionary
conf:=D();
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
Personal tools
Namespaces

Variants
Actions
Community
Explore
Misc
Toolbox