初始化项目

main
熊二 2 years ago
commit a0062b93b2
  1. 6
      .gitignore
  2. 379
      cmd/command.go
  3. 542
      cmd/option.go
  4. 388
      cmd/print.go
  5. 138
      db/config.go
  6. 117
      db/db.go
  7. 279
      db/proc.go
  8. 66
      db/utils.go
  9. 74
      db/worker.go
  10. 21
      go.mod
  11. 41
      go.sum
  12. 197
      main.go
  13. 3
      readme.md
  14. 17
      tools/start.go

6
.gitignore vendored

@ -0,0 +1,6 @@
.idea
tmp
*.db
*.iml
.env
.env.*

@ -0,0 +1,379 @@
package cmd
import (
"errors"
"fmt"
"os"
"strings"
)
type DyadicValue struct {
Value any
Another any
}
func (d *DyadicValue) GetValue() any {
if d.Value != nil {
return d.Value
}
return d.Another
}
type CommandAction func(*Command, map[string]any, ...any) error
type Command struct {
name string
usage *DyadicValue
version *Option
help *Option
description string
longDescription string
arguments []*Argument
options []*Option
types map[string]TypeParser
globalTypes map[string]TypeParser
commands []*Command
program *Command
action CommandAction
}
func New(name string, desc ...string) *Command {
cmd := &Command{
types: make(map[string]TypeParser),
}
cmd.Version("1.0.0").Help("")
switch len(desc) {
case 0:
return cmd.Name(name)
case 1:
return cmd.Name(name).Description(desc[0])
default:
return cmd.Name(name).Description(desc[0], desc[1:]...)
}
}
func (c *Command) Name(name string) *Command {
if len(name) == 0 {
panic(errors.New("empty command name"))
}
// 仅仅支持二级,所以只需要检查 Command.program.commands 中是否重复
if c.program != nil {
for _, command := range c.program.commands {
if command.name == name {
panic(errors.New("duplicated command name"))
}
}
}
c.name = name
return c
}
func (c *Command) GetName() string {
return c.name
}
func (c *Command) Type(name string, parser func(...string) any) *Command {
c.types[name] = parser
return c
}
func (c *Command) GlobalType(name string, parser func(...string) any) *Command {
if c.program != nil {
c.program.GlobalType(name, parser)
} else {
c.globalTypes[name] = parser
}
return c
}
func (c *Command) Command(name string, desc ...string) *Command {
program := c.program
if program == nil {
program = c
}
cmd := New(name, desc...)
cmd.program = program
program.commands = append(program.commands, cmd)
return cmd
}
func (c *Command) Version(ver string) *Command {
if c.version == nil {
c.VersionOption("-v, --version", "Show version number")
}
if len(ver) > 0 {
c.version.RuntimeValue = []string{ver}
} else {
c.version.RuntimeValue = make([]string, 0)
}
return c
}
func (c *Command) VersionOption(expr string, desc ...string) *Command {
ver, err := ensureOption(c, expr, desc, "version")
if errors.Is(err, ErrOptionDefinition) {
panic(ErrVersionDefinition)
} else if err != nil {
panic(err)
}
if c.help != nil {
err = hasConflicts(c.help, ver)
if err != nil {
panic(errors.New("version option conflicts with help option, " + err.Error()))
}
}
c.version = ver
return c
}
func (c *Command) GetVersion(opts ...*ShowOptions) string {
if c.program != nil {
return c.program.GetVersion()
}
if c.version == nil {
c.Version("1.0.0")
}
return versionToString(c, resolveShowOptions(opts))
}
func (c *Command) ShowVersion(opts ...*ShowOptions) error {
return showMessage(resolveShowOptions(opts), c.GetVersion)
}
func (c *Command) Usage(usage string) *Command {
if c.usage == nil {
c.usage = new(DyadicValue)
}
c.usage.Value = usage
return c
}
func (c *Command) GetUsage(opts ...*ShowOptions) string {
if c.usage != nil {
usage := c.usage.GetValue()
if usage != nil {
return usage.(string)
}
}
s := usageToString(c, resolveShowOptions(opts))
c.usage = new(DyadicValue)
c.usage.Another = s
return s
}
func (c *Command) ShowUsage(opts ...*ShowOptions) error {
return showMessage(resolveShowOptions(opts), c.GetUsage)
}
func (c *Command) Description(desc string, longDesc ...string) *Command {
c.description = desc
if len(longDesc) > 0 {
c.longDescription = strings.Join(longDesc, "\n")
}
return c
}
func (c *Command) GetDescription(opts ...*ShowOptions) string {
o := resolveShowOptions(opts)
var s string
if o.Long {
s = c.longDescription
} else {
s = c.description
}
if len(s) == 0 {
return ""
}
if o.Titles {
return "DESCRIPTION:\n " + s
}
return s
}
func (c *Command) ShowDescription(opts ...*ShowOptions) error {
return showMessage(resolveShowOptions(opts), c.GetDescription)
}
func (c *Command) Help(help string) *Command {
if c.help == nil {
c.HelpOption("-h, --help", "Show help information")
}
if len(help) > 0 {
c.help.RuntimeValue = []string{help}
} else {
c.help.RuntimeValue = make([]string, 0)
}
return c
}
func (c *Command) HelpOption(expr string, desc ...string) *Command {
help, err := ensureOption(c, expr, desc, "help")
if errors.Is(err, ErrOptionDefinition) {
panic(ErrVersionDefinition)
} else if err != nil {
panic(err)
}
if c.version != nil {
err = hasConflicts(help, c.version)
if err != nil {
panic(errors.New("help option conflicts with version option, " + err.Error()))
}
}
c.help = help
return c
}
func (c *Command) GetHelp(opts ...*ShowOptions) string {
if c.help != nil {
if len(c.help.RuntimeValue) > 0 {
return c.help.RuntimeValue[0]
}
if c.help.DefaultValue != nil {
return c.help.DefaultValue.(string)
}
}
s := helpToString(c, resolveShowOptions(opts))
c.help.DefaultValue = s
return s
}
func (c *Command) ShowHelp(opts ...*ShowOptions) error {
return showMessage(resolveShowOptions(opts), c.GetHelp)
}
func (c *Command) Argument(expr string, desc ...string) *Command {
a, err := createArgument(expr)
if err != nil {
panic(err)
}
switch len(desc) {
case 0:
break
case 1:
a.Description = desc[0]
default:
a.Description = desc[0]
a.LongDescription = desc[1]
}
var lastArg *Argument
for _, a2 := range c.arguments {
lastArg = a2
if a2.Name == a.Name {
panic(errors.New("duplicated arguments definition"))
}
}
if lastArg != nil {
if lastArg.Variadic {
panic(errors.New("after variadic argument"))
}
if a.Required && !lastArg.Required {
panic(errors.New("after optional argument"))
}
}
c.arguments = append(c.arguments, a)
return c
}
func (c *Command) Option(expr string, desc ...string) *Command {
o, err := ensureOption(c, expr, desc, "")
if err != nil {
panic(err)
}
if c.version != nil {
err = hasConflicts(c.version, o)
if err != nil {
panic(err)
}
}
if c.help != nil {
err = hasConflicts(c.help, o)
if err != nil {
panic(err)
}
}
c.options = append(c.options, o)
return c
}
func (c *Command) Action(action func(*Command, map[string]any, ...any) error) *Command {
c.action = action
return c
}
func (c *Command) Run(args ...[]string) error {
var argv []string
for _, arg := range args {
argv = arg
break
}
if argv == nil {
argv = os.Args[1:]
}
if c.program != nil {
return c.program.Run(argv)
}
command := c
cmdIdx := -1
for i, arg := range argv {
if !strings.HasPrefix(argv[0], "-") {
for _, cmd := range c.commands {
if cmd.name == arg {
command = cmd
cmdIdx = i
break
}
}
break
}
}
if cmdIdx == 0 {
argv = argv[1:]
} else if cmdIdx == len(argv)-1 {
argv = argv[:cmdIdx]
} else if cmdIdx > -1 {
argv = append(argv[:cmdIdx], argv[cmdIdx+1:]...)
}
help, version, options, arguments := parseArgs(argv, command)
opts := resolveShowOptions([]*ShowOptions{})
if help {
return command.ShowHelp(opts)
} else if version {
return command.ShowVersion(&ShowOptions{
Colors: opts.Colors,
Types: opts.Types,
Long: opts.Long,
Titles: false,
LineWidth: opts.LineWidth,
Writer: opts.Writer,
})
} else if command.action == nil {
fmt.Println("没有操作,则显示帮助文档")
// 没有操作,则显示帮助文档
return command.ShowHelp()
} else {
return command.action(command, options, arguments...)
}
}
func hasConflicts(o1, o2 *Option) error {
if len(o1.Short) > 0 && o1.Short == o2.Short {
return errors.New("duplicated short flag of option definition")
}
if o1.Long == o2.Long {
return errors.New("duplicated long flag of option definition")
}
if o1.Name == o2.Name {
return errors.New("duplicated arguments definition")
}
return nil
}

