add description for options

Fri, 14 Nov 2025 15:09:37 +0100

author
Mike Becker <universe@uap-core.de>
date
Fri, 14 Nov 2025 15:09:37 +0100
changeset 162
79eeb33c738f
parent 161
3d1cd23f88f7
child 163
12a5e64cab34

add description for options

resolves #738 and resolves #739

src/main/java/de/unixwork/uwproj/AbstractOption.java file | annotate | diff | comparison | revisions
src/main/java/de/unixwork/uwproj/Feature.java file | annotate | diff | comparison | revisions
src/main/java/de/unixwork/uwproj/Option.java file | annotate | diff | comparison | revisions
src/main/resources/make/configure.vm file | annotate | diff | comparison | revisions
src/main/resources/make/uwproj.xsd file | annotate | diff | comparison | revisions
test/configure2 file | annotate | diff | comparison | revisions
test/make/configure.vm file | annotate | diff | comparison | revisions
test/make/project2.xml file | annotate | diff | comparison | revisions
test/make/uwproj.xsd file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/unixwork/uwproj/AbstractOption.java	Fri Nov 14 15:09:37 2025 +0100
@@ -0,0 +1,140 @@
+package de.unixwork.uwproj;
+
+public abstract class AbstractOption {
+    protected final String arg;
+    protected String desc = "";
+
+    public AbstractOption(String arg) {
+        this.arg = arg;
+    }
+
+    /**
+     * The name of the argument.
+     *
+     * @return the argument name
+     * @see #getHelpTextArg()
+     */
+    public abstract String getArg();
+
+    /**
+     * The display text for the argument.
+     * <p>
+     * This is not necessarily the same as {@link #getArg()},
+     * e.g. for options that have --enable-opt / --disable-opt variants.
+     * Also, the help text may show a set of selecatable values or other info.
+     *
+     * @return the text for the argument to include in the help text
+     * @see #getArg()
+     */
+    public abstract String getHelpTextArg();
+
+    /**
+     * The description of the option.
+     * Rarely used, better use {@link #getHelpText()} instead.
+     * @return the description without any formatting
+     * @see #getHelpText()
+     */
+    public abstract String getDesc();
+
+    /**
+     * The internal variable name for the option.
+     * @return the variable name where the option's value will be stored
+     */
+    public abstract String getVarName();
+
+    /**
+     * Generates help text for the feature option.
+     * <p>
+     * If no description is available via {@link #getDesc()}, only the option name is generated
+     * via {@link #getHelpTextArg()}. Otherwise, the description is added according to the following rules:
+     * <p>
+     * When the option name does not consume more than 25 characters, the description starts in the same line.
+     * Otherwise, a line break is added, first. The description will be placed in a block starting from column
+     * 27 to 80 and automatically break when necessary. The description must not contain a single word
+     * that is longer than 54 characters, or it will break the layout.
+     *
+     * @return a help text for terminal output
+     */
+    public String getHelpText() {
+        final var builder = new StringBuilder();
+
+        // Get the option name and prepend it with indentation
+        final var opt = "  "+getHelpTextArg();
+
+        // Add option name
+        builder.append(opt);
+
+        // Stop, if there is no description
+        if (desc.isBlank()) return builder.toString();
+
+        // Prepare the description by replacing some unwanted spaces
+        final var hdesc = desc.trim()
+                .replaceAll("\\r", "")
+                .replaceAll("\\t", "  ");
+
+        // Declare our frame where the description shall be placed
+        final int startx = 26;
+        final int endx = 80;
+
+        // Move to startx (break, if we already past that)
+        if (opt.length() >= startx) {
+            builder.append("\n");
+            builder.append(" ".repeat(startx));
+        } else {
+            builder.append(" ".repeat(startx-opt.length()));
+        }
+
+        // Append the description keeping the layout intact
+        int x = startx;
+        for (int i = 0 ; i < hdesc.length() ;) {
+            // get the next word and see where we would end
+            int s = hdesc.indexOf(' ', i);
+            if (s < 0) s = hdesc.length();
+            int n = hdesc.indexOf('\n', i);
+            if (n < 0) n = hdesc.length();
+            s = Math.min(s, n);
+            int l = s-i;
+            if (x + l > endx) {
+                // does not fit, break into next line
+                builder.append('\n');
+                builder.append(" ".repeat(startx));
+                x = startx;
+            }
+            // append the word
+            builder.append(hdesc, i, s);
+            x += l;
+            i += l;
+            // append the next spaces
+            while (i < hdesc.length()) {
+                int c = hdesc.charAt(i);
+                if (c == ' ') {
+                    // as long as we don't need to break, add the spaces
+                    i++;
+                    x++;
+                    if (x < endx) builder.append(' ');
+                } else if (c == '\n') {
+                    // if user wants us to break, comply
+                    i++;
+                    // if we have still space, just move to the end of the line
+                    if (x < endx) {
+                        x = endx;
+                    } else {
+                        // otherwise, we need to add an extra blank line
+                        builder.append('\n');
+                    }
+                } else {
+                    // we have found the next word, so continue with the outer loop
+                    break;
+                }
+            }
+            // break to new line, when spaces moved us outside the frame
+            if (x > endx) {
+                builder.append('\n');
+                builder.append(" ".repeat(startx));
+                x = startx;
+            }
+        }
+
+        return builder.toString();
+    }
+}
--- a/src/main/java/de/unixwork/uwproj/Feature.java	Thu Nov 13 18:46:08 2025 +0100
+++ b/src/main/java/de/unixwork/uwproj/Feature.java	Fri Nov 14 15:09:37 2025 +0100
@@ -6,17 +6,15 @@
 
 import static de.unixwork.uwproj.Util.shId;
 
