diff --git a/bin/dart_doc_syncer.dart b/bin/dart_doc_syncer.dart index e25eafb..c5b8076 100644 --- a/bin/dart_doc_syncer.dart +++ b/bin/dart_doc_syncer.dart @@ -9,34 +9,47 @@ import 'package:dart_doc_syncer/options.dart'; /// Syncs an example application living in the angular.io repository to a /// dedicated repository that will contain a generated cleaned-up version. /// -/// dart_doc_syncer [-h|-n|-v] [ | ] +/// dart_doc_syncer [-h|options] [ | ] Future main(List _args) async { var args = processArgs(_args); - Logger.root.level = dryRun || verbose ? Level.ALL : Level.WARNING; + Logger.root.level = options.verbose ? Level.ALL : Level.WARNING; Logger.root.onRecord.listen((LogRecord rec) { var msg = '${rec.message}'; - if (!dryRun) msg = '${rec.level.name}: ${rec.time}: ' + msg; + if (!options.dryRun && !options.verbose) + msg = '${rec.level.name}: ${rec.time}: ' + msg; print(msg); }); - var path, repositoryUri; + var exampleName, path, repositoryUri; switch (args.length) { + case 0: + if (options.match == null) + printUsageAndExit('No examples specified; name example or use --match'); + // #NotReached + break; case 1: - var e2u = new Example2Uri(args[0]); + exampleName = args[0]; + var e2u = new Example2Uri(exampleName); path = e2u.path; repositoryUri = e2u.repositoryUri; break; case 2: path = args[0]; repositoryUri = args[1]; + exampleName = getExampleName(path); break; default: - printUsageAndExit("Expected 1 or 2 arguments but found ${args.length}"); + printUsageAndExit("Too many arguments (${args.length})"); // #NotReached } final documentation = new DocumentationUpdater(); - await documentation.updateRepository(path, repositoryUri); - - print('Done updating $repositoryUri'); + if (options.match == null) { + await documentation.updateRepository(path, repositoryUri, + clean: !options.keepTmp, exampleName: exampleName, push: options.push); + print('Done updating $repositoryUri'); + } else { + await documentation.updateMatchingRepo(options.match, + clean: !options.keepTmp, push: options.push); + } } diff --git a/bin/sync_all.dart b/bin/sync_all.dart index bd04e86..87210ce 100644 --- a/bin/sync_all.dart +++ b/bin/sync_all.dart @@ -8,10 +8,10 @@ import 'package:logging/logging.dart'; Future main(List _args) async { processArgs(_args); - Logger.root.level = dryRun || verbose ? Level.ALL : Level.WARNING; + Logger.root.level = options.verbose ? Level.ALL : Level.WARNING; Logger.root.onRecord.listen((LogRecord rec) { var msg = '${rec.message}'; - if (!dryRun) msg = '${rec.level.name}: ${rec.time}: ' + msg; + if (!options.dryRun && !options.verbose) msg = '${rec.level.name}: ${rec.time}: ' + msg; print(msg); }); diff --git a/lib/documentation_updater.dart b/lib/documentation_updater.dart index 305ae12..643d212 100644 --- a/lib/documentation_updater.dart +++ b/lib/documentation_updater.dart @@ -10,5 +10,9 @@ abstract class DocumentationUpdater { /// Updates [outRepositoryUri] based on the content of the example under /// [examplePath] in the angular.io repository. Future updateRepository(String examplePath, String outRepositoryUri, - {bool push, bool clean}); + {String exampleName, bool push, bool clean}); + + /// Updates all example repositories containing a doc syncer data file + /// and whose path matches [re]. + Future updateMatchingRepo(RegExp re, {bool push, bool clean}); } diff --git a/lib/example2uri.dart b/lib/example2uri.dart index 531bba8..b6d113d 100644 --- a/lib/example2uri.dart +++ b/lib/example2uri.dart @@ -1,3 +1,5 @@ +import 'package:path/path.dart' as p; + class Example2Uri { final String _exampleName; @@ -8,3 +10,13 @@ class Example2Uri { String get repositoryUri => 'git@github.com:angular-examples/$_exampleName.git'; } + +/// [path] is assumed to be of the form +/// 'public/docs/_examples//dart'; +/// returns . +String getExampleName(String path) { + assert (p.basename(path) == 'dart'); + var name = p.basename(p.dirname(path)); + assert (name.isNotEmpty); + return name; +} diff --git a/lib/src/generate_doc.dart b/lib/src/generate_doc.dart index 5b7d01a..f376ac0 100644 --- a/lib/src/generate_doc.dart +++ b/lib/src/generate_doc.dart @@ -50,7 +50,7 @@ Future assembleDocumentationExample(Directory snapshot, Directory out, /// Rewrites all files under the [path] directory by filtering out the /// documentation tags. Future _removeDocTagsFromApplication(String path) async { - if (Process.dryRun) return new Future.value(null); + if (Process.options.dryRun) return new Future.value(null); final files = await new Directory(path) .list(recursive: true) diff --git a/lib/src/generate_gh_pages.dart b/lib/src/generate_gh_pages.dart index 87d9e97..ff75da3 100644 --- a/lib/src/generate_gh_pages.dart +++ b/lib/src/generate_gh_pages.dart @@ -4,14 +4,13 @@ import 'dart:io'; import 'package:path/path.dart' as p; import 'runner.dart' as Process; // TODO(chalin) tmp name to avoid code changes - -final String _basePath = p.dirname(Platform.script.path); +import 'options.dart'; /// Returns the path to the folder where the application assets have been /// generated. Future generateApplication( Directory example, String exampleName) async { - final applicationPath = p.join(_basePath, '.tmp/${exampleName}_app'); + final applicationPath = p.join(workDirPath, '${exampleName}_app'); // Copy the application code into a seperate folder. await Process.run('cp', ['-a', p.join(example.path, '.'), applicationPath]); diff --git a/lib/src/generate_readme.dart b/lib/src/generate_readme.dart index 74efe34..bee48f3 100644 --- a/lib/src/generate_readme.dart +++ b/lib/src/generate_readme.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:dart_doc_syncer/example2uri.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; @@ -11,13 +12,13 @@ final Logger _logger = new Logger('generateReadme'); /// Generates a README file for the example at [path]. Future generateReadme(String path, {String angularIoPath}) async { - final syncDataFile = new File(p.join(path, '.docsync.json')); + final syncDataFile = new File(p.join(path, exampleConfigFileName)); final dataExists = await syncDataFile.exists(); final syncData = dataExists ? new SyncData.fromJson(await syncDataFile.readAsStringSync(), path: angularIoPath) - : new SyncData(name: p.basename(path), path: angularIoPath); + : new SyncData(title: p.basename(path), path: angularIoPath); await _generateReadme(path, syncData); if (dataExists) await syncDataFile.delete(); @@ -25,7 +26,7 @@ Future generateReadme(String path, {String angularIoPath}) async { /// Generates a README file for the example at [path] based on [syncData]. Future _generateReadme(String path, SyncData syncData) async { - final warningMessage = (syncData.docHref == null) + final warningMessage = (syncData.docHref.isEmpty) ? ''' **WARNING:** This example is preliminary and subject to change. @@ -40,21 +41,25 @@ Future _generateReadme(String path, SyncData syncData) async { return '- $link'; }).join('\n'); - final liveExampleSection = syncData.liveExampleHref == null - ? 'To run your own copy:\n' - : 'You can run a [hosted copy](${syncData.liveExampleHref}) of this' - 'sample. Or run your own copy:\n'; + final liveExampleSection = syncData.liveExampleHref.isEmpty + ? 'To run your own copy:' + : 'You can run a [hosted copy](${syncData.liveExampleHref}) of this ' + 'sample. Or run your own copy:'; + + final newIssueUri = '//github.com/angular/angular.io/issues/new' + '?labels=dart,example&title=%5BDart%5D%5Bexample%5D%20' + '${syncData.id}%3A%20'; final readmeContent = ''' $warningMessage -${syncData.name} ---------------- +## ${syncData.title} Welcome to the example application used in angular.io/dart's -[${syncData.name}](${syncData.docHref ?? syncData.repoHref}) page. +[${syncData.title}](${syncData.docHref}) page. $liveExampleSection + 1. Clone this repo. 2. Download the dependencies: @@ -74,41 +79,61 @@ $linkSection [the angular.io repository](${syncData.repoHref}) by running the [dart-doc-syncer](//github.com/angular/dart-doc-syncer) tool. If you find a problem with this sample's code, please open an -[issue at angular/angular.io](https://github.com/angular/angular.io/issues/new). +[issue at angular/angular.io]($newIssueUri). '''; final readmeFile = new File(p.join(path, 'README.md')); _logger.fine('Generating $readmeFile.'); - if (dryRun) return new Future.value(null); await readmeFile.writeAsStringSync(readmeContent); } /// Holds metadata about the example application that is used to generate a /// README file. class SyncData { - final String name; + final String name; // e.g., 'architecture' + final String title; // e.g. 'Architecture Overview' + final String docPart; // e.g. 'guide' (from 'guide/architecture') final String docHref; final String repoHref; final String liveExampleHref; final List links; + String get id => p.join(docPart, name); // e.g. 'quickstart' or 'guide/architecture' + SyncData( - {this.name, - this.docHref, - this.liveExampleHref, + {String name: '', + this.title: '', + String docPart: '', + String docHref: '', + String liveExampleHref: '', this.links: const [], - String repoHref, + String repoHref: '', String path}) - : this.repoHref = - repoHref ?? '//github.com/angular/angular.io/tree/master/' + path; + : this.name = name.isEmpty ? getExampleName(path) : name, + this.docPart = docPart, + this.docHref = docHref.isEmpty + ? p.join(dartDocUriPrefix, docPart, '${getExampleName(path)}.html') + : docHref.startsWith('http') + ? docHref + : p.join(dartDocUriPrefix, docPart, docHref), + this.liveExampleHref = liveExampleHref.isEmpty + ? p.join(exampleHostUriPrefix, getExampleName(path)) + : liveExampleHref.startsWith('http') + ? liveExampleHref + : p.join(exampleHostUriPrefix, liveExampleHref), + this.repoHref = repoHref.isEmpty + ? '//github.com/angular/angular.io/tree/master/' + path + : repoHref; factory SyncData.fromJson(String json, {String path}) { final data = JSON.decode(json); return new SyncData( - name: data['name'], - docHref: data['docHref'], - repoHref: data['repoHref'], - liveExampleHref: data['liveExampleHref'], + name: data['name'] ?? '', + title: data['title'] ?? '', + docPart: data['docPart'] ?? '', + docHref: data['docHref'] ?? '', + repoHref: data['repoHref'] ?? '', + liveExampleHref: data['liveExampleHref'] ?? '', links: data['links'] ?? [], path: path); } diff --git a/lib/src/git_documentation_updater.dart b/lib/src/git_documentation_updater.dart index 1408fba..324050c 100644 --- a/lib/src/git_documentation_updater.dart +++ b/lib/src/git_documentation_updater.dart @@ -2,85 +2,156 @@ import 'dart:async'; import 'dart:io'; import 'package:dart_doc_syncer/documentation_updater.dart'; +import 'package:dart_doc_syncer/example2uri.dart'; import 'package:dart_doc_syncer/src/generate_gh_pages.dart'; import 'package:path/path.dart' as p; import 'package:logging/logging.dart'; import 'git_repository.dart'; import 'generate_doc.dart'; +import 'options.dart'; +import 'util.dart'; final Logger _logger = new Logger('update_doc_repo'); -final String _basePath = p.dirname(Platform.script.path); -const String _angularRepositoryUri = 'https://github.com/angular/angular.io'; - class GitDocumentationUpdater implements DocumentationUpdater { final GitRepositoryFactory _gitFactory; + final String _angularRepositoryUri = + 'https://github.com/${options.user}/angular.io'; GitDocumentationUpdater(this._gitFactory); + @override + Future updateMatchingRepo(RegExp re, {bool push, bool clean}) async { + int updateCount = 0; + try { + var angularRepository = await _cloneAngularRepoIntoTmp(); + final files = await new Directory(angularRepository.directory) + .list(recursive: true) + .where( + (e) => e is File && p.basename(e.path) == exampleConfigFileName) + .toList(); + for (var e in files) { + var dartDir = p.dirname(e.path); + var exampleName = getExampleName(dartDir); + if (re.hasMatch(e.path)) { + var e2u = new Example2Uri(exampleName); + var updated = await updateRepository(e2u.path, e2u.repositoryUri, + clean: clean, push: push); + if (updated) updateCount++; + } else if (options.verbose) { + print('Skipping $exampleName'); + } + } + } on GitException catch (e) { + _logger.severe(e.message); + } finally { + await _deleteWorkDir(clean); + } + + print("Example repo(s) updated: $updateCount"); + return updateCount; + } + @override Future updateRepository(String examplePath, String outRepositoryUri, - {bool push: true, bool clean: true}) async { - print('Processing $examplePath'); + {String exampleName: '', bool push: true, bool clean: true}) async { + if (exampleName.isEmpty) exampleName = getExampleName(examplePath); + print('Processing $exampleName'); var updated = false; try { - // Clone content of angular repo into tmp folder. - final tmpAngularPath = p.join(_basePath, '.tmp/angular_io'); - final angularRepository = _gitFactory.create(tmpAngularPath); - - // Only clone into the repository if the directory is not already there. - if (!new Directory(angularRepository.directory).existsSync()) { - await angularRepository.cloneFrom(_angularRepositoryUri); - } + var angularRepository = await _cloneAngularRepoIntoTmp(); // Clone [outRepository] into tmp folder. - final exampleName = p.basename(p.dirname(examplePath)); - final outPath = p.join(_basePath, '.tmp/${exampleName}'); - final outRepository = _gitFactory.create(outPath); - await outRepository.cloneFrom(outRepositoryUri); + final outPath = p.join(workDirPath, exampleName); + final outRepo = _gitFactory.create(outPath); + await outRepo.cloneFrom(outRepositoryUri); // Remove existing content as we will generate an updated version. - await outRepository.deleteAll(); + await outRepo.deleteAll(); _logger.fine('Generating updated example application into $outPath.'); final exampleFolder = p.join(angularRepository.directory, examplePath); await assembleDocumentationExample( - new Directory(exampleFolder), new Directory(outRepository.directory), + new Directory(exampleFolder), new Directory(outRepo.directory), angularDirectory: new Directory(angularRepository.directory), angularIoPath: examplePath); final commitMessage = await _createCommitMessage(angularRepository, examplePath); - try { - await _updateMaster(outRepository, commitMessage, push); - updated = true; - print(" Sample code changed."); - print(" Updated repo: $examplePath (master)"); - } catch (_) {} - - try { - await _updateGhPages(outRepository, exampleName, commitMessage, push); - updated = true; - print(" Compiled version changed."); - print(" Updated repo: $examplePath (gh-pages)"); - } catch (_) {} + updated = await __handleUpdate( + () => _update(outRepo, commitMessage, push), + 'Example source changed', + exampleName, + outRepo.branch); + + if (updated || options.forceBuild) { + print(" Building app"); + updated = updated || + await __handleUpdate( + () => _updateGhPages(outRepo, exampleName, commitMessage, push), + 'App files have changed', + exampleName, + 'gh-pages'); + } else { + final msg = 'not built (to force use `--force-build`)'; + print(" $exampleName (gh-pages): $msg"); + } } on GitException catch (e) { _logger.severe(e.message); } finally { - if (clean) { - // Clean up .tmp folder - await new Directory(p.join(_basePath, '.tmp')).delete(recursive: true); + await _deleteWorkDir(clean); + } + return updated; + } + + final _errorOrFatal = new RegExp(r'error|fatal', caseSensitive: false); + Future __handleUpdate(Future update(), String infoMsg, String exampleName, + String branch) async { + var updated = false; + try { + await update(); + print(" $infoMsg: updated $exampleName ($branch)"); + updated = true; + } catch (e) { + var es = e.toString(); + if (es.contains(_errorOrFatal)) { + throw e; // propagate serious errors + } else if (!es.contains('nothing to commit')) { + print("** $es"); + } else { + print(" $exampleName ($branch): nothing to commit"); } } + return updated; + } - if (!updated) { - print(" No changes."); + Future _deleteWorkDir(bool clean) async { + var workDir = new Directory(workDirPath); + if (!clean) { + _logger.fine('Keeping $workDirPath.'); + } else if (await workDir.exists()) { + _logger.fine('Deleting $workDirPath.'); + await workDir.delete(recursive: true); } + } - return updated; + /// Clone angular repo into tmp folder if it is not already present. + Future _cloneAngularRepoIntoTmp() async { + dryRunMkDir(workDirPath); + + // Clone content of angular repo into tmp folder. + final tmpAngularPath = p.join(workDirPath, 'angular_io'); + final angularRepository = + _gitFactory.create(tmpAngularPath, options.branch); + + // Only clone into the repository if the directory is not already there. + if (!new Directory(angularRepository.directory).existsSync()) { + await angularRepository.cloneFrom(_angularRepositoryUri); + } + return angularRepository; } /// Generates a commit message containing the commit hash of the angular.io @@ -91,19 +162,17 @@ class GitDocumentationUpdater implements DocumentationUpdater { final long = await repo.getCommitHash(); return 'Sync with $short\n\n' - 'Synced with angular/angular.io master branch, commit $short:\n' + 'Synced with angular/angular.io ${repo.branch} branch, commit $short:\n' '$_angularRepositoryUri/tree/$long/$angularIoPath'; } - /// Updates the master branch with the latest cleaned example application - /// code. - Future _updateMaster(GitRepository repo, String message, bool push) async { - await repo.updateMaster(message: message); - + /// Updates the branch with the latest cleaned example application code. + Future _update(GitRepository repo, String message, bool push) async { + await repo.update(message: message); if (push) { - // Push the new content to [outRepository]. - _logger.fine('Pushing to master branch.'); await repo.pushCurrent(); + } else { + _logger.fine('NOT Pushing changes for ${repo.directory}.'); } } @@ -114,11 +183,10 @@ class GitDocumentationUpdater implements DocumentationUpdater { String applicationAssetsPath = await generateApplication(new Directory(repo.directory), name); await repo.updateGhPages(applicationAssetsPath, commitMessage); - if (push) { - // Push the new content to [outRepository]. - _logger.fine('Pushing to gh-pages branch for example "$name".'); await repo.pushGhPages(); + } else { + _logger.fine('NOT Pushing changes to gh-pages for ${repo.directory}.'); } } } diff --git a/lib/src/git_repository.dart b/lib/src/git_repository.dart index 5f048a0..f1e3c05 100644 --- a/lib/src/git_repository.dart +++ b/lib/src/git_repository.dart @@ -5,23 +5,25 @@ import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'runner.dart' as Process; // TODO(chalin) tmp name to avoid code changes +import 'util.dart'; class GitRepositoryFactory { - GitRepository create(String directory) => new GitRepository(directory); + GitRepository create(String directory, [String branch = 'master']) => new GitRepository(directory, branch); } class GitRepository { final _logger = new Logger('GitRepository'); - + final String branch; final String directory; - GitRepository(this.directory); + GitRepository(this.directory, this.branch); - /// Clones the git [repository] into this [directory]. + /// Clones the git [repository]'s [branch] into this [directory]. Future cloneFrom(String repository) async { - _logger.fine('Cloning $repository into $directory.'); - await _assertSuccess( - () => Process.run('git', ['clone', repository, directory])); + _logger.fine('Cloning $repository ($branch) into $directory.'); + dryRunMkDir(directory); + await _assertSuccess(() => + Process.run('git', ['clone', '-b', branch, repository, directory])); } /// Deletes all files in this git [directory]. @@ -48,16 +50,16 @@ class GitRepository { workingDirectory: directory)); } - Future updateMaster({String message}) async { - _logger.fine('Checkout master.'); - await _assertSuccess(() => Process.run('git', ['checkout', 'master'], + Future update({String message}) async { + _logger.fine('Checkout $branch.'); + await _assertSuccess(() => Process.run('git', ['checkout', branch], workingDirectory: directory)); _logger.fine('Staging local changes for $directory.'); await _assertSuccess( () => Process.run('git', ['add', '.'], workingDirectory: directory)); - _logger.fine('Comitting changes for $directory.'); + _logger.fine('Committing changes for $directory.'); await _assertSuccess(() => Process.run('git', ['commit', '-m', message], workingDirectory: directory)); } @@ -91,7 +93,7 @@ class GitRepository { _logger.fine('Copy from $sourcePath to $directory.'); await Process.run('cp', ['-a', p.join(sourcePath, '.'), directory]); - _logger.fine('Comitting gh-pages changes for $directory.'); + _logger.fine('Committing gh-pages changes for $directory.'); await _assertSuccess( () => Process.run('git', ['add', '.'], workingDirectory: directory)); await _assertSuccess(() => Process.run('git', ['commit', '-m', message], @@ -100,7 +102,7 @@ class GitRepository { /// Returns the commit hash at HEAD. Future getCommitHash({bool short: false}) async { - if (Process.dryRun) return new Future.value(null); + if (Process.options.dryRun) return new Future.value('COMMIT_HASH_CODE'); final args = "rev-parse${short ? ' --short' : ''} HEAD".split(' '); final hash = await _assertSuccess( @@ -114,6 +116,11 @@ class GitException implements Exception { final String message; GitException(this.message); + + String toString() { + if (message == null) return "GitException"; + return "GitException: $message"; + } } /// Throws if the exitCode returned by [command] is not 0. diff --git a/lib/src/options.dart b/lib/src/options.dart index fe76145..8eb80f2 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -1,35 +1,96 @@ import 'dart:io'; import 'package:args/args.dart'; +import 'package:path/path.dart' as p; -/// Global option -bool dryRun = true; -bool verbose = false; +/// Global options +class Options { + String branch = 'master'; + bool dryRun = true; + bool forceBuild = false; + bool keepTmp = false; + bool push = true; + /*@nullable*/ RegExp match; + String user = 'angular'; + bool verbose = false; + // Usage helpt text generated by arg parser + String usage = ''; +} + +Options options = new Options(); + +// TODO: make these configurable? (with defaults as given) +const dartDocHostUri = 'https://angular.io'; +const dartDocUriPrefix = dartDocHostUri + '/docs/dart/latest'; +const exampleConfigFileName = '.docsync.json'; +const exampleHostUriPrefix = 'http://angular-examples.github.io/'; +const tmpDirName = '.tmp'; -ArgParser _parser; +String _workDirBase = [ + p.join(Platform.environment['HOME'], 'tmp'), + '/tmp', + p.dirname(Platform.script.path) +].firstWhere((path) => new Directory(path).existsSync()); +String workDirPath = p.join(_workDirBase, tmpDirName); + +const Map _help = const { + 'branch': '\ngit angular.io branch to fetch', + 'dry-run': 'show which commands would be executed but make (almost) ' + 'no changes;\nonly the temporary directory will be created', + 'force-build': 'forces build of example app when sources have not changed', + 'help': 'show this usage information', + 'keep-tmp': + 'do not delete temporary working directory ($tmpDirName) once done', + 'push': 'prepare updates and push to example repo', + 'match': '\n' + 'sync all examples having a data file ($exampleConfigFileName)\n' + 'and whose repo path matches the given regular expression;\n' + 'use "." to match all', + 'user': '\nGitHub id of angular.io repo to fetch', +}; /// Processes command line options and returns remaining arguments. List processArgs(List args) { - _parser = new ArgParser(allowTrailingOptions: true); + ArgParser argParser = new ArgParser(allowTrailingOptions: true); - _parser.addFlag('help', - abbr: 'h', negatable: false, help: 'Shows usage information.'); - const dryRunMsg = 'Show which commands would be executed but make (almost) ' - 'no changes. (Only the temporary directory will be created and deleted.)'; - _parser.addFlag('dry-run', abbr: 'n', negatable: false, help: dryRunMsg); - _parser.addFlag('verbose', abbr: 'v', negatable: false); + argParser.addFlag('help', abbr: 'h', negatable: false, help: _help['help']); + argParser.addOption('branch', + abbr: 'b', help: _help['branch'], defaultsTo: options.branch); + argParser.addFlag('dry-run', + abbr: 'n', negatable: false, help: _help['dry-run']); + argParser.addFlag('force-build', + abbr: 'f', negatable: false, help: _help['force-build']); + argParser.addFlag('keep-tmp', + abbr: 'k', negatable: false, help: _help['keep-tmp']); + argParser.addFlag('push', + abbr: 'p', help: _help['push'], defaultsTo: options.push); + argParser.addOption('match', abbr: 'm', help: _help['match']); + argParser.addOption('user', + abbr: 'u', help: _help['user'], defaultsTo: options.user); + argParser.addFlag('verbose', + abbr: 'v', negatable: false, defaultsTo: options.verbose); var argResults; try { - argResults = _parser.parse(args); + argResults = argParser.parse(args); } on FormatException catch (e) { printUsageAndExit(e.message, 0); } + options.usage = argParser.usage; if (argResults['help']) printUsageAndExit(); - dryRun = argResults['dry-run']; - verbose = argResults['verbose']; + options + ..branch = argResults['branch'] + ..dryRun = argResults['dry-run'] + ..forceBuild = argResults['force-build'] + ..keepTmp = argResults['keep-tmp'] + ..push = argResults['push'] + ..match = + argResults['match'] != null ? new RegExp(argResults['match']) : null + ..user = argResults['user'] + ..verbose = argResults['verbose']; + return argResults.rest; } @@ -40,9 +101,9 @@ void printUsageAndExit([String _msg, int exitCode = 1]) { $msg. -Usage: ${Platform.script} [options] [ | ] +Usage: ${p.basenameWithoutExtension(Platform.script.path)} [options] [ | ] -${_parser != null ? _parser.usage : ''} +${options.usage} '''); exit(exitCode); } diff --git a/lib/src/runner.dart b/lib/src/runner.dart index 96c348b..3d8a231 100644 --- a/lib/src/runner.dart +++ b/lib/src/runner.dart @@ -3,19 +3,20 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'options.dart'; -export 'options.dart' show dryRun; +export 'options.dart' show options; final Logger _logger = new Logger('runner'); Future run(String executable, List arguments, {String workingDirectory}) { - if (!dryRun) - return Process.run(executable, arguments, - workingDirectory: workingDirectory); - var message = " > $executable ${arguments.join(' ')}"; if (workingDirectory != null) message += " ($workingDirectory)"; _logger.finest(message); + + if (!options.dryRun) + return Process.run(executable, arguments, + workingDirectory: workingDirectory); + if (executable == 'git' && arguments[0] == 'clone') { var path = arguments[2]; new Directory(path).createSync(recursive: true); diff --git a/lib/src/util.dart b/lib/src/util.dart new file mode 100644 index 0000000..cd7712a --- /dev/null +++ b/lib/src/util.dart @@ -0,0 +1,23 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:logging/logging.dart'; + +import 'options.dart'; + +final Logger _logger = new Logger('update_doc_repo'); + +// TODO: consider using this helper method so that we can log dir creation. +//Future mkdir(String path) async { +// _logger.fine('mkdir $path.'); +// if (dryRun) return new Future.value(); +// return await new Directory(path).create(); +//} + +/// Create directory only during dry runs. +Future dryRunMkDir(String path) async { + if (!options.dryRun) return new Future.value(); + _logger.fine('[dryrun] mkdir $path.'); + var dir = new Directory(path); + return await dir.create(); +}