@ -0,0 +1,542 @@
package cmd
import (
"errors"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
)
var (
ErrArgumentDefinition = errors.New("invalid createArgument definition")
ErrOptionDefinition = errors.New("invalid createOption definition")
ErrVersionDefinition = errors.New("invalid version definition")
optionArgumentSplitRE *regexp.Regexp = regexp.MustCompile(`[, ] *`)
tokenRE *regexp.Regexp = regexp.MustCompile("^[a-z]\\w*$")
)
type Option struct {
Name string // 参数名称
Type string // 数据类型
Short string // 段名称,e.g. -w
Long string // 长名称,e.g. --watch
Description string // 简短描述
LongDescription string // 详细描述
Variadic bool // 是否支持多个值
Required bool // 是否必须
DefaultValue any // 默认值
RuntimeValue []string // 运行时的值
}
func (o *Option) toArgument() *Argument {
return &Argument{
Name: o.Name,
Type: o.Type,
Description: o.Description,
LongDescription: o.LongDescription,
Variadic: o.Variadic,
Required: o.Required,
DefaultValue: o.DefaultValue,
}
}
type Argument struct {
Name string // 参数名称
Type string // 数据类型
Description string // 简短描述
LongDescription string // 详细描述
Variadic bool // 是否支持多个值
Required bool // 是否必须
DefaultValue any // 默认值
}
// createArgument 解析参数
//
// <Name>、<Name:string>、<Name...:string>
// [Name]、[Name:string]、[Name...:string]
func createArgument(expr string) (*Argument, error) {
if len(expr) == 0 {
return nil, ErrArgumentDefinition
}
var a Argument
if strings.HasPrefix(expr, "[") {
if !strings.HasSuffix(expr, "]") {
return nil, ErrArgumentDefinition
}
a.Required = true
} else if strings.HasPrefix(expr, "<") {
if !strings.HasSuffix(expr, ">") {
return nil, ErrArgumentDefinition
}
a.Required = false
} else {
return nil, ErrArgumentDefinition
}
// 移除 [] <>
expr = expr[1 : len(expr)-1]
// 没有参数名称
if len(expr) == 0 {
return nil, ErrArgumentDefinition
}
parts := strings.Split(expr, ":")
switch len(parts) {
case 1:
a.Name = parts[0]
case 2:
a.Name = parts[0]
a.Type = parts[1]
// 支持多个值
if strings.HasPrefix(a.Type, "...") {
a.Type = a.Type[3:]
a.Variadic = true
}
default:
return nil, ErrArgumentDefinition
}
// 验证名称是否合法
if !tokenRE.MatchString(a.Name) {
return nil, ErrArgumentDefinition
}
// 类型名称错误
if len(a.Type) > 0 && !tokenRE.MatchString(a.Type) {
fmt.Println(a.Type)
return nil, ErrArgumentDefinition
}
return &a, nil
}
func createOption(expr string) (*Option, error) {
if len(expr) == 0 {
return nil, ErrArgumentDefinition
}
var o Option
shortIndex := -1
longIndex := -1
nameIndex := -1
parts := optionArgumentSplitRE.Split(expr, -1)
for i, part := range parts {
barLen := computeBarLen(part)
// 解析的时参数
if barLen == 0 {
// 存在多个参数
if nameIndex > -1 {
return nil, ErrOptionDefinition
}
nameIndex = i
a, err := createArgument(part)
// 参数格式错误
if errors.Is(err, ErrArgumentDefinition) {
return nil, ErrOptionDefinition
}
o.Name = a.Name
o.Type = a.Type
o.Variadic = a.Variadic
o.Required = a.Required
} else if barLen == 1 {
// 短名称重复
if shortIndex > -1 {
return nil, ErrOptionDefinition
}
shortIndex = i
o.Short = part[barLen:]
} else if barLen == 2 {
if longIndex > -1 {
return nil, ErrOptionDefinition
}
longIndex = i
o.Long = part[barLen:]
} else {
// 3个或超过3个横杠
return nil, ErrOptionDefinition
}
// 严格检查顺序
var positionError bool
if nameIndex > -1 {
positionError = nameIndex < longIndex || nameIndex < shortIndex
} else if longIndex > -1 {
positionError = longIndex < shortIndex
}
if positionError {
return nil, ErrOptionDefinition
}
}
// 验证短名称是否合法
if shortIndex > -1 && (len(o.Short) != 1 || !tokenRE.MatchString(o.Short)) {
return nil, ErrOptionDefinition
}
// 未解析到长名称或者长名称不合法
if longIndex == -1 || !tokenRE.MatchString(o.Long) {
return nil, ErrOptionDefinition
}
// 未指定名称,则使用 long flag
if nameIndex == -1 {
o.Name = o.Long
}
return &o, nil
}
func computeBarLen(str string) int {
for i, s := range str {
if s != '-' {
return i
}
}
return 0
}
type TypeParser func(...string) any
func parseValue(cmd *Command, typ string, values []string, defaultValue any, variadic bool) any {
if parser, ok := getTypeParser(cmd, typ); ok {
return parser(values...)
} else if l := len(values); l > 0 {
switch typ {
case "bool":
if l == 0 {
if variadic {
return []bool{}
}
return true
}
var parsed []bool
for _, value := range values {
v, e := strconv.ParseBool(value)
if e != nil {
panic(errors.New("invalid value of type " + typ))
}
if variadic {
parsed = append(parsed, v)
} else if l == 1 {
return v
} else {
panic(errors.New("found multi values"))
}
}
return parsed
case "string":
if variadic {
return values[:]
} else if l == 0 {
return ""
} else if l == 1 {
return values[0]
} else {
panic(errors.New("found multi values"))
}
case "float":
if l == 0 {
if variadic {
return []float64{}
}
return float64(0)
}
var parsed []float64
for _, value := range values {
v, e := strconv.ParseFloat(value, 64)
if e != nil {
panic(errors.New("invalid value of type " + typ))
}
if variadic {
parsed = append(parsed, v)
} else if l == 1 {
return v
} else {
panic(errors.New("found multi values"))
}
}
return parsed
case "int":
if l == 0 {
if variadic {
return []int64{}
}
return int64(0)
}
var parsed []int64
for _, value := range values {
v, e := strconv.ParseInt(value, 10, 64)
if e != nil {
panic(errors.New("invalid value of type " + typ))
}
if variadic {
parsed = append(parsed, v)
} else if l == 1 {
return v
} else {
panic(errors.New("found multi values"))
}
}
return parsed
case "uint":
if l == 0 {
if variadic {
return []uint64{}
}
return int64(0)
}
var parsed []uint64
for _, value := range values {
v, e := strconv.ParseUint(value, 10, 64)
if e != nil {
panic(errors.New("invalid value of type " + typ))
}
if variadic {
parsed = append(parsed, v)
} else if l == 1 {
return v
} else {
panic(errors.New("found multi values"))
}
}
return parsed
default:
if variadic {
return values[:]
} else if l == 0 {
return nil
} else if l == 1 {
return values[0]
} else {
panic(errors.New("found multi values"))
}
}
} else if defaultValue != nil {
return defaultValue
} else {
return nil
}
}
func getTypeParser(cmd *Command, typ string) (TypeParser, bool) {
if cmd.types != nil {
if parser, ok := cmd.types[typ]; ok {
return parser, true
}
}
if cmd.globalTypes != nil {
if parser, ok := cmd.globalTypes[typ]; ok {
return parser, true
}
}
if cmd.program != nil && cmd.program.globalTypes != nil {
parser, ok := cmd.program.globalTypes[typ]
return parser, ok
}
return nil, false
}
func parseArgs(args []string, cmd *Command) (help bool, version bool, options map[string]any, arguments []any) {
var removes []int
var foundOption *Option
var variadicValues []string // 可变参数值
var variadicOption *Option // 可变参数选项
variadicStart := -1 // 可变参数开始位置
variadicStop := -1 // 可变参数结束位置(不包含)
foundPosition := -1
findOption := func(key string, isLong, isShort bool) *Option {
for _, o := range cmd.options {
if (isLong && o.Long == key) || (isShort && o.Short == key) {
return o
}
}
return nil
}
saveParsedVariadicValues := func() {
if variadicOption == nil {
return
}
for i := variadicStart; i < variadicStop; i++ {
removes = append(removes, i)
}
variadicOption.RuntimeValue = variadicValues[:]
variadicOption = nil
variadicValues = []string{}
variadicStart = -1
variadicStop = -1
}
saveFound := func(value string) {
if foundOption == nil {
return
}
if len(value) > 0 {
foundOption.RuntimeValue = []string{value}
}
removes = append(removes, foundPosition, foundPosition+1) // foundPosition+1 可能会越界
foundOption = nil
foundPosition = -1
}
for i := 0; i < len(args); i++ {
v := args[i]
barLen := computeBarLen(v)
var isShort bool
var isLong bool
switch barLen {
case 0:
if variadicOption != nil {
variadicValues = append(variadicValues, v)
} else /*if foundOption != nil*/ {
saveFound(v)
}
continue
case 1:
isShort = true
v = v[1:]
case 2:
isLong = true
v = v[2:]
default:
panic("invalid argument")
}
// 保存之前找到的参数
variadicStop = i
saveParsedVariadicValues()
saveFound("")
// 解析参数
var val string
var key string
var equalsSign bool
if j := strings.IndexByte(v, '='); j > -1 {
equalsSign = true
key = v[:j]
if j < len(v)-1 {
val = v[j+1:]
}
} else {
key = v
}
// 是不是帮助命令
if cmd.help != nil && ((isShort && cmd.help.Short == key) || (isLong && cmd.help.Long == key)) {
help = true
removes = append(removes, i)
continue
}
// 是不是版本命令
if cmd.version != nil && ((isShort && cmd.version.Short == key) || (isLong && cmd.version.Long == key)) {
version = true
removes = append(removes, i)
continue
}
opt := findOption(key, isLong, isShort)
if opt == nil {
// TODO show help information
panic("unsupported flag \"" + v + "\"")
}
foundOption = opt
foundPosition = i
// 后面的不解析
if equalsSign {
if opt.Variadic {
// TODO show help information
panic("invalid values for flag \"" + v + "\"")
}
saveFound(val)
continue
}
if opt.Variadic {
foundOption = nil
foundPosition = -1
variadicOption = opt
variadicStart = i
}
}
if variadicOption != nil {
variadicStop = len(args)
saveParsedVariadicValues()
} else {
saveFound("")
}
// 移除上面解析 flags 标记的参数
temp := args[:]
sort.Sort(sort.Reverse(sort.IntSlice(removes)))
for _, remove := range removes {
l := len(temp)
if remove >= l {
continue
}
if l == 0 {
break
}
if remove != len(temp)-1 {
temp = append(temp[:remove], temp[remove+1:]...)
} else {
temp = temp[:remove]
}
}
// 参数赋值
arguments = make([]any, len(cmd.arguments))
l := len(temp) - 1
for i, arg := range cmd.arguments {
// 可变参数
if arg.Variadic {
if i <= l {
arguments[i] = parseValue(cmd, arg.Type, temp[i:], arg.DefaultValue, true)
} else if arg.DefaultValue == nil {
if arg.Required {
panic(errors.New("missing argument with " + arg.Name))
}
arguments[i] = []any{}
} else if list, ok := arg.DefaultValue.([]any); ok {
arguments[i] = list
} else {
panic(errors.New("invalid default value, except a(n) `[]any`"))
}
break
}
// 找到输入的参数
if i <= l {
arguments[i] = parseValue(cmd, arg.Type, []string{temp[i]}, arg.DefaultValue, false)
} else if arg.DefaultValue != nil {
// 启用默认值
arguments[i] = arg.DefaultValue
} else if arg.Required {
panic(errors.New("missing argument with " + arg.Name))
} else {
arguments[i] = nil
}
}
// 解析出需要的可选参数
options = make(map[string]any)
for _, o := range cmd.options {
options[o.Name] = parseValue(cmd, o.Type, o.RuntimeValue, o.DefaultValue, o.Variadic)
o.RuntimeValue = nil
}
return
}

