Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: generate kcl data from json #145

Merged
merged 2 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions pkg/tools/gen/genkcl.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gen

import (
"encoding/json"
"errors"
"fmt"
"io"
Expand All @@ -24,6 +25,7 @@ const (
ModeGoStruct
ModeJsonSchema
ModeTerraformSchema
ModeJson
)

type kclGenerator struct {
Expand All @@ -46,25 +48,26 @@ func newKclGenerator(opts *GenKclOptions) *kclGenerator {

func (k *kclGenerator) GenSchema(w io.Writer, filename string, src interface{}) error {
if k.opts.Mode == ModeAuto {
code, err := readSource(filename, src)
if err != nil {
return err
}
codeStr := string(code)
var i interface{}
switch {
case strings.HasSuffix(filename, ".go"):
case strings.Contains(codeStr, "package "):
k.opts.Mode = ModeGoStruct
default:
code, err := readSource(filename, src)
if err != nil {
return err
}
codeStr := string(code)
case json.Unmarshal(code, &i) == nil:
switch {
case strings.Contains(codeStr, "package "):
k.opts.Mode = ModeGoStruct
case strings.Contains(codeStr, "$schema"):
k.opts.Mode = ModeJsonSchema
case strings.Contains(codeStr, "\"provider_schemas\""):
k.opts.Mode = ModeTerraformSchema
default:
return errors.New("failed to detect mode")
k.opts.Mode = ModeJson
}
default:
return errors.New("failed to detect mode")
}
}

Expand All @@ -75,6 +78,8 @@ func (k *kclGenerator) GenSchema(w io.Writer, filename string, src interface{})
return k.genSchemaFromJsonSchema(w, filename, src)
case ModeTerraformSchema:
return k.genSchemaFromTerraformSchema(w, filename, src)
case ModeJson:
return k.genKclFromJsonData(w, filename, src)
default:
return errors.New("unknown mode")
}
Expand Down
54 changes: 54 additions & 0 deletions pkg/tools/gen/genkcl_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package gen

import (
"encoding/json"
"io"
"sort"
)

func (k *kclGenerator) genKclFromJsonData(w io.Writer, filename string, src interface{}) error {
code, err := readSource(filename, src)
if err != nil {
return err
}
jsonData := &map[string]interface{}{}
if err = json.Unmarshal(code, jsonData); err != nil {
return err
}

// convert json data to kcl
result := convertKclFromJson(jsonData)

// generate kcl code
return k.genKcl(w, kclFile{Data: result})
}

func convertKclFromJson(jsonData *map[string]interface{}) []data {
var result []data
for key, value := range *jsonData {
switch value := value.(type) {
case map[string]interface{}:
result = append(result, data{
Key: key,
Value: convertKclFromJson(&value),
})
case []interface{}:
var vals []interface{}
for _, v := range value {
switch v := v.(type) {
case map[string]interface{}:
vals = append(vals, convertKclFromJson(&v))
default:
vals = append(vals, v)
}
}
result = append(result, data{Key: key, Value: vals})
default:
result = append(result, data{Key: key, Value: value})
}
}
sort.Slice(result, func(i, j int) bool {
return result[i].Key < result[j].Key
})
return result
}
4 changes: 2 additions & 2 deletions pkg/tools/gen/genkcl_jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (k *kclGenerator) genSchemaFromJsonSchema(w io.Writer, filename string, src
if !result.IsSchema {
panic("result is not schema")
}
kclSch := kclSchema{
kclSch := kclFile{
Imports: []string{},
Schemas: []schema{result.schema},
}
Expand All @@ -62,7 +62,7 @@ func (k *kclGenerator) genSchemaFromJsonSchema(w io.Writer, filename string, src
}

// generate kcl schema code
return k.genKclSchema(w, kclSch)
return k.genKcl(w, kclSch)
}

func convertSchemaFromJsonSchema(ctx convertContext, s *jsonschema.Schema, name string) convertResult {
Expand Down
4 changes: 2 additions & 2 deletions pkg/tools/gen/genkcl_terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ func (k *kclGenerator) genSchemaFromTerraformSchema(w io.Writer, filename string
})

// generate kcl schema code
kclSch := kclSchema{
kclSch := kclFile{
Imports: []string{},
Schemas: result,
}
return k.genKclSchema(w, kclSch)
return k.genKcl(w, kclSch)
}

func tfTypeToKclType(t interface{}) typeInterface {
Expand Down
16 changes: 15 additions & 1 deletion pkg/tools/gen/genkcl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ schema Company:

}

func TestGenKclFromJson(t *testing.T) {
func TestGenKclFromJsonSchema(t *testing.T) {
type testCase struct {
name string
input string
Expand Down Expand Up @@ -166,6 +166,20 @@ func TestGenKclFromTerraform(t *testing.T) {
assert2.Equal(t, expect, string(bytes.ReplaceAll(result, []byte("\r\n"), []byte("\n"))))
}

func TestGenKclFromJson(t *testing.T) {
input := filepath.Join("testdata", "json", "input.json")
expectFilepath := filepath.Join("testdata", "json", "expect.k")
expect := readFileString(t, expectFilepath)

var buf bytes.Buffer
err := GenKcl(&buf, input, nil, &GenKclOptions{})
if err != nil {
t.Fatal(err)
}
result := buf.Bytes()
assert2.Equal(t, expect, string(bytes.ReplaceAll(result, []byte("\r\n"), []byte("\n"))))
}

func readFileString(t testing.TB, p string) (content string) {
data, err := os.ReadFile(p)
if err != nil {
Expand Down
40 changes: 35 additions & 5 deletions pkg/tools/gen/template.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gen

import (
"bytes"
_ "embed"
"fmt"
"io"
Expand All @@ -9,6 +10,8 @@ import (
)

var (
//go:embed templates/kcl/data.gotmpl
dataTmpl string
//go:embed templates/kcl/document.gotmpl
documentTmpl string
//go:embed templates/kcl/header.gotmpl
Expand All @@ -17,21 +20,43 @@ var (
validatorTmpl string
//go:embed templates/kcl/schema.gotmpl
schemaTmpl string
//go:embed templates/kcl/index.gotmpl
indexTmpl string
)

var funcs = template.FuncMap{
"formatType": formatType,
"formatValue": formatValue,
"formatName": formatName,
"formatDoc": formatDoc,
"indentLines": indentLines,
"isKclData": func(v interface{}) bool {
_, ok := v.([]data)
return ok
},
"isArray": func(v interface{}) bool {
_, ok := v.([]interface{})
return ok
},
}

func (k *kclGenerator) genKclSchema(w io.Writer, s kclSchema) error {
func (k *kclGenerator) genKcl(w io.Writer, s kclFile) error {
tmpl := &template.Template{}

// add "include" function. It works like "template" but can be used in pipeline.
funcs["include"] = func(name string, data interface{}) (string, error) {
buf := bytes.NewBuffer(nil)
if err := tmpl.ExecuteTemplate(buf, name, data); err != nil {
return "", err
}
return buf.String(), nil
}

tmpl = addTemplate(tmpl, "data", dataTmpl)
tmpl = addTemplate(tmpl, "document", documentTmpl)
tmpl = addTemplate(tmpl, "header", headerTmpl)
tmpl = addTemplate(tmpl, "validator", validatorTmpl)
tmpl = addTemplate(tmpl, "schema", schemaTmpl)
tmpl = addTemplate(tmpl, "index", indexTmpl)
return tmpl.Funcs(funcs).Execute(w, s)
}

Expand Down Expand Up @@ -106,7 +131,12 @@ func formatName(name string) string {
return name
}

func formatDoc(doc, indent string) string {
doc = strings.Replace(doc, "\r\n", "\n", -1)
return indent + strings.Replace(doc, "\n", "\n"+indent, -1)
func indentLines(s, indent string) string {
s = strings.Replace(s, "\r\n", "\n", -1)
n := strings.Count(s, "\n")
if s[len(s)-1] == '\n' {
// ignore last empty line
n--
}
return indent + strings.Replace(s, "\n", "\n"+indent, n)
}
24 changes: 24 additions & 0 deletions pkg/tools/gen/templates/kcl/data.gotmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{ formatName .Key }}{{ " = " }}
{{- if isKclData .Value }}
{{- "{\n" }}
{{- range .Value -}}
{{- indentLines (include "data" .) " " }}
{{- end }}
{{- "}" }}
{{- else if isArray .Value }}
{{- "[\n" }}
{{- range .Value -}}
{{- if isKclData . }}
{{- " {\n" }}
{{- range . -}}
{{- indentLines (include "data" .) " " }}
{{- end }}
{{- " }\n" }}
{{- else }}
{{- indentLines (formatValue .) " " }}
{{- end }}
{{- end }}
{{- "]" }}
{{- else }}
{{- formatValue .Value }}
{{- end }}
6 changes: 3 additions & 3 deletions pkg/tools/gen/templates/kcl/document.gotmpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{- if .Description }}
{{- formatDoc .Description " " }}
{{- indentLines .Description " " }}
{{- else }}
{{- formatDoc .Name " " }}
{{- indentLines .Name " " }}
{{- end }}

{{- if .Properties }}
Expand All @@ -11,7 +11,7 @@
{{- range .Properties }}
{{ formatName .Name }} : {{ formatType .Type }}, {{ if .Required }}required{{ else }}optional{{ end }}
{{- if .HasDefault }}, default is {{ formatValue .DefaultValue }}{{ end }}
{{- if .Description }}{{ "\n" }}{{ formatDoc .Description " " }}{{ end }}
{{- if .Description }}{{ "\n" }}{{ indentLines .Description " " }}{{ end }}
{{- end -}}

{{- end -}}
2 changes: 1 addition & 1 deletion pkg/tools/gen/templates/kcl/header.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ Editing this file might prove futile when you re-run the KCL auto-gen generate c
{{- range .Imports }}
import {{ . }}
{{- end }}
{{- end }}
{{- end -}}
9 changes: 9 additions & 0 deletions pkg/tools/gen/templates/kcl/index.gotmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{{ template "header" . }}

{{ range .Schemas -}}
{{ template "schema" . }}
{{ end -}}

{{ range .Data -}}
{{ template "data" . -}}
{{ end -}}
5 changes: 1 addition & 4 deletions pkg/tools/gen/templates/kcl/schema.gotmpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{{ template "header" . }}
{{ range .Schemas -}}
schema {{ formatName .Name }}:
"""{{- "\n" }}
{{- template "document" . }}
Expand All @@ -15,5 +13,4 @@ schema {{ formatName .Name }}:
{{ if .Validations }}
check:
{{- template "validator" .Validations }}
{{- end }}
{{ end -}}
{{- end -}}
41 changes: 41 additions & 0 deletions pkg/tools/gen/testdata/json/expect.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
This file was generated by the KCL auto-gen tool. DO NOT EDIT.
Editing this file might prove futile when you re-run the KCL auto-gen generate command.
"""

apiVersion = "apps/v1"
kind = "Deployment"
metadata = {
labels = {
app = "nginx"
}
name = "nginx-deployment"
}
spec = {
replicas = 3
selector = {
matchLabels = {
app = "nginx"
}
}
template = {
metadata = {
labels = {
app = "nginx"
}
}
spec = {
containers = [
{
image = "nginx:latest"
name = "nginx-container"
ports = [
{
containerPort = 80
}
]
}
]
}
}
}
Loading
Loading