all tasks
This commit is contained in:
parent
b83f433714
commit
68f8f3e56b
14735 changed files with 178959 additions and 0 deletions
52
Task/Update-a-configuration-file/0DESCRIPTION
Normal file
52
Task/Update-a-configuration-file/0DESCRIPTION
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
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.
|
||||
|
||||
# The first word on each non comment line is the configuration option.
|
||||
# Remaining words or numbers on the line are configuration parameter
|
||||
# data fields.
|
||||
|
||||
# Note that configuration option names are not case sensitive. However,
|
||||
# configuration parameter data is case sensitive and the lettercase must
|
||||
# be preserved.
|
||||
|
||||
# This is a favourite fruit
|
||||
FAVOURITEFRUIT banana
|
||||
|
||||
# This is a boolean that should be set
|
||||
NEEDSPEELING
|
||||
|
||||
# This boolean is commented out
|
||||
; SEEDSREMOVED
|
||||
|
||||
# How many bananas we have
|
||||
NUMBEROFBANANAS 48
|
||||
|
||||
The task is to manipulate the configuration file as follows:
|
||||
|
||||
* Disable the needspeeling option (using a semicolon prefix)
|
||||
* Enable the seedsremoved option by removing the semicolon and any leading whitespace
|
||||
* Change the numberofbananas parameter to 1024
|
||||
* Enable (or create if it does not exist in the file) a parameter for numberofstrawberries with a value of 62000
|
||||
|
||||
Note that configuration option names are not case sensitive. This means that changes should be effected, regardless of the case.
|
||||
|
||||
Options should always be disabled by prefixing them with a semicolon.
|
||||
|
||||
Lines beginning with hash symbols should not be manipulated and left unchanged in the revised file.
|
||||
|
||||
If a configuration option does not exist within the file (in either enabled or disabled form), it should be added during this update. Duplicate configuration option names in the file should be removed, leaving just the first entry.
|
||||
|
||||
For the purpose of this task, the revised file should contain appropriate entries, whether enabled or not for needspeeling,seedsremoved,numberofbananas and numberofstrawberries.)
|
||||
|
||||
The update should rewrite configuration option names in capital letters. However lines beginning with hashes and any parameter data must not be altered (eg the banana for favourite fruit must not become capitalized). The update process should also replace double semicolon prefixes with just a single semicolon (unless it is uncommenting the option, in which case it should remove all leading semicolons).
|
||||
|
||||
Any lines beginning with a semicolon or groups of semicolons, but no following option should be removed, as should any leading or trailing whitespace on the lines. Whitespace between the option and paramters should consist only of a single
|
||||
space, and any non ascii extended characters, tabs characters, or control codes
|
||||
(other than end of line markers), should also be removed.
|
||||
|
||||
'''See also:'''
|
||||
* [[Read a configuration file]]
|
||||
2
Task/Update-a-configuration-file/1META.yaml
Normal file
2
Task/Update-a-configuration-file/1META.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
note: File handling
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
; Author: AlephX, Aug 17 2011
|
||||
data = %A_scriptdir%\rosettaconfig.txt
|
||||
outdata = %A_scriptdir%\rosettaconfig.tmp
|
||||
FileDelete, %outdata%
|
||||
|
||||
NUMBEROFBANANAS := 1024
|
||||
numberofstrawberries := 560
|
||||
NEEDSPEELING = "0"
|
||||
FAVOURITEFRUIT := "bananas"
|
||||
SEEDSREMOVED = "1"
|
||||
BOOL0 = "0"
|
||||
BOOL1 = "1"
|
||||
NUMBER1 := 1
|
||||
number0 := 0
|
||||
STRINGA := "string here"
|
||||
|
||||
parameters = bool0|bool1|NUMBER1|number0|stringa|NEEDSPEELING|seedsremoved|numberofbananas|numberofstrawberries
|
||||
|
||||
Loop, Read, %data%, %outdata%
|
||||
{
|
||||
if (instr(A_LoopReadLine, "#") == 1 OR A_LoopReadLine == "")
|
||||
{
|
||||
Line := A_LoopReadLine
|
||||
}
|
||||
else
|
||||
{
|
||||
if instr(A_LoopReadLine, ";") == 1
|
||||
{
|
||||
parameter := RegExReplace(Substr(A_LoopReadLine,2), "^[ \s]+|[ \s]+$", "")
|
||||
|
||||
parametervalue = %parameter%
|
||||
value := %parametervalue%
|
||||
if value == 0
|
||||
Line := A_loopReadLine
|
||||
else
|
||||
Line := Parameter
|
||||
}
|
||||
else
|
||||
{
|
||||
parameter := RegExReplace(A_LoopReadLine, "^[ \s]+|[ \s]+$", "")
|
||||
if instr(parameter, A_Space)
|
||||
parameter := substr(parameter, 1, instr(parameter, A_Space)-1)
|
||||
|
||||
if instr(parameters, parameter) > 0
|
||||
{
|
||||
parametervalue = %parameter%
|
||||
value := %parametervalue%
|
||||
|
||||
if (value = chr(34) . "0" . chr(34))
|
||||
Line := "; " . Parameter
|
||||
else
|
||||
{
|
||||
if (value = chr(34) . "1" . chr(34))
|
||||
Line := Parameter
|
||||
else
|
||||
Line = %parametervalue% %value%
|
||||
}
|
||||
}
|
||||
else
|
||||
Line := A_LoopReadLine
|
||||
}
|
||||
|
||||
}
|
||||
StringReplace, parameters, parameters, %parametervalue%,,
|
||||
StringReplace, parameters, parameters,||,|
|
||||
|
||||
FileAppend, %Line%`n
|
||||
}
|
||||
|
||||
Loop, parse, parameters,|
|
||||
{
|
||||
if (A_Loopfield <> "")
|
||||
{
|
||||
StringUpper, parameter, A_LoopField
|
||||
parametervalue = %parameter%
|
||||
value := %parametervalue%
|
||||
|
||||
if (value = chr(34) . "0" . chr(34))
|
||||
Line := "; " . parameter
|
||||
else
|
||||
{
|
||||
if (value = chr(34) . "1" . chr(34))
|
||||
Line := parameter
|
||||
else
|
||||
Line = %parametervalue% %value%
|
||||
}
|
||||
|
||||
FileAppend, %Line%`n, %outdata%
|
||||
}
|
||||
}
|
||||
|
||||
FileCopy, %A_scriptdir%\rosettaconfig.tmp, %A_scriptdir%\rosettaconfig.txt, 1
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define strcomp(X, Y) strcasecmp(X, Y)
|
||||
|
||||
struct option
|
||||
{ const char *name, *value;
|
||||
int flag; };
|
||||
|
||||
/* TODO: dynamically obtain these */
|
||||
struct option updlist[] =
|
||||
{ { "NEEDSPEELING", NULL },
|
||||
{ "SEEDSREMOVED", "" },
|
||||
{ "NUMBEROFBANANAS", "1024" },
|
||||
{ "NUMBEROFSTRAWBERRIES", "62000" },
|
||||
{ NULL, NULL } };
|
||||
|
||||
int output_opt(FILE *to, struct option *opt)
|
||||
{ if (opt->value == NULL)
|
||||
return fprintf(to, "; %s\n", opt->name);
|
||||
else if (opt->value[0] == 0)
|
||||
return fprintf(to, "%s\n", opt->name);
|
||||
else
|
||||
return fprintf(to, "%s %s\n", opt->name, opt->value); }
|
||||
|
||||
int update(FILE *from, FILE *to, struct option *updlist)
|
||||
{ char line_buf[256], opt_name[128];
|
||||
int i;
|
||||
for (;;)
|
||||
{ size_t len, space_span, span_to_hash;
|
||||
if (fgets(line_buf, sizeof line_buf, from) == NULL)
|
||||
break;
|
||||
len = strlen(line_buf);
|
||||
space_span = strspn(line_buf, "\t ");
|
||||
span_to_hash = strcspn(line_buf, "#");
|
||||
if (space_span == span_to_hash)
|
||||
goto line_out;
|
||||
if (space_span == len)
|
||||
goto line_out;
|
||||
if ((sscanf(line_buf, "; %127s", opt_name) == 1) ||
|
||||
(sscanf(line_buf, "%127s", opt_name) == 1))
|
||||
{ int flag = 0;
|
||||
for (i = 0; updlist[i].name; i++)
|
||||
{ if (strcomp(updlist[i].name, opt_name) == 0)
|
||||
{ if (output_opt(to, &updlist[i]) < 0)
|
||||
return -1;
|
||||
updlist[i].flag = 1;
|
||||
flag = 1; } }
|
||||
if (flag == 0)
|
||||
goto line_out; }
|
||||
else
|
||||
line_out:
|
||||
if (fprintf(to, "%s", line_buf) < 0)
|
||||
return -1;
|
||||
continue; }
|
||||
{ for (i = 0; updlist[i].name; i++)
|
||||
{ if (!updlist[i].flag)
|
||||
if (output_opt(to, &updlist[i]) < 0)
|
||||
return -1; } }
|
||||
return feof(from) ? 0 : -1; }
|
||||
|
||||
int main(void)
|
||||
{ if (update(stdin, stdout, updlist) < 0)
|
||||
{ fprintf(stderr, "failed\n");
|
||||
return (EXIT_FAILURE); }
|
||||
return 0; }
|
||||
113
Task/Update-a-configuration-file/D/update-a-configuration-file.d
Normal file
113
Task/Update-a-configuration-file/D/update-a-configuration-file.d
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import std.stdio, std.file, std.string, std.regex, std.path,
|
||||
std.typecons;
|
||||
|
||||
final class Config {
|
||||
enum EntryType { empty, enabled, disabled, comment, ignore }
|
||||
|
||||
static protected struct Entry {
|
||||
EntryType type;
|
||||
string name, value;
|
||||
}
|
||||
protected Entry[] entries;
|
||||
protected string path;
|
||||
|
||||
this(in string path) {
|
||||
if (!isValidPath(path) || (exists(path) && !isFile(path)))
|
||||
throw new Exception("Invalid filename");
|
||||
|
||||
this.path = path;
|
||||
if (!exists(path))
|
||||
return;
|
||||
|
||||
auto r = regex(r"^(;*)\s*([A-Z0-9]+)\s*([A-Z0-9]*)", "i");
|
||||
auto f = File(path, "r");
|
||||
foreach (const buf; f.byLine()) {
|
||||
auto line = buf.strip().idup;
|
||||
if (!line.length)
|
||||
entries ~= Entry(EntryType.empty);
|
||||
else if (line[0] == '#')
|
||||
entries ~= Entry(EntryType.comment, line);
|
||||
else {
|
||||
line = line.removechars("^a-zA-Z0-9\x20;");
|
||||
auto m = match(line, r);
|
||||
if (!m.empty && m.front[2].length) {
|
||||
EntryType t = EntryType.enabled;
|
||||
if (m.front[1].length)
|
||||
t = EntryType.disabled;
|
||||
addOption(m.front[2], m.front[3], t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void enableOption(in string name) pure {
|
||||
immutable i = getOptionIndex(name);
|
||||
if (!i.isNull)
|
||||
entries[i].type = EntryType.enabled;
|
||||
}
|
||||
|
||||
void disableOption(in string name) pure {
|
||||
immutable i = getOptionIndex(name);
|
||||
if (!i.isNull)
|
||||
entries[i].type = EntryType.disabled;
|
||||
}
|
||||
|
||||
void setOption(in string name, in string value) pure {
|
||||
immutable i = getOptionIndex(name);
|
||||
if (!i.isNull)
|
||||
entries[i].value = value;
|
||||
}
|
||||
|
||||
void addOption(in string name, in string val,
|
||||
in EntryType t = EntryType.enabled) pure {
|
||||
entries ~= Entry(t, name.toUpper(), val);
|
||||
}
|
||||
|
||||
void removeOption(in string name) pure {
|
||||
immutable i = getOptionIndex(name);
|
||||
if (!i.isNull)
|
||||
entries[i].type = EntryType.ignore;
|
||||
}
|
||||
|
||||
Nullable!size_t getOptionIndex(in string name) const pure {
|
||||
foreach (immutable i, const ref e; entries) {
|
||||
if (e.type != EntryType.enabled &&
|
||||
e.type != EntryType.disabled)
|
||||
continue;
|
||||
if (e.name == name.toUpper())
|
||||
return typeof(return)(i);
|
||||
}
|
||||
return typeof(return).init;
|
||||
}
|
||||
|
||||
void store() {
|
||||
auto f = File(path, "w+");
|
||||
foreach (immutable e; entries) {
|
||||
final switch (e.type) {
|
||||
case EntryType.empty:
|
||||
f.writeln();
|
||||
break;
|
||||
case EntryType.enabled:
|
||||
f.writefln("%s %s", e.name, e.value);
|
||||
break;
|
||||
case EntryType.disabled:
|
||||
f.writefln("; %s %s", e.name, e.value);
|
||||
break;
|
||||
case EntryType.comment:
|
||||
f.writeln(e.name);
|
||||
break;
|
||||
case EntryType.ignore:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
auto cfg = new Config("config.txt");
|
||||
cfg.enableOption("seedsremoved");
|
||||
cfg.disableOption("needspeeling");
|
||||
cfg.setOption("numberofbananas", "1024");
|
||||
cfg.addOption("numberofstrawberries", "62000");
|
||||
cfg.store();
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// line represents a single line in the configuration file.
|
||||
type line struct {
|
||||
kind lineKind
|
||||
option string
|
||||
value string
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// lineKind represents the different kinds of configuration line.
|
||||
type lineKind int
|
||||
|
||||
const (
|
||||
_ lineKind = iota
|
||||
ignore
|
||||
parseError
|
||||
comment
|
||||
blank
|
||||
value
|
||||
)
|
||||
|
||||
func (l line) String() string {
|
||||
switch l.kind {
|
||||
case ignore, parseError, comment, blank:
|
||||
return l.value
|
||||
case value:
|
||||
s := l.option
|
||||
if l.disabled {
|
||||
s = "; " + s
|
||||
}
|
||||
if l.value != "" {
|
||||
s += " " + l.value
|
||||
}
|
||||
return s
|
||||
}
|
||||
panic("unexpected line kind")
|
||||
}
|
||||
|
||||
func removeDross(s string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if r < 32 || r > 0x7f || unicode.IsControl(r) {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, s)
|
||||
}
|
||||
|
||||
func parseLine(s string) line {
|
||||
if s == "" {
|
||||
return line{kind: blank}
|
||||
}
|
||||
if s[0] == '#' {
|
||||
return line{kind: comment, value: s}
|
||||
}
|
||||
s = removeDross(s)
|
||||
fields := strings.Fields(s)
|
||||
if len(fields) == 0 {
|
||||
return line{kind: blank}
|
||||
}
|
||||
// Strip leading semicolons (but record that we found them)
|
||||
semi := false
|
||||
for len(fields[0]) > 0 && fields[0][0] == ';' {
|
||||
semi = true
|
||||
fields[0] = fields[0][1:]
|
||||
}
|
||||
// Lose the first field if it was all semicolons
|
||||
if fields[0] == "" {
|
||||
fields = fields[1:]
|
||||
}
|
||||
switch len(fields) {
|
||||
case 0:
|
||||
// This can only happen if the line starts
|
||||
// with a semicolon but has no other information
|
||||
return line{kind: ignore}
|
||||
case 1:
|
||||
return line{
|
||||
kind: value,
|
||||
option: strings.ToUpper(fields[0]),
|
||||
disabled: semi,
|
||||
}
|
||||
case 2:
|
||||
return line{
|
||||
kind: value,
|
||||
option: strings.ToUpper(fields[0]),
|
||||
value: fields[1],
|
||||
disabled: semi,
|
||||
}
|
||||
}
|
||||
return line{kind: parseError, value: s}
|
||||
}
|
||||
|
||||
// Config represents a "standard" configuration file.
|
||||
type Config struct {
|
||||
options map[string]int // index of each option in lines.
|
||||
lines []line
|
||||
}
|
||||
|
||||
// index returns the index of the given option in
|
||||
// c.lines, or -1 if not found.
|
||||
func (c *Config) index(option string) int {
|
||||
if i, ok := c.options[option]; ok {
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// addLine adds a line to the config, ignoring
|
||||
// duplicate options and "ignore" lines.
|
||||
func (c *Config) addLine(l line) {
|
||||
switch l.kind {
|
||||
case ignore:
|
||||
return
|
||||
case value:
|
||||
if c.index(l.option) >= 0 {
|
||||
return
|
||||
}
|
||||
c.options[l.option] = len(c.lines)
|
||||
c.lines = append(c.lines, l)
|
||||
default:
|
||||
c.lines = append(c.lines, l)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadConfig reads a configuration file from path and returns it.
|
||||
func ReadConfig(path string) (*Config, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
r := bufio.NewReader(f)
|
||||
c := &Config{options: make(map[string]int)}
|
||||
for {
|
||||
s, err := r.ReadString('\n')
|
||||
if s != "" {
|
||||
if err == nil {
|
||||
// strip newline unless we encountered an error without finding one.
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
c.addLine(parseLine(s))
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Set sets an option to a value, adding the option if necessary. If
|
||||
// the option was previously disabled, it will be enabled.
|
||||
func (c *Config) Set(option string, val string) {
|
||||
if i := c.index(option); i >= 0 {
|
||||
line := &c.lines[i]
|
||||
line.disabled = false
|
||||
line.value = val
|
||||
return
|
||||
}
|
||||
c.addLine(line{
|
||||
kind: value,
|
||||
option: option,
|
||||
value: val,
|
||||
})
|
||||
}
|
||||
|
||||
// Enable sets the enabled status of an option. It is
|
||||
// ignored if the option does not already exist.
|
||||
func (c *Config) Enable(option string, enabled bool) {
|
||||
if i := c.index(option); i >= 0 {
|
||||
c.lines[i].disabled = !enabled
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes the configuration file to the writer.
|
||||
func (c *Config) Write(w io.Writer) {
|
||||
for _, line := range c.lines {
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
c, err := ReadConfig("/tmp/cfg")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
c.Enable("NEEDSPEELING", false)
|
||||
c.Set("SEEDSREMOVED", "")
|
||||
c.Set("NUMBEROFBANANAS", "1024")
|
||||
c.Set("NUMBEROFSTRAWBERRIES", "62000")
|
||||
c.Write(os.Stdout)
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
require 'regex strings'
|
||||
|
||||
normalize=:3 :0
|
||||
seen=. a:
|
||||
eol=. {:;y
|
||||
t=. ''
|
||||
for_line.<;._2;y do. lin=. deb line=.>line
|
||||
if. '#'={.line do. t=.t,line,eol
|
||||
elseif. ''-:lin do. t =. t,eol
|
||||
elseif. do.
|
||||
line=. 1 u:([-.-.)&(32+i.95)&.(3&u:) line
|
||||
base=. ('^ *;;* *';'') rxrplc line
|
||||
nm=. ;name=. {.;:toupper base
|
||||
if. -. name e. seen do.
|
||||
seen=. seen, name
|
||||
t=. t,eol,~dtb ('; '#~';'={.lin),(('^(?i) *',nm,'\b *');(nm,' ')) rxrplc base
|
||||
end.
|
||||
end.
|
||||
end.t
|
||||
)
|
||||
|
||||
enable=:1 :0
|
||||
(<m) 1!:2~ normalize (,y,{:);<@rxrplc~&(y;~'^; *(?i)',y,'\b');.2]1!:1<m
|
||||
)
|
||||
|
||||
disable=:1 :0
|
||||
(<m) 1!:2~ normalize (,'; ',y,{:);<@rxrplc~&(('; ',y);~'^ *(?i)',y,'\b');.2]1!:1<m
|
||||
)
|
||||
|
||||
set=:1 :0
|
||||
:
|
||||
t=. 1!:1<m
|
||||
pat=. '^ *(?i)',y,'\b.*'
|
||||
upd=. y,' ',":x
|
||||
(<m) 1!:2~ normalize (,upd,{:);<@rxrplc~&(pat;upd) t
|
||||
)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
'example.file' disable 'needspeeling'
|
||||
'example.file' enable 'seedsremoved'
|
||||
1024 'example.file' set 'numberofbananas'
|
||||
62000 'example.file' set 'numberofstrawberries'
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
use warnings;
|
||||
use strict;
|
||||
|
||||
my $needspeeling = 0;
|
||||
my $seedsremoved = 1;
|
||||
my $numberofstrawberries = 62000;
|
||||
my $numberofbananas = 1024;
|
||||
my $favouritefruit = 'bananas';
|
||||
|
||||
my @out;
|
||||
|
||||
sub config {
|
||||
my (@config) = <DATA>;
|
||||
push @config, "NUMBEROFSTRAWBERRIES $numberofstrawberries\n"
|
||||
unless grep { /^;*[ \t]*NUMBEROFSTRAWBERRIES\b/; } @config;
|
||||
|
||||
foreach my $line (@config) {
|
||||
|
||||
if (substr($line, 0, 1) eq '#') {
|
||||
push @out, $line;
|
||||
next;
|
||||
}
|
||||
|
||||
next if $line =~ /^[;\t ]+$/;
|
||||
|
||||
my ($option, $option_name);
|
||||
|
||||
if ($line =~ /^([A-Z]+[0-9]*)/) {
|
||||
$option_name = lc $1;
|
||||
$option = eval "\\\$$option_name";
|
||||
my $value = eval "\${$option_name}";
|
||||
|
||||
if ($value) {
|
||||
$$option = $value;
|
||||
}
|
||||
else {
|
||||
$line = '; ' . $line;
|
||||
$$option = undef;
|
||||
}
|
||||
}
|
||||
elsif ($line =~ /^;+\s*([A-Z]+[0-9]*)/) {
|
||||
$option_name = lc $1;
|
||||
$option = eval "\\\$$option_name";
|
||||
my $value = eval "\${$option_name}";
|
||||
|
||||
if ($value) {
|
||||
$line =~ s/^;+\s*//;
|
||||
$$option = $value == 1 ? '' : $value;
|
||||
}
|
||||
else {
|
||||
$$option = undef;
|
||||
}
|
||||
}
|
||||
|
||||
if (defined $$option) {
|
||||
push @out, "\U$option_name\E $$option\n"
|
||||
unless grep { /^\U$option_name\E\b/; } @out;
|
||||
}
|
||||
else {
|
||||
$line =~ s/\s*[^[:ascii:]]+\s*$//;
|
||||
$line =~ s/[[:cntrl:]\s]+$//;
|
||||
push(@out, "$line\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config();
|
||||
|
||||
my $file = join('', @out);
|
||||
$file =~ s/\n{3,}/\n\n/g;
|
||||
|
||||
print $file;
|
||||
|
||||
__DATA__
|
||||
# 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.
|
||||
|
||||
# The first word on each non comment line is the configuration option.
|
||||
# Remaining words or numbers on the line are configuration parameter
|
||||
# data fields.
|
||||
|
||||
# Note that configuration option names are not case sensitive. However,
|
||||
# configuration parameter data is case sensitive and the lettercase must
|
||||
# be preserved.
|
||||
|
||||
# This is a favourite fruit
|
||||
FAVOURITEFRUIT banana
|
||||
|
||||
# This is a boolean that should be set
|
||||
NEEDSPEELING
|
||||
|
||||
# This boolean is commented out
|
||||
; SEEDSREMOVED
|
||||
|
||||
# How many bananas we have
|
||||
NUMBEROFBANANAS 48
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
(let Data # Read all data
|
||||
(in "config"
|
||||
(make
|
||||
(until (eof)
|
||||
(link (trim (split (line) " "))) ) ) )
|
||||
(setq Data # Fix comments
|
||||
(mapcar
|
||||
'((L)
|
||||
(while (head '(";" ";") (car L))
|
||||
(pop L) )
|
||||
(if (= '(";") (car L))
|
||||
L
|
||||
(cons NIL L) ) )
|
||||
Data ) )
|
||||
(let (Need NIL Seed NIL NBan NIL NStr NIL Favo NIL)
|
||||
(map
|
||||
'((L)
|
||||
(let D (mapcar uppc (cadar L))
|
||||
(cond
|
||||
((= '`(chop "NEEDSPEELING") D)
|
||||
(if Need
|
||||
(set L)
|
||||
(on Need)
|
||||
(unless (caar L)
|
||||
(set (car L) '(";")) ) ) )
|
||||
((= '`(chop "SEEDSREMOVED") D)
|
||||
(if Seed
|
||||
(set L)
|
||||
(on Seed)
|
||||
(when (caar L)
|
||||
(set (car L)) ) ) )
|
||||
((= '`(chop "NUMBEROFBANANAS") D)
|
||||
(if NBan
|
||||
(set L)
|
||||
(on NBan)
|
||||
(set (cddar L) 1024) ) )
|
||||
((= '`(chop "NUMBEROFSTRAWBERRIES") D)
|
||||
(if NStr
|
||||
(set L)
|
||||
(on NStr) ) )
|
||||
((= '`(chop "FAVOURITEFRUIT") D)
|
||||
(if Favo
|
||||
(set L)
|
||||
(on Favo) ) ) ) ) )
|
||||
Data )
|
||||
(unless Need
|
||||
(conc Data (cons (list NIL "NEEDSPEELING"))) )
|
||||
(unless Seed
|
||||
(conc Data (cons (list NIL "SEEDSREMOVED"))) )
|
||||
(unless NBan
|
||||
(conc Data (cons (list NIL "NUMBEROFBANANAS" 1024))) )
|
||||
(unless NStr
|
||||
(conc Data (cons (list NIL "NUMBEROFSTRAWBERRIES" 62000))) ) )
|
||||
(out "config"
|
||||
(for L Data
|
||||
(prinl (glue " " (if (car L) L (cdr L)))) ) ) )
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# STANDARD MODULES
|
||||
#----------------------------------------------------------------------------
|
||||
import re
|
||||
import string
|
||||
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# GLOBAL: VARIABLES
|
||||
#----------------------------------------------------------------------------
|
||||
DISABLED_PREFIX = ';'
|
||||
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# CLASS Option
|
||||
#----------------------------------------------------------------------------
|
||||
class Option(object):
|
||||
"""An option, characterized by its name and its (optional) value. and by
|
||||
its status, which can be enabled or disabled.
|
||||
If its value is None, it is regarded to as a boolean option with a
|
||||
value of true.
|
||||
"""
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def __init__(self, name, value=None, disabled=False,
|
||||
disabled_prefix=DISABLED_PREFIX):
|
||||
"""Create an Option instance, setting its name to 'name' (always
|
||||
converted to a string) and its value to 'value'. If 'disabled' is
|
||||
True, the option is considered disabled, otherwise enabled.
|
||||
The string 'disabled_prefix' is used as a prefix when generating the
|
||||
string representation of the option.
|
||||
"""
|
||||
self.name = str(name)
|
||||
self.value = value
|
||||
self.disabled = bool(disabled)
|
||||
self.disabled_prefix = disabled_prefix
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def __str__(self):
|
||||
"""Return a string representation of the Option instance.
|
||||
This always includes the option name, followed by a space and the
|
||||
option value (if it is not None). If the option is disabled, the
|
||||
whole string is preprendend by the string stored in the instance
|
||||
attribute 'disabled_prefix' and a space.
|
||||
"""
|
||||
disabled = ('', '%s ' % self.disabled_prefix)[self.disabled]
|
||||
value = (' %s' % self.value, '')[self.value is None]
|
||||
return ''.join((disabled, self.name, value))
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def get(self):
|
||||
"""Return the option value.
|
||||
If the stored value is None, the option is regarded to as a
|
||||
boolean one and its enabled status is returned. Othrwise its value
|
||||
is returned.
|
||||
"""
|
||||
enabled = not bool(self.disabled)
|
||||
if self.value is None:
|
||||
value = enabled
|
||||
else:
|
||||
value = enabled and self.value
|
||||
return value
|
||||
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# CLASS Config
|
||||
#----------------------------------------------------------------------------
|
||||
class Config(object):
|
||||
"""A set of configuration options and comment strings.
|
||||
"""
|
||||
# Regular expression matching a valid option line.
|
||||
reOPTION = r'^\s*(?P<disabled>%s*)\s*(?P<name>\w+)(?:\s+(?P<value>.+?))?\s*$'
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def __init__(self, fname=None, disabled_prefix=DISABLED_PREFIX):
|
||||
"""Initialize a Config instance, optionally reading the contents of
|
||||
the configuration file 'fname'.
|
||||
The string 'disabled_prefix' is used as a prefix when generating the
|
||||
string representation of the options.
|
||||
"""
|
||||
self.disabled_prefix = disabled_prefix
|
||||
self.contents = [] # Sequence of strings and Option instances.
|
||||
self.options = {} # Map an option name to an Option instance.
|
||||
self.creOPTION = re.compile(self.reOPTION % self.disabled_prefix)
|
||||
if fname:
|
||||
self.parse_file(fname)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def __str__(self):
|
||||
"""Return a string representation of the Config instance.
|
||||
This is just the concatenation of all the items stored in the
|
||||
attribute 'contents'.
|
||||
"""
|
||||
return '\n'.join(map(str, self.contents))
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def parse_file(self, fname):
|
||||
"""Parse all the lines of file 'fname' by applying the method
|
||||
'parser_lines' on the file contents.
|
||||
"""
|
||||
with open(fname) as f:
|
||||
self.parse_lines(f)
|
||||
return self
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def parse_lines(self, lines):
|
||||
"""Parse all the lines of iterable 'lines' by invoking the method
|
||||
'parse_line' for each line in 'lines'.
|
||||
"""
|
||||
for line in lines:
|
||||
self.parse_line(line)
|
||||
return self
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def parse_line(self, line):
|
||||
"""Parse the line 'line', looking for options.
|
||||
If an option line is found, spaces are stripped from the start and
|
||||
the end of 'line' and any non-printable character is removed as well.
|
||||
Only the first occurrence of an option is processed, all the other
|
||||
occurrences are ignored. A valid option is added to the instance
|
||||
attribute 'contents' (in order to preserve its position among the
|
||||
other lines). It is also added to the mapping stored in the instance
|
||||
attribute 'options'.
|
||||
Any non-option string is added the the instance attribute 'contents',
|
||||
except those lines starting with the string stored into the instance
|
||||
attribute 'disabled_prefix' which are not followed by any option
|
||||
name.
|
||||
"""
|
||||
s = ''.join(c for c in line.strip() if c in string.printable)
|
||||
moOPTION = self.creOPTION.match(s)
|
||||
if moOPTION:
|
||||
name = moOPTION.group('name').upper()
|
||||
if not name in self.options:
|
||||
self.add_option(name, moOPTION.group('value'),
|
||||
moOPTION.group('disabled'))
|
||||
else:
|
||||
if not s.startswith(self.disabled_prefix):
|
||||
self.contents.append(line.rstrip())
|
||||
return self
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def add_option(self, name, value=None, disabled=False):
|
||||
"""Create a new Option instance, named 'name' (always converted to
|
||||
uppercase) with value 'value' and set its disabled status to
|
||||
'disabled'.
|
||||
The Option instance is added to the instance attribute 'contents'.
|
||||
It is also added to the mapping stored in the instance attribute
|
||||
'options'.
|
||||
"""
|
||||
name = name.upper()
|
||||
opt = Option(name, value, disabled)
|
||||
self.options[name] = opt
|
||||
self.contents.append(opt)
|
||||
return opt
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def set_option(self, name, value=None, disabled=False):
|
||||
"""Look for an option named 'name' (always converted to
|
||||
uppercase) among the options stored in the instance
|
||||
attribute 'options'.
|
||||
If it is not found, a new Option instance is created.
|
||||
In any case its value is set to 'value' and its disabled
|
||||
status to 'disabled'.
|
||||
"""
|
||||
name = name.upper()
|
||||
opt = self.options.get(name)
|
||||
if opt:
|
||||
opt.value = value
|
||||
opt.disabled = disabled
|
||||
else:
|
||||
opt = self.add_option(name, value, disabled)
|
||||
return opt
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def enable_option(self, name, value=None):
|
||||
"""Enable the option named 'name' (always converted to
|
||||
uppercase) and set its value to 'value'.
|
||||
If the option is not found, it is created and added to the
|
||||
end of the instance attribute 'contents'.
|
||||
"""
|
||||
return self.set_option(name, value, False)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def disable_option(self, name, value=None):
|
||||
"""Disable the option named 'name' (always converted to
|
||||
uppercase) and set its value to 'value'.
|
||||
If the option is not found, it is created and added to the
|
||||
end of the instance attribute 'contents'.
|
||||
"""
|
||||
return self.set_option(name, value, True)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
def get_option(self, name):
|
||||
"""Return the value of the option named 'name' (always
|
||||
converted to uppercase).
|
||||
If the option is not found in the instance attribute
|
||||
'options', None is returned. If the stored value is None,
|
||||
it is regarded to as a boolean option and its enable status
|
||||
is returned. Otherwise its value is returned.
|
||||
"""
|
||||
opt = self.options.get(name.upper())
|
||||
value = opt.get() if opt else None
|
||||
return value
|
||||
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# MAIN
|
||||
#----------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
cfg = Config(sys.argv[1] if len(sys.argv) > 1 else None)
|
||||
cfg.disable_option('needspeeling')
|
||||
cfg.enable_option('seedsremoved')
|
||||
cfg.enable_option('numberofbananas', 1024)
|
||||
cfg.enable_option('numberofstrawberries', 62000)
|
||||
print cfg
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
require 'stringio'
|
||||
|
||||
class ConfigFile
|
||||
|
||||
# create a ConfigFile object from a file
|
||||
def self.file(filename)
|
||||
fh = File.open(filename)
|
||||
obj = self.new(fh)
|
||||
obj.filename = filename
|
||||
fh.close
|
||||
obj
|
||||
end
|
||||
|
||||
# create a ConfigFile object from a string
|
||||
def self.data(string)
|
||||
fh = StringIO.new(string)
|
||||
obj = self.new(fh)
|
||||
fh.close
|
||||
obj
|
||||
end
|
||||
|
||||
def initialize(filehandle)
|
||||
@lines = filehandle.readlines
|
||||
@filename = nil
|
||||
tidy_file
|
||||
end
|
||||
attr :filename
|
||||
|
||||
def save()
|
||||
if @filename
|
||||
File.open(@filename, "w") {|f| f.write(self)}
|
||||
end
|
||||
end
|
||||
|
||||
def tidy_file()
|
||||
@lines.map! do |line|
|
||||
# remove leading whitespace
|
||||
line.lstrip!
|
||||
|
||||
if line.match(/^#/)
|
||||
# Lines beginning with hash symbols should not be manipulated and left
|
||||
# unchanged in the revised file.
|
||||
line
|
||||
else
|
||||
# replace double semicolon prefixes with just a single semicolon
|
||||
line.sub!(/^;+\s+/, "; ")
|
||||
|
||||
if line.match(/^; \s*$/)
|
||||
# Any lines beginning with a semicolon or groups of semicolons, but no
|
||||
# following option should be removed
|
||||
line = ""
|
||||
else
|
||||
# remove ... any trailing whitespace on the lines
|
||||
line = line.rstrip + "\n"
|
||||
|
||||
# Whitespace between the option and paramters should consist only of a
|
||||
# single space
|
||||
if m = line.match(/^(; )?([[:upper:]]+)\s+(.*)/)
|
||||
line = (m[1].nil? ? "" : m[1]) + format_line(m[2], m[3])
|
||||
end
|
||||
end
|
||||
|
||||
line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def format_line(option, value)
|
||||
"%s%s\n" % [option.upcase.strip, value.nil? ? "" : " " + value.to_s.strip]
|
||||
end
|
||||
|
||||
# returns the index of the option, or nil if not found
|
||||
def find_option(option)
|
||||
@lines.find_index {|line| line.match(/^#{option.upcase.strip}\b/)}
|
||||
end
|
||||
|
||||
# uncomments a disabled option
|
||||
def enable_option(option)
|
||||
if idx = find_option("; " + option)
|
||||
@lines[idx][/^; /] = ""
|
||||
end
|
||||
end
|
||||
|
||||
# comment a line with a semi-colon
|
||||
def disable_option(option)
|
||||
if idx = find_option(option)
|
||||
@lines[idx][/^/] = "; "
|
||||
end
|
||||
end
|
||||
|
||||
# add an option, or change the value of an existing option.
|
||||
# use nil for the value to set a boolean option
|
||||
def set_value(option, value)
|
||||
if idx = find_option(option)
|
||||
@lines[idx] = format_line(option, value)
|
||||
else
|
||||
@lines << format_line(option, value)
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
@lines.join('')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
config = ConfigFile.data(DATA.read)
|
||||
config.disable_option('needspeeling')
|
||||
config.enable_option('seedsremoved')
|
||||
config.set_value('numberofbananas', 1024)
|
||||
config.set_value('numberofstrawberries', 62000)
|
||||
puts config
|
||||
|
||||
|
||||
__END__
|
||||
# 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.
|
||||
|
||||
# The first word on each non comment line is the configuration option.
|
||||
# Remaining words or numbers on the line are configuration parameter
|
||||
# data fields.
|
||||
|
||||
# Note that configuration option names are not case sensitive. However,
|
||||
# configuration parameter data is case sensitive and the lettercase must
|
||||
# be preserved.
|
||||
|
||||
# This is a favourite fruit
|
||||
FAVOURITEFRUIT banana
|
||||
|
||||
# This is a boolean that should be set
|
||||
NEEDSPEELING
|
||||
|
||||
# This boolean is commented out
|
||||
;;; SEEDSREMOVED
|
||||
;;;
|
||||
|
||||
# How many bananas we have
|
||||
NUMBEROFBANANAS 48
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
@(next :args)
|
||||
@configfile
|
||||
@(maybe)
|
||||
@ (next configfile)
|
||||
@ (collect :vars (config))
|
||||
@config
|
||||
@ (end)
|
||||
@(end)
|
||||
@(collect)
|
||||
@ (cases)
|
||||
@option=
|
||||
@ (output :into new_opt_line :filter :upcase)
|
||||
; @option
|
||||
@ (end)
|
||||
@ (or)
|
||||
@option=@val
|
||||
@ (output :into new_opt_line :filter :upcase)
|
||||
@option @val
|
||||
@ (end)
|
||||
@ (or)
|
||||
@option
|
||||
@ (output :into new_opt_line :filter :upcase)
|
||||
@option
|
||||
@ (end)
|
||||
@ (end)
|
||||
@ (next :var config)
|
||||
@ (local new_config)
|
||||
@ (bind new_config ())
|
||||
@ (collect :vars ((opt_there "")))
|
||||
@ (block)
|
||||
@ (cases)
|
||||
@ (cases)
|
||||
@{line /[ \t]*/}
|
||||
@ (or)
|
||||
@{line /#.*/}
|
||||
@ (end)
|
||||
@ (output :append :into new_config)
|
||||
@line
|
||||
@ (end)
|
||||
@ (accept)
|
||||
@ (or)
|
||||
@ (maybe)
|
||||
; @opt_there
|
||||
@ (or)
|
||||
@opt_there @(skip)
|
||||
@ (or)
|
||||
@opt_there
|
||||
@ (or)
|
||||
@original_line
|
||||
@ (end)
|
||||
@ (end)
|
||||
@ (cases)
|
||||
@ (bind opt_there option :filter :upcase)
|
||||
@ (output :append :into new_config)
|
||||
@new_opt_line
|
||||
@ (end)
|
||||
@ (or)
|
||||
@ (output :append :into new_config)
|
||||
@original_line
|
||||
@ (end)
|
||||
@ (end)
|
||||
@ (end)
|
||||
@ (cases)
|
||||
@ (bind opt_there option :filter :upcase)
|
||||
@ (or)
|
||||
@ (output :append :into new_config)
|
||||
@new_opt_line
|
||||
@ (end)
|
||||
@ (end)
|
||||
@ (set config new_config)
|
||||
@(end)
|
||||
@(output)
|
||||
@ (repeat)
|
||||
@config
|
||||
@ (end)
|
||||
@(end)
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package require Tcl 8.6
|
||||
oo::class create Config {
|
||||
variable filename contents
|
||||
constructor fileName {
|
||||
set filename $fileName
|
||||
set contents {}
|
||||
try {
|
||||
set f [open $filename]
|
||||
### Sanitize during input
|
||||
foreach line [split [read $f] \n] {
|
||||
if {[string match "#*" $line]} {
|
||||
lappend contents $line
|
||||
continue
|
||||
}
|
||||
if {[regexp {^;\W*$} $line]} continue
|
||||
set line [string trim [regsub -all {[^\u0020-\u007e]} $line {}]]
|
||||
if {[regexp {^(\W*)(\w+)(.*)$} $line -> a b c]} {
|
||||
set line "[regsub -all {^;+} $a {;}][string toupper $b]$c"
|
||||
}
|
||||
lappend contents $line
|
||||
}
|
||||
} finally {
|
||||
if {[info exists f]} {
|
||||
close $f
|
||||
}
|
||||
}
|
||||
}
|
||||
method save {} {
|
||||
set f [open $filename w]
|
||||
puts $f [join $contents \n]
|
||||
close $f
|
||||
}
|
||||
|
||||
# Utility methods (not exposed API)
|
||||
method Transform {pattern vars replacement} {
|
||||
set matched 0
|
||||
set line -1
|
||||
set RE "(?i)^$pattern$"
|
||||
foreach l $contents {
|
||||
incr line
|
||||
if {[uplevel 1 [list regexp $RE $l -> {*}$vars]]} {
|
||||
if {$matched} {
|
||||
set contents [lreplace $contents $line $line]
|
||||
incr line -1
|
||||
} else {
|
||||
lset contents $line [uplevel 1 [list subst $replacement]]
|
||||
}
|
||||
set matched 1
|
||||
}
|
||||
}
|
||||
return $matched
|
||||
}
|
||||
method Format {k v} {
|
||||
set v " [string trimleft $v]"
|
||||
return "[string toupper $k][string trimright $v]"
|
||||
}
|
||||
|
||||
# Public API for modifying options
|
||||
method enable {option} {
|
||||
if {![my Transform ";?\\s*($option)\\M\s*(.*)" {k v} \
|
||||
{[my Format $k $v]}]} {
|
||||
lappend contents [my Format $option ""]
|
||||
}
|
||||
}
|
||||
method disable {option} {
|
||||
if {![my Transform ";?\\s*($option)\\M\s*(.*)" {k v} \
|
||||
{; [my Format $k $v]}]} {
|
||||
lappend contents "; [my Format $option ""]"
|
||||
}
|
||||
}
|
||||
method set {option {value ""}} {
|
||||
if {![my Transform ";?\\s*($option)\\M.*" k {[my Format $k $value]}]} {
|
||||
lappend contents [my Format $option $value]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
set cfg [Config new "sample.cfg"]
|
||||
$cfg disable needspeeling
|
||||
$cfg enable seedsremoved
|
||||
$cfg set numberofbananas 1024
|
||||
$cfg set numberofstrawberries 62000
|
||||
$cfg save
|
||||
Loading…
Add table
Add a link
Reference in a new issue