| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env dart | |
| 2 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 3 // for details. All rights reserved. Use of this source code is governed by a | |
| 4 // BSD-style license that can be found in the LICENSE file. | |
| 5 | |
| 6 import 'dart:convert'; | |
| 7 import 'dart:io'; | |
| 8 | |
| 9 import 'package:args/args.dart'; | |
| 10 import 'package:path/path.dart' as path; | |
| 11 | |
| 12 import 'package:analyzer/src/services/formatter_impl.dart'; | |
| 13 | |
| 14 const BINARY_NAME = 'dartfmt'; | |
| 15 final dartFileRegExp = new RegExp(r'^[^.].*\.dart$', caseSensitive: false); | |
| 16 final argParser = _initArgParser(); | |
| 17 final defaultSelection = new Selection(-1, -1); | |
| 18 | |
| 19 var formatterSettings; | |
| 20 | |
| 21 CodeKind kind; | |
| 22 bool machineFormat; | |
| 23 bool overwriteFileContents; | |
| 24 Selection selection; | |
| 25 final List<String> paths = []; | |
| 26 | |
| 27 const HELP_FLAG = 'help'; | |
| 28 const KIND_FLAG = 'kind'; | |
| 29 const MACHINE_FLAG = 'machine'; | |
| 30 const WRITE_FLAG = 'write'; | |
| 31 const SELECTION_FLAG = 'selection'; | |
| 32 const TRANSFORM_FLAG = 'transform'; | |
| 33 const MAX_LINE_FLAG = 'max_line_length'; | |
| 34 const INDENT_FLAG = 'indent'; | |
| 35 | |
| 36 const FOLLOW_LINKS = false; | |
| 37 | |
| 38 main(args) { | |
| 39 var options = argParser.parse(args); | |
| 40 if (options['help']) { | |
| 41 _printUsage(); | |
| 42 return; | |
| 43 } | |
| 44 | |
| 45 _readOptions(options); | |
| 46 | |
| 47 if (options.rest.isEmpty) { | |
| 48 _formatStdin(kind); | |
| 49 } else { | |
| 50 paths.addAll(options.rest); | |
| 51 _formatPaths(paths); | |
| 52 } | |
| 53 } | |
| 54 | |
| 55 _readOptions(options) { | |
| 56 kind = _parseKind(options[KIND_FLAG]); | |
| 57 machineFormat = options[MACHINE_FLAG]; | |
| 58 overwriteFileContents = options[WRITE_FLAG]; | |
| 59 selection = _parseSelection(options[SELECTION_FLAG]); | |
| 60 formatterSettings = new FormatterOptions( | |
| 61 codeTransforms: options[TRANSFORM_FLAG], | |
| 62 tabsForIndent: _parseTabsForIndent(options[INDENT_FLAG]), | |
| 63 spacesPerIndent: _parseSpacesPerIndent(options[INDENT_FLAG]), | |
| 64 pageWidth: _parseLineLength(options[MAX_LINE_FLAG])); | |
| 65 } | |
| 66 | |
| 67 /// Translate the indent option into spaces per indent. | |
| 68 int _parseSpacesPerIndent(String indentOption) { | |
| 69 if (indentOption == 'tab') { | |
| 70 return 1; | |
| 71 } | |
| 72 int spacesPerIndent = _toInt(indentOption); | |
| 73 if (spacesPerIndent == null) { | |
| 74 throw new FormatterException( | |
| 75 'Indentation is specified as an Integer or ' 'the value "tab".'); | |
| 76 } | |
| 77 return spacesPerIndent; | |
| 78 } | |
| 79 | |
| 80 /// Translate the indent option into tabs for indent. | |
| 81 bool _parseTabsForIndent(String indentOption) => indentOption == 'tab'; | |
| 82 | |
| 83 CodeKind _parseKind(kindOption) { | |
| 84 switch (kindOption) { | |
| 85 case 'stmt': | |
| 86 return CodeKind.STATEMENT; | |
| 87 default: | |
| 88 return CodeKind.COMPILATION_UNIT; | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 int _parseLineLength(String lengthOption) { | |
| 93 var length = _toInt(lengthOption); | |
| 94 if (length == null) { | |
| 95 var val = lengthOption.toUpperCase(); | |
| 96 if (val == 'INF' || val == 'INFINITY') { | |
| 97 length = -1; | |
| 98 } else { | |
| 99 throw new FormatterException( | |
| 100 'Line length is specified as an Integer or ' 'the value "Inf".'); | |
| 101 } | |
| 102 } | |
| 103 return length; | |
| 104 } | |
| 105 | |
| 106 Selection _parseSelection(String selectionOption) { | |
| 107 if (selectionOption == null) return null; | |
| 108 | |
| 109 var units = selectionOption.split(','); | |
| 110 if (units.length == 2) { | |
| 111 var offset = _toInt(units[0]); | |
| 112 var length = _toInt(units[1]); | |
| 113 if (offset != null && length != null) { | |
| 114 return new Selection(offset, length); | |
| 115 } | |
| 116 } | |
| 117 throw new FormatterException( | |
| 118 'Selections are specified as integer pairs ' '(e.g., "(offset, length)".')
; | |
| 119 } | |
| 120 | |
| 121 int _toInt(str) => int.parse(str, onError: (_) => null); | |
| 122 | |
| 123 _formatPaths(paths) { | |
| 124 paths.forEach((path) { | |
| 125 if (FileSystemEntity.isDirectorySync(path)) { | |
| 126 _formatDirectory(new Directory(path)); | |
| 127 } else { | |
| 128 _formatFile(new File(path)); | |
| 129 } | |
| 130 }); | |
| 131 } | |
| 132 | |
| 133 _formatResource(resource) { | |
| 134 if (resource is Directory) { | |
| 135 _formatDirectory(resource); | |
| 136 } else if (resource is File) { | |
| 137 _formatFile(resource); | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 _formatDirectory(dir) => dir | |
| 142 .listSync(followLinks: FOLLOW_LINKS) | |
| 143 .forEach((resource) => _formatResource(resource)); | |
| 144 | |
| 145 _formatFile(file) { | |
| 146 if (_isDartFile(file)) { | |
| 147 if (_isPatchFile(file) && !paths.contains(file.path)) { | |
| 148 _log('Skipping patch file "${file.path}"'); | |
| 149 return; | |
| 150 } | |
| 151 try { | |
| 152 var rawSource = file.readAsStringSync(); | |
| 153 var formatted = _format(rawSource, CodeKind.COMPILATION_UNIT); | |
| 154 if (overwriteFileContents) { | |
| 155 // Only touch files files whose contents will be changed | |
| 156 if (rawSource != formatted) { | |
| 157 file.writeAsStringSync(formatted); | |
| 158 } | |
| 159 } else { | |
| 160 print(formatted); | |
| 161 } | |
| 162 } catch (e) { | |
| 163 _log('Unable to format "${file.path}": $e'); | |
| 164 } | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 _isPatchFile(file) => file.path.endsWith('_patch.dart'); | |
| 169 | |
| 170 _isDartFile(file) => dartFileRegExp.hasMatch(path.basename(file.path)); | |
| 171 | |
| 172 _formatStdin(kind) { | |
| 173 var input = new StringBuffer(); | |
| 174 stdin.transform(new Utf8Decoder()).listen((data) => input.write(data), | |
| 175 onError: (error) => _log('Error reading from stdin'), | |
| 176 onDone: () => print(_format(input.toString(), kind))); | |
| 177 } | |
| 178 | |
| 179 /// Initialize the arg parser instance. | |
| 180 ArgParser _initArgParser() { | |
| 181 // NOTE: these flags are placeholders only! | |
| 182 var parser = new ArgParser(); | |
| 183 parser.addFlag(WRITE_FLAG, | |
| 184 abbr: 'w', | |
| 185 negatable: false, | |
| 186 help: 'Write reformatted sources to files (overwriting contents). ' | |
| 187 'Do not print reformatted sources to standard output.'); | |
| 188 parser.addFlag(TRANSFORM_FLAG, | |
| 189 abbr: 't', negatable: false, help: 'Perform code transformations.'); | |
| 190 parser.addOption(MAX_LINE_FLAG, | |
| 191 abbr: 'l', defaultsTo: '80', help: 'Wrap lines longer than this length. ' | |
| 192 'To never wrap, specify "Infinity" or "Inf" for short.'); | |
| 193 parser.addOption(INDENT_FLAG, | |
| 194 abbr: 'i', | |
| 195 defaultsTo: '2', | |
| 196 help: 'Specify number of spaces per indentation. ' | |
| 197 'To indent using tabs, specify "--$INDENT_FLAG tab".' '--- [PROVISIONAL AP
I].', | |
| 198 hide: true); | |
| 199 parser.addOption(KIND_FLAG, | |
| 200 abbr: 'k', | |
| 201 defaultsTo: 'cu', | |
| 202 help: 'Specify source snippet kind ("stmt" or "cu") ' '--- [PROVISIONAL AP
I].', | |
| 203 hide: true); | |
| 204 parser.addOption(SELECTION_FLAG, | |
| 205 abbr: 's', help: 'Specify selection information as an offset,length pair ' | |
| 206 '(e.g., -s "0,4").', hide: true); | |
| 207 parser.addFlag(MACHINE_FLAG, | |
| 208 abbr: 'm', | |
| 209 negatable: false, | |
| 210 help: 'Produce output in a format suitable for parsing.'); | |
| 211 parser.addFlag(HELP_FLAG, | |
| 212 abbr: 'h', negatable: false, help: 'Print this usage information.'); | |
| 213 return parser; | |
| 214 } | |
| 215 | |
| 216 /// Displays usage information. | |
| 217 _printUsage() { | |
| 218 var buffer = new StringBuffer(); | |
| 219 buffer | |
| 220 ..write('$BINARY_NAME formats Dart programs.') | |
| 221 ..write('\n\n') | |
| 222 ..write('Without an explicit path, $BINARY_NAME processes the standard ' | |
| 223 'input. Given a file, it operates on that file; given a ' | |
| 224 'directory, it operates on all .dart files in that directory, ' | |
| 225 'recursively. (Files starting with a period are ignored.) By ' | |
| 226 'default, $BINARY_NAME prints the reformatted sources to ' 'standard out
put.') | |
| 227 ..write('\n\n') | |
| 228 ..write('Usage: $BINARY_NAME [flags] [path...]\n\n') | |
| 229 ..write('Supported flags are:\n') | |
| 230 ..write('${argParser.usage}\n\n'); | |
| 231 _log(buffer.toString()); | |
| 232 } | |
| 233 | |
| 234 /// Format this [src], treating it as the given snippet [kind]. | |
| 235 String _format(src, kind) { | |
| 236 var formatResult = new CodeFormatter(formatterSettings).format(kind, src, | |
| 237 selection: selection); | |
| 238 if (machineFormat) { | |
| 239 if (formatResult.selection == null) { | |
| 240 formatResult.selection = defaultSelection; | |
| 241 } | |
| 242 return _toJson(formatResult); | |
| 243 } | |
| 244 return formatResult.source; | |
| 245 } | |
| 246 | |
| 247 _toJson(formatResult) => // Actual JSON format TBD | |
| 248 JSON.encode({ | |
| 249 'source': formatResult.source, | |
| 250 'selection': { | |
| 251 'offset': formatResult.selection.offset, | |
| 252 'length': formatResult.selection.length | |
| 253 } | |
| 254 }); | |
| 255 | |
| 256 /// Log the given [msg]. | |
| 257 _log(String msg) { | |
| 258 //TODO(pquitslund): add proper log support | |
| 259 print(msg); | |
| 260 } | |
| OLD | NEW |