Split j2html in 2 projects, "code_gen" and "library" (Issue #161) (#165)

* split the project in 2, to make it easier to iterate on the code generation without breaking the build. This could also help reduce the compiled size of the library.

* update CONTRIBUTING.md

* try to fix github workflow
This commit is contained in:
pointbazaar
2020-10-17 10:23:57 +02:00
committed by GitHub
parent 07427c1433
commit acf6bff72a
310 changed files with 491 additions and 236 deletions

View File

@@ -0,0 +1,33 @@
package j2html_codegen;
import j2html_codegen.generators.AttributeInterfaceCodeGenerator;
import j2html_codegen.generators.SpecializedTagClassCodeGenerator;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public final class App
{
public static void main( String[] args )
{
final Path relPath = Paths.get("../library/src/main/java/j2html/");
final Path absPath = relPath.toAbsolutePath();
System.out.println("writing in "+absPath);
//decide if the files should be
//deleted or generated
final boolean delete = false;
try {
AttributeInterfaceCodeGenerator.generate(absPath, delete);
SpecializedTagClassCodeGenerator.generate(absPath, delete);
//TagCreatorCodeGenerator.print();
} catch (IOException e) {
e.printStackTrace();
}
//don't forget to auto-reformat the generated code.
}
}

View File

@@ -0,0 +1,190 @@
package j2html_codegen.generators;
import j2html_codegen.model.AttrD;
import j2html_codegen.model.AttributesList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public final class AttributeInterfaceCodeGenerator {
public static void generate(final Path absPath, final boolean delete) throws IOException {
for (final AttrD attr : AttributesList.attributesDescriptive()) {
final Path path = makePath(attr.attr, absPath);
final String interfaceName = interfaceNameFromAttribute(attr.attr)+"<T extends Tag>";
/*
IFormAction<T extends Tag> extends IInstance<T>
default T withFormAction(String formAction){
get().attr("formaction", formAction);
return get();
}
*/
final String interfaceStr = getInterfaceTemplate(
interfaceName,
Optional.of("IInstance<T>"),
Arrays.asList("j2html.tags.Tag"),
interfaceNameFromAttribute(attr.attr).substring(1),
attr
);
if (delete) {
if(Files.exists(path)) {
System.out.println("deleting " + path);
Files.delete(path);
}
}else{
System.out.println("writing to "+path);
Files.write(path, interfaceStr.getBytes());
}
}
}
private static String getPackage(){
return "package j2html.tags.attributes;\n";
}
private static String makeReturnTypeAndMethodName(final String name){
return "default "+ "T "+name;
}
private static String getInterfaceTemplate(
final String interfaceName,
final Optional<String> optExtends,
final List<String> imports,
final String interfaceNameSimple,
final AttrD attrD
){
final StringBuilder sb = new StringBuilder();
sb.append(getPackage());
sb.append("\n");
for(String importName : imports){
sb.append("import ").append(importName).append(";\n");
}
sb.append("\n");
sb.append("public interface ")
.append(interfaceName);
if(optExtends.isPresent()) {
sb.append(" extends ").append(optExtends.get())
.append(" ");
}
sb.append(" {\n");
//interface contents
/*
IFormAction<T extends Tag> extends IInstance<T>
default T withFormAction(String formAction){
get().attr("formaction", formAction);
return get();
}
*/
//IMPORTANT: '_' added as suffix to mitigate problems
//where attributes are java keywords. Just to make it consistent and avoid special cases.
final String attrName = interfaceNameSimple.toLowerCase();
final String paramName = attrName+"_";
//depending on if the attribute has an argument or not,
//generate methods according to the convention in Tag.java
// arg -> with$ATTR(arg), withCond$ATTR(condition, arg)
// no arg -> is$ATTR(), withCond$ATTR(condition)
//append the 'with$ATTR' method
writeAttributeMethod(interfaceNameSimple, attrD, sb, attrName, paramName);
writeAttributeMethodCond(interfaceNameSimple, attrD, sb, attrName, paramName);
sb.append("}\n");
return sb.toString();
}
private static void addAttributeNoArg(final StringBuilder sb, final String attrName){
//generate the code to add an attribute without an argument
//there are some special attributes
//which do take an argument, but where the argument
//is boolean (meaning on/off, yes/no and the like)
sb.append("get().attr(\"");
if(attrName.equals("autocomplete")){
sb.append(attrName).append("\",\"on\"");
}else {
sb.append(attrName).append("\"");
}
sb.append(");\n");
}
private static void writeAttributeMethodCond(String interfaceNameSimple, AttrD attrD, StringBuilder sb, String attrName, String paramName) {
sb.append(makeReturnTypeAndMethodName("withCond"+interfaceNameSimple));
if(attrD.hasArgument){
//add a variant where you can specify the argument
sb.append("(final boolean enable, final String ").append(paramName).append(") {");
sb.append("if (enable){\n");
sb.append("get().attr(\"").append(attrName).append("\", "+ paramName +");\n");
sb.append("}\n");
sb.append("return get();\n");
}else{
//add a variant where you can toggle the attribute
sb.append("(final boolean enable) {");
sb.append("if (enable){\n");
addAttributeNoArg(sb, attrName);
sb.append("}\n");
sb.append("return get();\n");
}
sb.append("}\n");
}
private static void writeAttributeMethod(String interfaceNameSimple, AttrD attrD, StringBuilder sb, String attrName, String paramName) {
sb.append(makeReturnTypeAndMethodName(
((attrD.hasArgument)?"with":"is")+interfaceNameSimple)
);
if(attrD.hasArgument){
//add a variant where you can specify the argument
sb.append("(final String ").append(paramName).append(") {")
.append("get().attr(\"").append(attrName).append("\", "+ paramName +");\n")
.append("return get();\n");
}else{
//add a variant where you can toggle the attribute
sb.append("() {");
addAttributeNoArg(sb, attrName);
sb.append("return get();\n");
}
sb.append("}\n");
}
public static String interfaceNameFromAttribute(String attribute){
String res = attribute.substring(0,1).toUpperCase()+attribute.substring(1);
return "I" + res;
}
private static Path makePath(String tagLowerCase, final Path absPath){
final String filename = interfaceNameFromAttribute(tagLowerCase)+".java";
return Paths.get(absPath.toString(),"tags/attributes/",filename);
}
}

View File

@@ -0,0 +1,167 @@
package j2html_codegen.generators;
import j2html_codegen.model.AttributesList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static j2html_codegen.generators.TagCreatorCodeGenerator.containerTags;
import static j2html_codegen.generators.TagCreatorCodeGenerator.emptyTags;
public final class SpecializedTagClassCodeGenerator {
public static void generate(final Path absPath, final boolean delete) throws IOException {
//the delete argument serves to give the possibility
//to delete the classes that were written before
System.out.println("// EmptyTags, generated in " + SpecializedTagClassCodeGenerator.class);
for (final String tag : emptyTags()) {
final String className = classNameFromTag(tag);
final Path path = makePath(absPath,tag);
final List<String> interfaceNames = getInterfaceNamesForTag(tag);
final String classString =
getClassTemplate(
className,
Optional.of("EmptyTag<"+className+">"),
Arrays.asList(
"j2html.tags.EmptyTag",
"j2html.tags.attributes.*"
),
tag,
interfaceNames
);
/*
public InputTag() {
super("input");
}
*/
if(delete){
if(Files.exists(path)) {
System.out.println("deleting " + path);
Files.delete(path);
}
}else {
System.out.println("writing to "+path);
Files.write(path, classString.getBytes());
}
}
System.out.println("// ContainerTags, generated in " + SpecializedTagClassCodeGenerator.class);
for (final String tag : containerTags()) {
final Path path = makePath(absPath, tag);
final String className = classNameFromTag(tag);
final List<String> interfaceNames = getInterfaceNamesForTag(tag);
final String classString =
getClassTemplate(
className,
Optional.of("ContainerTag<"+className+">"),
Arrays.asList(
"j2html.tags.ContainerTag",
"j2html.tags.attributes.*"
),
tag,
interfaceNames
);
if(delete){
if(Files.exists(path)) {
System.out.println("deleting " + path);
Files.delete(path);
}
}else {
System.out.println("writing to "+path);
Files.write(path, classString.getBytes());
}
}
}
public static String classNameFromTag(String tageNameLowerCase){
String res = tageNameLowerCase.substring(0,1).toUpperCase()+tageNameLowerCase.substring(1);
return res + "Tag";
}
private static Path makePath(final Path absPath, String tagLowerCase){
final String filename = classNameFromTag(tagLowerCase)+".java";
return Paths.get(absPath.toString(),"tags/specialized/",filename);
}
private static String getPackage(){
return "package j2html.tags.specialized;\n";
}
private static String getClassTemplate(
final String className,
final Optional<String> optExtends,
final List<String> imports,
final String tag,
final List<String> interfaces
){
final StringBuilder sb = new StringBuilder();
sb.append(getPackage());
sb.append("\n");
for(String importName : imports){
sb.append("import ").append(importName).append(";\n");
}
sb.append("\n");
sb.append("public final class ")
.append(className)
.append(" ");
if(optExtends.isPresent()) {
sb.append("extends ").append(optExtends.get())
.append(" ");
}
//add the 'implements' clause
if(!interfaces.isEmpty()) {
sb.append("\n");
sb.append("implements ");
final List<String> genericInterfaceNames
= interfaces.stream().map(iName -> iName+"<"+className+">")
.collect(Collectors.toList());
sb.append(
String.join(",", genericInterfaceNames)
);
}
sb.append(" {\n");
//class contents
sb.append("public ")
.append(className)
.append("() {")
.append("super(\"").append(tag).append("\");")
.append("}\n");
sb.append("}\n");
return sb.toString();
}
private static List<String> getInterfaceNamesForTag(final String tagNameLowercase){
return AttributesList.getCustomAttributesForHtmlTag(tagNameLowercase)
.stream()
.map(
AttributeInterfaceCodeGenerator::interfaceNameFromAttribute
).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,185 @@
package j2html_codegen.generators;
import java.util.Arrays;
import java.util.List;
public final class TagCreatorCodeGenerator {
public static void print() {
System.out.println("// EmptyTags, generated in " + TagCreatorCodeGenerator.class);
for (String tag : emptyTags()) {
final String className = SpecializedTagClassCodeGenerator.classNameFromTag(tag);
final String publicstaticTypeMethod = "public static "+className+" "+tag+" ";
final String castReturn = " return ("+className+") ";
final String construct = " new "+className+"()";
String emptyA1 = publicstaticTypeMethod + "()";
String emptyA2 = "{ return "+construct+"; }";
// Attr shorthands
String emptyB1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr)";
String emptyB2 = "{ "+castReturn+" Attr.addTo("+construct+", shortAttr); }";
// Print
System.out.println(String.format("%-80s%1s", emptyA1, emptyA2));
System.out.println(String.format("%-80s%1s", emptyB1, emptyB2));
System.out.println("");
}
System.out.println("// ContainerTags, generated in " + TagCreatorCodeGenerator.class);
for (String tag : containerTags()) {
final String className = SpecializedTagClassCodeGenerator.classNameFromTag(tag);
final String publicstaticTypeMethod = "public static "+className+" "+tag+" ";
final String castReturn = " return ("+className+") ";
final String construct = " new "+className+"()";
String containerA1 = publicstaticTypeMethod+ "()";
String containerA2 = "{ "+castReturn + construct + "; }";
String containerB1 = publicstaticTypeMethod + "(String text)";
String containerB2 = "{ "+castReturn + construct + ".withText(text); }";
String containerC1 = publicstaticTypeMethod + "(DomContent... dc)";
String containerC2 = "{ "+castReturn + construct+".with(dc); }";
// Attr shorthands
String containerD1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr)";
String containerD2 = "{ "+castReturn+" Attr.addTo("+construct+", shortAttr); }";
String containerE1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr, String text)";
String containerE2 = "{ "+castReturn+" Attr.addTo("+construct+".withText(text), shortAttr); }";
String containerF1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr, DomContent... dc)";
String containerF2 = "{ "+castReturn+" Attr.addTo("+construct+".with(dc), shortAttr); }";
// Print
System.out.println(String.format("%-80s%1s", containerA1, containerA2));
System.out.println(String.format("%-80s%1s", containerB1, containerB2));
System.out.println(String.format("%-80s%1s", containerC1, containerC2));
System.out.println(String.format("%-80s%1s", containerD1, containerD2));
System.out.println(String.format("%-80s%1s", containerE1, containerE2));
System.out.println(String.format("%-80s%1s", containerF1, containerF2));
System.out.println("");
}
}
// This is a method that contains all ContainerTags, there is nothing below it
static List<String> emptyTags() {
return Arrays.asList(
"area",
"base",
"br",
"col",
//"!DOCTYPE html",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"meta",
"param",
"source",
"track",
"wbr"
);
}
static List<String> containerTags() {
return Arrays.asList(
"a",
"abbr",
"address",
"article",
"aside",
"audio",
"b",
"bdi",
"bdo",
"blockquote",
//"body" BodyTag is managed manually,
"button",
"canvas",
"caption",
"cite",
"code",
"colgroup",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
//"head", HeadTag is managed manually
"header",
//"html" HtmlTag is managed manually
"i",
"iframe",
"ins",
"kbd",
"label",
"legend",
"li",
"generate",
"map",
"mark",
"menu",
"menuitem",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"small",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"u",
"ul",
"var",
"video"
);
}
}

