Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e5727f021 | |||
| 3012507fcc |
+8
-6
@@ -5,12 +5,14 @@ prompts:
|
|||||||
preset: "下面的代码来自文件 {{path}}:\n---\n{{content}}"
|
preset: "下面的代码来自文件 {{path}}:\n---\n{{content}}"
|
||||||
|
|
||||||
preset:
|
preset:
|
||||||
all:
|
all:
|
||||||
- "cmd/*"
|
- "*"
|
||||||
- "internal/tree/*"
|
# - "cmd/*"
|
||||||
- "internal/utils/*"
|
# - "internal/tree/*"
|
||||||
- "internal/config/*"
|
# - "internal/utils/*"
|
||||||
- "main.go"
|
# - "internal/config/*"
|
||||||
|
# - "internal/resolver/*"
|
||||||
|
# - "main.go"
|
||||||
|
|
||||||
internal:
|
internal:
|
||||||
- "internal/tree/*"
|
- "internal/tree/*"
|
||||||
|
|||||||
+27
-15
@@ -4,14 +4,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"cliprepo/internal/config"
|
"cliprepo/internal/config"
|
||||||
|
"cliprepo/internal/resolver"
|
||||||
"cliprepo/internal/tree"
|
"cliprepo/internal/tree"
|
||||||
"cliprepo/internal/utils"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/atotto/clipboard"
|
"github.com/atotto/clipboard"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var delineate string
|
var delineate string
|
||||||
@@ -35,43 +36,53 @@ var runCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
|
|
||||||
// builder.WriteString("===== PROJECT TREE =====\n")
|
|
||||||
builder.WriteString(treeStr)
|
builder.WriteString(treeStr)
|
||||||
builder.WriteString("\n\n")
|
builder.WriteString("\n\n")
|
||||||
|
|
||||||
// 2. preset files
|
// 2. resolver (GLOBAL FILE REGISTRY)
|
||||||
var patterns []string
|
r := resolver.New(".")
|
||||||
|
|
||||||
raw, ok := cfg.Preset[delineate]
|
raw, ok := cfg.Preset[delineate]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("preset not found: %s", delineate)
|
return fmt.Errorf("preset not found: %s", delineate)
|
||||||
}
|
}
|
||||||
|
|
||||||
patterns = append(patterns, raw...)
|
patterns := []string(raw)
|
||||||
|
var files []string
|
||||||
|
|
||||||
files, err := utils.ExpandGlob(patterns)
|
for _, pattern := range patterns {
|
||||||
if err != nil {
|
// 统一交给 resolver 处理,无论是 "*", "cmd/*" 还是 "**/*.go"
|
||||||
return err
|
matches, err := r.ResolvePattern(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
files = append(files, matches...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. template
|
// 3. dedup + stable order
|
||||||
|
files = r.Unique(files)
|
||||||
|
sort.Strings(files)
|
||||||
|
|
||||||
|
// 4. template
|
||||||
template := cfg.Prompts.Preset
|
template := cfg.Prompts.Preset
|
||||||
|
if template == "" {
|
||||||
|
template = "The following code is from file {{path}}:\n---\n{{content}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. render
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
|
||||||
content, err := os.ReadFile(file)
|
content, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
path := file
|
filePath := file
|
||||||
if cfg.Config.PathMode == "absolute" {
|
if cfg.Config.PathMode == "absolute" {
|
||||||
abs, _ := filepath.Abs(file)
|
abs, _ := filepath.Abs(file)
|
||||||
path = abs
|
filePath = abs
|
||||||
}
|
}
|
||||||
|
|
||||||
block := strings.ReplaceAll(template, "{{path}}", path)
|
block := strings.ReplaceAll(template, "{{path}}", filePath)
|
||||||
block = strings.ReplaceAll(block, "{{content}}", string(content))
|
block = strings.ReplaceAll(block, "{{content}}", string(content))
|
||||||
|
|
||||||
builder.WriteString(block)
|
builder.WriteString(block)
|
||||||
@@ -80,6 +91,7 @@ var runCmd = &cobra.Command{
|
|||||||
|
|
||||||
result := builder.String()
|
result := builder.String()
|
||||||
|
|
||||||
|
// 6. output
|
||||||
if useClipboard {
|
if useClipboard {
|
||||||
return clipboard.WriteAll(result)
|
return clipboard.WriteAll(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cliprepo/internal/tree"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
|
root string
|
||||||
|
seen map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(root string) *Resolver {
|
||||||
|
return &Resolver{
|
||||||
|
root: root,
|
||||||
|
seen: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolvePattern 智能解析各种 pattern 并过滤 ignore 文件
|
||||||
|
func (r *Resolver) ResolvePattern(pattern string) ([]string, error) {
|
||||||
|
if pattern == "*" {
|
||||||
|
return r.WalkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches []string
|
||||||
|
// 处理类似 cmd/* 或 internal/tree/* 的情况
|
||||||
|
// 如果包含 * 且不是以 ** 开头,转换为通过 filepath.Walk 匹配或直接 Glob 过滤
|
||||||
|
rawMatches, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ig, _ := tree.Load(r.root)
|
||||||
|
|
||||||
|
for _, m := range rawMatches {
|
||||||
|
info, err := os.Stat(m)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rel, _ := filepath.Rel(r.root, m)
|
||||||
|
// 统一检查 .gitignore
|
||||||
|
if ig != nil && ig.MatchesPath(rel) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
// 如果匹配到的是目录(比如 internal/* 匹配到了 internal/config),则深层遍历该目录
|
||||||
|
err := filepath.Walk(m, func(path string, fInfo os.FileInfo, walkErr error) error {
|
||||||
|
if walkErr != nil || fInfo.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fRel, _ := filepath.Rel(r.root, path)
|
||||||
|
if ig != nil && ig.MatchesPath(fRel) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r.add(fRel, &matches)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.add(rel, &matches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkAll 遍历全库并过滤
|
||||||
|
func (r *Resolver) WalkAll() ([]string, error) {
|
||||||
|
var result []string
|
||||||
|
ig, _ := tree.Load(r.root)
|
||||||
|
|
||||||
|
err := filepath.Walk(r.root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() && info.Name() == ".git" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
rel, _ := filepath.Rel(r.root, path)
|
||||||
|
|
||||||
|
if ig != nil && ig.MatchesPath(rel) {
|
||||||
|
if info.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.add(rel, &result)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) AddFile(path string, result *[]string) {
|
||||||
|
r.add(path, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) add(path string, result *[]string) {
|
||||||
|
if _, ok := r.seen[path]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.seen[path] = struct{}{}
|
||||||
|
*result = append(*result, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) Unique(in []string) []string {
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
out := make([]string, 0, len(in))
|
||||||
|
|
||||||
|
for _, f := range in {
|
||||||
|
if _, ok := seen[f]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[f] = struct{}{}
|
||||||
|
out = append(out, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
+6
-38
@@ -3,52 +3,20 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
// "strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExpandGlob(patterns []string) ([]string, error) {
|
func ExpandGlob(patterns []string) ([]string, error) {
|
||||||
var result []string
|
var result []string
|
||||||
seen := make(map[string]struct{})
|
|
||||||
|
|
||||||
for _, pattern := range patterns {
|
for _, pattern := range patterns {
|
||||||
|
|
||||||
if pattern == "*" {
|
if pattern == "*" {
|
||||||
filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
files, err := filepath.Glob("**") // fallback
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
add(path, seen, &result)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
isRecursive := strings.Contains(pattern, "**")
|
|
||||||
|
|
||||||
if isRecursive {
|
|
||||||
root := strings.Split(pattern, "**")[0]
|
|
||||||
if root == "" {
|
|
||||||
root = "."
|
|
||||||
}
|
}
|
||||||
|
result = append(result, files...)
|
||||||
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil || info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
matched, _ := filepath.Match(
|
|
||||||
strings.Replace(pattern, "**", "*", 1),
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
|
|
||||||
if matched {
|
|
||||||
add(path, seen, &result)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +30,7 @@ func ExpandGlob(patterns []string) ([]string, error) {
|
|||||||
if err != nil || info.IsDir() {
|
if err != nil || info.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
add(m, seen, &result)
|
result = append(result, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user