- Rename directory - Update all 9 devops scripts - Update 4 generator source files - Rebuild all binaries
160 lines
4.5 KiB
Go
160 lines
4.5 KiB
Go
// 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, "|", "\\|")
|
|
}
|