package tp import ( "bufio" "bytes" "fmt" "github.com/loveuer/nf/nft/log" "io/fs" "os" "path" "path/filepath" "regexp" "strings" ) type Cmd interface { String() string Execute() error } var ( _ Cmd = (*Generate)(nil) _ Cmd = (*ReplaceContent)(nil) _ Cmd = (*ReplaceName)(nil) ) type Generate struct { pwd string filename string content []string } func (t *Generate) String() string { return fmt.Sprintf("!generate\n%s\n%s\n", t.filename, strings.Join(t.content, "\n")) } 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) } if err = os.MkdirAll(path.Dir(location), 0644); err != nil { return err } if !strings.HasSuffix(location, "/") { if input, err = os.OpenFile(location, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0744); err != nil { return err } if len(t.content) > 0 { content := strings.Join(t.content, "\n") _, err = input.WriteString(content) return err } } return nil } func newGenerate(pwd string, lines []string) (*Generate, error) { if len(lines) == 0 { return nil, fmt.Errorf("generate cmd require file/folder name") } return &Generate{ pwd: pwd, filename: lines[0], content: lines[1:], }, nil } 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 } 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 }