View File

@@ -0,0 +1,24 @@
package j2html_codegen.model;
public final class AttrD {
//attribute descriptor
public final String attr;
public final boolean hasArgument;
//the html tags that this attribute can be used on
public final String[] tags;
public AttrD(final String attr, boolean hasArgument){
this.attr = attr;
this.hasArgument = hasArgument;
this.tags = new String[]{};
}
public AttrD(final String attr, boolean hasArgument, final String... tags) {
this.attr = attr;
this.hasArgument = hasArgument;
this.tags = tags;
}
}

View File

@@ -0,0 +1,182 @@
package j2html_codegen.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class AttributesList {
//https://www.w3schools.com/tags/ref_attributes.asp
public static List<String> getCustomAttributesForHtmlTag(final String tagLowercase){
final List<String> attrs = new ArrayList<>();
for(AttrD attrD : attributesDescriptive()){
if(
Arrays.asList(attrD.tags).contains(tagLowercase)
){
attrs.add(attrD.attr);
}
}
return attrs;
}
public static List<AttrD> attributesDescriptive() {
return Arrays.asList(
new AttrD("accept", true, "input"),
//new AttrD("accept-charset","form"), //contains dashes, TODO
//new AttrD("accesskey"), //global attribute
new AttrD("action", true, "form"),
//"align", not supported in HTML5
new AttrD("alt", true, "area","img","input"),
new AttrD("async", false, "script"),
new AttrD("autocomplete", false, "form","input"),
new AttrD("autofocus", false, "button","input","select","textarea"),
new AttrD("autoplay", false, "audio","video"),
//"bgcolor", not supported in HTMTL5
//"border", not supported in HTML5
new AttrD("charset", true, "meta","script"),
new AttrD("checked", false, "input"),
new AttrD("cite", true, "blockquote","del","ins","q"),
//"class" already implemented in Tag.java // global attribute
new AttrD("cols", true, "textarea"),
new AttrD("colspan", true, "td","th"),
new AttrD("content", true, "meta"),
//"contenteditable" global attribute, should be in Tag.java
new AttrD("controls", false, "audio","video"),
new AttrD("coords", true, "area"),
new AttrD("data", true, "object"),
new AttrD("datetime", true, "del","ins","time"),
new AttrD("default", false, "track"),
new AttrD("defer", false, "script"),
//new AttrD("dir"), //global attribute
new AttrD("dirname", true, "input","textarea"),
new AttrD("disabled",false, "button","fieldset","input","optgroup","option","select","textarea"),
new AttrD("download",false, "a","area"),
//new AttrD("draggable") global attribute, should be in Tag.java
new AttrD("enctype", true, "form"),
new AttrD("for", true, "label","output"),
new AttrD("form", true, "button","fieldset","input","label","meter","object","output","select","textarea"),
new AttrD("formaction", true, "button","input"),
new AttrD("headers", true, "td","th"),
new AttrD("height", true, "canvas","embed","iframe","img","input","object","video"),
//new AttrD("hidden"), global attribute
new AttrD("high", true, "meter"),
new AttrD("href", true, "a","area","base","link"),
new AttrD("hreflang", true, "a","area","link"),
//"http-equiv", //TODO: '-' is problematic in code generation
//"id" global attribute, should be in Tag.java
new AttrD("ismap", false, "img"),
new AttrD("kind", true, "track"),
new AttrD("label", true, "track","option","optgroup"),
//"lang" global attribute, should be in Tag.java
new AttrD("list", true, "input"),
new AttrD("loop", false, "audio","video"),
new AttrD("low", true, "meter"),
new AttrD("max", true, "input","meter","progress"),
new AttrD("maxlength", true, "input","textarea"),
new AttrD("media", true, "a","area","link","source","style"),
new AttrD("method", true, "form"),
new AttrD("min", true, "input","meter"),
new AttrD("multiple", false, "input","select"),
new AttrD("muted", false, "video","audio"),
new AttrD("name", true, "button","fieldset","form","iframe","input","map","meta","object","output","param","select","textarea"),
new AttrD("novalidate", false, "form"),
new AttrD("onabort", true, "audio","embed","img","object","video"),
new AttrD("onafterprint", true, "body"),
new AttrD("onbeforeprint", true, "body"),
new AttrD("onbeforeunload", true, "body"),
//new AttrD("onblur"), global attribute
new AttrD("oncanplay", true, "audio","embed","object","video"),
new AttrD("oncanplaythrough", true, "audio","video"),
/* a bunch of event attributes that are on all visible elements (so should be in Tag.java)
"onchange",
"onclick",
"oncontextmenu",
"oncopy",
*/
new AttrD("oncuechange", true, "track"),
/*
"oncut",
...
"ondrop",
*/
new AttrD("ondurationchange", true, "audio","video"),
new AttrD("onemptied", true, "audio","video"),
new AttrD("onended", true, "audio","video"),
new AttrD("onerror", true, "audio","body","embed","img","object","script","style","video"),
//new AttrD("onfocus"),// global attribute
new AttrD("onhashchange", true, "body"),
// ... a bunch of event attributes visible on all elements
new AttrD("onload", true, "body","iframe","img","input","link","script","style"),
new AttrD("onloadeddata", true, "audio","video"),
new AttrD("onloadedmetadata", true, "audio","video"),
new AttrD("onloadstart", true, "audio","video"),
// ... a bunch of event attributes visible on all elements
new AttrD("onoffline", true, "body"),
new AttrD("ononline", true, "body"),
new AttrD("onpagehide", true, "body"),
new AttrD("onpageshow", true, "body"),
//new AttrD("onpaste"),// global attribute
new AttrD("onpause", true, "audio","video"),
new AttrD("onplay", true, "audio","video"),
new AttrD("onplaying", true, "audio","video"),
new AttrD("onpopstate", true, "body"),
new AttrD("onprogress", true, "audio","video"),
new AttrD("onratechange", true, "audio","video"),
new AttrD("onreset", true, "form"),
new AttrD("onresize", true, "body"),
//new AttrD("onscroll"), //global attribute
new AttrD("onsearch", true, "input"),
new AttrD("onseeked", true, "audio","video"),
new AttrD("onseeking", true, "audio","video"),
//new AttrD("onselect"), //global attribute
new AttrD("onstalled", true, "audio","video"),
new AttrD("onstorage", true, "body"),
new AttrD("onsubmit", true, "form"),
new AttrD("onsuspend", true, "audio","video"),
new AttrD("ontimeupdate", true, "audio","video"),
new AttrD("ontoggle", true, "details"),
new AttrD("onunload", true, "body"),
new AttrD("onvolumechanged", true, "audio","video"),
new AttrD("onwaiting", true, "audio","video"),
//new AttrD("onwheel"), //global attribute
new AttrD("open", false, "details"),
new AttrD("optimum", true, "meter"),
new AttrD("pattern", true, "input"),
new AttrD("placeholder", true, "input","textarea"),
new AttrD("poster", true, "video"),
new AttrD("preload", true, "audio","video"),
new AttrD("readonly", false, "input","textarea"),
new AttrD("rel", true, "a","area","form","link"),
new AttrD("required", false, "input","select","textarea"),
new AttrD("reversed", false, "ol"),
new AttrD("rows", true, "textarea"),
new AttrD("rowspan", true, "td","th"),
new AttrD("sandbox", false, "iframe"),
new AttrD("scope", true, "th"),
new AttrD("selected", false, "option"),
new AttrD("shape", true, "area"),
new AttrD("size", true, "input","select"),
new AttrD("sizes", true, "img","link","source"),
new AttrD("span", true, "col","colgroup"),
//new AttrD("spellcheck"), //global attribute
new AttrD("src", true, "audio","embed","iframe","img","input","script","source","track","video"),
new AttrD("srcdoc", true, "iframe"),
new AttrD("srclang", true, "track"),
new AttrD("srcset", true, "img","source"),
new AttrD("start", true, "ol"),
new AttrD("step", true, "input"),
//new AttrD("style"), //global attribute
//new AttrD("tabindex"), //global attribute
new AttrD("target", true, "a","area","base","form"),
//new AttrD("title"), //global attribute
//new AttrD("translate"),// global attribute
new AttrD("type", true, "a","button","embed","input","link","menu","object","script","source","style"),
new AttrD("usemap", true, "img","object"),
new AttrD("value", true, "button","input","li","option","meter","progress","param"),
new AttrD("width", true, "canvas","embed","iframe","img","input","object","video"),
new AttrD("wrap", true, "textarea")
);
}
}