feat: add nfctl(ctl to start nf project)
This commit is contained in:
		
							
								
								
									
										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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user