src/main/java/de/unixwork/uwproj/Feature.java

Fri, 19 Jan 2024 20:48:26 +0100

author
Mike Becker <universe@uap-core.de>
date
Fri, 19 Jan 2024 20:48:26 +0100
changeset 97
1af1bfacd25a
parent 79
ef0635b78b7b
child 98
4bf47ea3fc64
permissions
-rw-r--r--

add helptext descriptions for features

fixes #314 and fixes #315

package de.unixwork.uwproj;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.List;

import static de.unixwork.uwproj.Util.isNotNullOrBlank;
import static de.unixwork.uwproj.Util.shId;

public class Feature {
    private String name;
    private String arg;
    private boolean auto;
    private String desc;

    private TargetData targetData;

    public static Feature parse(Project project, Element e) throws Exception {
        Feature feature = new Feature();
        String name = e.getAttribute("name");
        String arg = e.getAttribute("arg");

        if (name.isBlank()) {
            throw new Exception("feature element requires name attribute");
        }

        feature.setName(name);
        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;
    }

    public String getVarName() {
        return shId("FEATURE_" + name.toUpperCase());
    }

    public List<String> getDependencies() {
        return getTargetData().getDependencies();
    }

    public List<Define> getDefines() {
        return getTargetData().getDefines();
    }

    public String getMake() {
        return targetData.getMake();
    }

    public boolean hasMake() {
        return isNotNullOrBlank(targetData.getMake());
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getArg() {
        return arg;
    }

    public void setArg(String arg) {
        this.arg = arg;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public boolean isAuto() {
        return auto;
    }

    public void setAuto(boolean auto) {
        this.auto = auto;
    }

    public TargetData getTargetData() {
        return targetData;
    }

    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();
    }
}

mercurial