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.datetime;
17 import std.exception;
18 import std.file;
19 import std.format;
20 import std.process;
21 import std.string;
22 
23 
24 /** Intializes a new package in the given directory.
25 
26 	The given `root_path` will be checked for any of the files that will be
27 	created	by this function. If any exist, an exception will be thrown before
28 	altering the directory.
29 
30 	Params:
31 		root_path = Directory in which to create the new package. If the
32 			directory doesn't exist, a new one will be created.
33 		deps = A set of extra dependencies to add to the package recipe. The
34 			associative array is expected to map from package name to package
35 			version.
36 		type = The type of package skeleton to create. Can currently be
37 			"minimal", "vibe.d" or "deimos"
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(Path root_path, string[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), "The target directory already contains a '"~filename~"' file. Aborting.");
50 	}
51 
52 	string username = getUserName();
53 
54 	PackageRecipe p;
55 	p.name = root_path.head.toString().toLower();
56 	p.authors ~= username;
57 	p.license = "proprietary";
58 	p.copyright = .format("Copyright © %s, %s", Clock.currTime().year, username);
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", ".gitignore"];
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: throw new Exception("Unknown package init type: "~type);
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);
93 }
94 
95 alias RecipeCallback = void delegate(ref PackageRecipe, ref PackageFormat);
96 
97 private void initMinimalPackage(Path 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(Path 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.7.28");
117 	p.description = "A simple vibe.d server application.";
118 	p.buildSettings.versions[""] ~= "VibeDefaultMain";
119 	pre_write_callback();
120 
121 	createDirectory(root_path ~ "source");
122 	createDirectory(root_path ~ "views");
123 	createDirectory(root_path ~ "public");
124 	write((root_path ~ "source/app.d").toNativeString(),
125 q{import vibe.d;
126 
127 shared static this()
128 {
129 	auto settings = new HTTPServerSettings;
130 	settings.port = 8080;
131 	settings.bindAddresses = ["::1", "127.0.0.1"];
132 	listenHTTP(settings, &hello);
133 
134 	logInfo("Please open http://127.0.0.1:8080/ in your browser.");
135 }
136 
137 void hello(HTTPServerRequest req, HTTPServerResponse res)
138 {
139 	res.writeBody("Hello, World!");
140 }
141 });
142 }
143 
144 private void initDeimosPackage(Path 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 private void writeGitignore(Path root_path)
159 {
160 	write((root_path ~ ".gitignore").toNativeString(),
161 		".dub\ndocs.json\n__dummy.html\n*.o\n*.obj\n__test__*__\n");
162 }
163 
164 private string getUserName()
165 {
166 	version (Windows)
167 		return environment.get("USERNAME", "Peter Parker");
168 	else version (Posix)
169 	{
170 		import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc.string : strlen;
171 		import std.algorithm : splitter;
172 
173 		if (auto pw = getpwuid(getuid))
174 		{
175 			auto uinfo = pw.pw_gecos[0 .. strlen(pw.pw_gecos)].splitter(',');
176 			if (!uinfo.empty && uinfo.front.length)
177 				return uinfo.front.idup;
178 		}
179 		return environment.get("USER", "Peter Parker");
180 	}
181 	else
182 		static assert(0);
183 }