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}