|
2 | 2 | package command |
3 | 3 |
|
4 | 4 | import ( |
5 | | - "bufio" |
6 | 5 | "encoding/json" |
7 | 6 | "fmt" |
8 | | - "os" |
9 | | - "path/filepath" |
10 | | - "strings" |
11 | 7 |
|
12 | 8 | "github.com/spf13/cobra" |
13 | | - "gopkg.in/yaml.v3" |
14 | 9 |
|
15 | 10 | "github.com/n1rna/ee-cli/internal/entities" |
16 | 11 | "github.com/n1rna/ee-cli/internal/output" |
17 | 12 | "github.com/n1rna/ee-cli/internal/parser" |
18 | 13 | ) |
19 | 14 |
|
20 | | -type SchemaCommand struct { |
21 | | - reader *bufio.Reader |
22 | | -} |
| 15 | +type SchemaCommand struct{} |
23 | 16 |
|
24 | 17 | func NewSchemaCommand(groupId string) *cobra.Command { |
25 | | - sc := &SchemaCommand{ |
26 | | - reader: bufio.NewReader(os.Stdin), |
27 | | - } |
| 18 | + sc := &SchemaCommand{} |
28 | 19 |
|
29 | 20 | cmd := &cobra.Command{ |
30 | 21 | Use: "schema", |
@@ -152,295 +143,52 @@ func (c *SchemaCommand) runCreate(cmd *cobra.Command, args []string) error { |
152 | 143 | printer := output.NewPrinter(output.Format(format), quiet) |
153 | 144 |
|
154 | 145 | schemaName := args[0] |
| 146 | + schemaParser := parser.NewSchemaParser() |
| 147 | + |
| 148 | + var schemaData *parser.SchemaData |
| 149 | + var err error |
155 | 150 |
|
156 | 151 | // Check if we should import from file |
157 | 152 | 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) |
211 | 154 | 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) |
222 | 156 | } |
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) |
288 | 161 | 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 |
297 | 163 | } |
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)) |
398 | 166 | } |
399 | | - variables = schema.Variables |
400 | | - description = schema.Description |
401 | 167 | } else { |
402 | | - // For other files, read and try YAML/JSON |
403 | | - data, err := os.ReadFile(filename) |
| 168 | + // Interactive mode |
| 169 | + schemaData, err = schemaParser.ParseInteractive() |
404 | 170 | 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 |
428 | 172 | } |
429 | 173 | } |
430 | 174 |
|
431 | 175 | // Create schema using the manager |
432 | 176 | s, err := manager.Schemas.Create( |
433 | | - name, |
434 | | - description, |
435 | | - variables, |
436 | | - extends, |
| 177 | + schemaName, |
| 178 | + schemaData.Description, |
| 179 | + schemaData.Variables, |
| 180 | + schemaData.Extends, |
437 | 181 | ) |
438 | 182 | if err != nil { |
439 | 183 | return fmt.Errorf("failed to create schema: %w", err) |
440 | 184 | } |
441 | 185 |
|
442 | 186 | 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 | + ), |
444 | 192 | ) |
445 | 193 | return printer.PrintSchema(s) |
446 | 194 | } |
|
0 commit comments