1 /**
2 	Package skeleton initialization code.
3 
4 	Copyright: © 2013-2016 rejectedsoftware e.K.
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig
7 */
8 module dub.init;
9 
10 import dub.internal.vibecompat.core.file;
11 import dub.internal.logging;
12 import dub.package_ : PackageFormat, packageInfoFiles, defaultPackageFilename;
13 import dub.recipe.packagerecipe;
14 import dub.dependency;
15 
16 import std.exception;
17 import std.file;
18 import std.format;
19 import std.process : environment;
20 import std.string;
21 
22 
23 /** Initializes a new package in the given directory.
24 
25 	The given `root_path` will be checked for any of the files that will be
26 	created	by this function. If any exist, an exception will be thrown before
27 	altering the directory.
28 
29 	Params:
30 		root_path = Directory in which to create the new package. If the
31 			directory doesn't exist, a new one will be created.
32 		deps = A set of extra dependencies to add to the package recipe. The
33 			associative array is expected to map from package name to package
34 			version.
35 		type = The type of package skeleton to create. Can currently be
36 			"minimal", "vibe.d" or "deimos"
37 		format = Format in which the recipe will be written (SDL / JSON)
38 		recipe_callback = Optional callback that can be used to customize the
39 			package recipe and the file format used to store it prior to
40 			writing it to disk.
41 */
42 void initPackage(NativePath root_path, VersionRange[string] deps, string type,
43 	PackageFormat format, scope RecipeCallback recipe_callback = null)
44 {
45 	import std.conv : to;
46 	import dub.recipe.io : writePackageRecipe;
47 
48 	void enforceDoesNotExist(string filename) {
49 		enforce(!existsFile(root_path ~ filename),
50 			"The target directory already contains a '%s' %s. Aborting."
51 				.format(filename, filename.isDir ? "directory" : "file"));
52 	}
53 
54 	string username = getUserName();
55 
56 	PackageRecipe p;
57 	p.name = root_path.head.name.toLower();
58 	p.authors ~= username;
59 	p.license = "proprietary";
60 	foreach (pack, v; deps) {
61 		p.buildSettings.dependencies[pack] = Dependency(v);
62 	}
63 
64 	//Check to see if a target directory needs to be created
65 	if (!root_path.empty) {
66 		ensureDirectory(root_path);
67 	}
68 
69 	//Make sure we do not overwrite anything accidentally
70 	foreach (fil; packageInfoFiles)
71 		enforceDoesNotExist(fil.filename);
72 
73 	auto files = ["source/", "views/", "public/", "dub.json"];
74 	foreach (fil; files)
75 		enforceDoesNotExist(fil);
76 
77 	void processRecipe()
78 	{
79 		if (recipe_callback)
80 			recipe_callback(p, format);
81 	}
82 
83 	switch (type) {
84 		default: break;
85 		case "minimal": initMinimalPackage(root_path, p, &processRecipe); break;
86 		case "vibe.d": initVibeDPackage(root_path, p, &processRecipe); break;
87 		case "deimos": initDeimosPackage(root_path, p, &processRecipe); break;
88 	}
89 
90 	writePackageRecipe(root_path ~ ("dub."~format.to!string), p);
91 	writeGitignore(root_path, p.name);
92 }
93 
94 alias RecipeCallback = void delegate(ref PackageRecipe, ref PackageFormat);
95 
96 private void initMinimalPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback)
97 {
98 	p.description = "A minimal D application.";
99 	pre_write_callback();
100 
101 	ensureDirectory(root_path ~ "source");
102 	write((root_path ~ "source/app.d").toNativeString(),
103 q{import std.stdio;
104 
105 void main()
106 {
107 	writeln("Edit source/app.d to start your project.");
108 }
109 });
110 }
111 
112 private void initVibeDPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback)
113 {
114 	if ("vibe-d" !in p.buildSettings.dependencies)
115 		p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.9");
116 	p.description = "A simple vibe.d server application.";
117 	pre_write_callback();
118 
119 	ensureDirectory(root_path ~ "source");
120 	ensureDirectory(root_path ~ "views");
121 	ensureDirectory(root_path ~ "public");
122 	write((root_path ~ "source/app.d").toNativeString(),
123 q{import vibe.vibe;
124 
125 void main()
126 {
127 	auto settings = new HTTPServerSettings;
128 	settings.port = 8080;
129 	settings.bindAddresses = ["::1", "127.0.0.1"];
130 	auto listener = listenHTTP(settings, &hello);
131 	scope (exit)
132 	{
133 		listener.stopListening();
134 	}
135 
136 	logInfo("Please open http://127.0.0.1:8080/ in your browser.");
137 	runApplication();
138 }
139 
140 void hello(HTTPServerRequest req, HTTPServerResponse res)
141 {
142 	res.writeBody("Hello, World!");
143 }
144 });
145 }
146 
147 private void initDeimosPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback)
148 {
149 	import dub.compilers.buildsettings : TargetType;
150 
151 	p.description = format("Deimos Bindings for "~p.name~".");
152 	p.buildSettings.importPaths[""] ~= ".";
153 	p.buildSettings.targetType = TargetType.sourceLibrary;
154 	pre_write_callback();
155 
156 	ensureDirectory(root_path ~ "C");
157 	ensureDirectory(root_path ~ "deimos");
158 }
159 
160 /**
161  * Write the `.gitignore` file to the directory, if it does not already exists
162  *
163  * As `dub` is often used with `git`, adding a `.gitignore` is a nice touch for
164  * most users. However, this file is not mandatory for `dub` to do its job,
165  * so we do not depend on the content.
166  * One important use case we need to support is people running `dub init` on
167  * a GitHub-initialized repository. Those might already contain a `.gitignore`
168  * (and a README and a LICENSE), thus we should not bail out if the file already
169  * exists, just ignore it.
170  *
171  * Params:
172  *   root_path = The path to the directory hosting the project
173  *   pkg_name = Name of the package, to generate a list of binaries to ignore
174  */
175 private void writeGitignore(NativePath root_path, const(char)[] pkg_name)
176 {
177     auto full_path = (root_path ~ ".gitignore").toNativeString();
178 
179     if (existsFile(full_path))
180         return;
181 
182     write(full_path,
183 q"{.dub
184 docs.json
185 __dummy.html
186 docs/
187 /%1$s
188 %1$s.so
189 %1$s.dylib
190 %1$s.dll
191 %1$s.a
192 %1$s.lib
193 %1$s-test-*
194 *.exe
195 *.pdb
196 *.o
197 *.obj
198 *.lst
199 }".format(pkg_name));
200 }
201 
202 private string getUserName()
203 {
204 	version (Windows)
205 		return environment.get("USERNAME", "Peter Parker");
206 	else version (Posix)
207 	{
208 		import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc.string : strlen;
209 		import std.algorithm : splitter;
210 
211 		// Bionic doesn't have pw_gecos on ARM
212 		version(CRuntime_Bionic) {} else
213 		if (auto pw = getpwuid(getuid))
214 		{
215 			auto uinfo = pw.pw_gecos[0 .. strlen(pw.pw_gecos)].splitter(',');
216 			if (!uinfo.empty && uinfo.front.length)
217 				return uinfo.front.idup;
218 		}
219 		return environment.get("USER", "Peter Parker");
220 	}
221 	else
222 		static assert(0);
223 }