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