Skip to content

Commit

Permalink
Enable generating into an empty directory (once we remove the files f…
Browse files Browse the repository at this point in the history
…rom git).

Using template for config_reference.
  • Loading branch information
tomas-langer committed Jul 29, 2024
1 parent 3a2e507 commit f034842
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 "
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<URL> files;
try {
files = ConfigDocs.class.getClassLoader().getResources(METADATA_JSON_LOCATION);
Expand Down Expand Up @@ -233,31 +225,13 @@ public void process() {

List<String> 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<String> existingLines = Files.readAllLines(configReference, StandardCharsets.UTF_8);
List<String> 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
Expand Down Expand Up @@ -427,9 +401,9 @@ private static void generateType(List<String> 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);
}
Expand All @@ -445,6 +419,23 @@ private static void sortOptions(CmType type) {
type.setOptions(options);
}

private static CharSequence configReferenceFile(Template template,
List<String> generatedFiles,
String copyrightYears) {
List<CmReference> references = new ArrayList<>();
for (String generatedFile : generatedFiles) {
references.add(new CmReference(generatedFile,
titleFromFileName(generatedFile)));
}
Map<String, Object> context = Map.of("year", copyrightYears,
"data", references);
try {
return template.apply(context);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private static CharSequence typeFile(Map<String, CmType> configuredTypes,
Template template,
CmType type,
Expand All @@ -468,12 +459,12 @@ private static CharSequence typeFile(Map<String, CmType> 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;
}
Expand All @@ -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()) {
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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<Path> 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<String> 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<String, CmType> configuredTypes) {
for (CmType value : configuredTypes.values()) {
value.setTitle(title(value.getType()));
Expand Down Expand Up @@ -775,7 +830,8 @@ private void resolveInheritance(Map<String, CmType> resolved, CmType next) {

List<String> inherits = next.getInherits();
// Traverse from higher to lower in the inheritance structure so more specific settings take precedence.
for (ListIterator<String> inheritsIt = inherits.listIterator(inherits.size()); inheritsIt.hasPrevious();) {
ListIterator<String> inheritsIt = inherits.listIterator(inherits.size());
while (inheritsIt.hasPrevious()) {
resolved.get(inheritsIt.previous())
.getOptions()
.forEach(inheritedOption -> options.put(inheritedOption.getKey(), inheritedOption));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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}}

0 comments on commit f034842

Please sign in to comment.