| // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | /// A CLI tool to generate documentation for packages from pub.dartlang.org. | 
 | library dartdoc.doc_packages; | 
 |  | 
 | import 'dart:async'; | 
 | import 'dart:convert' show JSON, UTF8; | 
 | import 'dart:io'; | 
 |  | 
 | import 'package:args/args.dart'; | 
 | import 'package:http/http.dart' as http; | 
 | import 'package:pub_semver/pub_semver.dart'; | 
 | import 'package:yaml/yaml.dart'; | 
 |  | 
 | // TODO: Use isolates; Platform.numberOfProcessors. | 
 |  | 
 | const String _rootDir = 'pub.dartlang.org'; | 
 |  | 
 | /// To use: | 
 | /// | 
 | ///     dart tool/doc_packages.dart foo_package bar_package | 
 | /// | 
 | /// or: | 
 | /// | 
 | ///     dart tool/doc_packages.dart --list --page=1 | 
 | /// | 
 | /// or: | 
 | /// | 
 | ///     dart tool/doc_packages.dart --generate --page=3 | 
 | /// | 
 | void main(List<String> _args) { | 
 |   var parser = _createArgsParser(); | 
 |   var args = parser.parse(_args); | 
 |  | 
 |   if (args['help']) { | 
 |     _printUsageAndExit(parser); | 
 |   } else if (args['list']) { | 
 |     performList(int.parse(args['page'])); | 
 |   } else if (args['generate']) { | 
 |     performGenerate(int.parse(args['page'])); | 
 |   } else if (args.rest.isEmpty) { | 
 |     _printUsageAndExit(parser, exitCode: 1); | 
 |   } else { | 
 |     // Handle args.rest. | 
 |     generateForPackages(args.rest); | 
 |   } | 
 | } | 
 |  | 
 | ArgParser _createArgsParser() { | 
 |   var parser = new ArgParser(); | 
 |   parser.addFlag('help', | 
 |       abbr: 'h', negatable: false, help: 'Show command help.'); | 
 |   parser.addFlag('list', help: 'Show available pub packages', negatable: false); | 
 |   parser.addFlag('generate', | 
 |       help: 'Generate docs for available pub packages.', negatable: false); | 
 |   parser.addOption('page', | 
 |       help: 'The pub.dartlang.org page to list or generate for.', | 
 |       defaultsTo: '1'); | 
 |   return parser; | 
 | } | 
 |  | 
 | /// Print help if we are passed the help option or invalid arguments. | 
 | void _printUsageAndExit(ArgParser parser, {int exitCode: 0}) { | 
 |   print('Generate documentation for published pub packages.\n'); | 
 |   print('Usage: _doc_packages [OPTIONS] <package1> <package2>\n'); | 
 |   print(parser.usage); | 
 |   exit(exitCode); | 
 | } | 
 |  | 
 | void performList(int page) { | 
 |   print('Listing pub packages for page ${page}'); | 
 |   print(''); | 
 |  | 
 |   _packageUrls(page).then((List<String> packages) { | 
 |     return _getPackageInfos(packages).then((List<PackageInfo> infos) { | 
 |       infos.forEach(print); | 
 |     }); | 
 |   }); | 
 | } | 
 |  | 
 | void performGenerate(int page) { | 
 |   print('Generating docs for page ${page} into ${_rootDir}/'); | 
 |   print(''); | 
 |  | 
 |   _packageUrls(page).then((List<String> packages) { | 
 |     return _getPackageInfos(packages).then((List<PackageInfo> infos) { | 
 |       return Future.forEach(infos, (info) { | 
 |         return _printGenerationResult(info, _generateFor(info)); | 
 |       }); | 
 |     }); | 
 |   }); | 
 | } | 
 |  | 
 | void generateForPackages(List<String> packages) { | 
 |   print('Generating docs into ${_rootDir}/'); | 
 |   print(''); | 
 |  | 
 |   List<String> urls = packages | 
 |       .map((s) => 'https://pub.dartlang.org/packages/${s}.json') | 
 |       .toList(); | 
 |  | 
 |   _getPackageInfos(urls).then((List<PackageInfo> infos) { | 
 |     return Future.forEach(infos, (PackageInfo info) { | 
 |       return _printGenerationResult(info, _generateFor(info)); | 
 |     }); | 
 |   }); | 
 | } | 
 |  | 
 | Future _printGenerationResult(PackageInfo package, Future generationResult) { | 
 |   String name = package.name.padRight(20); | 
 |  | 
 |   return generationResult.then((bool result) { | 
 |     if (result) { | 
 |       print('${name}: passed'); | 
 |     } else { | 
 |       print('${name}: (skipped)'); | 
 |     } | 
 |   }).catchError((e) { | 
 |     print('${name}: * failed *'); | 
 |   }); | 
 | } | 
 |  | 
 | Future<List<String>> _packageUrls(int page) { | 
 |   return http | 
 |       .get('https://pub.dartlang.org/packages.json?page=${page}') | 
 |       .then((response) { | 
 |     return JSON.decode(response.body)['packages']; | 
 |   }); | 
 | } | 
 |  | 
 | Future<List<PackageInfo>> _getPackageInfos(List<String> packageUrls) { | 
 |   List<Future> futures = packageUrls.map((String p) { | 
 |     return http.get(p).then((response) { | 
 |       var json = JSON.decode(response.body); | 
 |       String name = json['name']; | 
 |       List<Version> versions = | 
 |           json['versions'].map((v) => new Version.parse(v)).toList(); | 
 |       return new PackageInfo(name, Version.primary(versions)); | 
 |     }); | 
 |   }).toList(); | 
 |  | 
 |   return Future.wait(futures); | 
 | } | 
 |  | 
 | StringBuffer _logBuffer; | 
 |  | 
 | /// Generate the docs for the given package into _rootDir. Return whether | 
 | /// generation was performed or was skipped (due to an older package). | 
 | Future<bool> _generateFor(PackageInfo package) async { | 
 |   _logBuffer = new StringBuffer(); | 
 |  | 
 |   // Get the package archive (tar zxvf foo.tar.gz). | 
 |   var response = await http.get(package.archiveUrl); | 
 |   if (response.statusCode != 200) throw response; | 
 |  | 
 |   Directory output = new Directory('${_rootDir}/${package.name}'); | 
 |   output.createSync(recursive: true); | 
 |  | 
 |   try { | 
 |     new File(output.path + '/archive.tar.gz') | 
 |         .writeAsBytesSync(response.bodyBytes); | 
 |  | 
 |     await _exec('tar', ['zxvf', 'archive.tar.gz'], | 
 |         cwd: output.path, quiet: true); | 
 |  | 
 |     // Rule out any old packages (old sdk constraints). | 
 |     File pubspecFile = new File(output.path + '/pubspec.yaml'); | 
 |     var pubspecInfo = loadYaml(pubspecFile.readAsStringSync()); | 
 |  | 
 |     // Check for old versions. | 
 |     if (_isOldSdkConstraint(pubspecInfo)) { | 
 |       _log('skipping ${package.name} - non-matching sdk constraint'); | 
 |       return false; | 
 |     } | 
 |  | 
 |     // Run pub get. | 
 |     await _exec('pub', ['get'], | 
 |         cwd: output.path, timeout: new Duration(seconds: 30)); | 
 |  | 
 |     // Run dartdoc. | 
 |     await _exec('dart', ['../../bin/dartdoc.dart'], cwd: output.path); | 
 |  | 
 |     return true; | 
 |   } catch (e, st) { | 
 |     _log(e.toString()); | 
 |     _log(st.toString()); | 
 |     rethrow; | 
 |   } finally { | 
 |     new File(output.path + '/output.txt') | 
 |         .writeAsStringSync(_logBuffer.toString()); | 
 |   } | 
 | } | 
 |  | 
 | Future _exec(String command, List<String> args, | 
 |     {String cwd, | 
 |     bool quiet: false, | 
 |     Duration timeout: const Duration(seconds: 60)}) { | 
 |   return Process | 
 |       .start(command, args, workingDirectory: cwd) | 
 |       .then((Process process) { | 
 |     if (!quiet) { | 
 |       process.stdout.listen((bytes) => _log(UTF8.decode(bytes))); | 
 |       process.stderr.listen((bytes) => _log(UTF8.decode(bytes))); | 
 |     } | 
 |  | 
 |     Future f = process.exitCode.then((code) { | 
 |       if (code != 0) throw code; | 
 |     }); | 
 |  | 
 |     if (timeout != null) { | 
 |       return f.timeout(timeout, onTimeout: () { | 
 |         _log('Timing out operation ${command}.'); | 
 |         process.kill(); | 
 |         throw 'timeout on ${command}'; | 
 |       }); | 
 |     } else { | 
 |       return f; | 
 |     } | 
 |   }); | 
 | } | 
 |  | 
 | bool _isOldSdkConstraint(var pubspecInfo) { | 
 |   var environment = pubspecInfo['environment']; | 
 |   if (environment != null) { | 
 |     var sdk = environment['sdk']; | 
 |     if (sdk != null) { | 
 |       VersionConstraint constraint = new VersionConstraint.parse(sdk); | 
 |       String version = Platform.version; | 
 |       if (version.contains(' ')) version = | 
 |           version.substring(0, version.indexOf(' ')); | 
 |       if (!constraint.allows(new Version.parse(version))) { | 
 |         _log('sdk constraint = ${constraint}'); | 
 |         return true; | 
 |       } else { | 
 |         return false; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | void _log(String str) { | 
 |   _logBuffer.write(str); | 
 | } | 
 |  | 
 | class PackageInfo { | 
 |   final String name; | 
 |   final Version version; | 
 |  | 
 |   PackageInfo(this.name, this.version); | 
 |  | 
 |   String get archiveUrl => | 
 |       'https://storage.googleapis.com/pub.dartlang.org/packages/${name}-${version}.tar.gz'; | 
 |  | 
 |   String toString() => '${name}, ${version}'; | 
 | } |