From f034842675ffa9c162ad2fd74872dffa5f0877da Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 30 Jul 2024 01:44:46 +0200 Subject: [PATCH] Enable generating into an empty directory (once we remove the files from git). Using template for config_reference. --- .../config/metadata/docs/CmReference.java | 48 ++++++ .../config/metadata/docs/ConfigDocs.java | 146 ++++++++++++------ .../io/helidon/config/metadata/docs/Main.java | 9 +- .../metadata/docs/config_reference.adoc.hbs | 42 +++++ 4 files changed, 193 insertions(+), 52 deletions(-) create mode 100644 config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/CmReference.java create mode 100644 config/metadata/docs/src/main/resources/io/helidon/config/metadata/docs/config_reference.adoc.hbs diff --git a/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/CmReference.java b/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/CmReference.java new file mode 100644 index 00000000000..e461a047cc5 --- /dev/null +++ b/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/CmReference.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.config.metadata.docs; + +/** + * Type required for template processing for config reference. + */ +public class CmReference { + private final String file; + private final String title; + + CmReference(String file, String title) { + this.file = file; + this.title = title; + } + + /** + * File name that was generated. + * + * @return file name + */ + public String file() { + return file; + } + + /** + * Title of the file. + * + * @return title + */ + public String title() { + return title; + } +} diff --git a/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/ConfigDocs.java b/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/ConfigDocs.java index f5bd4d39727..1ed530f5a2f 100644 --- a/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/ConfigDocs.java +++ b/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/ConfigDocs.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.UncheckedIOException; import java.lang.System.Logger.Level; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -60,7 +61,6 @@ public class ConfigDocs { private static final System.Logger LOGGER = System.getLogger(ConfigDocs.class.getName()); private static final String CONFIG_REFERENCE_ADOC = "config_reference.adoc"; private static final String METADATA_JSON_LOCATION = "META-INF/helidon/config-metadata.json"; - private static final String CODEGEN_BOUNDARY = "// do not remove: codegen boundary"; private static final String RELATIVE_PATH_ADOC = "{rootdir}/config/"; private static final Pattern MODULE_PATTERN = Pattern.compile("(.*?)(\\.spi)?\\.([a-zA-Z0-9]*?)"); private static final Pattern COPYRIGHT_LINE_PATTERN = Pattern.compile(".*Copyright \\(c\\) (.*) Oracle and/or its " @@ -90,7 +90,7 @@ private ConfigDocs(Path path) { * Create a new instance that will update config reference documentation in the {code targetPath}. * * @param targetPath path of the config reference documentation, must contain the {@value #CONFIG_REFERENCE_ADOC} - * file, with {@value #CODEGEN_BOUNDARY} boundary String, after which the list of all files is pasted + * file, or be empty * @return new instance of config documentation to call {@link #process()} on */ public static ConfigDocs create(Path targetPath) { @@ -171,25 +171,17 @@ static String translateHtml(String text) { * The documentation is updated, including copyright years */ public void process() { - // make sure it contains the config_reference.adoc Path configReference = path.resolve(ConfigDocs.CONFIG_REFERENCE_ADOC); - if (!(Files.exists(configReference) && Files.isRegularFile(configReference))) { - throw new ConfigDocsException("Cannot generate config reference documentation, unless target path contains " - + CONFIG_REFERENCE_ADOC + " file. Target path: " + path.toAbsolutePath()); - } - - Handlebars handlebars = new Handlebars(); - URL resource = ConfigDocs.class.getResource("type-docs.adoc.hbs"); - if (resource == null) { - throw new ConfigDocsException("Failed to locate required handlebars template on classpath: type-docs.adoc.hbs"); - } - Template template; try { - template = handlebars.compile(new URLTemplateSource("type-docs.adoc.hbs", resource)); + checkTargetPath(configReference); } catch (IOException e) { - throw new ConfigDocsException("Failed to load handlebars template on classpath: type-docs.adoc.hbs", e); + throw new ConfigDocsException("Failed to check if target path exists and is valid", e); } + Handlebars handlebars = new Handlebars(); + Template typeTemplate = template(handlebars, "type-docs.adoc.hbs"); + Template configReferenceTemplate = template(handlebars, "config_reference.adoc.hbs"); + Enumeration files; try { files = ConfigDocs.class.getClassLoader().getResources(METADATA_JSON_LOCATION); @@ -233,31 +225,13 @@ public void process() { List generatedFiles = new LinkedList<>(); for (CmModule module : allModules) { - moduleDocs(configuredTypes, template, path, module, generatedFiles); + moduleDocs(configuredTypes, typeTemplate, path, module, generatedFiles); } // sort alphabetically by page title generatedFiles.sort(Comparator.comparing(ConfigDocs::titleFromFileName)); - try { - List existingLines = Files.readAllLines(configReference, StandardCharsets.UTF_8); - List newLines = new ArrayList<>(); - for (String existingLine : existingLines) { - newLines.add(existingLine); - if (existingLine.equals(CODEGEN_BOUNDARY)) { - break; - } - } - - for (String generatedFile : generatedFiles) { - newLines.add("- xref:" + RELATIVE_PATH_ADOC + generatedFile + "[" + titleFromFileName(generatedFile) + "]"); - } - - LOGGER.log(Level.INFO, "Updating " + configReference.toAbsolutePath()); - Files.write(configReference, newLines); - } catch (IOException e) { - LOGGER.log(Level.ERROR, "Failed to update " + configReference.toAbsolutePath(), e); - } + generateConfigReference(configReference, configReferenceTemplate, generatedFiles); // and now report obsolete files // filter out generated files @@ -427,9 +401,9 @@ private static void generateType(List generatedFiles, } } - private static boolean sameContent(Path typePath, CharSequence current) { + private static boolean sameContent(Path path, CharSequence current) { try { - return Files.readString(typePath).equals(current.toString()); + return Files.readString(path).equals(current.toString()); } catch (IOException e) { throw new RuntimeException(e); } @@ -445,6 +419,23 @@ private static void sortOptions(CmType type) { type.setOptions(options); } + private static CharSequence configReferenceFile(Template template, + List generatedFiles, + String copyrightYears) { + List references = new ArrayList<>(); + for (String generatedFile : generatedFiles) { + references.add(new CmReference(generatedFile, + titleFromFileName(generatedFile))); + } + Map context = Map.of("year", copyrightYears, + "data", references); + try { + return template.apply(context); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + private static CharSequence typeFile(Map configuredTypes, Template template, CmType type, @@ -468,12 +459,12 @@ private static CharSequence typeFile(Map configuredTypes, return template.apply(context); } - private static String newCopyrightYears(Path typePath) { + private static String newCopyrightYears(Path path) { String currentYear = String.valueOf(ZonedDateTime.now().getYear()); - if (Files.exists(typePath)) { + if (Files.exists(path)) { // get current copyright year - String copyrightYears = currentCopyrightYears(typePath); + String copyrightYears = currentCopyrightYears(path); if (copyrightYears == null) { return currentYear; } @@ -489,8 +480,8 @@ private static String newCopyrightYears(Path typePath) { return currentYear; } - private static String currentCopyrightYears(Path typePath) { - try (var lines = Files.lines(typePath)) { + private static String currentCopyrightYears(Path path) { + try (var lines = Files.lines(path)) { return lines.flatMap(line -> { Matcher matcher = COPYRIGHT_LINE_PATTERN.matcher(line); if (matcher.matches()) { @@ -501,7 +492,7 @@ private static String currentCopyrightYears(Path typePath) { .findFirst() .orElse(null); } catch (IOException e) { - LOGGER.log(Level.WARNING, "Could not discover existing copyright year for " + typePath.toAbsolutePath(), e); + LOGGER.log(Level.WARNING, "Could not discover existing copyright year for " + path.toAbsolutePath(), e); return null; } } @@ -604,6 +595,70 @@ private static String mapAllowedValues(CmOption option, String displayType) { + " (" + values.stream().map(CmAllowedValue::getValue).collect(Collectors.joining(", ")) + ")"; } + // the target path must either contain zero files, or the config reference + // and it must exist + private void checkTargetPath(Path configReference) throws IOException { + if (Files.exists(path) && Files.isDirectory(path)) { + // either empty, or contains config reference + if (Files.exists(configReference) && Files.isRegularFile(configReference)) { + return; + } + // must be empty + try (Stream stream = Files.list(path)) { + if (stream.findAny() + .isPresent()) { + + throw new ConfigDocsException("Cannot generate config reference documentation, unless target path contains " + + CONFIG_REFERENCE_ADOC + " file or it is empty. " + + "Target path: " + path.toAbsolutePath() + " contains files"); + } + } + } else { + throw new IllegalArgumentException("Target path must be a directory and must exist: " + + path.toAbsolutePath().normalize()); + } + } + + private Template template(Handlebars handlebars, String template) { + URL resource = ConfigDocs.class.getResource(template); + if (resource == null) { + throw new ConfigDocsException("Failed to locate required handlebars template on classpath: " + template); + } + Template typeTemplate; + Template configReferenceTemplate; + try { + return handlebars.compile(new URLTemplateSource(template, resource)); + } catch (IOException e) { + throw new ConfigDocsException("Failed to load handlebars template on classpath: " + template, e); + } + } + + private void generateConfigReference(Path configReference, Template template, List generatedFiles) { + if (Files.exists(configReference)) { + // if content not modified, do not update copyright + CharSequence current = configReferenceFile(template, + generatedFiles, + currentCopyrightYears(configReference)); + if (sameContent(configReference, current)) { + return; + } + } + + CharSequence fileContent = configReferenceFile(template, + generatedFiles, + newCopyrightYears(configReference)); + + try { + LOGGER.log(Level.INFO, "Updating " + configReference.toAbsolutePath()); + Files.writeString(configReference, + fileContent, + StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.CREATE); + } catch (IOException e) { + LOGGER.log(Level.ERROR, "Failed to update " + configReference.toAbsolutePath(), e); + } + } + private void addTitle(Map configuredTypes) { for (CmType value : configuredTypes.values()) { value.setTitle(title(value.getType())); @@ -775,7 +830,8 @@ private void resolveInheritance(Map resolved, CmType next) { List inherits = next.getInherits(); // Traverse from higher to lower in the inheritance structure so more specific settings take precedence. - for (ListIterator inheritsIt = inherits.listIterator(inherits.size()); inheritsIt.hasPrevious();) { + ListIterator inheritsIt = inherits.listIterator(inherits.size()); + while (inheritsIt.hasPrevious()) { resolved.get(inheritsIt.previous()) .getOptions() .forEach(inheritedOption -> options.put(inheritedOption.getKey(), inheritedOption)); diff --git a/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/Main.java b/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/Main.java index 36629437bc9..e8f3085ce71 100644 --- a/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/Main.java +++ b/config/metadata/docs/src/main/java/io/helidon/config/metadata/docs/Main.java @@ -55,13 +55,8 @@ public static void main(String[] args) { } Path path = targetPath.toAbsolutePath().normalize(); - if (Files.exists(path) && Files.isDirectory(path)) { - ConfigDocs docs = ConfigDocs.create(path); - docs.process(); - } else { - throw new IllegalArgumentException("Target path must be a directory and must exist: " - + path.toAbsolutePath().normalize()); - } + ConfigDocs docs = ConfigDocs.create(path); + docs.process(); } private static Path findReasonablePath() { diff --git a/config/metadata/docs/src/main/resources/io/helidon/config/metadata/docs/config_reference.adoc.hbs b/config/metadata/docs/src/main/resources/io/helidon/config/metadata/docs/config_reference.adoc.hbs new file mode 100644 index 00000000000..c2284b2dadd --- /dev/null +++ b/config/metadata/docs/src/main/resources/io/helidon/config/metadata/docs/config_reference.adoc.hbs @@ -0,0 +1,42 @@ +{{! +Copyright (c) 2024 Oracle and/or its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +}}/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) {{year}} Oracle and/or its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +/////////////////////////////////////////////////////////////////////////////// + +ifndef::rootdir[:rootdir: {docdir}/..] +:description: Configuration Reference +:keywords: helidon, config, reference + += Configuration Reference + +The following section lists all configurable types in Helidon. + +{{#each data}}- xref:{rootdir}/config/{{this.file}}[{{this.title}}] +{{/each}} \ No newline at end of file