Read a configuration file: Difference between revisions

From Rosetta Code
Content added Content deleted
No edit summary
(added Fantom example)
Line 244: Line 244:
writefln("%14s = %s", VarName!count, count) ;
writefln("%14s = %s", VarName!count, count) ;
}</lang>
}</lang>

=={{header|Fantom}}==

<lang fantom>
class Main
{
// remove the given key and an optional '=' from start of line
Str removeKey (Str key, Str line)
{
remainder := line.replace(key, "").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.startsWith("FULLNAME"))
{
fullname = removeKey("FULLNAME", line)
}
else if (line.startsWith("FAVOURITEFRUIT"))
{
favouritefruit = removeKey("FAVOURITEFRUIT", line)
}
else if (line.startsWith("NEEDSPEELING"))
{
needspeeling = true
}
else if (line.startsWith("SEEDSREMOVED"))
{
seedsremoved = true
}
else if (line.startsWith("OTHERFAMILY"))
{
otherfamily = removeKey("OTHERFAMILY", line).split(',')
}
}

// report results
echo ("Full name is $fullname")
echo ("Favourite fruit is $favouritefruit")
echo ("Needs peeling is $needspeeling")
echo ("Seeds removed is $seedsremoved")
echo ("Other family is " + otherfamily.join(", "))
}
}
</lang>


=={{header|Go}}==
=={{header|Go}}==

Revision as of 18:38, 25 January 2012

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


AutoHotkey

<lang AutoHotkey>

Author
AlephX, Aug 18 2011

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

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

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

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

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


C++

Library: Boost
Works with: Visual Studio version 2005

unoptimized

<lang cpp>#include "stdafx.h"

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

using namespace std; using namespace boost;

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

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

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

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

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

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

return 0; } </lang>

output

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

D

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

}</lang>

Fantom

<lang fantom> class Main {

 // remove the given key and an optional '=' from start of line
 Str removeKey (Str key, Str line)
 {
   remainder := line.replace(key, "").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.startsWith("FULLNAME"))
     {
       fullname = removeKey("FULLNAME", line)
     }
     else if (line.startsWith("FAVOURITEFRUIT"))
     {
       favouritefruit = removeKey("FAVOURITEFRUIT", line)
     }
     else if (line.startsWith("NEEDSPEELING"))
     {
       needspeeling = true
     }
     else if (line.startsWith("SEEDSREMOVED"))
     {
       seedsremoved = true
     }
     else if (line.startsWith("OTHERFAMILY"))
     {
       otherfamily = removeKey("OTHERFAMILY", line).split(',')
     }
   }
   // report results
   echo ("Full name is $fullname")
   echo ("Favourite fruit is $favouritefruit")
   echo ("Needs peeling is $needspeeling")
   echo ("Seeds removed is $seedsremoved")
   echo ("Other family is " + otherfamily.join(", "))
 }

} </lang>

Go

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

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

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

type varError struct { err error n string t VarType }

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

type VarType int

const ( Bool VarType = 1 + iota Array String )

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

panic("Unknown VarType") }

type confvar struct { Type VarType Val interface{} }

type Config struct { m map[string]confvar }

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

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

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

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

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

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

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

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

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

return }

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

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

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

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

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

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

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

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

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

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

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

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

return v, nil }</lang>

Usage example: <lang go>package main

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

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

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

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

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

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

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

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

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

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

Haskell

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

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

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

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

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

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

data Config = Config

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

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

J

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

cfgString=:4 :0

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

)

cfgBoolean=:4 :0

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

)

taskCfg=:3 :0

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

)</lang>

Example use:

<lang j> taskCfg 'fruit.conf'

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

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

Liberty BASIC

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

optionCount = ParseConfiguration("a.txt")

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

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

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

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

wend end

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

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

end function

function ParseKey$(s$)

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

end function


function GetOption$( key$, optionCount)

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

end function


function HasOption(key$, optionCount)

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

end function

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

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

end function </lang>

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

Lua

<lang lua>conf = {}

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

for line in fp:lines() do

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

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

if not value then

	    conf[option] = true

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

   end

end

fp:close()


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

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

print( "", entry )

   end

end</lang>

Perl

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

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

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

my $conf_definition = {

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

};

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

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

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

read_conf_file($file, $conf_definition);

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

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

}

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

sub read_conf_file {

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

}

</lang>

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. <lang perl6>my $fullname; my $favouritefruit; my $needspeeling = False; my $seedsremoved = False; my @otherfamily;

grammar ConfFile {

   token TOP {

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

   }
   token fullline {

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

   }
   proto token line() 
   token line:misc  { {} (\S+) { die "Unrecognized word: $0" } }
   token line:sym<comment> { ^^ [ ';' | '#' ] \N* }
   token line:sym<blank>   { ^^ \h* $$ }
   token line:sym<fullname>       {:i fullname»       <rest> { $fullname = $<rest>[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;</lang>

Output:

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

PicoLisp

'read' supports only a single comment character. Therefore, we use a pipe to filter the comments. <lang PicoLisp>(de rdConf (File)

  (pipe (in File (while (echo "#" ";") (till "^J")))
     (while (read)
        (set @ (or (line T) T)) ) ) )</lang>

Test: <lang PicoLisp>(off FULLNAME FAVOURITEFRUIT NEEDSPEELING SEEDSREMOVED OTHERFAMILY) (rdConf "conf.txt")</lang> Output:

: (list FULLNAME FAVOURITEFRUIT NEEDSPEELING SEEDSREMOVED OTHERFAMILY)
-> ("Foo Barber" "banana" T NIL "Rhu Barber, Harry Barber")

PL/I

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

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

variables:

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

end set; </lang> Output, using the given text file as input:-

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

Python

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

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


if __name__ == '__main__':

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


Ruby

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

Tcl

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

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

set cfg($var) $defaultValue

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

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

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

   }

}

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

readConfig "fruit.cfg" {

   needspeeling false
   seedsremoved false

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

TXR

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

  1. @/.*/

@ (or)

@/.*/

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

Sample run:

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

Vedit macro language

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

  1. 12 = 0 // seedsremoved = FALSE

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

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

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

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

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

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

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

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

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

   #11 = 1

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

   #12 = 1

}

Buf_Quit(OK) // close .cfg file

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

Output:

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

Visual Basic

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

Sub readini()

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

readinilabela:

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

readinilabelb:

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

readinierror:

End Sub

Sub openfileread()

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

openfilereaderror:

 var.errorcode = Err
 Call errorhandler
 resume '!!

End Sub

Public Sub checkfileexists()

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

checkfileexistserror:

 var.errorcode = Err
 Call errorhandler

End Sub

Public Sub getfreestream()

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

getfreestreamerror:

 var.errorcode = Err
 Call errorhandler
 resume '!!

End Sub

Sub closestream()

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

closestreamerror:

 var.errorcode = Err
 Call errorhandler
 resume next

End Sub

Sub readlinefromfile()

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

readlinefromfileerror:

 var.errorcode = Err
 Call errorhandler
 resume '!!

End Sub

Public Sub errorhandler()

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

' Resume Next

 Else
   flg.error = "N"

' Resume

 End If

End Sub

Public Function btrim$(arg$)

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

End Function</lang>