Wworking on improving the tool

This commit is contained in:
Martino Ferrari
2026-05-21 07:41:56 +02:00
parent 6ff8fb5c25
commit 71430bc3b0
30 changed files with 1820 additions and 331 deletions
+97 -7
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"unicode"
@@ -80,10 +81,16 @@ func (s *Store) List() ([]InterfaceMeta, error) {
}
var out []InterfaceMeta
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".xml") {
name := e.Name()
if e.IsDir() || !strings.HasSuffix(strings.ToLower(name), ".xml") {
continue
}
id := strings.TrimSuffix(e.Name(), ".xml")
// Skip versioned backups: id.v1.xml, id.v2.xml, etc.
if isVersioned(name) {
continue
}
id := strings.TrimSuffix(name, filepath.Ext(name))
meta, err := s.readMeta(id)
if err != nil {
continue // skip corrupt files silently
@@ -96,6 +103,20 @@ func (s *Store) List() ([]InterfaceMeta, error) {
return out, nil
}
func isVersioned(name string) bool {
// Format: <id>.v<number>.xml
parts := strings.Split(name, ".")
if len(parts) != 3 {
return false
}
vPart := parts[1]
if !strings.HasPrefix(vPart, "v") {
return false
}
_, err := strconv.Atoi(vPart[1:])
return err == nil
}
// rootAttrs is used to parse only the top-level XML attributes.
type rootAttrs struct {
XMLName xml.Name `xml:"interface"`
@@ -145,18 +166,87 @@ func (s *Store) Create(xmlData []byte) (string, error) {
if _, err := os.Stat(s.filePath(id)); err == nil {
id = id + "-" + fmt.Sprintf("%d", time.Now().UnixMilli())
}
return id, os.WriteFile(s.filePath(id), xmlData, 0o644)
// Initialize version to 1.
updatedData, err := setXMLAttribute(xmlData, "version", "1")
if err != nil {
return "", fmt.Errorf("initialize version attribute: %w", err)
}
return id, os.WriteFile(s.filePath(id), updatedData, 0o644)
}
// Update replaces the XML for an existing interface.
// Update replaces the XML for an existing interface, preserving the previous
// version as a separate file.
func (s *Store) Update(id string, xmlData []byte) error {
if err := validateID(id); err != nil {
return ErrNotFound
}
if _, err := os.Stat(s.filePath(id)); errors.Is(err, os.ErrNotExist) {
return ErrNotFound
oldPath := s.filePath(id)
oldData, err := os.ReadFile(oldPath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return ErrNotFound
}
return err
}
return os.WriteFile(s.filePath(id), xmlData, 0o644)
// Parse current version to name the backup file.
var oldRoot rootAttrs
if err := xml.Unmarshal(oldData, &oldRoot); err != nil {
return fmt.Errorf("parse existing XML: %w", err)
}
if oldRoot.Version < 1 {
oldRoot.Version = 1
}
// Move old file to backup path: id.vN.xml
backupPath := filepath.Join(s.dir, fmt.Sprintf("%s.v%d.xml", id, oldRoot.Version))
if err := os.WriteFile(backupPath, oldData, 0o644); err != nil {
return fmt.Errorf("create backup: %w", err)
}
// Update version in new data.
newVersion := oldRoot.Version + 1
updatedData, err := setXMLAttribute(xmlData, "version", fmt.Sprintf("%d", newVersion))
if err != nil {
return fmt.Errorf("update version attribute: %w", err)
}
return os.WriteFile(oldPath, updatedData, 0o644)
}
// setXMLAttribute is a primitive helper to update a top-level attribute in an
// XML blob without full unmarshal/marshal (which might mess up formatting).
func setXMLAttribute(data []byte, attr, value string) ([]byte, error) {
// Very basic implementation: look for the attribute in the first 512 bytes.
// A proper implementation would use an XML encoder or a better regex.
s := string(data)
tagEnd := strings.Index(s, ">")
if tagEnd == -1 {
return nil, errors.New("malformed XML: no root tag end")
}
rootTag := s[:tagEnd]
attrSearch := " " + attr + "=\""
idx := strings.Index(rootTag, attrSearch)
if idx == -1 {
// Attribute not found, insert it before the tag ends.
insertIdx := tagEnd
if s[tagEnd-1] == '/' {
insertIdx-- // handle <tag />
}
return []byte(s[:insertIdx] + fmt.Sprintf(" %s=\"%s\"", attr, value) + s[insertIdx:]), nil
}
// Attribute found, replace its value.
valStart := idx + len(attrSearch)
valEnd := strings.Index(s[valStart:], "\"")
if valEnd == -1 {
return nil, errors.New("malformed XML: attribute value not closed")
}
return []byte(s[:valStart] + value + s[valStart+valEnd:]), nil
}
// Delete removes the interface with the given ID.