From 0a45c493edcf4a1b7aa023f8865fc6fb35702bcc Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 3 Oct 2023 12:12:47 +0200 Subject: [PATCH] chore: refactor text caser out of schema generator --- internal/x/text/cases.go | 126 ++++++++++++++++++++++++++++++++++++++ pkg/generator/generate.go | 76 +++++------------------ pkg/generator/utils.go | 60 ------------------ 3 files changed, 140 insertions(+), 122 deletions(-) create mode 100644 internal/x/text/cases.go diff --git a/internal/x/text/cases.go b/internal/x/text/cases.go new file mode 100644 index 0000000..0eabe88 --- /dev/null +++ b/internal/x/text/cases.go @@ -0,0 +1,126 @@ +package text + +import ( + "path/filepath" + "strings" + "unicode" +) + +func NewCaser(capitalizations, resolveExtensions []string) *Caser { + return &Caser{ + capitalizations: capitalizations, + resolveExtensions: resolveExtensions, + } +} + +type Caser struct { + capitalizations []string + resolveExtensions []string +} + +func (c *Caser) IdentifierFromFileName(fileName string) string { + s := filepath.Base(fileName) + for _, ext := range c.resolveExtensions { + trimmed := strings.TrimSuffix(s, ext) + if trimmed != s { + s = trimmed + + break + } + } + + return c.Identifierize(s) +} + +func (c *Caser) Identifierize(s string) string { + if s == "" { + return "Blank" + } + + // FIXME: Better handling of non-identifier chars. + var sb strings.Builder + for _, part := range splitIdentifierByCaseAndSeparators(s) { + _, _ = sb.WriteString(c.Capitalize(part)) + } + + ident := sb.String() + + if !unicode.IsLetter(rune(ident[0])) { + ident = "A" + ident + } + + return ident +} + +func (c *Caser) Capitalize(s string) string { + if len(s) == 0 { + return "" + } + + for _, c := range c.capitalizations { + if strings.EqualFold(c, s) { + return c + } + } + + return strings.ToUpper(s[0:1]) + s[1:] +} + +func splitIdentifierByCaseAndSeparators(s string) []string { + if len(s) == 0 { + return nil + } + + type state int + + const ( + stateNothing state = iota + stateLower + stateUpper + stateNumber + stateDelimiter + ) + + var result []string + + currState, j := stateNothing, 0 + + for i := 0; i < len(s); i++ { + var nextState state + + c := rune(s[i]) + + switch { + case unicode.IsLower(c): + nextState = stateLower + + case unicode.IsUpper(c): + nextState = stateUpper + + case unicode.IsNumber(c): + nextState = stateNumber + + default: + nextState = stateDelimiter + } + + if nextState != currState { + if currState == stateDelimiter { + j = i + } else if !(currState == stateUpper && nextState == stateLower) { + if i > j { + result = append(result, s[j:i]) + } + j = i + } + + currState = nextState + } + } + + if currState != stateDelimiter && len(s)-j > 0 { + result = append(result, s[j:]) + } + + return result +} diff --git a/pkg/generator/generate.go b/pkg/generator/generate.go index 78f98bd..429d205 100644 --- a/pkg/generator/generate.go +++ b/pkg/generator/generate.go @@ -7,8 +7,8 @@ import ( "os" "path/filepath" "strings" - "unicode" + "github.com/atombender/go-jsonschema/internal/x/text" "github.com/atombender/go-jsonschema/pkg/codegen" "github.com/atombender/go-jsonschema/pkg/schemas" ) @@ -35,10 +35,11 @@ type SchemaMapping struct { } type Generator struct { + caser *text.Caser config Config + inScope map[qualifiedDefinition]struct{} outputs map[string]*output schemaCacheByFileName map[string]*schemas.Schema - inScope map[qualifiedDefinition]struct{} warner func(string) formatters []formatter } @@ -71,10 +72,11 @@ func New(config Config) (*Generator, error) { } return &Generator{ + caser: text.NewCaser(config.Capitalizations, config.ResolveExtensions), config: config, + inScope: map[qualifiedDefinition]struct{}{}, outputs: map[string]*output{}, schemaCacheByFileName: map[string]*schemas.Schema{}, - inScope: map[qualifiedDefinition]struct{}{}, warner: config.Warner, formatters: formatters, }, nil @@ -234,10 +236,10 @@ func (g *Generator) getRootTypeName(schema *schemas.Schema, fileName string) str } if g.config.StructNameFromTitle && schema.Title != "" { - return g.identifierize(schema.Title) - } else { - return g.identifierFromFileName(fileName) + return g.caser.Identifierize(schema.Title) } + + return g.caser.IdentifierFromFileName(fileName) } func (g *Generator) findOutputFileForSchemaID(id string) (*output, error) { @@ -294,58 +296,10 @@ func (g *Generator) beginOutput( func (g *Generator) makeEnumConstantName(typeName, value string) string { if strings.ContainsAny(typeName[len(typeName)-1:], "0123456789") { - return typeName + "_" + g.identifierize(value) - } - - return typeName + g.identifierize(value) -} - -func (g *Generator) identifierFromFileName(fileName string) string { - s := filepath.Base(fileName) - for _, ext := range g.config.ResolveExtensions { - trimmed := strings.TrimSuffix(s, ext) - if trimmed != s { - s = trimmed - - break - } + return typeName + "_" + g.caser.Identifierize(value) } - return g.identifierize(s) -} - -func (g *Generator) identifierize(s string) string { - if s == "" { - return "Blank" - } - - // FIXME: Better handling of non-identifier chars. - var sb strings.Builder - for _, part := range splitIdentifierByCaseAndSeparators(s) { - _, _ = sb.WriteString(g.capitalize(part)) - } - - ident := sb.String() - - if !unicode.IsLetter(rune(ident[0])) { - ident = "A" + ident - } - - return ident -} - -func (g *Generator) capitalize(s string) string { - if len(s) == 0 { - return "" - } - - for _, c := range g.config.Capitalizations { - if strings.EqualFold(c, s) { - return c - } - } - - return strings.ToUpper(s[0:1]) + s[1:] + return typeName + g.caser.Identifierize(value) } type schemaGenerator struct { @@ -363,7 +317,7 @@ func (g *schemaGenerator) generateRootType() error { for _, name := range sortDefinitionsByName(g.schema.Definitions) { def := g.schema.Definitions[name] - _, err := g.generateDeclaredType(def, newNameScope(g.identifierize(name))) + _, err := g.generateDeclaredType(def, newNameScope(g.caser.Identifierize(name))) if err != nil { return err } @@ -408,7 +362,7 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro defName = scope[len(prefix):] } - var schema *schemas.Schema + schema := g.schema if fileName != "" { var err error @@ -417,8 +371,6 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro if err != nil { return nil, fmt.Errorf("could not follow $ref %q to file %q: %w", ref, fileName, err) } - } else { - schema = g.schema } qual := qualifiedDefinition{ @@ -441,7 +393,7 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro return &codegen.EmptyInterfaceType{}, nil } - defName = g.identifierize(defName) + defName = g.caser.Identifierize(defName) } else { def = (*schemas.Type)(schema.ObjectAsType) defName = g.getRootTypeName(schema, fileName) @@ -782,7 +734,7 @@ func (g *schemaGenerator) generateStructType( prop := t.Properties[name] isRequired := requiredNames[name] - fieldName := g.identifierize(name) + fieldName := g.caser.Identifierize(name) if ext := prop.GoJSONSchemaExtension; ext != nil { for _, pkg := range ext.Imports { diff --git a/pkg/generator/utils.go b/pkg/generator/utils.go index 0182ed2..77aff3e 100644 --- a/pkg/generator/utils.go +++ b/pkg/generator/utils.go @@ -2,71 +2,11 @@ package generator import ( "sort" - "unicode" "github.com/atombender/go-jsonschema/pkg/codegen" "github.com/atombender/go-jsonschema/pkg/schemas" ) -func splitIdentifierByCaseAndSeparators(s string) []string { - if len(s) == 0 { - return nil - } - - type state int - - const ( - stateNothing state = iota - stateLower - stateUpper - stateNumber - stateDelimiter - ) - - var result []string - - currState, j := stateNothing, 0 - - for i := 0; i < len(s); i++ { - var nextState state - - c := rune(s[i]) - - switch { - case unicode.IsLower(c): - nextState = stateLower - - case unicode.IsUpper(c): - nextState = stateUpper - - case unicode.IsNumber(c): - nextState = stateNumber - - default: - nextState = stateDelimiter - } - - if nextState != currState { - if currState == stateDelimiter { - j = i - } else if !(currState == stateUpper && nextState == stateLower) { - if i > j { - result = append(result, s[j:i]) - } - j = i - } - - currState = nextState - } - } - - if currState != stateDelimiter && len(s)-j > 0 { - result = append(result, s[j:]) - } - - return result -} - func sortPropertiesByName(props map[string]*schemas.Type) []string { names := make([]string, 0, len(props)) for name := range props {