// Package docs — генерация ops-документации. package docs import ( "encoding/json" "fmt" "os" "strings" "ops-generator/internal/types" ) // Entry — запись в индексе документации. type Entry struct { ServiceID int Name string File string Title string OpCount int } // ServiceDoc генерирует Markdown-документацию операций сервиса. func ServiceDoc(path string, spec types.ServiceOpsSpec) error { var b strings.Builder b.WriteString(fmt.Sprintf("# Operations for nubes_%s\n\n", spec.Name)) b.WriteString(fmt.Sprintf("Service ID: `%d`\n\n", spec.ServiceID)) if t := Title(spec); t != "" { b.WriteString(fmt.Sprintf("Service: %s\n\n", t)) } if strings.TrimSpace(spec.ServiceMan) != "" { b.WriteString("## UI description\n\n") b.WriteString(spec.ServiceMan) b.WriteString("\n\n") } b.WriteString("## Operations\n\n") b.WriteString("Non-CRUD operations are typically executed via an action resource.\n") b.WriteString("Domain operations (for example create_user, create_database) are best modeled as separate resources.\n\n") if len(spec.Operations) == 0 { b.WriteString("No operations found.\n") return os.WriteFile(path, []byte(b.String()), 0o644) } for _, op := range spec.Operations { b.WriteString(fmt.Sprintf("- `%s` (id: %d)\n", op.Name, op.ID)) } b.WriteString("\n") for _, op := range spec.Operations { b.WriteString(fmt.Sprintf("## Operation: %s\n\n", op.Name)) b.WriteString(fmt.Sprintf("Operation ID: `%d`\n\n", op.ID)) if strings.TrimSpace(op.Man) != "" { b.WriteString(op.Man) b.WriteString("\n\n") } if len(op.Params) == 0 { b.WriteString("No parameters.\n\n") continue } b.WriteString("| Code | Type | Required | Default | ID | RefSvcId | ValueList | Func | Regex | Min | Max | Sensitive | DependsOn |\n") b.WriteString("|---|---|---|---|---|---|---|---|---|---|---|---|---|\n") for _, p := range op.Params { b.WriteString(fmt.Sprintf("| `%s` | `%s` | `%t` | `%s` | `%d` | `%s` | `%s` | `%s` | `%s` | `%s` | `%s` | `%t` | `%s` |\n", Esc(p.Code), Esc(p.DataType), p.Required, Esc(Value(p.Default)), p.ID, Esc(Ref(p.RefSvcId)), Esc(List(p.ValueList)), Esc(p.Func), Esc(p.Regex), Esc(Value(p.MinValue)), Esc(Value(p.MaxValue)), p.IsSensitive, Esc(Value(p.DependsOn)), )) } b.WriteString("\n") } return os.WriteFile(path, []byte(b.String()), 0o644) } // Index генерирует index.md со списком всех операций. func Index(path string, entries []Entry) error { var b strings.Builder b.WriteString("# Operations by service\n\n") b.WriteString("Auto-generated list of available operations per service.\n\n") b.WriteString("Note: non-CRUD operations should be invoked via an action resource,\n") b.WriteString("and domain operations are best represented as dedicated resources.\n\n") for _, e := range entries { b.WriteString(fmt.Sprintf("- %d - nubes_%s (%d ops): [%s](%s)\n", e.ServiceID, e.Name, e.OpCount, e.Title, e.File)) } return os.WriteFile(path, []byte(b.String()), 0o644) } // Name нормализует имя файла. func Name(name string) string { name = strings.ToLower(strings.TrimSpace(name)) name = strings.ReplaceAll(name, " ", "_") name = strings.ReplaceAll(name, "/", "_") name = strings.ReplaceAll(name, "\\", "_") name = strings.ReplaceAll(name, ":", "_") name = strings.ReplaceAll(name, "-", "_") return name } // Title возвращает отображаемое имя сервиса. func Title(spec types.ServiceOpsSpec) string { if strings.TrimSpace(spec.ServiceDisplayName) != "" { return spec.ServiceDisplayName } if strings.TrimSpace(spec.ServiceShortName) != "" { return spec.ServiceShortName } return "" } // List форматирует список значений. func List(items []string) string { if len(items) == 0 { return "" } return strings.Join(items, ",") } // Ref форматирует RefSvcId. func Ref(value *int) string { if value == nil { return "" } return fmt.Sprintf("%d", *value) } // Value форматирует значение параметра. func Value(value interface{}) string { if value == nil { return "" } switch t := value.(type) { case string: return t default: b, err := json.Marshal(t) if err != nil { return fmt.Sprintf("%v", value) } return string(b) } } // Esc экранирует pipe-символы для Markdown-таблиц. func Esc(value string) string { return strings.ReplaceAll(value, "|", "\\|") }