blob: 4c2f77e878a45d136f7f6fb7502e1f81ed8772fe [file] [log] [blame]
// Copyright (c) 2012, 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.
/**
* The main entrypoint for the pub command line application.
*/
library pub;
import '../../pkg/args/lib/args.dart';
import 'dart:io';
import 'dart:math';
import 'io.dart';
import 'command_help.dart';
import 'command_install.dart';
import 'command_update.dart';
import 'command_version.dart';
import 'entrypoint.dart';
import 'exit_codes.dart' as exit_codes;
import 'git_source.dart';
import 'hosted_source.dart';
import 'package.dart';
import 'pubspec.dart';
import 'sdk_source.dart';
import 'source.dart';
import 'source_registry.dart';
import 'system_cache.dart';
import 'utils.dart';
import 'version.dart';
Version get pubVersion => new Version(0, 0, 0);
/**
* The commands that Pub understands.
*/
Map<String, PubCommand> get pubCommands => {
'help': new HelpCommand(),
'install': new InstallCommand(),
'update': new UpdateCommand(),
'version': new VersionCommand()
};
/**
* The parser for arguments that are global to Pub rather than specific to a
* single command.
*/
ArgParser get pubArgParser {
var parser = new ArgParser();
parser.addFlag('help', abbr: 'h', negatable: false,
help: 'Prints this usage information');
parser.addFlag('version', negatable: false,
help: 'Prints the version of Pub');
parser.addFlag('trace', help: 'Prints a stack trace when an error occurs');
return parser;
}
main() {
var globalOptions;
try {
globalOptions = pubArgParser.parse(new Options().arguments);
} on FormatException catch (e) {
printError(e.message);
printError('Run "pub help" to see available options.');
exit(exit_codes.USAGE);
}
if (globalOptions['version']) {
printVersion();
return;
}
if (globalOptions['help'] || globalOptions.rest.isEmpty) {
printUsage();
return;
}
// TODO(nweiz): Have a fallback for this this out automatically once 1145 is
// fixed.
var sdkDir = Platform.environment['DART_SDK'];
var cacheDir;
if (Platform.environment.containsKey('PUB_CACHE')) {
cacheDir = Platform.environment['PUB_CACHE'];
} else if (Platform.operatingSystem == 'windows') {
var appData = Platform.environment['APPDATA'];
cacheDir = join(appData, 'Pub', 'Cache');
} else {
cacheDir = '${Platform.environment['HOME']}/.pub-cache';
}
var cache = new SystemCache(cacheDir);
cache.register(new SdkSource(sdkDir));
cache.register(new GitSource());
cache.register(new HostedSource());
cache.sources.setDefault('hosted');
// Select the command.
var command = pubCommands[globalOptions.rest[0]];
if (command == null) {
printError('Could not find a command named "${globalOptions.rest[0]}".');
printError('Run "pub help" to see available commands.');
exit(exit_codes.USAGE);
return;
}
var commandArgs =
globalOptions.rest.getRange(1, globalOptions.rest.length - 1);
command.run(cache, globalOptions, commandArgs);
}
/** Displays usage information for the app. */
void printUsage([String description = 'Pub is a package manager for Dart.']) {
print(description);
print('');
print('Usage: pub command [arguments]');
print('');
print('Global options:');
print(pubArgParser.getUsage());
print('');
// Show the commands sorted.
print('Available commands:');
// TODO(rnystrom): A sorted map would be nice.
int length = 0;
var names = <String>[];
for (var command in pubCommands.keys) {
length = max(length, command.length);
names.add(command);
}
names.sort((a, b) => a.compareTo(b));
for (var name in names) {
print(' ${padRight(name, length)} ${pubCommands[name].description}');
}
print('');
print('Use "pub help [command]" for more information about a command.');
}
void printVersion() {
print('Pub $pubVersion');
}
abstract class PubCommand {
SystemCache cache;
ArgResults globalOptions;
ArgResults commandOptions;
Entrypoint entrypoint;
/**
* A one-line description of this command.
*/
String get description;
/**
* How to invoke this command (e.g. `"pub install [package]"`).
*/
String get usage;
/// Whether or not this command requires [entrypoint] to be defined. If false,
/// Pub won't look for a pubspec and [entrypoint] will be null when the
/// command runs.
bool get requiresEntrypoint => true;
/**
* Override this to define command-specific options. The results will be made
* available in [commandOptions].
*/
ArgParser get commandParser => new ArgParser();
void run(SystemCache cache_, ArgResults globalOptions_,
List<String> commandArgs) {
cache = cache_;
globalOptions = globalOptions_;
try {
commandOptions = commandParser.parse(commandArgs);
} on FormatException catch (e) {
printError(e.message);
printError('Use "pub help" for more information.');
exit(exit_codes.USAGE);
}
handleError(error, trace) {
// This is basically the top-level exception handler so that we don't
// spew a stack trace on our users.
var message = error.toString();
// TODO(rnystrom): The default exception implementation class puts
// "Exception:" in the output, so strip that off.
if (message.startsWith("Exception: ")) {
message = message.substring("Exception: ".length);
}
printError(message);
if (globalOptions['trace'] && trace != null) {
printError(trace);
}
exit(_chooseExitCode(error));
}
var future = new Future.immediate(null);
if (requiresEntrypoint) {
// TODO(rnystrom): Will eventually need better logic to walk up
// subdirectories until we hit one that looks package-like. For now, just
// assume the cwd is it.
future = Package.load(null, currentWorkingDir, cache.sources)
.transform((package) => new Entrypoint(package, cache));
}
future = future.chain((entrypoint) {
this.entrypoint = entrypoint;
try {
var commandFuture = onRun();
if (commandFuture == null) return new Future.immediate(true);
return commandFuture;
} catch (error, trace) {
handleError(error, trace);
return new Future.immediate(null);
}
});
future = future.chain((_) => cache_.deleteTempDir());
future.handleException((e) {
if (e is PubspecNotFoundException && e.name == null) {
e = 'Could not find a file named "pubspec.yaml" in the directory '
'$currentWorkingDir.';
} else if (e is PubspecHasNoNameException && e.name == null) {
e = 'pubspec.yaml is missing the required "name" field (e.g. "name: '
'${basename(currentWorkingDir)}").';
}
handleError(e, future.stackTrace);
});
// Explicitly exit on success to ensure that any dangling dart:io handles
// don't cause the process to never terminate.
future.then((_) => exit(0));
}
/**
* Override this to perform the specific command. Return a future that
* completes when the command is done or fails if the command fails. If the
* command is synchronous, it may return `null`.
*/
Future onRun();
/** Displays usage information for this command. */
void printUsage([String description]) {
if (description == null) description = this.description;
print(description);
print('');
print('Usage: $usage');
var commandUsage = commandParser.getUsage();
if (!commandUsage.isEmpty) {
print('');
print(commandUsage);
}
}
/// Returns the appropriate exit code for [exception], falling back on 1 if no
/// appropriate exit code could be found.
int _chooseExitCode(exception) {
if (exception is HttpException || exception is HttpParserException ||
exception is SocketIOException || exception is PubHttpException) {
return exit_codes.UNAVAILABLE;
} else if (exception is FormatException) {
return exit_codes.DATA;
} else {
return 1;
}
}
}