1 /**
2 	Defines the behavior of the DUB command line client.
3 
4 	Copyright: © 2012-2013 Matthias Dondorff
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Matthias Dondorff, Sönke Ludwig
7 */
8 module dub.commandline;
9 
10 import dub.compilers.compiler;
11 import dub.dependency;
12 import dub.dub;
13 import dub.generators.generator;
14 import dub.internal.vibecompat.core.file;
15 import dub.internal.vibecompat.core.log;
16 import dub.internal.vibecompat.inet.url;
17 import dub.package_;
18 import dub.packagesupplier;
19 import dub.project;
20 import dub.internal.utils : getDUBVersion;
21 
22 import std.algorithm;
23 import std.array;
24 import std.conv;
25 import std.encoding;
26 import std.exception;
27 import std.file;
28 import std.getopt;
29 import std.process;
30 import std.stdio;
31 import std.string;
32 import std.typecons : Tuple, tuple;
33 import std.variant;
34 
35 
36 int runDubCommandLine(string[] args)
37 {
38 	logDiagnostic("DUB version %s", getDUBVersion());
39 
40 	version(Windows){
41 		// rdmd uses $TEMP to compute a temporary path. since cygwin substitutes backslashes
42 		// with slashes, this causes OPTLINK to fail (it thinks path segments are options)
43 		// we substitute the other way around here to fix this.
44 		environment["TEMP"] = environment["TEMP"].replace("/", "\\");
45 	}
46 
47 	// split application arguments from DUB arguments
48 	string[] app_args;
49 	auto app_args_idx = args.countUntil("--");
50 	if (app_args_idx >= 0) {
51 		app_args = args[app_args_idx+1 .. $];
52 		args = args[0 .. app_args_idx];
53 	}
54 	args = args[1 .. $]; // strip the application name
55 
56 	// parse general options
57 	bool verbose, vverbose, quiet, vquiet;
58 	bool help, annotate;
59 	LogLevel loglevel = LogLevel.info;
60 	string[] registry_urls;
61 	string root_path = getcwd();
62 
63 	auto common_args = new CommandArgs(args);
64 	try {
65 		common_args.getopt("h|help", &help, ["Display general or command specific help"]);
66 		common_args.getopt("root", &root_path, ["Path to operate in instead of the current working dir"]);
67 		common_args.getopt("registry", &registry_urls, ["Search the given DUB registry URL first when resolving dependencies. Can be specified multiple times."]);
68 		common_args.getopt("annotate", &annotate, ["Do not perform any action, just print what would be done"]);
69 		common_args.getopt("v|verbose", &verbose, ["Print diagnostic output"]);
70 		common_args.getopt("vverbose", &vverbose, ["Print debug output"]);
71 		common_args.getopt("q|quiet", &quiet, ["Only print warnings and errors"]);
72 		common_args.getopt("vquiet", &vquiet, ["Print no messages"]);
73 
74 		if( vverbose ) loglevel = LogLevel.debug_;
75 		else if( verbose ) loglevel = LogLevel.diagnostic;
76 		else if( vquiet ) loglevel = LogLevel.none;
77 		else if( quiet ) loglevel = LogLevel.warn;
78 		setLogLevel(loglevel);
79 	} catch (Throwable e) {
80 		logError("Error processing arguments: %s", e.msg);
81 		logDiagnostic("Full exception: %s", e.toString().sanitize);
82 		logInfo("Run 'dub help' for usage information.");
83 		return 1;
84 	}
85 
86 	// create the list of all supported commands
87 
88 	CommandGroup[] commands = [
89 		CommandGroup("Package creation",
90 			new InitCommand
91 		),
92 		CommandGroup("Build, test and run",
93 			new RunCommand,
94 			new BuildCommand,
95 			new TestCommand,
96 			new GenerateCommand,
97 			new DescribeCommand
98 		),
99 		CommandGroup("Package management",
100 			new FetchCommand,
101 			new InstallCommand,
102 			new RemoveCommand,
103 			new UninstallCommand,
104 			new UpgradeCommand,
105 			new AddPathCommand,
106 			new RemovePathCommand,
107 			new AddLocalCommand,
108 			new RemoveLocalCommand,
109 			new ListCommand,
110 			new ListInstalledCommand
111 		)
112 	];
113 
114 	// extract the command
115 	string cmdname;
116 	args = common_args.extractRemainingArgs();
117 	if (args.length >= 1 && !args[0].startsWith("-")) {
118 		cmdname = args[0];
119 		args = args[1 .. $];
120 	} else {
121 		if (help) {
122 			showHelp(commands, common_args);
123 			return 0;
124 		}
125 		cmdname = "run";
126 	}
127 	auto command_args = new CommandArgs(args);
128 
129 	if (cmdname == "help") {
130 		showHelp(commands, common_args);
131 		return 0;
132 	}
133 
134 	// execute the sepected command
135 	foreach (grp; commands) foreach (cmd; grp.commands)
136 		if (cmd.name == cmdname) {
137 			try {
138 				cmd.prepare(command_args);
139 				enforceUsage(cmd.acceptsAppArgs || app_args.length == 0, cmd.name ~ " doesn't accept application arguments.");
140 			} catch (Throwable e) {
141 				logError("Error processing arguments: %s", e.msg);
142 				logDiagnostic("Full exception: %s", e.toString().sanitize);
143 				logInfo("Run 'dub help' for usage information.");
144 				return 1;
145 			}
146 
147 			if (help) {
148 				showCommandHelp(cmd, command_args, common_args);
149 				return 0;
150 			}
151 
152 			// initialize DUB
153 			auto package_suppliers = registry_urls.map!(url => cast(PackageSupplier)new RegistryPackageSupplier(Url(url))).array;
154 			Dub dub = new Dub(package_suppliers, root_path);
155 			dub.dryRun = annotate;
156 			
157 			// make the CWD package available so that for example sub packages can reference their
158 			// parent package.
159 			try dub.packageManager.getTemporaryPackage(Path(root_path), Version("~master"));
160 			catch (Exception e) { logDiagnostic("No package found in current working directory."); }
161 
162 			try return cmd.execute(dub, command_args.extractRemainingArgs(), app_args);
163 			catch (UsageException e) {
164 				logError("%s", e.msg);
165 				logDiagnostic("Full exception: %s", e.toString().sanitize);
166 				return 1;
167 			}
168 			catch (Throwable e) {
169 				logError("Error executing command %s: %s\n", cmd.name, e.msg);
170 				logDiagnostic("Full exception: %s", e.toString().sanitize);
171 				return 2;
172 			}
173 		}
174 	
175 	logError("Unknown command: %s", cmdname);
176 	writeln();
177 	showHelp(commands, common_args);
178 	return 1;
179 }
180 
181 class CommandArgs {
182 	struct Arg {
183 		Variant defaultValue;
184 		Variant value;
185 		string names;
186 		string[] helpText;
187 	}
188 	private {
189 		string[] m_args;
190 		Arg[] m_recognizedArgs;
191 	}
192 
193 	this(string[] args)
194 	{
195 		m_args = "dummy" ~ args;
196 	}
197 
198 	@property const(Arg)[] recognizedArgs() { return m_recognizedArgs; }
199 
200 	void getopt(T)(string names, T* var, string[] help_text = null)
201 	{
202 		foreach (ref arg; m_recognizedArgs)
203 			if (names == arg.names) {
204 				assert(help_text is null);
205 				*var = arg.value.get!T;
206 				return;
207 			}
208 		assert(help_text.length > 0);
209 		Arg arg;
210 		arg.defaultValue = *var;
211 		arg.names = names;
212 		arg.helpText = help_text;
213 		m_args.getopt(config.passThrough, names, var);
214 		arg.value = *var;
215 		m_recognizedArgs ~= arg;
216 	}
217 
218 	void dropAllArgs()
219 	{
220 		m_args = null;
221 	}
222 
223 	string[] extractRemainingArgs()
224 	{
225 		auto ret = m_args[1 .. $];
226 		m_args = null;
227 		return ret;
228 	}
229 }
230 
231 class Command {
232 	string name;
233 	string argumentsPattern;
234 	string description;
235 	string[] helpText;
236 	bool acceptsAppArgs;
237 	bool hidden = false; // used for deprecated commands
238 
239 	abstract void prepare(scope CommandArgs args);
240 	abstract int execute(Dub dub, string[] free_args, string[] app_args);
241 }
242 
243 struct CommandGroup {
244 	string caption;
245 	Command[] commands;
246 
247 	this(string caption, Command[] commands...)
248 	{
249 		this.caption = caption;
250 		this.commands = commands.dup;
251 	}
252 }
253 
254 
255 /******************************************************************************/
256 /* INIT                                                                       */
257 /******************************************************************************/
258 
259 class InitCommand : Command {
260 	this()
261 	{
262 		this.name = "init";
263 		this.argumentsPattern = "[<directory> [<type>]]";
264 		this.description = "Initializes an empty package skeleton";
265 		this.helpText = [
266 			"Initializes an empty package of the specified type in the given directory. By default, the current working dirctory is used. Available types:",
267 			"",
268 			"minimal - a simple \"hello world\" project with no dependencies (default)",
269 			"vibe.d - minimal HTTP server based on vibe.d"
270 		];
271 	}
272 
273 	override void prepare(scope CommandArgs args)
274 	{
275 	}
276 
277 	override int execute(Dub dub, string[] free_args, string[] app_args)
278 	{
279 		string dir, type = "minimal";
280 		enforceUsage(app_args.empty, "Unexpected application arguments.");
281 		enforceUsage(free_args.length <= 2, "Too many arguments.");
282 		if (free_args.length >= 1) dir = free_args[0];
283 		if (free_args.length >= 2) type = free_args[1];
284 		dub.createEmptyPackage(Path(dir), type);
285 		return 0;
286 	}
287 }
288 
289 
290 /******************************************************************************/
291 /* GENERATE / BUILD / RUN / TEST / DESCRIBE                                   */
292 /******************************************************************************/
293 
294 abstract class PackageBuildCommand : Command {
295 	protected {
296 		string m_build_type;
297 		string m_build_config;
298 		string m_compiler_name = "dmd";
299 		string m_arch;
300 		string[] m_debug_versions;
301 		Compiler m_compiler;
302 		BuildPlatform m_buildPlatform;
303 		BuildSettings m_buildSettings;
304 		string m_defaultConfig;
305 		bool m_nodeps;
306 	}
307 
308 	override void prepare(scope CommandArgs args)
309 	{
310 		args.getopt("b|build", &m_build_type, [
311 			"Specifies the type of build to perform. Note that setting the DFLAGS environment variable will override the build type with custom flags.",
312 			"Possible names:",
313 			"  debug (default), plain, release, unittest, profile, docs, ddox, cov, unittest-cov and custom types"
314 		]);
315 		args.getopt("c|config", &m_build_config, [
316 			"Builds the specified configuration. Configurations can be defined in package.json"
317 		]);
318 		args.getopt("compiler", &m_compiler_name, [
319 			"Specifies the compiler binary to use. Arbitrary pre- and suffixes to the identifiers below are recognized (e.g. ldc2 or dmd-2.063) and matched to the proper compiler type:",
320 			"  dmd (default), gdc, ldc, gdmd, ldmd"
321 		]);
322 		args.getopt("a|arch", &m_arch, [
323 			"Force a different architecture (e.g. x86 or x86_64)"
324 		]);
325 		args.getopt("d|debug", &m_debug_versions, [
326 			"Define the specified debug version identifier when building - can be used multiple times"
327 		]);
328 		args.getopt("nodeps", &m_nodeps, [
329 			"Do not check/update dependencies before building"
330 		]);
331 	}
332 
333 	protected void setupPackage(Dub dub, string package_name)
334 	{
335 		m_compiler = getCompiler(m_compiler_name);
336 		m_buildPlatform = m_compiler.determinePlatform(m_buildSettings, m_compiler_name, m_arch);
337 		m_buildSettings.addDebugVersions(m_debug_versions);
338 
339 		m_defaultConfig = null;
340 		enforce (loadSpecificPackage(dub, package_name), "Failed to load package.");
341 
342 		enforce(m_build_config.length == 0 || dub.configurations.canFind(m_build_config), "Unknown build configuration: "~m_build_config);
343 
344 		if (m_build_type.length == 0) {
345 			if (environment.get("DFLAGS")) m_build_type = "$DFLAGS";
346 			else m_build_type = "debug";
347 		}
348 
349 		if (!m_nodeps) {
350 			logInfo("Checking dependencies in '%s'", dub.projectPath.toNativeString());
351 			dub.update(UpdateOptions.none);
352 		}
353 	}
354 
355 	private bool loadSpecificPackage(Dub dub, string package_name)
356 	{
357 		Package pack;
358 		if (!package_name.empty) {
359 			// load package in root_path to enable searching for sub packages
360 			loadCwdPackage(dub, null, false);
361 			pack = dub.packageManager.getFirstPackage(package_name);
362 			enforce(pack, "Failed to find a package named '"~package_name~"'.");
363 			logInfo("Building package %s in %s", pack.name, pack.path.toNativeString());
364 			dub.rootPath = pack.path;
365 		}
366 		if (!loadCwdPackage(dub, pack, true)) return false;
367 		return true;
368 	}
369 
370 	private bool loadCwdPackage(Dub dub, Package pack, bool warn_missing_package)
371 	{
372 		if (warn_missing_package && !existsFile(dub.rootPath~"package.json") && !existsFile(dub.rootPath~"source/app.d")) {
373 			logInfo("");
374 			logInfo("Neither package.json, nor source/app.d was found in the current directory.");
375 			logInfo("Please run dub from the root directory of an existing package, or create a new");
376 			logInfo("package using \"dub init <name>\".");
377 			logInfo("");
378 			return false;
379 		}
380 
381 		if (pack) dub.loadPackage(pack);
382 		else dub.loadPackageFromCwd();
383 
384 		m_defaultConfig = dub.getDefaultConfiguration(m_buildPlatform);
385 
386 		return true;
387 	}
388 }
389 
390 class GenerateCommand : PackageBuildCommand {
391 	protected {
392 		string m_generator;
393 		bool m_rdmd = false;
394 		bool m_run = false;
395 		bool m_force = false;
396 		bool m_print_platform, m_print_builds, m_print_configs;
397 	}
398 
399 	this()
400 	{
401 		this.name = "generate";
402 		this.argumentsPattern = "<generator> [<package>]";
403 		this.description = "Generates project files using the specified generator";
404 		this.helpText = [
405 			"Generates project files using one of the supported generators:",
406 			"",
407 			"visuald - VisualD project files",
408 			"visuald-combined - VisualD single project file",
409 			"build - Builds the package directly",
410 			"",
411 			"An optional package name can be given to generate a different package than the root/CWD package."
412 		];
413 	}
414 
415 	override void prepare(scope CommandArgs args)
416 	{
417 		super.prepare(args);
418 
419 		args.getopt("print-builds", &m_print_builds, [
420 			"Prints the list of available build types"
421 		]);
422 		args.getopt("print-configs", &m_print_configs, [
423 			"Prints the list of available configurations"
424 		]);
425 		args.getopt("print-platform", &m_print_platform, [
426 			"Prints the identifiers for the current build platform as used for the build fields in package.json"
427 		]);
428 	}
429 
430 	override int execute(Dub dub, string[] free_args, string[] app_args)
431 	{
432 		string package_name;
433 		if (!m_generator.length) {
434 			enforceUsage(free_args.length >= 1 && free_args.length <= 2, "Expected one or two arguments.");
435 			m_generator = free_args[0];
436 			if (free_args.length >= 2) package_name = free_args[1];
437 		} else {
438 			enforceUsage(free_args.length <= 1, "Expected one or zero arguments.");
439 			if (free_args.length >= 1) package_name = free_args[0];
440 		}
441 
442 		setupPackage(dub, package_name);
443 		
444 		if (m_print_builds) { // FIXME: use actual package data
445 			logInfo("Available build types:");
446 			foreach (tp; ["debug", "release", "unittest", "profile"])
447 				logInfo("  %s", tp);
448 			logInfo("");
449 		}
450 
451 		if (m_print_configs) {
452 			logInfo("Available configurations:");
453 			foreach (tp; dub.configurations)
454 				logInfo("  %s%s", tp, tp == m_defaultConfig ? " [default]" : null);
455 			logInfo("");
456 		}
457 
458 		if (!m_nodeps) {
459 			logInfo("Checking dependencies in '%s'", dub.projectPath.toNativeString());
460 			dub.update(UpdateOptions.none);
461 		}
462 
463 		GeneratorSettings gensettings;
464 		gensettings.platform = m_buildPlatform;
465 		gensettings.config = m_build_config.length ? m_build_config : m_defaultConfig;
466 		gensettings.buildType = m_build_type;
467 		gensettings.compiler = m_compiler;
468 		gensettings.buildSettings = m_buildSettings;
469 		gensettings.run = m_run;
470 		gensettings.runArgs = app_args;
471 		gensettings.force = m_force;
472 		gensettings.rdmd = m_rdmd;
473 
474 		logDiagnostic("Generating using %s", m_generator);
475 		dub.generateProject(m_generator, gensettings);
476 		if (m_build_type == "ddox") dub.runDdox(gensettings.run);
477 		return 0;
478 	}
479 }
480 
481 class BuildCommand : GenerateCommand {
482 	this()
483 	{
484 		this.name = "build";
485 		this.argumentsPattern = "[<package>]";
486 		this.description = "Builds a package (uses the main package in the current working directory by default)";
487 		this.helpText = [
488 			"Builds a package (uses the main package in the current working directory by default)"
489 		];
490 	}
491 
492 	override void prepare(scope CommandArgs args)
493 	{
494 		args.getopt("rdmd", &m_rdmd, [
495 			"Use rdmd instead of directly invoking the compiler"
496 		]);
497 		args.getopt("f|force", &m_force, [
498 			"Forces a recompilation even if the target is up to date"
499 		]);
500 		super.prepare(args);
501 		m_generator = "build";
502 	}
503 
504 	override int execute(Dub dub, string[] free_args, string[] app_args)
505 	{
506 		return super.execute(dub, free_args, app_args);
507 	}
508 }
509 
510 class RunCommand : BuildCommand {
511 	this()
512 	{
513 		this.name = "run";
514 		this.argumentsPattern = "[<package>]";
515 		this.description = "Builds and runs a package (default command)";
516 		this.helpText = [
517 			"Builds and runs a package (uses the main package in the current working directory by default)"
518 		];
519 		this.acceptsAppArgs = true;
520 	}
521 
522 	override void prepare(scope CommandArgs args)
523 	{
524 		super.prepare(args);
525 		m_run = true;
526 	}
527 
528 	override int execute(Dub dub, string[] free_args, string[] app_args)
529 	{
530 		return super.execute(dub, free_args, app_args);
531 	}
532 }
533 
534 class TestCommand : PackageBuildCommand {
535 	private {
536 		string m_mainFile;
537 	}
538 
539 	this()
540 	{
541 		this.name = "test";
542 		this.argumentsPattern = "[<package>]";
543 		this.description = "Executes the tests of the selected package";
544 		this.helpText = [
545 			"Builds a library configuration of the selected package and executes all contained unit tests."
546 		];
547 		this.acceptsAppArgs = true;
548 	}
549 
550 	override void prepare(scope CommandArgs args)
551 	{
552 		args.getopt("main-file", &m_mainFile, [
553 			"Specifies a custom file containing the main() function to use for running the tests."
554 		]);
555 		super.prepare(args);
556 	}
557 
558 	override int execute(Dub dub, string[] free_args, string[] app_args)
559 	{
560 		string package_name;
561 		enforceUsage(free_args.length <= 1, "Expected one or zero arguments.");
562 		if (free_args.length >= 1) package_name = free_args[0];
563 
564 		setupPackage(dub, package_name);
565 
566 		dub.testProject(m_buildSettings, m_buildPlatform, m_build_config, Path(m_mainFile), app_args);
567 		return 0;
568 	}
569 }
570 
571 class DescribeCommand : PackageBuildCommand {
572 	this()
573 	{
574 		this.name = "describe";
575 		this.argumentsPattern = "[<package>]";
576 		this.description = "Prints a JSON description of the project and its dependencies";
577 		this.helpText = [
578 			"Prints a JSON build description for the root package an all of their dependencies in a format similar to a JSON package description file. This is useful mostly for IDEs.",
579 			"All usual options that are also used for build/run/generate apply."
580 		];
581 	}
582 
583 	override void prepare(scope CommandArgs args)
584 	{
585 		super.prepare(args);
586 	}
587 
588 	override int execute(Dub dub, string[] free_args, string[] app_args)
589 	{
590 		string package_name;
591 		enforceUsage(free_args.length <= 1, "Expected one or zero arguments.");
592 		if (free_args.length >= 1) package_name = free_args[1];
593 
594 		setupPackage(dub, package_name);
595 
596 		if (!m_nodeps) {
597 			logInfo("Checking dependencies in '%s'", dub.projectPath.toNativeString());
598 			dub.update(UpdateOptions.none);
599 		}
600 
601 		dub.describeProject(m_buildPlatform, m_build_config.length ? m_build_config : m_defaultConfig);				
602 		return 0;
603 	}
604 }
605 
606 
607 /******************************************************************************/
608 /* FETCH / REMOVE / UPGRADE                                                   */
609 /******************************************************************************/
610 
611 class UpgradeCommand : Command {
612 	private {
613 		bool m_prerelease = false;
614 	}
615 
616 	this()
617 	{
618 		this.name = "upgrade";
619 		this.argumentsPattern = "";
620 		this.description = "Forces an upgrade of all dependencies";
621 		this.helpText = [
622 			"Upgrades all dependencies of the package by querying the package registry(ies) for new versions."
623 		];
624 	}
625 
626 	override void prepare(scope CommandArgs args)
627 	{
628 		args.getopt("prerelease", &m_prerelease, [
629 			"Uses the latest pre-release version, even if release versions are available"
630 		]);
631 	}
632 
633 	override int execute(Dub dub, string[] free_args, string[] app_args)
634 	{
635 		enforceUsage(free_args.length == 0, "Unexpected arguments.");
636 		enforceUsage(app_args.length == 0, "Unexpected application arguments.");
637 		dub.loadPackageFromCwd();
638 		logInfo("Upgrading project in %s", dub.projectPath.toNativeString());
639 		auto options = UpdateOptions.upgrade;
640 		if (m_prerelease) options |= UpdateOptions.preRelease;
641 		dub.update(options);
642 		return 0;
643 	}
644 }
645 
646 class FetchRemoveCommand : Command {
647 	protected {
648 		string m_version;
649 		bool m_system = false;
650 		bool m_local = false;
651 	}
652 
653 	override void prepare(scope CommandArgs args)
654 	{
655 		args.getopt("version", &m_version, [
656 			"Use the specified version/branch instead of the latest available match",
657 			"The remove command also accepts \"*\" here as a wildcard to remove all versions of the package from the specified location"
658 		]);
659 
660 		args.getopt("system", &m_system, ["Puts the package into the system wide package cache instead of the user local one."]);
661 		args.getopt("local", &m_system, ["Puts the package into a sub folder of the current working directory. Cannot be mixed with --system."]);
662 	}
663 
664 	abstract override int execute(Dub dub, string[] free_args, string[] app_args);
665 }
666 
667 class FetchCommand : FetchRemoveCommand {
668 	this()
669 	{
670 		this.name = "fetch";
671 		this.argumentsPattern = "<name>";
672 		this.description = "Manually retrieves and caches a package";
673 		this.helpText = [
674 			"Note: Use the \"dependencies\" field in the package description file (e.g. package.json) if you just want to use a certain package as a dependency, you don't have to explicitly fetch packages.",
675 			"",
676 			"Explicit retrieval/removal of packages is only needed when you want to put packages to a place where several applications can share these. If you just have an dependency to a package, just add it to your package.json, dub will do the rest for you."
677 			"",
678 			"Without specified options, placement/removal will default to a user wide shared location."
679 			"",
680 			"Complete applications can be retrieved and run easily by e.g.",
681 			"$ dub fetch vibelog --local",
682 			"$ cd vibelog",
683 			"$ dub",
684 			""
685 			"This will grab all needed dependencies and compile and run the application.",
686 			"",
687 			"Note: DUB does not do a system installation of packages. Packages are instead only registered within DUB's internal ecosystem. Generation of native system packages/installers may be added later as a separate feature."
688 		];
689 	}
690 
691 	override void prepare(scope CommandArgs args)
692 	{
693 		super.prepare(args);
694 	}
695 
696 	override int execute(Dub dub, string[] free_args, string[] app_args)
697 	{
698 		enforceUsage(!m_local || !m_system, "--local and --system are exclusive to each other.");
699 		enforceUsage(free_args.length == 1, "Expecting exactly one argument.");
700 		enforceUsage(app_args.length == 0, "Unexpected application arguments.");
701 
702 		auto location = PlacementLocation.userWide;
703 		if (m_local) location = PlacementLocation.local;
704 		else if (m_system) location = PlacementLocation.systemWide;
705 
706 		auto name = free_args[0];
707 
708 		if (m_version.length) dub.fetch(name, Dependency(m_version), location, true, false);
709 		else {
710 			try {
711 				dub.fetch(name, Dependency(">=0.0.0"), location, true, false);
712 				logInfo(
713 					"Please note that you need to use `dub run <pkgname>` " ~ 
714 					"or add it to dependencies of your package to actually use/run it. " ~
715 					"dub does not do actual installation of packages outside of its own ecosystem.");
716 			}
717 			catch(Exception e){
718 				logInfo("Getting a release version failed: %s", e.msg);
719 				logInfo("Retry with ~master...");
720 				dub.fetch(name, Dependency("~master"), location, true, true);
721 			}
722 		}
723 		return 0;
724 	}
725 }
726 
727 class InstallCommand : FetchCommand {
728 	this() { this.name = "install"; hidden = true; }
729 	override void prepare(scope CommandArgs args) { super.prepare(args); }
730 	override int execute(Dub dub, string[] free_args, string[] app_args)
731 	{
732 		warnRenamed("install", "fetch");
733 		return super.execute(dub, free_args, app_args);
734 	}
735 }
736 
737 class RemoveCommand : FetchRemoveCommand {
738 	this()
739 	{
740 		this.name = "remove";
741 		this.argumentsPattern = "<name>";
742 		this.description = "Removes a cached package";
743 		this.helpText = [
744 			"Removes a package that is cached on the local system."
745 		];
746 	}
747 
748 	override void prepare(scope CommandArgs args)
749 	{
750 		super.prepare(args);
751 	}
752 
753 	override int execute(Dub dub, string[] free_args, string[] app_args)
754 	{
755 		enforceUsage(!m_local || !m_system, "--local and --system are exclusive to each other.");
756 		enforceUsage(free_args.length == 1, "Expecting exactly one argument.");
757 		enforceUsage(app_args.length == 0, "Unexpected application arguments.");
758 
759 		auto package_id = free_args[0];
760 		auto location = PlacementLocation.userWide;
761 		if (m_local) location = PlacementLocation.local;
762 		else if (m_system) location = PlacementLocation.systemWide;
763 
764 		try dub.remove(package_id, m_version, location);
765 		catch {
766 			logError("Please specify a individual version or use the wildcard identifier '%s' (without quotes).", Dub.RemoveVersionWildcard);
767 			return 1;
768 		}
769 
770 		return 0;
771 	}
772 }
773 
774 class UninstallCommand : RemoveCommand {
775 	this() { this.name = "uninstall"; hidden = true; }
776 	override void prepare(scope CommandArgs args) { super.prepare(args); }
777 	override int execute(Dub dub, string[] free_args, string[] app_args)
778 	{
779 		warnRenamed("uninstall", "remove");
780 		return super.execute(dub, free_args, app_args);
781 	}
782 }
783 
784 
785 /******************************************************************************/
786 /* ADD/REMOVE PATH/LOCAL                                                      */
787 /******************************************************************************/
788 
789 abstract class RegistrationCommand : Command {
790 	private {
791 		bool m_system;
792 	}
793 
794 	override void prepare(scope CommandArgs args)
795 	{
796 		args.getopt("system", &m_system, [
797 			"Register system-wide instead of user-wide"
798 		]);
799 	}
800 
801 	abstract override int execute(Dub dub, string[] free_args, string[] app_args);
802 }
803 
804 class AddPathCommand : RegistrationCommand {
805 	this()
806 	{
807 		this.name = "add-path";
808 		this.argumentsPattern = "<path>";
809 		this.description = "Adds a default package search path";
810 		this.helpText = ["Adds a default package search path"];
811 	}
812 
813 	override int execute(Dub dub, string[] free_args, string[] app_args)
814 	{
815 		enforceUsage(free_args.length == 1, "Missing search path.");
816 		dub.addSearchPath(free_args[0], m_system);
817 		return 0;
818 	}
819 }
820 
821 class RemovePathCommand : RegistrationCommand {
822 	this()
823 	{
824 		this.name = "remove-path";
825 		this.argumentsPattern = "<path>";
826 		this.description = "Removes a package search path";
827 		this.helpText = ["Removes a package search path"];
828 	}
829 
830 	override int execute(Dub dub, string[] free_args, string[] app_args)
831 	{
832 		enforceUsage(free_args.length == 1, "Expected one argument.");
833 		dub.removeSearchPath(free_args[0], m_system);
834 		return 0;
835 	}
836 }
837 
838 class AddLocalCommand : RegistrationCommand {
839 	this()
840 	{
841 		this.name = "add-local";
842 		this.argumentsPattern = "<path> <version>";
843 		this.description = "Adds a local package directory (e.g. a git repository)";
844 		this.helpText = ["Adds a local package directory (e.g. a git repository)"];
845 	}
846 
847 	override int execute(Dub dub, string[] free_args, string[] app_args)
848 	{
849 		enforceUsage(free_args.length == 2, "Expecting two arguments.");
850 		dub.addLocalPackage(free_args[0], free_args[1], m_system);
851 		return 0;
852 	}
853 }
854 
855 class RemoveLocalCommand : RegistrationCommand {
856 	this()
857 	{
858 		this.name = "remove-local";
859 		this.argumentsPattern = "<path>";
860 		this.description = "Removes a local package directory";
861 		this.helpText = ["Removes a local package directory"];
862 	}
863 
864 	override int execute(Dub dub, string[] free_args, string[] app_args)
865 	{
866 		enforceUsage(free_args.length == 1, "Missing path to package.");
867 		dub.removeLocalPackage(free_args[0], m_system);
868 		return 0;
869 	}
870 }
871 
872 
873 /******************************************************************************/
874 /* LIST                                                                       */
875 /******************************************************************************/
876 
877 class ListCommand : Command {
878 	this()
879 	{
880 		this.name = "list";
881 		this.argumentsPattern = "";
882 		this.description = "Prints a list of all local packages dub is aware of";
883 		this.helpText = [
884 			"Prints a list of all local packages. This includes all cached packages (user or system wide), all packages in the package search paths (\"dub add-path\") and all manually registered packages (\"dub add-local\")."
885 		];
886 	}
887 	override void prepare(scope CommandArgs args) {}
888 	override int execute(Dub dub, string[] free_args, string[] app_args)
889 	{
890 		logInfo("Packages present in the system and known to dub:");
891 		foreach (p; dub.packageManager.getPackageIterator())
892 			logInfo("  %s %s: %s", p.name, p.ver, p.path.toNativeString());
893 		logInfo("");
894 		return true;
895 	}
896 }
897 
898 class ListInstalledCommand : ListCommand {
899 	this() { this.name = "list-installed"; hidden = true; }
900 	override void prepare(scope CommandArgs args) { super.prepare(args); }
901 	override int execute(Dub dub, string[] free_args, string[] app_args)
902 	{
903 		warnRenamed("list-installed", "list");
904 		return super.execute(dub, free_args, app_args);
905 	}
906 }
907 
908 
909 /******************************************************************************/
910 /* HELP                                                                       */
911 /******************************************************************************/
912 
913 private {
914 	enum shortArgColumn = 2;
915 	enum longArgColumn = 6;
916 	enum descColumn = 24;
917 	enum lineWidth = 80;
918 }
919 
920 private void showHelp(in CommandGroup[] commands, CommandArgs common_args)
921 {
922 	writeln(
923 `USAGE: dub [<command>] [<options...>] [-- [<application arguments...>]]
924 
925 Manages the DUB project in the current directory. If the command is omitted,
926 DUB will default to "run". When running an application, "--" can be used to
927 separate DUB options from options passed to the application. 
928 
929 Run "dub <command> --help" to get help for a specific command.
930 
931 You can use the "http_proxy" environment variable to configure a proxy server
932 to be used for fetching packages.
933 
934 
935 Available commands
936 ==================`);
937 
938 	foreach (grp; commands) {
939 		writeln();
940 		writeWS(shortArgColumn);
941 		writeln(grp.caption);
942 		writeWS(shortArgColumn);
943 		writerep!'-'(grp.caption.length);
944 		writeln();
945 		foreach (cmd; grp.commands) {
946 			if (cmd.hidden) continue;
947 			writeWS(shortArgColumn);
948 			writef("%s %s", cmd.name, cmd.argumentsPattern);
949 			auto chars_output = cmd.name.length + cmd.argumentsPattern.length + shortArgColumn + 1;
950 			if (chars_output < descColumn) {
951 				writeWS(descColumn - chars_output);
952 			} else {
953 				writeln();
954 				writeWS(descColumn);
955 			}
956 			writeWrapped(cmd.description, descColumn, descColumn);
957 		}
958 	}
959 	writeln();
960 	writeln();
961 	writeln(`Common options`);
962 	writeln(`==============`);
963 	writeln();
964 	writeOptions(common_args);
965 	writeln();
966 	writefln("DUB version %s", getDUBVersion());
967 }
968 
969 private void showCommandHelp(Command cmd, CommandArgs args, CommandArgs common_args)
970 {
971 	writefln(`USAGE: dub %s %s [<options...>]%s`, cmd.name, cmd.argumentsPattern, cmd.acceptsAppArgs ? " [-- <application arguments...>]": null);
972 	writeln();
973 	foreach (ln; cmd.helpText)
974 		ln.writeWrapped();
975 	
976 	if (args.recognizedArgs.length) {
977 		writeln();
978 		writeln();
979 		writeln("Command specific options");
980 		writeln("========================");
981 		writeln();
982 		writeOptions(args);
983 	}
984 	
985 	writeln();
986 	writeln();
987 	writeln("Common options");
988 	writeln("==============");
989 	writeln();
990 	writeOptions(common_args);
991 	writeln();
992 	writefln("DUB version %s", getDUBVersion());
993 }
994 
995 private void writeOptions(CommandArgs args)
996 {
997 	foreach (arg; args.recognizedArgs) {
998 		auto names = arg.names.split("|");
999 		assert(names.length == 1 || names.length == 2);
1000 		string sarg = names[0].length == 1 ? names[0] : null;
1001 		string larg = names[0].length > 1 ? names[0] : names.length > 1 ? names[1] : null;
1002 		if (sarg) {
1003 			writeWS(shortArgColumn);
1004 			writef("-%s", sarg);
1005 			writeWS(longArgColumn - shortArgColumn - 2);
1006 		} else writeWS(longArgColumn);
1007 		size_t col = longArgColumn;
1008 		if (larg) {
1009 			if (arg.defaultValue.peek!bool) {
1010 				writef("--%s", larg);
1011 				col += larg.length + 2;
1012 			} else {
1013 				writef("--%s=VALUE", larg);
1014 				col += larg.length + 8;
1015 			}
1016 		}
1017 		if (col < descColumn) {
1018 			writeWS(descColumn - col);
1019 		} else {
1020 			writeln();
1021 			writeWS(descColumn);
1022 		}
1023 		foreach (i, ln; arg.helpText) {
1024 			if (i > 0) writeWS(descColumn);
1025 			ln.writeWrapped(descColumn, descColumn);
1026 		}
1027 	}
1028 }
1029 
1030 private void writeWrapped(string string, size_t indent = 0, size_t first_line_pos = 0)
1031 {
1032 	auto wrapped = string.wrap(lineWidth, getRepString!' '(first_line_pos), getRepString!' '(indent));
1033 	wrapped = wrapped[first_line_pos .. $];
1034 	foreach (ln; wrapped.splitLines())
1035 		writeln(ln);
1036 }
1037 
1038 private void writeWS(size_t num) { writerep!' '(num); }
1039 private void writerep(char ch)(size_t num) { write(getRepString!ch(num)); }
1040 
1041 private string getRepString(char ch)(size_t len)
1042 {
1043 	static string buf;
1044 	if (len > buf.length) buf ~= [ch].replicate(len-buf.length);
1045 	return buf[0 .. len];
1046 }
1047 
1048 /***
1049 */
1050 
1051 
1052 private void enforceUsage(bool cond, string text)
1053 {
1054 	if (!cond) throw new UsageException(text);
1055 }
1056 
1057 private class UsageException : Exception {
1058 	this(string message, string file = __FILE__, int line = __LINE__, Throwable next = null)
1059 	{
1060 		super(message, file, line, next);
1061 	}
1062 }
1063 
1064 private void warnRenamed(string prev, string curr)
1065 {
1066 	logWarn("The '%s' Command was renamed to '%s'. Please update your scripts.", prev, curr);
1067 }