Skip to content

Commit 5ebc706

Browse files
committed
chore: add commit message placeholder
1 parent ffffce6 commit 5ebc706

13 files changed

Lines changed: 2848 additions & 3 deletions

File tree

cmd/generate.go

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
8+
"github.com/charmbracelet/huh"
9+
"github.com/charmbracelet/lipgloss"
10+
"github.com/matpdev/cpp-gen/internal/generator/filegen"
11+
"github.com/matpdev/cpp-gen/internal/localconfig"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
// ─────────────────────────────────────────────────────────────────────────────
16+
// Flags
17+
// ─────────────────────────────────────────────────────────────────────────────
18+
19+
var (
20+
genFlagType string
21+
genFlagOutput string
22+
genFlagNoTest bool
23+
genFlagNoInteractive bool
24+
genFlagBrief string
25+
genFlagLayer string
26+
genFlagList bool
27+
)
28+
29+
// ─────────────────────────────────────────────────────────────────────────────
30+
// Command
31+
// ─────────────────────────────────────────────────────────────────────────────
32+
33+
var generateCmd = &cobra.Command{
34+
Use: "generate [type|schematic] [name]",
35+
Aliases: []string{"g"},
36+
Short: "Gera um arquivo C++ baseado na configuração do projeto (.cppgenrc.json)",
37+
Long: `Gera arquivos C++ (.hpp / .cpp) no projeto atual com base nas configurações
38+
definidas em .cppgenrc.json.
39+
40+
Tipos primitivos:
41+
class — par .hpp/.cpp com classe C++
42+
struct — header-only com struct C++
43+
free — módulo de funções livres (.hpp + .cpp)
44+
interface — classe abstrata pura (header-only)
45+
46+
Schematics (definidos pela arquitetura do projeto):
47+
service — serviço de domínio com interface
48+
repository — interface + implementação de acesso a dados
49+
command — padrão Command
50+
(use --list para ver todos os schematics disponíveis)
51+
52+
O comando lê .cppgenrc.json a partir do diretório atual (percorrendo a árvore
53+
de diretórios para cima até encontrar o arquivo). Se não encontrar, execute
54+
'cpp-gen init' primeiro para inicializar o projeto.`,
55+
Args: cobra.MaximumNArgs(2),
56+
RunE: runGenerate,
57+
Example: ` cpp-gen generate class Foo
58+
cpp-gen generate service UserService --layer application
59+
cpp-gen generate repository UserRepo
60+
cpp-gen g class Renderer --no-test
61+
cpp-gen g class Foo --output src/graphics
62+
cpp-gen generate --list`,
63+
}
64+
65+
func init() {
66+
generateCmd.Flags().StringVarP(&genFlagType, "type", "t", "",
67+
"Tipo de arquivo a gerar: class | struct | free | interface")
68+
generateCmd.Flags().StringVarP(&genFlagOutput, "output", "o", "",
69+
"Diretório de saída (sobrescreve os paths do .cppgenrc.json)")
70+
generateCmd.Flags().BoolVar(&genFlagNoTest, "no-test", false,
71+
"Não gera arquivo de teste")
72+
generateCmd.Flags().BoolVarP(&genFlagNoInteractive, "no-interactive", "n", false,
73+
"Modo não-interativo (falha se faltar nome ou tipo)")
74+
generateCmd.Flags().StringVarP(&genFlagBrief, "brief", "b", "",
75+
"Descrição curta para o campo @brief do cabeçalho")
76+
generateCmd.Flags().StringVarP(&genFlagLayer, "layer", "l", "",
77+
"Camada arquitetural de destino (ex: application, infrastructure)")
78+
generateCmd.Flags().BoolVar(&genFlagList, "list", false,
79+
"Lista todos os schematics disponíveis no projeto")
80+
}
81+
82+
// ─────────────────────────────────────────────────────────────────────────────
83+
// Runner
84+
// ─────────────────────────────────────────────────────────────────────────────
85+
86+
func runGenerate(cmd *cobra.Command, args []string) error {
87+
// ── Styles ────────────────────────────────────────────────────────────────
88+
green := lipgloss.NewStyle().Foreground(lipgloss.Color("2"))
89+
yellow := lipgloss.NewStyle().Foreground(lipgloss.Color("3"))
90+
91+
// ── 1. Detect working directory ───────────────────────────────────────────
92+
dir, err := os.Getwd()
93+
if err != nil {
94+
return fmt.Errorf("não foi possível determinar o diretório atual: %w", err)
95+
}
96+
97+
// ── 2. Load config (local → global → default) ───────────────────────────
98+
cfg, src, err := localconfig.Resolve(dir)
99+
if err != nil {
100+
return fmt.Errorf("erro ao carregar configuração: %w", err)
101+
}
102+
103+
// Inform the user which config source is active (skip for default to avoid
104+
// noise when the user just wants a quick generation).
105+
muted := lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
106+
switch src {
107+
case localconfig.SourceGlobal:
108+
fmt.Fprintln(os.Stderr, muted.Render("ℹ usando configuração global (~/.config/cpp-gen/config.json)"))
109+
case localconfig.SourceDefault:
110+
fmt.Fprintln(os.Stderr, muted.Render("ℹ nenhuma configuração encontrada; usando padrões embutidos"))
111+
}
112+
113+
// ── --list: mostrar schematics disponíveis e sair ─────────────────────────
114+
if genFlagList {
115+
return runListSchematics(cfg)
116+
}
117+
118+
// ── 3. Resolve fileType/schematicName and fileName ─────────────────────────
119+
primitiveTypes := map[string]bool{
120+
"class": true, "struct": true, "free": true, "interface": true,
121+
}
122+
123+
schematicName := ""
124+
fileType := ""
125+
fileName := ""
126+
127+
// args[0] = type or schematic name; args[1] = entity name
128+
if len(args) >= 1 {
129+
arg0 := strings.ToLower(args[0])
130+
if primitiveTypes[arg0] {
131+
fileType = arg0
132+
} else {
133+
schematicName = arg0
134+
}
135+
}
136+
if len(args) >= 2 {
137+
fileName = args[1]
138+
}
139+
140+
// --type flag forces primitive mode
141+
if genFlagType != "" {
142+
t := strings.ToLower(genFlagType)
143+
if primitiveTypes[t] {
144+
fileType = t
145+
schematicName = ""
146+
}
147+
}
148+
149+
// If neither was resolved, fall back to config default type
150+
if fileType == "" && schematicName == "" {
151+
fileType = cfg.Generate.DefaultType
152+
}
153+
154+
// ── 4. Prompt for name if missing ─────────────────────────────────────────
155+
if fileName == "" && !genFlagNoInteractive {
156+
if err := huh.NewInput().
157+
Title("Nome do arquivo").
158+
Description("Nome da classe, struct ou módulo a gerar (ex: FooBar, utils/math)").
159+
Value(&fileName).
160+
Run(); err != nil {
161+
return fmt.Errorf("entrada cancelada: %w", err)
162+
}
163+
fileName = strings.TrimSpace(fileName)
164+
}
165+
166+
// ── 5. Prompt for type/schematic if still missing ─────────────────────────
167+
if fileType == "" && schematicName == "" && !genFlagNoInteractive {
168+
if err := huh.NewSelect[string]().
169+
Title("Tipo de arquivo").
170+
Description("Selecione o tipo de construct C++ a gerar").
171+
Options(
172+
huh.NewOption("class — par .hpp/.cpp com classe C++", "class"),
173+
huh.NewOption("struct — header-only com struct C++", "struct"),
174+
huh.NewOption("free — módulo de funções livres (.hpp + .cpp)", "free"),
175+
huh.NewOption("interface — classe abstrata pura (header-only)", "interface"),
176+
).
177+
Value(&fileType).
178+
Run(); err != nil {
179+
return fmt.Errorf("seleção cancelada: %w", err)
180+
}
181+
}
182+
183+
// ── 6. Validate that name is present ─────────────────────────────────────
184+
if fileName == "" {
185+
return fmt.Errorf("nome do arquivo não fornecido; use 'cpp-gen generate [type] [name]' ou omita --no-interactive")
186+
}
187+
188+
// ── Gerar via schematic ou tipo primitivo ─────────────────────────────────
189+
var generated []filegen.GeneratedFile
190+
191+
if schematicName != "" {
192+
req := filegen.SchematicRequest{
193+
Name: fileName,
194+
SchematicName: schematicName,
195+
Layer: genFlagLayer,
196+
Brief: genFlagBrief,
197+
NoTest: genFlagNoTest,
198+
}
199+
generated, err = filegen.GenerateSchematic(req, cfg)
200+
} else {
201+
req := filegen.FileRequest{
202+
Name: fileName,
203+
Type: filegen.FileType(fileType),
204+
Brief: genFlagBrief,
205+
OutputDir: genFlagOutput,
206+
NoTest: genFlagNoTest,
207+
}
208+
generated, err = filegen.Generate(req, cfg)
209+
}
210+
if err != nil {
211+
return fmt.Errorf("erro durante a geração: %w", err)
212+
}
213+
214+
// ── Report results ────────────────────────────────────────────────────────
215+
for _, gf := range generated {
216+
if gf.Skipped {
217+
fmt.Println(yellow.Render("⚠ já existe: " + gf.Path))
218+
} else {
219+
fmt.Println(green.Render("✓ criado: " + gf.Path))
220+
}
221+
}
222+
223+
return nil
224+
}
225+
226+
// runListSchematics prints all schematics available in the current project.
227+
func runListSchematics(cfg *localconfig.LocalConfig) error {
228+
entries := filegen.ListSchematics(cfg)
229+
230+
titleStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#7C3AED"))
231+
builtinStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
232+
customStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#06B6D4"))
233+
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("15")).Width(16)
234+
descStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("7"))
235+
countStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
236+
237+
archLabel := string(cfg.Architecture.Style)
238+
placementLabel := string(cfg.Architecture.Placement)
239+
fmt.Printf("\n%s\n\n",
240+
titleStyle.Render(fmt.Sprintf("Schematics disponíveis (%s · %s)", archLabel, placementLabel)),
241+
)
242+
243+
lastWasCustom := false
244+
printedBuiltin := false
245+
for _, e := range entries {
246+
if e.IsCustom && !lastWasCustom {
247+
fmt.Printf(" %s\n", customStyle.Render("custom (.cppgenrc.json)"))
248+
lastWasCustom = true
249+
} else if !e.IsCustom && !printedBuiltin {
250+
fmt.Printf(" %s\n", builtinStyle.Render("built-in"))
251+
printedBuiltin = true
252+
}
253+
fmt.Printf(" %s %s %s\n",
254+
nameStyle.Render(e.Name),
255+
descStyle.Render(e.Description),
256+
countStyle.Render(fmt.Sprintf("(%d arquivo(s))", e.FileCount)),
257+
)
258+
}
259+
fmt.Println()
260+
return nil
261+
}

0 commit comments

Comments
 (0)