Skip to content

Commit

Permalink
Merge branch 'dev.cli'
Browse files Browse the repository at this point in the history
  • Loading branch information
caixw committed Feb 3, 2025
2 parents 22c1b9f + 4f9438e commit b57c48f
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 23 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ require (
github.com/issue9/term/v3 v3.3.2
github.com/issue9/unique/v2 v2.1.0
github.com/jellydator/ttlcache/v3 v3.3.0
github.com/kardianos/service v1.2.2
github.com/klauspost/compress v1.17.11
github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/puzpuzpuz/xsync/v3 v3.5.0
github.com/wk8/go-ordered-map/v2 v2.1.8
golang.org/x/crypto v0.32.0
golang.org/x/text v0.21.0
Expand Down
7 changes: 5 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ github.com/issue9/unique/v2 v2.1.0/go.mod h1:qZoDKnfu+7Q0yxhifVseRKD2Wea9Tc9zdXw
github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
Expand All @@ -66,8 +68,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=
github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/redis/go-redis/v9 v9.6.2 h1:w0uvkRbc9KpgD98zcvo5IrVUsn0lXpRMuhNgiHDJzdk=
github.com/redis/go-redis/v9 v9.6.2/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
Expand All @@ -91,6 +93,7 @@ golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
Expand Down
9 changes: 9 additions & 0 deletions locales/und.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ messages:
- key: cmd.action
message:
msg: cmd.action
- key: cmd.daemon
message:
msg: cmd.daemon
- key: cmd.show_help
message:
msg: cmd.show_help
- key: cmd.show_version
message:
msg: cmd.show_version
- key: cmd.test_syntax
message:
msg: cmd.test_syntax
- key: conflict with certificates
message:
msg: conflict with certificates
Expand Down Expand Up @@ -346,6 +352,9 @@ messages:
- key: should great than %v
message:
msg: should great than %v
- key: syntax OK
message:
msg: syntax OK
- key: the client miss content-type header
message:
msg: the client miss content-type header
Expand Down
16 changes: 16 additions & 0 deletions locales/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,25 @@ messages:
- key: cmd.action
message:
msg: 运行的指令
- key: cmd.daemon
message:
msg: >
将当前程序作为守护进程进行的相关操作,包含以下几种取值:
- install 安装为守护进程;
- uninstall 卸载;
- start 启动服务;
- stop 停止服务;
- restart 重启服务;
- status 查看守护进程的当前状态;
- key: cmd.show_help
message:
msg: 显示帮助信息
- key: cmd.show_version
message:
msg: 显示版本号
- key: cmd.test_syntax
message:
msg: 测试配置文件的语法是否正确
- key: conflict with certificates
message:
msg: 与 certificates 字段存在冲突
Expand Down Expand Up @@ -364,6 +377,9 @@ messages:
- key: should great than %v
message:
msg: 必须大于 %v
- key: syntax OK
message:
msg: 语法正确
- key: the client miss content-type header
message:
msg: 客户端未指定 Content-Type 报头
Expand Down
6 changes: 3 additions & 3 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type (
Server interface {
context.Context

// ID 应用的 ID 标记
// ID 应用的 ID
ID() string

// Version 应用的版本
Expand Down Expand Up @@ -192,7 +192,7 @@ type (
InternalServer struct {
server Server

id string
id string
version string

locale *locale.Locale
Expand Down Expand Up @@ -248,7 +248,7 @@ func InternalNewServer(
is := &InternalServer{
server: s,

id: id,
id: id,
version: ver,

locale: l,
Expand Down
32 changes: 31 additions & 1 deletion server/app/app.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2018-2024 caixw
// SPDX-FileCopyrightText: 2018-2025 caixw
//
// SPDX-License-Identifier: MIT

Expand All @@ -9,6 +9,8 @@ import (
"sync"
"time"

"github.com/kardianos/service"

"github.com/issue9/web"
)

Expand Down Expand Up @@ -47,6 +49,10 @@ type app struct {
// shutdown 每次关闭服务操作的等待时间;
// newServer 构建新服务的方法。
func New(shutdown time.Duration, newServer func() (web.Server, error)) App {
return newApp(shutdown, newServer)
}

func newApp(shutdown time.Duration, newServer func() (web.Server, error)) *app {
if newServer == nil {
panic("newServer 不能为空")
}
Expand Down Expand Up @@ -108,3 +114,27 @@ func (app *app) Restart() {
old.Close(app.shutdownTimeout) // 新服务声明成功,尝试关闭旧服务。
<-app.exit // 等待 server.Serve 退出
}

// 执行守护进程功能并返回当前的状态
//
// action 可以是 [service.ControlAction] 和 'status' 中的任意元素;
func (app *app) runDaemon(action string, conf *service.Config) (service.Status, error) {
d, err := service.New(app, conf)
if err != nil {
return service.StatusUnknown, err
}

if action != "status" {
if err := service.Control(d, action); err != nil {
return service.StatusUnknown, err
}
}
if action == "uninstall" { // 已卸载,无法获取状态。
return service.StatusUnknown, nil
}
return d.Status()
}

// 只使用了 [service.Control] 的功能,不需要实现 [service.Interface] 的具体功能。
func (app *app) Start(s service.Service) error { return nil }
func (app *app) Stop(s service.Service) error { return nil }
6 changes: 5 additions & 1 deletion server/app/app_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// SPDX-FileCopyrightText: 2018-2024 caixw
// SPDX-FileCopyrightText: 2018-2025 caixw
//
// SPDX-License-Identifier: MIT

package app

import "github.com/kardianos/service"

type empty struct{}

var _ service.Interface = &app{}
86 changes: 73 additions & 13 deletions server/app/cli.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2018-2024 caixw
// SPDX-FileCopyrightText: 2018-2025 caixw
//
// SPDX-License-Identifier: MIT

Expand All @@ -13,6 +13,7 @@ import (
"slices"
"time"

"github.com/kardianos/service"
"golang.org/x/text/message"

"github.com/issue9/web"
Expand All @@ -24,10 +25,12 @@ import (
const (
cmdShowVersion = web.StringPhrase("cmd.show_version")
cmdAction = web.StringPhrase("cmd.action")
cmdDaemon = web.StringPhrase("cmd.daemon")
cmdShowHelp = web.StringPhrase("cmd.show_help")
cmdTestSyntax = web.StringPhrase("cmd.test_syntax")
)

type CLIOptions[T any] struct {
type CLIOptions[T comparable] struct {
ID string // 程序 ID
Version string // 程序版本

Expand Down Expand Up @@ -75,6 +78,9 @@ type CLIOptions[T any] struct {
// - cmd.action
// - cmd.show_help
// - can not be empty
// - cmd.test_syntax
// - status %v
// - syntax OK
//
// NOTE: 此设置仅影响命令行的本地化,[web.Server] 的本地化由其自身管理。
Printer *message.Printer
Expand All @@ -86,9 +92,21 @@ type CLIOptions[T any] struct {
//
// 默认值为 [flag.ContinueOnError]
ErrorHandling flag.ErrorHandling

// 守护进程的设置项
//
// 如果该值不为 nil,将会注册 -d 选项,
// 通过 -d 可以对服务进行以下操作:
// - install 安装服务
// - uninstall 卸载服务
// - start 启动服务
// - stop 停止服务
// - restart 重启服务
// - status 查看服务状态
Daemon *service.Config
}

type cli[T any] struct {
type cli[T comparable] struct {
App
exec func(args []string) error
}
Expand All @@ -99,6 +117,8 @@ type cli[T any] struct {
// - -v 显示版本号;
// - -h 显示帮助信息;
// - -a 执行的指令,该值会传递给 [CLIOptions.NewServer],由用户根据此值决定初始化方式;
// - -d 将当前程序作为守护进程的相关操作;
// - -t 测试配置文件的语法是否正确;
//
// T 表示的是配置文件中的用户自定义数据类型,可参考 [config.Load] 中有关 User 的说明。
//
Expand All @@ -118,7 +138,7 @@ func NewCLI[T comparable](o *CLIOptions[T]) App {
return o.NewServer(o.ID, o.Version, opt, user, action)
}

app := New(o.ShutdownTimeout, initServer)
app := newApp(o.ShutdownTimeout, initServer)

return &cli[T]{
App: app,
Expand All @@ -128,11 +148,28 @@ func NewCLI[T comparable](o *CLIOptions[T]) App {

v := fs.Bool("v", false, cmdShowVersion.LocaleString(o.Printer))
h := fs.Bool("h", false, cmdShowHelp.LocaleString(o.Printer))
t := fs.Bool("t", false, cmdTestSyntax.LocaleString(o.Printer))
fs.StringVar(&action, "a", "", cmdAction.LocaleString(o.Printer))

var daemon string // -d 选项
if o.Daemon != nil {
fs.StringVar(&daemon, "d", "", cmdDaemon.LocaleString(o.Printer))
}

if err = fs.Parse(args[1:]); err != nil {
return web.NewStackError(localeError(err, o.Printer))
}

if o.Daemon != nil && daemon != "" { // 在其它选项之前
status, err := app.runDaemon(daemon, o.Daemon)
if err != nil {
return err
}

_, err = fmt.Fprintln(o.Out, o.Printer.Sprintf("status %v", status))
return err
}

if *v {
_, err = fmt.Fprintln(o.Out, o.ID, o.Version)
return web.NewStackError(localeError(err, o.Printer))
Expand All @@ -143,6 +180,23 @@ func NewCLI[T comparable](o *CLIOptions[T]) App {
return nil
}

if *t {
_, _, err := config.Load[T](o.ConfigDir, o.ConfigFilename)
if err != nil {
var msg string
if le, ok := err.(web.LocaleStringer); ok { // 对错误信息进行本地化转换
msg = le.LocaleString(o.Printer)
} else {
msg = err.Error()
}

fmt.Fprintln(o.Out, msg)
} else {
fmt.Fprintln(o.Out, web.Phrase("syntax OK").LocaleString(o.Printer))
}
return nil
}

if slices.Index(o.ServeActions, action) < 0 { // 非服务
_, err = initServer()
return localeError(err, o.Printer)
Expand All @@ -153,15 +207,6 @@ func NewCLI[T comparable](o *CLIOptions[T]) App {
}
}

func localeError(err error, p *message.Printer) error {
if err != nil {
if le, ok := err.(web.LocaleStringer); ok { // 对错误信息进行本地化转换
return errors.New(le.LocaleString(p))
}
}
return err
}

func (cmd *cli[T]) Exec() error { return cmd.exec(os.Args) }

func (o *CLIOptions[T]) sanitize() error {
Expand Down Expand Up @@ -195,5 +240,20 @@ func (o *CLIOptions[T]) sanitize() error {
o.ErrorHandling = flag.ContinueOnError
}

if o.Daemon != nil {
if o.Daemon.Name == "" {
o.Daemon.Name = o.ID
}
}

return nil
}

func localeError(err error, p *message.Printer) error {
if err != nil {
if le, ok := err.(web.LocaleStringer); ok { // 对错误信息进行本地化转换
return errors.New(le.LocaleString(p))
}
}
return err
}
8 changes: 7 additions & 1 deletion server/app/cli_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2018-2024 caixw
// SPDX-FileCopyrightText: 2018-2025 caixw
//
// SPDX-License-Identifier: MIT

Expand All @@ -15,6 +15,8 @@ import (
"github.com/issue9/web/server"
)

var _ App = &cli[int]{}

func TestCLI(t *testing.T) {
a := assert.New(t, false)
const shutdownTimeout = 0
Expand All @@ -40,6 +42,10 @@ func TestCLI(t *testing.T) {

buf.Reset()
a.NotError(ocli.exec([]string{"app", "-a=install"})).Equal(action, "install")

buf.Reset()
msg := web.Phrase("syntax OK").LocaleString(o.Printer) + "\n"
a.NotError(ocli.exec([]string{"app", "-t"})).Equal(buf.String(), msg)
}

func TestCLI_sanitize(t *testing.T) {
Expand Down
Loading

0 comments on commit b57c48f

Please sign in to comment.