1 module dub.packagesuppliers.packagesupplier;
2 
3 public import dub.dependency : PackageName, Dependency, Version, VersionRange;
4 import dub.dependency : visit;
5 public import dub.internal.vibecompat.core.file : NativePath;
6 public import dub.internal.vibecompat.data.json : Json;
7 
8 /**
9 	Base interface for remote package suppliers.
10 
11 	Provides functionality necessary to query package versions, recipes and
12 	contents.
13 */
14 interface PackageSupplier {
15 	/// Represents a single package search result.
16 	static struct SearchResult { string name, description, version_; }
17 
18 	/// Returns a human-readable representation of the package supplier.
19 	@property string description();
20 
21 	/** Retrieves a list of all available versions(/branches) of a package.
22 
23 		Throws: Throws an exception if the package name is not known, or if
24 			an error occurred while retrieving the version list.
25 	*/
26 	deprecated("Use `getVersions(PackageName)` instead")
27 	final Version[] getVersions(string name)
28 	{
29 		return this.getVersions(PackageName(name));
30 	}
31 
32 	Version[] getVersions(in PackageName name);
33 
34 
35 	/** Downloads a package and returns its binary content
36 
37 		Params:
38 			name = Name of the package to retrieve
39 			dep = Version constraint to match against
40 			pre_release = If true, matches the latest pre-release version.
41 				Otherwise prefers stable versions.
42 	*/
43 	ubyte[] fetchPackage(in PackageName name, in VersionRange dep,
44 		bool pre_release);
45 
46 	deprecated("Use `writeFile(path, fetchPackage(PackageName, VersionRange, bool))` instead")
47 	final void fetchPackage(in NativePath path, in PackageName name,
48 		in VersionRange dep, bool pre_release)
49 	{
50 		import dub.internal.vibecompat.core.file : writeFile;
51 		if (auto res = this.fetchPackage(name, dep, pre_release))
52 			writeFile(path, res);
53 	}
54 
55     deprecated("Use `fetchPackage(NativePath, PackageName, VersionRange, bool)` instead")
56 	final void fetchPackage(NativePath path, string name, Dependency dep, bool pre_release)
57     {
58         return dep.visit!(
59             (const VersionRange rng) {
60                 return this.fetchPackage(path, PackageName(name), rng, pre_release);
61             }, (any) {
62                 assert(0, "Trying to fetch a package with a non-version dependency: " ~ any.toString());
63             },
64         );
65     }
66 
67 	/** Retrieves only the recipe of a particular package.
68 
69 		Params:
70 			package_id = Name of the package of which to retrieve the recipe
71 			dep = Version constraint to match against
72 			pre_release = If true, matches the latest pre-release version.
73 				Otherwise prefers stable versions.
74 	*/
75 	Json fetchPackageRecipe(in PackageName name, in VersionRange dep, bool pre_release);
76 
77     deprecated("Use `fetchPackageRecipe(PackageName, VersionRange, bool)` instead")
78 	final Json fetchPackageRecipe(string name, Dependency dep, bool pre_release)
79     {
80         return dep.visit!(
81             (const VersionRange rng) {
82                 return this.fetchPackageRecipe(PackageName(name), rng, pre_release);
83             }, (any) {
84                 return Json.init;
85             },
86         );
87     }
88 
89 	/** Searches for packages matching the given search query term.
90 
91 		Search queries are currently a simple list of words separated by
92 		white space. Results will get ordered from best match to worst.
93 	*/
94 	SearchResult[] searchPackages(string query);
95 }
96 
97 // TODO: Could drop the "best package" behavior and let retrievePackage/
98 //       getPackageDescription take a Version instead of Dependency. But note
99 //       this means that two requests to the registry are necessary to retrieve
100 //       a package recipe instead of one (first get version list, then the
101 //       package recipe)
102 
103 package Json getBestPackage(Json metadata, in PackageName name,
104 	in VersionRange dep, bool pre_release)
105 {
106 	import std.exception : enforce;
107 	import std.format : format;
108 
109 	if (metadata.type == Json.Type.null_)
110 		return metadata;
111 	Json best = null;
112 	Version bestver;
113 	foreach (json; metadata["versions"]) {
114 		auto cur = Version(json["version"].get!string);
115 		if (!dep.matches(cur)) continue;
116 		if (best == null) best = json;
117 		else if (pre_release) {
118 			if (cur > bestver) best = json;
119 		} else if (bestver.isPreRelease) {
120 			if (!cur.isPreRelease || cur > bestver) best = json;
121 		} else if (!cur.isPreRelease && cur > bestver) best = json;
122 		bestver = Version(cast(string)best["version"]);
123 	}
124 	enforce(best != null,
125 		"No package candidate found for %s@%s".format(name.main, dep));
126 	return best;
127 }