feat: add nfctl(ctl to start nf project)
This commit is contained in:
parent
8a423c2887
commit
0f139cda98
6
go.mod
6
go.mod
@ -4,8 +4,10 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
)
|
||||
|
||||
require (
|
||||
@ -16,15 +18,17 @@ require (
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/savioxavier/termlink v1.3.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
|
10
go.sum
10
go.sum
@ -11,6 +11,7 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -34,6 +35,8 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4er
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
@ -56,11 +59,18 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/savioxavier/termlink v1.3.0 h1:3Gl4FzQjUyiHzmoEDfmWEhgIwDiJY4poOQHP+k8ReA4=
|
||||
github.com/savioxavier/termlink v1.3.0/go.mod h1:5T5ePUlWbxCHIwyF8/Ez1qufOoGM89RCg9NvG+3G3gc=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
|
36
nft/nfctl/clone/clone.go
Normal file
36
nft/nfctl/clone/clone.go
Normal file
@ -0,0 +1,36 @@
|
||||
package clone
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-git/go-git/v5"
|
||||
_ "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func Clone(pwd string, ins *url.URL) error {
|
||||
uri := fmt.Sprintf("%s://%s%s", ins.Scheme, ins.Host, ins.Path)
|
||||
opt := &git.CloneOptions{
|
||||
URL: uri,
|
||||
Depth: 1,
|
||||
InsecureSkipTLS: true,
|
||||
SingleBranch: true,
|
||||
}
|
||||
|
||||
if ins.User != nil {
|
||||
password, _ := ins.User.Password()
|
||||
opt.Auth = &http.BasicAuth{
|
||||
Username: ins.User.Username(),
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("start clone %s", uri)
|
||||
_, err := git.PlainClone(pwd, false, opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
19
nft/nfctl/cmd/cmd.go
Normal file
19
nft/nfctl/cmd/cmd.go
Normal file
@ -0,0 +1,19 @@
|
||||
package cmd
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var (
|
||||
Root = &cobra.Command{
|
||||
Use: "nfctl",
|
||||
Short: "nfctl: easy start your nf backend work",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
initNew()
|
||||
|
||||
Root.AddCommand(
|
||||
versionCmd,
|
||||
cmdNew,
|
||||
)
|
||||
}
|
131
nft/nfctl/cmd/new.go
Normal file
131
nft/nfctl/cmd/new.go
Normal file
@ -0,0 +1,131 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"github.com/loveuer/nf/nft/nfctl/clone"
|
||||
"github.com/loveuer/nf/nft/nfctl/opt"
|
||||
"github.com/loveuer/nf/nft/nfctl/tp"
|
||||
"github.com/spf13/cobra"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
var (
|
||||
cmdNew = &cobra.Command{
|
||||
Use: "new",
|
||||
Short: "nfctl new: start new project",
|
||||
Example: `nfctl new {project} -t ultone [recommend]
|
||||
nfctl new {project} -t https://github.com/loveuer/ultone.git
|
||||
nfctl new {project} --template http://username:token@my.gitlab.com/my-zone/my-repo.git
|
||||
`,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
template string
|
||||
disableInit bool
|
||||
|
||||
preTemplateMap = map[string]string{
|
||||
"ultone": "https://gitcode.com/loveuer/ultone.git",
|
||||
}
|
||||
)
|
||||
|
||||
func initNew() {
|
||||
cmdNew.Flags().BoolVar(&opt.Debug, "debug", false, "debug mode")
|
||||
cmdNew.Flags().StringVarP(&template, "template", "t", "", "template name/url[example:ultone, https://github.com/xxx/yyy.git]")
|
||||
cmdNew.Flags().BoolVar(&disableInit, "without-init", false, "don't run template init script")
|
||||
|
||||
cmdNew.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
if opt.Debug {
|
||||
log.SetLogLevel(log.LogLevelDebug)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
urlIns *url.URL
|
||||
pwd string
|
||||
projectDir string
|
||||
initBs []byte
|
||||
renderBs []byte
|
||||
scripts []tp.Cmd
|
||||
)
|
||||
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("project name required")
|
||||
}
|
||||
|
||||
if pwd, err = os.Getwd(); err != nil {
|
||||
return fmt.Errorf("get work dir err")
|
||||
}
|
||||
|
||||
projectDir = path.Join(pwd, args[0])
|
||||
|
||||
if _, err = os.Stat(projectDir); !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("project folder already exist")
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(projectDir, 0750); err != nil {
|
||||
return fmt.Errorf("create project dir err: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(projectDir)
|
||||
}
|
||||
}()
|
||||
|
||||
if template == "" {
|
||||
// todo no template new project
|
||||
return fmt.Errorf("😥create basic project(without template) comming soon...")
|
||||
}
|
||||
|
||||
cloneUrl := template
|
||||
if ptUrl, ok := preTemplateMap[cloneUrl]; ok {
|
||||
cloneUrl = ptUrl
|
||||
}
|
||||
|
||||
if urlIns, err = url.Parse(cloneUrl); err != nil {
|
||||
return fmt.Errorf("invalid clone url: %v", err)
|
||||
}
|
||||
|
||||
if err = clone.Clone(projectDir, urlIns); err != nil {
|
||||
return fmt.Errorf("clone template err: %v", err)
|
||||
}
|
||||
|
||||
if initBs, err = os.ReadFile(path.Join(projectDir, ".nfctl")); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("read nfctl script file err: %v", err)
|
||||
}
|
||||
|
||||
if renderBs, err = tp.RenderVar(initBs, map[string]any{
|
||||
"PROJECT_NAME": args[0],
|
||||
}); err != nil {
|
||||
return fmt.Errorf("render template init script err: %v", err)
|
||||
}
|
||||
|
||||
if scripts, err = tp.ParseCmd(projectDir, renderBs); err != nil {
|
||||
return fmt.Errorf("parse template init script err: %v", err)
|
||||
}
|
||||
|
||||
for _, script := range scripts {
|
||||
if opt.Debug {
|
||||
log.Debug("start script:\n%s\n", script.String())
|
||||
}
|
||||
|
||||
if err = script.Execute(); err != nil {
|
||||
return fmt.Errorf("execute template init script err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = os.RemoveAll(path.Join(projectDir, ".git")); err != nil {
|
||||
log.Warn("remove .git folder err: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
17
nft/nfctl/cmd/version.go
Normal file
17
nft/nfctl/cmd/version.go
Normal file
@ -0,0 +1,17 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"github.com/loveuer/nf/nft/nfctl/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "print nfctl version and exit",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
log.Info("version: %s", version.Version)
|
||||
},
|
||||
}
|
||||
)
|
@ -1,50 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5"
|
||||
_ "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"io"
|
||||
"context"
|
||||
"github.com/loveuer/nf/nft/nfctl/cmd"
|
||||
"github.com/loveuer/nf/nft/nfctl/version"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
memo := memory.NewStorage()
|
||||
fs := memfs.New()
|
||||
repo, err := git.Clone(memo, fs, &git.CloneOptions{
|
||||
URL: "http://10.220.10.35/dev/template/ultone.git",
|
||||
Auth: &http.BasicAuth{Username: "loveuer", Password: "uu_L6neSDseoWx55babJ"},
|
||||
Depth: 1,
|
||||
SingleBranch: true,
|
||||
InsecureSkipTLS: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
defer cancel()
|
||||
|
||||
infos, err := fs.ReadDir(".")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version.Check()
|
||||
defer version.Fn()
|
||||
|
||||
for _, item := range infos {
|
||||
log.Info("[fs.info] %s", item.Name())
|
||||
if item.Name() == "main.go" {
|
||||
file, err := fs.Open(item.Name())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bs, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Info("[fs.main]\n%s", string(bs))
|
||||
}
|
||||
}
|
||||
|
||||
_ = repo
|
||||
_ = cmd.Root.ExecuteContext(ctx)
|
||||
}
|
||||
|
5
nft/nfctl/opt/var.go
Normal file
5
nft/nfctl/opt/var.go
Normal file
@ -0,0 +1,5 @@
|
||||
package opt
|
||||
|
||||
var (
|
||||
Debug bool
|
||||
)
|
23
nft/nfctl/readme.md
Normal file
23
nft/nfctl/readme.md
Normal file
@ -0,0 +1,23 @@
|
||||
# nfctl
|
||||
|
||||
# 通过 nfctl 快速开启后台项目
|
||||
|
||||
### 1. Installation
|
||||
|
||||
- ① `go install github.com/loveuer/nf/nft/nfctl@latest`
|
||||
- ② download prebuild binary [release](https://github.com/loveuer/nf/releases)
|
||||
|
||||
### 2. Usage
|
||||
|
||||
- `nfctl new {project}`
|
||||
- `nfctl new project -t ultone`
|
||||
- `nfctl new project -t https://github.com/xxx/yyy.git`
|
||||
- `nfctl new project --template https://gitcode/loveuer/ultone.git`
|
||||
- `nfctl new project --template https://{username}:{password/token}@my.gitlab.com/name/project.git`
|
||||
|
||||
### 3. nfctl init script
|
||||
|
||||
- `为方便模版的初始化, 可以采用 nfctl init script, 当 nfctl new project -t xxx 从模版开始项目时会自动执行`
|
||||
- `具体的编写规则如下:`
|
||||
* [init 脚本规则](https://github.com/loveuer/nf/nft/nfctl/script.md) 或者
|
||||
* [国内](https://gitcode.com/loveuer/nf/nft/nfctl/script.md)
|
@ -7,10 +7,10 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ParseCmd(pwd string, content []byte) ([]TpCmd, error) {
|
||||
func ParseCmd(pwd string, content []byte) ([]Cmd, error) {
|
||||
var (
|
||||
err error
|
||||
cmds = make([]TpCmd, 0)
|
||||
cmds = make([]Cmd, 0)
|
||||
start = false
|
||||
)
|
||||
|
||||
@ -24,7 +24,7 @@ func ParseCmd(pwd string, content []byte) ([]TpCmd, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "#") {
|
||||
if !start && strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ func ParseCmd(pwd string, content []byte) ([]TpCmd, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
var cmd TpCmd
|
||||
var cmd Cmd
|
||||
if cmd, err = ParseBlock(pwd, record); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -66,10 +66,12 @@ func ParseCmd(pwd string, content []byte) ([]TpCmd, error) {
|
||||
return cmds, err
|
||||
}
|
||||
|
||||
func ParseBlock(pwd string, lines []string) (TpCmd, error) {
|
||||
func ParseBlock(pwd string, lines []string) (Cmd, error) {
|
||||
switch lines[0] {
|
||||
case "!replace":
|
||||
return newReplace(pwd, lines[1:])
|
||||
case "!replace content":
|
||||
return newReplaceContent(pwd, lines[1:])
|
||||
case "!replace name":
|
||||
return newReplaceName(pwd, lines[1:])
|
||||
case "!generate":
|
||||
return newGenerate(pwd, lines[1:])
|
||||
}
|
@ -7,43 +7,16 @@ import (
|
||||
)
|
||||
|
||||
func TestParseInitFile(t *testing.T) {
|
||||
const init_bs = `
|
||||
!replace
|
||||
content
|
||||
reg
|
||||
*.go
|
||||
ultone => {{.PROJECT_NAME}}
|
||||
EOF
|
||||
|
||||
!replace
|
||||
content
|
||||
exact
|
||||
go.mod
|
||||
module ultone => module {{.PROJECT_NAME}}
|
||||
EOF
|
||||
|
||||
!replace
|
||||
name
|
||||
main.go => loveuer.go
|
||||
EOF
|
||||
|
||||
!generate
|
||||
readme.md
|
||||
# {{.PROJECT_NAME}}
|
||||
|
||||
### run
|
||||
- ` + "`" + `go run . --help` + "`" + `
|
||||
- ` + "`" + `go run .` + "`" + `
|
||||
|
||||
### build
|
||||
- ` + "`" + `docker build -t {repo:tag} -f Dockerfile .` + "`" + `
|
||||
EOF
|
||||
`
|
||||
data := map[string]any{
|
||||
"PROJECT_NAME": "loveuer",
|
||||
bs, err := os.ReadFile("xtest")
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
result, err := RenderVar([]byte(init_bs), data)
|
||||
data := map[string]any{
|
||||
"PROJECT_NAME": "myproject",
|
||||
}
|
||||
|
||||
result, err := RenderVar(bs, data)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
@ -57,5 +30,8 @@ EOF
|
||||
|
||||
for _, item := range cmds {
|
||||
log.Info("one cmd => %s\n\n", item.String())
|
||||
if err = item.Execute(); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,48 @@
|
||||
package tp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TpCmd interface {
|
||||
type Cmd interface {
|
||||
String() string
|
||||
Execute() error
|
||||
}
|
||||
|
||||
var (
|
||||
_ TpCmd = (*TpGenerate)(nil)
|
||||
_ TpCmd = (*TpReplace)(nil)
|
||||
_ Cmd = (*Generate)(nil)
|
||||
_ Cmd = (*ReplaceContent)(nil)
|
||||
_ Cmd = (*ReplaceName)(nil)
|
||||
)
|
||||
|
||||
type TpGenerate struct {
|
||||
type Generate struct {
|
||||
pwd string
|
||||
filename string
|
||||
content []string
|
||||
}
|
||||
|
||||
func (t *TpGenerate) String() string {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
func (t *Generate) String() string {
|
||||
return fmt.Sprintf("!generate\n%s\n%s\n", t.filename, strings.Join(t.content, "\n"))
|
||||
}
|
||||
|
||||
func (t *TpGenerate) Execute() error {
|
||||
func (t *Generate) Execute() error {
|
||||
var (
|
||||
err error
|
||||
location = t.filename
|
||||
input *os.File
|
||||
)
|
||||
|
||||
log.Debug("[Generate] generate[%s]", t.filename)
|
||||
|
||||
if !path.IsAbs(t.filename) {
|
||||
location = path.Join(t.pwd, t.filename)
|
||||
}
|
||||
@ -44,7 +52,7 @@ func (t *TpGenerate) Execute() error {
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(location, "/") {
|
||||
if input, err = os.OpenFile(location, os.O_CREATE|os.O_APPEND, 0744); err != nil {
|
||||
if input, err = os.OpenFile(location, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -58,35 +66,251 @@ func (t *TpGenerate) Execute() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newGenerate(pwd string, lines []string) (*TpGenerate, error) {
|
||||
func newGenerate(pwd string, lines []string) (*Generate, error) {
|
||||
if len(lines) == 0 {
|
||||
return nil, fmt.Errorf("generate cmd require file/folder name")
|
||||
}
|
||||
|
||||
return &TpGenerate{
|
||||
return &Generate{
|
||||
pwd: pwd,
|
||||
filename: lines[0],
|
||||
content: lines[1:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
type TpReplace struct {
|
||||
pwd string
|
||||
}
|
||||
type replaceNameMatchType int
|
||||
|
||||
func (t *TpReplace) String() string {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
const (
|
||||
replaceNameMatchReg replaceNameMatchType = iota + 1
|
||||
replaceNameMatchExact
|
||||
replaceNameMatchPrefix
|
||||
replaceNameMatchSuffix
|
||||
)
|
||||
|
||||
func (t *TpReplace) Execute() error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newReplace(pwd string, lines []string) (*TpReplace, error) {
|
||||
if len(lines) < 2 {
|
||||
return nil, fmt.Errorf("invalid replace cmd")
|
||||
func (rm replaceNameMatchType) Label() string {
|
||||
switch rm {
|
||||
case replaceNameMatchReg:
|
||||
return "reg"
|
||||
case replaceNameMatchExact:
|
||||
return "exact"
|
||||
case replaceNameMatchPrefix:
|
||||
return "prefix"
|
||||
case replaceNameMatchSuffix:
|
||||
return "suffix"
|
||||
}
|
||||
return &TpReplace{pwd: pwd}, nil
|
||||
|
||||
log.Panic("unknown replace match type: %v", rm)
|
||||
return ""
|
||||
}
|
||||
|
||||
type ReplaceContent struct {
|
||||
pwd string
|
||||
name string
|
||||
content string
|
||||
|
||||
targetName string
|
||||
matchType replaceNameMatchType
|
||||
fromContent string
|
||||
targetEmpty bool
|
||||
targetContent string
|
||||
}
|
||||
|
||||
func (t *ReplaceContent) String() string {
|
||||
return fmt.Sprintf("!replace content\n%s\n%s\n", t.name, t.content)
|
||||
}
|
||||
|
||||
func (t *ReplaceContent) Execute() error {
|
||||
var (
|
||||
fn filepath.WalkFunc
|
||||
|
||||
handler = func(location string) error {
|
||||
bs, err := os.ReadFile(location)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("[ReplaceContent] handle[%s] replace [%s] => [%s]", location, t.fromContent, t.targetContent)
|
||||
newbs, err := t.executeFile(bs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(location, newbs, 0644)
|
||||
}
|
||||
)
|
||||
|
||||
switch t.matchType {
|
||||
case replaceNameMatchExact:
|
||||
fn = func(location string, info fs.FileInfo, err error) error {
|
||||
if location == path.Join(t.pwd, t.targetName) {
|
||||
log.Debug("[ReplaceContent] exact match: %s", location)
|
||||
return handler(location)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
case replaceNameMatchPrefix:
|
||||
fn = func(location string, info fs.FileInfo, err error) error {
|
||||
if strings.HasPrefix(path.Base(location), t.targetName) {
|
||||
log.Debug("[ReplaceContent] prefix match: %s", location)
|
||||
return handler(location)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
case replaceNameMatchSuffix:
|
||||
fn = func(location string, info fs.FileInfo, err error) error {
|
||||
if strings.HasSuffix(location, t.targetName) {
|
||||
log.Debug("[ReplaceContent] suffix match: %s", location)
|
||||
return handler(location)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
case replaceNameMatchReg:
|
||||
fn = func(location string, info fs.FileInfo, err error) error {
|
||||
if match, err := regexp.MatchString(t.targetName, location); err == nil && match {
|
||||
log.Debug("[ReplaceContent] reg match: %s", location)
|
||||
return handler(location)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return filepath.Walk(t.pwd, fn)
|
||||
}
|
||||
|
||||
func (t *ReplaceContent) executeFile(raw []byte) ([]byte, error) {
|
||||
scanner := bufio.NewScanner(bytes.NewReader(raw))
|
||||
scanner.Buffer(make([]byte, 1024), 1024*1024)
|
||||
|
||||
lines := make([]string, 0)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
lines = append(
|
||||
lines,
|
||||
strings.ReplaceAll(line, t.fromContent, t.targetContent),
|
||||
)
|
||||
}
|
||||
|
||||
return []byte(strings.Join(lines, "\n")), nil
|
||||
}
|
||||
|
||||
func newReplaceContent(pwd string, lines []string) (*ReplaceContent, error) {
|
||||
if len(lines) != 2 {
|
||||
return nil, fmt.Errorf("invalid replace_content cmd: required 2 lines params")
|
||||
}
|
||||
|
||||
var (
|
||||
name = lines[0]
|
||||
content = lines[1]
|
||||
matchType replaceNameMatchType
|
||||
)
|
||||
|
||||
names := strings.SplitN(name, " ", 2)
|
||||
if len(names) != 2 {
|
||||
return nil, fmt.Errorf("invalid replace_content cmd: name line, required: [reg/exact/prefix/shuffix] {filename}")
|
||||
}
|
||||
|
||||
switch names[0] {
|
||||
case "exact":
|
||||
matchType = replaceNameMatchExact
|
||||
case "reg":
|
||||
matchType = replaceNameMatchReg
|
||||
case "prefix":
|
||||
matchType = replaceNameMatchPrefix
|
||||
case "suffix":
|
||||
matchType = replaceNameMatchSuffix
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid replace_content name match type, example: [reg *.go] [exact go.mod]")
|
||||
}
|
||||
|
||||
var (
|
||||
targetName string = names[1]
|
||||
targetEmpty = false
|
||||
targetContent string
|
||||
)
|
||||
contents := strings.SplitN(content, "=>", 2)
|
||||
fromContent := strings.TrimSpace(contents[0])
|
||||
if len(contents) == 1 {
|
||||
targetEmpty = true
|
||||
} else {
|
||||
if targetContent = strings.TrimSpace(contents[1]); targetContent == "" || targetContent == `""` || targetContent == `''` {
|
||||
targetEmpty = true
|
||||
}
|
||||
}
|
||||
|
||||
return &ReplaceContent{
|
||||
pwd: pwd,
|
||||
name: name,
|
||||
content: content,
|
||||
|
||||
matchType: matchType,
|
||||
targetName: targetName,
|
||||
fromContent: fromContent,
|
||||
targetEmpty: targetEmpty,
|
||||
targetContent: targetContent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ReplaceName struct {
|
||||
pwd string
|
||||
line string
|
||||
|
||||
targetEmpty bool
|
||||
fromContent string
|
||||
targetContent string
|
||||
}
|
||||
|
||||
func (t *ReplaceName) String() string {
|
||||
return fmt.Sprintf("!replace name\n%s\n", t.line)
|
||||
}
|
||||
|
||||
func (t *ReplaceName) Execute() error {
|
||||
fullpath := path.Join(t.pwd, t.fromContent)
|
||||
if t.targetEmpty {
|
||||
return os.RemoveAll(fullpath)
|
||||
}
|
||||
|
||||
ftpath := path.Join(t.pwd, t.targetContent)
|
||||
return os.Rename(fullpath, ftpath)
|
||||
}
|
||||
|
||||
func newReplaceName(pwd string, lines []string) (*ReplaceName, error) {
|
||||
if len(lines) != 1 {
|
||||
return nil, fmt.Errorf("replace_name need one line param, for example: mian.go => main.go")
|
||||
}
|
||||
|
||||
var (
|
||||
content = lines[0]
|
||||
targetEmpty = false
|
||||
fromContent string
|
||||
targetContent string
|
||||
)
|
||||
|
||||
contents := strings.SplitN(content, "=>", 2)
|
||||
fromContent = strings.TrimSpace(contents[0])
|
||||
if len(contents) == 1 {
|
||||
targetEmpty = true
|
||||
} else {
|
||||
if targetContent = strings.TrimSpace(contents[1]); targetContent == "" || targetContent == `""` || targetContent == `''` {
|
||||
targetEmpty = true
|
||||
}
|
||||
}
|
||||
|
||||
if !targetEmpty {
|
||||
if (strings.HasPrefix(targetContent, `"`) && strings.HasSuffix(targetContent, `"`)) || (strings.HasPrefix(targetContent, `'`) && strings.HasSuffix(targetContent, `'`)) {
|
||||
targetContent = targetContent[1 : len(targetContent)-1]
|
||||
}
|
||||
}
|
||||
|
||||
return &ReplaceName{
|
||||
pwd: pwd,
|
||||
line: content,
|
||||
|
||||
targetEmpty: targetEmpty,
|
||||
fromContent: fromContent,
|
||||
targetContent: targetContent,
|
||||
}, nil
|
||||
}
|
||||
|
66
nft/nfctl/version/version.go
Normal file
66
nft/nfctl/version/version.go
Normal file
@ -0,0 +1,66 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/fatih/color"
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"github.com/savioxavier/termlink"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const Version = "v24.07.12-r1"
|
||||
|
||||
var (
|
||||
lk = &sync.Mutex{}
|
||||
empty = func() {}
|
||||
upgrade = func(v string) func() {
|
||||
return func() {
|
||||
color.Green("\n🎉 🎉 🎉 [nfctl] New Version Found: %s", v)
|
||||
color.Cyan("Upgrade it with: [go install github.com/loveuer/nf/nft/nfctl@latest]")
|
||||
fmt.Print("Or Download by: ")
|
||||
color.Cyan(termlink.Link("Releases", "https://github.com/loveuer/nf/releases"))
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
Fn = empty
|
||||
)
|
||||
|
||||
func Check() {
|
||||
ready := make(chan struct{})
|
||||
go func() {
|
||||
ready <- struct{}{}
|
||||
uri := "https://raw.gitcode.com/loveuer/nf/raw/master/nft/nfctl/version/version.go"
|
||||
prefix := "const Version = "
|
||||
resp, err := http.Get(uri)
|
||||
if err != nil {
|
||||
log.Debug("[Check] http get[%s] err: %v", uri, err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
scanner.Buffer(make([]byte, 16*1024), 1024*1024)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
log.Debug("[Check] version.go line: %s", line)
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
v := strings.TrimPrefix(line, prefix)
|
||||
if len(v) > 2 {
|
||||
v = v[1 : len(v)-1]
|
||||
}
|
||||
|
||||
if v != "" && v > Version {
|
||||
lk.Lock()
|
||||
Fn = upgrade(v)
|
||||
lk.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
<-ready
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user