/** * Contains type definition for `dub.selections.json` */ module dub.recipe.selection; import dub.dependency; import dub.internal.vibecompat.core.file : NativePath; import dub.internal.configy.Attributes; import std.exception; public struct Selected { /// The current version of the file format public uint fileVersion; /// The selected package and their matching versions public SelectedDependency[string] versions; } /// Wrapper around `SelectedDependency` to do deserialization but still provide /// a `Dependency` object to client code. private struct SelectedDependency { public Dependency actual; alias actual this; /// Constructor, used in `fromYAML` public this (inout(Dependency) dep) inout @safe pure nothrow @nogc { this.actual = dep; } /// Allow external code to assign to this object as if it was a `Dependency` public ref SelectedDependency opAssign (Dependency dep) return pure nothrow @nogc { this.actual = dep; return this; } /// Read a `Dependency` from the config file - Required to support both short and long form static SelectedDependency fromYAML (scope ConfigParser!SelectedDependency p) { import dub.internal.dyaml.node; if (p.node.nodeID == NodeID.scalar) return SelectedDependency(Dependency(Version(p.node.as!string))); auto d = p.parseAs!YAMLFormat; if (d.path.length) return SelectedDependency(Dependency(NativePath(d.path))); else { assert(d.version_.length); if (d.repository.length) return SelectedDependency(Dependency(Repository(d.repository, d.version_))); return SelectedDependency(Dependency(Version(d.version_))); } } /// In-file representation of a dependency as permitted in `dub.selections.json` private struct YAMLFormat { @Optional @Name("version") string version_; @Optional string path; @Optional string repository; public void validate () const scope @safe pure { enforce(this.version_.length || this.path.length || this.repository.length, "Need to provide a version string, or an object with one of the following fields: `version`, `path`, or `repository`"); enforce(!this.path.length || !this.repository.length, "Cannot provide a `path` dependency if a repository dependency is used"); enforce(!this.path.length || !this.version_.length, "Cannot provide a `path` dependency if a `version` dependency is used"); enforce(!this.repository.length || this.version_.length, "Cannot provide a `repository` dependency without a `version`"); } } } // Ensure we can read all type of dependencies unittest { import dub.internal.configy.Read : parseConfigString; import dub.internal.vibecompat.core.file : NativePath; immutable string content = `{ "fileVersion": 1, "versions": { "simple": "1.5.6", "branch": "~master", "branch2": "~main", "path": { "path": "../some/where" }, "repository": { "repository": "git+https://github.com/dlang/dub", "version": "123456123456123456" } } }`; auto s = parseConfigString!Selected(content, "/dev/null"); assert(s.fileVersion == 1); assert(s.versions.length == 5); assert(s.versions["simple"] == Dependency(Version("1.5.6"))); assert(s.versions["branch"] == Dependency(Version("~master"))); assert(s.versions["branch2"] == Dependency(Version("~main"))); assert(s.versions["path"] == Dependency(NativePath("../some/where"))); assert(s.versions["repository"] == Dependency(Repository("git+https://github.com/dlang/dub", "123456123456123456"))); }