| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file | 
|  | 2 // for details. All rights reserved. Use of this source code is governed by a | 
|  | 3 // BSD-style license that can be found in the LICENSE file. | 
|  | 4 | 
|  | 5 library args.src.usage; | 
|  | 6 | 
|  | 7 import 'dart:math'; | 
|  | 8 | 
|  | 9 import '../args.dart'; | 
|  | 10 | 
|  | 11 /** | 
|  | 12  * Takes an [ArgParser] and generates a string of usage (i.e. help) text for its | 
|  | 13  * defined options. Internally, it works like a tabular printer. The output is | 
|  | 14  * divided into three horizontal columns, like so: | 
|  | 15  * | 
|  | 16  *     -h, --help  Prints the usage information | 
|  | 17  *     |  |        |                                 | | 
|  | 18  * | 
|  | 19  * It builds the usage text up one column at a time and handles padding with | 
|  | 20  * spaces and wrapping to the next line to keep the cells correctly lined up. | 
|  | 21  */ | 
|  | 22 class Usage { | 
|  | 23   static const NUM_COLUMNS = 3; // Abbreviation, long name, help. | 
|  | 24 | 
|  | 25   /** The parser this is generating usage for. */ | 
|  | 26   final ArgParser args; | 
|  | 27 | 
|  | 28   /** The working buffer for the generated usage text. */ | 
|  | 29   StringBuffer buffer; | 
|  | 30 | 
|  | 31   /** | 
|  | 32    * The column that the "cursor" is currently on. If the next call to | 
|  | 33    * [write()] is not for this column, it will correctly handle advancing to | 
|  | 34    * the next column (and possibly the next row). | 
|  | 35    */ | 
|  | 36   int currentColumn = 0; | 
|  | 37 | 
|  | 38   /** The width in characters of each column. */ | 
|  | 39   List<int> columnWidths; | 
|  | 40 | 
|  | 41   /** | 
|  | 42    * The number of sequential lines of text that have been written to the last | 
|  | 43    * column (which shows help info). We track this so that help text that spans | 
|  | 44    * multiple lines can be padded with a blank line after it for separation. | 
|  | 45    * Meanwhile, sequential options with single-line help will be compacted next | 
|  | 46    * to each other. | 
|  | 47    */ | 
|  | 48   int numHelpLines = 0; | 
|  | 49 | 
|  | 50   /** | 
|  | 51    * How many newlines need to be rendered before the next bit of text can be | 
|  | 52    * written. We do this lazily so that the last bit of usage doesn't have | 
|  | 53    * dangling newlines. We only write newlines right *before* we write some | 
|  | 54    * real content. | 
|  | 55    */ | 
|  | 56   int newlinesNeeded = 0; | 
|  | 57 | 
|  | 58   Usage(this.args); | 
|  | 59 | 
|  | 60   /** | 
|  | 61    * Generates a string displaying usage information for the defined options. | 
|  | 62    * This is basically the help text shown on the command line. | 
|  | 63    */ | 
|  | 64   String generate() { | 
|  | 65     buffer = new StringBuffer(); | 
|  | 66 | 
|  | 67     calculateColumnWidths(); | 
|  | 68 | 
|  | 69     args.options.forEach((name, option) { | 
|  | 70       write(0, getAbbreviation(option)); | 
|  | 71       write(1, getLongOption(option)); | 
|  | 72 | 
|  | 73       if (option.help != null) write(2, option.help); | 
|  | 74 | 
|  | 75       if (option.allowedHelp != null) { | 
|  | 76         var allowedNames = option.allowedHelp.keys.toList(); | 
|  | 77         allowedNames.sort(); | 
|  | 78         newline(); | 
|  | 79         for (var name in allowedNames) { | 
|  | 80           write(1, getAllowedTitle(name)); | 
|  | 81           write(2, option.allowedHelp[name]); | 
|  | 82         } | 
|  | 83         newline(); | 
|  | 84       } else if (option.allowed != null) { | 
|  | 85         write(2, buildAllowedList(option)); | 
|  | 86       } else if (option.defaultValue != null) { | 
|  | 87         if (option.isFlag && option.defaultValue == true) { | 
|  | 88           write(2, '(defaults to on)'); | 
|  | 89         } else if (!option.isFlag) { | 
|  | 90           write(2, '(defaults to "${option.defaultValue}")'); | 
|  | 91         } | 
|  | 92       } | 
|  | 93 | 
|  | 94       // If any given option displays more than one line of text on the right | 
|  | 95       // column (i.e. help, default value, allowed options, etc.) then put a | 
|  | 96       // blank line after it. This gives space where it's useful while still | 
|  | 97       // keeping simple one-line options clumped together. | 
|  | 98       if (numHelpLines > 1) newline(); | 
|  | 99     }); | 
|  | 100 | 
|  | 101     return buffer.toString(); | 
|  | 102   } | 
|  | 103 | 
|  | 104   String getAbbreviation(Option option) { | 
|  | 105     if (option.abbreviation != null) { | 
|  | 106       return '-${option.abbreviation}, '; | 
|  | 107     } else { | 
|  | 108       return ''; | 
|  | 109     } | 
|  | 110   } | 
|  | 111 | 
|  | 112   String getLongOption(Option option) { | 
|  | 113     if (option.negatable) { | 
|  | 114       return '--[no-]${option.name}'; | 
|  | 115     } else { | 
|  | 116       return '--${option.name}'; | 
|  | 117     } | 
|  | 118   } | 
|  | 119 | 
|  | 120   String getAllowedTitle(String allowed) { | 
|  | 121     return '      [$allowed]'; | 
|  | 122   } | 
|  | 123 | 
|  | 124   void calculateColumnWidths() { | 
|  | 125     int abbr = 0; | 
|  | 126     int title = 0; | 
|  | 127     args.options.forEach((name, option) { | 
|  | 128       // Make room in the first column if there are abbreviations. | 
|  | 129       abbr = max(abbr, getAbbreviation(option).length); | 
|  | 130 | 
|  | 131       // Make room for the option. | 
|  | 132       title = max(title, getLongOption(option).length); | 
|  | 133 | 
|  | 134       // Make room for the allowed help. | 
|  | 135       if (option.allowedHelp != null) { | 
|  | 136         for (var allowed in option.allowedHelp.keys) { | 
|  | 137           title = max(title, getAllowedTitle(allowed).length); | 
|  | 138         } | 
|  | 139       } | 
|  | 140     }); | 
|  | 141 | 
|  | 142     // Leave a gutter between the columns. | 
|  | 143     title += 4; | 
|  | 144     columnWidths = [abbr, title]; | 
|  | 145   } | 
|  | 146 | 
|  | 147   newline() { | 
|  | 148     newlinesNeeded++; | 
|  | 149     currentColumn = 0; | 
|  | 150     numHelpLines = 0; | 
|  | 151   } | 
|  | 152 | 
|  | 153   write(int column, String text) { | 
|  | 154     var lines = text.split('\n'); | 
|  | 155 | 
|  | 156     // Strip leading and trailing empty lines. | 
|  | 157     while (lines.length > 0 && lines[0].trim() == '') { | 
|  | 158       lines.removeRange(0, 1); | 
|  | 159     } | 
|  | 160 | 
|  | 161     while (lines.length > 0 && lines[lines.length - 1].trim() == '') { | 
|  | 162       lines.removeLast(); | 
|  | 163     } | 
|  | 164 | 
|  | 165     for (var line in lines) { | 
|  | 166       writeLine(column, line); | 
|  | 167     } | 
|  | 168   } | 
|  | 169 | 
|  | 170   writeLine(int column, String text) { | 
|  | 171     // Write any pending newlines. | 
|  | 172     while (newlinesNeeded > 0) { | 
|  | 173       buffer.add('\n'); | 
|  | 174       newlinesNeeded--; | 
|  | 175     } | 
|  | 176 | 
|  | 177     // Advance until we are at the right column (which may mean wrapping around | 
|  | 178     // to the next line. | 
|  | 179     while (currentColumn != column) { | 
|  | 180       if (currentColumn < NUM_COLUMNS - 1) { | 
|  | 181         buffer.add(padRight('', columnWidths[currentColumn])); | 
|  | 182       } else { | 
|  | 183         buffer.add('\n'); | 
|  | 184       } | 
|  | 185       currentColumn = (currentColumn + 1) % NUM_COLUMNS; | 
|  | 186     } | 
|  | 187 | 
|  | 188     if (column < columnWidths.length) { | 
|  | 189       // Fixed-size column, so pad it. | 
|  | 190       buffer.add(padRight(text, columnWidths[column])); | 
|  | 191     } else { | 
|  | 192       // The last column, so just write it. | 
|  | 193       buffer.add(text); | 
|  | 194     } | 
|  | 195 | 
|  | 196     // Advance to the next column. | 
|  | 197     currentColumn = (currentColumn + 1) % NUM_COLUMNS; | 
|  | 198 | 
|  | 199     // If we reached the last column, we need to wrap to the next line. | 
|  | 200     if (column == NUM_COLUMNS - 1) newlinesNeeded++; | 
|  | 201 | 
|  | 202     // Keep track of how many consecutive lines we've written in the last | 
|  | 203     // column. | 
|  | 204     if (column == NUM_COLUMNS - 1) { | 
|  | 205       numHelpLines++; | 
|  | 206     } else { | 
|  | 207       numHelpLines = 0; | 
|  | 208     } | 
|  | 209   } | 
|  | 210 | 
|  | 211   buildAllowedList(Option option) { | 
|  | 212     var allowedBuffer = new StringBuffer(); | 
|  | 213     allowedBuffer.add('['); | 
|  | 214     bool first = true; | 
|  | 215     for (var allowed in option.allowed) { | 
|  | 216       if (!first) allowedBuffer.add(', '); | 
|  | 217       allowedBuffer.add(allowed); | 
|  | 218       if (allowed == option.defaultValue) { | 
|  | 219         allowedBuffer.add(' (default)'); | 
|  | 220       } | 
|  | 221       first = false; | 
|  | 222     } | 
|  | 223     allowedBuffer.add(']'); | 
|  | 224     return allowedBuffer.toString(); | 
|  | 225   } | 
|  | 226 } | 
|  | 227 | 
|  | 228 /** Pads [source] to [length] by adding spaces at the end. */ | 
|  | 229 String padRight(String source, int length) { | 
|  | 230   final result = new StringBuffer(); | 
|  | 231   result.add(source); | 
|  | 232 | 
|  | 233   while (result.length < length) { | 
|  | 234     result.add(' '); | 
|  | 235   } | 
|  | 236 | 
|  | 237   return result.toString(); | 
|  | 238 } | 
| OLD | NEW | 
|---|