feat: add nfctl(ctl to start nf project)

This commit is contained in:
loveuer 2024-07-12 16:00:15 +08:00
parent 8a423c2887
commit 0f139cda98
14 changed files with 593 additions and 111 deletions

6
go.mod
View File

@ -4,8 +4,10 @@ go 1.20
require ( require (
github.com/fatih/color v1.17.0 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/go-git/go-git/v5 v5.12.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/spf13/cobra v1.8.1
) )
require ( require (
@ -16,15 +18,17 @@ require (
github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/emirpasic/gods v1.18.1 // 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/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/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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pjbgf/sha1cd v0.3.0 // 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/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.2.2 // 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 github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.12.0 // indirect golang.org/x/mod v0.12.0 // indirect

10
go.sum
View File

@ -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.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 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 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 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= 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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/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 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 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/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 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= 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/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

36
nft/nfctl/clone/clone.go Normal file
View 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
View 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
View 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
View 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)
},
}
)

View File

@ -1,50 +1,19 @@
package main package main
import ( import (
"github.com/go-git/go-billy/v5/memfs" "context"
"github.com/go-git/go-git/v5" "github.com/loveuer/nf/nft/nfctl/cmd"
_ "github.com/go-git/go-git/v5" "github.com/loveuer/nf/nft/nfctl/version"
"github.com/go-git/go-git/v5/plumbing/transport/http" "os/signal"
"github.com/go-git/go-git/v5/storage/memory" "syscall"
"github.com/loveuer/nf/nft/log"
"io"
) )
func main() { func main() {
memo := memory.NewStorage() ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
fs := memfs.New() defer cancel()
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)
}
infos, err := fs.ReadDir(".") version.Check()
if err != nil { defer version.Fn()
panic(err)
}
for _, item := range infos { _ = cmd.Root.ExecuteContext(ctx)
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
} }

5
nft/nfctl/opt/var.go Normal file
View File

@ -0,0 +1,5 @@
package opt
var (
Debug bool
)

23
nft/nfctl/readme.md Normal file
View 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)

View File

@ -7,10 +7,10 @@ import (
"strings" "strings"
) )
func ParseCmd(pwd string, content []byte) ([]TpCmd, error) { func ParseCmd(pwd string, content []byte) ([]Cmd, error) {
var ( var (
err error err error
cmds = make([]TpCmd, 0) cmds = make([]Cmd, 0)
start = false start = false
) )
@ -24,7 +24,7 @@ func ParseCmd(pwd string, content []byte) ([]TpCmd, error) {
continue continue
} }
if strings.HasPrefix(line, "#") { if !start && strings.HasPrefix(line, "#") {
continue continue
} }
@ -44,7 +44,7 @@ func ParseCmd(pwd string, content []byte) ([]TpCmd, error) {
continue continue
} }
var cmd TpCmd var cmd Cmd
if cmd, err = ParseBlock(pwd, record); err != nil { if cmd, err = ParseBlock(pwd, record); err != nil {
return nil, err return nil, err
} }
@ -66,10 +66,12 @@ func ParseCmd(pwd string, content []byte) ([]TpCmd, error) {
return cmds, err return cmds, err
} }
func ParseBlock(pwd string, lines []string) (TpCmd, error) { func ParseBlock(pwd string, lines []string) (Cmd, error) {
switch lines[0] { switch lines[0] {
case "!replace": case "!replace content":
return newReplace(pwd, lines[1:]) return newReplaceContent(pwd, lines[1:])
case "!replace name":
return newReplaceName(pwd, lines[1:])
case "!generate": case "!generate":
return newGenerate(pwd, lines[1:]) return newGenerate(pwd, lines[1:])
} }

View File

@ -7,43 +7,16 @@ import (
) )
func TestParseInitFile(t *testing.T) { func TestParseInitFile(t *testing.T) {
const init_bs = ` bs, err := os.ReadFile("xtest")
!replace if err != nil {
content log.Fatal(err.Error())
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",
} }
result, err := RenderVar([]byte(init_bs), data) data := map[string]any{
"PROJECT_NAME": "myproject",
}
result, err := RenderVar(bs, data)
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
} }
@ -57,5 +30,8 @@ EOF
for _, item := range cmds { for _, item := range cmds {
log.Info("one cmd => %s\n\n", item.String()) log.Info("one cmd => %s\n\n", item.String())
if err = item.Execute(); err != nil {
log.Fatal(err.Error())
}
} }
} }

View File

@ -1,40 +1,48 @@
package tp package tp
import ( import (
"bufio"
"bytes"
"fmt" "fmt"
"github.com/loveuer/nf/nft/log"
"io/fs"
"os" "os"
"path" "path"
"path/filepath"
"regexp"
"strings" "strings"
) )
type TpCmd interface { type Cmd interface {
String() string String() string
Execute() error Execute() error
} }
var ( var (
_ TpCmd = (*TpGenerate)(nil) _ Cmd = (*Generate)(nil)
_ TpCmd = (*TpReplace)(nil) _ Cmd = (*ReplaceContent)(nil)
_ Cmd = (*ReplaceName)(nil)
) )
type TpGenerate struct { type Generate struct {
pwd string pwd string
filename string filename string
content []string content []string
} }
func (t *TpGenerate) String() string { func (t *Generate) String() string {
//TODO implement me return fmt.Sprintf("!generate\n%s\n%s\n", t.filename, strings.Join(t.content, "\n"))
panic("implement me")
} }
func (t *TpGenerate) Execute() error { func (t *Generate) Execute() error {
var ( var (
err error err error
location = t.filename location = t.filename
input *os.File input *os.File
) )
log.Debug("[Generate] generate[%s]", t.filename)
if !path.IsAbs(t.filename) { if !path.IsAbs(t.filename) {
location = path.Join(t.pwd, t.filename) location = path.Join(t.pwd, t.filename)
} }
@ -44,7 +52,7 @@ func (t *TpGenerate) Execute() error {
} }
if !strings.HasSuffix(location, "/") { 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 return err
} }
@ -58,35 +66,251 @@ func (t *TpGenerate) Execute() error {
return nil return nil
} }
func newGenerate(pwd string, lines []string) (*TpGenerate, error) { func newGenerate(pwd string, lines []string) (*Generate, error) {
if len(lines) == 0 { if len(lines) == 0 {
return nil, fmt.Errorf("generate cmd require file/folder name") return nil, fmt.Errorf("generate cmd require file/folder name")
} }
return &TpGenerate{ return &Generate{
pwd: pwd, pwd: pwd,
filename: lines[0], filename: lines[0],
content: lines[1:], content: lines[1:],
}, nil }, nil
} }
type TpReplace struct { type replaceNameMatchType int
pwd string
}
func (t *TpReplace) String() string { const (
//TODO implement me replaceNameMatchReg replaceNameMatchType = iota + 1
panic("implement me") replaceNameMatchExact
} replaceNameMatchPrefix
replaceNameMatchSuffix
)
func (t *TpReplace) Execute() error { func (rm replaceNameMatchType) Label() string {
//TODO implement me switch rm {
panic("implement me") case replaceNameMatchReg:
} return "reg"
case replaceNameMatchExact:
func newReplace(pwd string, lines []string) (*TpReplace, error) { return "exact"
if len(lines) < 2 { case replaceNameMatchPrefix:
return nil, fmt.Errorf("invalid replace cmd") 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
} }

View 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
}