refc: 重构了 nfctl

This commit is contained in:
loveuer
2024-12-26 19:40:39 +08:00
parent d8d771aec6
commit ad6b4fe7b6
27 changed files with 504 additions and 831 deletions

View File

@ -0,0 +1,153 @@
package cmd
import (
"bufio"
"bytes"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"text/template"
"github.com/loveuer/nf/nft/log"
"github.com/loveuer/nf/nft/nfctl/internal/opt"
"github.com/loveuer/nf/nft/nfctl/pkg/loading"
"github.com/loveuer/nf/nft/tool"
"github.com/spf13/cobra"
)
var newCmd = &cobra.Command{
Use: "new",
Short: "new a nf project",
Example: "nfctl new <project> -t ultone [options]",
RunE: doNew,
SilenceErrors: true,
}
func initNew() *cobra.Command {
newCmd.Flags().StringVarP(&opt.Cfg.New.Template, "template", "t", "ultone", "template name/url[example:ultone, https://gitea.loveuer.com/loveuer/ultone.git]")
newCmd.Flags().BoolVar(&opt.Cfg.New.DisableInitScript, "disable-init-script", false, "disable init script(.nfctl)")
return newCmd
}
func doNew(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("必须提供 project 名称")
}
if strings.HasSuffix(args[0], "/") {
return errors.New("project 名称不能以 / 结尾")
}
base := path.Base(args[0])
if strings.HasPrefix(base, ".") {
return errors.New("project 名称不能以 . 开头")
}
ch := make(chan *loading.Loading)
defer close(ch)
go loading.Print(cmd.Context(), ch)
ch <- &loading.Loading{Content: "开始新建项目: " + args[0], Type: loading.TypeInfo}
pwd, err := os.Getwd()
if err != nil {
ch <- &loading.Loading{Content: err.Error(), Type: loading.TypeError}
return err
}
moduleName := args[0]
pwd = path.Join(filepath.ToSlash(pwd), base)
log.Debug("cmd.new: new project, pwd = %s, name = %s, template = %s", pwd, moduleName, opt.Cfg.New.Template)
ch <- &loading.Loading{Content: "开始下载模板: " + opt.Cfg.New.Template, Type: loading.TypeProcessing}
repo := opt.Cfg.New.Template
if v, ok := opt.TemplateMap[repo]; ok {
repo = v
}
if err = tool.Clone(pwd, repo); err != nil {
ch <- &loading.Loading{Content: err.Error(), Type: loading.TypeError}
return err
}
ch <- &loading.Loading{Content: "下载模板完成: " + opt.Cfg.New.Template, Type: loading.TypeSuccess}
if err = os.RemoveAll(path.Join(pwd, ".git")); err != nil {
ch <- &loading.Loading{Content: err.Error(), Type: loading.TypeWarning}
}
ch <- &loading.Loading{Content: "开始初始化项目: " + args[0], Type: loading.TypeProcessing}
if err = filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "go.mod") {
var content []byte
if content, err = os.ReadFile(path); err != nil {
ch <- &loading.Loading{Content: "初始化文件失败: " + err.Error(), Type: loading.TypeWarning}
ch <- &loading.Loading{Content: "开始初始化项目: " + args[0], Type: loading.TypeProcessing}
return nil
}
scanner := bufio.NewScanner(bytes.NewReader(content))
replaced := make([]string, 0, 16)
for scanner.Scan() {
line := scanner.Text()
// 操作 go.mod 文件时, 忽略 toolchain 行, 以更好的兼容 go1.20
if strings.HasSuffix(path, "go.mod") && strings.HasPrefix(line, "toolchain") {
continue
}
replaced = append(replaced, strings.ReplaceAll(line, opt.Cfg.New.Template, moduleName))
}
if err = os.WriteFile(path, []byte(strings.Join(replaced, "\n")), 0o644); err != nil {
return err
}
}
return nil
}); err != nil {
ch <- &loading.Loading{Content: "初始化文件失败: " + err.Error(), Type: loading.TypeWarning}
return err
}
var (
render *template.Template
rf *os.File
)
if render, err = template.New(base).Parse(opt.README); err != nil {
log.Debug("cmd.new: new text template err, err = %s", err.Error())
ch <- &loading.Loading{Content: "生成 readme 失败", Type: loading.TypeWarning}
goto END
}
if rf, err = os.OpenFile(path.Join(pwd, "readme.md"), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0o644); err != nil {
log.Debug("cmd.new: new readme file err, err = %s", err.Error())
ch <- &loading.Loading{Content: "生成 readme 失败", Type: loading.TypeWarning}
goto END
}
defer rf.Close()
if err = render.Execute(rf, map[string]any{
"project_name": base,
}); err != nil {
log.Debug("cmd.new: template execute err, err = %s", err.Error())
ch <- &loading.Loading{Content: "生成 readme 失败", Type: loading.TypeWarning}
}
END:
ch <- &loading.Loading{Content: fmt.Sprintf("项目: %s 初始化成功", args[0]), Type: loading.TypeSuccess}
return nil
}

