Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.fluss.annotation.docs;

import org.apache.fluss.annotation.Internal;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Annotation used to override the default value string in the documentation. */
@Internal
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConfigOverrideDefault {
String value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.fluss.annotation.docs;

import org.apache.fluss.annotation.Internal;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Annotation used on config options to group them into logical sections. */
@Internal
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConfigSection {
String value();
}
20 changes: 20 additions & 0 deletions fluss-docgen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Fluss Documentation Generator

This module contains utilities to automatically generate documentation parts from the Fluss source code. This ensures that the documentation stays in sync with the actual implementation and default values.

## Configuration Options Generator

The `ConfigOptionsDocGenerator` scans the `ConfigOptions` class and generates an MDX partial file containing categorized tables of all available configuration settings.

### How it works
1. It uses reflection to find all `ConfigOption` fields in the `ConfigOptions` class.
2. It groups options into sections based on the `@ConfigSection` annotation or key prefixes.
3. It handles special default value formatting via `@ConfigOverrideDefault`.
4. It outputs an MDX file (config_reference.mdx) using React-compatible HTML table syntax.

### Running the Generator

To update the configuration documentation, run the following command from the project root:

```bash
./mvnw compile -pl fluss-docgen -am && ./mvnw exec:java -pl fluss-docgen -Dexec.mainClass="org.apache.fluss.docs.ConfigOptionsDocGenerator"
79 changes: 79 additions & 0 deletions fluss-docgen/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.apache.fluss</groupId>
<artifactId>fluss</artifactId>
<version>0.9-SNAPSHOT</version>
</parent>

<artifactId>fluss-docgen</artifactId>
<name>Fluss : Documentation Generator</name>

<dependencies>
<dependency>
<groupId>org.apache.fluss</groupId>
<artifactId>fluss-common</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.fluss</groupId>
<artifactId>fluss-test-utils</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>compile</phase> <goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.apache.fluss.docs.ConfigOptionsDocGenerator</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.fluss.docs;

import org.apache.fluss.config.ConfigOption;
import org.apache.fluss.config.MemorySize;

import java.time.Duration;

/** Utility class for formatting configuration options into human-readable documentation. */
public class ConfigDocUtils {

/**
* Formats the default value of a {@link ConfigOption} for documentation purposes.
*
* @param option The configuration option to format.
* @return A string representation of the default value.
*/
public static String formatDefaultValue(ConfigOption<?> option) {
Object value = option.defaultValue();

if (value == null) {
return "none";
}

// Handle Duration: Convert ISO-8601 (PT15M) to human-readable (15 min).
if (value instanceof Duration) {
Duration d = (Duration) value;
long seconds = d.getSeconds(); // Use Java 8 compatible method.

if (seconds == 0) {
return "0 s";
}
if (seconds >= 3600 && seconds % 3600 == 0) {
return (seconds / 3600) + " hours";
}
if (seconds >= 60 && seconds % 60 == 0) {
return (seconds / 60) + " min";
}
return seconds + " s";
}

// Handle MemorySize: Uses internal toString() for human-readable units (e.g., 64 mb).
if (value instanceof MemorySize) {
return value.toString();
}

// Handle Strings: Specifically check for empty values.
if (value instanceof String && ((String) value).isEmpty()) {
return "(empty)";
}

return value.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.fluss.docs;

import org.apache.fluss.annotation.docs.ConfigOverrideDefault;
import org.apache.fluss.annotation.docs.ConfigSection;
import org.apache.fluss.config.ConfigOption;
import org.apache.fluss.config.ConfigOptions;

import java.io.File;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/** Generator for configuration documentation. */
public class ConfigOptionsDocGenerator {

public static void main(String[] args) throws Exception {
Path projectRoot = findProjectRoot();
File outputFile =
projectRoot.resolve("website/docs/maintenance/config_reference.mdx").toFile();

System.out.println("Generating MDX partial: " + outputFile.getAbsolutePath());

if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}

String content = generateMDXContent();
Files.write(
outputFile.toPath(), Collections.singletonList(content), StandardCharsets.UTF_8);

System.out.println("SUCCESS: MDX partial generated.");
}

private static String generateMDXContent() throws IllegalAccessException {
StringBuilder builder = new StringBuilder();
builder.append("{/* This file is auto-generated. Do not edit directly. */}\n\n");

Field[] fields = ConfigOptions.class.getDeclaredFields();
Map<String, List<Field>> sections = new TreeMap<>();

// 1. Group the fields first
for (Field field : fields) {
if (field.getType().equals(ConfigOption.class)) {
String section = "Common";
if (field.isAnnotationPresent(ConfigSection.class)) {
section = field.getAnnotation(ConfigSection.class).value();
} else {
ConfigOption<?> option = (ConfigOption<?>) field.get(null);
String key = option.key();
if (key != null && key.contains(".")) {
section = capitalize(key.split("\\.")[0]);
}
}
sections.computeIfAbsent(section, k -> new ArrayList<>()).add(field);
}
}

// 2. Generate the HTML for each section
for (Map.Entry<String, List<Field>> entry : sections.entrySet()) {
builder.append("## ").append(entry.getKey()).append(" Configurations\n\n");
builder.append("<table class=\"configuration-table\">\n")
.append(" <thead>\n <tr>\n")
.append(" <th style={{width: '25%'}}>Key</th>\n")
.append(" <th style={{width: '15%'}}>Default</th>\n")
.append(" <th style={{width: '15%'}}>Type</th>\n")
.append(" <th style={{width: '45%'}}>Description</th>\n")
.append(" </tr>\n </thead>\n <tbody>\n");

for (Field field : entry.getValue()) {
ConfigOption<?> option = (ConfigOption<?>) field.get(null);

String defaultValue = ConfigDocUtils.formatDefaultValue(option);
if (field.isAnnotationPresent(ConfigOverrideDefault.class)) {
defaultValue = field.getAnnotation(ConfigOverrideDefault.class).value();
}

// ESCAPE DESCRIPTION: Crucial for MDX/React rendering success
// We escape <, >, {, and } which are special JSX characters
String description =
option.description()
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("{", "&#123;")
.replace("}", "&#125;")
.replace("%s", "");

builder.append(" <tr>\n")
.append(" <td><code>")
.append(option.key())
.append("</code></td>\n")
.append(" <td><code>")
.append(defaultValue.replace("<", "&lt;"))
.append("</code></td>\n")
.append(" <td>")
.append(getType(option))
.append("</td>\n")
.append(" <td>")
.append(description)
.append("</td>\n")
.append(" </tr>\n");
}
builder.append(" </tbody>\n</table>\n\n");
}

// Mandatory export for MDX partials to render correctly in Docusaurus
builder.append("export default ({children}) => <>{children}</>;\n");

return builder.toString();
}

private static String getType(ConfigOption<?> option) {
Object def = option.defaultValue();
if (def != null) {
return def.getClass().getSimpleName();
}
return "String";
}

private static String capitalize(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}

private static Path findProjectRoot() {
Path root = Paths.get(System.getProperty("user.dir"));
while (root != null && !Files.exists(root.resolve("pom.xml"))) {
root = root.getParent();
}
return root;
}
}
Loading
Loading