@ -0,0 +1,388 @@
package cmd
import (
"errors"
"fmt"
"github.com/mattn/go-isatty"
"io"
"os"
"regexp"
"strings"
)
var (
colorRE = regexp.MustCompile(`\x1b[;?[0-9]+m`)
defaultShowOptions = &ShowOptions{Colors: true, LineWidth: 80, Titles: true}
)
type ShowWriter interface {
io.Writer
Fd() uintptr
}
type ShowOptions struct {
Colors bool
Types bool
Long bool
Titles bool
LineWidth int
Writer ShowWriter
}
func length(str string) int {
return len(colorRE.ReplaceAllString(str, ""))
}
func colorize(enable bool, color int, str string) string {
if enable {
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", color, str)
}
return str
}
func Blue(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 34, str)
}
func Red(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 31, str)
}
func Yellow(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 33, str)
}
func Cyan(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 35, str)
}
func Green(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 32, str)
}
func resolveShowOptions(opts []*ShowOptions) *ShowOptions {
for _, opt := range opts {
if opt.LineWidth == 0 {
opt.LineWidth = 80
}
return opt
}
return defaultShowOptions
}
func showMessage(opts *ShowOptions, f func(...*ShowOptions) string) error {
out := opts.Writer
if out == nil {
out = os.Stdout
}
if opts.Colors && (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(out.Fd()) && !isatty.IsCygwinTerminal(out.Fd()))) {
opts.Colors = false
}
_, err := fmt.Fprintln(out, f(opts))
return err
}
func versionToString(cmd *Command, opts *ShowOptions) string {
var str string
if len(cmd.version.RuntimeValue) > 0 {
str = cmd.version.RuntimeValue[0]
} else {
str = cmd.version.DefaultValue.(string)
}
if opts.Colors {
str = Green(str, opts)
}
if opts.Titles {
str = "VERSION: " + str
}
return str
}
func usageToString(cmd *Command, opts *ShowOptions) string {
var str string
if opts.Titles {
str += "USAGE:"
}
if cmd.program != nil {
str += " " + Cyan(cmd.program.name, opts)
}
str += " " + Cyan(cmd.name, opts)
if len(cmd.options) > 0 {
str += " " + Yellow("[", opts) + Cyan("OPTIONS", opts) + Yellow("]", opts)
}
for _, a := range cmd.arguments {
str += " " + argumentToString(a, opts)
}
return strings.TrimSpace(str)
}
func argumentToString(a *Argument, opts *ShowOptions) string {
var str string
if a.Required {
str += Yellow("<", opts)
} else {
str += Yellow("[", opts)
}
str += Cyan(a.Name, opts)
if opts.Types && len(a.Type) > 0 {
str += ":"
if a.Variadic {
str += "..."
}
str += Green(a.Type, opts)
}
if a.Required {
str += Yellow(">", opts)
} else {
str += Yellow("]", opts)
}
return str
}
func helpToString(cmd *Command, opts *ShowOptions) string {
var str string
if usage := cmd.GetUsage(opts); len(usage) > 0 {
str += usage + "\n"
}
if version := cmd.GetVersion(opts); len(version) > 0 {
str += version + "\n"
}
if desc := cmd.GetDescription(opts); len(desc) > 0 {
str += "\n" + desc + "\n\n"
}
if options := optionsToString(cmd, opts); len(options) > 0 {
str += options + "\n"
}
if arguments := argumentsToString(cmd, opts); len(arguments) > 0 {
str += arguments + "\n"
}
if commands := commandsToString(cmd, opts); len(commands) > 0 {
str += commands + "\n"
}
return strings.TrimSpace(str) + "\n"
}
func commandsToString(cmd *Command, opts *ShowOptions) string {
if len(cmd.commands) == 0 {
return ""
}
tb := &table{
gaps: []string{" ", ""},
rows: [][]string{},
sizes: []int{0, 0},
}
for _, o := range cmd.commands {
tb.AddRow(
Cyan(o.name, opts),
o.description,
)
}
str := tb.String(" ", "", opts.LineWidth)
if opts.Titles {
return "SUBCOMMANDS:\n" + str
}
return str
}
func argumentsToString(cmd *Command, opts *ShowOptions) string {
if len(cmd.arguments) == 0 {
return ""
}
t := &table{
gaps: []string{" ", " ", ""},
sizes: []int{0, 0, 0},
rows: [][]string{},
}
if !opts.Types {
t.gaps[1] = ""
}
for _, a := range cmd.arguments {
var typ string
if opts.Types && len(a.Type) > 0 {
if a.Required {
typ += Yellow("<", opts)
} else {
typ += Yellow("[", opts)
}
typ += Cyan(a.Type, opts)
if a.Required {
typ += Yellow("<", opts)
} else {
typ += Yellow("[", opts)
}
}
t.AddRow(
Cyan(a.Name, opts),
typ,
a.Description,
)
}
if opts.Titles {
return "ARGUMENTS:\n" + t.String(" ", "", opts.LineWidth) + "\n"
}
return t.String(" ", "", opts.LineWidth) + "\n"
}
func optionsToString(cmd *Command, opts *ShowOptions) string {
if len(cmd.options) == 0 {
return ""
}
t := &table{
gaps: []string{" ", " ", ""},
sizes: []int{0, 0, 0},
rows: [][]string{},
}
for _, o := range cmd.options {
t.AddRow(
optionFlagsToString(o, opts),
argumentToString(o.toArgument(), opts),
o.Description,
)
}
str := t.String(" ", "", opts.LineWidth)
if opts.Titles {
return "OPTIONS:\n" + str + "\n"
}
return str + "\n"
}
func optionFlagsToString(o *Option, opts *ShowOptions) string {
var str string
if len(o.Short) > 0 {
str += Blue("-"+o.Short, opts) + ", "
} else {
// 保留 short 的位置
str += " "
}
return str + Blue("--"+o.Long, opts)
}
func ensureOption(c *Command, expr string, desc []string, owner string) (*Option, error) {
o1, err := createOption(expr)
if err != nil {
return nil, err
}
switch len(desc) {
case 0:
break
case 1:
o1.Description = desc[0]
default:
o1.Description = desc[0]
o1.LongDescription = desc[1]
}
for _, o2 := range c.options {
err := hasConflicts(o2, o1)
if err != nil {
if len(owner) > 0 {
return nil, errors.New(owner + " option conflicts with an optional parameter")
}
return nil, errors.New("option conflicts with another optional parameter")
}
}
return o1, nil
}
type table struct {
gaps []string
sizes []int
rows [][]string
}
var spaceRE = regexp.MustCompile(`[\t\n\v\f\r]+`)
var noNewlineSpaceRE = regexp.MustCompile(`[\t\v\f\r]+]`)
func (t *table) AddRow(columns ...string) {
l := len(columns)
width := len(t.sizes)
for i, column := range columns {
column = strings.TrimSpace(column)
if i < l-1 {
column = spaceRE.ReplaceAllString(column, "")
} else {
column = noNewlineSpaceRE.ReplaceAllString(column, "")
}
columns[i] = column
size := length(column)
if size > t.sizes[i] {
t.sizes[i] = size
}
}
for i := l; i < width; i++ {
columns = append(columns, "")
}
t.rows = append(t.rows, columns)
}
func (t *table) String(linePrefix, lineSuffix string, lineWidth int) string {
lastColumnIndex := len(t.sizes) - 1
lastColumnWidth := 0
var newlinePrefix string
var str string
for j, row := range t.rows {
line := linePrefix
for i, col := range row {
width := t.sizes[i]
// 不是最后一行
if i < lastColumnIndex {
line += col
count := length(col)
if count < width {
line += strings.Repeat(" ", width-count)
}
line += t.gaps[i]
continue
}
// 最后一行换行
if lastColumnWidth == 0 {
lastColumnWidth = lineWidth - length(line) - length(lineSuffix)
if lastColumnWidth < 10 {
lastColumnWidth = 10
}
newlinePrefix = strings.Repeat(" ", length(line))
}
for _, cline := range strings.Split(col, "\n") {
var s string
var w int
var x int
var y int
for _, word := range strings.Split(cline, " ") {
n := length(word)
if w+n > lastColumnWidth {
if y > 0 {
line += "\n" + newlinePrefix
}
line += s
y += 1
s = ""
w = 0
x = 0
continue
}
if x > 0 {
s += " "
}
s += word
w += n
x += 1
}
if x > 0 {
if y > 0 {
line += "\n" + newlinePrefix
}
line += s
}
}
}
if j > 0 {
str += "\n"
}
str += line
}
return str
}

