From 882953a4a2f58bf0370c75f7e2c56817c5bf535e Mon Sep 17 00:00:00 2001 From: scantlight Date: Thu, 7 Feb 2013 17:49:33 +0100 Subject: [PATCH 1/6] Application can be started with personalized configuration values. This configuration values can be saved using: 1. Default values inserted by developer priority 0 2. Configuration file priority 1 3. Passed as command line arguments priority 2 Values with greater priority will overwrite values with less priority. Default value is overwritten by value in configuration file which is overwritten by value passed on command line. If no value was provided in configuration file or on command line then the default value will be used. --- log/log.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/log/log.go b/log/log.go index df36ed5..7564495 100644 --- a/log/log.go +++ b/log/log.go @@ -85,6 +85,10 @@ func StringToLevel(name string) (int, error) { level = INFO case "DEBUG": level = DEBUG + case "": + // if no log level was passed in configuration file or on command line + // then we will use this default value + level = ERROR default: return -1, ErrLevel } @@ -92,7 +96,7 @@ func StringToLevel(name string) (int, error) { } func FlagLevel(flagname string) *string { - return flag.String(flagname, "ERROR", "set loglevel [ERROR|INFO|DEBUG]") + return flag.String(flagname, "", "set loglevel [ERROR|INFO|DEBUG]") } type logger struct { From a48ef4005167604b74ae17782783b845230fe512 Mon Sep 17 00:00:00 2001 From: scantlight Date: Thu, 7 Feb 2013 19:40:09 +0100 Subject: [PATCH 2/6] Moved configuration file here from mapo. --- conf/conf.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 conf/conf.go diff --git a/conf/conf.go b/conf/conf.go new file mode 100644 index 0000000..7f05d89 --- /dev/null +++ b/conf/conf.go @@ -0,0 +1,46 @@ +/* +Copyright 2013 Petru Ciobanu, Francesco Paglia, Lorenzo Pierfederici + +This file is part of Mapo. + +Mapo is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Mapo is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mapo. If not, see . +*/ + +/* +Package admin implements the API for Mapo's administration components. +*/ +package conf + +import ( + "goconf/conf" +) + +/* +GlobalConfiguration, il oggetto globale per l'accesso ai dati contenuti nel +file di configurazione. +*/ +var GlobalConfiguration *conf.ConfigFile + +/* +ReadConfiguration, attiva il GlobalConfiguration. +*/ +func ParseConfigFile(filepath string) error { + + c, err := conf.ReadConfigFile(filepath) + if err == nil { + GlobalConfiguration = c + } + + return err +} From 7e1c08d7fd8c434718987b5675c96bff5336aa31 Mon Sep 17 00:00:00 2001 From: Scantlight Date: Tue, 5 Mar 2013 13:00:16 +0100 Subject: [PATCH 3/6] Merge branch from lento/master --- conf/conf.go | 175 +++++++++++++++++++++++++++++++++++++++++++++------ log/log.go | 144 ++++++++++++++++++++---------------------- 2 files changed, 225 insertions(+), 94 deletions(-) diff --git a/conf/conf.go b/conf/conf.go index 7f05d89..de0ed0e 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -1,46 +1,183 @@ /* Copyright 2013 Petru Ciobanu, Francesco Paglia, Lorenzo Pierfederici -This file is part of Mapo. +This file is part of maponet/utils. -Mapo is free software: you can redistribute it and/or modify +maponet/utils is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. -Mapo is distributed in the hope that it will be useful, +maponet/utils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Mapo. If not, see . +along with maponet/utils. If not, see . */ /* -Package admin implements the API for Mapo's administration components. +Package conf contains a simple loader for configuration files. */ package conf import ( - "goconf/conf" + "bufio" + "fmt" + "io" + "os" + "regexp" + "strconv" + "sync" + "bytes" ) -/* -GlobalConfiguration, il oggetto globale per l'accesso ai dati contenuti nel -file di configurazione. -*/ -var GlobalConfiguration *conf.ConfigFile +type NotFoundError struct { + key string +} -/* -ReadConfiguration, attiva il GlobalConfiguration. -*/ -func ParseConfigFile(filepath string) error { +func (e *NotFoundError) Error() string { return fmt.Sprintf("Key not found: %s", e.key) } + +type ConfigSyntaxError struct { + line string +} + +func (e *ConfigSyntaxError) Error() string { + return fmt.Sprintf("Config syntax error on line: %s", e.line) +} + +// Regexp patterns +var ( + PatternOption, _ = regexp.Compile("(.*)=([^#]*)") + PatternComment, _ = regexp.Compile("^#") + PatternEmpty, _ = regexp.Compile("^[\t\n\f\r ]$") +) + +// Config is a simple synchronized object that can be used to parse +// configuration files and store key=value pairs +type Config struct { + values map[string]string + mutex sync.RWMutex +} - c, err := conf.ReadConfigFile(filepath) - if err == nil { - GlobalConfiguration = c +func NewConfig() *Config { + var c Config + c.values = make(map[string]string) + return &c +} + +// Parse +func (c *Config) Parse(r *bufio.Reader) error { + for { + l, err := r.ReadBytes('\n') + if err == io.EOF { + break + } else if err != nil { + return err + } + switch { + case PatternEmpty.Match(l): + continue + case PatternComment.Match(l): + continue + case PatternOption.Match(l): + m := PatternOption.FindSubmatch(l) + c.Set(string(bytes.TrimSpace(m[1])), string(bytes.TrimSpace(m[2]))) + default: + return &ConfigSyntaxError{string(l)} + } + } + return nil +} + +// ParseFile +func (c *Config) ParseFile(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + err = c.Parse(bufio.NewReader(f)) + if err != nil { + return err } + return nil +} + +// Set +func (c *Config) Set(key, value string) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.values[key] = value +} + +// GetString +func (c *Config) GetString(key string) (string, error) { + c.mutex.RLock() + defer c.mutex.RUnlock() + + v, ok := c.values[key] + if !ok { + return v, &NotFoundError{key} + } + return v, nil +} + +// GetBool +func (c *Config) GetBool(key string) (bool, error) { + s, err := c.GetString(key) + if err != nil { + return *new(bool), err + } + + return strconv.ParseBool(s) +} + +// GetInt +func (c *Config) GetInt(key string) (int, error) { + s, err := c.GetString(key) + if err != nil { + return *new(int), err + } + + return strconv.Atoi(s) +} + +// GetFloat64 +func (c *Config) GetFloat64(key string) (float64, error) { + s, err := c.GetString(key) + if err != nil { + return *new(float64), err + } + + return strconv.ParseFloat(s, 64) +} + +var DefaultConfig = NewConfig() + +func ParseFile(path string) error { + return DefaultConfig.ParseFile(path) +} + +func Set(key, value string) { + DefaultConfig.Set(key, value) +} + +func GetString(key string) (string, error) { + return DefaultConfig.GetString(key) +} + +func GetBool(key string) (bool, error) { + return DefaultConfig.GetBool(key) +} + +func GetInt(key string) (int, error) { + return DefaultConfig.GetInt(key) +} - return err +func GetFloat64(key string) (float64, error) { + return DefaultConfig.GetFloat64(key) } diff --git a/log/log.go b/log/log.go index 7564495..74b1131 100644 --- a/log/log.go +++ b/log/log.go @@ -23,9 +23,8 @@ Package log contains a simple multi-level logger. package log import ( - "errors" - "flag" "fmt" + "io" "os" "time" ) @@ -37,104 +36,99 @@ const ( DEBUG ) -// ErrLevel indicates that the specified log level is not valid -var ErrLevel = errors.New("Unknown log level") +// BadLevelError indicates that the specified log level is not valid. +type BadLevelError struct { + level interface{} +} -// SetLevel sets the output level for the global logger -func SetLevel(level int) error { - if level > DEBUG { - return ErrLevel - } - l.level = level - return nil +func (e *BadLevelError) Error() string { return fmt.Sprintf("Unknown log level: %v", e.level) } + +// Logger is a simple multi-level logger. +type Logger struct { + Level int + Writer io.Writer } -// SetLevelString sets the output level for the global logger -func SetLevelString(name string) error { - level, err := StringToLevel(name) - if err != nil || level > DEBUG { - return ErrLevel - } - l.level = level - return nil +func NewLogger(level interface{}, writer io.Writer) *Logger { + var l Logger + l.SetLevel(level) + l.Writer = writer + return &l } -// LevelToString converts a log level to its string representation -func LevelToString(level int) (string, error) { - var s string +// SetLevel sets the output level for the logger. +// 'level' can be either a string or an int. +func (l *Logger) SetLevel(level interface{}) error { switch level { - case ERROR: - s = "ERROR" - case INFO: - s = "INFO" - case DEBUG: - s = "DEBUG" + case ERROR, "ERROR": + l.Level = ERROR + case INFO, "INFO": + l.Level = INFO + case DEBUG, "DEBUG": + l.Level = DEBUG default: - return "", ErrLevel + return &BadLevelError{level} } - return s, nil + return nil } -// StringToLevel converts a string to the corresponding log level -func StringToLevel(name string) (int, error) { - var level int - switch name { - case "ERROR": - level = ERROR - case "INFO": - level = INFO - case "DEBUG": - level = DEBUG - case "": - // if no log level was passed in configuration file or on command line - // then we will use this default value - level = ERROR - default: - return -1, ErrLevel +// Log logs a message with custom level and type. +// To log messages at predefined levels you can use the convenience +// functions Fatal(), Error(), Info(), Debug(). +func (l *Logger) Log(level int, msgType string, format string, v ...interface{}) { + if level <= l.Level { + msg := fmt.Sprintf(format, v...) + t := time.Now().Format(time.RFC1123) + fmt.Fprintf(l.Writer, "%s [%s]: %s\n", t, msgType, msg) + } - return level, nil } -func FlagLevel(flagname string) *string { - return flag.String(flagname, "", "set loglevel [ERROR|INFO|DEBUG]") +// Fatal logs a message at "ERROR" level and terminates the program. +func (l *Logger) Fatal(format string, v ...interface{}) { + l.Log(ERROR, "ERROR", format, v...) + os.Exit(1) } -type logger struct { - level int +// Error logs a message at "ERROR" level. +func (l *Logger) Error(format string, v ...interface{}) { + l.Log(ERROR, "ERROR", format, v...) } -var l logger +// Info logs a message at "INFO" level. +func (l *Logger) Info(format string, v ...interface{}) { + l.Log(INFO, "INFO", format, v...) +} -func Log(level int, format string, v ...interface{}) { - if level <= l.level { - msgType, err := LevelToString(level) - if err != nil { - Fatal(err.Error()) - } - msg := fmt.Sprintf(format, v...) - t := time.Now().Format(time.RFC1123) - fmt.Printf("%s [%s]: %s\n", t, msgType, msg) +// Debug logs a message at "DEBUG" level. +func (l *Logger) Debug(format string, v ...interface{}) { + l.Log(DEBUG, "DEBUG", format, v...) +} - } +var DefaultLogger = NewLogger(ERROR, os.Stdout) + +// SetLevel sets the output level for the default logger. +func SetLevel(level interface{}) error { + return DefaultLogger.SetLevel(level) } -// Fatal logs a message at "ERROR" level and terminates the program -func Fatal(format string, v ...interface{}) { - Log(ERROR, format, v...) - os.Exit(1) +// Fatal logs a message at "ERROR" level with the default logger and +// terminates the program. +func Fatal(msg string, v ...interface{}) { + DefaultLogger.Fatal(msg, v...) } -// Error logs a message at "ERROR" level -func Error(format string, v ...interface{}) { - Log(ERROR, format, v...) +// Error logs a message at "ERROR" level with the default logger. +func Error(msg string, v ...interface{}) { + DefaultLogger.Error(msg, v...) } -// Info logs a message at "INFO" level -func Info(format string, v ...interface{}) { - Log(INFO, format, v...) +// Info logs a message at "INFO" level with the default logger. +func Info(msg string, v ...interface{}) { + DefaultLogger.Info(msg, v...) } -// Debug logs a message at "DEBUG" level -func Debug(format string, v ...interface{}) { - Log(DEBUG, format, v...) +// Debug logs a message at "DEBUG" level with the default logger. +func Debug(msg string, v ...interface{}) { + DefaultLogger.Debug(msg, v...) } From 468ea51e9c1e443cf8489d37609c7ab3edc340be Mon Sep 17 00:00:00 2001 From: Scantlight Date: Wed, 13 Mar 2013 11:51:10 +0100 Subject: [PATCH 4/6] Parse configuration sections in configuration file. Some times we need to group configuration keys, for example to separate client configuration from server configuration and use the same conf file for both situations. All conf key that are at the beginning of the file and aren't under some section then this keys are in automatic inserted under default section. --- conf/conf.go | 65 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/conf/conf.go b/conf/conf.go index de0ed0e..2238c90 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -24,13 +24,13 @@ package conf import ( "bufio" + "bytes" "fmt" "io" "os" "regexp" "strconv" "sync" - "bytes" ) type NotFoundError struct { @@ -51,24 +51,26 @@ func (e *ConfigSyntaxError) Error() string { var ( PatternOption, _ = regexp.Compile("(.*)=([^#]*)") PatternComment, _ = regexp.Compile("^#") - PatternEmpty, _ = regexp.Compile("^[\t\n\f\r ]$") + PatternEmpty, _ = regexp.Compile("^[\t\n\f\r ]$") + PatternSection, _ = regexp.Compile("^\\[(.*)\\]([^#]*)") ) // Config is a simple synchronized object that can be used to parse // configuration files and store key=value pairs type Config struct { - values map[string]string - mutex sync.RWMutex + sections map[string]map[string]string + mutex sync.RWMutex } func NewConfig() *Config { var c Config - c.values = make(map[string]string) + c.sections = make(map[string]map[string]string) return &c } // Parse func (c *Config) Parse(r *bufio.Reader) error { + var section string = "default" for { l, err := r.ReadBytes('\n') if err == io.EOF { @@ -81,9 +83,12 @@ func (c *Config) Parse(r *bufio.Reader) error { continue case PatternComment.Match(l): continue + case PatternSection.Match(l): + m := PatternSection.FindSubmatch(l) + section = string(bytes.TrimSpace(m[1])) case PatternOption.Match(l): m := PatternOption.FindSubmatch(l) - c.Set(string(bytes.TrimSpace(m[1])), string(bytes.TrimSpace(m[2]))) + c.Set(section, string(bytes.TrimSpace(m[1])), string(bytes.TrimSpace(m[2]))) default: return &ConfigSyntaxError{string(l)} } @@ -107,19 +112,27 @@ func (c *Config) ParseFile(path string) error { } // Set -func (c *Config) Set(key, value string) { +func (c *Config) Set(section, key, value string) { c.mutex.Lock() defer c.mutex.Unlock() - c.values[key] = value + if _, ok := c.sections[section]; !ok { + c.sections[section] = make(map[string]string) + } + + c.sections[section][key] = value } // GetString -func (c *Config) GetString(key string) (string, error) { +func (c *Config) GetString(section, key string) (string, error) { c.mutex.RLock() defer c.mutex.RUnlock() - v, ok := c.values[key] + if _, ok := c.sections[section]; !ok { + return "", &NotFoundError{section} + } + + v, ok := c.sections[section][key] if !ok { return v, &NotFoundError{key} } @@ -127,8 +140,8 @@ func (c *Config) GetString(key string) (string, error) { } // GetBool -func (c *Config) GetBool(key string) (bool, error) { - s, err := c.GetString(key) +func (c *Config) GetBool(section, key string) (bool, error) { + s, err := c.GetString(section, key) if err != nil { return *new(bool), err } @@ -137,8 +150,8 @@ func (c *Config) GetBool(key string) (bool, error) { } // GetInt -func (c *Config) GetInt(key string) (int, error) { - s, err := c.GetString(key) +func (c *Config) GetInt(section, key string) (int, error) { + s, err := c.GetString(section, key) if err != nil { return *new(int), err } @@ -147,8 +160,8 @@ func (c *Config) GetInt(key string) (int, error) { } // GetFloat64 -func (c *Config) GetFloat64(key string) (float64, error) { - s, err := c.GetString(key) +func (c *Config) GetFloat64(section, key string) (float64, error) { + s, err := c.GetString(section, key) if err != nil { return *new(float64), err } @@ -162,22 +175,22 @@ func ParseFile(path string) error { return DefaultConfig.ParseFile(path) } -func Set(key, value string) { - DefaultConfig.Set(key, value) +func Set(section, key, value string) { + DefaultConfig.Set(section, key, value) } -func GetString(key string) (string, error) { - return DefaultConfig.GetString(key) +func GetString(section, key string) (string, error) { + return DefaultConfig.GetString(section, key) } -func GetBool(key string) (bool, error) { - return DefaultConfig.GetBool(key) +func GetBool(section, key string) (bool, error) { + return DefaultConfig.GetBool(section, key) } -func GetInt(key string) (int, error) { - return DefaultConfig.GetInt(key) +func GetInt(section, key string) (int, error) { + return DefaultConfig.GetInt(section, key) } -func GetFloat64(key string) (float64, error) { - return DefaultConfig.GetFloat64(key) +func GetFloat64(section, key string) (float64, error) { + return DefaultConfig.GetFloat64(section, key) } From f0491c5075e6d18855beb5f2f24112dabec04dbc Mon Sep 17 00:00:00 2001 From: Scantlight Date: Tue, 19 Mar 2013 17:46:12 +0100 Subject: [PATCH 5/6] Possibility to get list of sections. --- conf/conf.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/conf/conf.go b/conf/conf.go index 2238c90..7a7352c 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -68,9 +68,18 @@ func NewConfig() *Config { return &c } +func (c *Config) GetSections() []string { + sections := make([]string, 0) + for k,_ := range(c.sections) { + sections = append(sections, k) + } + + return sections +} + // Parse func (c *Config) Parse(r *bufio.Reader) error { - var section string = "default" + var section string = "defaults" for { l, err := r.ReadBytes('\n') if err == io.EOF { From db86bc60ae77ab51e523c46ae3f1bdf57a53029d Mon Sep 17 00:00:00 2001 From: Scantlight Date: Tue, 19 Mar 2013 17:47:56 +0100 Subject: [PATCH 6/6] On Linux, new line is made by the only LF character, but on windows there is CR and LF at the same time. This difference cause an error when parsing config file on windows OS. --- conf/conf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/conf.go b/conf/conf.go index 7a7352c..754c771 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -51,7 +51,7 @@ func (e *ConfigSyntaxError) Error() string { var ( PatternOption, _ = regexp.Compile("(.*)=([^#]*)") PatternComment, _ = regexp.Compile("^#") - PatternEmpty, _ = regexp.Compile("^[\t\n\f\r ]$") + PatternEmpty, _ = regexp.Compile("^[\t\n\f\r ]+$") PatternSection, _ = regexp.Compile("^\\[(.*)\\]([^#]*)") )