diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7d6289d..e961c20 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -8,11 +8,11 @@ jobs: steps: - uses: actions/checkout@v2 with: - submodules: 'recursive' + submodules: "recursive" - name: Setup Zig uses: goto-bus-stop/setup-zig@v2 with: - version: master + version: 0.13.0 - name: Render website run: | zig build @@ -34,5 +34,5 @@ jobs: Heya! You can check out a preview of your PR at [staging.microzig.tech/pulls/${{ github.event.number }}](https://staging.microzig.tech/pulls/${{ github.event.number }}/)! repo-token: ${{ secrets.GITHUB_TOKEN }} - repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens + repo-token-user-login: "github-actions[bot]" # The user.login for temporary GitHub tokens allow-repeats: false # This is the default diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index daa3250..2bc39d1 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -3,8 +3,8 @@ name: Render Website on: push: branches: - - 'main' - - 'master' + - "main" + - "master" jobs: build: @@ -13,12 +13,12 @@ jobs: steps: - uses: actions/checkout@v2 with: - submodules: 'recursive' + submodules: "recursive" - name: Setup Zig uses: goto-bus-stop/setup-zig@v2 with: - version: master + version: 0.13.0 - name: Render website run: | diff --git a/.gitignore b/.gitignore index 06606ca..09def3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ zig-cache/ +.zig-cache/ render/ zig-out/ .direnv/ diff --git a/build.zig b/build.zig index f1d8096..cd92888 100644 --- a/build.zig +++ b/build.zig @@ -4,12 +4,10 @@ const zine = @import("zine"); pub fn build(b: *std.Build) !void { // zine.scriptyReferenceDocs(b, "content/documentation/scripty/index.md"); try zine.addWebsite(b, .{ + .title = "Zig Embedded Group", + .host_url = "https://microzig.tech", .layouts_dir_path = "layouts", .content_dir_path = "content", .static_dir_path = "static", - .site = .{ - .base_url = "https://microzig.tech", - .title = "Zig Embedded Group", - }, }); } diff --git a/build.zig.zon b/build.zig.zon index 4ec7868..1fc4ea9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,8 +4,8 @@ .paths = .{"."}, .dependencies = .{ .zine = .{ - .url = "https://github.com/kristoff-it/zine/archive/03f80646b83cadb2e693ff5d97445d3e16c8e222.tar.gz", - .hash = "1220e3e4938edf652776349c45b3bb58774d540a050034488ff8dab7dbe410cc2977", + .url = "git+https://github.com/kristoff-it/zine#c4924570fa54ab204bb8b1bf1a140d0cc04ab08a", + .hash = "1220e2050b5fb6788675c0af6b2bfb601226fc90fb32aeeccc95cf9f4945543bd13a", }, }, } diff --git a/content/getting-started.md b/content/getting-started.md index d9edbd1..dd9e9bb 100644 --- a/content/getting-started.md +++ b/content/getting-started.md @@ -1,11 +1,11 @@ --- { - "title": "Home", - "date": "2020-07-06T00:00:00", - "author": "Felix Queißner", - "draft": false, - "layout": "getting-started.html", - "tags": [] -} + .title = "Getting Started", + .date = @date("2020-07-06T00:00:00"), + .author = "Felix Queißner", + .draft = false, + .layout = "getting-started.html", + .tags = [] +} --- Dummy, full text is implemented in the HTML file for now. diff --git a/content/index.md b/content/index.md index af1d6e7..2b1e9ce 100644 --- a/content/index.md +++ b/content/index.md @@ -1,11 +1,11 @@ --- { - "title": "Home", - "date": "2020-07-06T00:00:00", - "author": "Felix Queißner", - "draft": false, - "layout": "index.html", - "tags": [] -} + .title = "Home", + .date = @date("2020-07-06T00:00:00"), + .author = "Felix Queißner", + .draft = false, + .layout = "index.html", + .tags = [], +} --- Dummy, full text is implemented in the HTML file for now. diff --git a/content/tutorials/01-embedded-basics.md b/content/tutorials/01-embedded-basics.md index e1a246a..b1b2fa5 100644 --- a/content/tutorials/01-embedded-basics.md +++ b/content/tutorials/01-embedded-basics.md @@ -1,12 +1,12 @@ --- { - "title": "Embedded Basics", - "date": "2020-07-06T00:00:00", - "author": "Felix Queißner", - "draft": false, - "layout": "tutorial.html", - "tags": [] -} + .title = "Embedded Basics", + .date = @date("2020-07-06T00:00:00"), + .author = "Felix Queißner", + .draft = false, + .layout = "tutorial.html", + .tags = [] +} --- # Embedded Basics diff --git a/content/tutorials/02-embedded-programming.md b/content/tutorials/02-embedded-programming.md index 86e04e1..9d93959 100644 --- a/content/tutorials/02-embedded-programming.md +++ b/content/tutorials/02-embedded-programming.md @@ -1,11 +1,11 @@ --- { - "title": "Embedded Basics", - "date": "2020-07-06T00:00:00", - "author": "Felix Queißner", - "draft": false, - "layout": "tutorial.html", - "tags": [] + .title = "Embedded Programming", + .date = @date("2020-07-06T00:00:00"), + .author = "Felix Queißner", + .draft = false, + .layout = "tutorial.html", + .tags = [] } --- # Embedded Programming @@ -14,7 +14,7 @@ In this tutorial, you'll learn the ways of the embedded programmer and how to ma ## Prerequisites -- [Embedded Basics](01-embedded-basics.htm) +- [Embedded Basics](01-embedded-basics/) ## Contents @@ -38,7 +38,7 @@ Every embedded programmer requires some materials to get their work done efficie First of all, the *SOC datasheet*. It contains all relevant information about the SOC/chip you are using, which functions each pin of the package has, where your RAM and flash is located in the memory map and so on. You *will* learn to navigate this document very quickly, as it's the main reference for everything you do. -Second, you need the schematics of the device you want to program. You usually can obtain them from the manufacturer of your development board (assuming you are using one), by the vendor of the device you're hacking (if you are lucky enough) or by reverse engineering the device you have at hands (consider this the *hard mode* of embedded development). Reading a schematic is crucial to get your device do what you want, and you can learn a bit about this in the [Embedded Basics](01-embedded-basics.htm) tutorial. +Second, you need the schematics of the device you want to program. You usually can obtain them from the manufacturer of your development board (assuming you are using one), by the vendor of the device you're hacking (if you are lucky enough) or by reverse engineering the device you have at hands (consider this the *hard mode* of embedded development). Reading a schematic is crucial to get your device do what you want, and you can learn a bit about this in the [Embedded Basics](01-embedded-basics/) tutorial. These two documents are the ones you *definitly* need and it's near-impossible to work without them. But usually you need more documents than this: Datasheets for all the peripherial devices like displays, display controllers, motor controllers, expander chips and so on. Another document that helps a lot is the CPU datasheet for the core of your SOC. This document contains a precise description of the startup procedure of your system, what instructions are available, how the interrupts work in detail and similar topics. @@ -60,7 +60,7 @@ The RAM will contain garbage after startup. Our variables aren't initialized and To do this, we first have to tell the compiler to store our pre-initialized data somewhere in flash memory, so we can copy it into RAM at startup. This is done via the linker script: -```ld +```cpp /* This section declares two memory regions: * flash: 512k of non-writeable memory at position 0, * ram: 32k of writeable memory at position 256M diff --git a/layouts/index.html b/layouts/index.html index 4210966..bc46819 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -32,7 +32,7 @@

important links

diff --git a/legacy-ssg/main.zig b/legacy-ssg/main.zig deleted file mode 100644 index 5cb44b1..0000000 --- a/legacy-ssg/main.zig +++ /dev/null @@ -1,684 +0,0 @@ -const std = @import("std"); -const koino = @import("koino"); - -const markdown_options = koino.Options{ - .extensions = .{ - .table = true, - .autolink = true, - .strikethrough = true, - }, - .render = .{ - .header_anchors = true, - .anchor_icon = "§ ", - }, -}; - -/// verifies and parses a file name in the format -/// "YYYY-MM-DD - " [.*] ".md" -fn isValidArticleFileName(path: []const u8) ?Date { - if (path.len < 16) - return null; - if (!std.mem.endsWith(u8, path, ".md")) - return null; - if (path[4] != '-' or path[7] != '-' or !std.mem.eql(u8, path[10..13], " - ")) - return null; - return Date{ - .year = std.fmt.parseInt(u16, path[0..4], 10) catch return null, - .month = std.fmt.parseInt(u8, path[5..7], 10) catch return null, - .day = std.fmt.parseInt(u8, path[8..10], 10) catch return null, - }; -} - -pub fn main() anyerror!void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); - - const allocator = gpa.allocator(); - - var website = Website{ - .allocator = allocator, - .arena = std.heap.ArenaAllocator.init(allocator), - .articles = std.ArrayList(Article).init(allocator), - .tutorials = std.ArrayList(Tutorial).init(allocator), - .images = std.ArrayList([]const u8).init(allocator), - }; - defer website.deinit(); - - // gather step - { - var root_dir = try std.fs.cwd().openDir("website", .{}); - defer root_dir.close(); - - // Tutorials are maintained manually right now - try website.addTutorial(Tutorial{ - .src_file = "website/tutorials/01-embedded-basics.md", - }); - try website.addTutorial(Tutorial{ - .src_file = "website/tutorials/02-embedded-programming.md", - }); - // try website.addTutorial(Tutorial{ - // .src_file = "website/tutorials/03-lpc1768.md", - // }); - // try website.addTutorial(Tutorial{ - // .src_file = "website/tutorials/03-nrf52.md", - // }); - // try website.addTutorial(Tutorial{ - // .src_file = "website/tutorials/03-avr.md", - // }); - // try website.addTutorial(Tutorial{ - // .src_file = "website/tutorials/03-pi-pico.md", - // }); - // try website.addTutorial(Tutorial{ - // .src_file = "website/tutorials/03-stm32.md", - // }); - // try website.addTutorial(Tutorial{ - // .src_file = "website/tutorials/04-chose-device.md", - // }); - // try website.addTutorial(Tutorial{ - // .src_file = "website/tutorials/05-hal.md", - // }); - - // img articles - { - var dir = try root_dir.openIterableDir("img", .{}); - defer dir.close(); - - var iter = dir.iterate(); - - while (try iter.next()) |entry| { - if (entry.kind != .File) { - std.log.err("Illegal folder in directory website/img: {s}", .{entry.name}); - continue; - } - - const path = try std.fs.path.join(website.arena.allocator(), &[_][]const u8{ - "website", - "img", - entry.name, - }); - - try website.addImage(path); - } - } - - // gather articles - { - var dir = try root_dir.openIterableDir("articles", .{}); - defer dir.close(); - - var iter = dir.iterate(); - while (try iter.next()) |entry| { - if (entry.kind != .File) { - std.log.err("Illegal folder in directory website/articles: {s}", .{entry.name}); - continue; - } - - const date = isValidArticleFileName(entry.name) orelse { - if (!std.mem.eql(u8, entry.name, ".keep")) - std.log.err("Illegal file name in directory website/articles: {s}", .{entry.name}); - continue; - }; - - var article = Article{ - .title = "Not yet generated", - .src_file = undefined, - .date = date, - }; - - article.src_file = try std.fs.path.join(website.arena.allocator(), &[_][]const u8{ - "website", - "articles", - entry.name, - }); - - try website.addArticle(article); - } - } - } - - try website.prepareRendering(); - - // final rendering - { - var root_dir = try std.fs.cwd().makeOpenPath("render", .{}); - defer root_dir.close(); - - try std.fs.Dir.copyFile( - std.fs.cwd(), - "src/style.css", - root_dir, - "style.css", - .{}, - ); - - try std.fs.Dir.copyFile( - std.fs.cwd(), - "website/favicon.ico", - root_dir, - "favicon.ico", - .{}, - ); - - try website.renderHtmlFile("website/index.htm", root_dir, "index.htm"); - try website.renderHtmlFile("website/getting-started.htm", root_dir, "getting-started.htm"); - - try website.renderArticleIndex(root_dir, "articles.htm"); - - var art_dir = try root_dir.makeOpenPath("articles", .{}); - defer art_dir.close(); - - var tut_dir = try root_dir.makeOpenPath("tutorials", .{}); - defer tut_dir.close(); - - var img_dir = try root_dir.makeOpenPath("img", .{}); - defer img_dir.close(); - - try website.renderArticles(art_dir); - - try website.renderTutorials(tut_dir); - - try website.renderAtomFeed(root_dir, "feed.atom"); - - try website.renderImages(img_dir); - } -} - -const Date = struct { - const Self = @This(); - - day: u8, - month: u8, - year: u16, - - fn toInteger(self: Self) u32 { - return @as(u32, self.day) + 33 * @as(u32, self.month) + (33 * 13) * @as(u32, self.year); - } - - pub fn lessThan(lhs: Self, rhs: Self) bool { - return lhs.toInteger() < rhs.toInteger(); - } - - pub fn eql(a: Self, b: Self) bool { - return std.meta.eql(a, b); - } - - pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - try writer.print("{d:0>4}-{d:0>2}-{d:0>2}", .{ - self.year, self.month, self.day, - }); - } -}; - -const Article = struct { - date: Date, - src_file: []const u8, - title: []const u8 = "", -}; - -const Tutorial = struct { - src_file: []const u8, - title: []const u8 = "", -}; - -const Website = struct { - const Self = @This(); - - is_prepared: bool = false, - allocator: std.mem.Allocator, - arena: std.heap.ArenaAllocator, - articles: std.ArrayList(Article), - tutorials: std.ArrayList(Tutorial), - images: std.ArrayList([]const u8), - - fn deinit(self: *Self) void { - self.tutorials.deinit(); - self.articles.deinit(); - self.images.deinit(); - self.arena.deinit(); - self.* = undefined; - } - - fn addArticle(self: *Self, article: Article) !void { - self.is_prepared = false; - try self.articles.append(Article{ - .date = article.date, - .src_file = try self.arena.allocator().dupe(u8, article.src_file), - .title = try self.arena.allocator().dupe(u8, article.title), - }); - } - - fn addTutorial(self: *Self, tutorial: Tutorial) !void { - self.is_prepared = false; - try self.tutorials.append(Tutorial{ - .src_file = try self.arena.allocator().dupe(u8, tutorial.src_file), - .title = try self.arena.allocator().dupe(u8, tutorial.title), - }); - } - - fn addImage(self: *Self, path: []const u8) !void { - self.is_prepared = false; - try self.images.append(try self.arena.allocator().dupe(u8, path)); - } - - fn findTitle(self: *Self, file: []const u8) !?[]const u8 { - var doc = blk: { - var p = try koino.parser.Parser.init(self.allocator, markdown_options); - defer p.deinit(); - - const markdown = try std.fs.cwd().readFileAlloc(self.allocator, file, 10_000_000); - defer self.allocator.free(markdown); - - try p.feed(markdown); - - break :blk try p.finish(); - }; - defer doc.deinit(); - - std.debug.assert(doc.data.value == .Document); - - var iter = doc.first_child; - var heading_or_null: ?*koino.nodes.AstNode = while (iter) |item| : (iter = item.next) { - if (item.data.value == .Heading) { - if (item.data.value.Heading.level == 1) { - break item; - } - } - } else null; - - if (heading_or_null) |heading| { - var list = std.ArrayList(u8).init(self.arena.allocator()); - defer list.deinit(); - - var options = markdown_options; - options.render.header_anchors = false; - - try koino.html.print(list.writer(), self.arena.allocator(), options, heading); - - const string = list.toOwnedSlice(); - - std.debug.assert(std.mem.startsWith(u8, string, "

")); - std.debug.assert(std.mem.endsWith(u8, string, "

\n")); - - return string[4 .. string.len - 6]; - } else { - return null; - } - } - - fn prepareRendering(self: *Self) !void { - std.sort.sort(Article, self.articles.items, self.*, sortArticlesDesc); - - for (self.articles.items) |*article| { - if (try self.findTitle(article.src_file)) |title| { - article.title = title; - } - } - - for (self.tutorials.items) |*tutorial| { - std.debug.print("{s}\n", .{tutorial.src_file}); - if (try self.findTitle(tutorial.src_file)) |title| { - tutorial.title = title; - } - } - - self.is_prepared = true; - } - - fn sortArticlesDesc(self: Self, lhs: Article, rhs: Article) bool { - _ = self; - if (lhs.date.lessThan(rhs.date)) - return false; - if (rhs.date.lessThan(lhs.date)) - return true; - return (std.mem.order(u8, lhs.title, rhs.title) == .gt); - } - - fn removeExtension(src_name: []const u8) []const u8 { - const ext = std.fs.path.extension(src_name); - return src_name[0 .. src_name.len - ext.len]; - } - - fn changeExtension(self: *Self, src_name: []const u8, new_ext: []const u8) ![]const u8 { - return std.mem.join(self.arena.allocator(), "", &[_][]const u8{ - removeExtension(src_name), - new_ext, - }); - } - - fn urlEscape(self: *Self, text: []const u8) ![]u8 { - const legal_character = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"; - - var len: usize = 0; - for (text) |c| { - len += if (std.mem.indexOfScalar(u8, legal_character, c) == null) - @as(usize, 3) - else - @as(usize, 1); - } - - const buf = try self.arena.allocator().alloc(u8, len); - var offset: usize = 0; - for (text) |c| { - if (std.mem.indexOfScalar(u8, legal_character, c) == null) { - const hexdigits = "0123456789ABCDEF"; - buf[offset + 0] = '%'; - buf[offset + 1] = hexdigits[(c >> 4) & 0xF]; - buf[offset + 2] = hexdigits[(c >> 0) & 0xF]; - offset += 3; - } else { - buf[offset] = c; - offset += 1; - } - } - - return buf; - } - - fn renderArticles(self: *Self, dst_dir: std.fs.Dir) !void { - std.debug.assert(self.is_prepared); - for (self.articles.items) |art| { - try self.renderMarkdownFile( - art.src_file, - dst_dir, - try self.changeExtension(std.fs.path.basename(art.src_file), ".htm"), - ); - } - } - - fn renderTutorials(self: *Self, dst_dir: std.fs.Dir) !void { - std.debug.assert(self.is_prepared); - for (self.tutorials.items) |tut| { - try self.renderMarkdownFile( - tut.src_file, - dst_dir, - try self.changeExtension(std.fs.path.basename(tut.src_file), ".htm"), - ); - } - } - - /// Renders a list of all possible articles - fn renderArticleIndex(self: *Self, dst_dir: std.fs.Dir, file_name: []const u8) !void { - std.debug.assert(self.is_prepared); - - try self.renderMarkdown( - \\# Articles - \\ - \\ - , dst_dir, file_name); - } - - /// Render a given markdown file into `dst_path`. - fn renderMarkdownFile(self: *Self, src_path: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void { - std.debug.assert(self.is_prepared); - - var markdown_input = try std.fs.cwd().readFileAlloc(self.allocator, src_path, 10_000_000); - defer self.allocator.free(markdown_input); - - try self.renderMarkdown(markdown_input, dst_dir, dst_path); - } - - /// Render the given markdown source into `dst_path`. - /// supported features here are: - /// - `` (renders a table of contents with all items that come *after* said TOC - /// - `` Renders the 10 latest articles - /// - `` Renders all articles - fn renderMarkdown(self: *Self, source: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void { - std.debug.assert(self.is_prepared); - - var doc: *koino.nodes.AstNode = blk: { - var p = try koino.parser.Parser.init(self.arena.allocator(), markdown_options); - try p.feed(source); - defer p.deinit(); - break :blk try p.finish(); - }; - defer doc.deinit(); - - std.debug.assert(doc.data.value == .Document); - - var output_file = try dst_dir.createFile(dst_path, .{}); - defer output_file.close(); - - var writer = output_file.writer(); - - try self.renderHeader(writer); - { - var renderer = koino.html.makeHtmlFormatter(writer, self.arena.allocator(), markdown_options); - defer renderer.deinit(); - - var iter = doc.first_child; - while (iter) |item| : (iter = item.next) { - if (item.data.value == .HtmlBlock) { - const raw_string = item.data.value.HtmlBlock.literal.items; - - const string = std.mem.trim(u8, raw_string, " \t\r\n"); - - if (std.mem.eql(u8, string, "")) { - var min_heading_level: ?u8 = null; - var current_heading_level: u8 = undefined; - - var heading_options = markdown_options; - heading_options.render.header_anchors = false; - - try writer.writeAll(""); - current_heading_level -= 1; - } - while (current_heading_level < heading.level) { - try writer.writeAll(""); - current_heading_level -= 1; - } - - try writer.writeAll(""); - } - } - - if (min_heading_level) |mhl| { - while (current_heading_level > mhl) { - try writer.writeAll(""); - current_heading_level -= 1; - } - } - - try writer.writeAll(""); - } else if (std.mem.eql(u8, string, "")) { - for (self.articles.items[0..std.math.min(self.articles.items.len, 10)]) |art| { - try writer.print( - \\
  • {} - {s}
  • - \\ - , .{ - try self.urlEscape(removeExtension(std.fs.path.basename(art.src_file))), - art.date, - art.title, - }); - } - } else if (std.mem.eql(u8, string, "")) { - try writer.writeAll("\n"); - } else { - std.log.err("Unhandled HTML inline: {s}", .{string}); - } - } else { - try renderer.format(item, false); - } - } - } - try self.renderFooter(writer); - } - - /// Render a given markdown file into `dst_path`. - fn renderHtmlFile(self: *Self, src_path: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void { - std.debug.assert(self.is_prepared); - - var html_input = try std.fs.cwd().readFileAlloc(self.allocator, src_path, 10_000_000); - defer self.allocator.free(html_input); - - try self.renderHtml(html_input, dst_dir, dst_path); - } - - /// Render the html body into `dst_path`. - fn renderHtml(self: Self, source: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void { - std.debug.assert(self.is_prepared); - - var output_file = try dst_dir.createFile(dst_path, .{}); - defer output_file.close(); - - var writer = output_file.writer(); - - try self.renderHeader(writer); - try writer.writeAll(source); - try self.renderFooter(writer); - } - - fn renderHeader(self: Self, writer: anytype) !void { - std.debug.assert(self.is_prepared); - try writer.writeAll( - \\ - \\ - \\ - \\ - \\ - \\ - \\ ZEG - \\ - \\ - \\ - ); - } - - fn renderFooter(self: Self, writer: anytype) !void { - std.debug.assert(self.is_prepared); - try writer.writeAll( - \\ - \\ - \\ - ); - } - - fn renderAtomFeed(self: *Self, dir: std.fs.Dir, file_name: []const u8) !void { - var feed_file = try dir.createFile(file_name, .{}); - defer feed_file.close(); - - var feed_writer = feed_file.writer(); - - try feed_writer.writeAll( - \\ - \\ - \\ - \\ Zig Embedded Group - \\ - \\ Zig Embedded Group - \\ https://zeg.random-projects.net/ - \\ - ); - - var last_update = Date{ .year = 0, .month = 0, .day = 0 }; - var article_count: usize = 0; - for (self.articles.items) |article| { - if (last_update.lessThan(article.date)) { - last_update = article.date; - article_count = 0; - } else { - article_count += 1; - } - } - - try feed_writer.print(" {d:0>4}-{d:0>2}-{d:0>2}T{d:0>2}:00:00Z\n", .{ - last_update.year, - last_update.month, - last_update.day, - article_count, // this is fake, but is just here for creating a incremental version for multiple articles a day - }); - - for (self.articles.items) |article| { - const uri_name = try self.urlEscape(removeExtension(article.src_file)); - try feed_writer.print( - \\ - \\ {s} - \\ - \\ zeg.random-projects.net/articles/{s}.htm - \\ {d:0>4}-{d:0>2}-{d:0>2}T00:00:00Z - \\ - \\ - , .{ - article.title, - uri_name, - uri_name, - article.date.year, - article.date.month, - article.date.day, - }); - } - - try feed_writer.writeAll(""); - } - - fn renderImages(self: Self, target_dir: std.fs.Dir) !void { - for (self.images.items) |img| { - try std.fs.Dir.copyFile( - std.fs.cwd(), - img, - target_dir, - std.fs.path.basename(img), - .{}, - ); - } - } - - // fn renderArticle(self: *Website, article: Article, dst_dir: std.fs.Dir, dst_name: []const u8) !void { - // var formatter = HtmlFormatter.init(allocator, options); - // defer formatter.deinit(); - - // try formatter.format(root, false); - - // return formatter.buffer.toOwnedSlice(); - // } -};