You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hudi.apache.org by yi...@apache.org on 2023/02/23 00:31:14 UTC

[hudi] branch asf-site updated: [DOCS] Change Config generator to generate subgroups (#7970)

This is an automated email from the ASF dual-hosted git repository.

yihua pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/hudi.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new 48bc60d8298 [DOCS] Change Config generator to generate subgroups (#7970)
48bc60d8298 is described below

commit 48bc60d82988d02c14c4d75e263a1f9823edfd17
Author: Bhavani Sudha Saktheeswaran <21...@users.noreply.github.com>
AuthorDate: Wed Feb 22 16:31:05 2023 -0800

    [DOCS] Change Config generator to generate subgroups (#7970)
    
    Changes Config generator to generate subgroups.  Also, makes changes so that required configs bubble up to the top of the section.
---
 hudi-utils/generate_config.sh                      |   2 +-
 hudi-utils/pom.xml                                 |   8 +-
 .../hudi/utils/HoodieConfigDocGenerator.java       | 325 ++++++++++++++++-----
 3 files changed, 263 insertions(+), 72 deletions(-)

diff --git a/hudi-utils/generate_config.sh b/hudi-utils/generate_config.sh
index 89900de8b00..64e2b3dceef 100755
--- a/hudi-utils/generate_config.sh
+++ b/hudi-utils/generate_config.sh
@@ -17,7 +17,7 @@
 # limitations under the License.
 #
 
-VERSION=0.13.0
+VERSION=0.14.0
 
 JARS=(
 "$HOME/.m2/repository/org/apache/hudi/hudi-utilities-bundle_2.11/$VERSION-SNAPSHOT/hudi-utilities-bundle_2.11-$VERSION-SNAPSHOT.jar"
diff --git a/hudi-utils/pom.xml b/hudi-utils/pom.xml
index 2272f6b9609..90863a88a39 100644
--- a/hudi-utils/pom.xml
+++ b/hudi-utils/pom.xml
@@ -27,7 +27,7 @@
 
     <properties>
         <jdk.version>1.8</jdk.version>
-        <hudi.version>0.13.0-SNAPSHOT</hudi.version>
+        <hudi.version>0.14.0-SNAPSHOT</hudi.version>
         <hudi.spark.module>hudi-spark2</hudi.spark.module>
         <scala.binary.version>2.11</scala.binary.version>
         <junit.version>4.11</junit.version>
@@ -74,6 +74,12 @@
           <version>${hudi.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.hive</groupId>
+            <artifactId>hive-common</artifactId>
+            <version>2.3.1</version>
+        </dependency>
+
         <!-- Logging -->
         <!-- https://mvnrepository.com/artifact/log4j/log4j -->
         <dependency>
diff --git a/hudi-utils/src/main/java/org/apache/hudi/utils/HoodieConfigDocGenerator.java b/hudi-utils/src/main/java/org/apache/hudi/utils/HoodieConfigDocGenerator.java
index 1533599e936..4073f00bd8d 100644
--- a/hudi-utils/src/main/java/org/apache/hudi/utils/HoodieConfigDocGenerator.java
+++ b/hudi-utils/src/main/java/org/apache/hudi/utils/HoodieConfigDocGenerator.java
@@ -47,11 +47,15 @@ import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.EnumSet;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static org.apache.hudi.common.config.ConfigGroups.SubGroupNames.NONE;
 import static org.reflections.ReflectionUtils.getAllFields;
 import static org.reflections.ReflectionUtils.withTypeAssignableTo;
 
@@ -76,10 +80,14 @@ public class HoodieConfigDocGenerator {
           "file `hudi-default.conf`. By default, Hudi would load the configuration file under `/etc/hudi/conf` directory. You can\n" +
           "specify a different configuration directory location by setting the `HUDI_CONF_DIR` environment variable. This can be\n" +
           "useful for uniformly enforcing repeated configs (like Hive sync or write/index tuning), across your entire data lake.";
+  private static final Integer DEFAULT_CONFIG_GROUP_HEADING_LEVEL = 2;
+  private static final Integer DEFAULT_CONFIG_PARAM_HEADING_LEVEL = 3;
 
   public static void main(String[] args) {
     Reflections reflections = new Reflections("org.apache.hudi");
-    Set<Class<? extends HoodieConfig>> subTypes = reflections.getSubTypesOf(HoodieConfig.class);
+    // Scan and collect meta info of all HoodieConfig superclasses by using reflection
+    List<HoodieConfigClassMetaInfo> hoodieConfigClassMetaInfos = getSortedListOfHoodieConfigClassMetaInfo(reflections.getSubTypesOf(HoodieConfig.class));
+
     // Top heading
     StringBuilder mainDocBuilder = new StringBuilder();
     generateHeader(mainDocBuilder);
@@ -91,44 +99,61 @@ public class HoodieConfigDocGenerator {
     // and also does not use ConfigClassProperty
     populateSparkConfigs(contentMap);
 
-    // Automated: Scan through all HoodieConfig superclasses using reflection
-    for (Class<? extends HoodieConfig> subType : subTypes) {
-      // sub-heading using the annotation
-      ConfigClassProperty configGroupProperty = subType.getAnnotation(ConfigClassProperty.class);
-      try {
-        if (configGroupProperty != null) {
-          StringBuilder configParamsBuilder = contentMap.get(configGroupProperty.groupName());
-          LOG.info("Processing params for config class: " + subType.getName() + " " + configGroupProperty.name()
-            + " " + configGroupProperty.description());
-          configParamsBuilder.append("### ").append(configGroupProperty.name())
-              .append(" {" + "#").append(configGroupProperty.name().replace(" ", "-")).append("}")
-              .append(DOUBLE_NEWLINE);
-          configParamsBuilder.append(configGroupProperty.description()).append(DOUBLE_NEWLINE);
-
-          configParamsBuilder
+    // generate Docs from the config classes
+    ConfigGroups.SubGroupNames prevSubGroupName = NONE;
+    boolean isPartOfSubGroup = false;
+    int configParamHeadingLevel = DEFAULT_CONFIG_PARAM_HEADING_LEVEL;
+    for (HoodieConfigClassMetaInfo configClassMetaInfo: hoodieConfigClassMetaInfos) {
+      Class<? extends HoodieConfig> subType = configClassMetaInfo.subType;
+      ConfigClassProperty configClassProperty = subType.getAnnotation(ConfigClassProperty.class);
+      StringBuilder groupOrSubGroupStringBuilder = contentMap.get(configClassProperty.groupName());
+      LOG.info("Processing params for config class: " + subType.getName() + " " + configClassProperty.name()
+              + " " + configClassProperty.description());
+      if (configClassMetaInfo.subGroupName == NONE){
+        isPartOfSubGroup = false;
+        configParamHeadingLevel = DEFAULT_CONFIG_PARAM_HEADING_LEVEL;
+      } else if (configClassMetaInfo.subGroupName == prevSubGroupName) {
+        // Continuation of more HoodieConfig classes that are part of the same subgroup
+        isPartOfSubGroup = true;
+        groupOrSubGroupStringBuilder = new StringBuilder();
+        configParamHeadingLevel = DEFAULT_CONFIG_PARAM_HEADING_LEVEL + 1;
+      } else if (configClassMetaInfo.hasCommonConfigs) {
+        // This is a new valid Subgroup encountered. Add description for the subgroup.
+        isPartOfSubGroup = true;
+        groupOrSubGroupStringBuilder = new StringBuilder();
+        generateConfigGroupSummary(groupOrSubGroupStringBuilder,
+                configClassMetaInfo.subGroupName.name,
+                configClassMetaInfo.subGroupName.name(),
+                configClassProperty.subGroupName().getDescription(),
+                DEFAULT_CONFIG_GROUP_HEADING_LEVEL + 1);
+        configParamHeadingLevel = DEFAULT_CONFIG_PARAM_HEADING_LEVEL + 1;
+      }
+      prevSubGroupName = configClassMetaInfo.subGroupName;
+      generateConfigGroupSummary(groupOrSubGroupStringBuilder,
+              configClassProperty.name(),
+              configClassProperty.name().replace(" ", "-"),
+              configClassProperty.description(),
+              configParamHeadingLevel);
+      groupOrSubGroupStringBuilder
               .append("`")
               .append(new Text("Config Class"))
               .append("`")
               .append(": ")
               .append(subType.getName()).append(LINE_BREAK);
 
-          // Special casing Flink Configs since the class does not use ConfigClassProperty
-          // Also, we need to split Flink Options into Flink Read Options, Write Options...
-          if (subType.getName().equals(FLINK_CONFIG_CLASS_NAME)) {
-            generateFlinkConfigMarkup(subType, configParamsBuilder);
-          } else {
-            Set<Field> fields = getAllFields(subType, withTypeAssignableTo(ConfigProperty.class));
-            for (Field field : fields) {
-              generateConfigMarkup(subType, field, null, configParamsBuilder);
-            }
-          }
-        } else {
-          LOG.error("FATAL error Please add `ConfigClassProperty` annotation for " + subType.getName());
+      // Special casing Flink Configs since the class does not use ConfigClassProperty
+      // Also, we need to split Flink Options into Flink Read Options, Write Options...
+      if (subType.getName().equals(FLINK_CONFIG_CLASS_NAME)) {
+        generateFlinkConfigMarkup(subType, groupOrSubGroupStringBuilder);
+      } else {
+        generateAllOtherConfigs(subType, isPartOfSubGroup, groupOrSubGroupStringBuilder);
+        if (isPartOfSubGroup) {
+          // If the config class is part of a subgroup, close the string builder for subgroup and append it to the main group builder.
+          contentMap.get(configClassProperty.groupName()).append(groupOrSubGroupStringBuilder.toString());
         }
-      } catch (Exception e) {
-        LOG.error("FATAL error while processing config class: " + subType.getName(), e);
       }
     }
+
     try {
       LOG.info("Generating markdown file");
       mainDocBuilder.append(contentTableBuilder.build()).append(DOUBLE_NEWLINE);
@@ -140,6 +165,32 @@ public class HoodieConfigDocGenerator {
     }
   }
 
+  private static List<HoodieConfigClassMetaInfo> getSortedListOfHoodieConfigClassMetaInfo(Set<Class<? extends HoodieConfig>> subTypes) {
+    // Scan and collect meta info of all HoodieConfig superclasses by using reflection
+    List<HoodieConfigClassMetaInfo> hoodieConfigClassMetaInfos = new ArrayList<>();
+    for (Class<? extends HoodieConfig> subType : subTypes) {
+      // sub-heading using the annotation
+      ConfigClassProperty configClassProperty = subType.getAnnotation(ConfigClassProperty.class);
+      try{
+        if (configClassProperty != null) {
+          hoodieConfigClassMetaInfos.add(new HoodieConfigClassMetaInfo(configClassProperty.groupName(), configClassProperty.subGroupName(), configClassProperty.areCommonConfigs(), subType));
+        } else {
+          LOG.error("FATAL error Please add `ConfigClassProperty` annotation for " + subType.getName());
+        }
+      } catch (Exception e) {
+        LOG.error("FATAL error while processing config class: " + subType.getName(), e);
+      }
+    }
+
+    // Now sort them based on these columns in the order - groupname, subgroupname (reverse order) and areCommonConfigs (reverse order)
+    // We want to list all groups with no subgroups first. Followed by groups with subgroups and among them list the
+    // class that has common configs first.
+    hoodieConfigClassMetaInfos.sort(Comparator.comparing(HoodieConfigClassMetaInfo::getGroupName)
+            .thenComparing(HoodieConfigClassMetaInfo::getSubGroupName, Comparator.reverseOrder())
+            .thenComparing(HoodieConfigClassMetaInfo::areCommonConfigs, Comparator.reverseOrder()));
+    return hoodieConfigClassMetaInfos;
+  }
+
   private static void generateHeader(StringBuilder builder) {
     /*
       ---
@@ -148,6 +199,8 @@ public class HoodieConfigDocGenerator {
       permalink: /docs/configurations.html
       summary: This section offers an overview of tools available to operate an ecosystem of Hudi
       toc: true
+      toc_min_heading_level: 2
+      toc_max_heading_level: 4
       last_modified_at: 2019-12-30T15:59:57-04:00
       ---
      */
@@ -158,6 +211,8 @@ public class HoodieConfigDocGenerator {
         .append("permalink: /docs/configurations.html").append(NEWLINE)
         .append("summary: " + SUMMARY).append(NEWLINE)
         .append("toc: true").append(NEWLINE)
+        .append("toc_min_heading_level: 2").append(NEWLINE)
+        .append("toc_max_heading_level: 4").append(NEWLINE)
         .append("last_modified_at: " + DateTimeFormatter.ISO_DATE_TIME.format(now)).append(NEWLINE)
         .append(new HorizontalRule())
         .append(DOUBLE_NEWLINE);
@@ -173,17 +228,29 @@ public class HoodieConfigDocGenerator {
     Map<ConfigGroups.Names, StringBuilder> contentMap = new LinkedHashMap<>();
     EnumSet.allOf(ConfigGroups.Names.class).forEach(groupName -> {
       StringBuilder stringBuilder = new StringBuilder();
-      stringBuilder.append("## ")
-          .append(groupName.name)
-          .append(" {" + "#").append(groupName.name()).append("}")
-          .append(NEWLINE)
-          .append(ConfigGroups.getDescription(groupName))
-          .append(DOUBLE_NEWLINE);
+      generateConfigGroupSummary(stringBuilder, groupName.name, groupName.name(), ConfigGroups.getDescription(groupName), DEFAULT_CONFIG_GROUP_HEADING_LEVEL);
       contentMap.put(groupName, stringBuilder);
     });
     return contentMap;
   }
 
+  private static void generateConfigGroupSummary(StringBuilder stringBuilder, String friendlyName, String groupName, String description, int headingSize) {
+    stringBuilder.append(getHeadingSizeMarkup(headingSize))
+            .append(friendlyName)
+            .append(" {" + "#").append(groupName).append("}")
+            .append(NEWLINE)
+            .append(description)
+            .append(DOUBLE_NEWLINE);
+  }
+
+  private static String getHeadingSizeMarkup(int headingSize){
+    StringBuilder stringBuilder = new StringBuilder();
+    for (int i = 0; i < headingSize; i++) {
+      stringBuilder.append("#");
+    }
+    stringBuilder.append(" ");
+    return stringBuilder.toString();
+  }
   private static StringBuilder generateExternalizedConfigs() {
     StringBuilder stringBuilder = new StringBuilder();
     stringBuilder.append(EXTERNALIZED_CONFIGS);
@@ -212,17 +279,26 @@ public class HoodieConfigDocGenerator {
 
 
       Set<Field> hardcodedFields = ReflectionUtils.getAllFields(sparkConfigObject.getClass(), withTypeAssignableTo(ConfigProperty.class));
+
+      List<ConfigMarkup> allConfigs = new ArrayList<>();
       for (Field field : hardcodedFields) {
         field.setAccessible(true);
-        generateConfigMarkup(sparkConfigObject.getClass(), field, sparkConfigObject, configParamsBuilder);
+        ConfigMarkup configMarkup = generateConfigMarkup(sparkConfigObject.getClass(), field, sparkConfigObject, DEFAULT_CONFIG_PARAM_HEADING_LEVEL);
+        allConfigs.add(configMarkup);
       }
+      // sort the configs based on config key prefix and add to the configParamsBuilder
+      allConfigs.sort(Comparator.comparing(ConfigMarkup::isConfigRequired).reversed()
+              .thenComparing(ConfigMarkup::getConfigKey));
+      allConfigs.forEach(cfg -> configParamsBuilder.append(cfg.configMarkupString));
     }
   }
 
   private static void generateFlinkConfigMarkup(Class subType, StringBuilder configParamsBuilder) {
       try {
+        List<ConfigMarkup> allConfigs = new ArrayList();
         Set<Field> fields = getAllFields(FlinkOptions.class, withTypeAssignableTo(ConfigOption.class));
         for (Field field : fields) {
+          StringBuilder tmpConfigParamBuilder = new StringBuilder();
           ConfigOption cfgProperty = (ConfigOption) field.get(null);
           String description = new HtmlFormatter().format(cfgProperty.description());
           if (description.isEmpty()) {
@@ -232,99 +308,208 @@ public class HoodieConfigDocGenerator {
                 + field.getName());
           }
           // Config Header
-          configParamsBuilder.append("> ").append("#### ").append(new Text(cfgProperty.key())).append(NEWLINE);
+          tmpConfigParamBuilder.append("> ").append("#### ").append(new Text(cfgProperty.key())).append(NEWLINE);
 
           // Description
-          configParamsBuilder
+          tmpConfigParamBuilder
               .append("> ")
               .append(description)
               .append(LINE_BREAK);
 
           // Default value
-          addDefaultValue(configParamsBuilder, cfgProperty.hasDefaultValue() ? cfgProperty.defaultValue() : null);
+          Object defaultValue = cfgProperty.hasDefaultValue() ? cfgProperty.defaultValue() : null;
+          addDefaultValue(tmpConfigParamBuilder, defaultValue);
+          boolean isConfigRequired = (defaultValue == null);
+
+          // TODO: Custom config tags like "Doc on Default Value:" cannot be added for Flink.
+          //  ConfigOption is a Flink class. In order to support custom config apis like getDocOnDefaultValue
+          //  this class needs to be wrapped in Hudi first.
 
           // Config param name
-          generateConfigKeyValue(configParamsBuilder, true, "Config Param", field.getName());
+          generateConfigKeyValue(tmpConfigParamBuilder, "Config Param", field.getName());
 
-          configParamsBuilder
+          tmpConfigParamBuilder
               .append(NEWLINE)
               .append(new HorizontalRule(3))
               .append(DOUBLE_NEWLINE);
+
+          ConfigMarkup configMarkup = new ConfigMarkup(cfgProperty.key(), isConfigRequired, tmpConfigParamBuilder.toString());
+          allConfigs.add(configMarkup);
         }
+
+        // sort the configs based on config key prefix and add to the configParamsBuilder
+        allConfigs.sort(Comparator.comparing(ConfigMarkup::isConfigRequired).reversed()
+                .thenComparing(ConfigMarkup::getConfigKey));
+        allConfigs.forEach(cfg -> configParamsBuilder.append(cfg.configMarkupString));
       } catch (IllegalAccessException e) {
         LOG.error("Error while getting field through reflection for config class: " + subType.getName(), e);
       }
   }
 
-  private static void generateConfigMarkup(Class subType, Field field, Object object, StringBuilder configParamsBuilder) {
+  private static void generateAllOtherConfigs(Class<? extends HoodieConfig> subType, boolean isPartOfSubGroup, StringBuilder stringBuilder) {
+    Set<Field> fields = getAllFields(subType, withTypeAssignableTo(ConfigProperty.class));
+    List<ConfigMarkup> allConfigs = new ArrayList<>();
+    for (Field field : fields) {
+      ConfigMarkup configMarkup = generateConfigMarkup(subType, field, null, isPartOfSubGroup? DEFAULT_CONFIG_PARAM_HEADING_LEVEL + 1 : DEFAULT_CONFIG_PARAM_HEADING_LEVEL);
+      allConfigs.add(configMarkup);
+    }
+    // sort the configs based on config key prefix and add to the configParamsBuilder
+    allConfigs.sort(Comparator.comparing(ConfigMarkup::isConfigRequired).reversed()
+            .thenComparing(ConfigMarkup::getConfigKey));
+    for (ConfigMarkup cfg: allConfigs) {
+      stringBuilder.append(cfg.configMarkupString);
+    }
+  }
+
+  private static ConfigMarkup generateConfigMarkup(Class subType, Field field, Object object, int headingLevel) {
     try {
+      StringBuilder configParamsBuilder = new StringBuilder();
       ConfigProperty cfgProperty = (ConfigProperty) field.get(object);
       if (StringUtils.isNullOrEmpty(cfgProperty.doc())) {
         LOG.warn("Found empty or null description for config class = "
-            + subType.getName()
-            + " for param = "
-            + field.getName());
+                + subType.getName()
+                + " for param = "
+                + field.getName());
       }
 
       // Config Header
-      configParamsBuilder.append("> ").append("#### ").append(new Text(cfgProperty.key())).append(NEWLINE);
+      configParamsBuilder.append("> ").append(getHeadingSizeMarkup(headingLevel)).append(new Text(cfgProperty.key())).append(NEWLINE);
 
       // Description
       String description = StringUtils.isNullOrEmpty(cfgProperty.doc()) ? "" : cfgProperty.doc();
       configParamsBuilder
-          .append("> ")
-          .append(description)
-          .append(LINE_BREAK);
+              .append("> ")
+              .append(description)
+              .append(LINE_BREAK);
 
       // Default value
-      addDefaultValue(configParamsBuilder, cfgProperty.hasDefaultValue() ? cfgProperty.defaultValue() : null);
+      Object defaultValue = cfgProperty.hasDefaultValue() ? cfgProperty.defaultValue() : (cfgProperty.hasInferFunction() ? "" : null );
+      addDefaultValue(configParamsBuilder, defaultValue);
+      boolean isConfigRequired = (defaultValue == null);
+
+      // Note on Default value
+      if (StringUtils.nonEmpty(cfgProperty.getDocOnDefaultValue()) && !cfgProperty.getDocOnDefaultValue().equals(StringUtils.EMPTY_STRING)) {
+        addDocOnDefaultValue(configParamsBuilder, cfgProperty.getDocOnDefaultValue());
+      }
 
       // Config param name
-      generateConfigKeyValue(configParamsBuilder, true, "Config Param", field.getName());
+      generateConfigKeyValue(configParamsBuilder, "Config Param", field.getName());
 
       // First version
       if (cfgProperty.getSinceVersion().isPresent()) {
-        generateConfigKeyValue(configParamsBuilder, true, "Since Version", cfgProperty.getSinceVersion().get());
+        generateConfigKeyValue(configParamsBuilder, "Since Version", String.valueOf(cfgProperty.getSinceVersion().get()));
       }
 
       if (cfgProperty.getDeprecatedVersion().isPresent()) {
-        generateConfigKeyValue(configParamsBuilder, true, "Deprecated Version", cfgProperty.getDeprecatedVersion().get());
+        generateConfigKeyValue(configParamsBuilder, "Deprecated Version", String.valueOf(cfgProperty.getDeprecatedVersion().get()));
       }
 
       configParamsBuilder
-          .append(NEWLINE)
-          .append(new HorizontalRule(3))
-          .append(DOUBLE_NEWLINE);
+              .append(NEWLINE)
+              .append(new HorizontalRule(3))
+              .append(DOUBLE_NEWLINE);
+      return new ConfigMarkup(cfgProperty.key(), isConfigRequired, configParamsBuilder.toString());
     } catch (IllegalAccessException e) {
       LOG.error("Error while getting field through reflection for config class: " + subType.getName(), e);
+      throw new IllegalArgumentException("Error while getting field through reflection for config class: " + subType.getName(), e);
     }
   }
 
   private static void addDefaultValue(StringBuilder builder, @Nullable Object defaultValue) {
-    generateConfigKeyValue(builder, false, "Default Value",
-        (defaultValue != null) ? defaultValue + " (Optional)" : "N/A (Required)");
+    boolean isRequired = false;
+    if (defaultValue != null) {
+      builder
+              .append("> ")
+              .append("`")
+              .append(new Text("Default Value"))
+              .append(": ")
+              .append(new Text(defaultValue + " (Optional)"))
+              .append("`")
+              .append(LINE_BREAK);
+    } else {
+      builder
+              .append("> ")
+              .append("`")
+              .append(new Text("Default Value"))
+              .append(": N/A ")
+              .append("`")
+              .append(new BoldText("(Required)"))
+              .append(LINE_BREAK);
+      // TODO: Use this to highlight required configs blue in a later release. We are not enabling this now since we need
+      //  to review required configs in Hudi repo first towards simplification.
+      //.append(new BoldText("<span style={{color: '#0db1f9'}}>(Required)</span>"))
+    }
+  }
+
+  private static void addDocOnDefaultValue(StringBuilder builder, String value) {
+    generateConfigKeyValue(builder, "Note on Default Value", value);
   }
 
   private static void generateConfigKeyValue(StringBuilder builder,
-                                             boolean shouldHighlight,
                                              String key,
-                                             Object value) {
-    if (shouldHighlight) {
+                                             String value) {
       builder
           .append("> ")
           .append("`")
           .append(new Text(key))
           .append(": ")
-          .append(new Text(value))
+          .append(value)
           .append("`")
           .append(LINE_BREAK);
-    } else {
-      builder
-          .append("> ")
-          .append(new BoldText(key))
-          .append(": ")
-          .append(new Text(value))
-          .append(LINE_BREAK);
+  }
+
+  /**
+  Class for storing info about each config within a HoodieConfig class subtype. We want to know if a config is required or not. And also store the config key
+  along with the markup. We can use this info to pull up all required configs and also sort configs based on prefix
+  for better readability.
+  **/
+
+  static class ConfigMarkup {
+    String configKey;
+    boolean isConfigRequired;
+    String configMarkupString;
+
+    ConfigMarkup(String configKey, boolean isConfigRequired, String configMarkupString){
+      this.configKey = configKey;
+      this.isConfigRequired = isConfigRequired;
+      this.configMarkupString = configMarkupString;
+    }
+
+    public boolean isConfigRequired() {
+      return isConfigRequired;
+    }
+
+    public String getConfigKey() {
+      return configKey;
+    }
+  }
+
+  /**
+   * Class for storing meta info about each HoodiConfig class subtype. This class has info on the group name, subgroup
+   * name if any, indication of whether this subtype has common configs for that subgroup and the subtype itself.
+   */
+  static class HoodieConfigClassMetaInfo {
+    ConfigGroups.Names groupName;
+    ConfigGroups.SubGroupNames subGroupName;
+    boolean hasCommonConfigs;
+    Class<? extends HoodieConfig> subType;
+    HoodieConfigClassMetaInfo(ConfigGroups.Names groupName, ConfigGroups.SubGroupNames subGroupName, boolean  hasCommonConfigs, Class<? extends HoodieConfig> subType) {
+      this.groupName = groupName;
+      this.subGroupName = subGroupName;
+      this.hasCommonConfigs = hasCommonConfigs;
+      this.subType = subType;
+    }
+
+    public ConfigGroups.Names getGroupName() {
+      return groupName;
+    }
+
+    public ConfigGroups.SubGroupNames getSubGroupName() {
+      return subGroupName;
+    }
+
+    public boolean areCommonConfigs() {
+      return hasCommonConfigs;
     }
   }
 }