-public final class Feature {
+public final class Feature extends AbstractOption {
     private final String name;
-    private final String arg;
     private final boolean auto;
     private final TargetData targetData;
     private final TargetData disabled = new TargetData();
-    private String desc = "";
 
     public Feature(Element e) {
+        super(Util.getAttrOrDefault(e, "arg", e.getAttribute("name")));
         name = e.getAttribute("name");
-        arg = Util.getAttrOrDefault(e, "arg", name);
         auto = Boolean.parseBoolean(e.getAttribute("default"));
         targetData = new TargetData(e);
 
@@ -68,102 +66,8 @@
         return auto;
     }
 
-    /**
-     * Generates help text for the feature option.
-     * <p>
-     * If {@link #isAuto()} returns true, the option name will be --disable-${@link #getArg()}, otherwise
-     * it will be --enable-${@link #getArg()}, preceded by two spaces, respectively.
-     * <p>
-     * If no description is available via {@link #getDesc()}, only the option name is generated. Otherwise,
-     * description is added according to the following rules:
-     * <p>
-     * When the option name does not consume more than 25 characters, the description starts in the same line.
-     * Otherwise, a line break is added, first. The description will be placed in a block starting from column
-     * 27 to 80 and automatically break when necessary. The description must not contain a single word
-     * that is longer than 54 characters, or it will break the layout.
-     *
-     * @return a help text for terminal output
-     */
-    public String getHelpText() {
-        final var builder = new StringBuilder();
-
-        // Compute option name
-        final var opt = (auto ? "  --disable-" : "  --enable-")+arg;
-
-        // Add option name
-        builder.append(opt);
-
-        // Stop, if there is no description
-        if (desc.isBlank()) return builder.toString();
-
-        // Prepare the description by replacing some unwanted spaces
-        final var hdesc = desc.trim()
-                .replaceAll("\\r", "")
-                .replaceAll("\\t", "  ");
-
-        // Declare our frame where the description shall be placed
-        final int startx = 26;
-        final int endx = 80;
-
-        // Move to startx (break, if we already past that)
-        if (opt.length() >= startx) {
-            builder.append("\n");
-            builder.append(" ".repeat(startx));
-        } else {
-            builder.append(" ".repeat(startx-opt.length()));
-        }
-
-        // Append the description keeping the layout intact
-        int x = startx;
-        for (int i = 0 ; i < hdesc.length() ;) {
-            // get the next word and see where we would end
-            int s = hdesc.indexOf(' ', i);
-            if (s < 0) s = hdesc.length();
-            int n = hdesc.indexOf('\n', i);
-            if (n < 0) n = hdesc.length();
-            s = Math.min(s, n);
-            int l = s-i;
-            if (x + l > endx) {
-                // does not fit, break into next line
-                builder.append('\n');
-                builder.append(" ".repeat(startx));
-                x = startx;
-            }
-            // append the word
-            builder.append(hdesc, i, s);
-            x += l;
-            i += l;
-            // append the next spaces
-            while (i < hdesc.length()) {
-                int c = hdesc.charAt(i);
-                if (c == ' ') {
-                    // as long as we don't need to break, add the spaces
-                    i++;
-                    x++;
-                    if (x < endx) builder.append(' ');
-                } else if (c == '\n') {
-                    // if user wants us to break, comply
-                    i++;
-                    // if we have still space, just move to the end of the line
-                    if (x < endx) {
-                        x = endx;
-                    } else {
-                        // otherwise, we need to add an extra blank line
-                        builder.append('\n');
-                    }
-                } else {
-                    // we have found the next word, so continue with the outer loop
-                    break;
-                }
-            }
-            // break to new line, when spaces moved us outside the frame
-            if (x > endx) {
-                builder.append('\n');
-                builder.append(" ".repeat(startx));
-                x = startx;
-            }
-        }
-
-        return builder.toString();
+    @Override
+    public String getHelpTextArg() {
+        return (auto ? "--disable-" : "--enable-")+arg;
     }
 }
--- a/src/main/java/de/unixwork/uwproj/Option.java	Thu Nov 13 18:46:08 2025 +0100
+++ b/src/main/java/de/unixwork/uwproj/Option.java	Fri Nov 14 15:09:37 2025 +0100
@@ -8,16 +8,15 @@
 
 import static de.unixwork.uwproj.Util.shId;
 
-public final class Option {
-    private final String arg;
-
+public final class Option extends AbstractOption {
     private final LinkedList<OptionValue> values = new LinkedList<>();
     private final LinkedList<OptionDefault> defaults = new LinkedList<>();
 
     public Option(Element element) {
-        arg = element.getAttribute("arg");
+        super(element.getAttribute("arg"));
         Util.getChildElements(element).forEach(elm -> {
             switch (elm.getNodeName()) {
+                case "desc" -> desc = Util.getContent(elm);
                 case "value" -> values.add(new OptionValue(this,
                         elm.getAttribute("str"),
                         new TargetData(elm)));
@@ -28,10 +27,22 @@
         });
     }
 
-    public String getArgument() {
+    @Override
+    public String getDesc() {
+        return desc;
+    }
+
+    @Override
+    public String getArg() {
         return arg;
     }
 
+    @Override
+    public String getHelpTextArg() {
+        return "--" + arg + "=" + getValuesString();
+    }
+
+    @Override
     public String getVarName() {
         return shId("OPT_" + arg.toUpperCase());
     }
--- a/src/main/resources/make/configure.vm	Thu Nov 13 18:46:08 2025 +0100
+++ b/src/main/resources/make/configure.vm	Fri Nov 14 15:09:37 2025 +0100
@@ -115,7 +115,7 @@
 
 Options:
 #foreach( $opt in $options )
-  --${opt.argument}=${opt.valuesString}
+${opt.helpText}
 #end
 #end
 #if( $features.size() > 0 )
@@ -215,8 +215,8 @@
         "--debug")            BUILD_TYPE="debug" ;;
         "--release")          BUILD_TYPE="release" ;;
     #foreach( $opt in $options )
-        "--${opt.argument}="*) ${opt.varName}=${D}{ARG#--${opt.argument}=} ;;
-        "--${opt.argument}")  echo "option '$ARG' needs a value:"; echo "  $ARG=${opt.valuesString}"; abort_configure ;;
+        "--${opt.arg}="*) ${opt.varName}=${D}{ARG#--${opt.arg}=} ;;
+        "--${opt.arg}")  echo "option '$ARG' needs a value:"; echo "  $ARG=${opt.valuesString}"; abort_configure ;;
     #end
     #foreach( $feature in $features )
         "--enable-${feature.arg}") ${feature.varName}=on ;;
@@ -670,9 +670,9 @@
 #end
 
 #foreach( $opt in $target.options )
-# Option: --${opt.argument}
+# Option: --${opt.arg}
 if [ -z "${D}${opt.varName}" ]; then
-    echo "auto-detecting option '${opt.argument}'"
+    echo "auto-detecting option '${opt.arg}'"
     SAVED_ERROR="$ERROR"
     SAVED_DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED"
     ERROR=1
@@ -683,7 +683,7 @@
         if isplatform "$optdef.platform"; then
         #end
         if $optdef.func ; then
-            echo "  ${opt.argument}: ${optdef.valueName}" >> "$TEMP_DIR/options"
+            echo "  ${opt.arg}: ${optdef.valueName}" >> "$TEMP_DIR/options"
             ERROR=0
             break
         fi
@@ -695,28 +695,28 @@
     done
     if [ $ERROR -ne 0 ]; then
         SAVED_ERROR=1
-        SAVED_DEPENDENCIES_FAILED="option '${opt.argument}' $SAVED_DEPENDENCIES_FAILED"
+        SAVED_DEPENDENCIES_FAILED="option '${opt.arg}' $SAVED_DEPENDENCIES_FAILED"
     fi
     ERROR="$SAVED_ERROR"
     DEPENDENCIES_FAILED="$SAVED_DEPENDENCIES_FAILED"
 else
-    echo "checking option ${opt.argument} = ${D}${opt.varName}"
+    echo "checking option ${opt.arg} = ${D}${opt.varName}"
     if false; then
         false
     #foreach( $optval in $opt.values )
     elif [ "${D}${opt.varName}" = "${optval.value}" ]; then
-        echo "  ${opt.argument}: ${D}${opt.varName}" >> $TEMP_DIR/options
+        echo "  ${opt.arg}: ${D}${opt.varName}" >> $TEMP_DIR/options
         if $optval.func ; then
             :
         else
             ERROR=1
-            DEPENDENCIES_FAILED="option '${opt.argument}' $DEPENDENCIES_FAILED"
+            DEPENDENCIES_FAILED="option '${opt.arg}' $DEPENDENCIES_FAILED"
         fi
     #end
     else
         echo
         echo "Invalid option value - usage:"
-        echo "  --${opt.argument}=${opt.valuesString}"
+        echo "  --${opt.arg}=${opt.valuesString}"
         abort_configure
     fi
 fi
--- a/src/main/resources/make/uwproj.xsd	Thu Nov 13 18:46:08 2025 +0100
+++ b/src/main/resources/make/uwproj.xsd	Fri Nov 14 15:09:37 2025 +0100
@@ -228,7 +228,9 @@
             <xs:documentation>
                 Declares a configuration option.
                 The option argument name is specified with the <code>arg</code> attribute.
-                Then, the children of this element specify possible <code>values</code> by defining the conditions
+                Optionally, a description for the help text of the resulting configure script can be specified by
+                a <code>desc</code> element.
+                Then, the next children of this element specify possible <code>values</code> by defining the conditions
                 (in terms of dependencies) and effects (in terms of defines and make variables) of each value.
                 Finally, a set of <code>default</code>s is specified which supposed to automagically select the most
                 appropriate value for a specific platform under the available dependencies (in case the option is not
@@ -236,6 +238,7 @@
             </xs:documentation>
         </xs:annotation>
         <xs:sequence>
+            <xs:element name="desc" type="xs:string" minOccurs="0"/>
             <xs:element name="value" type="OptionValueType" minOccurs="0" maxOccurs="unbounded"/>
             <xs:element name="default" type="OptionDefaultType" minOccurs="0" maxOccurs="unbounded"/>
         </xs:sequence>
--- a/test/configure2	Thu Nov 13 18:46:08 2025 +0100
+++ b/test/configure2	Fri Nov 14 15:09:37 2025 +0100
@@ -113,6 +113,7 @@
 
 Options:
   --toolkit=(gtk3|cli|gtk2|wpf)
+                          The toolkit to use for the UI.
 
 Optional Features:
   --disable-db            Needlessly adds a completely useless SQLite database 
--- a/test/make/configure.vm	Thu Nov 13 18:46:08 2025 +0100
+++ b/test/make/configure.vm	Fri Nov 14 15:09:37 2025 +0100
@@ -115,7 +115,7 @@
 
 Options:
 #foreach( $opt in $options )
-  --${opt.argument}=${opt.valuesString}
+${opt.helpText}
 #end
 #end
 #if( $features.size() > 0 )
@@ -215,8 +215,8 @@
         "--debug")            BUILD_TYPE="debug" ;;
         "--release")          BUILD_TYPE="release" ;;
     #foreach( $opt in $options )
