first commit
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
// Package toolbox provides small shared helpers, mirroring email_server/tool_box.py:
|
||||
// logging, the configured-timezone clock, and Message-ID generation.
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// Logger is a tiny leveled logger matching the Python format:
|
||||
// "%(asctime)s - %(name)s - %(levelname)s - %(message)s".
|
||||
type Logger struct {
|
||||
name string
|
||||
level Level
|
||||
out *log.Logger
|
||||
}
|
||||
|
||||
type Level int
|
||||
|
||||
const (
|
||||
LevelDebug Level = iota
|
||||
LevelInfo
|
||||
LevelWarning
|
||||
LevelError
|
||||
LevelCritical
|
||||
)
|
||||
|
||||
func parseLevel(s string) Level {
|
||||
switch strings.ToUpper(strings.TrimSpace(s)) {
|
||||
case "DEBUG":
|
||||
return LevelDebug
|
||||
case "WARNING", "WARN":
|
||||
return LevelWarning
|
||||
case "ERROR":
|
||||
return LevelError
|
||||
case "CRITICAL":
|
||||
return LevelCritical
|
||||
default:
|
||||
return LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case LevelDebug:
|
||||
return "DEBUG"
|
||||
case LevelWarning:
|
||||
return "WARNING"
|
||||
case LevelError:
|
||||
return "ERROR"
|
||||
case LevelCritical:
|
||||
return "CRITICAL"
|
||||
default:
|
||||
return "INFO"
|
||||
}
|
||||
}
|
||||
|
||||
var globalLevel = LevelInfo
|
||||
|
||||
// Configure sets the process-wide log level from settings.ini's [Logging] section,
|
||||
// mirroring tool_box.setup_logging.
|
||||
func Configure(cfg *ini.File) {
|
||||
section := cfg.Section("Logging")
|
||||
globalLevel = parseLevel(section.Key("LOG_LEVEL").MustString("INFO"))
|
||||
}
|
||||
|
||||
// GetLogger returns a Logger for the given component name, mirroring tool_box.get_logger.
|
||||
// Python derives the name from the caller's filename when omitted; Go callers pass it
|
||||
// explicitly instead, since introspecting the caller module isn't idiomatic here.
|
||||
func GetLogger(name string) *Logger {
|
||||
return &Logger{name: name, level: globalLevel, out: log.New(os.Stderr, "", 0)}
|
||||
}
|
||||
|
||||
func (l *Logger) log(level Level, format string, args ...any) {
|
||||
if level < globalLevel {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
ts := time.Now().Format("2006-01-02 15:04:05,000")
|
||||
l.out.Printf("%s - %s - %s - %s", ts, l.name, level, msg)
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(format string, args ...any) { l.log(LevelDebug, format, args...) }
|
||||
func (l *Logger) Info(format string, args ...any) { l.log(LevelInfo, format, args...) }
|
||||
func (l *Logger) Warning(format string, args ...any) { l.log(LevelWarning, format, args...) }
|
||||
func (l *Logger) Error(format string, args ...any) { l.log(LevelError, format, args...) }
|
||||
func (l *Logger) Critical(format string, args ...any) { l.log(LevelCritical, format, args...) }
|
||||
|
||||
// EnsureFolderExists creates the parent directory of filepath (a file path, not a
|
||||
// directory path), mirroring tool_box.ensure_folder_exists including its handling of
|
||||
// "sqlite:///" prefixed database URLs.
|
||||
func EnsureFolderExists(path string) error {
|
||||
path = strings.TrimPrefix(path, "sqlite:///")
|
||||
dir := path
|
||||
if idx := strings.LastIndexAny(path, "/\\"); idx >= 0 {
|
||||
dir = path[:idx]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
if dir == "" {
|
||||
return nil
|
||||
}
|
||||
return os.MkdirAll(dir, 0o755)
|
||||
}
|
||||
|
||||
// GetCurrentTime returns the current time in the configured server timezone, mirroring
|
||||
// tool_box.get_current_time. Falls back to UTC if the configured zone can't be loaded.
|
||||
func GetCurrentTime(cfg *ini.File) time.Time {
|
||||
tzName := cfg.Section("Server").Key("time_zone").MustString("UTC")
|
||||
loc, err := time.LoadLocation(tzName)
|
||||
if err != nil {
|
||||
loc = time.UTC
|
||||
}
|
||||
return time.Now().In(loc)
|
||||
}
|
||||
|
||||
// GenerateMessageID builds a Message-ID local-part@hostname string, mirroring
|
||||
// tool_box.generate_message_id: system wall-clock time (not the configured timezone)
|
||||
// plus a 6-digit random suffix.
|
||||
func GenerateMessageID(hostname string) string {
|
||||
digits := make([]byte, 6)
|
||||
for i := range digits {
|
||||
n, _ := rand.Int(rand.Reader, big.NewInt(10))
|
||||
digits[i] = byte('0' + n.Int64())
|
||||
}
|
||||
return fmt.Sprintf("%s.%s@%s", time.Now().Format("20060102150405"), string(digits), hostname)
|
||||
}
|
||||
Reference in New Issue
Block a user