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 	// Use proprietary as conservative default, so that we don't announce a more
60 	// permissive license than actually chosen in case the dub.json wasn't updated.
61 	p.license = "proprietary";
62 	foreach (pack, v; deps) {
63 		p.buildSettings.dependencies[pack] = Dependency(v);
64 	}
65 
66 	//Check to see if a target directory needs to be created
67 	if (!root_path.empty) {
68 		ensureDirectory(root_path);
69 	}
70 
71 	//Make sure we do not overwrite anything accidentally
72 	foreach (fil; packageInfoFiles)
73 		enforceDoesNotExist(fil.filename);
74 
75 	auto files = ["source/", "views/", "public/", "dub.json"];
76 	foreach (fil; files)
77 		enforceDoesNotExist(fil);
78 
79 	void processRecipe()
80 	{
81 		if (recipe_callback)
82 			recipe_callback(p, format);
83 	}
84 
85 	switch (type) {
86 		default: break;
87 		case "minimal": initMinimalPackage(root_path, p, &processRecipe); break;
88 		case "vibe.d": initVibeDPackage(root_path, p, &processRecipe); break;
89 		case "deimos": initDeimosPackage(root_path, p, &processRecipe); break;
90 	}
91 
92 	writePackageRecipe(root_path ~ ("dub."~format.to!string), p);
93 	writeGitignore(root_path, p.name);
94 }
95 
96 alias RecipeCallback = void delegate(ref PackageRecipe, ref PackageFormat);
97 
98 private void initMinimalPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback)
99 {
100 	p.description = "A minimal D application.";
101 	pre_write_callback();
102 
103 	ensureDirectory(root_path ~ "source");
104 	write((root_path ~ "source/app.d").toNativeString(),
105 q{import std.stdio;
106 
107 void main()
108 {
109 	writeln("Edit source/app.d to start your project.");
110 }
111 });
112 }
113 
114 private void initVibeDPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback)
115 {
116 	if ("vibe-d" !in p.buildSettings.dependencies)
117 		p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.9");
118 	p.description = "A simple vibe.d server application.";
119 	pre_write_callback();
120 
121 	ensureDirectory(root_path ~ "source");
122 	ensureDirectory(root_path ~ "views");
123 	ensureDirectory(root_path ~ "public");
124 	write((root_path ~ "source/app.d").toNativeString(),
125 q{import vibe.vibe;
126 
127 void main()
128 {
129 	auto settings = new HTTPServerSettings;
130 	settings.port = 8080;
131 	settings.bindAddresses = ["::1", "127.0.0.1"];
132 	auto listener = listenHTTP(settings, &hello);
133 	scope (exit)
134 	{
135 		listener.stopListening();
136 	}
137 
138 	logInfo("Please open http://127.0.0.1:8080/ in your browser.");
139 	runApplication();
140 }
141 
142 void hello(HTTPServerRequest req, HTTPServerResponse res)
143 {
144 	res.writeBody("Hello, World!");
145 }
146 });
147 }
148 
149 private void initDeimosPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback)
150 {
151 	import dub.compilers.buildsettings : TargetType;
152 
153 	p.description = format("Deimos Bindings for "~p.name~".");
154 	p.buildSettings.importPaths[""] ~= ".";
155 	p.buildSettings.targetType = TargetType.sourceLibrary;
156 	pre_write_callback();
157 
158 	ensureDirectory(root_path ~ "C");
159 	ensureDirectory(root_path ~ "deimos");
160 }
161 
162 /**
163  * Write the `.gitignore` file to the directory, if it does not already exists
164  *
165  * As `dub` is often used with `git`, adding a `.gitignore` is a nice touch for
166  * most users. However, this file is not mandatory for `dub` to do its job,
167  * so we do not depend on the content.
168  * One important use case we need to support is people running `dub init` on
169  * a GitHub-initialized repository. Those might already contain a `.gitignore`
170  * (and a README and a LICENSE), thus we should not bail out if the file already
171  * exists, just ignore it.
172  *
173  * Params:
174  *   root_path = The path to the directory hosting the project
175  *   pkg_name = Name of the package, to generate a list of binaries to ignore
176  */
177 private void writeGitignore(NativePath root_path, const(char)[] pkg_name)
178 {
179     auto full_path = (root_path ~ ".gitignore").toNativeString();
180 
181     if (existsFile(full_path))
182         return;
183 
184     write(full_path,
185 q"{.dub
186 docs.json
187 __dummy.html
188 docs/
189 /%1$s
190 %1$s.so
191 %1$s.dylib
192 %1$s.dll
193 %1$s.a
194 %1$s.lib
195 %1$s-test-*
196 *.exe
197 *.pdb
198 *.o
199 *.obj
200 *.lst
201 }".format(pkg_name));
202 }
203 
204 private string getUserName()
205 {
206 	version (Windows)
207 		return environment.get("USERNAME", "Peter Parker");
208 	else version (Posix)
209 	{
210 		import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc.string : strlen;
211 		import std.algorithm : splitter;
212 
213 		// Bionic doesn't have pw_gecos on ARM
214 		version(CRuntime_Bionic) {} else
215 		if (auto pw = getpwuid(getuid))
216 		{
217 			auto uinfo = pw.pw_gecos[0 .. strlen(pw.pw_gecos)].splitter(',');
218 			if (!uinfo.empty && uinfo.front.length)
219 				return uinfo.front.idup;
220 		}
221 		return environment.get("USER", "Peter Parker");
222 	}
223 	else
224 		static assert(0);
225 }