@ -0,0 +1,138 @@
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
}

@ -0,0 +1,117 @@
package db
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"time"
)
var DB *gorm.DB
func init() {
var err error
DB, err = gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
if err != nil {
panic(err)
}
}
type App struct {
ID uint `json:"id" gorm:"primarykey"` // 应用ID
PID int `json:"pid"` // 应用PID
Name string `json:"name" gorm:"unique"` // 应用名称
Description string `json:"description"` // 应用描述
Script string `json:"script"` // 启动脚本
Command []string `json:"command" gorm:"serializer:json"` // 启动命令
Arguments []string `json:"arguments" gorm:"serializer:json"` // 启动参数
Stdin string `json:"stdin"` // 标准输入数据
Cwd string `json:"cwd"` // 工作目录
Environments map[string]string `json:"environments" gorm:"serializer:json"` // 自定义环境变量
Interpreter string `json:"interpreter"` // 脚本解释程序
InterpreterArgs []string `json:"interpreter_args" gorm:"serializer:json"` // 解释程序参数
Options map[string]any `json:"options" gorm:"serializer:json"` // 其它参数
RerunOnError *RerunOnError `json:"rerun_on_error" gorm:"serializer:json"` // 错误重启策略
StartCount int `json:"start_count"` // 应用启动次数
ErrorCount int `json:"error_count"` // 错误次数,配合 RerunOnError 使用
Status uint8 `json:"status"` // 应用状态
CreatedAt time.Time `json:"created_at"` // 创建时间
}
// RerunOnError 错误重启策略
type RerunOnError struct {
Enable bool `json:"enable"` // 是否支持错误重试
Count int64 `json:"count_on_error"` // 允许重试次数
}
func SaveApp(app *App) error {
if app.ID > 0 {
return DB.Save(app).Error
} else {
return DB.Create(app).Error
}
}
func WithAppID(id uint) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("id=?", id)
}
}
func WithAppName(name string) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("name=?", name)
}
}
func WithAppStatus(statuses ...uint32) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
switch len(statuses) {
case 0:
return db
case 1:
return db.Where("status=?", statuses[0])
default:
return db.Where("status IN ?", statuses)
}
}
}
// FindApp 查找一个 App
func FindApp(opts ...func(*gorm.DB) *gorm.DB) (*App, error) {
var app App
err := DB.Model(&App{}).Scopes(opts...).First(&app).Error
if err != nil {
return nil, err
}
return &app, nil
}
// FindApps 查找多个 App
func FindApps(opts ...func(*gorm.DB) *gorm.DB) ([]App, error) {
var apps []App
err := DB.Model(&App{}).Scopes(opts...).Find(&apps).Error
if err != nil {
return nil, err
}
return apps, nil
}
func ConfigPid(pid int) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Set("pid", pid)
}
}
func ConfigStatus(status uint8) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Set("status", status)
}
}
func IncreaseStartCounter(db *gorm.DB) *gorm.DB {
return db.Set("starts", gorm.Expr("starts + ?", 1))
}
func UpdateApp(id uint, opts ...func(*gorm.DB) *gorm.DB) error {
return DB.Model(&App{}).Scopes(opts...).Where("id=?", id).Error
}

