Read a configuration file: Difference between revisions
m (→{{header|REXX}}: removed superflous blanks. -- ~~~~) |
|||
Line 1,180: | Line 1,180: | ||
No assumptions where made about what variables are (or aren't) in the configuration file. |
No assumptions where made about what variables are (or aren't) in the configuration file. |
||
<br>Code was written to make the post-mordem report as readable as possible. |
<br>Code was written to make the post-mordem report as readable as possible. |
||
⚫ | |||
<lang rexx> |
|||
⚫ | |||
signal on syntax; signal on novalue /*handle REXX program errors. */ |
signal on syntax; signal on novalue /*handle REXX program errors. */ |
||
parse arg cfgFile _ . /*F = input file to be read. */ |
parse arg cfgFile _ . /*F = input file to be read. */ |
||
Line 1,231: | Line 1,229: | ||
say copies('=',60) |
say copies('=',60) |
||
exit |
exit |
||
/*───────────────────────────────error handling subroutines and others.─*/ |
/*───────────────────────────────error handling subroutines and others.─*/ |
||
err: say; say; say center(' error! ',max(40,linesize()%2),"*"); say |
err: say; say; say center(' error! ',max(40,linesize()%2),"*"); say |
||
Line 1,241: | Line 1,238: | ||
s: if arg(1)==1 then return arg(3);return word(arg(2) 's',1) |
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*/ |
#: return right(arg(1),length(j)+11) /*right justify a number +indent*/</lang> |
||
</lang> |
|||
Output when using the input (file) as specified in the task description: |
Output when using the input (file) as specified in the task description: |
||
<pre style="height:35ex;overflow:scroll"> |
<pre style="height:35ex;overflow:scroll"> |
Revision as of 20:11, 26 May 2012
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
Ada
Uses package config_file_parser described in Ada in Denmak Wiki http://wiki.ada-dk.org/simple_configuration_file_reader_-_using_a_generic <lang Ada>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;</lang> Main program : <lang Ada> 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;</lang>
output
Reading Configuration File. Fullname := Foo Barber Favorite Fruit := banana Other Family := Rhu Barber, Harry Barber NEEDSPEELLING := True SEEDSREMOVED := False
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++
unoptimized
<lang cpp>#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; } </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[key.size..-1].trim if (remainder.startsWith("=")) { remainder = remainder.replace("=", "").trim } return remainder }
Void main () { // define the variables which need configuring fullname := "" favouritefruit := "" needspeeling := false seedsremoved := false Str[] otherfamily := [,]
// loop through the file, setting variables as needed File(`config.dat`).eachLine |Str line| { line = line.trim if (line.isEmpty || line.startsWith("#") || line.startsWith(";")) { // do nothing for empty and comment lines } else if (line.upper.startsWith("FULLNAME")) { fullname = removeKey("FULLNAME", line) } else if (line.upper.startsWith("FAVOURITEFRUIT")) { favouritefruit = removeKey("FAVOURITEFRUIT", line) } else if (line.upper.startsWith("NEEDSPEELING")) { needspeeling = true } else if (line.upper.startsWith("SEEDSREMOVED")) { seedsremoved = true } else if (line.upper.startsWith("OTHERFAMILY")) { otherfamily = removeKey("OTHERFAMILY", line).split(',') } }
// report results echo ("Full name is $fullname") echo ("Favourite fruit is $favouritefruit") echo ("Needs peeling is $needspeeling") echo ("Seeds removed is $seedsremoved") echo ("Other family is " + otherfamily.join(", ")) }
} </lang>
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>
Or, use Data.Configfile:
<lang haskell> import Data.ConfigFile import Data.Either.Utils
getSetting cp x = forceEither $ get cp "Default" x
cp <- return . forceEither =<< readfile emptyCP "name_of_configuration_file" let username = getSetting cp "username"
password = getSetting cp "password"
</lang> This works with configuration files in standard format, i.e.,
# this is a comment username = myname
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>
MATLAB / Octave
This is defined as a function, parameters are returned as part of a struct. When the first line, and the assignment to return values are removed, it is a script that stores the parameters in the local workspace.
<lang MATLAB>function R = readconf(configfile) % READCONF reads configuration file. % % The value of boolean parameters can be tested with % exist(parameter,'var')
if nargin<1,
configfile = 'q.conf';
end;
fid = fopen(configfile); if fid<0, error('cannot open file %s\n',a); end;
while ~feof(fid)
line = strtrim(fgetl(fid)); if isempty(line) || all(isspace(line)) || strncmp(line,'#',1) || strncmp(line,';',1),
; % no operation
else
[var,tok] = strtok(line,' \t='); var = upper(var); if any(tok==','), k = 1; while (1) [val, tok]=strtok(tok,','); R.(var){k} = strtrim(val); % return value of function eval(sprintf('%s{%i}=%s;',var,k,strtrim(val))); % stores variable in local workspace if isempty(tok), break; end; k=k+1; end; else tok = strtrim(tok); R.(var) = tok; % return value of function eval(sprintf('%s=%s; ',var,tok)); % stores variable in local workspace end;
end;
end; fclose(fid); whos, % shows the parameter in the local workspace
</lang>
R=readconf('file.conf') Variables in the current scope: Attr Name Size Bytes Class ==== ==== ==== ===== ===== FAVOURITEFRUIT 1x6 6 char FULLNAME 1x10 10 char NEEDSPEELING 0x0 0 char OTHERFAMILY 1x2 22 cell f R 1x1 38 struct f configfile 1x6 6 char fid 1x1 8 double k 1x1 8 double line 0x0 0 char tok 0x0 0 char val 1x13 13 char var 1x11 11 char Total is 51 elements using 122 bytes R = scalar structure containing the fields: FULLNAME = Foo Barber FAVOURITEFRUIT = banana NEEDSPEELING = OTHERFAMILY = { [1,1] = Rhu Barber [1,2] = Harry Barber }
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;
- 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)"; } }
}
</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>
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.
<lang rexx>/*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*/</lang>
Output when using the input (file) as specified in the task description:
28 records were read from file: CONFIG.DAT 8 blank records were read. 15 records ignored that began with a # (hash). 1 record ignored that began with a ; (semicolon). The list of 4 variables and their values follows: FULLNAME = Foo Barber FAVOURITEFRUIT = banana NEEDSPEELING = true OTHERFAMILY = Rhu Barber, Harry Barber ============================================================
Ruby
<lang ruby>fullname = favouritefruit = needspeeling = seedsremoved = false
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>
Seed7
The Seed7 library scanfile.s7i, can be used to scan a file with functions like getWord, skipSpace and getLine.
<lang seed7>$ include "seed7_05.s7i";
include "scanfile.s7i";
var string: fullname is ""; var string: favouritefruit is ""; var boolean: needspeeling is FALSE; var boolean: seedsremoved is FALSE; var array string: otherfamily is 0 times "";
const proc: main is func
local var file: configFile is STD_NULL; var string: symbol is ""; var integer: index is 0; begin configFile := open("readcfg.txt", "r"); configFile.bufferChar := getc(configFile); symbol := lower(getWord(configFile)); while symbol <> "" do skipSpace(configFile); if symbol = "#" or symbol = ";" then skipLine(configFile); elsif symbol = "fullname" then fullname := getLine(configFile); elsif symbol = "favouritefruit" then favouritefruit := getLine(configFile); elsif symbol = "needspeeling" then needspeeling := TRUE; elsif symbol = "seedsremoved" then seedsremoved := TRUE; elsif symbol = "otherfamily" then otherfamily := split(getLine(configFile), ","); for key index range otherfamily do otherfamily[index] := trim(otherfamily[index]); end for; else writeln(" *** Illegal line " <& literal(getLine(configFile))); end if; symbol := lower(getWord(configFile)); end while; close(configFile); writeln("fullname: " <& fullname); writeln("favouritefruit: " <& favouritefruit); writeln("needspeeling: " <& needspeeling); writeln("seedsremoved: " <& seedsremoved); for key index range otherfamily do writeln(("otherfamily[" <& index <& "]:") rpad 16 <& otherfamily[index]); end for; end func;</lang>
Output:
fullname: Foo Barber favouritefruit: banana needspeeling: TRUE seedsremoved: FALSE otherfamily[1]: Rhu Barber otherfamily[2]: Harry Barber
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" }
}
}
- 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)
- @/.*/
@ (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
- 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>