Text optionText = ansi().new Text(0); List<OptionSpec> options = new ArrayList<OptionSpec>(commandSpec.options()); // iterate in declaration order if (optionSort != null) { optionText = optionText.concat(" ").concat(colorScheme.optionText(clusteredRequired.toString())); optionText = optionText.concat(" [").concat(colorScheme.optionText(clusteredOptional.toString())).concat("]"); Text param = parameterLabelRenderer().renderParameterLabel(option, colorScheme.ansi(), colorScheme.optionParamStyles); if (option.required()) { // e.g., -x=VAL optionText = optionText.concat(" ").concat(name).concat(param).concat(""); if (option.isMultiValue()) { // e.g., -x=VAL [-x=VAL]... optionText = optionText.concat(" [").concat(name).concat(param).concat("]..."); optionText = optionText.concat(" [").concat(name).concat(param).concat("]"); if (option.isMultiValue()) { // add ellipsis to show option is repeatable optionText = optionText.concat("...");
private Text createLongOptionText(OptionSpec option, IParamLabelRenderer renderer, ColorScheme scheme, String longOption) { Text paramLabelText = renderer.renderParameterLabel(option, scheme.ansi(), scheme.optionParamStyles); // if no long option, fill in the space between the short option name and the param label value if (paramLabelText.length > 0 && longOption.length() == 0) { sep = renderer.separator(); // #181 paramLabelText may be =LABEL or [=LABEL...] int sepStart = paramLabelText.plainString().indexOf(sep); Text prefix = paramLabelText.substring(0, sepStart); paramLabelText = prefix.concat(paramLabelText.substring(sepStart + sep.length())); } Text longOptionText = scheme.optionText(longOption); longOptionText = longOptionText.concat(paramLabelText); return longOptionText; }
private Text[][] renderDescriptionLines(OptionSpec option, ColorScheme scheme, String requiredOption, String shortOption, Text longOptionText) { Text EMPTY = Ansi.EMPTY_TEXT; boolean[] showDefault = {option.internalShowDefaultValue(showDefaultValues)}; List<Text[]> result = new ArrayList<Text[]>(); String[] description = option.renderedDescription(); Text[] descriptionFirstLines = createDescriptionFirstLines(scheme, option, description, showDefault); result.add(new Text[] { scheme.optionText(requiredOption), scheme.optionText(shortOption), scheme.ansi().new Text(sep), longOptionText, descriptionFirstLines[0] }); for (int i = 1; i < descriptionFirstLines.length; i++) { result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] }); } for (int i = 1; i < description.length; i++) { Text[] descriptionNextLines = scheme.ansi().new Text(description[i]).splitLines(); for (Text line : descriptionNextLines) { result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line }); } } if (showDefault[0]) { addTrailingDefaultLine(result, option, scheme); } return result.toArray(new Text[result.size()][]); } }
? copy(BreakIterator.getLineInstance(), value, textAt(row, col), indent) : copy(value, textAt(row, col), indent); value = value.substring(charsWritten); indent = 0; if (value.length > 0) { // value did not fit in column do { int charsWritten = copy(lineBreakIterator, value, textAt(row, col), indent); value = value.substring(charsWritten); indent = column.indent + indentWrappedLines; if (value.length > 0) { // value did not fit in column
addStyledSection(plain.length(), items[1].length(), Style.on(styles), Style.off(reverse(styles)) + Style.reset.off()); plain.append(items[1]);
@Test public void testTextSubString() { Ansi ansi = Ansi.ON; Ansi.Text txt = ansi.new Text("@|bold 01234|@").concat("56").concat("@|underline 7890|@"); assertEquals(ansi.new Text("@|bold 01234|@56@|underline 7890|@"), txt.substring(0)); assertEquals(ansi.new Text("@|bold 1234|@56@|underline 7890|@"), txt.substring(1)); assertEquals(ansi.new Text("@|bold 234|@56@|underline 7890|@"), txt.substring(2)); assertEquals(ansi.new Text("@|bold 34|@56@|underline 7890|@"), txt.substring(3)); assertEquals(ansi.new Text("@|bold 4|@56@|underline 7890|@"), txt.substring(4)); assertEquals(ansi.new Text("56@|underline 7890|@"), txt.substring(5)); assertEquals(ansi.new Text("6@|underline 7890|@"), txt.substring(6)); assertEquals(ansi.new Text("@|underline 7890|@"), txt.substring(7)); assertEquals(ansi.new Text("@|underline 890|@"), txt.substring(8)); assertEquals(ansi.new Text("@|underline 90|@"), txt.substring(9)); assertEquals(ansi.new Text("@|underline 0|@"), txt.substring(10)); assertEquals(ansi.new Text(""), txt.substring(11)); assertEquals(ansi.new Text("@|bold 01234|@56@|underline 7890|@"), txt.substring(0, 11)); assertEquals(ansi.new Text("@|bold 01234|@56@|underline 789|@"), txt.substring(0, 10)); assertEquals(ansi.new Text("@|bold 01234|@56@|underline 78|@"), txt.substring(0, 9)); assertEquals(ansi.new Text("@|bold 01234|@56@|underline 7|@"), txt.substring(0, 8)); assertEquals(ansi.new Text("@|bold 01234|@56"), txt.substring(0, 7)); assertEquals(ansi.new Text("@|bold 01234|@5"), txt.substring(0, 6)); assertEquals(ansi.new Text("@|bold 01234|@"), txt.substring(0, 5)); assertEquals(ansi.new Text("@|bold 0123|@"), txt.substring(0, 4)); assertEquals(ansi.new Text("@|bold 012|@"), txt.substring(0, 3)); assertEquals(ansi.new Text("@|bold 01|@"), txt.substring(0, 2)); assertEquals(ansi.new Text("@|bold 0|@"), txt.substring(0, 1)); assertEquals(ansi.new Text(""), txt.substring(0, 0)); assertEquals(ansi.new Text("@|bold 1234|@56@|underline 789|@"), txt.substring(1, 10)); assertEquals(ansi.new Text("@|bold 234|@56@|underline 78|@"), txt.substring(2, 9));
@Test public void testTextSplitLinesStartEndIntermediate() { Ansi ansi = Ansi.ON; Ansi.Text[] all = { ansi.new Text("\n@|bold 012\n\n\n34|@").concat("5\n\n\nAA\n\n\n6").concat("@|underline 78\n90|@\n"), ansi.new Text("\r@|bold 012\r\r\r34|@").concat("5\r\r\rAA\r\r\r6").concat("@|underline 78\r90|@\r"), ansi.new Text("\r\n@|bold 012\r\n\r\n\r\n34|@").concat("5\r\n\r\n\r\nAA\r\n\r\n\r\n6").concat("@|underline 78\r\n90|@\r\n"), }; for (Ansi.Text text : all) { Ansi.Text[] lines = text.splitLines(); int i = 0; assertEquals(ansi.new Text(""), lines[i++]); assertEquals(ansi.new Text("@|bold 012|@"), lines[i++]); assertEquals(ansi.new Text(""), lines[i++]); assertEquals(ansi.new Text(""), lines[i++]); assertEquals(ansi.new Text("@|bold 34|@5"), lines[i++]); assertEquals(ansi.new Text(""), lines[i++]); assertEquals(ansi.new Text(""), lines[i++]); assertEquals(ansi.new Text("AA"), lines[i++]); assertEquals(ansi.new Text(""), lines[i++]); assertEquals(ansi.new Text(""), lines[i++]); assertEquals(ansi.new Text("6@|underline 78|@"), lines[i++]); assertEquals(ansi.new Text("@|underline 90|@"), lines[i++]); assertEquals(ansi.new Text(""), lines[i++]); } }
private Text[][] renderDescriptionLines(OptionSpec option, ColorScheme scheme, String requiredOption, String shortOption, Text longOptionText) { Text EMPTY = Ansi.EMPTY_TEXT; boolean[] showDefault = {option.internalShowDefaultValue(showDefaultValues)}; List<Text[]> result = new ArrayList<Text[]>(); String[] description = option.renderedDescription(); Text[] descriptionFirstLines = createDescriptionFirstLines(scheme, option, description, showDefault); result.add(new Text[] { scheme.optionText(requiredOption), scheme.optionText(shortOption), scheme.ansi().new Text(sep), longOptionText, descriptionFirstLines[0] }); for (int i = 1; i < descriptionFirstLines.length; i++) { result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] }); } for (int i = 1; i < description.length; i++) { Text[] descriptionNextLines = scheme.ansi().new Text(description[i]).splitLines(); for (Text line : descriptionNextLines) { result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line }); } } if (showDefault[0]) { addTrailingDefaultLine(result, option, scheme); } return result.toArray(new Text[result.size()][]); } }
/** Returns a String representation of the text with ANSI escape codes embedded, unless ANSI is * {@linkplain Ansi#enabled()} not enabled}, in which case the plain text is returned. * @return a String representation of the text with ANSI escape codes embedded (if enabled) */ public String toString() { if (!Ansi.this.enabled()) { return plain.toString().substring(from, from + length); } if (length == 0) { return ""; } StringBuilder sb = new StringBuilder(plain.length() + 20 * sections.size()); StyledSection current = null; int end = Math.min(from + length, plain.length()); for (int i = from; i < end; i++) { StyledSection section = findSectionContaining(i); if (section != current) { if (current != null) { sb.append(current.endStyles); } if (section != null) { sb.append(section.startStyles); } current = section; } sb.append(plain.charAt(i)); } if (current != null) { sb.append(current.endStyles); } return sb.toString(); }
@Test public void testTextSplitLinesStartEnd() { Ansi ansi = Ansi.ON; Ansi.Text[] all = { ansi.new Text("\n@|bold 012\n34|@").concat("5\nAA\n6").concat("@|underline 78\n90|@\n"), ansi.new Text("\r@|bold 012\r34|@").concat("5\rAA\r6").concat("@|underline 78\r90|@\r"), ansi.new Text("\r\n@|bold 012\r\n34|@").concat("5\r\nAA\r\n6").concat("@|underline 78\r\n90|@\r\n"), }; for (Ansi.Text text : all) { Ansi.Text[] lines = text.splitLines(); int i = 0; assertEquals(ansi.new Text(""), lines[i++]); assertEquals(ansi.new Text("@|bold 012|@"), lines[i++]); assertEquals(ansi.new Text("@|bold 34|@5"), lines[i++]); assertEquals(ansi.new Text("AA"), lines[i++]); assertEquals(ansi.new Text("6@|underline 78|@"), lines[i++]); assertEquals(ansi.new Text("@|underline 90|@"), lines[i++]); assertEquals(ansi.new Text(""), lines[i++]); } } @Test
/** Returns a 2-column list with command names and the first line of their header or (if absent) description. * @return a usage help section describing the added commands */ public String commandList() { if (subcommands().isEmpty()) { return ""; } int commandLength = maxLength(subcommands().keySet()); Help.TextTable textTable = Help.TextTable.forColumns(ansi(), new Help.Column(commandLength + 2, 2, Help.Column.Overflow.SPAN), new Help.Column(width() - (commandLength + 2), 2, Help.Column.Overflow.WRAP)); for (Map.Entry<String, Help> entry : subcommands().entrySet()) { Help help = entry.getValue(); UsageMessageSpec usage = help.commandSpec().usageMessage(); String header = !empty(usage.header()) ? usage.header()[0] : (!empty(usage.description()) ? usage.description()[0] : ""); Text[] lines = ansi().text(format(header)).splitLines(); for (int i = 0; i < lines.length; i++) { textTable.addRowValues(i == 0 ? help.commandNamesText(", ") : Ansi.EMPTY_TEXT, lines[i]); } } return textTable.toString(); } private static int maxLength(Collection<String> any) {
public Text[][] render(PositionalParamSpec param, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) { Text label = paramLabelRenderer.renderParameterLabel(param, scheme.ansi(), scheme.parameterStyles); Text requiredParameter = scheme.parameterText(param.arity().min > 0 ? requiredMarker : ""); Text EMPTY = Ansi.EMPTY_TEXT; boolean[] showDefault = {param.internalShowDefaultValue(showDefaultValues)}; List<Text[]> result = new ArrayList<Text[]>(); String[] description = param.renderedDescription(); Text[] descriptionFirstLines = createDescriptionFirstLines(scheme, param, description, showDefault); result.add(new Text[] { requiredParameter, EMPTY, EMPTY, label, descriptionFirstLines[0] }); for (int i = 1; i < descriptionFirstLines.length; i++) { result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] }); } for (int i = 1; i < description.length; i++) { Text[] descriptionNextLines = scheme.ansi().new Text(description[i]).splitLines(); for (Text line : descriptionNextLines) { result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line }); } } if (showDefault[0]) { addTrailingDefaultLine(result, param, scheme); } return result.toArray(new Text[result.size()][]); } }
/** Formats each of the specified values and appends it to the specified StringBuilder. * @param ansi whether the result should contain ANSI escape codes or not * @param usageHelpWidth the width of the usage help message * @param values the values to format and append to the StringBuilder * @param sb the StringBuilder to collect the formatted strings * @param params the parameters to pass to the format method when formatting each value * @return the specified StringBuilder */ public static StringBuilder join(Ansi ansi, int usageHelpWidth, String[] values, StringBuilder sb, Object... params) { if (values != null) { TextTable table = TextTable.forColumnWidths(ansi, usageHelpWidth); table.indentWrappedLines = 0; for (String summaryLine : values) { Text[] lines = ansi.new Text(format(summaryLine, params)).splitLines(); for (Text line : lines) { table.addRowValues(line); } } table.toString(sb); } return sb; } private static String format(String formatString, Object... params) {
/** Returns a copy of this {@code Text} instance with the specified text concatenated to the end. Does not modify this instance! * @param other the text to concatenate to the end of this Text * @return a new Text instance * @since 3.0 */ public Text concat(Text other) { Text result = (Text) clone(); result.plain = new StringBuilder(plain.toString().substring(from, from + length)); result.from = 0; result.sections = new ArrayList<StyledSection>(); for (StyledSection section : sections) { result.sections.add(section.withStartIndex(section.startIndex - from)); } result.plain.append(other.plain.toString().substring(other.from, other.from + other.length)); for (StyledSection section : other.sections) { int index = result.length + section.startIndex - other.from; result.sections.add(section.withStartIndex(index)); } result.length = result.plain.length(); return result; }
private int copy(BreakIterator line, Text text, Text columnValue, int offset) { // Deceive the BreakIterator to ensure no line breaks after '-' character line.setText(text.plainString().replace("-", "\u00ff")); int done = 0; for (int start = line.first(), end = line.next(); end != BreakIterator.DONE; start = end, end = line.next()) { Text word = text.substring(start, end); //.replace("\u00ff", "-"); // not needed if (columnValue.maxLength >= offset + done + length(word)) { done += copy(word, columnValue, offset + done); // TODO messages length } else { break; } } if (done == 0 && length(text) + offset > columnValue.maxLength) { // The value is a single word that is too big to be written to the column. Write as much as we can. done = copy(text, columnValue, offset); } return done; } private static int copy(Text value, Text destination, int offset) {
@Test public void testTextSplitLines() { Ansi ansi = Ansi.ON; Ansi.Text[] all = { ansi.new Text("@|bold 012\n34|@").concat("5\nAA\n6").concat("@|underline 78\n90|@"), ansi.new Text("@|bold 012\r34|@").concat("5\rAA\r6").concat("@|underline 78\r90|@"), ansi.new Text("@|bold 012\r\n34|@").concat("5\r\nAA\r\n6").concat("@|underline 78\r\n90|@"), }; for (Ansi.Text text : all) { Ansi.Text[] lines = text.splitLines(); int i = 0; assertEquals(ansi.new Text("@|bold 012|@"), lines[i++]); assertEquals(ansi.new Text("@|bold 34|@5"), lines[i++]); assertEquals(ansi.new Text("AA"), lines[i++]); assertEquals(ansi.new Text("6@|underline 78|@"), lines[i++]); assertEquals(ansi.new Text("@|underline 90|@"), lines[i++]); } } @Test
public Text[] splitLines() { List<Text> result = new ArrayList<Text>(); int start = 0, end = 0; for (int i = 0; i < plain.length(); i++, end = i) { char c = plain.charAt(i); boolean eol = c == '\n'; if (c == '\r' && i + 1 < plain.length() && plain.charAt(i + 1) == '\n') { eol = true; i++; } // \r\n eol |= c == '\r'; if (eol) { result.add(this.substring(start, end)); start = i + 1; } } // add remainder (may be empty string) result.add(this.substring(start, plain.length())); return result.toArray(new Text[result.size()]); }
/** Returns a Text object containing a partial detailed synopsis showing only the positional parameters, starting with a {@code " "} space. * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}). * @return the formatted positional parameters, starting with a {@code " "} space, or an empty Text if this command has no positional parameters * @since 3.9 */ protected Text createDetailedSynopsisPositionalsText() { Text positionalParamText = ansi().new Text(0); for (PositionalParamSpec positionalParam : commandSpec.positionalParameters()) { if (!positionalParam.hidden()) { positionalParamText = positionalParamText.concat(" "); Text label = parameterLabelRenderer().renderParameterLabel(positionalParam, colorScheme.ansi(), colorScheme.parameterStyles); positionalParamText = positionalParamText.concat(label); } } return positionalParamText; }
private Text createLongOptionText(OptionSpec option, IParamLabelRenderer renderer, ColorScheme scheme, String longOption) { Text paramLabelText = renderer.renderParameterLabel(option, scheme.ansi(), scheme.optionParamStyles); // if no long option, fill in the space between the short option name and the param label value if (paramLabelText.length > 0 && longOption.length() == 0) { sep = renderer.separator(); // #181 paramLabelText may be =LABEL or [=LABEL...] int sepStart = paramLabelText.plainString().indexOf(sep); Text prefix = paramLabelText.substring(0, sepStart); paramLabelText = prefix.concat(paramLabelText.substring(sepStart + sep.length())); } Text longOptionText = scheme.optionText(longOption); longOptionText = longOptionText.concat(paramLabelText); return longOptionText; }
/** Returns a Text object containing a partial detailed synopsis showing only the subcommands, starting with a {@code " "} space. * Follows the unix convention of showing optional elements in square brackets ({@code [ ]}). * @return this implementation returns a hard-coded string {@code " [COMMAND]"} if this command has subcommands, an empty Text otherwise * @since 3.9 */ protected Text createDetailedSynopsisCommandText() { Text commandText = ansi().new Text(0); if (!commandSpec.subcommands().isEmpty()){ commandText = commandText.concat(" [") .concat("COMMAND") .concat("]"); } return commandText; }