@ -0,0 +1,279 @@
package db
import (
"errors"
"os"
"path/filepath"
"strconv"
"sync"
"syscall"
)
const (
StatusUnknown uint8 = iota // 未知状态
StatusStarting // 正在启动
StatusRunning // 正在运行
StatusStopping // 正在停止
StatusStopped // 已经停止
)
type Proc struct {
sync.RWMutex
tempDir string // 数据缓存目录
stdinFile string // 数据输入文件
stdoutFile string // 数据输出文件
stderrFile string // 错误输出文件
pidfile string // 记录PID的文件
starts int // 重启次数
status uint8 // 程序状态
pid int // 进程PID
app *App // 应用信息
process *os.Process // 系统进程
}
func NewProc(app *App) *Proc {
tempDir, err := TempDir(app.Name)
if err != nil {
panic(err)
}
proc := &Proc{
tempDir: tempDir,
stdinFile: filepath.Join(tempDir, "stdin.txt"),
stdoutFile: filepath.Join(tempDir, "stdout.txt"),
stderrFile: filepath.Join(tempDir, "stderr.txt"),
pidfile: filepath.Join(tempDir, "pid.txt"),
starts: app.StartCount,
status: app.Status,
pid: app.PID,
app: app,
process: nil,
}
inner, err := os.FindProcess(app.PID)
if err == nil && inner.Signal(syscall.Signal(0)) == nil {
proc.process = inner
}
return proc
}
// Start 启动进程
func (p *Proc) Start() error {
p.RLock()
if p.status != StatusUnknown && p.status != StatusStopped {
p.RUnlock()
return errors.New("process was running")
}
stdinFile := p.stdinFile
stdoutFile := p.stdoutFile
stderrFile := p.stderrFile
app := p.app
p.setStatus(StatusStarting)
p.RUnlock()
// 确定数据输入文件
stdin, err := GetFile(stdinFile)
if err != nil {
return err
}
if len(app.Stdin) > 0 {
err = WriteFile(stdinFile, []byte(app.Stdin))
if err != nil {
return err
}
}
// 确定数据输出文件
// TODO 监听内容输入,记录到数据库
stdout, err := GetFile(stdoutFile)
if err != nil {
return err
}
// 确定错误输出文件
// TODO 监听内容输入,记录到数据库
stderr, err := GetFile(stderrFile)
if err != nil {
return err
}
// 确定工作目录
cwd := app.Cwd
if len(cwd) == 0 {
cwd, _ = os.Getwd()
}
// 确定进程属性
attr := &os.ProcAttr{
Dir: cwd,
Env: os.Environ(),
Files: []*os.File{stdin, stdout, stderr},
//Sys: &syscall.SysProcAttr{HideWindow: true},
}
// 确定启动参数
var args []string
if len(app.InterpreterArgs) > 0 {
args = append(args, app.InterpreterArgs...)
}
if len(app.Script) > 0 {
args = append(args, app.Script)
}
if len(app.Command) > 0 {
args = append(args, app.Command...)
}
if len(app.Arguments) > 0 {
args = append(args, app.Arguments...)
}
// 启动进程
process, err := os.StartProcess(app.Interpreter, args, attr)
if err != nil {
return err
}
p.Lock()
defer p.Unlock()
p.process = process
p.pid = process.Pid
p.starts++
err = WriteFile(p.pidfile, []byte(strconv.Itoa(p.pid)))
if err != nil {
go p.Stop(true)
return err
}
err = UpdateApp(app.ID,
ConfigStatus(StatusRunning),
ConfigPid(process.Pid),
IncreaseStartCounter)
if err != nil {
go p.Stop(true)
return err
}
return nil
}
// Stop 停止进程
func (p *Proc) Stop(force ...bool) error {
p.Lock()
if p.process == nil {
p.Unlock()
return errors.New("process does not exist")
}
p.setStatus(StatusStopping)
p.Unlock()
// 确定是否优雅关停
gracefully := true
if len(force) > 0 {
gracefully = force[0] == false
}
p.Lock()
defer p.Unlock()
var err error
if gracefully {
err = p.process.Signal(syscall.SIGTERM)
} else {
err = p.process.Signal(syscall.SIGKILL)
p.process.Release()
}
p.setStatus(StatusStopped)
UpdateApp(p.app.ID, ConfigPid(-1), ConfigStatus(StatusStopped))
DeleteFile(p.pidfile)
return err
}
// Restart 重启进程
func (p *Proc) Restart(force ...bool) error {
// 如果进程可用则停止
if p.IsAlive() {
err := p.Stop(force...)
if err != nil {
return err
}
}
return p.Start()
}
// Wait 等待进程结束
func (p *Proc) Wait() (*os.ProcessState, error) {
p.RLock()
defer p.RUnlock()
if p.process == nil {
return nil, errors.New("process does not started")
}
return p.process.Wait()
}
func (p *Proc) setStatus(status uint8) {
UpdateApp(p.app.ID, ConfigStatus(status))
}
// IsAlive 判断进程是否激活
func (p *Proc) IsAlive() bool {
p.RLock()
pid := p.pid
p.RUnlock()
if proc, err := os.FindProcess(pid); err != nil {
return false
} else {
return proc.Signal(syscall.Signal(0)) == nil
}
}
// Release 释放进程资源
func (p *Proc) Release() error {
p.Lock()
defer p.Unlock()
var err error
if p.process != nil {
err = p.process.Release()
}
if de := DeleteFile(p.pidfile); de != nil {
return de
}
if de := DeleteFile(p.stdinFile); de != nil {
return de
}
if de := DeleteFile(p.stdoutFile); de != nil {
return de
}
if de := DeleteFile(p.stderrFile); de != nil {
return de
}
if de := DeleteFile(p.tempDir); de != nil {
return err
}
return err
}
// Status 获取运行状态
func (p *Proc) Status() uint8 {
p.RLock()
defer p.RUnlock()
return p.status
}
// Pid 获取进程PID
func (p *Proc) Pid() int {
p.RLock()
defer p.RUnlock()
return p.pid
}
func (p *Proc) NotifyStopped() {
p.Lock()
defer p.Unlock()
p.pid = -1
}

