From 8d46144f17e43bbb073c27fcf817cd47f43d9cb0 Mon Sep 17 00:00:00 2001 From: Miguel Prieto Date: Mon, 13 Oct 2025 10:22:35 -0300 Subject: [PATCH] Code generation changes --- cmd/code.go | 420 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 321 insertions(+), 99 deletions(-) diff --git a/cmd/code.go b/cmd/code.go index 442cc31..adee4b8 100644 --- a/cmd/code.go +++ b/cmd/code.go @@ -1,139 +1,361 @@ package cmd import ( - "github.com/spf13/cobra" + "bufio" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" - "strings" "os" + "path/filepath" + "strconv" + "strings" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +const ( + defaultTemplateRepo = "mp-orkes/cli-templates" ) +type TemplatesConfig struct { + Repo string `yaml:"repo"` +} + +func getTemplateRepo() string { + home, err := os.UserHomeDir() + if err != nil { + return defaultTemplateRepo + } + + configPath := filepath.Join(home, ".conductor-cli", "templates.yaml") + data, err := os.ReadFile(configPath) + if err != nil { + // File doesn't exist, use default + return defaultTemplateRepo + } + + var config TemplatesConfig + if err := yaml.Unmarshal(data, &config); err != nil { + return defaultTemplateRepo + } + + if config.Repo == "" { + return defaultTemplateRepo + } + + return config.Repo +} + +func getTemplateBaseURL() string { + repo := getTemplateRepo() + return fmt.Sprintf("https://raw.githubusercontent.com/%s/main", repo) +} + +func getListURL() string { + return fmt.Sprintf("%s/manifest.json", getTemplateBaseURL()) +} + type Field struct { - Name string `json:"name"` - Attribute string `json:"attribute"` + Name string `json:"name"` + Attribute string `json:"attribute"` Prompt string `json:"prompt"` } type BoilerPlateFile struct { - Name string `json:"name"` - Fields []Field `json:"fields"` + Name string `json:"name"` + Fields []Field `json:"fields"` } + type BoilerPlate struct { Files []BoilerPlateFile `json:"files"` } +type Template struct { + Name string `json:"name"` + Description string `json:"description"` + Path string `json:"path"` +} + +type Framework struct { + Name string `json:"name"` + Templates []Template `json:"templates"` +} + +type Language struct { + Name string `json:"name"` + Frameworks []Framework `json:"frameworks"` +} + +type TemplateList struct { + Languages []Language `json:"languages"` +} + var codeCmd = &cobra.Command{ - Use: "code", - Short: "Code Generation", + Use: "code", + Short: "Generate projects from templates", + Long: "Interactive project generation from boilerplate templates", + RunE: interactiveCodeGenerate, + SilenceUsage: true, +} + +var codeListCmd = &cobra.Command{ + Use: "list", + Short: "List available templates", + Long: "Display all available project templates organized by language and framework", + RunE: listTemplates, + SilenceUsage: true, } -var ( - generateCodeCmd = &cobra.Command{ - Use: "generate", - Short: "Generate code from boilerplate", - Args: cobra.ArbitraryArgs, - RunE: GenerateCode, - SilenceUsage: true, +func fetchTemplateList() (*TemplateList, error) { + listURL := getListURL() + resp, err := http.Get(listURL) + if err != nil { + return nil, fmt.Errorf("failed to fetch template list: %w", err) } -) + defer resp.Body.Close() -func GenerateCode(cmd *cobra.Command, args []string) error { - var boilerplate BoilerPlate + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response: %w", err) + } - name, _ := cmd.Flags().GetString("name") - tpe, _ := cmd.Flags().GetString("type") - lang, _ := cmd.Flags().GetString("lang") - bpl, _ := cmd.Flags().GetString("bpl") - - fmt.Println("Generating code...") - - // Creating project directory - fmt.Println("Creating directory...") - err := os.Mkdir(name, 0755) + var list TemplateList + if err := json.Unmarshal(body, &list); err != nil { + return nil, fmt.Errorf("failed to parse template list: %w", err) + } + + return &list, nil +} + +func listTemplates(cmd *cobra.Command, args []string) error { + list, err := fetchTemplateList() if err != nil { - fmt.Println(err) return err } - fmt.Println("Directory created successfully!") - - // Loading Boilerplate configuration - fmt.Println("Loading Boilerplate configuration file...") - response, errget := http.Get("https://raw.githubusercontent.com/conductor-sdk/boilerplates/main/"+lang+"/"+tpe+"/"+bpl+"/bp.json") - - if errget != nil { - fmt.Println(errget) - return errget - } - - body, errbody := ioutil.ReadAll(response.Body) - if errbody != nil { - fmt.Println(errbody) - return errbody - } - // close response body - response.Body.Close() - - errparse := json.Unmarshal(body, &boilerplate) - - if errparse != nil { - fmt.Println(errparse) - return errparse - } - fmt.Println("Boilerplate configuration loaded successfully!") - - - for _, element := range boilerplate.Files { - fmt.Println("Generating " + element.Name + " file...") - resFile, errGetFile := http.Get("https://raw.githubusercontent.com/conductor-sdk/boilerplates/main/"+lang+"/"+tpe+"/"+bpl+"/"+element.Name) - if errGetFile != nil { - fmt.Println(errGetFile) - return errGetFile + + fmt.Println("Available templates:") + fmt.Println() + + for _, lang := range list.Languages { + fmt.Printf("%s:\n", lang.Name) + for _, fw := range lang.Frameworks { + fmt.Printf(" %s:\n", fw.Name) + for _, tmpl := range fw.Templates { + fmt.Printf(" • %s - %s\n", tmpl.Name, tmpl.Description) + } + } + fmt.Println() + } + + return nil +} + +func interactiveCodeGenerate(cmd *cobra.Command, args []string) error { + lang, _ := cmd.Flags().GetString("lang") + framework, _ := cmd.Flags().GetString("framework") + template, _ := cmd.Flags().GetString("template") + name, _ := cmd.Flags().GetString("name") + + // If flags provided, go direct + if lang != "" && template != "" && name != "" { + // Default framework to "core" if not provided + if framework == "" { + framework = "core" + } + + // Find the template path + list, err := fetchTemplateList() + if err != nil { + return err + } + + // Find matching language + var selectedLang *Language + for _, l := range list.Languages { + if strings.EqualFold(l.Name, lang) { + selectedLang = &l + break + } + } + if selectedLang == nil { + return fmt.Errorf("language '%s' not found", lang) } - bodyFile, errbodyFile := ioutil.ReadAll(resFile.Body) - if errbodyFile != nil { - fmt.Println(errbodyFile) - return errbodyFile + // Find matching framework + var selectedFw *Framework + for _, fw := range selectedLang.Frameworks { + if strings.EqualFold(fw.Name, framework) { + selectedFw = &fw + break + } } + if selectedFw == nil { + return fmt.Errorf("framework '%s' not found for language '%s'", framework, lang) + } + + // Find matching template + var selectedTemplate *Template + for _, tmpl := range selectedFw.Templates { + if strings.EqualFold(tmpl.Name, template) { + selectedTemplate = &tmpl + break + } + } + if selectedTemplate == nil { + return fmt.Errorf("template '%s' not found for framework '%s/%s'", template, lang, framework) + } + + return generateFromTemplate(selectedTemplate.Path, selectedTemplate.Name, name) + } + + // Interactive mode + list, err := fetchTemplateList() + if err != nil { + return err + } + + reader := bufio.NewReader(os.Stdin) + + fmt.Println("Select a language:") + for i, l := range list.Languages { + fmt.Printf("%d. %s\n", i+1, l.Name) + } + fmt.Print("\nChoice: ") + choice, _ := reader.ReadString('\n') + langIdx, err := strconv.Atoi(strings.TrimSpace(choice)) + if err != nil || langIdx < 1 || langIdx > len(list.Languages) { + return fmt.Errorf("invalid choice") + } + selectedLang := list.Languages[langIdx-1] + + fmt.Printf("\nSelect a framework for %s:\n", selectedLang.Name) + for i, fw := range selectedLang.Frameworks { + fmt.Printf("%d. %s\n", i+1, fw.Name) + } + fmt.Print("\nChoice: ") + choice, _ = reader.ReadString('\n') + fwIdx, err := strconv.Atoi(strings.TrimSpace(choice)) + if err != nil || fwIdx < 1 || fwIdx > len(selectedLang.Frameworks) { + return fmt.Errorf("invalid choice") + } + selectedFw := selectedLang.Frameworks[fwIdx-1] + + fmt.Printf("\nSelect a template for %s/%s:\n", selectedLang.Name, selectedFw.Name) + for i, tmpl := range selectedFw.Templates { + fmt.Printf("%d. %s - %s\n", i+1, tmpl.Name, tmpl.Description) + } + fmt.Print("\nChoice: ") + choice, _ = reader.ReadString('\n') + tmplIdx, err := strconv.Atoi(strings.TrimSpace(choice)) + if err != nil || tmplIdx < 1 || tmplIdx > len(selectedFw.Templates) { + return fmt.Errorf("invalid choice") + } + selectedTemplate := selectedFw.Templates[tmplIdx-1] + + fmt.Print("\nProject name: ") + projectName, _ := reader.ReadString('\n') + projectName = strings.TrimSpace(projectName) + if projectName == "" { + return fmt.Errorf("project name is required") + } + + fmt.Println() + return generateFromTemplate(selectedTemplate.Path, selectedTemplate.Name, projectName) +} + +func generateFromTemplate(templatePath, templateName, projectName string) error { + var boilerplate BoilerPlate + baseURL := getTemplateBaseURL() + + fmt.Printf("Creating project directory '%s'...\n", projectName) + err := os.Mkdir(projectName, 0755) + if err != nil { + return fmt.Errorf("failed to create directory: %w", err) + } + + fmt.Println("Loading template configuration...") + bpURL := fmt.Sprintf("%s/%s/bp.json", baseURL, templatePath) + response, err := http.Get(bpURL) + if err != nil { + os.RemoveAll(projectName) + return fmt.Errorf("failed to fetch template: %w", err) + } + defer response.Body.Close() + + if response.StatusCode == 404 { + os.RemoveAll(projectName) + return fmt.Errorf("template not found at: %s", templatePath) + } + + body, err := io.ReadAll(response.Body) + if err != nil { + os.RemoveAll(projectName) + return fmt.Errorf("failed to read template: %w", err) + } + + if err := json.Unmarshal(body, &boilerplate); err != nil { + os.RemoveAll(projectName) + return fmt.Errorf("failed to parse template: %w", err) + } + + fmt.Println("✓ Template loaded") + fmt.Println() + + reader := bufio.NewReader(os.Stdin) + for _, file := range boilerplate.Files { + fmt.Printf("Generating %s...\n", file.Name) + + fileURL := fmt.Sprintf("%s/%s/%s", baseURL, templatePath, file.Name) + resFile, err := http.Get(fileURL) + if err != nil { + os.RemoveAll(projectName) + return fmt.Errorf("failed to fetch file %s: %w", file.Name, err) + } + + bodyFile, err := io.ReadAll(resFile.Body) resFile.Body.Close() + if err != nil { + os.RemoveAll(projectName) + return fmt.Errorf("failed to read file %s: %w", file.Name, err) + } + + fileContent := string(bodyFile) - var fileContent = string(bodyFile) - - for _, elementField := range element.Fields { - - var attrValue string - - fmt.Println(elementField.Prompt) - fmt.Scanln(&attrValue) - - fileContent = strings.Replace(fileContent, "_"+elementField.Name+"_", attrValue, -1) + // Prompt for field values + for _, field := range file.Fields { + fmt.Printf(" %s ", field.Prompt) + attrValue, _ := reader.ReadString('\n') + attrValue = strings.TrimSpace(attrValue) + if attrValue == "" { + attrValue = projectName // Default to project name + } + fileContent = strings.ReplaceAll(fileContent, "_"+field.Name+"_", attrValue) } - errfo := ioutil.WriteFile(name+"/"+element.Name, []byte(fileContent), 0644) - if errfo != nil { - fmt.Println(errfo) - return errfo + + // Write file + if err := os.WriteFile(projectName+"/"+file.Name, []byte(fileContent), 0644); err != nil { + os.RemoveAll(projectName) + return fmt.Errorf("failed to write file %s: %w", file.Name, err) } - fmt.Println("File generated successfully!") - } - - fmt.Println("Project generated successfully!") + } + + fmt.Println() + fmt.Printf("✓ Project '%s' generated successfully!\n", projectName) + fmt.Println() + fmt.Println("Next steps:") + fmt.Printf(" cd %s\n", projectName) return nil } func init() { - generateCodeCmd.Flags().StringP("type", "t", "", "Type of project") - generateCodeCmd.Flags().StringP("name", "n", "", "Name of the project") - generateCodeCmd.Flags().StringP("lang", "l", "", "Programming Language") - generateCodeCmd.Flags().StringP("bpl", "b", "core", "Name of the boilerplate") - - codeCmd.AddCommand( - generateCodeCmd, - ) - - rootCmd.AddCommand( - codeCmd, - ) -} \ No newline at end of file + codeCmd.Flags().StringP("lang", "l", "", "Programming language") + codeCmd.Flags().StringP("framework", "f", "", "Framework (defaults to 'core' if not provided)") + codeCmd.Flags().StringP("template", "t", "", "Template name") + codeCmd.Flags().StringP("name", "n", "", "Project name") + + codeCmd.AddCommand(codeListCmd) + rootCmd.AddCommand(codeCmd) +}