├── analysis_options.yaml ├── pubspec.yaml ├── .gitignore ├── LICENSE ├── README.md └── bin └── tags.dart /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | analyzer: 3 | strong-mode: 4 | implicit-casts: false 5 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | homepage: https://github.com/nerdrew/dart-ctags 2 | description: ctags compatible tag file generator for dart code 3 | name: dart_ctags 4 | version: 1.2.1 5 | environment: 6 | sdk: ">=2.0.0 <3.0.0" 7 | executables: 8 | dart_ctags: tags 9 | dependencies: 10 | analyzer: ^0.40.0 11 | path: ^1.7.0 12 | quiver: ^2.1.3 13 | args: ^1.6.0 14 | dev_dependencies: 15 | pedantic: ^1.9.2 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/dart 3 | # Edit at https://www.gitignore.io/?templates=dart 4 | 5 | ### Dart ### 6 | # See https://www.dartlang.org/guides/libraries/private-files 7 | 8 | # Files and directories created by pub 9 | .dart_tool/ 10 | .packages 11 | build/ 12 | # If you're building an application, you may want to check-in your pubspec.lock 13 | pubspec.lock 14 | 15 | # Directory created by dartdoc 16 | # If you don't generate documentation locally you can remove this line. 17 | doc/api/ 18 | 19 | # Avoid committing generated Javascript files: 20 | *.dart.js 21 | *.info.json # Produced by the --dump-info flag. 22 | *.js # When generated by dart2js. Don't specify *.js if your 23 | # project includes source files written in JavaScript. 24 | *.js_ 25 | *.js.deps 26 | *.js.map 27 | 28 | # End of https://www.gitignore.io/api/dart 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrew Lazarus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vim compatible tags file for dart. 2 | > Now with native [Tagbar](https://github.com/majutsushi/tagbar) support! 3 | 4 | 5 | ## Example 6 | 7 | ### Install from pub 8 | ``` bash 9 | $ pub global activate dart_ctags 10 | ``` 11 | 12 | ### Install from Github 13 | ``` bash 14 | $ git clone https://github.com/nerdrew/dart-ctags.git 15 | $ cd dart-ctags 16 | $ pub global activate -s path . 17 | ``` 18 | 19 | ### Recommended Post-Installation 20 | 21 | > The installation of the `dart_ctags` executable from pub is not a compiled 22 | > binary. These steps will overwrite the bash script placed by pub in the bin 23 | > folder for dart_ctags with a natively compiled bin that is significantly 24 | > faster. 25 | 26 | ##### Installed from pub 27 | 28 | ``` bash 29 | $ cd $(find ~/.pub-cache/hosted -type d -name "dart_ctags*") && pub get 30 | $ dart2native bin/tags.dart -o ~/.pub-cache/bin/dart_ctags 31 | ``` 32 | 33 | ##### Installed from Github 34 | 35 | ``` bash 36 | $ cd ~/git_repo_of_dart_ctags 37 | $ dart2native bin/tags.dart -o ~/.pub-cache/bin/dart_ctags 38 | ``` 39 | 40 | ### Use 41 | ``` bash 42 | # make sure that pub-cache/bin is in your path 43 | # export PATH="$PATH":"$HOME/.pub-cache/bin" 44 | 45 | $ cd ~/dev/your-dart-project 46 | $ dart_ctags -l -o .git/tags 47 | ``` 48 | 49 | ### Use with Tagbar (the vim plugin) 50 | 51 | If you have `~/.pub-cache/bin` in your `$PATH`, then [tagbar](https://github.com/majutsushi/tagbar) should Just Work™ 52 | (i.e. it should detect `dart_ctags`). Otherwise, add this to your vim config: 53 | 54 | ```vimscript 55 | let g:tagbar_type_dart = { 'ctagsbin': '~/.pub-cache/bin/dart_ctags' } 56 | ``` 57 | 58 | ## Help 59 | 60 | ``` bash 61 | dart_ctags -h 62 | Usage: 63 | dart_ctags [OPTIONS] [FILES...] 64 | pub global run dart_ctags:tags [OPTIONS] [FILES...] 65 | 66 | -o, --output= Output file for tags (default: stdout) 67 | --follow-links Follow symbolic links (default: false) 68 | --include-hidden Include hidden directories (default: false) 69 | -l, --line-numbers Add line numbers to extension fields (default: false) 70 | --skip-sort Skip sorting the output (default: false) 71 | -h, --help Show this help 72 | ``` 73 | -------------------------------------------------------------------------------- /bin/tags.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:analyzer/dart/analysis/results.dart'; 5 | import 'package:analyzer/dart/ast/ast.dart'; 6 | import 'package:analyzer/dart/analysis/features.dart'; 7 | import 'package:analyzer/dart/analysis/utilities.dart' as an; 8 | import 'package:path/path.dart' as path; 9 | import 'package:args/args.dart'; 10 | 11 | class Ctags { 12 | ArgResults options; 13 | 14 | // /./ in dart/js does not match newlines, /[^]/ matches . + \n 15 | RegExp klass = RegExp(r'^[^]+?($|{)'); 16 | RegExp enumeration = RegExp(r'^[^]+?($|{)'); 17 | RegExp miXin = RegExp(r'^[^]+?($|{)'); 18 | RegExp constructor = RegExp(r'^[^]+?($|{|;)'); 19 | RegExp method = RegExp(r'^[^]+?($|{|;|\=>)'); 20 | 21 | Ctags(this.options); 22 | 23 | String _parseFieldType(String input) { 24 | // const int test = 1; 25 | // final List> list = [{'a': 1}]; 26 | var leftHandSide = input.split('=')[0]; 27 | 28 | // const int test 29 | // final List> list 30 | var varTypeList = leftHandSide 31 | .split(' ') 32 | .where((s) => s != 'const' && s != 'final' && s != 'static') 33 | .join(' ') 34 | .trim() 35 | .split(' '); 36 | 37 | // [int, test] 38 | // [List>, list] 39 | varTypeList.removeLast(); 40 | 41 | // [int] 42 | // [List>] 43 | return varTypeList.join(' '); 44 | } 45 | 46 | void generate() { 47 | Iterable dirs; 48 | 49 | if (options.rest.isEmpty) { 50 | dirs = ['.']; 51 | } else { 52 | dirs = options.rest; 53 | } 54 | 55 | final lines = [ 56 | '!_TAG_FILE_FORMAT\t2\t/extended format; --format=1 will not append ;" to lines/', 57 | '!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/' 58 | ]; 59 | 60 | Future.wait(dirs.map(addFileSystemEntity)) 61 | .then((Iterable>> files) { 62 | files.forEach((Iterable> file) { 63 | file.forEach((Iterable fileLines) => lines.addAll(fileLines)); 64 | }); 65 | 66 | if (!(options['skip-sort'] as bool)) { 67 | lines.sort(); 68 | } 69 | if (options['output'] != null) { 70 | File(options['output'] as String).writeAsString(lines.join('\n')); 71 | } else { 72 | print(lines.join('\n')); 73 | } 74 | }); 75 | } 76 | 77 | Future>> addFileSystemEntity(String name) async { 78 | final type = FileSystemEntity.typeSync(name); 79 | 80 | if (type == FileSystemEntityType.directory) { 81 | return Future.wait(await Directory(name) 82 | .list(recursive: true, followLinks: options['follow-links'] as bool) 83 | .map((file) async { 84 | if (file is File && path.extension(file.path) == '.dart') { 85 | return await parseFile(file); 86 | } else { 87 | return []; 88 | } 89 | }).toList()); 90 | } else if (type == FileSystemEntityType.file) { 91 | return Future.value([await parseFile(File(name))]); 92 | } else if (type == FileSystemEntityType.link && 93 | options['follow-links'] as bool) { 94 | return addFileSystemEntity(Link(name).targetSync()); 95 | } else { 96 | return Future.value([]); 97 | } 98 | } 99 | 100 | Future> parseFile(File file) async { 101 | if (!(options['include-hidden'] as bool) && 102 | path.split(file.path).any((name) => name[0] == '.' && name != '.')) { 103 | return []; 104 | } 105 | 106 | String root; 107 | if (options['output'] != null) { 108 | root = path.relative(path.dirname(options['output'] as String)); 109 | } else { 110 | root = '.'; 111 | } 112 | 113 | final lines = >[]; 114 | ParseStringResult result; 115 | CompilationUnit unit; 116 | try { 117 | result = 118 | an.parseFile(path: file.path, featureSet: FeatureSet.fromEnableFlags([])); 119 | unit = result.unit; 120 | } catch (e) { 121 | print('ERROR: unable to generate tags for ${file.path}'); 122 | return lines.map((line) => line.join('\t').trimRight()); 123 | } 124 | 125 | if (unit.directives.any((d) => d is ImportDirective)) { 126 | lines.add([ 127 | 'import', 128 | path.relative(file.path, from: root), 129 | '/import/;"', 130 | 'i', 131 | options['line-numbers'] as bool 132 | ? 'line:${unit.lineInfo.getLocation(unit.directives[0].offset).lineNumber}' 133 | : '', 134 | 'type:directives', 135 | ]); 136 | } 137 | 138 | // import, export, part, part of, library directives 139 | await Future.forEach(unit.directives, (Directive d) async { 140 | String tag, importDirective, display; 141 | 142 | if (d is ImportDirective) { 143 | display = d.childEntities 144 | .where((element) => '$element' != 'import' && '$element' != ';') 145 | .join(' ') 146 | .trim(); 147 | 148 | if (display.contains('dart:')) { 149 | tag = 'D'; 150 | } else if (display.contains('package:')) { 151 | tag = 'U'; 152 | } else { 153 | // local 154 | tag = 'L'; 155 | } 156 | importDirective = 'directive:import'; 157 | } else if (d is ExportDirective) { 158 | tag = 't'; 159 | display = d.childEntities 160 | .where((element) => '$element' != 'export' && '$element' != ';') 161 | .join(' ') 162 | .trim(); 163 | } else if (d is PartDirective) { 164 | tag = 'P'; 165 | display = d.childEntities 166 | .where((element) => '$element' != 'part' && '$element' != ';') 167 | .join(' ') 168 | .trim(); 169 | } else if (d is PartOfDirective) { 170 | tag = 'p'; 171 | display = d.childEntities 172 | .where((element) => 173 | '$element' != 'part' && '$element' != 'of' && '$element' != ';') 174 | .join(' ') 175 | .trim(); 176 | } else if (d is LibraryDirective) { 177 | tag = 'l'; 178 | display = d.childEntities 179 | .where((element) => '$element' != 'library' && '$element' != ';') 180 | .join(' ') 181 | .trim(); 182 | } 183 | 184 | // rm quotes 185 | display = display.replaceAll(RegExp(r"\'"), '').replaceAll(RegExp(r'\"'), ''); 186 | 187 | lines.add([ 188 | display, 189 | path.relative(file.path, from: root), 190 | '/^;"', 191 | tag, 192 | options['line-numbers'] as bool 193 | ? 'line:${unit.lineInfo.getLocation(d.offset).lineNumber}' 194 | : '', 195 | importDirective 196 | ]); 197 | }); 198 | 199 | unit.declarations.forEach((declaration) { 200 | if (declaration is MixinDeclaration) { 201 | lines.add([ 202 | declaration.name.name, 203 | path.relative(file.path, from: root), 204 | '/${miXin.matchAsPrefix(declaration.toSource())[0]}/;"', 205 | 'x', 206 | 'access:${declaration.name.name[0] == '_' ? 'private' : 'public'}', 207 | options['line-numbers'] as bool 208 | ? 'line:${unit.lineInfo.getLocation(declaration.offset).lineNumber}' 209 | : '', 210 | 'type:mixin', 211 | ]); 212 | 213 | declaration.members.forEach((member) { 214 | if (member is ConstructorDeclaration) { 215 | String name; 216 | int offset; 217 | if (member.name == null) { 218 | name = declaration.name.name; 219 | offset = declaration.offset; 220 | } else { 221 | name = member.name.name; 222 | offset = member.offset; 223 | } 224 | 225 | lines.add([ 226 | name, 227 | path.relative(file.path, from: root), 228 | '/${constructor.matchAsPrefix(member.toSource())[0]}/;"', 229 | 'r', 230 | 'access:${name[0] == '_' ? 'private' : 'public'}', 231 | options['line-numbers'] as bool 232 | ? 'line:${unit.lineInfo.getLocation(offset).lineNumber}' 233 | : '', 234 | 'mixin:{declaration.name}', 235 | 'signature:${member.parameters.toString()}', 236 | ]); 237 | } else if (member is FieldDeclaration) { 238 | member.fields.variables.forEach((variable) { 239 | var memberSource = member.toSource(); 240 | 241 | lines.add([ 242 | variable.name.name, 243 | path.relative(file.path, from: root), 244 | '/${memberSource}/;"', 245 | 'f', 246 | 'access:${variable.name.name[0] == '_' ? 'private' : 'public'}', 247 | options['line-numbers'] as bool 248 | ? 'line:${unit.lineInfo.getLocation(member.offset).lineNumber}' 249 | : '', 250 | 'mixin:{declaration.name}', 251 | 'type:${_parseFieldType(memberSource)}' 252 | ]); 253 | }); 254 | } else if (member is MethodDeclaration) { 255 | var tag = 'm'; 256 | if (member.isStatic) { 257 | tag = 'M'; 258 | } 259 | // better if static is least preferred 260 | if (member.isOperator) { 261 | tag = 'o'; 262 | } 263 | if (member.isGetter) { 264 | tag = 'g'; 265 | } 266 | if (member.isSetter) { 267 | tag = 's'; 268 | } 269 | if (member.isAbstract) { 270 | tag = 'a'; 271 | } 272 | 273 | var memberSource = member.toSource(); 274 | 275 | lines.add([ 276 | member.name.name, 277 | path.relative(file.path, from: root), 278 | '/${method.matchAsPrefix(memberSource)[0]}/;"', 279 | tag, 280 | 'access:${member.name.name[0] == '_' ? 'private' : 'public'}', 281 | options['line-numbers'] as bool 282 | ? 'line:${unit.lineInfo.getLocation(member.offset).lineNumber}' 283 | : '', 284 | 'mixin:${declaration.name}', 285 | 'signature:${tag == 'g' ? '' : member.parameters.toString()}', 286 | 'type:${member.returnType.toString()}' 287 | ]); 288 | } 289 | }); 290 | } else if (declaration is FunctionDeclaration) { 291 | lines.add([ 292 | declaration.name.name, 293 | path.relative(file.path, from: root), 294 | '/^;"', 295 | 'F', 296 | 'access:${declaration.name.name[0] == '_' ? 'private' : 'public'}', 297 | options['line-numbers'] as bool 298 | ? 'line:${unit.lineInfo.getLocation(declaration.offset).lineNumber}' 299 | : '', 300 | 'signature:${declaration.functionExpression.parameters.toString()}', 301 | 'type:${declaration.returnType.toString()}' 302 | ]); 303 | } else if (declaration is TopLevelVariableDeclaration) { 304 | var varType = declaration.variables.type.toString(); 305 | var isConst = declaration.variables.isConst; 306 | 307 | declaration.variables.variables.asMap().values.forEach((v) { 308 | lines.add([ 309 | v.name.name, 310 | path.relative(file.path, from: root), 311 | '/^;"', 312 | '${isConst ? 'C' : 'v'}', 313 | 'access:${v.name.name[0] == '_' ? 'private' : 'public'}', 314 | options['line-numbers'] as bool 315 | ? 'line:${unit.lineInfo.getLocation(declaration.offset).lineNumber}' 316 | : '', 317 | 'type:${varType == 'null' ? isConst ? '' : declaration.variables.keyword.toString() : varType}' 318 | ]); 319 | }); 320 | } else if (declaration is EnumDeclaration) { 321 | lines.add([ 322 | declaration.name.name, 323 | path.relative(file.path, from: root), 324 | '/${enumeration.matchAsPrefix(declaration.toSource())[0]}/;"', 325 | 'E', 326 | 'access:${declaration.name.name[0] == '_' ? 'private' : 'public'}', 327 | options['line-numbers'] as bool 328 | ? 'line:${unit.lineInfo.getLocation(declaration.offset).lineNumber}' 329 | : '', 330 | 'type:enum', 331 | ]); 332 | declaration.constants.forEach((constant) { 333 | String name; 334 | int offset; 335 | 336 | if (constant.name == null) { 337 | name = declaration.name.name; 338 | offset = declaration.offset; 339 | } else { 340 | name = constant.name.name; 341 | offset = constant.offset; 342 | } 343 | lines.add([ 344 | name, 345 | path.relative(file.path, from: root), 346 | '/${enumeration.matchAsPrefix(constant.toSource())[0]}/;"', 347 | 'e', 348 | options['line-numbers'] as bool 349 | ? 'line:${unit.lineInfo.getLocation(offset).lineNumber}' 350 | : '', 351 | 'enum:${declaration.name}', 352 | ]); 353 | }); 354 | } else if (declaration is ClassDeclaration) { 355 | lines.add([ 356 | declaration.name.name, 357 | path.relative(file.path, from: root), 358 | '/${klass.matchAsPrefix(declaration.toSource())[0]}/;"', 359 | 'c', 360 | 'access:${declaration.name.name[0] == '_' ? 'private' : 'public'}', 361 | options['line-numbers'] as bool 362 | ? 'line:${unit.lineInfo.getLocation(declaration.offset).lineNumber}' 363 | : '', 364 | 'type:${declaration.isAbstract ? 'abstract ' : ''}class', 365 | ]); 366 | 367 | // extends tag 368 | declaration.extendsClause?.childEntities?.forEach((c) { 369 | switch (c.toString()) { 370 | case 'extends': 371 | break; 372 | default: 373 | lines.add([ 374 | '$c', 375 | path.relative(file.path, from: root), 376 | '/$c/;"', 377 | 'd', 378 | options['line-numbers'] as bool 379 | ? 'line:${unit.lineInfo.getLocation(declaration.offset).lineNumber}' 380 | : '', 381 | 'class:${declaration.name}', 382 | ]); 383 | } 384 | }); 385 | 386 | // with tag 387 | declaration.withClause?.childEntities?.forEach((c) { 388 | switch (c.toString()) { 389 | case 'with': 390 | break; 391 | default: 392 | lines.add([ 393 | '$c', 394 | path.relative(file.path, from: root), 395 | '/$c/;"', 396 | 'w', 397 | options['line-numbers'] as bool 398 | ? 'line:${unit.lineInfo.getLocation(declaration.offset).lineNumber}' 399 | : '', 400 | 'class:${declaration.name}', 401 | ]); 402 | } 403 | }); 404 | 405 | // implements tag 406 | declaration.implementsClause?.childEntities?.forEach((c) { 407 | switch (c.toString()) { 408 | case 'implements': 409 | break; 410 | default: 411 | lines.add([ 412 | '$c', 413 | path.relative(file.path, from: root), 414 | '/$c/;"', 415 | 'z', 416 | options['line-numbers'] as bool 417 | ? 'line:${unit.lineInfo.getLocation(declaration.offset).lineNumber}' 418 | : '', 419 | 'class:${declaration.name}', 420 | ]); 421 | } 422 | }); 423 | 424 | declaration.members.forEach((member) { 425 | if (member is ConstructorDeclaration) { 426 | String name; 427 | int offset; 428 | if (member.name == null) { 429 | name = declaration.name.name; 430 | offset = declaration.offset; 431 | } else { 432 | name = member.name.name; 433 | offset = member.offset; 434 | } 435 | 436 | lines.add([ 437 | name, 438 | path.relative(file.path, from: root), 439 | '/${constructor.matchAsPrefix(member.toSource())[0]}/;"', 440 | 'r', 441 | 'access:${name[0] == '_' ? 'private' : 'public'}', 442 | options['line-numbers'] as bool 443 | ? 'line:${unit.lineInfo.getLocation(offset).lineNumber}' 444 | : '', 445 | 'class:${declaration.name}', 446 | 'signature:${member.parameters.toString()}', 447 | ]); 448 | } else if (member is FieldDeclaration) { 449 | member.fields.variables.forEach((variable) { 450 | var memberSource = member.toSource(); 451 | 452 | lines.add([ 453 | variable.name.name, 454 | path.relative(file.path, from: root), 455 | '/${memberSource}/;"', 456 | 'f', 457 | 'access:${variable.name.name[0] == '_' ? 'private' : 'public'}', 458 | options['line-numbers'] as bool 459 | ? 'line:${unit.lineInfo.getLocation(member.offset).lineNumber}' 460 | : '', 461 | 'class:${declaration.name}', 462 | 'type:${_parseFieldType(memberSource)}' 463 | ]); 464 | }); 465 | } else if (member is MethodDeclaration) { 466 | var tag = 'm'; 467 | if (member.isStatic) { 468 | tag = 'M'; 469 | } 470 | // better if static is least preferred 471 | if (member.isOperator) { 472 | tag = 'o'; 473 | } 474 | if (member.isGetter) { 475 | tag = 'g'; 476 | } 477 | if (member.isSetter) { 478 | tag = 's'; 479 | } 480 | if (member.isAbstract) { 481 | tag = 'a'; 482 | } 483 | 484 | var memberSource = member.toSource(); 485 | 486 | lines.add([ 487 | member.name.name, 488 | path.relative(file.path, from: root), 489 | '/${method.matchAsPrefix(memberSource)[0]}/;"', 490 | tag, 491 | 'access:${member.name.name[0] == '_' ? 'private' : 'public'}', 492 | options['line-numbers'] as bool 493 | ? 'line:${unit.lineInfo.getLocation(member.offset).lineNumber}' 494 | : '', 495 | 'class:${declaration.name}', 496 | 'signature:${tag == 'g' ? '' : member.parameters.toString()}', 497 | 'type:${member.returnType.toString()}' 498 | ]); 499 | } 500 | }); 501 | } 502 | }); 503 | // eliminate \t string termination 504 | return lines.map((line) => line.join('\t').trimRight()); 505 | } 506 | } 507 | 508 | void main([List args]) { 509 | final parser = ArgParser(); 510 | parser.addOption('output', 511 | abbr: 'o', help: 'Output file for tags (default: stdout)', valueHelp: 'FILE'); 512 | parser.addFlag('follow-links', 513 | help: 'Follow symbolic links (default: false)', negatable: false); 514 | parser.addFlag('include-hidden', 515 | help: 'Include hidden directories (default: false)', negatable: false); 516 | parser.addFlag('line-numbers', 517 | abbr: 'l', 518 | help: 'Add line numbers to extension fields (default: false)', 519 | negatable: false); 520 | parser.addFlag('skip-sort', 521 | help: 'Skip sorting the output (default: false)', negatable: false); 522 | parser.addFlag('help', abbr: 'h', help: 'Show this help', negatable: false); 523 | final options = parser.parse(args); 524 | if (options['help'] as bool) { 525 | print( 526 | 'Usage:\n\tdart_ctags [OPTIONS] [FILES...]\n\tpub global run dart_ctags:tags [OPTIONS] [FILES...]\n'); 527 | print(parser.usage); 528 | exit(0); 529 | } 530 | Ctags(options).generate(); 531 | } 532 | --------------------------------------------------------------------------------