    001/*
    002  Licensed to the Apache Software Foundation (ASF) under one or more
    003  contributor license agreements.  See the NOTICE file distributed with
    004  this work for additional information regarding copyright ownership.
    005  The ASF licenses this file to You under the Apache License, Version 2.0
    006  (the "License"); you may not use this file except in compliance with
    007  the License.  You may obtain a copy of the License at
    008
    009      https://www.apache.org/licenses/LICENSE-2.0
    010
    011  Unless required by applicable law or agreed to in writing, software
    012  distributed under the License is distributed on an "AS IS" BASIS,
    013  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014  See the License for the specific language governing permissions and
    015  limitations under the License.
    016 */
    017package org.apache.commons.cli.help;
    018
    019import java.util.function.Supplier;
    020
    021/**
    022 * The definition for styling recommendations blocks of text. Most common usage is to style columns in a table, but may also be used to specify default styling
    023 * for a {@link HelpAppendable}. HelpWriters are free to ignore the TextStyle recommendations particularly where they are not supported or contradict common
    024 * usage.
    025 *
    026 * @since 1.10.0
    027 */
    028public final class TextStyle {
    029
    030    /**
    031     * The alignment possibilities.
    032      */
    033    public enum Alignment {
    034
    035        /**
    036         * Left justifies the text.
    037         */
    038        LEFT,
    039
    040        /**
    041         * Centers the text.
    042         */
    043        CENTER,
    044
    045        /**
    046         * Right justifies the text.
    047         */
    048        RIGHT
    049    }
    050
    051    /**
    052     * The builder for the TextStyle. The default values are:
    053     * <ul>
    054     * <li>alignment = LEFT</li>
    055     * <li>leftPad = 0</li>
    056     * <li>scaling = VARIABLE</li>
    057     * <li>minWidth = 0</li>
    058     * <li>maxWidth = UNSET_MAX_WIDTH</li>
    059     * </ul>
    060     */
    061    public static final class Builder implements Supplier<TextStyle> {
    062
    063        /** The alignment. */
    064        private Alignment alignment = Alignment.LEFT;
    065
    066        /** The left padding. */
    067        private int leftPad;
    068
    069        /** The subsequent line indentation. */
    070        private int indent;
    071
    072        /** The scalable flag. Identifies text blocks that can be made narrower or wider as needed by the HelpAppendable. */
    073        private boolean scalable = true;
    074
    075        /** The minimum width. */
    076        private int minWidth;
    077
    078        /** The maximum width. */
    079        private int maxWidth = UNSET_MAX_WIDTH;
    080
    081        /**
    082         * Constructs a new instance. The default values are:
    083         * <ul>
    084         * <li>alignment = LEFT</li>
    085         * <li>leftPad = 0</li>
    086         * <li>scaling = VARIABLE</li>
    087         * <li>minWidth = 0</li>
    088         * <li>maxWidth = UNSET_MAX_WIDTH</li>
    089         * </ul>
    090         */
    091        private Builder() {
    092        }
    093
    094        @Override
    095        public TextStyle get() {
    096            return new TextStyle(this);
    097        }
    098
    099        /**
    100         * Gets the currently specified indent value.
    101         *
    102         * @return The currently specified indent value.
    103         */
    104        public int getIndent() {
    105            return indent;
    106        }
    107
    108        /**
    109         * Gets the currently specified leftPad.
    110         *
    111         * @return The currently specified leftPad.
    112         */
    113        public int getLeftPad() {
    114            return leftPad;
    115        }
    116
    117        /**
    118         * Gets the currently specified maximum width value.
    119         *
    120         * @return The currently specified maximum width value.
    121         */
    122        public int getMaxWidth() {
    123            return maxWidth;
    124        }
    125
    126        /**
    127         * Gets the currently specified minimum width value.
    128         *
    129         * @return The currently specified minimum width value.
    130         */
    131        public int getMinWidth() {
    132            return minWidth;
    133        }
    134
    135        /**
    136         * Specifies if the column can be made wider or to narrower width to fit constraints of the HelpAppendable and formatting.
    137         *
    138         * @return The currently specified scaling value.
    139         */
    140        public boolean isScalable() {
    141            return scalable;
    142        }
    143
    144        /**
    145         * Sets the alignment.
    146         *
    147         * @param alignment the desired alignment.
    148         * @return this
    149         */
    150        public Builder setAlignment(final Alignment alignment) {
    151            this.alignment = alignment;
    152            return this;
    153        }
    154
    155        /**
    156         * Sets the indent value.
    157         *
    158         * @param indent the new indent value.
    159         * @return this
    160         */
    161        public Builder setIndent(final int indent) {
    162            this.indent = indent;
    163            return this;
    164        }
    165
    166        /**
    167         * Sets the left padding.
    168         *
    169         * @param leftPad the new left padding.
    170         * @return this
    171         */
    172        public Builder setLeftPad(final int leftPad) {
    173            this.leftPad = leftPad;
    174            return this;
    175        }
    176
    177        /**
    178         * Sets the currently specified minimum width.
    179         *
    180         * @param maxWidth The currently specified maximum width.
    181         * @return this
    182         */
    183        public Builder setMaxWidth(final int maxWidth) {
    184            this.maxWidth = maxWidth;
    185            return this;
    186        }
    187
    188        /**
    189         * Sets the currently specified minimum width.
    190         *
    191         * @param minWidth The currently specified minimum width.
    192         * @return this
    193         */
    194        public Builder setMinWidth(final int minWidth) {
    195            this.minWidth = minWidth;
    196            return this;
    197        }
    198
    199        /**
    200         * Sets whether the column can be made wider or to narrower width to fit constraints of the HelpAppendable and formatting.
    201         *
    202         * @param scalable Whether the text width can be adjusted.
    203         * @return this instance.
    204         */
    205        public Builder setScalable(final boolean scalable) {
    206            this.scalable = scalable;
    207            return this;
    208        }
    209
    210        /**
    211         * Sets all properties from the given text style.
    212         *
    213         * @param style the source text style.
    214         * @return this instance.
    215         */
    216        public Builder setTextStyle(final TextStyle style) {
    217            this.alignment = style.alignment;
    218            this.leftPad = style.leftPad;
    219            this.indent = style.indent;
    220            this.scalable = style.scalable;
    221            this.minWidth = style.minWidth;
    222            this.maxWidth = style.maxWidth;
    223            return this;
    224        }
    225
    226    }
    227
    228    /**
    229     * The unset value for maxWidth: {@value}.
    230     */
    231    public static final int UNSET_MAX_WIDTH = Integer.MAX_VALUE;
    232
    233    /**
    234     * The default style as generated by the default Builder.
    235     */
    236    public static final TextStyle DEFAULT = builder().get();
    237
    238    /**
    239     * Creates a new builder.
    240     *
    241     * @return a new builder.
    242     */
    243    public static Builder builder() {
    244        return new Builder();
    245    }
    246
    247    /** The alignment. */
    248    private final Alignment alignment;
    249
    250    /** The size of the left pad. This is placed before each line of text. */
    251    private final int leftPad;
    252
    253    /** The size of the indent on the second and any subsequent lines of text. */
    254    private final int indent;
    255
    256    /** The scaling allowed for the block. */
    257    private final boolean scalable;
    258
    259    /** The minimum size of the text. */
    260    private final int minWidth;
    261
    262    /** The maximum size of the text. */
    263    private final int maxWidth;
    264
    265    /**
    266     * Constructs a new instance.
    267     *
    268     * @param builder the builder to build the text style from.
    269     */
    270    private TextStyle(final Builder builder) {
    271        this.alignment = builder.alignment;
    272        this.leftPad = builder.leftPad;
    273        this.indent = builder.indent;
    274        this.scalable = builder.scalable;
    275        this.minWidth = builder.minWidth;
    276        this.maxWidth = builder.maxWidth;
    277    }
    278
    279    /**
    280     * Gets the alignment.
    281     *
    282     * @return the alignment.
    283     */
    284    public Alignment getAlignment() {
    285        return alignment;
    286    }
    287
    288    /**
    289     * Gets the indent value.
    290     *
    291     * @return the indent value.
    292     */
    293    public int getIndent() {
    294        return indent;
    295    }
    296
    297    /**
    298     * Gets the left padding.
    299     *
    300     * @return the left padding.
    301     */
    302    public int getLeftPad() {
    303        return leftPad;
    304    }
    305
    306    /**
    307     * gets the maximum width.
    308     *
    309     * @return The maximum width.
    310     */
    311    public int getMaxWidth() {
    312        return maxWidth;
    313    }
    314
    315    /**
    316     * gets the minimum width.
    317     *
    318     * @return The minimum width.
    319     */
    320    public int getMinWidth() {
    321        return minWidth;
    322    }
    323
    324    /**
    325     * Specifies if the column can be made wider or to narrower width to fit constraints of the HelpAppendable and formatting.
    326     *
    327     * @return the scaling value.
    328     */
    329    public boolean isScalable() {
    330        return scalable;
    331    }
    332
    333    /**
    334     * Pads a string to the maximum width or optionally to the maximum width - indent.
    335     * <ul>
    336     * <li>Returns the string unchanged if it is longer than the specified length.</li>
    337     * <li>Will add the padding based on the alignment.</li>
    338     * </ul>
    339     *
    340     * @param addIndent if {@code true} account for the indent when padding the string.
    341     * @param text      the text to pad.
    342     * @return the padded string.
    343     */
    344    public CharSequence pad(final boolean addIndent, final CharSequence text) {
    345        if (text.length() >= maxWidth) {
    346            return text;
    347        }
    348        String indentPad;
    349        String rest;
    350        final StringBuilder sb = new StringBuilder();
    351        switch (alignment) {
    352        case CENTER:
    353            int padLen;
    354            if (maxWidth == UNSET_MAX_WIDTH) {
    355                padLen = addIndent ? indent : 0;
    356            } else {
    357                padLen = maxWidth - text.length();
    358            }
    359            final int left = padLen / 2;
    360            indentPad = Util.repeatSpace(left);
    361            rest = Util.repeatSpace(padLen - left);
    362            sb.append(indentPad).append(text).append(rest);
    363            break;
    364        case LEFT:
    365        case RIGHT:
    366        default: // default should never happen. It is here to keep code coverage happy.
    367            if (maxWidth == UNSET_MAX_WIDTH) {
    368                indentPad = addIndent ? Util.repeatSpace(indent) : "";
    369                rest = "";
    370            } else {
    371                int restLen = maxWidth - text.length();
    372                if (addIndent && restLen > indent) {
    373                    indentPad = Util.repeatSpace(indent);
    374                    restLen -= indent;
    375                } else {
    376                    indentPad = "";
    377                }
    378                rest = Util.repeatSpace(restLen);
    379            }
    380
    381            if (alignment == Alignment.LEFT) {
    382                sb.append(indentPad).append(text).append(rest);
    383            } else {
    384                sb.append(indentPad).append(rest).append(text);
    385            }
    386            break;
    387        }
    388        return sb.toString();
    389    }
    390
    391    @Override
    392    public String toString() {
    393        return String.format("TextStyle{%s, l:%s, i:%s, %s, min:%s, max:%s}", alignment, leftPad, indent, scalable, minWidth,
    394                maxWidth == UNSET_MAX_WIDTH ? "unset" : maxWidth);
    395    }
    396}