2024-07-09 18:08:49 +08:00
|
|
|
package tp
|
|
|
|
|
|
|
|
import (
|
2024-07-12 16:00:15 +08:00
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2024-07-09 18:08:49 +08:00
|
|
|
"fmt"
|
2024-07-12 16:00:15 +08:00
|
|
|
"github.com/loveuer/nf/nft/log"
|
|
|
|
"io/fs"
|
2024-07-09 18:08:49 +08:00
|
|
|
"os"
|
|
|
|
"path"
|
2024-07-12 16:00:15 +08:00
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
2024-07-09 18:08:49 +08:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
type Cmd interface {
|
2024-07-09 18:08:49 +08:00
|
|
|
String() string
|
|
|
|
Execute() error
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2024-07-12 16:00:15 +08:00
|
|
|
_ Cmd = (*Generate)(nil)
|
|
|
|
_ Cmd = (*ReplaceContent)(nil)
|
|
|
|
_ Cmd = (*ReplaceName)(nil)
|
2024-07-09 18:08:49 +08:00
|
|
|
)
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
type Generate struct {
|
2024-07-09 18:08:49 +08:00
|
|
|
pwd string
|
|
|
|
filename string
|
|
|
|
content []string
|
|
|
|
}
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
func (t *Generate) String() string {
|
|
|
|
return fmt.Sprintf("!generate\n%s\n%s\n", t.filename, strings.Join(t.content, "\n"))
|
2024-07-09 18:08:49 +08:00
|
|
|
}
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
func (t *Generate) Execute() error {
|
2024-07-09 18:08:49 +08:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
location = t.filename
|
|
|
|
input *os.File
|
|
|
|
)
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
log.Debug("[Generate] generate[%s]", t.filename)
|
|
|
|
|
2024-07-09 18:08:49 +08:00
|
|
|
if !path.IsAbs(t.filename) {
|
|
|
|
location = path.Join(t.pwd, t.filename)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = os.MkdirAll(path.Dir(location), 0644); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.HasSuffix(location, "/") {
|
2024-07-12 16:00:15 +08:00
|
|
|
if input, err = os.OpenFile(location, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0744); err != nil {
|
2024-07-09 18:08:49 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(t.content) > 0 {
|
|
|
|
content := strings.Join(t.content, "\n")
|
|
|
|
_, err = input.WriteString(content)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
func newGenerate(pwd string, lines []string) (*Generate, error) {
|
2024-07-09 18:08:49 +08:00
|
|
|
if len(lines) == 0 {
|
|
|
|
return nil, fmt.Errorf("generate cmd require file/folder name")
|
|
|
|
}
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
return &Generate{
|
2024-07-09 18:08:49 +08:00
|
|
|
pwd: pwd,
|
|
|
|
filename: lines[0],
|
|
|
|
content: lines[1:],
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
type replaceNameMatchType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
replaceNameMatchReg replaceNameMatchType = iota + 1
|
|
|
|
replaceNameMatchExact
|
|
|
|
replaceNameMatchPrefix
|
|
|
|
replaceNameMatchSuffix
|
|
|
|
)
|
|
|
|
|
|
|
|
func (rm replaceNameMatchType) Label() string {
|
|
|
|
switch rm {
|
|
|
|
case replaceNameMatchReg:
|
|
|
|
return "reg"
|
|
|
|
case replaceNameMatchExact:
|
|
|
|
return "exact"
|
|
|
|
case replaceNameMatchPrefix:
|
|
|
|
return "prefix"
|
|
|
|
case replaceNameMatchSuffix:
|
|
|
|
return "suffix"
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2024-07-09 18:08:49 +08:00
|
|
|
}
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
func (t *ReplaceName) String() string {
|
|
|
|
return fmt.Sprintf("!replace name\n%s\n", t.line)
|
2024-07-09 18:08:49 +08:00
|
|
|
}
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
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)
|
2024-07-09 18:08:49 +08:00
|
|
|
}
|
|
|
|
|
2024-07-12 16:00:15 +08:00
|
|
|
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")
|
2024-07-09 18:08:49 +08:00
|
|
|
}
|
2024-07-12 16:00:15 +08:00
|
|
|
|
|
|
|
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
|
2024-07-09 18:08:49 +08:00
|
|
|
}
|