1const std = @import("std");
2const builtin = @import("builtin");
3const string = []const u8;
4const gpa = std.heap.c_allocator;
5const inquirer = @import("inquirer");
6const detectlicense = @import("detect-license");
7const knownfolders = @import("known-folders");
8const ini = @import("ini");
9const time = @import("time");
10const extras = @import("extras");
11const nio = @import("nio");
12const nfs = @import("nfs");
13const root = @import("root");
14
15const u = @import("./../util/funcs.zig");
16const common = @import("./../common.zig");
17const zigmod = @import("./../lib.zig");
18
19//
20//
21
22pub fn execute(self_name: []const u8, args: []const [:0]const u8) !void {
23 _ = self_name;
24
25 std.debug.print("This utility will walk you through creating a zigmod.yml file.\n", .{});
26 std.debug.print("That will give a good launching off point to get your next project started.\n", .{});
27 std.debug.print("Press ^C at any time to quit.\n", .{});
28 std.debug.print("\n", .{});
29
30 const stdout = nfs.stdout();
31 const stdin = nfs.stdin();
32 const cwd = nfs.cwd();
33
34 const id = try inquirer.answer(stdout, "ID (this gets autogenerated):", string, "{s}", &u.random_string(48));
35
36 const ptype = try inquirer.forEnum(stdout, stdin, "Are you making an application or a library?", gpa, enum { exe, lib }, null);
37
38 const name = try inquirer.forString(stdout, stdin, "package name:", gpa, try u.detect_pkgname(gpa, u.try_index(string, args, 0, ""), ""));
39
40 const flatname = try gpa.dupe(u8, name);
41 _ = std.mem.replaceScalar(u8, flatname, '-', '_');
42
43 const entry_bare = if (ptype == .lib) try inquirer.forString(stdout, stdin, "package entry point:", gpa, u.detct_mainfile(gpa, u.try_index([:0]const u8, args, 1, ""), .cwd(), name) catch |err| switch (err) {
44 error.CantFindMain => try std.mem.concat(gpa, u8, &.{ flatname, ".zig" }),
45 else => |ee| return ee,
46 }) else null;
47 const entry = if (entry_bare) |ent| try gpa.dupeZ(u8, ent) else null;
48
49 const maybe_license_text = try detectlicense.detectInDir(gpa, cwd);
50 const license = try inquirer.forString(stdout, stdin, "license:", gpa, maybe_license_text);
51
52 const description = try inquirer.forString(stdout, stdin, "description:", gpa, null);
53
54 std.debug.print("\n", .{});
55 std.debug.print("About to write local zigmod.yml:\n", .{});
56
57 std.debug.print("\n", .{});
58 switch (ptype) {
59 .exe => try writeExeManifest(stdout, id, name, license, description),
60 .lib => try writeLibManifest(stdout, id, name, license, description, entry.?),
61 }
62
63 std.debug.print("\n", .{});
64 switch (try inquirer.forConfirm(stdout, stdin, "Is this okay?", gpa)) {
65 false => {
66 std.debug.print("okay. quitting...", .{});
67 return;
68 },
69 true => {
70 const file = try cwd.createFile("zigmod.yml", .{});
71 defer file.close();
72 const w = file;
73 switch (ptype) {
74 .exe => try writeExeManifest(w, id, name, license, description),
75 .lib => try writeLibManifest(w, id, name, license, description, entry.?),
76 }
77 std.debug.print("\n", .{});
78 std.debug.print("Successfully initialized new package {s}!\n", .{name});
79 },
80 }
81
82 // ask about LICENSE
83 if (maybe_license_text == null) {
84 if (detectlicense.licenses.find(license)) |text| {
85 if (try inquirer.forConfirm(stdout, stdin, "It appears you don't have a LICENSE file defined, would you like init to add it for you?", gpa)) {
86 var realtext = text;
87 realtext = try std.mem.replaceOwned(u8, gpa, realtext, "<year>", try inquirer.answer(
88 stdout,
89 "year:",
90 string,
91 "{s}",
92 try nio.fmt.allocPrint(gpa, "{d}", .{time.DateTime.now().years}),
93 ));
94 realtext = try std.mem.replaceOwned(u8, gpa, realtext, "<copyright holders>", try inquirer.forString(
95 stdout,
96 stdin,
97 "copyright holder's name:",
98 gpa,
99 try guessCopyrightName(),
100 ));
101
102 const file = try cwd.createFile("LICENSE", .{});
103 defer file.close();
104 const w = file;
105
106 // properly format license text to fit within 80 columns
107 var start: usize = 0;
108 var end: usize = 0;
109 var run: u32 = 0;
110 var i: usize = 0;
111 while (i < realtext.len) {
112 const c = realtext[i];
113 end += 1;
114 i += 1;
115
116 if (c == '\n') {
117 try w.writeAll(realtext[start..end]);
118 start = end;
119 run = 0;
120 i = start;
121 continue;
122 }
123 run += 1;
124
125 if (run >= 79) {
126 const s_ind = (std.mem.lastIndexOfScalar(u8, realtext[start..end], ' ') orelse end) + start;
127 const n_ind = (std.mem.lastIndexOfScalar(u8, realtext[start .. end - 1], '\n') orelse end) + start;
128 const ind = @min(s_ind, n_ind);
129 try w.print("{s}\n", .{realtext[start..ind]});
130 end = ind + 1;
131 start = end;
132 run = 0;
133 i = start;
134 }
135 }
136 try w.writeAll(realtext[start..realtext.len]);
137 }
138 }
139 }
140
141 // ask about .gitignore
142 if (try nfs.cwd().existsDir(".git")) {
143 const do = try inquirer.forConfirm(stdout, stdin, "It appears you're using git. Do you want init to add Zigmod to your .gitignore?", gpa);
144 if (do) {
145 const exists = try nfs.cwd().exists(".gitignore");
146 const file = try cwd.createFile(".gitignore", .{ .truncate = false });
147 defer file.close();
148 const len = try file.getEndPos();
149 if (len > 0) try file.seekTo(len - 1);
150 const w = file;
151 if (len > 0 and (try file.readByte()) != '\n') {
152 try w.writeAll("\n");
153 }
154 if (!exists) try w.writeAll(".zig-cache\n");
155 if (!exists) try w.writeAll("zig-out\n");
156 try w.writeAll(".zigmod\n");
157 try w.writeAll("deps.zig\n");
158 try w.writeAll("files.zig\n");
159 if (ptype == .lib) try w.writeAll("zigmod.lock\n");
160 }
161 }
162
163 // ask about .gitattributes
164 if (try nfs.cwd().existsDir(".git")) {
165 const do = try inquirer.forConfirm(stdout, stdin, "It appears you're using git. Do you want init to add Zigmod to your .gitattributes?", gpa);
166 if (do) {
167 const exists = try nfs.cwd().exists(".gitattributes");
168 const file = try cwd.createFile(".gitattributes", .{ .truncate = false });
169 defer file.close();
170 const len = try file.getEndPos();
171 if (len > 0) try file.seekTo(len - 1);
172 const w = file;
173 if (len > 0 and (try file.readByte()) != '\n') {
174 try w.writeAll("\n");
175 }
176 if (!exists) try w.writeAll("* text=auto\n");
177 if (!exists) try w.writeAll("*.zig text eol=lf\n");
178 try w.writeAll("zigmod.* text eol=lf\n");
179 }
180 }
181
182 // ask about build.zig
183 if (!try nfs.cwd().exists("build.zig")) {
184 const do = try inquirer.forConfirm(stdout, stdin, "It looks like there's no build.zig. Do you want Zigmod to generate one for you?", gpa);
185 if (do) {
186 const file = try cwd.createFile("build.zig", .{});
187 defer file.close();
188 const w = file;
189 switch (ptype) {
190 .exe => {
191 try w.writeAll(
192 \\const std = @import("std");
193 \\const deps = @import("./deps.zig");
194 \\
195 \\pub fn build(b: *std.Build) void {
196 \\ const target = b.standardTargetOptions(.{});
197 \\ const mode = b.option(std.builtin.Mode, "mode", "") orelse .Debug;
198 \\
199 \\ const exe = b.addExecutable(.{
200 \\
201 );
202 try w.print(" .name = \"{s}\",\n", .{name});
203 try w.writeAll(
204 \\ .root_source_file = b.path("main.zig"),
205 \\ .target = target,
206 \\ .optimize = mode,
207 \\ });
208 \\ deps.addAllTo(exe);
209 \\ b.installArtifact(exe);
210 \\
211 \\ const run_step = b.step("run", "Run the app");
212 \\ const run_cmd = b.addRunArtifact(exe);
213 \\ run_cmd.step.dependOn(b.getInstallStep());
214 \\ if (b.args) |args| run_cmd.addArgs(args);
215 \\ run_step.dependOn(&run_cmd.step);
216 \\}
217 \\
218 );
219 },
220 .lib => {
221 try w.writeAll(
222 \\const std = @import("std");
223 \\const deps = @import("./deps.zig");
224 \\
225 \\pub fn build(b: *std.Build) void {
226 \\ const target = b.standardTargetOptions(.{});
227 \\ const mode = b.option(std.builtin.Mode, "mode", "") orelse .Debug;
228 \\
229 \\ const tests = b.addTest(.{
230 \\ .root_source_file = b.path("test.zig"),
231 \\ .target = target,
232 \\ .optimize = mode,
233 \\ });
234 \\ deps.addAllTo(tests);
235 \\
236 \\ const test_step = b.step("test", "Run all library tests");
237 \\ const tests_run = b.addRunArtifact(tests);
238 \\ tests_run.setCwd(b.path("."));
239 \\ tests_run.has_side_effects = true;
240 \\ test_step.dependOn(&tests_run.step);
241 \\}
242 \\
243 );
244 },
245 }
246 }
247 }
248
249 // ask about main.zig / mod.zig
250 if (!try nfs.cwd().exists(entry orelse "main.zig")) {
251 const do = try inquirer.forConfirm(stdout, stdin, "It looks like your entry point doesn't exist. Do you want Zigmod to generate it for you?", gpa);
252 if (do) {
253 const file = try cwd.createFile(entry orelse "main.zig", .{});
254 defer file.close();
255 const w = file;
256 switch (ptype) {
257 .exe => {
258 try w.print(
259 \\const std = @import("std");
260 \\const {s} = @import("{s}");
261 \\
262 \\pub fn main() void {{
263 \\ //
264 \\}}
265 \\
266 ,
267 .{
268 flatname,
269 name,
270 },
271 );
272 },
273 .lib => {
274 try w.writeAll(
275 \\const std = @import("std");
276 \\
277 );
278 },
279 }
280 }
281 }
282
283 // ask about test.zig
284 if (!try nfs.cwd().exists("test.zig")) {
285 const do = try inquirer.forConfirm(stdout, stdin, "It looks like there's no test.zig. Do you want Zigmod to generate it for you?", gpa);
286 if (do) {
287 const file = try cwd.createFile("test.zig", .{});
288 defer file.close();
289 const w = file;
290 try w.print(
291 \\const std = @import("std");
292 \\const {s} = @import("{s}");
293 \\
294 ,
295 .{
296 flatname,
297 name,
298 },
299 );
300 }
301 }
302
303 // write a deps.zig
304 {
305 const alloc = gpa;
306 const cachepath = try u.find_cachepath();
307 const dir = nfs.cwd();
308
309 var options = common.CollectOptions{
310 .log = true,
311 .update = false,
312 .alloc = alloc,
313 };
314 const top_module = try common.collect_deps_deep(cachepath, dir, &options);
315
316 var list = std.array_list.Managed(zigmod.Module).init(alloc);
317 try common.collect_pkgs(top_module, &list);
318
319 const fetch = @import("./fetch.zig");
320 try fetch.create_depszig(alloc, cachepath, dir, top_module, &list);
321 }
322}
323
324pub fn writeExeManifest(w: nfs.File, id: string, name: string, license: ?string, description: ?string) !void {
325 try w.print("id: {s}\n", .{id});
326 try w.print("name: {s}\n", .{name});
327 if (license) |_| try w.print("license: {s}\n", .{license.?});
328 if (description) |_| try w.print("description: {s}\n", .{description.?});
329 try w.print("min_zig_version: {s}\n", .{builtin.zig_version_string});
330 try w.print("min_zigmod_version: r96\n", .{});
331 try w.writeAll("root_dependencies:\n");
332}
333
334pub fn writeLibManifest(w: nfs.File, id: string, name: string, license: string, description: string, entry: string) !void {
335 try w.print("id: {s}\n", .{id});
336 try w.print("name: {s}\n", .{name});
337 try w.print("main: {s}\n", .{entry});
338 try w.print("license: {s}\n", .{license});
339 try w.print("description: {s}\n", .{description});
340 try w.print("min_zig_version: {s}\n", .{builtin.zig_version_string});
341 try w.print("min_zigmod_version: r96\n", .{});
342 try w.writeAll("dependencies:\n");
343}
344
345fn guessCopyrightName() !?string {
346 const io = root.io;
347 const homepath = (try knownfolders.getPath(io, gpa, root.environ, .home)).?;
348 const home = try nfs.cwd().openDirC(homepath, .{});
349 if (!(try home.exists(".gitconfig"))) return null;
350 const file = try home.openFile(".gitconfig", .{});
351 const content = try file.readAllAlloc(gpa, 1024 * 1024);
352 var iniO = try ini.parseIntoMap(content, gpa);
353 return iniO.map.get("user.name");
354}