Skip to content

Commit

Permalink
chore: refactor text caser out of schema generator
Browse files Browse the repository at this point in the history
  • Loading branch information
omissis committed Nov 8, 2023
1 parent fc6ce77 commit 0a45c49
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 122 deletions.
126 changes: 126 additions & 0 deletions internal/x/text/cases.go
Original file line number Diff line number Diff line change
@@ -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
}
76 changes: 14 additions & 62 deletions pkg/generator/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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{
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
60 changes: 0 additions & 60 deletions pkg/generator/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 0a45c49

Please sign in to comment.