chore(repo): renamed gonf subfolder to something more traditional in go land
This commit is contained in:
parent
3b9af43738
commit
560becfd32
14 changed files with 2 additions and 2 deletions
95
pkg/commands.go
Normal file
95
pkg/commands.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
package gonf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log/slog"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// ----- Globals ---------------------------------------------------------------
|
||||
var commands []*CommandPromise
|
||||
|
||||
// ----- Init ------------------------------------------------------------------
|
||||
func init() {
|
||||
commands = make([]*CommandPromise, 0)
|
||||
}
|
||||
|
||||
// ----- Public ----------------------------------------------------------------
|
||||
func Command(cmd string, args ...string) *CommandPromise {
|
||||
return CommandWithEnv([]string{}, cmd, args...)
|
||||
}
|
||||
|
||||
func CommandWithEnv(env []string, cmd string, args ...string) *CommandPromise {
|
||||
return &CommandPromise{
|
||||
args: args,
|
||||
chain: nil,
|
||||
cmd: cmd,
|
||||
env: env,
|
||||
err: nil,
|
||||
Status: PROMISED,
|
||||
}
|
||||
}
|
||||
|
||||
type CommandPromise struct {
|
||||
args []string
|
||||
chain []Promise
|
||||
cmd string
|
||||
env []string
|
||||
err error
|
||||
Status Status
|
||||
Stdout bytes.Buffer
|
||||
Stderr bytes.Buffer
|
||||
}
|
||||
|
||||
func (c *CommandPromise) IfRepaired(p ...Promise) Promise {
|
||||
c.chain = p
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CommandPromise) Promise() Promise {
|
||||
commands = append(commands, c)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CommandPromise) Resolve() {
|
||||
cmd := exec.Command(c.cmd, c.args...)
|
||||
for _, e := range c.env {
|
||||
cmd.Env = append(cmd.Environ(), e)
|
||||
}
|
||||
cmd.Stdout = &c.Stdout
|
||||
cmd.Stderr = &c.Stderr
|
||||
|
||||
if c.err = cmd.Run(); c.err != nil {
|
||||
c.Status = BROKEN
|
||||
slog.Error("command", "args", c.args, "cmd", c.cmd, "env", c.env, "err", c.err, "stdout", c.Stdout.String(), "stderr", c.Stderr.String(), "status", c.Status)
|
||||
return
|
||||
}
|
||||
if c.Stdout.Len() == 0 && c.Stderr.Len() > 0 {
|
||||
c.Status = BROKEN
|
||||
slog.Error("command", "args", c.args, "cmd", c.cmd, "env", c.env, "stdout", c.Stdout.String(), "stderr", c.Stderr.String(), "status", c.Status)
|
||||
return
|
||||
}
|
||||
c.Status = REPAIRED
|
||||
slog.Info("command", "args", c.args, "cmd", c.cmd, "env", c.env, "stderr", c.Stderr.String(), "status", c.Status)
|
||||
// TODO add a notion of repaired?
|
||||
for _, p := range c.chain {
|
||||
p.Resolve()
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Internal --------------------------------------------------------------
|
||||
func resolveCommands() (status Status) {
|
||||
status = KEPT
|
||||
for _, c := range commands {
|
||||
if c.Status == PROMISED {
|
||||
c.Resolve()
|
||||
switch c.Status {
|
||||
case BROKEN:
|
||||
return BROKEN
|
||||
case REPAIRED:
|
||||
status = REPAIRED
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
151
pkg/files.go
Normal file
151
pkg/files.go
Normal file
|
@ -0,0 +1,151 @@
|
|||
package gonf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ----- Globals ---------------------------------------------------------------
|
||||
var files []*FilePromise
|
||||
|
||||
// ----- Init ------------------------------------------------------------------
|
||||
func init() {
|
||||
files = make([]*FilePromise, 0)
|
||||
}
|
||||
|
||||
// ----- Public ----------------------------------------------------------------
|
||||
type FilePromise struct {
|
||||
chain []Promise
|
||||
contents Value
|
||||
err error
|
||||
filename Value
|
||||
permissions *Permissions
|
||||
status Status
|
||||
}
|
||||
|
||||
func File(filename any) *FilePromise {
|
||||
return &FilePromise{
|
||||
chain: nil,
|
||||
contents: nil,
|
||||
err: nil,
|
||||
filename: interfaceToTemplateValue(filename),
|
||||
permissions: nil,
|
||||
status: PROMISED,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FilePromise) Contents(contents any) *FilePromise {
|
||||
f.contents = interfaceToValue(contents)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FilePromise) Permissions(p *Permissions) *FilePromise {
|
||||
f.permissions = p
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FilePromise) Template(contents any) *FilePromise {
|
||||
f.contents = interfaceToTemplateValue(contents)
|
||||
return f
|
||||
}
|
||||
|
||||
// We want to satisfy the Promise interface
|
||||
func (f *FilePromise) IfRepaired(p ...Promise) Promise {
|
||||
f.chain = p
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FilePromise) Promise() Promise {
|
||||
files = append(files, f)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FilePromise) Resolve() {
|
||||
filename := f.filename.String()
|
||||
if f.contents != nil {
|
||||
var sumFile []byte
|
||||
sumFile, f.err = sha256sumOfFile(filename)
|
||||
if f.err != nil {
|
||||
if !errors.Is(f.err, fs.ErrNotExist) {
|
||||
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
|
||||
f.status = BROKEN
|
||||
return
|
||||
}
|
||||
}
|
||||
contents := f.contents.Bytes()
|
||||
sumContents := sha256sum(contents)
|
||||
if !bytes.Equal(sumFile, sumContents) {
|
||||
if f.err = writeFile(filename, contents); f.err != nil {
|
||||
f.status = BROKEN
|
||||
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
|
||||
return
|
||||
}
|
||||
f.status = REPAIRED
|
||||
}
|
||||
}
|
||||
if f.permissions != nil {
|
||||
var status Status
|
||||
status, f.err = f.permissions.resolve(filename)
|
||||
if f.status == PROMISED || status == BROKEN {
|
||||
f.status = status
|
||||
}
|
||||
if f.err != nil {
|
||||
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if f.status == REPAIRED {
|
||||
slog.Info("file", "filename", f.filename, "status", f.status)
|
||||
for _, p := range f.chain {
|
||||
p.Resolve()
|
||||
}
|
||||
} else {
|
||||
f.status = KEPT
|
||||
slog.Debug("file", "filename", f.filename, "status", f.status)
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Internal --------------------------------------------------------------
|
||||
func resolveFiles() (status Status) {
|
||||
status = KEPT
|
||||
for _, f := range files {
|
||||
if f.status == PROMISED {
|
||||
f.Resolve()
|
||||
switch f.status {
|
||||
case BROKEN:
|
||||
return BROKEN
|
||||
case REPAIRED:
|
||||
status = REPAIRED
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sha256sumOfFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
func writeFile(filename string, contents []byte) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Write(contents)
|
||||
return err
|
||||
}
|
52
pkg/gonf.go
Normal file
52
pkg/gonf.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package gonf
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
func EnableDebugLogs() {
|
||||
h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
Level: slog.LevelDebug,
|
||||
})
|
||||
slog.SetDefault(slog.New(h))
|
||||
}
|
||||
|
||||
func Resolve() (status Status) {
|
||||
for {
|
||||
// ----- Files -------------------------------------------------
|
||||
status = resolveFiles()
|
||||
switch status {
|
||||
case BROKEN:
|
||||
return BROKEN
|
||||
case REPAIRED:
|
||||
continue
|
||||
}
|
||||
// ----- Packages ----------------------------------------------
|
||||
status = resolvePackages()
|
||||
switch status {
|
||||
case BROKEN:
|
||||
return BROKEN
|
||||
case REPAIRED:
|
||||
packages_list_function()
|
||||
continue
|
||||
}
|
||||
// ----- Services ----------------------------------------------
|
||||
status = resolveServices()
|
||||
switch status {
|
||||
case BROKEN:
|
||||
return BROKEN
|
||||
case REPAIRED:
|
||||
continue
|
||||
}
|
||||
// ----- Commands ----------------------------------------------
|
||||
status = resolveCommands()
|
||||
switch status {
|
||||
case BROKEN:
|
||||
return BROKEN
|
||||
case REPAIRED:
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
81
pkg/packages.go
Normal file
81
pkg/packages.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package gonf
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// ----- Globals ---------------------------------------------------------------
|
||||
var packages []*PackagePromise
|
||||
|
||||
// packages management functions
|
||||
var packages_install_function func([]string) (Status, []string)
|
||||
var packages_list_function func()
|
||||
var packages_update_function *CommandPromise
|
||||
|
||||
// ----- Init ------------------------------------------------------------------
|
||||
func init() {
|
||||
packages = make([]*PackagePromise, 0)
|
||||
}
|
||||
|
||||
// ----- Public ----------------------------------------------------------------
|
||||
func SetPackagesConfiguration(install func([]string) (Status, []string), list func(), update *CommandPromise) {
|
||||
packages_install_function = install
|
||||
packages_list_function = list
|
||||
packages_update_function = update
|
||||
}
|
||||
|
||||
func Package(names ...string) *PackagePromise {
|
||||
return &PackagePromise{
|
||||
chain: nil,
|
||||
err: nil,
|
||||
names: names,
|
||||
status: PROMISED,
|
||||
}
|
||||
}
|
||||
|
||||
type PackagePromise struct {
|
||||
chain []Promise
|
||||
err error
|
||||
names []string
|
||||
status Status
|
||||
}
|
||||
|
||||
func (p *PackagePromise) IfRepaired(ps ...Promise) Promise {
|
||||
p.chain = ps
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PackagePromise) Promise() Promise {
|
||||
packages = append(packages, p)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PackagePromise) Resolve() {
|
||||
status, affected := packages_install_function(p.names)
|
||||
switch status {
|
||||
case BROKEN:
|
||||
slog.Error("package", "names", p.names, "status", status, "broke", affected)
|
||||
case KEPT:
|
||||
slog.Debug("package", "names", p.names, "status", status)
|
||||
case REPAIRED:
|
||||
slog.Info("package", "names", p.names, "status", status, "repaired", affected)
|
||||
for _, pp := range p.chain {
|
||||
pp.Resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Internal --------------------------------------------------------------
|
||||
func resolvePackages() (status Status) {
|
||||
status = KEPT
|
||||
for _, c := range packages {
|
||||
if c.status == PROMISED {
|
||||
c.Resolve()
|
||||
switch c.status {
|
||||
case BROKEN:
|
||||
return BROKEN
|
||||
case REPAIRED:
|
||||
status = REPAIRED
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
89
pkg/permissions.go
Normal file
89
pkg/permissions.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package gonf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Permissions struct {
|
||||
group Value
|
||||
mode Value
|
||||
user Value
|
||||
}
|
||||
|
||||
func ModeUserGroup(mode, user, group interface{}) *Permissions {
|
||||
return &Permissions{
|
||||
group: interfaceToTemplateValue(group),
|
||||
mode: interfaceToTemplateValue(mode),
|
||||
user: interfaceToTemplateValue(user),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Permissions) resolve(filename string) (Status, error) {
|
||||
g, ok := p.group.(*IntValue)
|
||||
if !ok {
|
||||
if group, err := user.LookupGroup(p.group.String()); err != nil {
|
||||
return BROKEN, err
|
||||
} else {
|
||||
if groupId, err := strconv.Atoi(group.Gid); err != nil {
|
||||
return BROKEN, err
|
||||
} else {
|
||||
g = &IntValue{groupId}
|
||||
p.group = g
|
||||
}
|
||||
}
|
||||
}
|
||||
m, ok := p.mode.(*IntValue)
|
||||
if !ok {
|
||||
if i, err := strconv.Atoi(p.mode.String()); err != nil {
|
||||
return BROKEN, err
|
||||
} else {
|
||||
m = &IntValue{i}
|
||||
p.mode = m
|
||||
}
|
||||
}
|
||||
u, ok := p.user.(*IntValue)
|
||||
if !ok {
|
||||
if user, err := user.Lookup(p.user.String()); err != nil {
|
||||
return BROKEN, err
|
||||
} else {
|
||||
if userId, err := strconv.Atoi(user.Uid); err != nil {
|
||||
return BROKEN, err
|
||||
} else {
|
||||
u = &IntValue{userId}
|
||||
p.group = u
|
||||
}
|
||||
}
|
||||
}
|
||||
var status Status = KEPT
|
||||
if fileInfo, err := os.Lstat(filename); err != nil {
|
||||
return BROKEN, err
|
||||
} else {
|
||||
gv := g.Int()
|
||||
mv := fs.FileMode(m.Int())
|
||||
uv := u.Int()
|
||||
if fileInfo.Mode() != mv {
|
||||
if err := os.Chmod(filename, mv); err != nil {
|
||||
return BROKEN, err
|
||||
}
|
||||
status = REPAIRED
|
||||
}
|
||||
if stat, ok := fileInfo.Sys().(*syscall.Stat_t); ok {
|
||||
if stat.Gid != uint32(gv) || stat.Uid != uint32(uv) {
|
||||
if err := os.Chown(filename, uv, gv); err != nil {
|
||||
return BROKEN, err
|
||||
}
|
||||
status = REPAIRED
|
||||
}
|
||||
} else {
|
||||
return BROKEN, errors.New("Unsupported operating system")
|
||||
}
|
||||
_ = gv
|
||||
_ = uv
|
||||
}
|
||||
return status, nil
|
||||
}
|
50
pkg/promises.go
Normal file
50
pkg/promises.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package gonf
|
||||
|
||||
type Promise interface {
|
||||
IfRepaired(...Promise) Promise
|
||||
Promise() Promise
|
||||
Resolve()
|
||||
}
|
||||
|
||||
//type Operation int
|
||||
//
|
||||
//const (
|
||||
// AND = iota
|
||||
// OR
|
||||
// NOT
|
||||
//)
|
||||
//
|
||||
//func (o Operation) String() string {
|
||||
// switch o {
|
||||
// case AND:
|
||||
// return "and"
|
||||
// case OR:
|
||||
// return "or"
|
||||
// case NOT:
|
||||
// return "not"
|
||||
// }
|
||||
// panic("unknown")
|
||||
//}
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
PROMISED = iota
|
||||
BROKEN
|
||||
KEPT
|
||||
REPAIRED
|
||||
)
|
||||
|
||||
func (s Status) String() string {
|
||||
switch s {
|
||||
case PROMISED:
|
||||
return "promised"
|
||||
case BROKEN:
|
||||
return "broken"
|
||||
case KEPT:
|
||||
return "kept"
|
||||
case REPAIRED:
|
||||
return "repaired"
|
||||
}
|
||||
panic("unknown")
|
||||
}
|
96
pkg/services.go
Normal file
96
pkg/services.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package gonf
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// ----- Globals ---------------------------------------------------------------
|
||||
var services []*ServicePromise
|
||||
|
||||
// service management function
|
||||
var serviceFunction func(string, string) (Status, error)
|
||||
|
||||
// ----- Init ------------------------------------------------------------------
|
||||
func init() {
|
||||
services = make([]*ServicePromise, 0)
|
||||
}
|
||||
|
||||
// ----- Public ----------------------------------------------------------------
|
||||
func SetServiceFunction(f func(string, string) (Status, error)) {
|
||||
serviceFunction = f
|
||||
}
|
||||
|
||||
func Service(names ...string) *ServicePromise {
|
||||
return &ServicePromise{
|
||||
chain: nil,
|
||||
err: nil,
|
||||
names: names,
|
||||
states: nil,
|
||||
status: PROMISED,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServicePromise) State(states ...string) *ServicePromise {
|
||||
s.states = states
|
||||
return s
|
||||
}
|
||||
|
||||
type ServicePromise struct {
|
||||
chain []Promise
|
||||
err error
|
||||
names []string
|
||||
states []string
|
||||
status Status
|
||||
}
|
||||
|
||||
func (s *ServicePromise) IfRepaired(ps ...Promise) Promise {
|
||||
s.chain = ps
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *ServicePromise) Promise() Promise {
|
||||
services = append(services, s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *ServicePromise) Resolve() {
|
||||
for _, name := range s.names {
|
||||
var repaired = false
|
||||
for _, state := range s.states {
|
||||
s.status, s.err = serviceFunction(name, state)
|
||||
if s.status == BROKEN {
|
||||
slog.Error("service", "name", name, "state", state, "status", s.status, "error", s.err)
|
||||
return
|
||||
} else if s.status == REPAIRED {
|
||||
repaired = true
|
||||
}
|
||||
}
|
||||
if repaired {
|
||||
s.status = REPAIRED
|
||||
slog.Info("service", "name", name, "state", s.states, "status", s.status)
|
||||
} else {
|
||||
s.status = KEPT
|
||||
slog.Debug("service", "name", name, "state", s.states, "status", s.status)
|
||||
}
|
||||
}
|
||||
if s.status == REPAIRED {
|
||||
for _, pp := range s.chain {
|
||||
pp.Resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Internal --------------------------------------------------------------
|
||||
func resolveServices() (status Status) {
|
||||
status = KEPT
|
||||
for _, c := range services {
|
||||
if c.status == PROMISED {
|
||||
c.Resolve()
|
||||
switch c.status {
|
||||
case BROKEN:
|
||||
return BROKEN
|
||||
case REPAIRED:
|
||||
status = REPAIRED
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
44
pkg/templates.go
Normal file
44
pkg/templates.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package gonf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log/slog"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// ----- Globals ---------------------------------------------------------------
|
||||
var templates *template.Template
|
||||
|
||||
// ----- Init ------------------------------------------------------------------
|
||||
func init() {
|
||||
templates = template.New("")
|
||||
templates.Option("missingkey=error")
|
||||
templates.Funcs(builtinTemplateFunctions)
|
||||
}
|
||||
|
||||
// ----- Public ----------------------------------------------------------------
|
||||
type TemplateValue struct {
|
||||
contents []byte
|
||||
data string
|
||||
}
|
||||
|
||||
func (t *TemplateValue) Bytes() []byte {
|
||||
if t.contents == nil {
|
||||
tpl := templates.New("")
|
||||
if _, err := tpl.Parse(t.data); err != nil {
|
||||
slog.Error("template", "step", "Parse", "data", t.data, "error", err)
|
||||
return nil
|
||||
}
|
||||
var buff bytes.Buffer
|
||||
if err := tpl.Execute(&buff, nil /* no data needed */); err != nil {
|
||||
slog.Error("template", "step", "Execute", "data", t.data, "error", err)
|
||||
return nil
|
||||
}
|
||||
t.contents = buff.Bytes()
|
||||
}
|
||||
return t.contents
|
||||
}
|
||||
|
||||
func (t TemplateValue) String() string {
|
||||
return string(t.Bytes()[:])
|
||||
}
|
11
pkg/triggers.go
Normal file
11
pkg/triggers.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package gonf
|
||||
|
||||
type simpleTrigger struct {
|
||||
fact string
|
||||
status Status
|
||||
}
|
||||
|
||||
//type compositeTrigger struct {
|
||||
// triggers []simpleTrigger
|
||||
// operation Operation
|
||||
//}
|
27
pkg/utils.go
Normal file
27
pkg/utils.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package gonf
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
)
|
||||
|
||||
var builtinTemplateFunctions = map[string]any{
|
||||
//"encodeURIQueryParameter": url.QueryEscape,
|
||||
"var": getVariable,
|
||||
}
|
||||
|
||||
func FilterSlice[T any](slice *[]T, predicate func(T) bool) {
|
||||
i := 0
|
||||
for _, element := range *slice {
|
||||
if predicate(element) { // if the element matches the predicate function
|
||||
(*slice)[i] = element // then we keep it in the slice
|
||||
i++
|
||||
} // otherwise the element will get overwritten
|
||||
}
|
||||
*slice = (*slice)[:i] // or truncated out of the slice
|
||||
}
|
||||
|
||||
func sha256sum(contents []byte) []byte {
|
||||
h := sha256.New()
|
||||
h.Write(contents)
|
||||
return h.Sum(nil)
|
||||
}
|
104
pkg/values.go
Normal file
104
pkg/values.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
package gonf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Value interface {
|
||||
Bytes() []byte
|
||||
String() string
|
||||
}
|
||||
|
||||
func interfaceToValue(v any) Value {
|
||||
if vv, ok := v.([]byte); ok {
|
||||
return &BytesValue{vv}
|
||||
}
|
||||
if vv, ok := v.(int); ok {
|
||||
return &IntValue{vv}
|
||||
}
|
||||
if vv, ok := v.(string); ok {
|
||||
return &StringValue{vv}
|
||||
}
|
||||
if vv, ok := v.(*VariablePromise); ok {
|
||||
return vv
|
||||
}
|
||||
slog.Error("interfaceToValue", "value", v, "error", "Not Implemented")
|
||||
panic(fmt.Sprintf("interfaceToValue cannot take type %T as argument. Value was %#v.", v, v))
|
||||
}
|
||||
|
||||
func interfaceToTemplateValue(v any) Value {
|
||||
if vv, ok := v.([]byte); ok {
|
||||
return &TemplateValue{data: string(vv)}
|
||||
}
|
||||
if vv, ok := v.(int); ok {
|
||||
return &IntValue{vv}
|
||||
}
|
||||
if vv, ok := v.(string); ok {
|
||||
return &TemplateValue{data: vv}
|
||||
}
|
||||
if vv, ok := v.(*VariablePromise); ok {
|
||||
return vv
|
||||
}
|
||||
slog.Error("interfaceToTemplateValue", "value", v, "error", "Not Implemented")
|
||||
panic(fmt.Sprintf("interfaceToTemplateValue cannot take type %T as argument. Value was %#v.", v, v))
|
||||
}
|
||||
|
||||
// ----- BytesValue ------------------------------------------------------------
|
||||
type BytesValue struct {
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (b BytesValue) Bytes() []byte {
|
||||
return b.value
|
||||
}
|
||||
func (b BytesValue) String() string {
|
||||
return string(b.value[:])
|
||||
}
|
||||
|
||||
// ----- IntValue --------------------------------------------------------------
|
||||
type IntValue struct {
|
||||
value int
|
||||
}
|
||||
|
||||
func (i IntValue) Bytes() []byte {
|
||||
return []byte(string(i.value))
|
||||
}
|
||||
func (i IntValue) Int() int {
|
||||
return i.value
|
||||
}
|
||||
func (i IntValue) String() string {
|
||||
return string(i.value)
|
||||
}
|
||||
|
||||
// ----- StringsListValue ------------------------------------------------------
|
||||
type StringsListValue struct {
|
||||
value []string
|
||||
}
|
||||
|
||||
func (s *StringsListValue) Append(v ...string) {
|
||||
s.value = append(s.value, v...)
|
||||
}
|
||||
func (s StringsListValue) Bytes() []byte {
|
||||
return []byte(s.String())
|
||||
}
|
||||
func (s StringsListValue) String() string {
|
||||
return strings.Join(s.value, "\n")
|
||||
}
|
||||
|
||||
// ----- StringValue -----------------------------------------------------------
|
||||
type StringValue struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func (s StringValue) Bytes() []byte {
|
||||
return []byte(s.value)
|
||||
}
|
||||
func (s StringValue) String() string {
|
||||
return s.value
|
||||
}
|
||||
|
||||
// TODO maps
|
||||
|
||||
// TODO what else?
|
85
pkg/variables.go
Normal file
85
pkg/variables.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package gonf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// ----- Globals ---------------------------------------------------------------
|
||||
var variables map[string]*VariablePromise
|
||||
|
||||
// ----- Init ------------------------------------------------------------------
|
||||
func init() {
|
||||
variables = make(map[string]*VariablePromise)
|
||||
}
|
||||
|
||||
// ----- Public ----------------------------------------------------------------
|
||||
func AppendVariable(name string, values ...string) *VariablePromise {
|
||||
if v, ok := variables[name]; ok {
|
||||
if l, ok := v.value.(*StringsListValue); ok {
|
||||
l.Append(values...)
|
||||
}
|
||||
return v
|
||||
}
|
||||
v := &VariablePromise{
|
||||
isDefault: false,
|
||||
name: name,
|
||||
value: &StringsListValue{values},
|
||||
}
|
||||
variables[name] = v
|
||||
return v
|
||||
}
|
||||
|
||||
func Default(name string, value string) *VariablePromise {
|
||||
if v, ok := variables[name]; ok {
|
||||
if !v.isDefault {
|
||||
slog.Debug("default would overwrite a variable, ignoring", "name", name, "old_value", v.value, "new_value", value)
|
||||
return nil
|
||||
}
|
||||
slog.Error("default is being overwritten", "name", name, "old_value", v.value, "new_value", value)
|
||||
}
|
||||
v := &VariablePromise{
|
||||
isDefault: true,
|
||||
name: name,
|
||||
value: interfaceToTemplateValue(value),
|
||||
}
|
||||
variables[name] = v
|
||||
return v
|
||||
}
|
||||
|
||||
func Variable(name string, value string) *VariablePromise {
|
||||
if v, ok := variables[name]; ok && !v.isDefault {
|
||||
slog.Error("variable is being overwritten", "name", name, "old_value", v, "new_value", value)
|
||||
}
|
||||
v := &VariablePromise{
|
||||
isDefault: false,
|
||||
name: name,
|
||||
value: interfaceToTemplateValue(value),
|
||||
}
|
||||
variables[name] = v
|
||||
return v
|
||||
}
|
||||
|
||||
type VariablePromise struct {
|
||||
isDefault bool
|
||||
name string
|
||||
value Value
|
||||
}
|
||||
|
||||
// We want VariablePromise to satisfy the Value interface
|
||||
func (s VariablePromise) Bytes() []byte {
|
||||
return s.value.Bytes()
|
||||
}
|
||||
func (s VariablePromise) String() string {
|
||||
return s.value.String()
|
||||
}
|
||||
|
||||
// ----- Internal --------------------------------------------------------------
|
||||
func getVariable(name string) string {
|
||||
if v, ok := variables[name]; ok {
|
||||
return v.value.String()
|
||||
} else {
|
||||
slog.Error("undefined variable or default", "name", name)
|
||||
panic(fmt.Sprintf("undefined variable or default %s", name))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue