Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build-files.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,6 @@ source/dub/recipe/json.d
source/dub/recipe/packagerecipe.d
source/dub/recipe/selection.d
source/dub/recipe/sdl.d
source/dub/recipe/yaml.d
source/dub/semver.d
source/dub/version_.d
4 changes: 2 additions & 2 deletions source/dub/commandline.d
Original file line number Diff line number Diff line change
Expand Up @@ -1220,7 +1220,7 @@ class InitCommand : Command {
if (m_nonInteractive) return;

enum free_choice = true;
fmt = select("a package recipe format", !free_choice, fmt.to!string, "sdl", "json").to!PackageFormat;
fmt = select("a package recipe format", !free_choice, fmt.to!string, "sdl", "json", "yaml").to!PackageFormat;
auto author = p.authors.join(", ");
while (true) {
// Tries getting the name until a valid one is given.
Expand Down Expand Up @@ -2961,7 +2961,7 @@ class ConvertCommand : Command {

override void prepare(scope CommandArgs args)
{
args.getopt("f|format", &m_format, ["Specifies the target package recipe format. Possible values:", " json, sdl"]);
args.getopt("f|format", &m_format, ["Specifies the target package recipe format. Possible values:", " json, sdl, yaml"]);
args.getopt("s|stdout", &m_stdout, ["Outputs the converted package recipe to stdout instead of writing to disk."]);
}

Expand Down
2 changes: 1 addition & 1 deletion source/dub/dub.d
Original file line number Diff line number Diff line change
Expand Up @@ -1476,7 +1476,7 @@ class Dub {

Params:
destination_file_ext = The file extension matching the desired
format. Possible values are "json" or "sdl".
format. Possible values are "json", "sdl", or "yaml".
print_only = Print the converted recipe instead of writing to disk
*/
void convertRecipe(string destination_file_ext, bool print_only = false)
Expand Down
11 changes: 7 additions & 4 deletions source/dub/package_.d
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ import std.typecons : Nullable;
/// Lists the supported package recipe formats.
enum PackageFormat {
json, /// JSON based, using the ".json" file extension
sdl /// SDLang based, using the ".sdl" file extension
sdl, /// SDLang based, using the ".sdl" file extension
yaml, /// YAML based, using either `.yaml` or `.yml` extension
}

struct FilenameAndFormat {
Expand All @@ -45,9 +46,11 @@ struct FilenameAndFormat {

/// Supported package descriptions in decreasing order of preference.
static immutable FilenameAndFormat[] packageInfoFiles = [
{"dub.json", PackageFormat.json},
{"dub.sdl", PackageFormat.sdl},
{"package.json", PackageFormat.json}
{ "dub.json", PackageFormat.json },
{ "dub.sdl", PackageFormat.sdl },
{ "dub.yaml", PackageFormat.yaml }, // Official extension
{ "dub.yml", PackageFormat.yaml }, // Common alternative extension
{ "package.json", PackageFormat.json },
];

/// Returns a list of all recognized package recipe file names in descending order of precedence.
Expand Down
12 changes: 9 additions & 3 deletions source/dub/recipe/io.d
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ PackageRecipe parsePackageRecipe(string contents, string filename,
PackageRecipe ret;

ret.name = default_package_name;

if (filename.endsWith(".json"))
{
if (filename.endsWith(".yaml") || filename.endsWith(".yml")) {
// Warn users about unused field, but don't error for forward-compatibility
ret = parseConfigString!PackageRecipe(contents, filename, StrictMode.Warn);
fixDependenciesNames(ret.name, ret);
} else if (filename.endsWith(".json")) {
try {
ret = parseConfigString!PackageRecipe(contents, filename, mode);
fixDependenciesNames(ret.name, ret);
Expand Down Expand Up @@ -245,11 +247,15 @@ void serializePackageRecipe(R)(ref R dst, const scope ref PackageRecipe recipe,
import dub.internal.vibecompat.data.json : writeJsonString;
import dub.recipe.json : toJson;
import dub.recipe.sdl : toSDL;
import dub.recipe.yaml : toYAML;

if (filename.endsWith(".json"))
dst.writeJsonString!(R, true)(toJson(recipe));
else if (filename.endsWith(".sdl"))
toSDL(recipe).toSDLDocument(dst);
else if (filename.endsWith(".yaml") || filename.endsWith(".yml")) {
toJson(recipe).toYAML(dst);
}
else assert(false, "writePackageRecipe called with filename with unknown extension: "~filename);
}

Expand Down
97 changes: 97 additions & 0 deletions source/dub/recipe/yaml.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*******************************************************************************

YAML serialization helper

*******************************************************************************/

module dub.recipe.yaml;

import dub.internal.vibecompat.data.json;

import std.algorithm;
import std.array : appender, Appender;
import std.bigint;
import std.format;
import std.range;

package string toYAML (Json json) {
auto sb = appender!string();
serializeHelper(json, sb, 0);
return sb.data;
}

package void toYAML (R) (Json json, ref R dst) {
serializeHelper(json, dst, 0);
}

private void serializeHelper (R) (Json value, ref R dst, size_t indent, bool skipFirstIndent = false) {
final switch (value.type) {
case Json.Type.object:
foreach (fieldName; FieldOrder) {
if (auto ptr = fieldName in value) {
serializeField(dst, fieldName, *ptr, skipFirstIndent ? 0 : indent);
skipFirstIndent = false;
}
}
foreach (string key, fieldValue; value) {
if (FieldOrder.canFind(key)) continue;
serializeField(dst, key, fieldValue, skipFirstIndent ? 0 : indent);
skipFirstIndent = false;
}
break;
case Json.Type.array:
foreach (size_t idx, element; value) {
formattedWrite(dst, "%*.*0$s- ", indent, ` `);

if (element.isScalar) {
serializeHelper(element, dst, 0);
} else {
serializeHelper(element, dst, indent + 2, true);
}
}
break;
case Json.Type.string:
formattedWrite(dst, `"%s"`, value.get!string);
break;
case Json.Type.bool_:
dst.put(value.get!bool ? "true" : "false");
break;
case Json.Type.null_:
dst.put("null");
break;
case Json.Type.int_:
formattedWrite(dst, "%s", value.get!long);
break;
case Json.Type.bigInt:
formattedWrite(dst, "%s", value.get!BigInt);
break;
case Json.Type.float_:
formattedWrite(dst, "%s", value.get!double);
break;
case Json.Type.undefined:
break;
}
if (value.isScalar)
dst.put("\n");
}

private void serializeField (R) (ref R dst, string key, Json fieldValue, size_t indent) {
formattedWrite(dst, "%*.*0$s%s:", indent, ` `, key);
if (fieldValue.isScalar) {
dst.put(" ");
serializeHelper(fieldValue, dst, 0);
} else {
dst.put("\n");
serializeHelper(fieldValue, dst, indent + 2);
}
}

private bool isScalar(Json value) {
return value.type != Json.Type.object && value.type != Json.Type.array;
}

/// To get a better formatted YAML out of the box
private immutable FieldOrder = [
"name", "description", "homepage", "authors", "copyright", "license",
"toolchainRequirements", "mainSourceFile", "dependencies", "configurations",
];
47 changes: 47 additions & 0 deletions source/dub/test/others.d
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,50 @@ unittest
dub.loadPackage();
assert(dub.project.hasAllDependencies());
}

// Ensure that dub recognizes `dub.yaml`
unittest
{
scope dubJSON = new TestDub((scope Filesystem fs) {
fs.writeFile(TestDub.ProjectPath ~ "dub.json", `{"name":"json"}`);
fs.writeFile(TestDub.ProjectPath ~ "dub.sdl", `name "sdl"`);
fs.writeFile(TestDub.ProjectPath ~ "dub.yaml", `name: yaml`);
fs.writeFile(TestDub.ProjectPath ~ "dub.yml", `name: yml`);
fs.writeFile(TestDub.ProjectPath ~ "package.json", `{"name":"package"}`);
});
dubJSON.loadPackage();
assert(dubJSON.project.name() == "json");

scope dubSDL = dubJSON.newTest((scope Filesystem fs) {
fs.removeFile(TestDub.ProjectPath ~ "dub.json");
});
dubSDL.loadPackage();
assert(dubSDL.project.name() == "sdl");

scope dubYAML = dubSDL.newTest((scope Filesystem fs) {
fs.removeFile(TestDub.ProjectPath ~ "dub.sdl");
});
dubYAML.loadPackage();
assert(dubYAML.project.name() == "yaml");

scope dubYML = dubYAML.newTest((scope Filesystem fs) {
fs.removeFile(TestDub.ProjectPath ~ "dub.yaml");
});
dubYML.loadPackage();
assert(dubYML.project.name() == "yml");

scope dubPackageJSON = dubYML.newTest((scope Filesystem fs) {
fs.removeFile(TestDub.ProjectPath ~ "dub.yml");
});
dubPackageJSON.loadPackage();
assert(dubPackageJSON.project.name() == "package");

scope dubNothing = dubPackageJSON.newTest((scope Filesystem fs) {
fs.removeFile(TestDub.ProjectPath ~ "package.json");
});
try {
dubNothing.loadPackage();
assert(0, "dubNothing should have thrown");
} catch (Exception exc)
assert(exc.message().canFind("No package file found in"));
}
2 changes: 1 addition & 1 deletion test/0-init-interactive.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function runTest {
# sdl package format
runTest '1\ntest\ndesc\nauthor\ngpl\ncopy\n\n' 0-init-interactive.dub.sdl
# select package format out of bounds
runTest '3\n1\ntest\ndesc\nauthor\ngpl\ncopy\n\n' 0-init-interactive.dub.sdl
runTest '4\n1\ntest\ndesc\nauthor\ngpl\ncopy\n\n' 0-init-interactive.dub.sdl
# select package format not numeric, but in list
runTest 'sdl\ntest\ndesc\nauthor\ngpl\ncopy\n\n' 0-init-interactive.dub.sdl
# selected value not numeric and not in list
Expand Down