package config import ( "bufio" "fmt" "os" "strings" ) // Device represents a device configuration in tellstick.conf type Device struct { ID int `json:"id"` Name string `json:"name"` Protocol string `json:"protocol"` Model string `json:"model"` Parameters map[string]string `json:"parameters"` } // Controller represents a controller configuration type Controller struct { ID int `json:"id"` Name string `json:"name"` Type string `json:"type"` Serial string `json:"serial,omitempty"` } // Config represents the entire tellstick.conf structure type Config struct { User string `json:"user,omitempty"` Group string `json:"group,omitempty"` DevicePath string `json:"devicePath,omitempty"` IgnoreControllerConfirmation int `json:"ignoreControllerConfirmation,omitempty"` Controllers []Controller `json:"controllers"` Devices []Device `json:"devices"` } // Parser handles parsing and writing tellstick.conf files type Parser struct { filePath string } // NewParser creates a new config parser func NewParser(filePath string) *Parser { return &Parser{filePath: filePath} } // Parse reads and parses the tellstick.conf file func (p *Parser) Parse() (*Config, error) { file, err := os.Open(p.filePath) if err != nil { return nil, err } defer file.Close() config := &Config{ Controllers: []Controller{}, Devices: []Device{}, } scanner := bufio.NewScanner(file) var currentSection string var currentDevice *Device var currentController *Controller for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) // Skip empty lines and comments if line == "" || strings.HasPrefix(line, "#") { continue } // Check for section headers if strings.HasPrefix(line, "user") { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { config.User = strings.Trim(strings.TrimSpace(parts[1]), "\"") } continue } if strings.HasPrefix(line, "group") { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { config.Group = strings.Trim(strings.TrimSpace(parts[1]), "\"") } continue } if strings.HasPrefix(line, "deviceNode") { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { config.DevicePath = strings.Trim(strings.TrimSpace(parts[1]), "\"") } continue } if strings.HasPrefix(line, "ignoreControllerConfirmation") { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { fmt.Sscanf(strings.TrimSpace(parts[1]), "%d", &config.IgnoreControllerConfirmation) } continue } // Section detection if strings.HasPrefix(line, "controller {") || strings.HasPrefix(line, "controller{") { currentSection = "controller" currentController = &Controller{} continue } if strings.HasPrefix(line, "device {") || strings.HasPrefix(line, "device{") { currentSection = "device" currentDevice = &Device{ Parameters: make(map[string]string), } continue } // End of section if line == "}" { if currentSection == "device" && currentDevice != nil { config.Devices = append(config.Devices, *currentDevice) currentDevice = nil } if currentSection == "controller" && currentController != nil { config.Controllers = append(config.Controllers, *currentController) currentController = nil } currentSection = "" continue } // Parse device/controller properties if currentSection == "device" && currentDevice != nil { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { key := strings.TrimSpace(parts[0]) value := strings.Trim(strings.TrimSpace(parts[1]), "\"") switch key { case "id": fmt.Sscanf(value, "%d", ¤tDevice.ID) case "name": currentDevice.Name = value case "protocol": currentDevice.Protocol = value case "model": currentDevice.Model = value default: currentDevice.Parameters[key] = value } } } if currentSection == "controller" && currentController != nil { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { key := strings.TrimSpace(parts[0]) value := strings.Trim(strings.TrimSpace(parts[1]), "\"") switch key { case "id": fmt.Sscanf(value, "%d", ¤tController.ID) case "name": currentController.Name = value case "type": currentController.Type = value case "serial": currentController.Serial = value } } } } if err := scanner.Err(); err != nil { return nil, err } return config, nil } // Write writes the configuration to the tellstick.conf file func (p *Parser) Write(config *Config) error { file, err := os.Create(p.filePath) if err != nil { return err } defer file.Close() w := bufio.NewWriter(file) // Write global settings if config.User != "" { fmt.Fprintf(w, "user = \"%s\"\n", config.User) } if config.Group != "" { fmt.Fprintf(w, "group = \"%s\"\n", config.Group) } if config.DevicePath != "" { fmt.Fprintf(w, "deviceNode = \"%s\"\n", config.DevicePath) } if config.IgnoreControllerConfirmation > 0 { fmt.Fprintf(w, "ignoreControllerConfirmation = %d\n", config.IgnoreControllerConfirmation) } w.WriteString("\n") // Write controllers for _, ctrl := range config.Controllers { w.WriteString("controller {\n") fmt.Fprintf(w, " id = %d\n", ctrl.ID) if ctrl.Name != "" { fmt.Fprintf(w, " name = \"%s\"\n", ctrl.Name) } if ctrl.Type != "" { fmt.Fprintf(w, " type = \"%s\"\n", ctrl.Type) } if ctrl.Serial != "" { fmt.Fprintf(w, " serial = \"%s\"\n", ctrl.Serial) } w.WriteString("}\n\n") } // Write devices for _, dev := range config.Devices { w.WriteString("device {\n") fmt.Fprintf(w, " id = %d\n", dev.ID) fmt.Fprintf(w, " name = \"%s\"\n", dev.Name) fmt.Fprintf(w, " protocol = \"%s\"\n", dev.Protocol) if dev.Model != "" { fmt.Fprintf(w, " model = \"%s\"\n", dev.Model) } // Write parameters for key, value := range dev.Parameters { fmt.Fprintf(w, " %s = \"%s\"\n", key, value) } w.WriteString("}\n\n") } return w.Flush() } // GetDevice returns a device by ID func (c *Config) GetDevice(id int) *Device { for i := range c.Devices { if c.Devices[i].ID == id { return &c.Devices[i] } } return nil } // AddDevice adds a new device to the configuration func (c *Config) AddDevice(device Device) { c.Devices = append(c.Devices, device) } // UpdateDevice updates an existing device func (c *Config) UpdateDevice(device Device) bool { for i := range c.Devices { if c.Devices[i].ID == device.ID { c.Devices[i] = device return true } } return false } // DeleteDevice removes a device by ID func (c *Config) DeleteDevice(id int) bool { for i := range c.Devices { if c.Devices[i].ID == id { c.Devices = append(c.Devices[:i], c.Devices[i+1:]...) return true } } return false } // GetNextDeviceID returns the next available device ID func (c *Config) GetNextDeviceID() int { maxID := 0 for _, dev := range c.Devices { if dev.ID > maxID { maxID = dev.ID } } return maxID + 1 }