Created
October 30, 2024 14:57
-
-
Save Quorafind/c1c373b0515cb261b83145f39c77a78d to your computer and use it in GitHub Desktop.
Duplicate any templates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"templates": [ | |
{ | |
"name": "Vue3 + TypeScript", | |
"path": "./templates/vue3-ts", | |
"description": "Vue 3 项目模板,使用 TypeScript", | |
"version": "1.0.0" | |
}, | |
{ | |
"name": "React + TypeScript", | |
"path": "./templates/react-ts", | |
"description": "React 项目模板,使用 TypeScript", | |
"version": "1.0.0" | |
}, | |
{ | |
"name": "Node.js", | |
"path": "./templates/nodejs", | |
"description": "Node.js 后端项目模板", | |
"version": "1.0.0" | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"flag" | |
"fmt" | |
"os" | |
"os/exec" | |
"path/filepath" | |
"strings" | |
"time" | |
"encoding/json" | |
) | |
// 模板配置 | |
type Template struct { | |
Name string `json:"name"` | |
Path string `json:"path"` | |
Description string `json:"description"` | |
Version string `json:"version"` | |
} | |
type Config struct { | |
Templates []Template `json:"templates"` | |
} | |
var configPath string // 全局变量,用于存储配置文件路径 | |
// 添加加载配置函数 | |
func loadConfig() (*Config, error) { | |
configFile, err := os.ReadFile(configPath) | |
if err != nil { | |
return nil, fmt.Errorf("读取配置文件失败: %v", err) | |
} | |
var config Config | |
if err := json.Unmarshal(configFile, &config); err != nil { | |
return nil, fmt.Errorf("解析配置文件失败: %v", err) | |
} | |
return &config, nil | |
} | |
// getTemplateChoice 获取用户选择的模板 | |
func getTemplateChoice(reader *bufio.Reader, templates []Template) (Template, error) { | |
fmt.Println("可用的项目模板:") | |
for i, tmpl := range templates { | |
fmt.Printf("[%d] %s (%s)\n", i+1, tmpl.Name, tmpl.Description) | |
} | |
fmt.Print("请选择模板 (输入数字,直接回车使用第一个模板): ") | |
input, err := reader.ReadString('\n') | |
if err != nil { | |
return Template{}, fmt.Errorf("读取输入失败: %v", err) | |
} | |
input = strings.TrimSpace(input) | |
if input == "" { | |
return templates[0], nil | |
} | |
choice, err := validateTemplateChoice(input, len(templates)) | |
if err != nil { | |
return Template{}, err | |
} | |
return templates[choice-1], nil | |
} | |
// validateTemplateChoice 验证模板选择是否有效 | |
func validateTemplateChoice(input string, maxChoice int) (int, error) { | |
var choice int | |
if _, err := fmt.Sscanf(input, "%d", &choice); err != nil { | |
return 0, fmt.Errorf("请输入有效的数字") | |
} | |
if choice < 1 || choice > maxChoice { | |
return 0, fmt.Errorf("无效的选择,请输入 1 到 %d 之间的数字", maxChoice) | |
} | |
return choice, nil | |
} | |
// getProjectName 获取项目名称 | |
func getProjectName(reader *bufio.Reader) (string, error) { | |
fmt.Print("请输入项目名称 (直接回车使用默认名称): ") | |
projectName, err := reader.ReadString('\n') | |
if err != nil { | |
return "", fmt.Errorf("读取输入失败: %v", err) | |
} | |
projectName = strings.TrimSpace(projectName) | |
if projectName == "" { | |
now := time.Now() | |
return fmt.Sprintf("新建项目-%s", now.Format("20060102150405")), nil | |
} | |
return projectName, nil | |
} | |
// createProject 创建新项目 | |
func createProject(templatePath, targetPath string) error { | |
// 检查目标路径是否已存在 | |
if _, err := os.Stat(targetPath); err == nil { | |
return fmt.Errorf("目标路径 '%s' 已存在", targetPath) | |
} | |
// 复制模板文件夹 | |
if err := copyDir(templatePath, targetPath); err != nil { | |
return fmt.Errorf("复制模板失败: %v", err) | |
} | |
// 创建或追加TODO.md | |
todoPath := filepath.Join(targetPath, "TODO.md") | |
todoContent := "- [ ] 完成编辑\n" | |
if err := appendToFile(todoPath, todoContent); err != nil { | |
return fmt.Errorf("添加TODO失败: %v", err) | |
} | |
return nil | |
} | |
// openInVSCode 在VSCode中打开项目 | |
func openInVSCode(targetPath string) error { | |
cmd := exec.Command("code", ".") | |
cmd.Dir = targetPath | |
if err := cmd.Run(); err != nil { | |
return fmt.Errorf("打开VSCode失败: %v", err) | |
} | |
return nil | |
} | |
func main() { | |
// 命令行参数 | |
var projectName string | |
var templateName string | |
var targetDir string | |
flag.StringVar(&projectName, "name", "", "项目名称") | |
flag.StringVar(&projectName, "n", "", "项目名称") | |
flag.StringVar(&templateName, "template", "", "模板名称") | |
flag.StringVar(&templateName, "t", "", "模板名称") | |
flag.StringVar(&targetDir, "dir", "", "目标目录") | |
flag.StringVar(&targetDir, "d", "", "目标目录") | |
flag.StringVar(&configPath, "config", "config.json", "配置文件路径") | |
flag.StringVar(&configPath, "c", "config.json", "配置文件路径") | |
flag.Parse() | |
// 如果没有指定目标目录,使用当前目录 | |
if targetDir == "" { | |
var err error | |
targetDir, err = os.Getwd() | |
if err != nil { | |
fmt.Printf("获取当前目录失败: %v\n", err) | |
return | |
} | |
} | |
// 加载配置 | |
config, err := loadConfig() | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
reader := bufio.NewReader(os.Stdin) | |
var selectedTemplate Template | |
// 获取模板名称 | |
if templateName == "" { | |
var err error | |
selectedTemplate, err = getTemplateChoice(reader, config.Templates) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
} else { | |
// 根据命令行参数查找模板 | |
found := false | |
for _, tmpl := range config.Templates { | |
if tmpl.Name == templateName { | |
selectedTemplate = tmpl | |
found = true | |
break | |
} | |
} | |
if !found { | |
fmt.Printf("错误: 模板 '%s' 不存在\n", templateName) | |
return | |
} | |
} | |
// 获取项目名称 | |
if projectName == "" { | |
var err error | |
projectName, err = getProjectName(reader) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
} | |
targetPath := filepath.Join(targetDir, projectName) | |
// 创建项目 | |
if err := createProject(selectedTemplate.Path, targetPath); err != nil { | |
fmt.Println(err) | |
return | |
} | |
fmt.Printf("项目创建成功: %s\n", targetPath) | |
// 在VSCode中打开项目 | |
if err := openInVSCode(targetPath); err != nil { | |
fmt.Println(err) | |
return | |
} | |
} | |
// copyDir 递归复制目录 | |
func copyDir(src string, dst string) error { | |
// 创建目标目录 | |
err := os.MkdirAll(dst, 0755) | |
if err != nil { | |
return err | |
} | |
// 读取源目录 | |
entries, err := os.ReadDir(src) | |
if err != nil { | |
return err | |
} | |
for _, entry := range entries { | |
sourcePath := filepath.Join(src, entry.Name()) | |
destPath := filepath.Join(dst, entry.Name()) | |
if entry.IsDir() { | |
// 递归复制子目录 | |
err = copyDir(sourcePath, destPath) | |
if err != nil { | |
return err | |
} | |
} else { | |
// 复制文件 | |
err = copyFile(sourcePath, destPath) | |
if err != nil { | |
return err | |
} | |
} | |
} | |
return nil | |
} | |
// copyFile 复制单个文件 | |
func copyFile(src, dst string) error { | |
sourceFile, err := os.Open(src) | |
if err != nil { | |
return err | |
} | |
defer sourceFile.Close() | |
destFile, err := os.Create(dst) | |
if err != nil { | |
return err | |
} | |
defer destFile.Close() | |
_, err = destFile.ReadFrom(sourceFile) | |
return err | |
} | |
// appendToFile 追加内容到文件 | |
func appendToFile(filepath string, content string) error { | |
f, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
if err != nil { | |
return err | |
} | |
defer f.Close() | |
_, err = f.WriteString(content) | |
return err | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment