May 17, 2017
Hi,

I want to share with you a little application I wrote. With the following application you can add local and remote archive dependencies to your dub configuration.

/+ dub.json:
{
	"name":"test"
	"dependencies":{
		"sample1":{"path":"C:\\D\\projects\\test2\\sample1.zip",
		"sample2":{"url":"http://localhost:8080/sample2.zip"
	},
}
+/

void main() {}

Place the dubmore executable into the same folder as dub and use dubmore instead of dub.

Remarks:
- Only json is supported
- You need to have the console application unzip in your system path

--
module dubmore;
import std.file, std.path, std.exception, std.process, std.array, std.json, std.net.curl, std.experimental.logger, std.algorithm, std.getopt, std.string;

void main(string[] args)
{
	globalLogLevel = LogLevel.all;
	string dubFile;
	scope(exit) if (exists(dubFile~".old")) rename(dubFile~".old", dubFile);
	string[] dubArgs;
	
	string packagesFolder = buildPath(getcwd(), "packages");
	if (args.canFind("--force") && exists(packagesFolder)) rmdirRecurse(packagesFolder);
	if (!exists(packagesFolder)) mkdir(packagesFolder);
	
	if (exists(buildPath(getcwd(), "dub.json")))
	{
		dubFile = buildPath(getcwd(), "dub.json");
		dubArgs = args[1..$];
		auto jsRoot = parseJSON(readText(dubFile));
		if (processJson(jsRoot, packagesFolder))
		{
			copy(dubFile, dubFile~".old");
			writeTextFile(dubFile, jsRoot.toString);
		}
	}
	else if (args.length > 1 && buildPath(getcwd(), args[1].stripExtension~".d").exists)
	{
		enum marker = "dub.json:";
		dubFile = buildPath(getcwd(), args[1].stripExtension~".d");
		dubArgs = dubFile~args[2..$];
		string sourceCode = readText(dubFile);
		auto startPos = sourceCode.indexOf("/+");
		auto markerPos = sourceCode.indexOf(marker);
		auto endPos =  sourceCode.indexOf("+/");
		if (startPos > -1 && markerPos > startPos && markerPos < endPos)
		{
			auto jsRoot = parseJSON(sourceCode[markerPos+marker.length..endPos]);
			if (processJson(jsRoot, packagesFolder))
			{
				sourceCode = sourceCode[0..markerPos+marker.length]~" "~jsRoot.toString~" "~sourceCode[endPos..$];
				copy(dubFile, dubFile~".old");
				writeTextFile(dubFile, sourceCode);
			}
		}
	}
	import std.stdio: writeln;
	with (execute(["dub"]~dubArgs)) writeln(output);
}

private bool processJson(JSONValue jsRoot, string packagesFolder)
{
	bool found = processDependencies(jsRoot, packagesFolder);
	if ("configurations" in jsRoot && jsRoot["configurations"].type == JSON_TYPE.ARRAY)
			found = jsRoot["configurations"].array.any!(jsConfig => processDependencies(jsConfig, packagesFolder)) || found;
	return found;
}

private bool processDependencies(JSONValue js, string packagesFolder)
{
	bool result;
	if ("dependencies" in js && js["dependencies"].type == JSON_TYPE.OBJECT)
	{
		foreach(key; js["dependencies"].object.keys)
		{
			auto dep = js["dependencies"].object[key];
			if (dep.type == JSON_TYPE.OBJECT && "url" in dep && dep["url"].type == JSON_TYPE.STRING)
			{
				string url = dep["url"].str;
				string fileName = url.split("/").array[$-1];
				string destination = buildPath(packagesFolder, fileName);
				
				if (exists(destination))
				{
					dep["path"] = buildPath(destination.dirName, destination.stripExtension);
				}
				else
				{
					try	{ dep["path"] = downloadAndExtract(url, destination); }
					catch(Exception e)
					{
						error(e.msg);
						continue;
					}
				}
				dep.object.remove("url");
				result = true;
			}
			else if (dep.type == JSON_TYPE.OBJECT && "path" in dep && dep["path"].type == JSON_TYPE.STRING && dep["path"].str.toLower.endsWith(".zip"))
			{
				string filePath = dep["path"].str;
				string packageFolder = buildPath(packagesFolder, filePath.baseName.stripExtension);

				if (!exists(packageFolder))
					with (execute(["unzip", "-o", filePath, "-d", packagesFolder])) enforce(status == 0, output);

				dep["path"] = packageFolder;	
				result = true;
			}
		}
	}
	return result;
}

private string downloadAndExtract(string url, string destination)
{
	info("download: ", url, " to ", destination);
	download(url, destination);
	enforce(destination.toLower.endsWith(".zip"), "Unknown file extenstion");
	with (execute(["unzip", destination, "-d", destination.dirName])) enforce(status == 0, output);
	return buildPath(destination.dirName, destination.baseName!(std.path.CaseSensitive.no)(".zip"));
}

private void writeTextFile(string filePath, string content)
{
	import std.stdio: File;
	auto f = File(filePath, "wb");
	f.write(content);
    f.close();
}

--

Kind regards
André