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