-        "--${opt.argument}="*) ${opt.varName}=${D}{ARG#--${opt.argument}=} ;;
-        "--${opt.argument}")  echo "option '$ARG' needs a value:"; echo "  $ARG=${opt.valuesString}"; abort_configure ;;
+        "--${opt.arg}="*) ${opt.varName}=${D}{ARG#--${opt.arg}=} ;;
+        "--${opt.arg}")  echo "option '$ARG' needs a value:"; echo "  $ARG=${opt.valuesString}"; abort_configure ;;
     #end
     #foreach( $feature in $features )
         "--enable-${feature.arg}") ${feature.varName}=on ;;
@@ -670,9 +670,9 @@
 #end
 
 #foreach( $opt in $target.options )
-# Option: --${opt.argument}
+# Option: --${opt.arg}
 if [ -z "${D}${opt.varName}" ]; then
-    echo "auto-detecting option '${opt.argument}'"
+    echo "auto-detecting option '${opt.arg}'"
     SAVED_ERROR="$ERROR"
     SAVED_DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED"
     ERROR=1
@@ -683,7 +683,7 @@
         if isplatform "$optdef.platform"; then
         #end
         if $optdef.func ; then
-            echo "  ${opt.argument}: ${optdef.valueName}" >> "$TEMP_DIR/options"
+            echo "  ${opt.arg}: ${optdef.valueName}" >> "$TEMP_DIR/options"
             ERROR=0
             break
         fi
