diff --git a/WORKSPACE b/WORKSPACE index 4a8e7664..c0a0edeb 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -53,6 +53,22 @@ http_file( url = "https://github.com/JasonSteving99/claro-lang/releases/download/v0.1.271/claro-cli-install.tar.gz", ) +# ClaroDocs is built atop Google's Closure Templates in order to ensure that I'm not generating unsafe html since the +# intention is for users to be able to trust and host ClaroDocs themselves (particularly relevant since ClaroDocs +# automatically generate inlined docs for all of the binaries dependencies, whether first or 3rd party). +http_archive( + name = "io_bazel_rules_closure", + sha256 = "9498e57368efb82b985db1ed426a767cbf1ba0398fd7aed632fc3908654e1b1e", + strip_prefix = "rules_closure-0.12.0", + urls = [ + "https://github.com/bazelbuild/rules_closure/archive/0.12.0.tar.gz", + ], +) + +load("@io_bazel_rules_closure//closure:repositories.bzl", "rules_closure_dependencies", "rules_closure_toolchains") +rules_closure_dependencies() +rules_closure_toolchains() + # See this documentation to understand how fetching Maven deps works in Bazel: # https://github.com/bazelbuild/rules_jvm_external # When you add a new maven dep run the following command to update new deps: diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/BUILD b/src/java/com/claro/module_system/clarodocs/html_rendering/BUILD index 6b8e31aa..ac20abb6 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/BUILD +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/BUILD @@ -1,6 +1,21 @@ +load("@io_bazel_rules_closure//closure:defs.bzl", "closure_java_template_library") + +closure_java_template_library( + name = "html_soy", + srcs = [ + "code_block.soy", + "procedures.soy", + "tokens.soy", + "types.soy", + "utils.soy", + ], + java_package = "com.claro.module_system.clarodocs.html_rendering", + visibility = ["//src/java/com/claro/module_system/clarodocs/html_rendering:__subpackages__"], +) java_library( name = "util", srcs = ["Util.java"], + deps = [":html_soy"], visibility = ["//src/java/com/claro/module_system/clarodocs/html_rendering:__subpackages__"], ) \ No newline at end of file diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/Util.java b/src/java/com/claro/module_system/clarodocs/html_rendering/Util.java index 737edcdd..9e206a18 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/Util.java +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/Util.java @@ -1,11 +1,25 @@ package com.claro.module_system.clarodocs.html_rendering; +import com.google.template.soy.SoyFileSet; +import com.google.template.soy.tofu.SoyTofu; + import java.util.Arrays; import java.util.stream.Collectors; import static com.claro.module_system.clarodocs.html_rendering.Util.CssClass.*; +// TODO(steving) Migrate to pre-compiled SoySauce templates instead of slower (hence deprecated) SoyTofu templates. +@SuppressWarnings("deprecation") public class Util { + public static final SoyTofu SOY = + SoyFileSet.builder() + .add(Util.class.getResource("code_block.soy")) + .add(Util.class.getResource("procedures.soy")) + .add(Util.class.getResource("tokens.soy")) + .add(Util.class.getResource("types.soy")) + .add(Util.class.getResource("utils.soy")) + .build() + .compileToTofu(); public enum CssClass { TOKEN_GROUP_1("tokenGroup1"), diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/aliases/AliasHtml.java b/src/java/com/claro/module_system/clarodocs/html_rendering/aliases/AliasHtml.java index b0de7ef0..3633b9ff 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/aliases/AliasHtml.java +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/aliases/AliasHtml.java @@ -17,7 +17,7 @@ public static void renderAliasHtml( res.append(String.format( Util.wrapAsDefaultCodeBlock(ALIAS_DEF_CLASS_NAME, aliasName, ALIAS_DEF_TEMPLATE), aliasName, - TypeHtml.renderType(new StringBuilder(), wrappedType) + TypeHtml.renderTypeHtml(wrappedType) )); } } diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/code_block.soy b/src/java/com/claro/module_system/clarodocs/html_rendering/code_block.soy new file mode 100644 index 00000000..a23a6461 --- /dev/null +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/code_block.soy @@ -0,0 +1,13 @@ +{namespace codeblock} + + +{template .code} + {@param codeContent: html} + {@param class: string} + {@param id: string} +
+    
+        {$codeContent}
+    
+  
+{/template} \ No newline at end of file diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/contracts/ContractHtml.java b/src/java/com/claro/module_system/clarodocs/html_rendering/contracts/ContractHtml.java index eefbcc52..d793df3b 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/contracts/ContractHtml.java +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/contracts/ContractHtml.java @@ -25,7 +25,7 @@ public static void renderContractDefHtml( contractDef.getName().substring(0, contractDef.getName().indexOf('$')), Joiner.on(", ").join(contractDef.getTypeParamNamesList()), contractDef.getSignaturesList().stream() - .map(proc -> ProcedureHtml.generateProcedureHtmlWithIndentationLevel(proc, 1)) + .map(ProcedureHtml::generateProcedureHtml) .collect(Collectors.joining("\n")) )); } @@ -33,16 +33,11 @@ public static void renderContractDefHtml( public static void renderContractImplHtml( StringBuilder res, SerializedClaroModule.ExportedContractImplementation contractImpl) { String implementedContractName = contractImpl.getImplementedContractName(); - StringBuilder placeholder = new StringBuilder(); res.append(String.format( Util.wrapAsDefaultCodeBlock(CONTRACT_IMPL_CLASS_NAME, implementedContractName, CONTRACT_IMPL_TEMPLATE), implementedContractName.substring(0, implementedContractName.indexOf('$')), contractImpl.getConcreteTypeParamsList().stream() - .map(t -> { - String typeHtml = TypeHtml.renderType(placeholder, Types.parseTypeProto(t)).toString(); - placeholder.setLength(0); - return typeHtml; - }) + .map(t -> TypeHtml.renderTypeHtml(Types.parseTypeProto(t)).toString()) .collect(Collectors.joining(", ")) )); } diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/homepage/HomePageHtml.java b/src/java/com/claro/module_system/clarodocs/html_rendering/homepage/HomePageHtml.java index cf700eb3..fabc4f6e 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/homepage/HomePageHtml.java +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/homepage/HomePageHtml.java @@ -63,6 +63,27 @@ public static String renderHomePage( " color: #f8f8f2;\n" + " overflow-x: scroll;\n" + "}\n" + + ".procedure-def {\n" + + " margin-left: 30px;" + + "}\n" + + ".initializers {\n" + + " margin-left: 30px;" + + "}\n" + + ".unwrappers {\n" + + " margin-left: 30px;" + + "}\n" + + ".contract-def {\n" + + " margin-left: 30px;" + + "}\n" + + ".initializers .procedure-def {\n" + + " margin-left: 60px;" + + "}\n" + + ".unwrappers .procedure-def {\n" + + " margin-left: 60px;" + + "}\n" + + ".contract-def .procedure-def {\n" + + " margin-left: 60px;" + + "}\n" + "\n" + ".tj_container {\n" + " height: 100%;\n" + diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/initializers/BUILD b/src/java/com/claro/module_system/clarodocs/html_rendering/initializers/BUILD index ca210c4d..ede0e7db 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/initializers/BUILD +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/initializers/BUILD @@ -3,6 +3,8 @@ java_library( name = "initializers", srcs = ["InitializersHtml.java"], deps = [ + "//:guava", + "//src/java/com/claro/module_system/clarodocs/html_rendering:html_soy", "//src/java/com/claro/module_system/clarodocs/html_rendering:util", "//src/java/com/claro/module_system/clarodocs/html_rendering/procedures:procedure_html", "//src/java/com/claro/module_system/module_serialization/proto:serialized_claro_module_java_proto", diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/initializers/InitializersHtml.java b/src/java/com/claro/module_system/clarodocs/html_rendering/initializers/InitializersHtml.java index 0af234e7..e1576f95 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/initializers/InitializersHtml.java +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/initializers/InitializersHtml.java @@ -3,12 +3,17 @@ import com.claro.module_system.clarodocs.html_rendering.Util; import com.claro.module_system.clarodocs.html_rendering.procedures.ProcedureHtml; import com.claro.module_system.module_serialization.proto.SerializedClaroModule; +import com.google.template.soy.tofu.SoyTofu; import java.util.stream.Collectors; import static com.claro.module_system.clarodocs.html_rendering.Util.GrammarPart.INITIALIZERS; +// TODO(steving) Migrate to pre-compiled SoySauce templates instead of slower (hence deprecated) SoyTofu templates. +@SuppressWarnings("deprecation") public class InitializersHtml { + private static final SoyTofu.Renderer INITIALIZERS_TEMPLATE = + Util.SOY.newRenderer("initializers.initializers"); public static final String INITIALIZERS_CLASS = "initializers"; public static final String INITIALIZERS_BLOCK_TEMPLATE = INITIALIZERS + " %s {\n%s\n}"; @@ -25,7 +30,7 @@ public static void renderInitializersBlock( INITIALIZERS_BLOCK_TEMPLATE, initializedTypeName, procedures.getProceduresList().stream() - .map(procedure -> ProcedureHtml.generateProcedureHtmlWithIndentationLevel(procedure, 1)) + .map(ProcedureHtml::generateProcedureHtml) .collect(Collectors.joining("\n")) ) )); diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/procedures.soy b/src/java/com/claro/module_system/clarodocs/html_rendering/procedures.soy new file mode 100644 index 00000000..0dbbdfd8 --- /dev/null +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/procedures.soy @@ -0,0 +1,87 @@ +{namespace procedures} + + +{template .exportedProcedure} + {@param name: string} + {@param? requiredContracts: list<[contractName: string, genericTypeParams: list]>} + {@param? genericTypeParams: list} + {@param? argTypes: list} + {@param? outputType: html} + + {call codeblock.code} + {param codeContent kind="html"} + {call .procedureSignature data="all" /} + {/param} + {param class kind="text"}{call .procedureDefClass /}{/param} + {param id: $name /} + {/call} +{/template} + +{template .procedureDefClass kind="text" visibility="private"} + procedure-def +{/template} + +{template .requires visibility="private"} + {@param requiredContracts: list<[contractName: string, genericTypeParams: list]>} + {call tokens.REQUIRES /}( + {for $contract in $requiredContracts} + {$contract.contractName}{call tokens.LT /} + {for $genericTypeParam, $i in $contract.genericTypeParams} + {if $i > 0}, {/if} // Comma after all but first. + {$genericTypeParam} + {/for} + {call tokens.GT /} + {/for} + ) +{/template} + +{template .args visibility="private"} + {@param argTypes: list} + {for $arg, $i in $argTypes} + {if $i > 0}, {/if} + arg{$i}{call tokens.COLON /} {$arg} + {/for} +{/template} + +{template .procedureSignature visibility="private"} + {@param name: string} + {@param? requiredContracts: list<[contractName: string, genericTypeParams: list]>} + {@param? genericTypeParams: list} + {@param? argTypes: list} + {@param? outputType: html} + + {if $requiredContracts} + {call .requires} + {param requiredContracts: $requiredContracts /} + {/call}
+ {/if} + {if $argTypes} + {if $outputType} + {call tokens.FUNCTION /} + {else} + {call tokens.CONSUMER /} + {/if} + {else} + {call tokens.PROVIDER /} + {/if} + {sp}{$name} + {if $genericTypeParams} + {call tokens.LT /} + {for $genericTypeParam, $i in $genericTypeParams} + {if $i > 0}, {/if} + {$genericTypeParam} + {/for} + {call tokens.GT /} + {/if} + ( + {if $argTypes} + {call .args} + {param argTypes: $argTypes /} + {/call} + {/if} + ) + {if $outputType} + {sp}{call tokens.ARROW /}{sp}{$outputType} + {/if} + {call tokens.SEMICOLON /} +{/template} \ No newline at end of file diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/procedures/BUILD b/src/java/com/claro/module_system/clarodocs/html_rendering/procedures/BUILD index db5f083d..24b4a2fc 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/procedures/BUILD +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/procedures/BUILD @@ -3,7 +3,9 @@ java_library( name = "procedure_html", srcs = ["ProcedureHtml.java"], deps = [ + "//:guava", "//src/java/com/claro/intermediate_representation/types:types", + "//src/java/com/claro/module_system/clarodocs/html_rendering:html_soy", "//src/java/com/claro/module_system/clarodocs/html_rendering:util", "//src/java/com/claro/module_system/clarodocs/html_rendering/typedefs:type_html", "//src/java/com/claro/module_system/module_serialization/proto:serialized_claro_module_java_proto", diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/procedures/ProcedureHtml.java b/src/java/com/claro/module_system/clarodocs/html_rendering/procedures/ProcedureHtml.java index 2b676a01..96cbdf5f 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/procedures/ProcedureHtml.java +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/procedures/ProcedureHtml.java @@ -5,101 +5,90 @@ import com.claro.module_system.clarodocs.html_rendering.typedefs.TypeHtml; import com.claro.module_system.module_serialization.proto.SerializedClaroModule; import com.claro.module_system.module_serialization.proto.claro_types.TypeProtos; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.template.soy.tofu.SoyTofu; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static com.claro.module_system.clarodocs.html_rendering.Util.GrammarPart.*; - +// TODO(steving) Migrate to pre-compiled SoySauce templates instead of slower (hence deprecated) SoyTofu templates. +@SuppressWarnings("deprecation") public class ProcedureHtml { - public static String generateProcedureHtml(SerializedClaroModule.Procedure procedure) { - return generateProcedureHtml(procedure, 0); - } - - public static String generateProcedureHtmlWithIndentationLevel( - SerializedClaroModule.Procedure procedure, int indentationLevel) { - return generateProcedureHtml(procedure, indentationLevel); - } + private static final SoyTofu.Renderer PROCEDURES_TEMPLATE = + Util.SOY.newRenderer("procedures.exportedProcedure"); - private static String generateProcedureHtml(SerializedClaroModule.Procedure procedure, int indentationLevel) { + public static String generateProcedureHtml(SerializedClaroModule.Procedure procedure) { switch (procedure.getProcedureTypeCase()) { case FUNCTION: - return renderFunction(procedure.getName(), procedure.getFunction(), indentationLevel); + return renderFunction(procedure.getName(), procedure.getFunction()); case CONSUMER: - return renderConsumer(procedure.getName(), procedure.getConsumer(), indentationLevel); + return renderConsumer(procedure.getName(), procedure.getConsumer()); case PROVIDER: - return renderProvider(procedure.getName(), procedure.getProvider(), indentationLevel); + return renderProvider(procedure.getName(), procedure.getProvider()); default: throw new RuntimeException("Internal ClaroDocs Error! Unexpected procedure type case:\n" + procedure); } } - private static final String PROCEDURE_DEF_CLASS = "procedure-def"; - private static final String FUNCTION_TEMPLATE = "%s\n" + FUNCTION + " %s%s(%s) " + ARROW + " %s" + SEMICOLON; - private static final String CONSUMER_TEMPLATE = "%s\n" + CONSUMER + " %s%s(%s)" + SEMICOLON; - private static final String PROVIDER_TEMPLATE = "%s\n" + PROVIDER + " %s%s() " + ARROW + " %s" + SEMICOLON; - - public static String renderFunction(String name, TypeProtos.FunctionType function, int indentationLevel) { - return String.format( - Util.wrapAsDefaultCodeBlockWithIndentationLevel(PROCEDURE_DEF_CLASS, name, FUNCTION_TEMPLATE, indentationLevel), - renderRequiresClause(function.getRequiredContractsList()), - name, - renderGenericTypeParams(function.getOptionalGenericTypeParamNamesList()), - renderArgs(function.getArgTypesList()), - TypeHtml.renderType(new StringBuilder(), Types.parseTypeProto(function.getOutputType())) - ); + public static String renderFunction(String name, TypeProtos.FunctionType function) { + ImmutableMap.Builder args = ImmutableMap.builder(); + args.put("name", name) + .put( + "argTypes", + function.getArgTypesList().stream() + .map(Types::parseTypeProto) + .map(TypeHtml::renderTypeHtml) + .collect(ImmutableList.toImmutableList()) + ) + .put("outputType", TypeHtml.renderTypeHtml(Types.parseTypeProto(function.getOutputType()))); + setOptionalRequiredContracts(function.getRequiredContractsList(), args); + setOptionalGenericTypeParams(ImmutableList.copyOf(function.getOptionalGenericTypeParamNamesList()), args); + return PROCEDURES_TEMPLATE.setData(args.build()).render(); } - public static String renderConsumer(String name, TypeProtos.ConsumerType consumer, int indentationLevel) { - return String.format( - Util.wrapAsDefaultCodeBlockWithIndentationLevel(PROCEDURE_DEF_CLASS, name, CONSUMER_TEMPLATE, indentationLevel), - renderRequiresClause(consumer.getRequiredContractsList()), - name, - renderGenericTypeParams(consumer.getOptionalGenericTypeParamNamesList()), - renderArgs(consumer.getArgTypesList()) - ); + public static String renderConsumer(String name, TypeProtos.ConsumerType consumer) { + ImmutableMap.Builder args = ImmutableMap.builder(); + args.put("name", name) + .put( + "argTypes", + consumer.getArgTypesList().stream() + .map(Types::parseTypeProto) + .map(TypeHtml::renderTypeHtml) + .collect(ImmutableList.toImmutableList()) + ); + setOptionalRequiredContracts(consumer.getRequiredContractsList(), args); + setOptionalGenericTypeParams(ImmutableList.copyOf(consumer.getOptionalGenericTypeParamNamesList()), args); + return PROCEDURES_TEMPLATE.setData(args.build()).render(); } - public static String renderProvider(String name, TypeProtos.ProviderType provider, int indentationLevel) { - return String.format( - Util.wrapAsDefaultCodeBlockWithIndentationLevel(PROCEDURE_DEF_CLASS, name, PROVIDER_TEMPLATE, indentationLevel), - renderRequiresClause(provider.getRequiredContractsList()), - name, - renderGenericTypeParams(provider.getOptionalGenericTypeParamNamesList()), - TypeHtml.renderType(new StringBuilder(), Types.parseTypeProto(provider.getOutputType())) - ); + public static String renderProvider(String name, TypeProtos.ProviderType provider) { + ImmutableMap.Builder args = ImmutableMap.builder(); + args.put("name", name) + .put("outputType", TypeHtml.renderTypeHtml(Types.parseTypeProto(provider.getOutputType()))); + setOptionalRequiredContracts(provider.getRequiredContractsList(), args); + setOptionalGenericTypeParams(ImmutableList.copyOf(provider.getOptionalGenericTypeParamNamesList()), args); + return PROCEDURES_TEMPLATE.setData(args.build()).render(); } - private static String renderRequiresClause(List requiredContracts) { - if (requiredContracts.size() == 0) { - return ""; + private static void setOptionalGenericTypeParams( + List genericTypeParamNames, ImmutableMap.Builder args) { + if (genericTypeParamNames.size() > 0) { + args.put("genericTypeParams", genericTypeParamNames); } - return " requires(" + - requiredContracts.stream() - .map(req -> String.format( - "%s" + LT + "%s" + GT, req.getName(), String.join(", ", req.getGenericTypeParamsList()))) - .collect(Collectors.joining(", ")) + - ")\n"; } - private static String renderArgs(List argTypeProtos) { - StringBuilder placeholder = new StringBuilder(); - return IntStream.range(0, argTypeProtos.size()).boxed() - .map(i -> { - String fmt = String.format( - "arg%s: %s", i, TypeHtml.renderType(placeholder, Types.parseTypeProto(argTypeProtos.get(i)))); - placeholder.setLength(0); - return fmt; - }) - .collect(Collectors.joining(", ")); - } - - private static Object renderGenericTypeParams(List genericTypeParams) { - return genericTypeParams.isEmpty() - ? "" - : genericTypeParams.stream() - .collect(Collectors.joining(", ", LT.toString(), GT.toString())); + private static void setOptionalRequiredContracts( + List requiredContracts, ImmutableMap.Builder args) { + if (requiredContracts.size() > 0) { + args.put( + "requiredContracts", + requiredContracts.stream() + .map( + r -> ImmutableMap.of( + "contractName", r.getName(), + "genericTypeParams", ImmutableList.copyOf(r.getGenericTypeParamsList()) + )).collect(ImmutableList.toImmutableList()) + ); + } } } diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/tokens.soy b/src/java/com/claro/module_system/clarodocs/html_rendering/tokens.soy new file mode 100644 index 00000000..d8a336d6 --- /dev/null +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/tokens.soy @@ -0,0 +1,253 @@ +{namespace tokens} + + +{template .highlight} + {@param token: string} + {@param groupNum: int} + {let $tokenGroup kind="text"} + {call .tokenGroup} + {param groupNum: $groupNum /} + {/call} + {/let} + {$token} +{/template} + +{template .tokenGroup kind="text"} + {@param groupNum: int} + tokenGroup{$groupNum} +{/template} + +{template .highlightGroup1} + {@param token: string} + {call .highlight} + {param token: $token /} + {param groupNum: 1 /} + {/call} +{/template} + +{template .highlightGroup2} + {@param token: string} + {call .highlight} + {param token: $token /} + {param groupNum: 2 /} + {/call} +{/template} + +{template .highlightGroup3} + {@param token: string} + {call .highlight} + {param token: $token /} + {param groupNum: 3 /} + {/call} +{/template} + +{template .highlightGroup4} + {@param token: string} + {call .highlight} + {param token: $token /} + {param groupNum: 4 /} + {/call} +{/template} + +// GROUP 1 + +{template .ALIAS} + {call .highlightGroup1} + {param token: 'alias' /} + {/call} +{/template} + +{template .ATOM} + {call .highlightGroup1} + {param token: 'atom' /} + {/call} +{/template} + +{template .BLOCKING} + {call .highlightGroup1} + {param token: 'blocking' /} + {/call} +{/template} + +{template .FUNCTION} + {call .highlightGroup1} + {param token: 'function' /} + {/call} +{/template} + +{template .CONSUMER} + {call .highlightGroup1} + {param token: 'consumer' /} + {/call} +{/template} + +{template .PROVIDER} + {call .highlightGroup1} + {param token: 'provider' /} + {/call} +{/template} + +{template .GRAPH} + {call .highlightGroup1} + {param token: 'graph' /} + {/call} +{/template} + +{template .NEWTYPE} + {call .highlightGroup1} + {param token: 'newtype' /} + {/call} +{/template} + + +// GROUP 2 + +{template .COMMA} + {call .highlightGroup2} + {param token: ',' /} + {/call} +{/template} + +{template .ARROW} + {call .highlightGroup2} + {param token: '->' /} + {/call} +{/template} + +{template .COLON} + {call .highlightGroup2} + {param token: ':' /} + {/call} +{/template} + +{template .SEMICOLON} + {call .highlightGroup2} + {param token: ';' /} + {/call} +{/template} + +{template .LT} + {call .highlightGroup2} + {param token: '<' /} + {/call} +{/template} + +{template .GT} + {call .highlightGroup2} + {param token: '>' /} + {/call} +{/template} + +{template .CONTRACT} + {call .highlightGroup2} + {param token: 'contract' /} + {/call} +{/template} + +{template .ENDPOINT_HANDLERS} + {call .highlightGroup2} + {param token: 'endpoint_handlers' /} + {/call} +{/template} + +{template .IMPLEMENT} + {call .highlightGroup2} + {param token: 'implement' /} + {/call} +{/template} + +{template .INITIALIZERS} + {call .highlightGroup2} + {param token: 'initializers' /} + {/call} +{/template} + +{template .UNWRAPPERS} + {call .highlightGroup2} + {param token: 'unwrappers' /} + {/call} +{/template} + +{template .MUT} + {call .highlightGroup2} + {param token: 'mut' /} + {/call} +{/template} + +{template .REQUIRES} + {call .highlightGroup2} + {param token: 'requires' /} + {/call} +{/template} + + +// GROUP 3 + +{template .UNDERSCORE} + {call .highlightGroup3} + {param token: '_' /} + {/call} +{/template} + +{template .BOOLEAN} + {call .highlightGroup3} + {param token: 'boolean' /} + {/call} +{/template} + +{template .FLOAT} + {call .highlightGroup3} + {param token: 'float' /} + {/call} +{/template} + +{template .FUTURE} + {call .highlightGroup3} + {param token: 'future' /} + {/call} +{/template} + +{template .INT} + {call .highlightGroup3} + {param token: 'int' /} + {/call} +{/template} + +{template .ONEOF} + {call .highlightGroup3} + {param token: 'oneof' /} + {/call} +{/template} + +{template .STRING} + {call .highlightGroup3} + {param token: 'string' /} + {/call} +{/template} + +{template .STRUCT} + {call .highlightGroup3} + {param token: 'struct' /} + {/call} +{/template} + +{template .TUPLE} + {call .highlightGroup3} + {param token: 'tuple' /} + {/call} +{/template} + + +// GROUP 4 + +{template .QUESTION_MARK} + {call .highlightGroup3} + {param token: '?' /} + {/call} +{/template} + +{template .BAR} + {call .highlightGroup3} + {param token: '|' /} + {/call} +{/template} diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/typedefs/BUILD b/src/java/com/claro/module_system/clarodocs/html_rendering/typedefs/BUILD index c90cd5a7..2884ba1b 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/typedefs/BUILD +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/typedefs/BUILD @@ -7,6 +7,7 @@ java_library( "//src/java/com/claro/intermediate_representation/types:type", "//src/java/com/claro/intermediate_representation/types:types", "//src/java/com/claro/intermediate_representation/types:supports_mutable_variant", + "//src/java/com/claro/module_system/clarodocs/html_rendering:html_soy", "//src/java/com/claro/module_system/clarodocs/html_rendering:util", "//src/java/com/claro/module_system/module_serialization/proto:serialized_claro_module_java_proto", ], diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/typedefs/TypeHtml.java b/src/java/com/claro/module_system/clarodocs/html_rendering/typedefs/TypeHtml.java index 923c296a..37dcd4ed 100644 --- a/src/java/com/claro/module_system/clarodocs/html_rendering/typedefs/TypeHtml.java +++ b/src/java/com/claro/module_system/clarodocs/html_rendering/typedefs/TypeHtml.java @@ -1,17 +1,19 @@ package com.claro.module_system.clarodocs.html_rendering.typedefs; -import com.claro.intermediate_representation.types.SupportsMutableVariant; import com.claro.intermediate_representation.types.Type; import com.claro.intermediate_representation.types.Types; +import com.claro.module_system.clarodocs.html_rendering.Util; import com.claro.module_system.module_serialization.proto.SerializedClaroModule; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.template.soy.data.SanitizedContent; +import com.google.template.soy.tofu.SoyTofu; import java.util.stream.Collectors; -import java.util.stream.IntStream; import static com.claro.module_system.clarodocs.html_rendering.Util.GrammarPart.*; public class TypeHtml { + private static final SoyTofu SOY = Util.SOY.forNamespace("types"); private static final String TYPEDEF_TEMPLATE = "
\n" +
       "  \n" +
@@ -29,171 +31,162 @@ public static StringBuilder renderTypeDef(
             newTypeDef.getTypeParamNamesCount() == 0
             ? ""
             : String.format("%s%s%s", LT, String.join(", ", newTypeDef.getTypeParamNamesList()), GT),
-            renderType(new StringBuilder(), Types.parseTypeProto(newTypeDef.getWrappedType()))
+            renderTypeHtml(Types.parseTypeProto(newTypeDef.getWrappedType()))
         ));
     return res;
   }
 
-  public static StringBuilder renderType(StringBuilder res, Type type) {
-    StringBuilder placeholder = new StringBuilder();
+  public static SanitizedContent renderTypeHtml(Type type) {
     switch (type.baseType()) {
       case ATOM:
-        res.append(((Types.AtomType) type).getName());
-        break;
+        return renderSoy("atom", ImmutableMap.of("name", ((Types.AtomType) type).getName()));
       case INTEGER:
-        res.append(INT);
-        break;
+        return renderToken("INT");
       case FLOAT:
-        res.append(FLOAT);
-        break;
+        return renderToken("FLOAT");
       case BOOLEAN:
-        res.append(BOOLEAN);
-        break;
+        return renderToken("BOOLEAN");
       case STRING:
-        res.append(STRING);
-        break;
+        return renderToken("STRING");
       case LIST:
         Types.ListType listType = (Types.ListType) type;
-        maybeRenderMut(res, listType).append("[");
-        renderType(res, listType.getElementType())
-            .append("]");
-        break;
+        return renderSoy(
+            "list",
+            ImmutableMap.of(
+                "elemsType", renderTypeHtml(listType.getElementType()),
+                "isMut", listType.isMutable()
+            )
+        );
       case TUPLE:
         Types.TupleType tupleType = (Types.TupleType) type;
-        maybeRenderMut(res, tupleType).append("tuple").append(LT).append(
-                tupleType.getValueTypes().stream()
-                    .map(t -> {
-                      String fmt = renderType(placeholder, t).toString();
-                      placeholder.setLength(0);
-                      return fmt;
-                    }).collect(Collectors.joining(", ")))
-            .append(GT);
-        break;
+        return renderSoy(
+            "tuple",
+            ImmutableMap.of(
+                "elemsTypes", tupleType.getValueTypes()
+                    .stream()
+                    .map(TypeHtml::renderTypeHtml)
+                    .collect(Collectors.toList()),
+                "isMut", tupleType.isMutable()
+            )
+        );
       case SET:
-        maybeRenderMut(res, (Types.SetType) type).append("{");
-        renderType(res, type.parameterizedTypeArgs().get(Types.SetType.PARAMETERIZED_TYPE))
-            .append("}");
-        break;
+        return renderSoy(
+            "set",
+            ImmutableMap.of(
+                "elemsType", renderTypeHtml(type.parameterizedTypeArgs().get(Types.SetType.PARAMETERIZED_TYPE)),
+                "isMut", ((Types.SetType) type).isMutable()
+            )
+        );
       case MAP:
-        maybeRenderMut(res, (Types.MapType) type).append("{");
-        renderType(res, type.parameterizedTypeArgs().get(Types.MapType.PARAMETERIZED_TYPE_KEYS))
-            .append(COLON).append(" ");
-        renderType(res, type.parameterizedTypeArgs().get(Types.MapType.PARAMETERIZED_TYPE_VALUES))
-            .append("}");
-        break;
+        return renderSoy(
+            "map",
+            ImmutableMap.of(
+                "keyType", renderTypeHtml(type.parameterizedTypeArgs().get(Types.MapType.PARAMETERIZED_TYPE_KEYS)),
+                "valueType", renderTypeHtml(type.parameterizedTypeArgs().get(Types.MapType.PARAMETERIZED_TYPE_VALUES)),
+                "isMut", ((Types.MapType) type).isMutable()
+            )
+        );
       case STRUCT:
         Types.StructType structType = (Types.StructType) type;
-        maybeRenderMut(res, structType).append(STRUCT).append("{");
-        res.append(
-            IntStream.range(0, structType.getFieldTypes().size()).boxed()
-                .map(i -> {
-                  String fmt = String.format(
-                      "%s: %s",
-                      structType.getFieldNames().get(i),
-                      renderType(placeholder, structType.getFieldTypes().get(i)).toString()
-                  );
-                  placeholder.setLength(0);
-                  return fmt;
-                })
-                .collect(Collectors.joining(", ")));
-        res.append("}");
-        break;
+        return renderSoy(
+            "struct",
+            ImmutableMap.builder()
+                .put("fieldNames", structType.getFieldNames())
+                .put(
+                    "fieldTypes",
+                    structType.getFieldTypes()
+                        .stream()
+                        .map(TypeHtml::renderTypeHtml)
+                        .collect(Collectors.toList())
+                ).put("isMut", ((Types.StructType) type).isMutable()).build()
+        );
       case ONEOF:
-        res.append(ONEOF).append(LT).append(
-                ((Types.OneofType) type).getVariantTypes().stream()
-                    .map(t -> {
-                      String fmt = renderType(placeholder, t).toString();
-                      placeholder.setLength(0);
-                      return fmt;
-                    })
-                    .collect(Collectors.joining(", ")))
-            .append(GT);
-        break;
+        return renderSoy(
+            "oneof",
+            ImmutableMap.of(
+                "variantTypes",
+                ((Types.OneofType) type).getVariantTypes()
+                    .stream()
+                    .map(TypeHtml::renderTypeHtml)
+                    .collect(Collectors.toList())
+            )
+        );
       case FUNCTION:
         Types.ProcedureType procedureType = (Types.ProcedureType) type;
-        res.append(FUNCTION).append(LT);
-        renderProcedureTypeArgs(res, placeholder, procedureType.getArgTypes())
-            .append(ARROW).append(" ");
-        renderType(res, procedureType.getReturnType()).append(GT);
-        break;
+        return renderSoy(
+            "function",
+            ImmutableMap.of(
+                "argTypes",
+                procedureType.getArgTypes().stream().map(TypeHtml::renderTypeHtml).collect(Collectors.toList()),
+                "outputType", renderTypeHtml(procedureType.getReturnType())
+            )
+        );
       case CONSUMER_FUNCTION:
         procedureType = (Types.ProcedureType) type;
-        res.append(CONSUMER).append(LT);
-        renderProcedureTypeArgs(res, placeholder, procedureType.getArgTypes()).append(GT);
-        break;
+        return renderSoy(
+            "consumer",
+            ImmutableMap.of(
+                "argTypes",
+                procedureType.getArgTypes().stream().map(TypeHtml::renderTypeHtml).collect(Collectors.toList())
+            )
+        );
       case PROVIDER_FUNCTION:
         procedureType = (Types.ProcedureType) type;
-        res.append(PROVIDER).append(LT);
-        renderType(res, procedureType.getReturnType()).append(GT);
-        break;
+        return renderSoy(
+            "provider",
+            ImmutableMap.of("outputType", renderTypeHtml(procedureType.getReturnType())
+            )
+        );
       case FUTURE:
-        res.append(FUTURE).append(LT);
-        renderType(res, type.parameterizedTypeArgs().get(Types.FutureType.PARAMETERIZED_TYPE_KEY))
-            .append(GT);
-        break;
+        return renderSoy(
+            "future",
+            ImmutableMap.of(
+                "wrappedType",
+                renderTypeHtml(type.parameterizedTypeArgs().get(Types.FutureType.PARAMETERIZED_TYPE_KEY))
+            )
+        );
       case USER_DEFINED_TYPE:
         Types.UserDefinedType userDefinedType = (Types.UserDefinedType) type;
-        res.append("")
-            .append(userDefinedType.getTypeName())
-            .append("");
-        if (!type.parameterizedTypeArgs().isEmpty()) {
-          res.append(
-              type.parameterizedTypeArgs().values().stream()
-                  .map(t -> {
-                    String fmt = renderType(placeholder, t).toString();
-                    placeholder.setLength(0);
-                    return fmt;
-                  }).collect(Collectors.joining(", ", LT.toString(), GT.toString())));
+        ImmutableMap.Builder args =
+            ImmutableMap.builder()
+                .put("typeName", userDefinedType.getTypeName())
+                .put("definingModuleDisambig", userDefinedType.getDefiningModuleDisambiguator());
+        if (!userDefinedType.parameterizedTypeArgs().isEmpty()) {
+          args.put(
+              "concreteTypeParams",
+              userDefinedType.parameterizedTypeArgs().values().stream()
+                  .map(TypeHtml::renderTypeHtml)
+                  .collect(Collectors.toList())
+          );
         }
-        break;
+        return renderSoy("userDefinedType", args.build());
       case HTTP_SERVICE:
-        res.append("HttpService").append(LT).append(((Types.HttpServiceType) type).getServiceName()).append(GT);
-        break;
-      case HTTP_SERVER:
-        res.append("HttpServer").append(LT);
-        renderType(res, type.parameterizedTypeArgs().get(Types.HttpServerType.HTTP_SERVICE_TYPE)).append(GT);
-        break;
+        return renderSoy(
+            "httpService",
+            ImmutableMap.of("serviceName", ((Types.HttpServiceType) type).getServiceName())
+        );
       case HTTP_CLIENT:
-        res.append("HttpClient").append(LT).append(((Types.HttpClientType) type).getServiceName()).append(GT);
-        break;
+        return renderSoy(
+            "httpClient",
+            ImmutableMap.of("serviceName", ((Types.HttpClientType) type).getServiceName())
+        );
       case HTTP_RESPONSE:
-        res.append("HttpResponse");
-        break;
+        return renderSoy("httpResponse", ImmutableMap.of());
       case $GENERIC_TYPE_PARAM:
-        res.append(((Types.$GenericTypeParam) type).getTypeParamName());
-        break;
+        return renderSoy(
+            "genericTypeParam",
+            ImmutableMap.of("paramName", ((Types.$GenericTypeParam) type).getTypeParamName())
+        );
       default:
         throw new RuntimeException("Internal ClaroDocs Error! Attempt to render unknown type: " + type);
     }
-    return res;
   }
 
-  private static StringBuilder maybeRenderMut(StringBuilder res, SupportsMutableVariant type) {
-    if (type.isMutable()) {
-      res.append(MUT).append(" ");
-    }
-    return res;
+  public static SanitizedContent renderToken(String templateName) {
+    return Util.SOY.newRenderer("tokens." + templateName).renderHtml();
   }
 
-  private static StringBuilder renderProcedureTypeArgs(
-      StringBuilder res, StringBuilder placeholder, ImmutableList args) {
-    if (args.size() == 1) {
-      return renderType(res, args.get(0));
-    }
-    return res.append(BAR).append(
-        args.stream()
-            .map(t -> {
-              String fmt = renderType(placeholder, t).toString();
-              placeholder.setLength(0);
-              return fmt;
-            })
-            .collect(Collectors.joining(", "))
-    ).append(BAR);
+  public static SanitizedContent renderSoy(String templateName, ImmutableMap args) {
+    return SOY.newRenderer("." + templateName).setData(args).renderHtml();
   }
 }
diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/types.soy b/src/java/com/claro/module_system/clarodocs/html_rendering/types.soy
new file mode 100644
index 00000000..66d961b0
--- /dev/null
+++ b/src/java/com/claro/module_system/clarodocs/html_rendering/types.soy
@@ -0,0 +1,167 @@
+{namespace types}
+
+{template .atom}
+    {@param name: string}
+    {$name}
+{/template}
+
+{template .list}
+    {@param elemsType: html}
+    {@param isMut: bool}
+    {call .maybeMut data="all" /}
+    [{$elemsType}]
+{/template}
+
+{template .tuple}
+    {@param elemsTypes: list}
+    {@param isMut: bool}
+    {call .maybeMut data="all" /}
+    {call tokens.TUPLE /}{call tokens.LT /}
+    {call utils.commaSep}
+        {param elems: $elemsTypes /}
+    {/call}
+    {call tokens.GT /}
+{/template}
+
+{template .set}
+    {@param elemsType: html}
+    {@param isMut: bool}
+    {call .maybeMut data="all" /}
+    {'{'}{$elemsType}{'}'}
+{/template}
+
+{template .map}
+    {@param keyType: html}
+    {@param valueType: html}
+    {@param isMut: bool}
+    {call .maybeMut data="all" /}
+    {'{'}{$keyType}{call tokens.COLON /} {$valueType}{'}'}
+{/template}
+
+{template .struct}
+    {@param fieldNames: list}
+    {@param fieldTypes: list}
+    {@param isMut: bool}
+    {call .maybeMut data="all" /}
+    {call tokens.STRUCT /}{'{'}
+    {for $fieldName, $i in $fieldNames}
+        {if $i > 0}, {/if}
+        {$fieldName}{call tokens.COLON /} {$fieldTypes[$i]}
+    {/for}
+    {'}'}
+{/template}
+
+{template .oneof}
+    {@param variantTypes: list}
+    {call tokens.ONEOF /}{call tokens.LT /}
+    {call utils.commaSep}
+        {param elems: $variantTypes /}
+    {/call}
+    {call tokens.GT /}
+{/template}
+
+{template .barGroup visibility="private"}
+    {@param content: html}
+    {call tokens.BAR /}{$content}{call tokens.BAR /}
+{/template}
+
+{template .function}
+    {@param argTypes: list}
+    {@param outputType: html}
+
+    {call tokens.FUNCTION /}{call tokens.LT /}
+    {let $argsHtml kind="html"}
+        {call utils.commaSep}
+            {param elems: $argTypes /}
+        {/call}
+    {/let}
+    {if length($argTypes) > 1}
+        {call .barGroup}
+            {param content: $argsHtml /}
+        {/call}
+    {else}
+        {$argsHtml}
+    {/if}
+    {sp}{call tokens.ARROW /}{sp}{$outputType}{call tokens.GT /}
+{/template}
+
+{template .consumer}
+    {@param argTypes: list}
+
+    {call tokens.CONSUMER /}{call tokens.LT /}
+    {let $argsHtml kind="html"}
+        {call utils.commaSep}
+            {param elems: $argTypes /}
+        {/call}
+    {/let}
+    {if length($argTypes) > 1}
+        {call .barGroup}
+            {param content: $argsHtml /}
+        {/call}
+    {else}
+        {$argsHtml}
+    {/if}
+    {call tokens.GT /}
+{/template}
+
+{template .provider}
+    {@param outputType: html}
+
+    {call tokens.PROVIDER /}{call tokens.LT /}{$outputType}{call tokens.GT /}
+{/template}
+
+{template .future}
+    {@param wrappedType: html}
+    {call tokens.FUTURE /}{call tokens.LT /}{$wrappedType}{call tokens.GT /}
+{/template}
+
+{template .typeDefiningModuleLink visibility="private"}
+    {@param typeName: string}
+    {@param definingModuleDisambig: string}
+    
+        {$typeName}
+    
+{/template}
+
+{template .userDefinedType}
+    {@param typeName: string}
+    {@param definingModuleDisambig: string}
+    {@param? concreteTypeParams: list}
+    {call .typeDefiningModuleLink data="all"}{/call}
+    {if $concreteTypeParams}
+        {call tokens.LT /}
+        {call utils.commaSep}
+            {param elems: $concreteTypeParams /}
+        {/call}
+        {call tokens.GT /}
+    {/if}
+{/template}
+
+{template .httpService}
+    {@param serviceName: string}
+    HttpService{call tokens.LT /}{$serviceName}{call tokens.GT /}
+{/template}
+
+{template .httpClient}
+    {@param serviceName: string}
+    HttpClient{call tokens.LT /}{$serviceName}{call tokens.GT /}
+{/template}
+
+{template .httpResponse}
+    HttpResponse
+{/template}
+
+{template .genericTypeParam}
+    {@param paramName: string}
+    {$paramName}
+{/template}
+
+{template .maybeMut}
+    {@param isMut: bool}
+    {if $isMut}{call tokens.MUT /}{sp}{/if}
+{/template}
\ No newline at end of file
diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/unwrappers/UnwrappersHtml.java b/src/java/com/claro/module_system/clarodocs/html_rendering/unwrappers/UnwrappersHtml.java
index c9d2afc2..0c202d98 100644
--- a/src/java/com/claro/module_system/clarodocs/html_rendering/unwrappers/UnwrappersHtml.java
+++ b/src/java/com/claro/module_system/clarodocs/html_rendering/unwrappers/UnwrappersHtml.java
@@ -25,7 +25,7 @@ public static void renderUnwrappersBlock(
                 UNWRAPPERS_BLOCK_TEMPLATE,
                 unwrappedTypeName,
                 procedures.getProceduresList().stream()
-                    .map(procedure -> ProcedureHtml.generateProcedureHtmlWithIndentationLevel(procedure, 1))
+                    .map(ProcedureHtml::generateProcedureHtml)
                     .collect(Collectors.joining("\n"))
             )
         ));
diff --git a/src/java/com/claro/module_system/clarodocs/html_rendering/utils.soy b/src/java/com/claro/module_system/clarodocs/html_rendering/utils.soy
new file mode 100644
index 00000000..e6f16fc2
--- /dev/null
+++ b/src/java/com/claro/module_system/clarodocs/html_rendering/utils.soy
@@ -0,0 +1,9 @@
+{namespace utils}
+
+{template .commaSep}
+    {@param elems: list}
+    {for $e, $i in $elems}
+        {if $i > 0}, {/if}
+        {$e}
+    {/for}
+{/template}
\ No newline at end of file