feat(P2+P3): docs-generator — убрать ID, value_list читаемый, двойной пример, _nav_fragment.yml, mkdocs breadcrumbs

- renderParamTable/renderModifyTable/renderNestedParams: убрать колонку ID
- collectConstraints: value_list → Допустимые значения
- buildExamplePage: минимальный пример + полный в <details>
- minimalExampleBlock: только required без default
- WriteNavFragment: генерация _nav_fragment.yml с категориями
- mkdocs.yml: navigation.path, navigation.footer, navigation.indexes
This commit is contained in:
“Naeel”
2026-08-09 21:56:40 +04:00
parent c9a881aa84
commit 40a95cc4a6
3 changed files with 129 additions and 26 deletions
+125 -24
View File
@@ -46,6 +46,21 @@ func ResourceDocs(docsDir string, spec types.ServiceSpec, version string, apiEnd
}
}
// CollectSubresources возвращает уникальные subresource-имена.
func CollectSubresources(spec types.ServiceSpec) []string {
seen := map[string]bool{}
var out []string
for _, op := range spec.Operations {
if op.Kind == "subresource" && op.Subresource != "" {
if !seen[op.Subresource] {
seen[op.Subresource] = true
out = append(out, op.Subresource)
}
}
}
return out
}
// WriteFile пишет контент в файл.
func WriteFile(path, content string) {
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
@@ -72,21 +87,6 @@ func IndexMD(docsDir string, specs []types.ServiceSpec) {
}
}
// CollectSubresources возвращает уникальные subresource-имена.
func CollectSubresources(spec types.ServiceSpec) []string {
seen := map[string]bool{}
var out []string
for _, op := range spec.Operations {
if op.Kind == "subresource" && op.Subresource != "" {
if !seen[op.Subresource] {
seen[op.Subresource] = true
out = append(out, op.Subresource)
}
}
}
return out
}
// FindParams находит параметры операции по action.
func FindParams(ops []types.OperationSpec, action string) []types.ParamSpec {
for _, op := range ops {
@@ -187,10 +187,15 @@ func htmlToMarkdown(s string) string {
func buildExamplePage(spec types.ServiceSpec, nav, version string, apiEndpoint string, providerSource string) string {
var b strings.Builder
b.WriteString(buildHeader(spec, nav))
b.WriteString(fmt.Sprintf("## Copy-ready manifest (`%s_main.tf`)\n\n", spec.Name))
b.WriteString(fmt.Sprintf("## Minimal example — only required parameters\n\n"))
b.WriteString("```hcl\n")
b.WriteString(minimalExampleBlock(spec, version, apiEndpoint, providerSource))
b.WriteString("```\n\n")
b.WriteString("<details><summary>Full example (all parameters, including defaults)</summary>\n\n")
b.WriteString("```hcl\n")
b.WriteString(exampleBlock(spec, version, apiEndpoint, providerSource))
b.WriteString("```\n\n")
b.WriteString("</details>\n\n")
b.WriteString("## Outputs usage\n\n")
b.WriteString("```hcl\n")
b.WriteString("# Выходные параметры можно использовать как\n")
@@ -242,6 +247,40 @@ func exampleBlock(spec types.ServiceSpec, version string, apiEndpoint string, pr
return b.String()
}
// minimalExampleBlock — только required параметры без default (минимальный работающий пример).
func minimalExampleBlock(spec types.ServiceSpec, version string, apiEndpoint string, providerSource string) string {
createParams := FindParams(spec.Operations, "create")
var minimalParams []types.ParamSpec
for _, p := range createParams {
if p.Required && !hasDefault(p.Default) {
minimalParams = append(minimalParams, p)
}
}
var b strings.Builder
b.WriteString("terraform {\n")
b.WriteString(" required_providers {\n")
b.WriteString(" nubes = {\n")
b.WriteString(fmt.Sprintf(" source = \"%s\"\n", providerSource))
b.WriteString(fmt.Sprintf(" version = \"%s\"\n", version))
b.WriteString(" }\n")
b.WriteString(" }\n")
b.WriteString("}\n\n")
b.WriteString("provider \"nubes\" {\n")
b.WriteString(" api_token = \"***\"\n")
b.WriteString(fmt.Sprintf(" api_endpoint = \"%s\"\n", apiEndpoint))
b.WriteString("}\n")
b.WriteString(fmt.Sprintf("resource \"nubes_%s\" \"baza\" {\n", spec.Name))
b.WriteString(" resource_name = \"my-resource\"\n\n")
for _, p := range minimalParams {
b.WriteString(formatParamOrBlock(p, " ", true))
}
b.WriteString("}\n")
return b.String()
}
func buildCreateParamsPage(spec types.ServiceSpec, nav string) string {
var b strings.Builder
b.WriteString(buildHeader(spec, nav))
@@ -521,13 +560,12 @@ func renderParamTable(params []types.ParamSpec, requiredTable, noDefault bool) s
}
b.WriteString(fmt.Sprintf("<table class=\"%s\">\n", tableClass))
if noDefault {
b.WriteString("<thead><tr><th>ID</th><th>Code</th><th>Type</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
b.WriteString("<thead><tr><th>Code</th><th>Type</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
} else {
b.WriteString("<thead><tr><th>ID</th><th>Code</th><th>Type</th><th>Default</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
b.WriteString("<thead><tr><th>Code</th><th>Type</th><th>Default</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
}
for _, p := range params {
b.WriteString("<tr>")
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(formatID(p.ID))))
b.WriteString(fmt.Sprintf("<td>%s</td>", formatParamCode(p.Code)))
b.WriteString(fmt.Sprintf("<td>%s</td>", formatTypeCell(p)))
if !noDefault {
@@ -547,10 +585,9 @@ func renderModifyTable(params []types.ParamSpec) string {
}
var b strings.Builder
b.WriteString("<table class=\"resource-table resource-table-compact\">\n")
b.WriteString("<thead><tr><th>ID</th><th>Code</th><th>Type</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
b.WriteString("<thead><tr><th>Code</th><th>Type</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
for _, p := range params {
b.WriteString("<tr>")
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(formatID(p.ID))))
b.WriteString(fmt.Sprintf("<td>%s</td>", formatParamCode(p.Code)))
b.WriteString(fmt.Sprintf("<td>%s</td>", formatTypeCell(p)))
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(pickTextTable(p))))
@@ -806,7 +843,7 @@ func collectConstraints(p types.ParamSpec) string {
parts = append(parts, fmt.Sprintf("regex=%s", p.Regex))
}
if len(p.ValueList) > 0 {
parts = append(parts, fmt.Sprintf("value_list=%s", strings.Join(p.ValueList, ", ")))
parts = append(parts, fmt.Sprintf("Допустимые значения: %s", strings.Join(p.ValueList, ", ")))
}
if p.Func != "" {
parts = append(parts, fmt.Sprintf("func=%s", p.Func))
@@ -992,14 +1029,13 @@ func renderNestedParams(p types.ParamSpec) string {
}
b.WriteString(fmt.Sprintf("\n### %s\n\n", label))
b.WriteString("<table class=\"resource-table resource-table-compact resource-table-nested\">\n")
b.WriteString("<thead><tr><th>ID</th><th>Code</th><th>Type</th><th>Required</th><th>Default</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
b.WriteString("<thead><tr><th>Code</th><th>Type</th><th>Required</th><th>Default</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
for _, sp := range p.SubParams {
req := "no"
if sp.Required {
req = "**yes**"
}
b.WriteString("<tr>")
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(formatID(sp.ID))))
b.WriteString(fmt.Sprintf("<td>%s</td>", formatParamCode(sp.Code)))
b.WriteString(fmt.Sprintf("<td>%s</td>", formatTypeCell(sp)))
b.WriteString(fmt.Sprintf("<td>%s</td>", req))
@@ -1011,3 +1047,68 @@ func renderNestedParams(p types.ParamSpec) string {
b.WriteString("</tbody></table>\n")
return b.String()
}
// serviceCategory возвращает категорию для сервиса.
func serviceCategory(name string) string {
cats := map[string]string{
"postgres": "Базы данных", "redis": "Базы данных", "mongodb": "Базы данных",
"mariadb": "Базы данных", "clickhouse": "Базы данных",
"rabbitmq": "Очереди", "kafka": "Очереди",
"s3": "Хранилище", "s3bucket": "Хранилище", "nextcloud": "Хранилище",
"k8s_velero": "K8s", "k8s_sthutrval_cluster": "K8s", "k8s_openbao": "K8s",
"vc_mgmt_sthutrval_cluster": "K8s",
"vc_org": "VMware", "vc_vdc": "VMware", "vc_nsxt": "VMware",
"vcexternalip": "VMware", "vapp": "VMware", "vc_vm_v2": "VMware",
"vc_vm_v3": "VMware", "vc_vdc_group": "VMware",
"flask": "Приложения", "nodejs": "Приложения", "lucee": "Приложения",
"http": "Приложения", "gitea": "Приложения", "superset": "Приложения",
"pgadmin": "Приложения", "harbor": "Приложения", "akhq": "Приложения",
"llm_ai": "Приложения", "template": "Приложения", "dummy": "Приложения",
"valo_tenant": "Приложения",
"zones_v2": "Сеть", "dnsrecord": "Сеть",
}
if cat, ok := cats[name]; ok {
return cat
}
return "Другие"
}
// WriteNavFragment генерирует _nav_fragment.yml для mkdocs sidebar.
func WriteNavFragment(docsDir string, specs []types.ServiceSpec) {
catMap := map[string][]types.ServiceSpec{}
for _, s := range specs {
cat := serviceCategory(s.Name)
catMap[cat] = append(catMap[cat], s)
}
var b bytes.Buffer
b.WriteString("# auto-generated by docs-generator — DO NOT EDIT\n")
b.WriteString("resources_nav:\n")
order := []string{"Базы данных", "Очереди", "Хранилище", "K8s", "VMware", "Приложения", "Сеть", "Другие"}
for _, cat := range order {
svcs := catMap[cat]
if len(svcs) == 0 {
continue
}
b.WriteString(fmt.Sprintf(" - %s:\n", cat))
for _, s := range svcs {
disp := s.ServiceDisplayName
if disp == "" {
disp = s.Name
}
b.WriteString(fmt.Sprintf(" - %s:\n", disp))
b.WriteString(fmt.Sprintf(" - Обзор: %s.md\n", s.Name))
b.WriteString(fmt.Sprintf(" - Параметры создания: %s_params_create.md\n", s.Name))
b.WriteString(fmt.Sprintf(" - Параметры изменения: %s_params_modify.md\n", s.Name))
b.WriteString(fmt.Sprintf(" - Выходные данные: %s_outputs.md\n", s.Name))
b.WriteString(fmt.Sprintf(" - Операции: %s_ops.md\n", s.Name))
b.WriteString(fmt.Sprintf(" - Пример: %s_example.md\n", s.Name))
}
}
outPath := filepath.Join(docsDir, "_nav_fragment.yml")
if err := os.WriteFile(outPath, b.Bytes(), 0o644); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to write _nav_fragment.yml: %v\n", err)
}
}