Policy change. Edit privileges will now require email addresses to be confirmed. (Details) --Michael Mol 14:53, 14 March 2012 (UTC)

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

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

[edit] D

import std.stdio, std.getopt, std.string, std.conv, std.regexp ;
 
template VarName(alias Var) { enum VarName = Var.stringof.toupper ; }
 
void setOpt(alias Var)(const string line) {
auto m = RegExp(`^`~VarName!Var~`(\s+(.*))?`).match(line) ;
if(m.length > 0) {
static if (is(typeof(Var) == string))
Var = m.length > 2 ? m[2] : "" ;
static if (is(typeof(Var) == bool))
Var = true ;
static if (is(typeof(Var) == int))
Var = m.length > 2 ? to!int(m[2]) : 0 ;
}
}
 
void main(string[] args) {
string fullname, favouritefruit ;
bool needspeeling, seedsremoved ; // default false ;
int count ; // a line of "COUNT 5" added at end of config file
foreach(line ; File("readcfg.txt").byLine) {
auto opt = chomp(text(line)) ;
setOpt!fullname(opt) ;
setOpt!favouritefruit(opt) ;
setOpt!needspeeling(opt) ;
setOpt!seedsremoved(opt) ;
setOpt!count(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!count, count) ;
}

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

'read' supports only a single comment character. Therefore, we use a pipe to filter the comments.

(de rdConf (File)
(pipe (in File (while (echo "#" ";") (till "^J")))
(while (read)
(set @ (or (line T) T)) ) ) )

Test:

(off FULLNAME FAVOURITEFRUIT NEEDSPEELING SEEDSREMOVED OTHERFAMILY)
(rdConf "conf.txt")

Output:

: (list FULLNAME FAVOURITEFRUIT NEEDSPEELING SEEDSREMOVED OTHERFAMILY)
-> ("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] 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] REXX

No assumptions where made about what variables are (or aren't) in the configuration file.
Code was written to make the post-mordem 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 cfgFile _ . /*F = input file to be read. */
if cfgFile=='' then cfgFile='CONFIG.DAT' /*not specified? Use default*/
bad=
varList= /*this will contain all the VARs.*/
maxLenV=0
blanks=0
hashes=0
semics=0
badVar=0
 
do j=0 while lines(cfgFile)\==0 /*J count's the file's lines. */
txt=strip(linein(cfgFile)) /*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 /*get the variableName and value.*/
value=strip(value) /*strip leading & trailing blanks*/
upper xxx /*uppercase the variable name. */
varList=varList xxx /*add it to the list of vARiables*/
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
call value xxx,value /*now, use VALUE to set the var. */
maxLenV=max(maxLenV,length(value)) /*maxLen of varNames, pretty disp*/
end
 
vars=words(varList)
say #(j) 'record's(j) "were read from file: " cfgFile
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 j=1 for vars
v=word(varList,j)
say right(v,maxLenV) '=' value(v)
end
 
say copies('=',60)
exit
 
/*───────────────────────────────error handling subroutines and others.─*/
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)
 
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*/
 

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
 
IO.foreach("fruit.conf") 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 /^./; puts "#{key}: unknown key"
end
end
 
puts "fullname = #{fullname}"
puts "favouritefruit = #{favouritefruit}"
puts "needspeeling = #{needspeeling}"
puts "seedsremoved = #{seedsremoved}"

[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] 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
Personal tools
Namespaces
Variants
Actions
Community/News
Browse wiki
Misc
Toolbox