Fri, 14 Nov 2025 15:09:37 +0100
add description for options
resolves #738 and resolves #739
--- /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>