Merge pull request 'closes #57' (#59) from issue/57/handlebars into dev
nclazz/codebuilder/pipeline/head This commit looks good Details
nclazz/codebuilder-project/pipeline/head This commit looks good Details

Reviewed-on: de.nclazz/codebuilder#59
dev
Niclas Thobaben 2021-11-15 19:55:58 +01:00
commit 9ebf7644ae
14 changed files with 157 additions and 185 deletions

View File

@ -14,7 +14,7 @@
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<mustache.version>0.9.4</mustache.version>
<handlebars.version>4.0.6</handlebars.version>
<junit.jupiter.version>5.7.2</junit.jupiter.version>
</properties>
@ -50,9 +50,9 @@
</dependency>
<dependency>
<groupId>com.github.spullara.mustache.java</groupId>
<artifactId>compiler</artifactId>
<version>${mustache.version}</version>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars</artifactId>
<version>${handlebars.version}</version>
</dependency>
<dependency>

View File

@ -1,6 +1,5 @@
package de.nclazz.apps.codebuilder.facade;
import de.nclazz.apps.codebuilder.render.FormattedString;
import io.apibuilder.spec.v0.models.Attribute;
import io.apibuilder.spec.v0.models.Deprecation;
import lombok.Getter;
@ -13,8 +12,8 @@ import java.util.Map;
@Getter
public abstract class AbstractItem {
private final FormattedString name;
private final FormattedString plural;
private final String name;
private final String plural;
private final String description;
private final Deprecation deprecation;
private final Map<String, Object> attributes;
@ -22,8 +21,8 @@ public abstract class AbstractItem {
public AbstractItem(String name, String plural, String description, Deprecation deprecation,
String namespace, List<Attribute> attributes) {
this.name = new FormattedString(name);
this.plural = plural != null ? new FormattedString(plural) : null;
this.name = name;
this.plural = plural;
this.description = description;
this.deprecation = deprecation;
this.attributes = mapAttributes(attributes);

View File

@ -1,6 +1,5 @@
package de.nclazz.apps.codebuilder.facade;
import de.nclazz.apps.codebuilder.render.FormattedString;
import io.apibuilder.spec.v0.models.EnumValue;
import lombok.Getter;
@ -8,7 +7,7 @@ import lombok.Getter;
public class EnumValueFacade extends AbstractItem {
private final EnumValue enumValue;
private final FormattedString value;
private final String value;
private final boolean last;
public EnumValueFacade(EnumValue enumValue, String namespace, boolean isLast) {
@ -20,11 +19,8 @@ public class EnumValueFacade extends AbstractItem {
enumValue.getAttributes()
);
this.enumValue = enumValue;
this.value = new FormattedString(enumValue.getValue() != null ? enumValue.getValue() : enumValue.getName());
this.value = enumValue.getValue() != null ? enumValue.getValue() : enumValue.getName();
this.last = isLast;
}
public FormattedString getValue() {
return this.value;
}
}

View File

@ -1,8 +1,6 @@
package de.nclazz.apps.codebuilder.facade;
import de.nclazz.apps.codebuilder.render.FormattedString;
import de.nclazz.apps.codebuilder.types.DataType;
import de.nclazz.apps.codebuilder.utils.TypeUtils;
import io.apibuilder.spec.v0.models.Field;
import lombok.Getter;
@ -12,7 +10,7 @@ import java.util.List;
public class FieldFacade extends AbstractItem {
private final Field field;
private final List<FormattedString> annotations;
private final List<String> annotations;
private final DataType type;
public FieldFacade(Field field, DataType dataType) {
@ -24,7 +22,7 @@ public class FieldFacade extends AbstractItem {
field.getAttributes()
);
this.field = field;
this.annotations = TypeUtils.mapList(field.getAnnotations(), FormattedString::new);
this.annotations = field.getAnnotations();
this.type = dataType;
}

View File

@ -1,6 +1,5 @@
package de.nclazz.apps.codebuilder.facade;
import de.nclazz.apps.codebuilder.render.FormattedString;
import de.nclazz.apps.codebuilder.types.DataType;
import de.nclazz.apps.codebuilder.types.DataTypeResolver;
import de.nclazz.apps.codebuilder.utils.TypeUtils;
@ -35,7 +34,7 @@ public class ModelFacade extends AbstractItem {
field -> new FieldFacade(field, resolver.resolve(field.getType()))
);
this.interfaces = interfaces.stream()
.filter(anInterface -> model.getInterfaces().contains(anInterface.getName().toString()))
.filter(anInterface -> model.getInterfaces().contains(anInterface.getName()))
.collect(Collectors.toList());
this.unions = unions.stream()
.filter(filterRelatedUnion(model))
@ -52,13 +51,11 @@ public class ModelFacade extends AbstractItem {
this.unions.stream()
.map(UnionFacade::getName)
.map(FormattedString::toString)
.map(resolver::resolve)
.collect(Collectors.toCollection(() -> dependencies));
this.interfaces.stream()
.map(InterfaceFacade::getName)
.map(FormattedString::toString)
.map(resolver::resolve)
.collect(Collectors.toCollection(() -> dependencies));
@ -74,7 +71,6 @@ public class ModelFacade extends AbstractItem {
union.getTypes().stream()
.anyMatch(
type -> type.getName()
.toString()
.equals(model.getName())
);
}

View File

@ -1,6 +1,5 @@
package de.nclazz.apps.codebuilder.facade;
import de.nclazz.apps.codebuilder.render.FormattedString;
import de.nclazz.apps.codebuilder.types.DataTypeResolver;
import de.nclazz.apps.codebuilder.utils.TypeUtils;
import io.apibuilder.spec.v0.models.Union;
@ -12,7 +11,7 @@ import java.util.List;
public class UnionFacade extends AbstractItem {
private final Union union;
private final FormattedString discriminator;
private final String discriminator;
private final List<UnionTypeFacade> types;
public UnionFacade(Union union, DataTypeResolver resolver) {
@ -25,7 +24,7 @@ public class UnionFacade extends AbstractItem {
union.getAttributes()
);
this.union = union;
this.discriminator = new FormattedString(union.getDiscriminator());
this.discriminator = union.getDiscriminator();
this.types = TypeUtils.mapList(
union.getTypes(),
type -> new UnionTypeFacade(type, resolver.resolve(type.getType()))

View File

@ -0,0 +1,66 @@
package de.nclazz.apps.codebuilder.render;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
@RequiredArgsConstructor
public class CodeBuilderHelpers {
private static final String IGNORE_CHARS = "_-., /";
private final Map<String, String> reservedWords;
public CodeBuilderHelpers() {
this(Collections.emptyMap());
}
public String safe(String input) {
if (this.reservedWords.containsKey(input)) {
return this.reservedWords.get(input);
}
return input;
}
public String camelCase(@NonNull String input) {
if (input.length() == 0) {
return input;
}
StringBuilder sb = new StringBuilder();
char[] chars = input.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
for (int i = 0; i < input.length(); i++) {
char c = chars[i];
if ((IGNORE_CHARS.indexOf(c) != -1 && (i < input.length() - 1))) {
c = Character.toUpperCase(chars[++i]);
}
sb.append(c);
}
return sb.toString();
}
public String pascalCase(@NonNull String input) {
String converted = camelCase(input);
if (converted.length() == 0) {
return input;
}
char[] chars = converted.toCharArray();
chars[0] = Character.toUpperCase(chars[0]);
return String.valueOf(chars, 0, chars.length);
}
public String upperCase(@NonNull String input) {
return input.toUpperCase(Locale.ROOT);
}
public String lowerCase(@NonNull String input) {
return input.toLowerCase(Locale.ROOT);
}
}

View File

@ -1,81 +0,0 @@
package de.nclazz.apps.codebuilder.render;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@RequiredArgsConstructor
public class FormattedString {
private static final String IGNORE_CHARS = "_-., /";
@NonNull
private final String value;
public String getPascalCase() {
return toPascalCase(this.value);
}
public String getCamelCase() {
return toCamelCase(this.value);
}
public String getUpperCase() {
return this.value.toUpperCase(Locale.ROOT);
}
public String getLowerCase() {
return this.value.toUpperCase(Locale.ROOT);
}
public List<String> splitByDot() {
return Arrays.asList(this.value.split("\\."));
}
public List<String> splitBySlash() {
return Arrays.asList(this.value.split("/"));
}
public List<String> splitByBackslash() {
return Arrays.asList(this.value.split("\\\\"));
}
@Override
public String toString() {
return this.value;
}
public static String toCamelCase(@NonNull String input) {
if (input.length() == 0) {
return input;
}
StringBuilder sb = new StringBuilder();
char[] chars = input.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
for(int i = 0; i < input.length(); i++) {
char c = chars[i];
if((IGNORE_CHARS.indexOf(c) != -1 && (i < input.length() - 1))) {
c = Character.toUpperCase(chars[++i]);
}
sb.append(c);
}
return sb.toString();
}
public static String toPascalCase(@NonNull String input) {
String converted = toCamelCase(input);
if(converted.length() == 0) {
return input;
}
char[] chars = converted.toCharArray();
chars[0] = Character.toUpperCase(chars[0]);
return String.valueOf(chars, 0, chars.length);
}
}

View File

@ -1,17 +1,14 @@
package de.nclazz.apps.codebuilder.render;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import com.github.jknack.handlebars.Handlebars;
import de.nclazz.codebuilder.v0.models.Template;
import de.nclazz.codebuilder.v0.models.TemplateTarget;
import io.apibuilder.generator.v0.models.File;
import io.apibuilder.generator.v0.models.FileFlag;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -22,17 +19,17 @@ public class TemplateRenderer {
private static final List<FileFlag> SCAFFOLDING_FLAGS = Collections.singletonList(FileFlag.SCAFFOLDING);
private final MustacheFactory mustacheFactory = new DefaultMustacheFactory();
@Getter
private final Template template;
private Mustache fileNameTemplate;
private Mustache dirNameTemplate;
private Mustache contentTemplate;
private com.github.jknack.handlebars.Template fileNameTemplate;
private com.github.jknack.handlebars.Template dirNameTemplate;
private com.github.jknack.handlebars.Template contentTemplate;
public File renderFile(RenderView view) {
compileTemplates();
Handlebars handlebars = new Handlebars();
handlebars.registerHelpers(new CodeBuilderHelpers(view.getReservedWords()));
compileTemplates(handlebars);
String fileName = renderFileName(view);
String dirName = renderTemplate(this.dirNameTemplate, view);
@ -54,27 +51,26 @@ public class TemplateRenderer {
.collect(Collectors.toList());
}
private String renderTemplate(Mustache template, Object data) {
return template.execute(new StringWriter(), data).toString();
@SneakyThrows
private String renderTemplate(com.github.jknack.handlebars.Template template, Object data) {
return template.apply(data);
}
private void compileTemplates() {
if(this.fileNameTemplate == null) {
this.fileNameTemplate = compileTemplate("filename", this.template.getFilename());
private void compileTemplates(Handlebars handlebars) {
if (this.fileNameTemplate == null) {
this.fileNameTemplate = compileTemplate(handlebars, this.template.getFilename());
}
if(this.dirNameTemplate == null) {
this.dirNameTemplate = compileTemplate("directory", this.template.getDirectory());
if (this.dirNameTemplate == null) {
this.dirNameTemplate = compileTemplate(handlebars, this.template.getDirectory());
}
if(this.contentTemplate == null) {
this.contentTemplate = compileTemplate("content", this.template.getContent());
if (this.contentTemplate == null) {
this.contentTemplate = compileTemplate(handlebars, this.template.getContent());
}
}
private Mustache compileTemplate(String name, String template) {
return this.mustacheFactory.compile(
new StringReader(template),
this.template.getName() + "-" + name
);
@SneakyThrows
private com.github.jknack.handlebars.Template compileTemplate(Handlebars handlebars, String template) {
return handlebars.compileInline(template);
}
private File generateFile(String fileName, String dirName, String content) {

View File

@ -1,6 +1,5 @@
package de.nclazz.apps.codebuilder.types;
import de.nclazz.apps.codebuilder.render.FormattedString;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -10,19 +9,11 @@ import java.util.Objects;
@AllArgsConstructor
public class DataType implements Comparable<DataType> {
private final FormattedString name;
private final String name;
private final String namespace;
private final DataTypeCategory category;
private boolean isCollection;
public DataType(String name, String namespace, DataTypeCategory category, boolean isCollection) {
this.name = new FormattedString(name);
this.namespace = namespace;
this.isCollection = isCollection;
this.category = category;
}
public DataType asCollection() {
return new DataType(this.name, this.namespace, this.category, true);
@ -30,9 +21,9 @@ public class DataType implements Comparable<DataType> {
public String getFullQualifiedName() {
if(this.namespace == null || this.namespace.isEmpty()) {
return this.name.toString();
return this.name;
}
return String.format("%s.%s", this.namespace, this.name.toString());
return String.format("%s.%s", this.namespace, this.name);
}
public static DataType primitive(String name, String namespace) {
@ -59,7 +50,7 @@ public class DataType implements Comparable<DataType> {
}
DataType dataType = (DataType) obj;
return this.namespace.equals(dataType.namespace)
&& this.name.getUpperCase().equals(dataType.name.getUpperCase())
&& this.name.equalsIgnoreCase(dataType.name)
&& this.category.equals(dataType.category)
&& this.isCollection == dataType.isCollection;
}

View File

@ -0,0 +1,49 @@
package de.nclazz.apps.codebuilder.render;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CodeBuilderHelpersTest {
@Test
void SafeHelperReplacesReservedWords() {
CodeBuilderHelpers helpers = new CodeBuilderHelpers(Map.of("reserved", "replaced"));
assertEquals("replaced", helpers.safe("replaced"));
assertEquals("replaced", helpers.safe("reserved"));
}
@Test
void convertStringCamelCase() {
CodeBuilderHelpers helpers = new CodeBuilderHelpers();
assertEquals("camelCase", helpers.camelCase("camelCase"));
assertEquals("camelCase", helpers.camelCase("camel_Case"));
assertEquals("camelCase", helpers.camelCase("camel-Case"));
assertEquals("camelCase", helpers.camelCase("camel-case"));
assertEquals("camelCase", helpers.camelCase("camel case"));
assertEquals("camelCase", helpers.camelCase("CamelCase"));
assertEquals("camelCase", helpers.camelCase("Camel_Case"));
assertEquals("camelCase", helpers.camelCase("Camel_case"));
assertEquals("camelCase", helpers.camelCase("camel_case"));
assertEquals("cameLCase", helpers.camelCase("cameLCase"));
assertEquals("cameLCasE", helpers.camelCase("CameLCasE"));
}
@Test
void convertStringPascalCase() {
CodeBuilderHelpers helpers = new CodeBuilderHelpers();
assertEquals("PascalCase", helpers.pascalCase("PascalCase"));
assertEquals("PascalCase", helpers.pascalCase("Pascal_Case"));
assertEquals("PascalCase", helpers.pascalCase("Pascal-Case"));
assertEquals("PascalCase", helpers.pascalCase("Pascal case"));
assertEquals("PascalCase", helpers.pascalCase("pascalCase"));
assertEquals("PascalCase", helpers.pascalCase("pascal_Case"));
assertEquals("PascALCase", helpers.pascalCase("PascAL_Case"));
assertEquals("PascalCasE", helpers.pascalCase("pascal_CasE"));
}
}

View File

@ -26,7 +26,7 @@ class CodeGeneratorTest {
TemplateTarget.MODEL,
"{{item.name}}.file",
"dir",
"{{item.name}}-{{item.name.camelCase}}-{{item.name.pascalCase}}"
"{{item.name}}-{{#camelCase item.name}}{{/camelCase}}-{{#pascalCase item.name}}{{/pascalCase}}"
)
);

View File

@ -1,37 +0,0 @@
package de.nclazz.apps.codebuilder.render;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class FormattedStringTest {
@Test
void convertStringToCamelCase() {
assertEquals("camelCase", FormattedString.toCamelCase("camelCase"));
assertEquals("camelCase", FormattedString.toCamelCase("camel_Case"));
assertEquals("camelCase", FormattedString.toCamelCase("camel-Case"));
assertEquals("camelCase", FormattedString.toCamelCase("camel-case"));
assertEquals("camelCase", FormattedString.toCamelCase("camel case"));
assertEquals("camelCase", FormattedString.toCamelCase("CamelCase"));
assertEquals("camelCase", FormattedString.toCamelCase("Camel_Case"));
assertEquals("camelCase", FormattedString.toCamelCase("Camel_case"));
assertEquals("camelCase", FormattedString.toCamelCase("camel_case"));
assertEquals("cameLCase", FormattedString.toCamelCase("cameLCase"));
assertEquals("cameLCasE", FormattedString.toCamelCase("CameLCasE"));
}
@Test
void convertStringToPascalCase() {
assertEquals("PascalCase", FormattedString.toPascalCase("PascalCase"));
assertEquals("PascalCase", FormattedString.toPascalCase("Pascal_Case"));
assertEquals("PascalCase", FormattedString.toPascalCase("Pascal-Case"));
assertEquals("PascalCase", FormattedString.toPascalCase("Pascal case"));
assertEquals("PascalCase", FormattedString.toPascalCase("pascalCase"));
assertEquals("PascalCase", FormattedString.toPascalCase("pascal_Case"));
assertEquals("PascALCase", FormattedString.toPascalCase("PascAL_Case"));
assertEquals("PascalCasE", FormattedString.toPascalCase("pascal_CasE"));
}
}

View File

@ -62,7 +62,7 @@ class TemplateRendererTest {
Template template = new Template();
template.setFilename("{{global.filename}}");
template.setDirectory("{{embedded.directory}}");
template.setContent("{{embedded.bool}} {{#safe}}{{embedded.bool}}{{/safe}}");
template.setContent("{{embedded.bool}} {{#safe embedded.bool}}{{/safe}}");
TemplateRenderer renderer = new TemplateRenderer(template);
File file = renderer.renderFile(view);