Docs: introduce product identifier (#21670)

This commit is contained in:
Michael Geers 2025-06-07 13:38:43 +02:00 • committed by GitHub
parent 356841f6c6
commit a04a0cd8c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 100 additions and 12 deletions

2
go.mod
View file

@ -45,6 +45,7 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/gosimple/slug v1.15.0
github.com/gregdel/pushover v1.3.1
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/grid-x/modbus v0.0.0-20250516072809-4b99c910e8e7
@ -151,6 +152,7 @@ require (
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
github.com/google/renameio/v2 v2.0.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect

4
go.sum
View file

@ -308,6 +308,10 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/gregdel/pushover v1.3.1 h1:4bMLITOZ15+Zpi6qqoGqOPuVHCwSUvMCgVnN5Xhilfo=
github.com/gregdel/pushover v1.3.1/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=

View file

@ -3,7 +3,7 @@ covers: ["ocpp-abb"]
products:
- brand: ABB
description:
generic: Terra AC
generic: Terra AC (OCPP)
capabilities: ["mA", "rfid"]
requirements:
evcc: ["sponsorship", "skiptest"]

View file

@ -2,7 +2,7 @@ template: ocpp-alfen
products:
- brand: Alfen
description:
generic: Eve
generic: Eve (OCPP)
capabilities: ["mA", "rfid", "1p3p"]
requirements:
evcc: ["sponsorship", "skiptest"]

View file

@ -3,16 +3,16 @@ covers: ["ocpp-fronius-wattpilot"]
products:
- brand: go-e
description:
generic: Charger V3
generic: Charger V3 (OCPP)
- brand: go-e
description:
generic: Charger Gemini
generic: Charger Gemini (OCPP)
- brand: go-e
description:
generic: Charger PRO
generic: Charger PRO (OCPP)
- brand: Fronius
description:
generic: Wattpilot
generic: Wattpilot (OCPP)
capabilities: ["rfid", "1p3p"]
requirements:
evcc: ["sponsorship", "skiptest"]

View file

@ -2,7 +2,7 @@ template: ocpp-zaptec
products:
- brand: Zaptec
description:
generic: Go
generic: Go (OCPP)
capabilities: ["rfid"]
requirements:
description:

View file

@ -43,6 +43,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er
}
var modbusRender string
modbusData := make(map[string]interface{})
if modbusChoices := t.ModbusChoices(); len(modbusChoices) > 0 {
if i, _ := t.ParamByName(ParamModbus); i > -1 {
modbusTmpl, err := template.New("yaml").Funcs(sprig.FuncMap()).Parse(documentationModbusTmpl)
@ -50,7 +51,6 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er
panic(err)
}
modbusData := make(map[string]interface{})
t.ModbusValues(RenderModeDocs, modbusData)
out := new(bytes.Buffer)
@ -91,6 +91,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er
data := map[string]interface{}{
"Template": t.Template,
"ProductIdentifier": product.Identifier(),
"ProductBrand": product.Brand,
"ProductDescription": product.Description.String(lang),
"ProductGroup": t.GroupTitle(lang),
@ -102,6 +103,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er
"AdvancedParams": hasAdvancedParams,
"Usages": t.Usages(),
"Modbus": modbusRender,
"ModbusData": modbusData,
}
out := new(bytes.Buffer)

View file

@ -4,8 +4,8 @@
- {{ . }}
{{- end }}
{{- $unit := .Unit -}}
{{- $description := localize .Description | replace "\n" " " -}}
{{- $help := localize .Help | replace "\n" " " -}}
{{- $description := localize .Description | replace "\n" " " | trim -}}
{{- $help := localize .Help | replace "\n" " " | trim -}}
{{- $choices := join ", " .Choice -}}
{{- $optional := not .IsRequired -}}
{{- if or $help $choices $optional $description }} # {{end}}
@ -52,7 +52,9 @@
{{- end }}
{{- end -}}
template: {{ .Template }}
product:
identifier: {{ .ProductIdentifier }}
{{- if .ProductBrand }}
brand: {{ .ProductBrand }}
{{- end }}
@ -96,3 +98,25 @@ render:
{{- include "advanced" . | indent 4 }}
{{- end }}
{{- end }}
params:
{{- range .Params }}
{{- if and (not (eq .Name "usage")) (not .IsDeprecated) }}
- name: {{ .Name | quote }}
example: {{ .Example | quote }}
default: {{ .Default }}
choice: [{{ join ", " .Choice }}]
unit: {{ .Unit }}
{{- $description := localize .Description | replace "\n" " " | trim }}
description: {{ $description | quote }}
{{- $help := localize .Help | replace "\n" " " | trim }}
help: {{ $help | quote }}
advanced: {{ .IsAdvanced }}
optional: {{ not .IsRequired }}
{{- end }}
{{- end }}
{{- if .ModbusData }}
modbus:
{{- range $key, $value := .ModbusData }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}

View file

@ -9,16 +9,20 @@ import (
"strings"
"github.com/evcc-io/evcc/util/templates"
"github.com/gosimple/slug"
)
const (
docsPath = "../../../templates/docs"
websitePath = "../../../templates/evcc.io"
iconsPath = "../../../templates/icons"
)
//go:generate go run main.go
func main() {
slug.CustomSub = map[string]string{"+": "plus"}
for _, lang := range []string{"de", "en"} {
if err := generateDocs(lang); err != nil {
panic(err)
@ -28,6 +32,10 @@ func main() {
if err := generateBrandJSON(); err != nil {
panic(err)
}
if err := generateProductJSON(); err != nil {
panic(err)
}
}
func generateDocs(lang string) error {
@ -57,7 +65,7 @@ func generateClass(class templates.Class, lang string) error {
return err
}
for index, product := range tmpl.Products {
for _, product := range tmpl.Products {
fmt.Println(tmpl.Template + ": " + product.Title(lang))
b, err := tmpl.RenderDocumentation(product, lang)
@ -65,7 +73,12 @@ func generateClass(class templates.Class, lang string) error {
return err
}
filename := fmt.Sprintf("%s/%s/%s/%s_%d.yaml", docsPath, lang, strings.ToLower(class.String()), tmpl.Template, index)
filename := fmt.Sprintf("%s/%s/%s/%s.yaml", docsPath, lang, strings.ToLower(class.String()), product.Identifier())
if _, err := os.Stat(filename); err == nil {
return fmt.Errorf("file already exists: %s - product titles must be unique", filename)
}
if err := os.WriteFile(filename, b, 0o644); err != nil {
return err
}
@ -164,3 +177,39 @@ func generateBrandJSON() error {
return err
}
func generateProductJSON() error {
type ProductInfo struct {
Brand string `json:"brand"`
Description string `json:"description"`
}
products := make(map[string]map[string]ProductInfo)
for _, class := range templates.ClassValues() {
classKey := strings.ToLower(class.String())
products[classKey] = make(map[string]ProductInfo)
for _, tmpl := range templates.ByClass(class) {
for _, product := range tmpl.Products {
products[classKey][product.Identifier()] = ProductInfo{
Brand: product.Brand,
Description: product.Description.String("en"),
}
}
}
}
if _, err := os.Stat(iconsPath); os.IsNotExist(err) {
if err := os.MkdirAll(iconsPath, 0o755); err != nil {
return err
}
}
file, err := json.MarshalIndent(products, "", " ")
if err == nil {
err = os.WriteFile(iconsPath+"/products.json", file, 0o644)
}
return err
}

View file

@ -8,6 +8,7 @@ import (
"strings"
"dario.cat/mergo"
"github.com/gosimple/slug"
)
const (
@ -259,10 +260,16 @@ type Product struct {
Description TextLanguage `json:",omitempty"` // product name
}
// Title returns the product title in the given language
func (p Product) Title(lang string) string {
return strings.TrimSpace(fmt.Sprintf("%s %s", p.Brand, p.Description.String(lang)))
}
// Identifier returns a unique language-independent identifier for the product
func (p Product) Identifier() string {
return slug.Make(p.Title("en"))
}
type CountryCode string
func (c CountryCode) IsValid() bool {