package db import ( "encoding/json" "encoding/xml" "fmt" "github.com/pelletier/go-toml/v2" "gopkg.in/ini.v1" "gopkg.in/yaml.v3" "os" "strconv" "strings" ) // AppConfig 程序启动配置 type AppConfig struct { Name string `json:"name,omitempty" yaml:"name,omitempty" toml:"name,multiline,omitempty" xml:"name,omitempty" ini:"name,omitempty"` // 应用名称 Description string `json:"description,omitempty" yaml:"description,omitempty" toml:"description,multiline,omitempty" xml:"description,omitempty" ini:"description,omitempty"` // 应用描述 Script string `json:"script,omitempty" yaml:"script,omitempty" toml:"script,multiline,omitempty" xml:"script,omitempty" ini:"script,omitempty"` // 执行脚本 Args []string `json:"args,omitempty" yaml:"args,omitempty" toml:"args,multiline,omitempty" xml:"args,omitempty" ini:"args,omitempty"` // 启动参数 Stdin string `json:"stdin,omitempty" yaml:"stdin,omitempty" toml:"stdin,multiline,omitempty" xml:"stdin,omitempty" ini:"stdin,omitempty"` // 标准输入数据 Cwd string `json:"Cwd,omitempty" yaml:"Cwd,omitempty" toml:"Cwd,multiline,omitempty" xml:"Cwd,omitempty" ini:"cwd,omitempty"` // 工作目录 Env map[string]string `json:"env,omitempty" yaml:"env,omitempty" toml:"env,multiline,omitempty" xml:"env,omitempty" ini:"env,omitempty"` // 自定义环境变量 Interpreter string `json:"interpreter,omitempty" yaml:"interpreter,omitempty" toml:"interpreter,multiline,omitempty" xml:"interpreter,omitempty" ini:"interpreter,omitempty"` // 脚本解释程序 InterpreterArgs []string `json:"interpreter_args,omitempty" yaml:"interpreter_args,omitempty" toml:"interpreter_args,multiline,omitempty" xml:"interpreter_args,omitempty" ini:"interpreterArgs,omitempty"` // 解释程序参数 RerunOnError *RerunOnError `json:"rerunOnError" yaml:"rerunOnError,omitempty" toml:"rerunOnError,multiline,omitempty" xml:"rerunOnError,omitempty" ini:"rerunOnError,omitempty"` // 错误重启策略 Custom map[string]any `json:"custom,omitempty" yaml:"custom,omitempty" toml:"custom,multiline,omitempty" xml:"custom,omitempty" ini:"custom,omitempty"` // 其它自定义参数 } type Unmarshaller func(data []byte, v any) error func ParseInput(input string) ([]*App, error) { var configs []AppConfig var err error var app *App if IsAppID(input) { var id int if id, err = strconv.Atoi(input); err != nil { return nil, fmt.Errorf("invalid identifier") } app, err = FindApp(WithAppID(uint(id))) } else if strings.HasSuffix(input, ".pmt.json") { configs, err = Unmarshal(input, json.Unmarshal) } else if strings.HasSuffix(input, ".pmt.toml") { configs, err = Unmarshal(input, toml.Unmarshal) } else if strings.HasSuffix(input, ".pmt.yaml") { configs, err = Unmarshal(input, yaml.Unmarshal) } else if strings.HasSuffix(input, ".pmt.xml") { configs, err = Unmarshal(input, xml.Unmarshal) } else if strings.HasSuffix(input, ".pmt.ini") { configs, err = Unmarshal(input, func(data []byte, v any) error { if info, err := ini.Load(data); err != nil { return err } else { return info.MapTo(v) } }) } else if file, ex := os.Stat(input); ex == nil { if file.IsDir() { return nil, fmt.Errorf("unsupported directoty entry") } if app, err = scriptToApp(input); err != nil { return nil, err } } else { app, err = FindApp(WithAppName(input)) } if err != nil { return nil, err } if app != nil { return []*App{app}, nil } if len(configs) == 0 { return nil, fmt.Errorf("not found") } var apps []*App for _, cfg := range configs { apps = append(apps, optionsToApp(&cfg)) } return apps, err } func scriptToApp(script string) (*App, error) { name := ToSnakeCase(script) app, err := FindApp(WithAppName(name)) if err == nil { if app.Script != script { return nil, fmt.Errorf("应用 %s 已存在", script) } return app, nil } return &App{ PID: -1, Name: name, Script: script, }, nil } func optionsToApp(opts *AppConfig) *App { return &App{ PID: -1, Name: opts.Name, Description: opts.Description, Script: opts.Script, Command: nil, Arguments: opts.Args, Stdin: opts.Stdin, Cwd: opts.Cwd, Environments: opts.Env, Interpreter: opts.Interpreter, InterpreterArgs: opts.InterpreterArgs, RerunOnError: opts.RerunOnError, Options: opts.Custom, } } // Unmarshal 读取配置文件 func Unmarshal(file string, unmarshal Unmarshaller) ([]AppConfig, error) { bts, err := os.ReadFile(file) if err != nil { return nil, err } var config AppConfig if err = unmarshal(bts, &config); err == nil { return []AppConfig{config}, nil } var configs []AppConfig if unmarshal(bts, &configs) == nil { return configs, nil } return nil, err }