View File

@ -0,0 +1,33 @@
package cmd
import (
"github.com/loveuer/nf/nft/log"
"github.com/loveuer/nf/nft/nfctl/internal/opt"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "nfctl",
Short: "nfctl is a tool for quick start a nf projects",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if opt.Cfg.Debug {
log.SetLogLevel(log.LogLevelDebug)
}
if !opt.Cfg.DisableUpdate {
doUpdate(cmd.Context())
}
return nil
},
DisableSuggestions: true,
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {},
}
func initRoot(cmds ...*cobra.Command) {
rootCmd.PersistentFlags().BoolVar(&opt.Cfg.Debug, "debug", false, "debug mode")
rootCmd.PersistentFlags().BoolVar(&opt.Cfg.DisableUpdate, "disable-update", false, "disable self update")
rootCmd.AddCommand(cmds...)
}

View File

@ -0,0 +1,86 @@
package cmd
import (
"context"
"crypto/tls"
"fmt"
"regexp"
"strings"
"time"
resty "github.com/go-resty/resty/v2"
"github.com/loveuer/nf/nft/log"
"github.com/loveuer/nf/nft/nfctl/internal/opt"
"github.com/loveuer/nf/nft/nfctl/pkg/loading"
"github.com/spf13/cobra"
)
var updateCmd = &cobra.Command{
Use: "update",
Short: "update nfctl self",
RunE: func(cmd *cobra.Command, args []string) error { return nil },
}
func initUpdate() *cobra.Command {
return updateCmd
}
func doUpdate(ctx context.Context) (err error) {
ch := make(chan *loading.Loading)
defer close(ch)
go func() {
loading.Print(ctx, ch)
}()
ch <- &loading.Loading{Content: "正在检查更新...", Type: loading.TypeProcessing}
tip := "❗ 请尝试手动更新: go install github.com/loveuer/nf/nft/nfctl@latest"
version := ""
var rr *resty.Response
if rr, err = resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).R().
SetContext(ctx).
Get(opt.VersionURL); err != nil {
err = fmt.Errorf("检查更新失败: %s\n%s", err.Error(), tip)
ch <- &loading.Loading{Content: err.Error(), Type: loading.TypeError}
return err
}
log.Debug("cmd.update: url = %s, raw_response = %s", opt.VersionURL, rr.String())
if rr.StatusCode() != 200 {
err = fmt.Errorf("检查更新失败: %s\n%s", rr.Status(), tip)
ch <- &loading.Loading{Content: err.Error(), Type: loading.TypeError}
return
}
reg := regexp.MustCompile(`const Version = "v\d{2}\.\d{2}\.\d{2}-r\d{1,2}"`)
for _, line := range strings.Split(rr.String(), "\n") {
if reg.MatchString(line) {
version = strings.TrimSpace(strings.TrimPrefix(line, "const Version = "))
version = version[1 : len(version)-1]
break
}
}
if version == "" {
err = fmt.Errorf("检查更新失败: 未找到版本信息\n%s", tip)
ch <- &loading.Loading{Content: err.Error(), Type: loading.TypeError}
return err
}
log.Debug("cmd.update: find version = %s, now_version = %s", version, opt.Version)
if version <= opt.Version {
ch <- &loading.Loading{Content: fmt.Sprintf("已是最新版本: %s", opt.Version), Type: loading.TypeSuccess}
return nil
}
ch <- &loading.Loading{Content: fmt.Sprintf("发现新版本: %s", version), Type: loading.TypeInfo}
ch <- &loading.Loading{Content: fmt.Sprintf("正在更新到 %s ...", version)}
time.Sleep(2 * time.Second)
ch <- &loading.Loading{Content: "暂时无法自动更新, 请尝试手动更新: go install github.com/loveuer/nf/nft/nfctl@latest", Type: loading.TypeWarning}
return nil
}

View File

@ -0,0 +1,25 @@
package cmd
import (
"context"
"fmt"
"os"
"time"
)
func Init() {
initRoot(
initUpdate(),
initNew(),
)
}
func Run(ctx context.Context) {
if err := rootCmd.ExecuteContext(ctx); err != nil {
fmt.Printf("❌ %s\n", err.Error())
time.Sleep(300 * time.Millisecond)
os.Exit(1)
}
time.Sleep(300 * time.Millisecond)
}