View Javadoc

1   /*
2    * This file is part of hyphenType. hyphenType is free software: you can
3    * redistribute it and/or modify it under the terms of the GNU General Public
4    * License as published by the Free Software Foundation, either version 3 of the
5    * License, or (at your option) any later version. hyphenType is distributed in
6    * the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
8    * the GNU General Public License for more details. You should have received a
9    * copy of the GNU General Public License along with hyphenType. If not, see
10   * <http://www.gnu.org/licenses/>.
11   */
12  package org.hyphenType.documentation.lib;
13  
14  import java.io.ByteArrayOutputStream;
15  import java.io.PrintStream;
16  import java.lang.annotation.ElementType;
17  import java.lang.annotation.Retention;
18  import java.lang.annotation.RetentionPolicy;
19  import java.lang.annotation.Target;
20  import java.util.StringTokenizer;
21  
22  import org.hyphenType.datastructure.Options;
23  import org.hyphenType.datastructure.parser.option.StructureOption;
24  import org.hyphenType.datastructure.parser.option.StructureOptionArgument;
25  import org.hyphenType.datastructure.parser.simple.StructureSimpleArgument;
26  import org.hyphenType.documentation.Description;
27  import org.hyphenType.documentation.DocumentationFormatterEngine;
28  import org.hyphenType.exit.StatusCode;
29  import org.hyphenType.lexerparser.LexerParser;
30  import org.hyphenType.util.soc.StringParsingError;
31  
32  /**
33   * A documentation formatter inspired on the standard Unix way to show
34   * documentation in the command line. This documentation is typically invoked
35   * in Unix-like systems using --help or -h.
36   * 
37   * @author Aurelio Akira M. Matsui
38   * @param <T>
39   */
40  public class StandardFormatterEngine<T extends Options<?>> extends DocumentationFormatterEngine<T, StandardFormatterEngine.StandardFormatter> {
41  
42      /**
43       * Annotation to configure the {@link StandardFormatterEngine}.
44       * 
45       * @author Aurelio Akira M. Matsui
46       */
47      @Retention(RetentionPolicy.RUNTIME)
48      @Target(ElementType.TYPE)
49      public @interface StandardFormatter {
50  
51          /**
52           * The default value of the property {@link StandardFormatter#descriptionIndent()}.
53           */
54          final int DEFAULT_DESCRIPTION_INDENT = 20;
55          
56          /**
57           * The default value of the property {@link StandardFormatter#maxColumn()}.
58           */
59          final int DEFAULT_MAX_COLUMN = 80;
60          
61          /**
62           * The label that represents the executing program.
63           */
64          @Description("The label that represents the executing program.")
65          String program() default "program";
66  
67          /**
68           * The "usage" label.
69           */
70          @Description("The \"usage\" label.")
71          String usage() default "usage";
72  
73          /**
74           * The "options" label.
75           */
76          @Description("The \"options\" label.")
77          String options() default "options";
78  
79          /**
80           * The "Status code" label.
81           */
82          @Description("The \"Status code\" label.")
83          String statusCode() default "Status code";
84  
85          /**
86           * Whether lines should only break at spaces or if lines can break in the middle of a word.
87           */
88          @Description("Whether lines should only break at spaces or if lines can break in the middle of a word.")
89          boolean lineBreakAtSpace() default true;
90  
91          /**
92           * The number of characters before the description text.
93           */
94          @Description("The number of characters before the description text.")
95          int descriptionIndent() default DEFAULT_DESCRIPTION_INDENT;
96  
97          /**
98           * The maximum number of columns before a line break. This value should be larger than descriptionIndent.
99           */
100         @Description("The maximum number of columns before a line break.\nThis value should be larger than descriptionIndent.")
101         int maxColumn() default DEFAULT_MAX_COLUMN;
102     }
103 
104     @SuppressWarnings("unchecked")
105     @Override
106     public final void printDocumentation(final PrintStream pw, final LexerParser<T> lexPar, final StandardFormatter annotation) {
107 
108         try {
109 
110             pw.format("%s: %s ", annotation.usage(), annotation.program());
111             if (lexPar.getParsedOptions().size() != 0) {
112                 pw.format("[%s] ", annotation.options());
113             }
114 
115             for (StructureSimpleArgument simpleArgument : lexPar.getSimpleArguments()) {
116                 if (!simpleArgument.isMandatory()) {
117                     pw.print("[");
118                 }
119                 pw.print(getOptionsInterfaceValue(simpleArgument.getName(), simpleArgument.getName()));
120                 if (simpleArgument.method.getReturnType().isArray()) {
121                     pw.print("...");
122                 }
123                 if (!simpleArgument.isMandatory()) {
124                     pw.print("]");
125                 }
126                 pw.print(" ");
127             }
128 
129             pw.println();
130             pw.println(print("", getOptionsInterfaceValue("", lexPar.getArgsObject().description()), 0, 0, annotation.maxColumn(), annotation.lineBreakAtSpace()));
131 
132             String simpleArgumentDescriptions = "";
133             for (StructureSimpleArgument simpleArgument : lexPar.getSimpleArguments()) {
134                 simpleArgumentDescriptions += print(simpleArgument.getName(), simpleArgument.getDescription(), 2, annotation.descriptionIndent(), annotation.maxColumn(), annotation.lineBreakAtSpace()) + "\n";
135             }
136             if (simpleArgumentDescriptions.length() > 0) {
137                 pw.println();
138                 pw.println("Arguments");
139                 pw.println();
140                 pw.print(simpleArgumentDescriptions);
141             }
142 
143             pw.println();
144             pw.println("Options");
145             pw.println();
146 
147             for (StructureOption parsedOption : lexPar.getParsedOptions()) {
148                 String optionName = "";
149                 for (String alternative : parsedOption.alternatives) {
150                     if (optionName.contains(lexPar.getArgsObject().singleHyphen())) {
151                         optionName += ", ";
152                     }
153                     if (lexPar.getArgsObject().doubleHyphenInLongOptions() && alternative.length() > 1) {
154                         optionName += lexPar.getArgsObject().doubleHyphen();
155                     } else {
156                         optionName += lexPar.getArgsObject().singleHyphen();
157                     }
158                     optionName += alternative;
159                     if (parsedOption.value != null) {
160                         if (!parsedOption.value.mandatory) {
161                             optionName += "[";
162                         }
163                         optionName += lexPar.getArgsObject().equals() + parsedOption.value.name;
164                         if (!parsedOption.value.mandatory) {
165                             optionName += "]";
166                         }
167                     }
168                 }
169 
170                 for (StructureOptionArgument argument : parsedOption.arguments) {
171                     optionName += " ";
172                     if (!argument.isMandatory()) {
173                         optionName += "[";
174                     } else {
175                         optionName += "<";
176                     }
177                     optionName += argument.getName();
178                     if (argument.method.getReturnType().isArray()) {
179                         optionName += "...";
180                     }
181                     if (!argument.isMandatory()) {
182                         optionName += "]";
183                     } else {
184                         optionName += ">";
185                     }
186                 }
187 
188                 String optionDocumentation = getOptionsInterfaceValue(parsedOption.method.getName(), parsedOption.description);
189                 pw.println(print(optionName, optionDocumentation, 2, annotation.descriptionIndent(), annotation.maxColumn(), annotation.lineBreakAtSpace()));
190             }
191 
192             if (lexPar.getArgsObject().documentStatusCodes()) {
193 
194                 pw.println();
195                 pw.println(annotation.statusCode());
196 
197                 for (StatusCode statusCode : lexPar.getArgsObject().statusCodeEnum().getEnumConstants()) {
198                     Enum<?> enumConstant = Enum.valueOf((Class<? extends Enum>) lexPar.getArgsObject().statusCodeEnum(), statusCode.toString());
199                     pw.println(print(Integer.toString(enumConstant.ordinal()), getStatusCodeUserDescription(enumConstant), 2, annotation.descriptionIndent(), annotation.maxColumn(), annotation.lineBreakAtSpace()));
200                 }
201             }
202         } catch (StringParsingError e) {
203             e.printStackTrace();
204         }
205     }
206 
207     /**
208      * Generates a string that contains the textual representation of a title
209      * and a message, formatting using a certain indent and a maximum column.
210      * Format is as follows (hyphens are used to represent white spaces):
211      * 
212      * <pre>
213      * --Title-------Text-text-text
214      * --------------text-more-text
215      * --------------and-even-more-
216      * --------------text.
217      * </pre>
218      * 
219      * @param title The title of the group of lines.
220      * @param description The description that accompanies the title.
221      * @param titleIndent The indent of the title, in characters, relative to the left margin.
222      * @param descriptionIndent The indent of the description, in characters, relative to the left margin.
223      * @param maxColumn The maximum column to print characters.
224      * @param lineBreakAtSpace Whether lines should break always at a space character (creating a dented effect), or at any character (making the block look like a rectangle).
225      * @return A string representation of the title and description, formatted according with descriptionIndent, maxColumn, and lineBreakAtSpace.
226      */
227     public final String print(final String title, final String description, final int titleIndent, final int descriptionIndent, final int maxColumn, final boolean lineBreakAtSpace) {
228 
229         ByteArrayOutputStream baos = new ByteArrayOutputStream();
230         PrintStream pw = new PrintStream(baos);
231 
232         String line = space(titleIndent) + title;
233         if (2 + title.length() > descriptionIndent - 1) {
234             pw.println(line);
235             line = space(descriptionIndent);
236         } else {
237             line += space(descriptionIndent - line.length());
238         }
239 
240         if (lineBreakAtSpace) {
241             StringTokenizer st = new StringTokenizer(description.trim());
242             while (st.hasMoreTokens()) {
243                 String word = st.nextToken();
244                 if (line.length() + word.length() > maxColumn) {
245 
246                     if (word.length() > maxColumn - descriptionIndent) {
247                         while (word.length() > maxColumn - descriptionIndent) {
248                             // Word is larger than the space available for text.
249                             // We have no other choice but to cut the word.
250                             int removedRange = maxColumn - line.length();
251                             line += word.substring(0, removedRange);
252                             pw.println(line);
253                             word = word.substring(removedRange, word.length());
254                             line = space(descriptionIndent);
255                         }
256                         if (word.length() > 0) {
257                             line += word;
258                         }
259                         if (st.hasMoreTokens() && line.length() + 1 <= maxColumn) {
260                             line += " ";
261                         }
262                     } else {
263                         pw.println(line);
264                         line = space(descriptionIndent) + word;
265                         if (st.hasMoreTokens() && line.length() + 1 <= maxColumn) {
266                             line += " ";
267                         }
268                     }
269                 } else {
270                     line += word;
271                     if (st.hasMoreTokens() && line.length() + 1 <= maxColumn) {
272                         line += " ";
273                     }
274                 }
275             }
276         } else {
277             for (char b : description.toCharArray()) {
278                 if (line.equals("")) {
279                     line = space(descriptionIndent);
280                     pw.println();
281                 }
282                 line += b;
283                 if (line.length() == maxColumn) {
284                     pw.print(line);
285                     line = "";
286                 }
287             }
288         }
289         if (line.length() > descriptionIndent) {
290             pw.print(line);
291         }
292         return new String(baos.toByteArray());
293     }
294 
295     /**
296      * Creates a string with n space characters.
297      * 
298      * @param n How many space characters in the string.
299      * @return A string with n space characters.
300      */
301     private String space(final int n) {
302         String s = "";
303         for (int i = 0; i < n; i++) {
304             s += " ";
305         }
306         return s;
307     }
308 }