@@ -695,28 +695,28 @@
     done
     if [ $ERROR -ne 0 ]; then
         SAVED_ERROR=1
-        SAVED_DEPENDENCIES_FAILED="option '${opt.argument}' $SAVED_DEPENDENCIES_FAILED"
+        SAVED_DEPENDENCIES_FAILED="option '${opt.arg}' $SAVED_DEPENDENCIES_FAILED"
     fi
     ERROR="$SAVED_ERROR"
     DEPENDENCIES_FAILED="$SAVED_DEPENDENCIES_FAILED"
 else
-    echo "checking option ${opt.argument} = ${D}${opt.varName}"
+    echo "checking option ${opt.arg} = ${D}${opt.varName}"
     if false; then
         false
     #foreach( $optval in $opt.values )
     elif [ "${D}${opt.varName}" = "${optval.value}" ]; then
-        echo "  ${opt.argument}: ${D}${opt.varName}" >> $TEMP_DIR/options
+        echo "  ${opt.arg}: ${D}${opt.varName}" >> $TEMP_DIR/options
         if $optval.func ; then
             :
         else
             ERROR=1
-            DEPENDENCIES_FAILED="option '${opt.argument}' $DEPENDENCIES_FAILED"
+            DEPENDENCIES_FAILED="option '${opt.arg}' $DEPENDENCIES_FAILED"
         fi
     #end
     else
         echo
         echo "Invalid option value - usage:"
