Makko, the people-oriented static site generator made for blogging.

HOTFIX Revamped Changes system, Now the commands are handled by the system shell.

+81 -28
+9 -10
doc/blog/english.md
··· 215 215 [ 216 216 { 217 217 "status": "created", 218 - "id": "WHATEVER000", 218 + "id": 123456789, 219 219 220 - "source": "cakepie.md", 221 - "url": "cakepie.html", 220 + "source": "/home/someone/website/blog/cakepie.md", 221 + "output": "/home/someone/website/web/cakepie.html", 222 222 223 223 "title": "wow i love cake and pie!", 224 224 "description": "here's how to make a cake that is also a pie, yeah!", ··· 227 227 228 228 { 229 229 "status": "modified", 230 - "id": "SUPERCOOL123", 230 + "id": 123456789, 231 231 232 - "source": "supercool.md", 233 - "url": "supercool.html", 232 + "source": "/home/someone/website/blog/supercool.md", 233 + "output": "/home/someone/website/web/supercool.html", 234 234 235 235 "title": "i think superman is supercool", 236 236 "description": "i cried with the new movie.... i cried....", ··· 239 239 240 240 { 241 241 "status": "deleted", 242 - "id": "EMBARRASINGPOST985", 242 + "id": 123456789, 243 243 244 - "source": "jermakiss.md", 245 - "url": "jermakiss.html" 244 + "source": "/home/someone/website/blog/jermakiss.md" 246 245 }, 247 246 ... 248 247 ``` ··· 265 264 266 265 As you can see from above, the data gets passed through `<stdin>` and an environment variable named `FROM_MAKKO` is set (specifically to `yes`). 267 266 268 - > **Note:** The command you pass won't be run through bash or any similar shells, so syntax like `|` or `&` won't work. You can still call a shell manually, though. 267 + > **Note:** The command you pass will be subject to the OS' shell syntax, as it will run `cmd.exe` on Windows and `/bin/sh` on Linux 269 268 270 269 271 270 # Posts, and Frontmatter
+11 -7
src/Callbacks.zig
··· 19 19 id: Data.Id, 20 20 21 21 source: ?[]const u8 = null, 22 - url: ?[]const u8 = null, 22 + output: ?[]const u8 = null, 23 23 24 24 title: ?[]const u8 = null, 25 25 description: ?[]const u8 = null, ··· 37 37 else 38 38 null, 39 39 40 - .url = if (self.url) |url| 41 - try allocator.dupe(u8, url) 40 + .output = if (self.output) |output| 41 + try allocator.dupe(u8, output) 42 42 else 43 43 null, 44 44 ··· 63 63 64 64 pub fn deinit(self: Change, allocator: std.mem.Allocator) void { 65 65 if (self.source) |src| allocator.free(src); 66 - if (self.url) |url| allocator.free(url); 66 + if (self.output) |output| allocator.free(output); 67 67 if (self.title) |title| allocator.free(title); 68 68 if (self.description) |description| allocator.free(description); 69 69 if (self.author) |author| allocator.free(author); ··· 86 86 var args = std.ArrayList([]const u8).init(allocator); 87 87 defer args.deinit(); 88 88 89 - var it = std.mem.splitScalar(u8, program, ' '); 90 - while (it.next()) |arg| { 91 - try args.append(arg); 89 + if (builtin.os.tag == .windows) { 90 + try args.append("cmd.exe"); 91 + try args.append("/C"); 92 + } else { 93 + try args.append("/bin/sh"); 94 + try args.append("-c"); 92 95 } 96 + try args.append(program); 93 97 94 98 var child = std.process.Child.init(args.items, allocator); 95 99
+29 -2
src/Makko.zig
··· 355 355 return Pass.init(self); 356 356 } 357 357 358 + const EntryError = struct { []const u8, anyerror }; 359 + 360 + const ErrorList = struct { 361 + errors: std.ArrayList(EntryError), 362 + mutex: Mutex = .{}, 363 + }; 364 + 365 + // TODO: FIX SILENT ERRORS! 358 366 fn onThread( 359 367 pass: *Pass, 360 368 entry: []const u8, 369 + error_list: *ErrorList, 361 370 ) void { 362 - _ = pass.processFile(entry) catch {}; 371 + _ = pass.processFile(entry) catch |err| { 372 + error_list.mutex.lock(); 373 + defer error_list.mutex.unlock(); 374 + 375 + error_list.errors.append(.{ entry, err }) catch {}; 376 + }; 363 377 } 364 378 365 379 pub fn automaticPass(self: *Makko) !Pass { ··· 378 392 defer arena.deinit(); 379 393 const arena_alloc = arena.allocator(); 380 394 395 + var error_list: ErrorList = .{ 396 + .errors = std.ArrayList(EntryError).init(self.allocator), 397 + }; 398 + defer error_list.errors.deinit(); 399 + 381 400 var pool: std.Thread.Pool = undefined; 382 401 try pool.init(.{ .allocator = self.allocator }); 383 402 ··· 390 409 continue; 391 410 } 392 411 393 - try pool.spawn(onThread, .{ &pass, try arena_alloc.dupe(u8, entry.path) }); 412 + try pool.spawn(onThread, .{ 413 + &pass, 414 + try arena_alloc.dupe(u8, entry.path), 415 + &error_list, 416 + }); 394 417 } 395 418 } 396 419 397 420 pool.deinit(); 421 + 422 + for (error_list.errors.items) |err| { 423 + self.log.err("Error processing {s} ({})", err); 424 + } 398 425 399 426 { 400 427 var iter = self.hashes.iterator();
+32 -9
src/Pass.zig
··· 11 11 12 12 const Pass = @This(); 13 13 14 - const CallbackList = std.ArrayList(Callbacks.Change); 14 + const ChangeList = std.ArrayList(Callbacks.Change); 15 15 16 16 parent: *Makko, 17 - changes: CallbackList, 17 + changes: ChangeList, 18 18 changes_mutex: std.Thread.Mutex = .{}, 19 19 20 20 progress: std.Progress.Node, ··· 22 22 pub fn init(self: *Makko) Pass { 23 23 return Pass{ 24 24 .parent = self, 25 - .changes = CallbackList.init(self.allocator), 25 + .changes = ChangeList.init(self.allocator), 26 26 .progress = self.progress.start("Pass", 1), 27 27 }; 28 28 } ··· 226 226 pass.changes_mutex.lock(); 227 227 defer pass.changes_mutex.unlock(); 228 228 229 + const real_source = try parent.paths.source.realpathAlloc(allocator, file); 230 + defer allocator.free(real_source); 231 + 232 + const pre_real_output = try parent.paths.output.realpathAlloc(allocator, "."); 233 + defer allocator.free(pre_real_output); 234 + 235 + const real_output = try std.fs.path.join(allocator, &.{ 236 + pre_real_output, url, 237 + }); 238 + defer allocator.free(real_output); 239 + 229 240 const change: Callbacks.Change = .{ 230 241 .status = status, 231 242 .id = f.id, 232 243 233 - .source = file, 234 - .url = url, 244 + .source = real_source, 245 + .output = real_output, 235 246 236 247 .title = f.title, 237 248 .description = f.description, ··· 303 314 304 315 pub fn markDeletedById(pass: *Pass, id: Data.Id) !void { 305 316 const parent = pass.parent; 317 + const allocator = parent.allocator; 306 318 307 319 { 308 320 parent.hashes_mutex.lock(); ··· 322 334 pass.changes_mutex.lock(); 323 335 defer pass.changes_mutex.unlock(); 324 336 337 + var source: ?[]const u8 = null; 338 + if (pass.parent.processed_posts.get(id)) |path| { 339 + source = try allocator.dupe(u8, path); 340 + } 341 + 325 342 try pass.changes.append(.{ 326 343 .timestamp = std.time.milliTimestamp(), 327 344 .status = .deleted, 328 345 .id = id, 346 + .source = source, 329 347 }); 330 348 } 331 349 ··· 383 401 if (pass.changes.items.len == 0) 384 402 return; 385 403 404 + const allocator = pass.parent.allocator; 405 + 386 406 const log = pass.parent.log; 387 407 log.header("Changes"); 388 408 409 + const source_path = try pass.parent.paths.source.realpathAlloc(allocator, "."); 410 + defer allocator.free(source_path); 411 + 389 412 for (pass.changes.items) |item| { 390 - if (item.source) |src| 391 - log.raw("- '{s}' ", .{src}) 392 - else 393 - log.raw("- 'id: {}' ", .{item.id}); 413 + if (item.source) |src| { 414 + log.raw("- '{s}' ", .{src[source_path.len + 1 ..]}); 415 + } else log.raw("- 'id: {}' ", .{item.id}); 416 + 394 417 log.raw("[{s}]\n", .{@tagName(item.status)}); 395 418 } 396 419 }