--- a/src/main/java/de/unixwork/uwproj/Feature.java Sun Jan 07 11:05:24 2024 +0100 +++ b/src/main/java/de/unixwork/uwproj/Feature.java Fri Jan 19 20:48:26 2024 +0100 @@ -1,6 +1,8 @@ package de.unixwork.uwproj; import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import java.util.List; @@ -11,6 +13,7 @@ private String name; private String arg; private boolean auto; + private String desc; private TargetData targetData; @@ -18,22 +21,35 @@ Feature feature = new Feature(); String name = e.getAttribute("name"); String arg = e.getAttribute("arg"); - String auto = e.getAttribute("default"); if (name.isBlank()) { throw new Exception("feature element requires name attribute"); } feature.setName(name); - feature.setAuto(Boolean.parseBoolean(auto)); if (arg.isBlank()) { feature.setArg(name); } else { feature.setArg(arg); } + feature.setAuto(Boolean.parseBoolean(e.getAttribute("default"))); feature.setTargetData(TargetData.parse(e)); project.addFeature(feature); + + // TODO: when Option also receives this feature, we might move this to TargetData.parse() + NodeList nodes = e.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element elm = (Element) node; + String n = elm.getNodeName(); + if (n.equals("desc")) { + feature.setDesc(Util.getContent(elm)); + } + } + } + return feature; } @@ -73,6 +89,14 @@ this.arg = arg; } + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + public boolean isAuto() { return auto; } @@ -88,4 +112,85 @@ public void setTargetData(TargetData targetData) { this.targetData = targetData; } + + /** + * 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 == null || desc.isBlank()) return builder.toString(); + + // Prepare the description by replacing some unwanted spaces + final var hdesc = desc.trim() + .replaceAll("\\r", "") + .replaceAll("\\n", " ") + .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 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() && hdesc.charAt(i) == ' ') { + i++; + x++; + // as long as we don't need to break, add the spaces + if (x < endx) builder.append(' '); + } + // 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(); + } }