-        echo "  --${opt.argument}=${opt.valuesString}"
+        echo "  --${opt.arg}=${opt.valuesString}"
         abort_configure
     fi
 fi
--- a/test/make/project2.xml	Thu Nov 13 18:46:08 2025 +0100
+++ b/test/make/project2.xml	Fri Nov 14 15:09:37 2025 +0100
@@ -63,6 +63,9 @@
 			</disabled>
 		</feature>
 		<option arg="toolkit">
+            <desc>
+                The toolkit to use for the UI.
+            </desc>
 			<value str="gtk3">
 				<define name="a" value="b" />
 				<dependencies>gtk3</dependencies>
--- a/test/make/uwproj.xsd	Thu Nov 13 18:46:08 2025 +0100
+++ b/test/make/uwproj.xsd	Fri Nov 14 15:09:37 2025 +0100
@@ -228,7 +228,9 @@
             <xs:documentation>
                 Declares a configuration option.
                 The option argument name is specified with the <code>arg</code> attribute.
-                Then, the children of this element specify possible <code>values</code> by defining the conditions
+                Optionally, a description for the help text of the resulting configure script can be specified by
+                a <code>desc</code> element.
+                Then, the next children of this element specify possible <code>values</code> by defining the conditions
                 (in terms of dependencies) and effects (in terms of defines and make variables) of each value.
                 Finally, a set of <code>default</code>s is specified which supposed to automagically select the most
                 appropriate value for a specific platform under the available dependencies (in case the option is not
@@ -236,6 +238,7 @@
             </xs:documentation>
         </xs:annotation>
         <xs:sequence>
+            <xs:element name="desc" type="xs:string" minOccurs="0"/>
             <xs:element name="value" type="OptionValueType" minOccurs="0" maxOccurs="unbounded"/>
             <xs:element name="default" type="OptionDefaultType" minOccurs="0" maxOccurs="unbounded"/>
         </xs:sequence>

mercurial