@ -0,0 +1,66 @@
package db
import (
"os"
"path/filepath"
"regexp"
"strings"
)
var (
appIdRE = regexp.MustCompile(`^\d+$`)
matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
)
func ToSnakeCase(str string) string {
snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
return strings.ToLower(snake)
}
func IsAppID(s string) bool {
return len(s) > 0 && appIdRE.MatchString(s)
}
// WriteFile will write the info on array of bytes b to filepath. It will set the file
// permission mode to 0660
// Returns an error in case there's any.
func WriteFile(filepath string, b []byte) error {
return os.WriteFile(filepath, b, 0660)
}
// GetFile will open filepath.
// Returns a tuple with a file and an error in case there's any.
func GetFile(filepath string) (*os.File, error) {
return os.OpenFile(filepath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0777)
}
// DeleteFile will delete filepath permanently.
// Returns an error in case there's any.
func DeleteFile(filepath string) error {
_, err := os.Stat(filepath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
return os.Remove(filepath)
}
func TempDir(appName string) (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
tempDir, err := filepath.Abs(homeDir + "/.pmt/" + appName)
if err != nil {
return "", err
}
err = os.MkdirAll(tempDir, os.ModePerm)
if err != nil {
return "", err
}
return tempDir, nil
}

@ -0,0 +1,74 @@
package db
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"time"
)
// 移除不需要的参数
func strip(slice []string, remove string) []string {
for i := 0; i < len(slice); {
if slice[i] == remove {
if i != len(slice)-1 {
slice = append(slice[:i], slice[i+1:]...)
} else {
slice = slice[:i]
}
} else {
i++
}
}
return slice
}
func subProcess(args []string) *exec.Cmd {
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
fmt.Fprintf(os.Stderr, "[-] Error: %s\n", err)
}
return cmd
}
func main() {
daemon := flag.Bool("daemon", false, "run in daemon")
forever := flag.Bool("forever", false, "run forever")
flag.Parse()
// 启用守护模式
if *daemon {
subProcess(strip(os.Args, "-daemon"))
fmt.Printf("[*] Daemon running in pid: %d PPID: %d\n", os.Getpid(), os.Getppid())
os.Exit(0)
}
if *forever {
for {
cmd := subProcess(strip(os.Args, "-forever"))
fmt.Printf("[*] Forever running in pid: %d PPID: %d\n", os.Getpid(), os.Getppid())
if err := cmd.Wait(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
}
fmt.Printf("[*] Service running in pid: %d PPID: %d\n", os.Getpid(), os.Getppid())
fp, _ := os.OpenFile("./dosomething.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
log.SetOutput(fp)
for {
log.Printf("DoSomething running in pid: %d PPID: %d\n", os.Getpid(), os.Getppid())
time.Sleep(time.Second * 5)
}
}
func run() {
}

@ -0,0 +1,21 @@
module hupeh.vip/pm
go 1.19
require (
github.com/mattn/go-isatty v0.0.17
github.com/pelletier/go-toml/v2 v2.0.6
github.com/tint/env v1.0.2
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/sqlite v1.4.4
gorm.io/gorm v1.24.3
)
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
)

@ -0,0 +1,41 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tint/env v1.0.2 h1:1dUtj11RH07dSJQzbXKGHCjfbqYI7O7mUMdnAnnGDyQ=
github.com/tint/env v1.0.2/go.mod h1:SqqhryvPryCX0gRezG0zzett+Ib3RB+AWvKo3dYInVo=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.3 h1:WL2ifUmzR/SLp85CSURAfybcHnGZ+yLSGSxgYXlFBHg=
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=

@ -0,0 +1,197 @@
package main
import (
"fmt"
"github.com/tint/env"
"hupeh.vip/pm/cmd"
"hupeh.vip/pm/db"
"os"
"os/exec"
"strconv"
)
//import (
// "flag"
// "fmt"
// "log"
// "os"
// "os/exec"
// "time"
//)
//
//// 移除不需要的参数
//func strip(slice []string, remove string) []string {
// for i := 0; i < len(slice); {
// if slice[i] == remove {
// if i != len(slice)-1 {
// slice = append(slice[:i], slice[i+1:]...)
// } else {
// slice = slice[:i]
// }
// } else {
// i++
// }
// }
// return slice
//}
//
//func subProcess(args []string) *exec.Cmd {
// cmd := exec.Command(args[0], args[1:]...)
// cmd.Stdin = os.Stdin
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
// err := cmd.Start()
// if err != nil {
// fmt.Fprintf(os.Stderr, "[-] Error: %s\n", err)
// }
// return cmd
//}
//
//func main() {
// daemon := flag.Bool("daemon", false, "run in daemon")
// forever := flag.Bool("forever", false, "run forever")
// flag.Parse()
//
// // 启用守护模式
// if *daemon {
// subProcess(strip(os.Arguments, "-daemon"))
// fmt.Printf("[*] Daemon running in pid: %d PPID: %d\n", os.Getpid(), os.Getppid())
// os.Exit(0)
// }
//
// if *forever {
// for {
// cmd := subProcess(strip(os.Arguments, "-forever"))
// fmt.Printf("[*] Forever running in pid: %d PPID: %d\n", os.Getpid(), os.Getppid())
// if err := cmd.Wait(); err != nil {
// fmt.Println(err)
// os.Exit(1)
// }
// }
// }
//
// fp, _ := os.OpenFile("./dosomething.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
// log.SetOutput(fp)
// log.Printf("[*] Service running in pid: %d PPID: %d\n", os.Getpid(), os.Getppid())
// defer log.Println("exit ...")
// for {
// log.Printf("DoSomething running in pid: %d PPID: %d\n", os.Getpid(), os.Getppid())
// time.Sleep(time.Second * 5)
// }
//}
func main() {
env.Setup()
switch env.String("PMT_CURRENT_FUNCTION") {
case "start":
case "stop":
case "shell":
case "list":
default:
runCli()
}
}
func runCli() {
cmd.New("pm").
Name("pm").
Description("description").
// start 命令
Command("start", "Start a program in daemon").
Argument("[names:...string]", "file/name/id").
Option("-w, --watch <watch:boolean>", "Watches folder changes").
Option("-c, --cwd <cwd:string>", "Sets working directory "+cmd.Yellow("Sets working directory")+" Sets working directory "+cmd.Yellow("Sets working directory")+" Sets working directory "+cmd.Yellow(" Sets working directory")+" Sets working directory "+cmd.Yellow("Sets working directory")+" Sets working directory "+cmd.Yellow("Sets working directory")+" End ...").
Option("-n, --name", "Sets program name").
Option("-e, --env", "Sets current environment name").
Option("-i, --interpreter", "Sets interpreter name").
Action(func(c *cmd.Command, m map[string]any, a ...any) error {
if len(a) != 1 {
return c.ShowHelp()
}
var apps []*db.App
scripts := a[0].([]string)
for _, script := range scripts {
loads, err := db.ParseInput(script)
if err != nil {
return err
}
apps = append(apps, loads...)
}
if len(apps) == 0 {
fmt.Println("No app found")
return nil
}
for _, app := range apps {
if app.Status == db.StatusRunning {
fmt.Println(cmd.Yellow(app.Name), cmd.Green("running"))
continue
}
if app.ID == 0 {
if err := db.SaveApp(app); err != nil {
fmt.Println(cmd.Yellow(app.Name), cmd.Red("failed for save"))
return err
}
}
if err := runStart(app.ID); err != nil {
fmt.Println(cmd.Yellow(app.Name), cmd.Red("failed"))
fmt.Println(" ", err.Error())
}
}
fmt.Println("over")
return nil
}).
// stop 命令
Command("stop", "Stops a program").
Argument("<names:...string>", "file/name/id").
Action(func(c *cmd.Command, m map[string]any, a ...any) error {
fmt.Println("停止程序")
fmt.Println(a...)
return nil
}).
// 执行程序
Run()
}
func runStart(id uint) error {
proc := subProcess(
[]string{os.Args[0], "-id", strconv.Itoa(int(id))},
[]string{"PMT_CURRENT_FUNCTION=start"},
)
return proc.Wait()
}
func subProcess(args []string, env []string) *exec.Cmd {
cmd := exec.Command(args[0], args[1:]...)
cmd.Env = append(os.Environ(), env...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
fmt.Fprintf(os.Stderr, "[-] Error: %s\n", err)
}
return cmd
}
// 移除不需要的参数
func strip(slice []string, remove string) []string {
for i := 0; i < len(slice); {
if slice[i] == remove {
if i != len(slice)-1 {
slice = append(slice[:i], slice[i+1:]...)
} else {
slice = slice[:i]
}
} else {
i++
}
}
return slice
}

@ -0,0 +1,3 @@
go build -ldflags "-s -w" main.go
main.exe -daemon -forever

@ -0,0 +1,17 @@
package tools
type StartCommand struct {
App string `json:"app"`
Watch bool `json:"watch"`
Env string `json:"env"`
Name string `json:"name"`
Cwd string `json:"cwd"`
}
func RunStart(apps ...string) {
}
func (s *StartCommand) Execute() error {
return nil
}
Loading…
Cancel
Save