Skip to content

Commit 1c8c51e

Browse files
committed
fix: cleanup command modules and use parsers
1 parent ec95290 commit 1c8c51e

File tree

5 files changed

+523
-346
lines changed

5 files changed

+523
-346
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ deps: ## Download and verify dependencies
4848
$(GOMOD) download
4949
$(GOMOD) verify
5050

51-
fmt: ## Format code
51+
format: ## Format code
5252
@echo "Formatting code..."
5353
$(GOFMT) ./...
54+
golangci-lint run --fix
5455

5556
lint: ## Run linter
5657
@echo "Running linter..."

internal/command/schema.go

Lines changed: 27 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,20 @@
22
package command
33

44
import (
5-
"bufio"
65
"encoding/json"
76
"fmt"
8-
"os"
9-
"path/filepath"
10-
"strings"
117

128
"github.com/spf13/cobra"
13-
"gopkg.in/yaml.v3"
149

1510
"github.com/n1rna/ee-cli/internal/entities"
1611
"github.com/n1rna/ee-cli/internal/output"
1712
"github.com/n1rna/ee-cli/internal/parser"
1813
)
1914

20-
type SchemaCommand struct {
21-
reader *bufio.Reader
22-
}
15+
type SchemaCommand struct{}
2316

2417
func NewSchemaCommand(groupId string) *cobra.Command {
25-
sc := &SchemaCommand{
26-
reader: bufio.NewReader(os.Stdin),
27-
}
18+
sc := &SchemaCommand{}
2819

2920
cmd := &cobra.Command{
3021
Use: "schema",
@@ -152,295 +143,52 @@ func (c *SchemaCommand) runCreate(cmd *cobra.Command, args []string) error {
152143
printer := output.NewPrinter(output.Format(format), quiet)
153144

154145
schemaName := args[0]
146+
schemaParser := parser.NewSchemaParser()
147+
148+
var schemaData *parser.SchemaData
149+
var err error
155150

156151
// Check if we should import from file
157152
if importFile, _ := cmd.Flags().GetString("import"); importFile != "" {
158-
return c.importSchema(manager, printer, schemaName, importFile)
159-
}
160-
161-
// Check if we should create via CLI flags
162-
if variables, _ := cmd.Flags().GetStringSlice("variable"); len(variables) > 0 {
163-
description, _ := cmd.Flags().GetString("description")
164-
return c.createSchemaFromCLI(manager, printer, schemaName, description, variables)
165-
}
166-
167-
return c.createSchemaInteractively(manager, printer, schemaName)
168-
}
169-
170-
func (c *SchemaCommand) createSchemaInteractively(
171-
manager *entities.Manager,
172-
printer *output.Printer,
173-
name string,
174-
) error {
175-
printer.Info("Creating new schema...")
176-
printer.Info("For each variable, you'll need to specify:")
177-
printer.Info("- Name (e.g., DATABASE_URL)")
178-
printer.Info("- Type (string/number/boolean/url)")
179-
printer.Info("- Regex pattern (optional)")
180-
printer.Info("- Default value (optional)")
181-
printer.Info("- Required flag (y/n)")
182-
183-
var variables []entities.Variable
184-
185-
for {
186-
var variable entities.Variable
187-
188-
fmt.Print("Enter variable name (or empty to finish): ")
189-
name, err := c.reader.ReadString('\n')
190-
if err != nil {
191-
return fmt.Errorf("failed to read variable name: %w", err)
192-
}
193-
194-
name = strings.TrimSpace(name)
195-
if name == "" {
196-
break
197-
}
198-
199-
// Check for duplicate variable names
200-
for _, v := range variables {
201-
if v.Name == name {
202-
printer.Warning(fmt.Sprintf("Variable %s already exists in schema", name))
203-
continue
204-
}
205-
}
206-
207-
variable.Name = name
208-
209-
fmt.Print("Enter variable type (string/number/boolean/url): ")
210-
varType, err := c.reader.ReadString('\n')
153+
schemaData, err = schemaParser.ParseFile(importFile)
211154
if err != nil {
212-
return fmt.Errorf("failed to read variable type: %w", err)
213-
}
214-
215-
varType = strings.TrimSpace(strings.ToLower(varType))
216-
switch varType {
217-
case "string", "number", "boolean", "url":
218-
variable.Type = varType
219-
default:
220-
printer.Warning(fmt.Sprintf("Invalid type %s, defaulting to string", varType))
221-
variable.Type = "string"
155+
return fmt.Errorf("failed to parse schema from file: %w", err)
222156
}
223-
224-
fmt.Print("Enter regex pattern (optional): ")
225-
regex, err := c.reader.ReadString('\n')
226-
if err != nil {
227-
return fmt.Errorf("failed to read regex pattern: %w", err)
228-
}
229-
230-
regex = strings.TrimSpace(regex)
231-
if regex != "" {
232-
variable.Regex = regex
233-
}
234-
235-
fmt.Print("Enter default value (optional): ")
236-
defaultVal, err := c.reader.ReadString('\n')
237-
if err != nil {
238-
return fmt.Errorf("failed to read default value: %w", err)
239-
}
240-
241-
defaultVal = strings.TrimSpace(defaultVal)
242-
if defaultVal != "" {
243-
variable.Default = defaultVal
244-
}
245-
246-
fmt.Print("Is this variable required? (y/N): ")
247-
required, err := c.reader.ReadString('\n')
248-
if err != nil {
249-
return fmt.Errorf("failed to read required flag: %w", err)
250-
}
251-
252-
required = strings.TrimSpace(strings.ToLower(required))
253-
variable.Required = required == "y" || required == "yes"
254-
255-
variables = append(variables, variable)
256-
}
257-
258-
if len(variables) == 0 {
259-
return fmt.Errorf("schema must contain at least one variable")
260-
}
261-
262-
// Create schema using the manager
263-
s, err := manager.Schemas.Create(name, "Schema created interactively", variables, nil)
264-
if err != nil {
265-
return fmt.Errorf("failed to create schema: %w", err)
266-
}
267-
268-
printer.Success(
269-
fmt.Sprintf("Successfully created schema '%s' with %d variables", name, len(variables)),
270-
)
271-
return printer.PrintSchema(s)
272-
}
273-
274-
// createSchemaFromCLI creates a schema from CLI flags
275-
func (c *SchemaCommand) createSchemaFromCLI(
276-
manager *entities.Manager,
277-
printer *output.Printer,
278-
name, description string,
279-
variableSpecs []string,
280-
) error {
281-
printer.Info(fmt.Sprintf("Creating schema '%s' from CLI specifications...", name))
282-
283-
variables := []entities.Variable{}
284-
285-
// Parse each variable specification
286-
for _, varSpec := range variableSpecs {
287-
variable, err := c.parseVariableSpec(varSpec)
157+
} else if variables, _ := cmd.Flags().GetStringSlice("variable"); len(variables) > 0 {
158+
// Create via CLI flags
159+
description, _ := cmd.Flags().GetString("description")
160+
schemaData, err = schemaParser.ParseCLISpecs(description, variables)
288161
if err != nil {
289-
return fmt.Errorf("invalid variable specification '%s': %w", varSpec, err)
290-
}
291-
292-
// Check for duplicate variable names
293-
for _, existingVar := range variables {
294-
if existingVar.Name == variable.Name {
295-
return fmt.Errorf("duplicate variable name '%s'", variable.Name)
296-
}
162+
return err
297163
}
298-
299-
variables = append(variables, variable)
300-
printer.Info(fmt.Sprintf("Added variable: %s (%s)", variable.Name, variable.Type))
301-
}
302-
303-
if len(variables) == 0 {
304-
return fmt.Errorf("schema must contain at least one variable")
305-
}
306-
307-
// Create schema using the manager
308-
s, err := manager.Schemas.Create(name, description, variables, nil)
309-
if err != nil {
310-
return fmt.Errorf("failed to create schema: %w", err)
311-
}
312-
313-
printer.Success(
314-
fmt.Sprintf("Successfully created schema '%s' with %d variables", name, len(variables)),
315-
)
316-
return printer.PrintSchema(s)
317-
}
318-
319-
// parseVariableSpec parses a variable specification in the format: name:type:title:required[:default]
320-
func (c *SchemaCommand) parseVariableSpec(spec string) (entities.Variable, error) {
321-
// Split into at most 5 parts to handle cases where default values contain colons
322-
parts := strings.SplitN(spec, ":", 5)
323-
if len(parts) < 4 {
324-
return entities.Variable{}, fmt.Errorf(
325-
"format should be 'name:type:title:required[:default]', got %d parts",
326-
len(parts),
327-
)
328-
}
329-
330-
name := strings.TrimSpace(parts[0])
331-
varType := strings.TrimSpace(strings.ToLower(parts[1]))
332-
title := strings.TrimSpace(parts[2])
333-
requiredStr := strings.TrimSpace(strings.ToLower(parts[3]))
334-
335-
// Validate name
336-
if name == "" {
337-
return entities.Variable{}, fmt.Errorf("variable name cannot be empty")
338-
}
339-
340-
// Validate type
341-
validTypes := map[string]bool{"string": true, "number": true, "boolean": true, "url": true}
342-
if !validTypes[varType] {
343-
return entities.Variable{}, fmt.Errorf(
344-
"invalid type '%s', must be one of: string, number, boolean, url",
345-
varType,
346-
)
347-
}
348-
349-
// Parse required flag
350-
var required bool
351-
switch requiredStr {
352-
case "true", "t", "1", "yes", "y":
353-
required = true
354-
case "false", "f", "0", "no", "n":
355-
required = false
356-
default:
357-
return entities.Variable{}, fmt.Errorf(
358-
"invalid required value '%s', must be true/false",
359-
requiredStr,
360-
)
361-
}
362-
363-
// Parse default value (optional)
364-
var defaultValue string
365-
if len(parts) == 5 {
366-
defaultValue = strings.TrimSpace(parts[4])
367-
}
368-
369-
return entities.Variable{
370-
Name: name,
371-
Type: varType,
372-
Title: title,
373-
Required: required,
374-
Default: defaultValue,
375-
}, nil
376-
}
377-
378-
func (c *SchemaCommand) importSchema(
379-
manager *entities.Manager,
380-
printer *output.Printer,
381-
name string,
382-
filename string,
383-
) error {
384-
// Detect file format based on extension
385-
ext := strings.ToLower(filepath.Ext(filename))
386-
387-
var schemaObj entities.Schema
388-
var variables []entities.Variable
389-
var description string
390-
var extends []string
391-
392-
// If it's a .env file, use the dotenv parser to extract schema
393-
if ext == ".env" || strings.Contains(strings.ToLower(filename), ".env") {
394-
p := parser.NewAnnotatedDotEnvParser()
395-
_, schema, err := p.ParseFile(filename)
396-
if err != nil {
397-
return fmt.Errorf("failed to parse .env file: %w", err)
164+
for _, v := range schemaData.Variables {
165+
printer.Info(fmt.Sprintf("Added variable: %s (%s)", v.Name, v.Type))
398166
}
399-
variables = schema.Variables
400-
description = schema.Description
401167
} else {
402-
// For other files, read and try YAML/JSON
403-
data, err := os.ReadFile(filename)
168+
// Interactive mode
169+
schemaData, err = schemaParser.ParseInteractive()
404170
if err != nil {
405-
return fmt.Errorf("failed to read import file: %w", err)
406-
}
407-
408-
// Try YAML first, then JSON, then dotenv as fallback
409-
if err := yaml.Unmarshal(data, &schemaObj); err != nil {
410-
if err := json.Unmarshal(data, &schemaObj); err != nil {
411-
// Try parsing as dotenv file as fallback
412-
p := parser.NewAnnotatedDotEnvParser()
413-
_, schema, parseErr := p.ParseFile(filename)
414-
if parseErr != nil {
415-
return fmt.Errorf("file is neither valid YAML, JSON, nor dotenv format: %w", parseErr)
416-
}
417-
variables = schema.Variables
418-
description = schema.Description
419-
} else {
420-
variables = schemaObj.Variables
421-
description = schemaObj.Description
422-
extends = schemaObj.Extends
423-
}
424-
} else {
425-
variables = schemaObj.Variables
426-
description = schemaObj.Description
427-
extends = schemaObj.Extends
171+
return err
428172
}
429173
}
430174

431175
// Create schema using the manager
432176
s, err := manager.Schemas.Create(
433-
name,
434-
description,
435-
variables,
436-
extends,
177+
schemaName,
178+
schemaData.Description,
179+
schemaData.Variables,
180+
schemaData.Extends,
437181
)
438182
if err != nil {
439183
return fmt.Errorf("failed to create schema: %w", err)
440184
}
441185

442186
printer.Success(
443-
fmt.Sprintf("Successfully imported schema '%s' with %d variables", name, len(s.Variables)),
187+
fmt.Sprintf(
188+
"Successfully created schema '%s' with %d variables",
189+
schemaName,
190+
len(s.Variables),
191+
),
444192
)
445193
return printer.PrintSchema(s)
446194
}

0 commit comments

Comments
 (0)