diff --git a/source/dub/commandline.d b/source/dub/commandline.d index bbcfde5..0aa07b3 100644 --- a/source/dub/commandline.d +++ b/source/dub/commandline.d @@ -1213,6 +1213,10 @@ } class RemoveCommand : FetchRemoveCommand { + private { + bool m_nonInteractive; + } + this() { this.name = "remove"; @@ -1226,6 +1230,7 @@ override void prepare(scope CommandArgs args) { super.prepare(args); + args.getopt("n|non-interactive", &m_nonInteractive, ["Don't enter interactive mode."]); } override int execute(Dub dub, string[] free_args, string[] app_args) @@ -1236,7 +1241,37 @@ auto package_id = free_args[0]; auto location = dub.defaultPlacementLocation; - dub.remove(package_id, m_version, location, m_forceRemove); + size_t resolveVersion(in Package[] packages) { + // just remove only package version + if (packages.length == 1) + return 0; + + writeln("Select version of '", package_id, "' to remove from location '", location, "':"); + foreach (i, pack; packages) + writefln("%s) %s", i + 1, pack.version_); + writeln(packages.length + 1, ") ", "all versions"); + while (true) { + writef("> "); + auto inp = readln(); + if (!inp.length) // Ctrl+D + return size_t.max; + inp = inp.stripRight; + if (!inp.length) // newline or space + continue; + try { + immutable selection = inp.to!size_t - 1; + if (selection <= packages.length) + return selection; + } catch (ConvException e) { + } + logError("Please enter a number between 1 and %s.", packages.length + 1); + } + } + + if (m_nonInteractive || !m_version.empty) + dub.remove(package_id, m_version, location, m_forceRemove); + else + dub.remove(package_id, location, m_forceRemove, &resolveVersion); return 0; } } diff --git a/source/dub/dub.d b/source/dub/dub.d index 1d4a647..22c52a3 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -730,16 +730,14 @@ Params: package_id = Name of the package to be removed - version_ = Identifying a version or a wild card. If an empty string - is passed, the package will be removed from the location, if - there is only one version retrieved. This will throw an - exception, if there are multiple versions retrieved. location_ = Specifies the location to look for the given package name/version. force_remove = Forces removal of the package, even if untracked files are found in its folder. + resolve_version = Callback to select package version. */ - void remove(string package_id, string version_, PlacementLocation location, bool force_remove) + void remove(string package_id, PlacementLocation location, bool force_remove, + scope size_t delegate(in Package[] packages) resolve_version) { enforce(!package_id.empty); if (location == PlacementLocation.local) { @@ -749,28 +747,24 @@ } Package[] packages; - const bool wildcardOrEmpty = version_ == RemoveVersionWildcard || version_.empty; // Retrieve packages to be removed. foreach(pack; m_packageManager.getPackageIterator(package_id)) - if ((wildcardOrEmpty || pack.version_ == Version(version_)) && m_packageManager.isManagedPackage(pack)) + if (m_packageManager.isManagedPackage(pack)) packages ~= pack; // Check validity of packages to be removed. if(packages.empty) { throw new Exception("Cannot find package to remove. (" - ~ "id: '" ~ package_id ~ "', version: '" ~ version_ ~ "', location: '" ~ to!string(location) ~ "'" + ~ "id: '" ~ package_id ~ "', location: '" ~ to!string(location) ~ "'" ~ ")"); } - if(version_.empty && packages.length > 1) { - logError("Cannot remove package '" ~ package_id ~ "', there are multiple possibilities at location\n" - ~ "'" ~ to!string(location) ~ "'."); - logError("Available versions:"); - foreach(pack; packages) - logError(" %s", pack.version_); - throw new Exception("Please specify a individual version using --version=... or use the" - ~ " wildcard --version=" ~ RemoveVersionWildcard ~ " to remove all versions."); - } + + immutable idx = resolve_version(packages); + if (idx == size_t.max) + return; + else if (idx != packages.length) + packages = packages[idx .. idx + 1]; logDebug("Removing %s packages.", packages.length); foreach(pack; packages) { @@ -784,6 +778,43 @@ } } + /** Removes a specific version of a package. + + Params: + package_id = Name of the package to be removed + version_ = Identifying a version or a wild card. If an empty string + is passed, the package will be removed from the location, if + there is only one version retrieved. This will throw an + exception, if there are multiple versions retrieved. + location_ = Specifies the location to look for the given package + name/version. + force_remove = Forces removal of the package, even if untracked + files are found in its folder. + */ + void remove(string package_id, string version_, PlacementLocation location, bool force_remove) + { + remove(package_id, location, force_remove, (in packages) { + if (version_ == RemoveVersionWildcard) + return packages.length; + if (version_.empty && packages.length > 1) { + logError("Cannot remove package '" ~ package_id ~ "', there are multiple possibilities at location\n" + ~ "'" ~ to!string(location) ~ "'."); + logError("Available versions:"); + foreach(pack; packages) + logError(" %s", pack.version_); + throw new Exception("Please specify a individual version using --version=... or use the" + ~ " wildcard --version=" ~ RemoveVersionWildcard ~ " to remove all versions."); + } + foreach (i, p; packages) { + if (p.version_ == Version(version_)) + return i; + } + throw new Exception("Cannot find package to remove. (" + ~ "id: '" ~ package_id ~ "', version: '" ~ version_ ~ "', location: '" ~ to!string(location) ~ "'" + ~ ")"); + }); + } + /** Adds a directory to the list of locally known packages. Forwards to `PackageManager.addLocalPackage`. diff --git a/test/interactive-remove.sh b/test/interactive-remove.sh new file mode 100755 index 0000000..5c23bef --- /dev/null +++ b/test/interactive-remove.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -euo pipefail + +$DUB fetch dub --version=0.9.20 && [ -d $HOME/.dub/packages/dub-0.9.20/dub ] +$DUB fetch dub --version=0.9.21 && [ -d $HOME/.dub/packages/dub-0.9.21/dub ] +if $DUB remove dub --non-interactive; then + echo "Non-interactive remove should fail" 1>&2 + exit 1 +fi +echo 1 | $DUB remove dub | tr --delete '\n' | grep --ignore-case 'select.*0\.9\.20.*0\.9\.21.*' +if [ -d $HOME/.dub/packages/dub-0.9.20/dub ]; then + echo "Failed to remove dub-0.9.20" 1>&2 + exit 1 +fi +$DUB fetch dub --version=0.9.20 && [ -d $HOME/.dub/packages/dub-0.9.20/dub ] +# EOF aborts remove +echo -xn '' | $DUB remove dub +if [ ! -d $HOME/.dub/packages/dub-0.9.20/dub ] || [ ! -d $HOME/.dub/packages/dub-0.9.21/dub ]; then + echo "Aborted dub still removed a package" 1>&2 + exit 1 +fi +# validates input +echo -e 'abc\n4\n-1\n3' | $DUB remove dub +if [ -d $HOME/.dub/packages/dub-0.9.20/dub ] || [ -d $HOME/.dub/packages/dub-0.9.21/dub ]; then + echo "Failed to remove all version of dub" 1>&2 + exit 1 +fi +$DUB fetch dub --version=0.9.20 && [ -d $HOME/.dub/packages/dub-0.9.20/dub ] +$DUB fetch dub --version=0.9.21 && [ -d $HOME/.dub/packages/dub-0.9.21/dub ] +# is non-interactive with --version= +$DUB remove dub --version=\* +if [ -d $HOME/.dub/packages/dub-0.9.20/dub ] || [ -d $HOME/.dub/packages/dub-0.9.21/dub ]; then + echo 'Failed to non-interactively remove specified versions' 1>&2 + exit 1 +fi