From c19060163054fbdf24a9ecdad070b5c04a654f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Wed, 10 Mar 2021 12:21:04 +0100 Subject: [PATCH 01/49] Initial commit: Basic website rendering, no templating yet. --- .gitattributes | 1 + .github/workflows/website.yml | 30 +++++++ .gitignore | 2 + .gitmodules | 3 + README.md | 3 + build.zig | 32 +++++++ deps/koino | 1 + src/main.zig | 111 +++++++++++++++++++++++++ website/articles/01-embedded-basics.md | 3 + website/index.md | 44 ++++++++++ 10 files changed, 230 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/website.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 README.md create mode 100644 build.zig create mode 160000 deps/koino create mode 100644 src/main.zig create mode 100644 website/articles/01-embedded-basics.md create mode 100644 website/index.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0cb064a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.zig text=auto eol=lf diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 0000000..e0db162 --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,30 @@ +name: Render Website + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: "recursive" + + - name: Setup Zig + uses: goto-bus-stop/setup-zig@v1 + with: + version: master + + - name: Render website + run: | + zig build gen + + - name: Deploy with SCP + uses: noobly314/deploy-with-scp@v1 + with: + src: render/ + dest: zeg/ + username: generic-ci + server-ip: random-projects.net + ssh-key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b14c7fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +zig-cache/ +render/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3fc6085 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/koino"] + path = deps/koino + url = https://github.com/kivikakk/koino diff --git a/README.md b/README.md new file mode 100644 index 0000000..61d47cc --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Zig Embedded Group - Website and Articles + + diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..8056841 --- /dev/null +++ b/build.zig @@ -0,0 +1,32 @@ +const std = @import("std"); + +const pkgs = struct { + const koino = std.build.Pkg{ + .name = "koino", + .path = "./deps/koino/src/koino.zig", + .dependencies = &[_]std.build.Pkg{ + std.build.Pkg{ .name = "libpcre", .path = "deps/koino/vendor/libpcre.zig/src/main.zig" }, + std.build.Pkg{ .name = "htmlentities", .path = "deps/koino/vendor/htmlentities.zig/src/main.zig" }, + std.build.Pkg{ .name = "clap", .path = "deps/koino/vendor/zig-clap/clap.zig" }, + std.build.Pkg{ .name = "zunicode", .path = "deps/koino/vendor/zunicode/src/zunicode.zig" }, + }, + }; +}; + +const linkPcre = @import("deps/koino/vendor/libpcre.zig/build.zig").linkPcre; + +pub fn build(b: *std.build.Builder) !void { + const target = b.standardTargetOptions(.{}); + const mode = b.standardReleaseOptions(); + + const render_website = b.addExecutable("zeg-website", "src/main.zig"); + render_website.setTarget(target); + render_website.setBuildMode(mode); + try linkPcre(render_website); + render_website.addPackage(pkgs.koino); + + const gen_cmd = render_website.run(); + + const gen_step = b.step("gen", "Generates the website"); + gen_step.dependOn(&gen_cmd.step); +} diff --git a/deps/koino b/deps/koino new file mode 160000 index 0000000..060611d --- /dev/null +++ b/deps/koino @@ -0,0 +1 @@ +Subproject commit 060611d230cea7996b4518a80137b3e96319a293 diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..afab84d --- /dev/null +++ b/src/main.zig @@ -0,0 +1,111 @@ +const std = @import("std"); +const koino = @import("koino"); + +pub fn main() anyerror!void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + const allocator = &gpa.allocator; + + var root_dir = try std.fs.walkPath(allocator, "website"); + defer root_dir.deinit(); + + const markdown_options = koino.Options{ + .extensions = .{ + .table = true, + .autolink = true, + .strikethrough = true, + }, + }; + + while (try root_dir.next()) |entry| { + switch (entry.kind) { + // we create the directories by forcing them + .Directory => {}, + + .File => { + const ext = std.fs.path.extension(entry.path); + if (std.mem.eql(u8, ext, ".md")) { + std.log.info("render {s}", .{entry.path}); + + const out_name = try std.mem.concat(allocator, u8, &[_][]const u8{ + entry.path[8 .. entry.path.len - 3], + ".htm", + }); + defer allocator.free(out_name); + + var out_path = try std.fs.path.join(allocator, &[_][]const u8{ + "render", out_name, + }); + defer allocator.free(out_path); + + if (std.fs.path.dirname(out_path)) |dir| { + std.debug.print("{s}\n", .{dir}); + try std.fs.cwd().makePath(dir); + } + + var markdown_input = try std.fs.cwd().readFileAlloc(allocator, entry.path, 10_000_000); + defer allocator.free(markdown_input); + + var rendered_markdown = try markdownToHtml(allocator, markdown_options, markdown_input); + defer gpa.allocator.free(rendered_markdown); + + var output_file = try std.fs.cwd().createFile(out_path, .{}); + defer output_file.close(); + + var writer = output_file.writer(); + + try writer.writeAll( + \\ + \\ + \\ + \\ + \\ + \\ + \\ ZEG + \\ + \\ + \\ + ); + + try writer.writeAll(rendered_markdown); + + try writer.writeAll( + \\ + \\ + \\ + ); + } + }, + + else => std.debug.panic("Unsupported file type {s} in directory!", .{@tagName(entry.kind)}), + } + } +} + +fn markdownToHtmlInternal(resultAllocator: *std.mem.Allocator, internalAllocator: *std.mem.Allocator, options: koino.Options, markdown: []const u8) ![]u8 { + var p = try koino.parser.Parser.init(internalAllocator, options); + try p.feed(markdown); + + var doc = try p.finish(); + p.deinit(); + + defer doc.deinit(); + + return try koino.html.print(resultAllocator, p.options, doc); +} + +pub fn markdownToHtml(allocator: *std.mem.Allocator, options: koino.Options, markdown: []const u8) ![]u8 { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + return markdownToHtmlInternal(allocator, &arena.allocator, options, markdown); +} diff --git a/website/articles/01-embedded-basics.md b/website/articles/01-embedded-basics.md new file mode 100644 index 0000000..3e17739 --- /dev/null +++ b/website/articles/01-embedded-basics.md @@ -0,0 +1,3 @@ +# Embedded Basics + +This is a stub. \ No newline at end of file diff --git a/website/index.md b/website/index.md new file mode 100644 index 0000000..b331cdd --- /dev/null +++ b/website/index.md @@ -0,0 +1,44 @@ +# Zig Embedded Group + +This group was formed to document and improve the embedded programming experience with the [Zig programming language](https://ziglang.org). + +Right now, this group has the following members: + +- [Felix "xq" Queißner](https://github.com/masterQ32/) +- [Matthew "mattnite" Knight](https://github.com/mattnite/) +- [Vesim](https://github.com/vesim987/) +- [Timon "FireFox317" Kruiper](https://github.com/FireFox317) +- [Martin "SpexGuy" Wickham](https://github.com/SpexGuy) + +## Goals + +- Provide documents on how to get started with embedded programming (for absolute newbies) +- Provide example snippets for certain architectures (LPC, STM32, AVR, ...) +- Create register definition libraries +- Create a common interface/HAL over several architectures +- Create a performant common set of drivers for external platforms + +## Community + +This group uses the already existing community infrastructures that exist for Zig: + +- [Zig Programming Language - Discord Server](https://discord.gg/TyzJXjser6) + +## Planned Documents/Articles + +- Getting started with embedded (not specific to a platform) + Contains info about basic digital electronics, what MMIO is and how it works, what are the differences to "normal" programs how is the startup pocess, ... +- Getting started with + - Arduino/AVR + - LPC1768 + - STM32 + - MSP430 + - ESP2866/ESP32 + - NRF52 + - Raspberry PI +- Using `async` code on embedded. +- Make your own keyboard with zig (and replace qmk) + +## Required Stuff + +- Fanart by the guy from ziglings \ No newline at end of file From 7fec030a714f39d0f139509582f6d5cfe35432dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Wed, 10 Mar 2021 12:23:22 +0100 Subject: [PATCH 02/49] Fixes CI deployment. --- .github/workflows/website.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index e0db162..c42ef11 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -23,7 +23,7 @@ jobs: - name: Deploy with SCP uses: noobly314/deploy-with-scp@v1 with: - src: render/ + src: render/* dest: zeg/ username: generic-ci server-ip: random-projects.net From 81a232c5fa3dfd1e983aaaa6341f94195c06094d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Wed, 10 Mar 2021 13:15:33 +0100 Subject: [PATCH 03/49] Adds index concept. --- src/main.zig | 1 + website/index.md | 66 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/main.zig b/src/main.zig index afab84d..4b14d28 100644 --- a/src/main.zig +++ b/src/main.zig @@ -68,6 +68,7 @@ pub fn main() anyerror!void { \\ max-width: 40em; \\ margin-left: auto; \\ margin-right: auto; + \\ font-family: sans; \\ } \\ h1 { \\ text-align: center; diff --git a/website/index.md b/website/index.md index b331cdd..41538c4 100644 --- a/website/index.md +++ b/website/index.md @@ -2,14 +2,6 @@ This group was formed to document and improve the embedded programming experience with the [Zig programming language](https://ziglang.org). -Right now, this group has the following members: - -- [Felix "xq" Queißner](https://github.com/masterQ32/) -- [Matthew "mattnite" Knight](https://github.com/mattnite/) -- [Vesim](https://github.com/vesim987/) -- [Timon "FireFox317" Kruiper](https://github.com/FireFox317) -- [Martin "SpexGuy" Wickham](https://github.com/SpexGuy) - ## Goals - Provide documents on how to get started with embedded programming (for absolute newbies) @@ -18,12 +10,70 @@ Right now, this group has the following members: - Create a common interface/HAL over several architectures - Create a performant common set of drivers for external platforms +## Introduction to embedded programming + +If you've never done any embedded development before, it's a good point to start with one of our tutorials: + +- [Embedded Basics](articles/01-embedded-basics.htm) +- [Embedded Programming for Beginners](#) +- Getting started with... + - [Arduino/AVR](#) + - [LPC1768](#) + - [STM32](#) + - [MSP430](#) + - [ESP2866/ESP32](#) + - [NRF52](#) + - [Raspberry PI](#) +- [What device to chose?](#) +- [Introduction to HAL9001](#) + +## Latest Articles + +The latest articles on embedded programming with Zig: + +- [2021-03-15 zCOM, a network stack for embedded devices](#) +- [2021-03-10 `async`/`await` on embedded platforms](#) +- [2021-XX-YY Dummy Article](#) +- [2021-XX-YY Dummy Article](#) +- [2021-XX-YY Dummy Article](#) +- [2021-XX-YY Dummy Article](#) +- [2021-XX-YY Dummy Article](#) +- [2021-XX-YY Dummy Article](#) +- [2021-XX-YY Dummy Article](#) + +[See all articles...](#) + +## Code + +Here are some highlighted projects the ZEG provides: + +- [HAL9001 - One HAL to rule them all](#) +- [STM32 Binding](#) +- [AVR Binding](#) +- [LPC1768 Binding](#) +- [ESP32 Binding](#) +- [zCOM Network Driver](#) +- [TinySSL](#) + +[See all repositories...](#) + ## Community This group uses the already existing community infrastructures that exist for Zig: - [Zig Programming Language - Discord Server](https://discord.gg/TyzJXjser6) +## Members + +- [Felix "xq" Queißner](https://github.com/MasterQ32/) +- [Matthew "mattnite" Knight](https://github.com/mattnite/) +- [Vesim](https://github.com/vesim987/) +- [Timon "FireFox317" Kruiper](https://github.com/FireFox317) +- [Martin "SpexGuy" Wickham](https://github.com/SpexGuy) + +--- + + ## Planned Documents/Articles - Getting started with embedded (not specific to a platform) From a0c0b1b08a89be74ac60fa8b9326a019aa0c62d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Wed, 10 Mar 2021 14:42:24 +0100 Subject: [PATCH 04/49] Adds Pi Pico and the keyboard article stub. --- website/index.md | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/website/index.md b/website/index.md index 41538c4..08af373 100644 --- a/website/index.md +++ b/website/index.md @@ -24,6 +24,7 @@ If you've never done any embedded development before, it's a good point to start - [ESP2866/ESP32](#) - [NRF52](#) - [Raspberry PI](#) + - [Raspberry PI Pico](#) - [What device to chose?](#) - [Introduction to HAL9001](#) @@ -32,6 +33,7 @@ If you've never done any embedded development before, it's a good point to start The latest articles on embedded programming with Zig: - [2021-03-15 zCOM, a network stack for embedded devices](#) +- [2021-03-12 Make your own keyboard with zig (and replace qmk)](#) - [2021-03-10 `async`/`await` on embedded platforms](#) - [2021-XX-YY Dummy Article](#) - [2021-XX-YY Dummy Article](#) @@ -39,7 +41,6 @@ The latest articles on embedded programming with Zig: - [2021-XX-YY Dummy Article](#) - [2021-XX-YY Dummy Article](#) - [2021-XX-YY Dummy Article](#) -- [2021-XX-YY Dummy Article](#) [See all articles...](#) @@ -73,22 +74,6 @@ This group uses the already existing community infrastructures that exist for Zi --- - -## Planned Documents/Articles - -- Getting started with embedded (not specific to a platform) - Contains info about basic digital electronics, what MMIO is and how it works, what are the differences to "normal" programs how is the startup pocess, ... -- Getting started with - - Arduino/AVR - - LPC1768 - - STM32 - - MSP430 - - ESP2866/ESP32 - - NRF52 - - Raspberry PI -- Using `async` code on embedded. -- Make your own keyboard with zig (and replace qmk) - ## Required Stuff - Fanart by the guy from ziglings \ No newline at end of file From 35614e6b711cb76266d709a6c051f77eee852001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Sat, 13 Mar 2021 19:18:09 +0100 Subject: [PATCH 05/49] Adds automatic article index rendering. --- src/main.zig | 437 +++++++++++++++--- website/articles/2021-03-12 - async-await.md | 3 + website/articles/2021-03-16 - replace-qmk.md | 3 + website/articles/2021-04-10 - zCOM.md | 3 + website/index.md | 22 +- .../01-embedded-basics.md | 0 6 files changed, 373 insertions(+), 95 deletions(-) create mode 100644 website/articles/2021-03-12 - async-await.md create mode 100644 website/articles/2021-03-16 - replace-qmk.md create mode 100644 website/articles/2021-04-10 - zCOM.md rename website/{articles => tutorials}/01-embedded-basics.md (100%) diff --git a/src/main.zig b/src/main.zig index 4b14d28..494347a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,96 +1,99 @@ const std = @import("std"); const koino = @import("koino"); +const markdown_options = koino.Options{ + .extensions = .{ + .table = true, + .autolink = true, + .strikethrough = true, + }, +}; + +/// 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 root_dir = try std.fs.walkPath(allocator, "website"); - defer root_dir.deinit(); - - const markdown_options = koino.Options{ - .extensions = .{ - .table = true, - .autolink = true, - .strikethrough = true, - }, + var website = Website{ + .allocator = allocator, + .arena = std.heap.ArenaAllocator.init(allocator), + .articles = std.ArrayList(Article).init(allocator), }; + defer website.deinit(); - while (try root_dir.next()) |entry| { - switch (entry.kind) { - // we create the directories by forcing them - .Directory => {}, - - .File => { - const ext = std.fs.path.extension(entry.path); - if (std.mem.eql(u8, ext, ".md")) { - std.log.info("render {s}", .{entry.path}); - - const out_name = try std.mem.concat(allocator, u8, &[_][]const u8{ - entry.path[8 .. entry.path.len - 3], - ".htm", - }); - defer allocator.free(out_name); - - var out_path = try std.fs.path.join(allocator, &[_][]const u8{ - "render", out_name, - }); - defer allocator.free(out_path); - - if (std.fs.path.dirname(out_path)) |dir| { - std.debug.print("{s}\n", .{dir}); - try std.fs.cwd().makePath(dir); - } + // gather step + { + var root_dir = try std.fs.cwd().openDir("website", .{}); + defer root_dir.close(); + + // gather articles + { + var dir = try root_dir.openDir("articles", .{ .iterate = true }); + defer dir.close(); - var markdown_input = try std.fs.cwd().readFileAlloc(allocator, entry.path, 10_000_000); - defer allocator.free(markdown_input); - - var rendered_markdown = try markdownToHtml(allocator, markdown_options, markdown_input); - defer gpa.allocator.free(rendered_markdown); - - var output_file = try std.fs.cwd().createFile(out_path, .{}); - defer output_file.close(); - - var writer = output_file.writer(); - - try writer.writeAll( - \\ - \\ - \\ - \\ - \\ - \\ - \\ ZEG - \\ - \\ - \\ - ); - - try writer.writeAll(rendered_markdown); - - try writer.writeAll( - \\ - \\ - \\ - ); + 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; } - }, - else => std.debug.panic("Unsupported file type {s} in directory!", .{@tagName(entry.kind)}), + const date = isValidArticleFileName(entry.name) orelse { + 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 website.renderIndexFile("website/index.md", root_dir, "index.htm"); + + try website.renderArticleIndex(root_dir, "articles.htm"); + + var art_dir = try root_dir.makeOpenPath("articles", .{}); + defer art_dir.close(); + + try website.renderArticles(art_dir); + } } fn markdownToHtmlInternal(resultAllocator: *std.mem.Allocator, internalAllocator: *std.mem.Allocator, options: koino.Options, markdown: []const u8) ![]u8 { @@ -110,3 +113,283 @@ pub fn markdownToHtml(allocator: *std.mem.Allocator, options: koino.Options, mar defer arena.deinit(); return markdownToHtmlInternal(allocator, &arena.allocator, options, markdown); } + +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 { + 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 Website = struct { + const Self = @This(); + + is_prepared: bool = false, + allocator: *std.mem.Allocator, + arena: std.heap.ArenaAllocator, + articles: std.ArrayList(Article), + + fn deinit(self: *Self) void { + self.articles.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 prepareRendering(self: *Self) !void { + std.sort.sort(Article, self.articles.items, self.*, sortArticlesDesc); + + for (self.articles.items) |*article| { + 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, article.src_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| { + const string = try koino.html.print(&self.arena.allocator, markdown_options, heading); + + std.debug.assert(std.mem.startsWith(u8, string, "

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

\n")); + + article.title = string[4 .. string.len - 6]; + } + } + + self.is_prepared = true; + } + + fn sortArticlesDesc(self: Self, lhs: Article, rhs: Article) bool { + 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"), + ); + } + } + + /// Renders the root file and replaces `` with the first 10 articles, + /// in descending order + fn renderIndexFile(self: *Self, src_path: []const u8, dst_dir: std.fs.Dir, file_name: []const u8) !void { + std.debug.assert(self.is_prepared); + + var src_code = try std.fs.cwd().readFileAlloc(self.allocator, src_path, 10_000_000); + defer self.allocator.free(src_code); + + var array_buffer = std.ArrayList(u8).init(self.allocator); + defer array_buffer.deinit(); + + const offset = std.mem.indexOf(u8, src_code, "") orelse return error.MissingArticlesMarker; + + var writer = array_buffer.writer(); + + try writer.writeAll(src_code[0..offset]); + + for (self.articles.items[0..std.math.min(self.articles.items.len, 10)]) |art| { + try writer.print("- [{} - {s}](articles/{s}.htm)\n", .{ + art.date, + art.title, + try self.urlEscape(removeExtension(std.fs.path.basename(art.src_file))), + }); + } + + try writer.writeAll(src_code[offset + 17 ..]); + + try self.renderMarkdown(array_buffer.items, dst_dir, file_name); + } + + /// Renders the root file and replaces `` with the first 10 articles, + /// in descending order + fn renderArticleIndex(self: *Self, dst_dir: std.fs.Dir, file_name: []const u8) !void { + std.debug.assert(self.is_prepared); + + var array_buffer = std.ArrayList(u8).init(self.allocator); + defer array_buffer.deinit(); + + var writer = array_buffer.writer(); + + try writer.writeAll("# Articles\n"); + try writer.writeAll("\n"); + + for (self.articles.items[0..std.math.min(self.articles.items.len, 10)]) |art| { + try writer.print("- [{} - {s}](articles/{s}.htm)\n", .{ + art.date, + art.title, + try self.urlEscape(removeExtension(std.fs.path.basename(art.src_file))), + }); + } + + try self.renderMarkdown(array_buffer.items, 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`. + fn renderMarkdown(self: Self, source: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void { + std.debug.assert(self.is_prepared); + + var rendered_markdown = try markdownToHtml(self.allocator, markdown_options, source); + defer self.allocator.free(rendered_markdown); + + try self.renderHtml(rendered_markdown, dst_dir, dst_path); + } + + /// Render the markdown 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( + \\ + \\ + \\ + ); + } +}; diff --git a/website/articles/2021-03-12 - async-await.md b/website/articles/2021-03-12 - async-await.md new file mode 100644 index 0000000..2c6ddd6 --- /dev/null +++ b/website/articles/2021-03-12 - async-await.md @@ -0,0 +1,3 @@ +# `async`/`await` on embedded platforms + +Blabla this is a stub \ No newline at end of file diff --git a/website/articles/2021-03-16 - replace-qmk.md b/website/articles/2021-03-16 - replace-qmk.md new file mode 100644 index 0000000..b09b36f --- /dev/null +++ b/website/articles/2021-03-16 - replace-qmk.md @@ -0,0 +1,3 @@ +# Make your own keyboard with zig (and replace qmk) + +Blabla this is a stub \ No newline at end of file diff --git a/website/articles/2021-04-10 - zCOM.md b/website/articles/2021-04-10 - zCOM.md new file mode 100644 index 0000000..e61f2c8 --- /dev/null +++ b/website/articles/2021-04-10 - zCOM.md @@ -0,0 +1,3 @@ +# zCOM, a network stack for embedded devices + +Blabla this is a stub \ No newline at end of file diff --git a/website/index.md b/website/index.md index 08af373..dca8a92 100644 --- a/website/index.md +++ b/website/index.md @@ -32,17 +32,9 @@ If you've never done any embedded development before, it's a good point to start The latest articles on embedded programming with Zig: -- [2021-03-15 zCOM, a network stack for embedded devices](#) -- [2021-03-12 Make your own keyboard with zig (and replace qmk)](#) -- [2021-03-10 `async`/`await` on embedded platforms](#) -- [2021-XX-YY Dummy Article](#) -- [2021-XX-YY Dummy Article](#) -- [2021-XX-YY Dummy Article](#) -- [2021-XX-YY Dummy Article](#) -- [2021-XX-YY Dummy Article](#) -- [2021-XX-YY Dummy Article](#) - -[See all articles...](#) + + +[See all articles...](articles.htm) ## Code @@ -56,7 +48,7 @@ Here are some highlighted projects the ZEG provides: - [zCOM Network Driver](#) - [TinySSL](#) -[See all repositories...](#) +[See all repositories...](https://github.com/ZigEmbeddedGroup/) ## Community @@ -71,9 +63,3 @@ This group uses the already existing community infrastructures that exist for Zi - [Vesim](https://github.com/vesim987/) - [Timon "FireFox317" Kruiper](https://github.com/FireFox317) - [Martin "SpexGuy" Wickham](https://github.com/SpexGuy) - ---- - -## Required Stuff - -- Fanart by the guy from ziglings \ No newline at end of file diff --git a/website/articles/01-embedded-basics.md b/website/tutorials/01-embedded-basics.md similarity index 100% rename from website/articles/01-embedded-basics.md rename to website/tutorials/01-embedded-basics.md From d56f0bf86e493780ebf4c9f6ad9da5e2ea7f1b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Sat, 13 Mar 2021 19:49:37 +0100 Subject: [PATCH 06/49] Adds tutorial rendering. --- src/main.zig | 127 +++++++++++++++---- website/index.md | 24 ++-- website/tutorials/01-embedded-basics.md | 19 ++- website/tutorials/02-embedded-programming.md | 10 ++ website/tutorials/03-avr.md | 1 + website/tutorials/03-lpc1768.md | 1 + website/tutorials/03-nrf52.md | 2 + website/tutorials/03-pi-pico.md | 1 + website/tutorials/03-stm32.md | 1 + website/tutorials/04-chose-device.md | 7 + website/tutorials/05-hal.md | 1 + 11 files changed, 155 insertions(+), 39 deletions(-) create mode 100644 website/tutorials/02-embedded-programming.md create mode 100644 website/tutorials/03-avr.md create mode 100644 website/tutorials/03-lpc1768.md create mode 100644 website/tutorials/03-nrf52.md create mode 100644 website/tutorials/03-pi-pico.md create mode 100644 website/tutorials/03-stm32.md create mode 100644 website/tutorials/04-chose-device.md create mode 100644 website/tutorials/05-hal.md diff --git a/src/main.zig b/src/main.zig index 494347a..b83db83 100644 --- a/src/main.zig +++ b/src/main.zig @@ -36,6 +36,7 @@ pub fn main() anyerror!void { .allocator = allocator, .arena = std.heap.ArenaAllocator.init(allocator), .articles = std.ArrayList(Article).init(allocator), + .tutorials = std.ArrayList(Tutorial).init(allocator), }; defer website.deinit(); @@ -44,6 +45,35 @@ pub fn main() anyerror!void { 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", + }); + // gather articles { var dir = try root_dir.openDir("articles", .{ .iterate = true }); @@ -92,7 +122,12 @@ pub fn main() anyerror!void { var art_dir = try root_dir.makeOpenPath("articles", .{}); defer art_dir.close(); + var tut_dir = try root_dir.makeOpenPath("tutorials", .{}); + defer tut_dir.close(); + try website.renderArticles(art_dir); + + try website.renderTutorials(tut_dir); } } @@ -143,7 +178,12 @@ const Date = struct { const Article = struct { date: Date, src_file: []const u8, - title: []const u8, + title: []const u8 = "", +}; + +const Tutorial = struct { + src_file: []const u8, + title: []const u8 = "", }; const Website = struct { @@ -153,8 +193,10 @@ const Website = struct { allocator: *std.mem.Allocator, arena: std.heap.ArenaAllocator, articles: std.ArrayList(Article), + tutorials: std.ArrayList(Tutorial), fn deinit(self: *Self) void { + self.tutorials.deinit(); self.articles.deinit(); self.arena.deinit(); self.* = undefined; @@ -169,41 +211,63 @@ const Website = struct { }); } - fn prepareRendering(self: *Self) !void { - std.sort.sort(Article, self.articles.items, self.*, sortArticlesDesc); + 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), + }); + } - for (self.articles.items) |*article| { - var doc = blk: { - var p = try koino.parser.Parser.init(self.allocator, markdown_options); - defer p.deinit(); + 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, article.src_file, 10_000_000); - defer self.allocator.free(markdown); + const markdown = try std.fs.cwd().readFileAlloc(self.allocator, file, 10_000_000); + defer self.allocator.free(markdown); - try p.feed(markdown); + try p.feed(markdown); - break :blk try p.finish(); - }; - defer doc.deinit(); + break :blk try p.finish(); + }; + defer doc.deinit(); - std.debug.assert(doc.data.value == .Document); + 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; - } + 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; + } + } else null; + + if (heading_or_null) |heading| { + const string = try koino.html.print(&self.arena.allocator, markdown_options, heading); - if (heading_or_null) |heading| { - const string = try koino.html.print(&self.arena.allocator, markdown_options, heading); + 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; + } + } - std.debug.assert(std.mem.startsWith(u8, string, "

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

\n")); + fn prepareRendering(self: *Self) !void { + std.sort.sort(Article, self.articles.items, self.*, sortArticlesDesc); - article.title = string[4 .. string.len - 6]; + for (self.articles.items) |*article| { + if (try self.findTitle(article.src_file)) |title| { + article.title = title; + } + } + + for (self.tutorials.items) |*tutorial| { + if (try self.findTitle(tutorial.src_file)) |title| { + tutorial.title = title; } } @@ -270,6 +334,17 @@ const Website = struct { } } + 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 the root file and replaces `` with the first 10 articles, /// in descending order fn renderIndexFile(self: *Self, src_path: []const u8, dst_dir: std.fs.Dir, file_name: []const u8) !void { diff --git a/website/index.md b/website/index.md index dca8a92..6fdd918 100644 --- a/website/index.md +++ b/website/index.md @@ -14,19 +14,19 @@ This group was formed to document and improve the embedded programming experienc If you've never done any embedded development before, it's a good point to start with one of our tutorials: -- [Embedded Basics](articles/01-embedded-basics.htm) -- [Embedded Programming for Beginners](#) +- [Embedded Basics](tutorials/01-embedded-basics.htm) +- [Embedded Programming for Beginners](tutorials/02-embedded-programming.htm) - Getting started with... - - [Arduino/AVR](#) - - [LPC1768](#) - - [STM32](#) - - [MSP430](#) - - [ESP2866/ESP32](#) - - [NRF52](#) - - [Raspberry PI](#) - - [Raspberry PI Pico](#) -- [What device to chose?](#) -- [Introduction to HAL9001](#) + - [Arduino/AVR](tutorials/03-avr.htm) + - [LPC1768](tutorials/03-lpc1768.htm) + - [NRF52](tutorials/03-nrf52.htm) + - [Raspberry PI Pico](tutorials/03-pi-pico.htm) + - [STM32](tutorials/03-stm32.htm) + - [MSP430](#) (*missing*) + - [ESP2866/ESP32](#) (*missing*) + - [Raspberry PI](#) (*missing*) +- [What device to chose?](tutorials/04-chose-device.htm) +- [Introduction to HAL 9001](tutorials/05-hal.htm) ## Latest Articles diff --git a/website/tutorials/01-embedded-basics.md b/website/tutorials/01-embedded-basics.md index 3e17739..50ff712 100644 --- a/website/tutorials/01-embedded-basics.md +++ b/website/tutorials/01-embedded-basics.md @@ -1,3 +1,20 @@ # Embedded Basics -This is a stub. \ No newline at end of file +In this tutorial, you'll learn the absolute basics of the embedded world: + +## Contents + +- What are embedded systems? +- Electronics 101 + - Current, voltage, power and all + - Diodes, Resistors, Capacitors + - Ohm's law + - LEDs and pre-resistors + - MosFETs +- Breadboard vs. Soldering +- Digital I/O +- Analog I/O +- Bus Systems + - UART + - SPI + - I²C / TWI \ No newline at end of file diff --git a/website/tutorials/02-embedded-programming.md b/website/tutorials/02-embedded-programming.md new file mode 100644 index 0000000..e930292 --- /dev/null +++ b/website/tutorials/02-embedded-programming.md @@ -0,0 +1,10 @@ +# Embedded Programming + +In this tutorial, you'll learn the ways of the embedded programmer and how to master your MCU. + +## Contents + +- Differences to desktop programming +- The startup procedure +- Interacting with peripherials +- Interrupts and how to get them \ No newline at end of file diff --git a/website/tutorials/03-avr.md b/website/tutorials/03-avr.md new file mode 100644 index 0000000..5ce4d0a --- /dev/null +++ b/website/tutorials/03-avr.md @@ -0,0 +1 @@ +# Getting started with Arduino/AVR \ No newline at end of file diff --git a/website/tutorials/03-lpc1768.md b/website/tutorials/03-lpc1768.md new file mode 100644 index 0000000..77ef59e --- /dev/null +++ b/website/tutorials/03-lpc1768.md @@ -0,0 +1 @@ +# Getting started with LPC1768 \ No newline at end of file diff --git a/website/tutorials/03-nrf52.md b/website/tutorials/03-nrf52.md new file mode 100644 index 0000000..ed51cde --- /dev/null +++ b/website/tutorials/03-nrf52.md @@ -0,0 +1,2 @@ +# Getting started with NRF52 + diff --git a/website/tutorials/03-pi-pico.md b/website/tutorials/03-pi-pico.md new file mode 100644 index 0000000..ed46e0a --- /dev/null +++ b/website/tutorials/03-pi-pico.md @@ -0,0 +1 @@ +# Getting started with Raspberry Pi Pico \ No newline at end of file diff --git a/website/tutorials/03-stm32.md b/website/tutorials/03-stm32.md new file mode 100644 index 0000000..0f7d792 --- /dev/null +++ b/website/tutorials/03-stm32.md @@ -0,0 +1 @@ +# Getting started with STM32 \ No newline at end of file diff --git a/website/tutorials/04-chose-device.md b/website/tutorials/04-chose-device.md new file mode 100644 index 0000000..1c8509a --- /dev/null +++ b/website/tutorials/04-chose-device.md @@ -0,0 +1,7 @@ +# What device to chose? + +Consider: +- Project +- Experience +- Personal taste +- Price \ No newline at end of file diff --git a/website/tutorials/05-hal.md b/website/tutorials/05-hal.md new file mode 100644 index 0000000..2bddb7d --- /dev/null +++ b/website/tutorials/05-hal.md @@ -0,0 +1 @@ +# Introduction to HAL 9001 \ No newline at end of file From 96b10b11ffeee1ede589aaf461bdf598c5c7e167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Sat, 13 Mar 2021 20:19:34 +0100 Subject: [PATCH 07/49] Adds atom feed and nice index. --- src/main.zig | 108 +++++++++++++++++++++++++++++++++++++++++++ website/img/atom.svg | 21 +++++++++ website/index.md | 18 ++++---- 3 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 website/img/atom.svg diff --git a/src/main.zig b/src/main.zig index b83db83..ad2b859 100644 --- a/src/main.zig +++ b/src/main.zig @@ -37,6 +37,7 @@ pub fn main() anyerror!void { .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(); @@ -74,6 +75,29 @@ pub fn main() anyerror!void { .src_file = "website/tutorials/05-hal.md", }); + // img articles + { + var dir = try root_dir.openDir("img", .{ .iterate = true }); + 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.openDir("articles", .{ .iterate = true }); @@ -125,9 +149,16 @@ pub fn main() anyerror!void { 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); } } @@ -194,10 +225,12 @@ const Website = struct { 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; } @@ -219,6 +252,11 @@ const Website = struct { }); } + 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); @@ -467,4 +505,74 @@ const Website = struct { \\ ); } + + 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), + .{}, + ); + } + } }; diff --git a/website/img/atom.svg b/website/img/atom.svg new file mode 100644 index 0000000..b870f3e --- /dev/null +++ b/website/img/atom.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/website/index.md b/website/index.md index 6fdd918..e3cdf07 100644 --- a/website/index.md +++ b/website/index.md @@ -2,7 +2,7 @@ This group was formed to document and improve the embedded programming experience with the [Zig programming language](https://ziglang.org). -## Goals +## 🏁 Goals - Provide documents on how to get started with embedded programming (for absolute newbies) - Provide example snippets for certain architectures (LPC, STM32, AVR, ...) @@ -10,7 +10,7 @@ This group was formed to document and improve the embedded programming experienc - Create a common interface/HAL over several architectures - Create a performant common set of drivers for external platforms -## Introduction to embedded programming +## 🧑‍🏫 Introduction to embedded programming If you've never done any embedded development before, it's a good point to start with one of our tutorials: @@ -28,15 +28,17 @@ If you've never done any embedded development before, it's a good point to start - [What device to chose?](tutorials/04-chose-device.htm) - [Introduction to HAL 9001](tutorials/05-hal.htm) -## Latest Articles +## 📚 Latest Articles The latest articles on embedded programming with Zig: -[See all articles...](articles.htm) +[↗️ See all articles...](articles.htm) -## Code +[![Atom Feed](img/atom.svg) Subscribe the Atom feed](feed.atom) + +## 💻 Code Here are some highlighted projects the ZEG provides: @@ -48,15 +50,15 @@ Here are some highlighted projects the ZEG provides: - [zCOM Network Driver](#) - [TinySSL](#) -[See all repositories...](https://github.com/ZigEmbeddedGroup/) +[↗️ See all repositories...](https://github.com/ZigEmbeddedGroup/) -## Community +## 💬 Community This group uses the already existing community infrastructures that exist for Zig: - [Zig Programming Language - Discord Server](https://discord.gg/TyzJXjser6) -## Members +## 👥 Members - [Felix "xq" Queißner](https://github.com/MasterQ32/) - [Matthew "mattnite" Knight](https://github.com/mattnite/) From f4847afbadb745954f94c62a21c56583714c75ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Sat, 13 Mar 2021 21:52:43 +0100 Subject: [PATCH 08/49] Replaces emoji with svg. --- src/main.zig | 5 +++++ website/img/articles.svg | 20 ++++++++++++++++++++ website/img/battery.svg | 14 ++++++++++++++ website/img/code.svg | 17 +++++++++++++++++ website/img/community.svg | 18 ++++++++++++++++++ website/img/goals.svg | 35 +++++++++++++++++++++++++++++++++++ website/img/members.svg | 15 +++++++++++++++ website/img/pager.svg | 21 +++++++++++++++++++++ website/img/read-more.svg | 11 +++++++++++ website/img/teacher.svg | 32 ++++++++++++++++++++++++++++++++ website/index.md | 18 +++++++++--------- 11 files changed, 197 insertions(+), 9 deletions(-) create mode 100644 website/img/articles.svg create mode 100644 website/img/battery.svg create mode 100644 website/img/code.svg create mode 100644 website/img/community.svg create mode 100644 website/img/goals.svg create mode 100644 website/img/members.svg create mode 100644 website/img/pager.svg create mode 100644 website/img/read-more.svg create mode 100644 website/img/teacher.svg diff --git a/src/main.zig b/src/main.zig index ad2b859..11320da 100644 --- a/src/main.zig +++ b/src/main.zig @@ -491,6 +491,11 @@ const Website = struct { \\ h1 { \\ text-align: center; \\ } + \\ h1 img, h2 img, h3 img, h3 img, h4 img, h5 img, h6 img, a img { + \\ width: 1em; + \\ height: 1em; + \\ vertical-align: middle; + \\ } \\ \\ \\ diff --git a/website/img/articles.svg b/website/img/articles.svg new file mode 100644 index 0000000..a2daf51 --- /dev/null +++ b/website/img/articles.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/website/img/battery.svg b/website/img/battery.svg new file mode 100644 index 0000000..0545c03 --- /dev/null +++ b/website/img/battery.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/website/img/code.svg b/website/img/code.svg new file mode 100644 index 0000000..f92f207 --- /dev/null +++ b/website/img/code.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/website/img/community.svg b/website/img/community.svg new file mode 100644 index 0000000..9fb0f21 --- /dev/null +++ b/website/img/community.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/website/img/goals.svg b/website/img/goals.svg new file mode 100644 index 0000000..137cb27 --- /dev/null +++ b/website/img/goals.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/img/members.svg b/website/img/members.svg new file mode 100644 index 0000000..324b268 --- /dev/null +++ b/website/img/members.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/website/img/pager.svg b/website/img/pager.svg new file mode 100644 index 0000000..fdca7b6 --- /dev/null +++ b/website/img/pager.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/website/img/read-more.svg b/website/img/read-more.svg new file mode 100644 index 0000000..95bb3a3 --- /dev/null +++ b/website/img/read-more.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/website/img/teacher.svg b/website/img/teacher.svg new file mode 100644 index 0000000..10f3236 --- /dev/null +++ b/website/img/teacher.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/index.md b/website/index.md index e3cdf07..4da2466 100644 --- a/website/index.md +++ b/website/index.md @@ -1,8 +1,8 @@ -# Zig Embedded Group +# ![](img/pager.svg) Zig Embedded Group ![](img/battery.svg) This group was formed to document and improve the embedded programming experience with the [Zig programming language](https://ziglang.org). -## 🏁 Goals +## ![](img/goals.svg) Goals - Provide documents on how to get started with embedded programming (for absolute newbies) - Provide example snippets for certain architectures (LPC, STM32, AVR, ...) @@ -10,7 +10,7 @@ This group was formed to document and improve the embedded programming experienc - Create a common interface/HAL over several architectures - Create a performant common set of drivers for external platforms -## 🧑‍🏫 Introduction to embedded programming +## ![](img/teacher.svg) Introduction to embedded programming If you've never done any embedded development before, it's a good point to start with one of our tutorials: @@ -28,17 +28,17 @@ If you've never done any embedded development before, it's a good point to start - [What device to chose?](tutorials/04-chose-device.htm) - [Introduction to HAL 9001](tutorials/05-hal.htm) -## 📚 Latest Articles +## ![](img/articles.svg) Latest Articles The latest articles on embedded programming with Zig: -[↗️ See all articles...](articles.htm) +[![](img/read-more.svg) See all articles...](articles.htm) [![Atom Feed](img/atom.svg) Subscribe the Atom feed](feed.atom) -## 💻 Code +## ![](img/code.svg) Code Here are some highlighted projects the ZEG provides: @@ -50,15 +50,15 @@ Here are some highlighted projects the ZEG provides: - [zCOM Network Driver](#) - [TinySSL](#) -[↗️ See all repositories...](https://github.com/ZigEmbeddedGroup/) +[![](img/read-more.svg) See all repositories...](https://github.com/ZigEmbeddedGroup/) -## 💬 Community +## ![](img/community.svg) Community This group uses the already existing community infrastructures that exist for Zig: - [Zig Programming Language - Discord Server](https://discord.gg/TyzJXjser6) -## 👥 Members +## ![](img/members.svg) Members - [Felix "xq" Queißner](https://github.com/MasterQ32/) - [Matthew "mattnite" Knight](https://github.com/mattnite/) From 07ced466753460b441d8fad20bace7c5265c1553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Sat, 13 Mar 2021 22:01:23 +0100 Subject: [PATCH 09/49] Adds LICENCE. --- LICENCE | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 LICENCE diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..16b66a9 --- /dev/null +++ b/LICENCE @@ -0,0 +1,19 @@ +Copyright (c) 2021 Felix "xq" Queißner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. From 31229687887678e519371e31ec1f6cb369b203c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Sat, 13 Mar 2021 22:52:51 +0100 Subject: [PATCH 10/49] Adds new article stub, adds links to nrf52. --- website/articles/2021-03-13 black magic.md | 4 ++++ website/tutorials/03-nrf52.md | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 website/articles/2021-03-13 black magic.md diff --git a/website/articles/2021-03-13 black magic.md b/website/articles/2021-03-13 black magic.md new file mode 100644 index 0000000..e95dcf9 --- /dev/null +++ b/website/articles/2021-03-13 black magic.md @@ -0,0 +1,4 @@ +# Creating your own JTAG debugger + +Black Magic Probe: +https://paramaggarwal.medium.com/converting-an-stm32f103-board-to-a-black-magic-probe-c013cf2cc38c \ No newline at end of file diff --git a/website/tutorials/03-nrf52.md b/website/tutorials/03-nrf52.md index ed51cde..8a716d2 100644 --- a/website/tutorials/03-nrf52.md +++ b/website/tutorials/03-nrf52.md @@ -1,2 +1,9 @@ # Getting started with NRF52 +Used hardware: nRF52840 Dongle + +https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle/GetStarted + +https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-desktop + +https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nc_programmer%2FUG%2Fnrf_connect_programmer%2Fncp_programming_dongle.html \ No newline at end of file From 59c7f308e34f682d2a0e66770b4673a9b92fc0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Sun, 14 Mar 2021 10:41:39 +0100 Subject: [PATCH 11/49] Wrong file name. --- .../{2021-03-13 black magic.md => 2021-03-13 - black magic.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename website/articles/{2021-03-13 black magic.md => 2021-03-13 - black magic.md} (100%) diff --git a/website/articles/2021-03-13 black magic.md b/website/articles/2021-03-13 - black magic.md similarity index 100% rename from website/articles/2021-03-13 black magic.md rename to website/articles/2021-03-13 - black magic.md From 0bf245d9d26039e10a385bc60522061c7579bdee Mon Sep 17 00:00:00 2001 From: Matt Knight Date: Sun, 14 Mar 2021 13:29:47 -0700 Subject: [PATCH 12/49] added bit about what embedded systems are --- website/tutorials/01-embedded-basics.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/website/tutorials/01-embedded-basics.md b/website/tutorials/01-embedded-basics.md index 50ff712..14b5ef1 100644 --- a/website/tutorials/01-embedded-basics.md +++ b/website/tutorials/01-embedded-basics.md @@ -4,7 +4,7 @@ In this tutorial, you'll learn the absolute basics of the embedded world: ## Contents -- What are embedded systems? +- [What are embedded systems?](#what-are-embedded-systems) - Electronics 101 - Current, voltage, power and all - Diodes, Resistors, Capacitors @@ -17,4 +17,25 @@ In this tutorial, you'll learn the absolute basics of the embedded world: - Bus Systems - UART - SPI - - I²C / TWI \ No newline at end of file + - I²C / TWI + +## What are embedded systems? + +Wikipedia does a good job defining embedded systems with this opener: + +> An embedded system is a computer system—a combination of a computer processor, computer memory, and input/output peripheral devices—that has a dedicated function within a larger mechanical or electrical system. + +So at the end of the day, if you are adding any sort of computation to some object who's main purpose is not being a computer, it's an embedded system. + +Some examples of Embedded systems: +- cars +- industrial control systems +- mars rovers + +### Real time + +An important characteristic that's often required for an embedded system is "real time". +This is simply the ability for the system to respond to an input within a hard deadline, Eg. automatic breaks for a car. +A general operating system like Linux is not suitable for these applications because it uses time sharing when scheduling tasks/programs, and unreliably responds to important signals. + + From a433e24d4912a69ea390f213ed6b7d67fb3c2839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 15 Mar 2021 13:55:34 +0100 Subject: [PATCH 13/49] Adds Prerequisites chapter to tutorial 01 --- website/tutorials/01-embedded-basics.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/website/tutorials/01-embedded-basics.md b/website/tutorials/01-embedded-basics.md index 14b5ef1..fe4229f 100644 --- a/website/tutorials/01-embedded-basics.md +++ b/website/tutorials/01-embedded-basics.md @@ -1,6 +1,12 @@ # Embedded Basics -In this tutorial, you'll learn the absolute basics of the embedded world: +In this tutorial, you'll learn the absolute basics of the embedded world. If +you have already experience with embedded systems and/or electronics, this +chapter probably doesn't provide anything new to you. + +## Prerequisites + +None! This is your entry point into the embedded world! ## Contents @@ -31,6 +37,7 @@ Some examples of Embedded systems: - cars - industrial control systems - mars rovers +- digital thermometer ### Real time From 3beb6a6327d71e47ded9eafb2e31f9919b334b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 15 Mar 2021 16:32:42 +0100 Subject: [PATCH 14/49] Enables anchor rendering, adds some nice basic styles. --- src/main.zig | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main.zig b/src/main.zig index 11320da..e637d1f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -7,6 +7,9 @@ const markdown_options = koino.Options{ .autolink = true, .strikethrough = true, }, + .render = .{ + .header_anchors = true, + }, }; /// verifies and parses a file name in the format @@ -482,20 +485,46 @@ const Website = struct { \\ \\ ZEG \\ \\ \\ From 0bf65bef97dd8482a9ae8535fd808e73d2f8bcd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 15 Mar 2021 17:18:46 +0100 Subject: [PATCH 15/49] Starts to write 02 - Embedded Programming --- website/img/memory-map.png | Bin 0 -> 163132 bytes website/tutorials/02-embedded-programming.md | 148 ++++++++++++++++++- 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 website/img/memory-map.png diff --git a/website/img/memory-map.png b/website/img/memory-map.png new file mode 100644 index 0000000000000000000000000000000000000000..a41ac4090f46612642b5f1407ed726b13724bdeb GIT binary patch literal 163132 zcma&ObyQVd_$|C?ML{J*8tLwql6Y$Pq(MNsySqUeq@|_1ySu*S`@8p!asRku ze0aw2u+Khwul1}a<}>Ho1j);ap&;WRLlA@_Aug;4L2y|R1nc|^792U8fBhT$hxl1a zOc;9l_bODWndE2WZ9S}Q@6*}_U2Glhr5>3)#=EDX5BJ^8E5(tb1wmiiKQX?0 ziTL$qh3IJ<`G4MnLr>`CIU)c1Bc}FPdc^-ZakB?rtMT9Y;Inu_bjzL3|9!jtlb7`W zuP^M@p_EpZvR73Hie{iPR&5t!d~NsOxP}!}cH)N4nV6$vqKKWeRrKN%6vfGu5S{iO zOE66T;&yEraoBQ^Cvt1{Q!h~?|95}QI)^p8s$Y71)&s-RNG5rU51N?GArYx3ce-o{ z-NCpK+%#kYq-eazuiW0)Y5QtcoM9_OQBjnVj{x-ndm^b%;qo6)oLgsW#&pZ zFt8PM9VJX$&`%R@wkKO>uNW1BzJwJl9MQqo8XDef5F6P?3g8xBS@{w5e&5lPpArcn3&p^-Xia#wQ3wJ zaBwt-C>heE>i$~eTUo(DF^)CXceUSh^TfI9Zy#%#RgUtsE~y9l-~>E$YxAx~xA$K{ zjAJ8cT8%5%1ts74z1QC<(WT&t?);D$NMNINIMXmR6*f*BHZOBR$4rVwfO=aRu&?QB z^3xN(0)qjU*}?hBPMh_kPeE)L?L!s&j+5bKrc*#P(|`_j26_du0nl^JjTl1sPwx~F-FajOL4k)MS0>czi*7F%`LtWN9IP^_0$Ye zQUwnTna?nS`?|pT8p(&m<#(~7Kp8WyqAwuE3Ya4_%w-$ z0VANGD(fLegkfFp^?0AOxIcGsLee8 z3HSNa?{jwMnyP&S=yF6$MAvJ6vE-$yqo%E|IQ(!{2ejN)e6vR(D-=HDcuwnpqN;QG zS8e=tu&_4Y(t>{1k^NhK)K}KVJBem{;1$fE<7AByER4kaHlK!CWooK95E;&AEp@;_ zWfHuoJqbv@GS}pP+TO&pq`&Hm<)wc>gl)SY7qdaUozqlrUh@3;k-TTq5Z0%rfXib@ zlmvy&QG~27zs%dEX?vfr-|p15haMQ+&V?rlMT%K6_^(WSqfM?!)w;9Rs)Dk{VVzgc zkiZj2Af#w!i-DsiZ%a}=SC>=LUxD9m%hNnm-QQ=ibJCf0cPp7{YiP)N^{_^;t>r-i zs96){-P%y#2PggAl?4fe24;tXwN$Ad)N=eLXDJU|_OOqC!Fly4h^%4UmB!`4BjCZ2nWL$~@J%x>8|qd#)|cm#Oo|xN z{wk{W@wqFF&4%|%+fO&*K-4xoq6}shrO8}(w>vIL8D6+fP+QaW_Qlj6vh@uJ@|Pyy zH{EXNw)lb4Q>;(N=e%zw48oK&TD^GWiN&h^L(gfBgH|i87;^s`W9=5Idhd{&{(gbEqF#Up60VGIn@UPU3+cs8C zI7XxmkM?*WGc(7Me}$7G13Xh3=4DluV7O?I zF`y0w4h+40=D!;(3EZwHb#ck-&RpPQu{bF#B<0!3duTtybJ)S+JyDgk_*+Eh(wj^q zOGl#uhR!;CXoxsc+%rtZ71oO+01nF7p;AIUvYTyjTDy3$Q9}#GAFQt$vwUkW)OC6! zh4vfWNtpQS@_bKh#!c!b!i0%!U4H2}+kgZyqj8BABn--r%!kSk0Ses4ZJ#2=9VoUt z7nUQS9}0@=mG%bO*0ocH&S{gniIV*mWvR{B+pi!+Wt@PXwPTl70{-c_-Y#jax}X6K z`;Eb5r2y&b>N}jI6Zm_Sso^cA0Vut?am;IAx6W39eZg%%ZG+3n$I8L!`@o;DULQmV zH9}2UqvGVZzwUSP?>LE7DcYPoi&ecNv%eWIF9f$KP$M{81BI#tjWWaoCtmwLTUa@{ z!6S$m*vfLD*k4Lj(5`m>ojfowC5OLzO-XCozmn3sQBBfB=#PN1W9{g*bgGEzI&-kTxK!P;VAFeh}bHHU?8A1`12%s_QB z-vwp0N{-;8M9cBz%?-ye!pD_UCiJcFcg?3!9D+t#u6-XExpYNz^`}C)nnOdK=Ud3B ztfc1NFO+;gFNcLtL~^>b=5KM5JjFwkwmh@zD_2L#Uc4ICRaeogTRuP1$EJdaUL$ky z5F`y)VSljwKaR^xVVa#$V-$QxbVh)Vh;lb+wm#`I1yZJ^Y^=~Q!}t=&)e+53ua2xx zAXF_iZ&`_7`%iZB0&~VIcrIUXA@+y+7nQIy<`$QWO|F|4%@^k(P}vA|!+|WX3l>-8J=0wNid_3?6$XRe`pJA5wGbc^awOr|n z>MJ{>wbTSf-|oDGXz0B(icguS9u-{V?<+zo6&11KN<(_3-QQih-W?Q)5iG?-$6o5x zl}x%C8VU?ZN;J>8suN-03_j(As$j+?#n^5lrbyPzl(SOP@N%-5<#q<54->N6ab^`; zT@IhZL-?2~TTATsC+CZ_VkXEN1MYEBt-kIif28Pp_iFm9B0ERrm0OyfEF3*kF6XJV z`S^)hhSZhrrpJ*$$au1awR!gZkgQFL4KT?j@Yr^DesIE&C8*l(x;LWfd3*_djO-N` zqG?S&VP!Uq&0;Ca;A5nMfi^kAc9jLYHjDx8&DG?_C6|fI9X0t7W_Q3ZE+?Zyigx1f z7x{oV?Npb%u{}R9(j_l6V0Jfq^_vo9ohQUw8ka(*)A0{h|hOB;fD#Om*uu>?^mdf z_ZI8(K6`+ND0t#r?^)3xoN3FYVHo^T_5g)jDz0_`owNEaX6X9IyE>~~T1+liR?8=1 zY921lnMMhAJi4*+Bt*S*o12f=;CCRj8oBsg)+>jMi9qX8 zSe~^jPmP@JJU!k;MoU`Ec@x=py} z`mtGOXZeh&K1)jzzalEk@{lw0e3?=thxC8${z=-Lu8L?@Ap|gZliLjv$dYd+Z)yz+ z^T0=Uy;Bm9P(kR|R~2e5^!x0j@f^B8ceF*AEeYIq(IN``$V0(cEhY!&hyrb!jL8Ix zf(-pqHAbAYpOy|5>M1MBGS!eOGK_hfh46kghX{-@m~KI~MjP~GgVl~p-C|Fh?qQu< zm})8z6s@wbA^rQmCPfK2gO2UV&#Qr+G1gm%<%Nib#<~1YVF^`Po^!w2<2r4nxi;f0 z{+(|B?HeSB7bQ#u6Js{`YPMendJWdXZtFX!1x~Qa! zD0QU+02Tok+l~yRuOaQbS8v_#&36)Ed`b&7r!)9XDkxsrzB4&{R8W7-jQ|TF#wY9* znXSjMWSg?Gip%-9%wbDXajSmxphAOzCOFt&1h9*!DcPP&xZ~i!3Fs&(RecdBjlu}~ zjrHvDWk7hx8yF}t5-X&ug|t%v^eq`rTt5$Yc#P}PhUe<@XMc-U6S)03Mn%~UCVj*; zH*J}<{W2aFqM)$@yp}KksjSuMux_XIk^NzcoPI0M$^y971O^5ulD0QI6=kBRNfgU) zKEDRz(CIJ?ACn{hXDLGq@6(!|=a@O5Mo^#$!Ucq>N(QdQD5qnqTsZ12^s?TKQ(3_* z%Z>!-MG&q(zy6!MQ2kSxbc*whz>hxslkk6>Kxn+IRwKfOw8O+<=TH-{lKH9~#==5k zLqTB~dXnu+RZM8RkacOVgt*K{bRX4aFioF7ndtX%=){j|Lsx8URz8kuv0*+@V^FEm zl+Ye|V-E`uXhy=TjfwW;DbaM?t`twxN3MTa29y*5gLCrPXfzElri%Di;_MpbWY3fS z(J!Ci_~^~aL*SMo@PZVFQ}1OYK+abmVI7qxKu0vdkm|5KgL|MAJS2j|-~i{-nEU(Q zP82=P;_M;>Qf5Z*52Bya;f>S#Goqx#P+EM;7v7(?1+glrsHs@(Lvs{w+5rYwr05zcP(IFjm`b% zzvas7n2>?aY+YiN7IN@=$Mn|-NMNOUioH!Lf)JA zL)y6*n`O;zP8%Xe$DzuQI32Y(buR)WLxu?*pYw5BAN>{67fg{6H=#06VY_=SlIi4m z!Okp5QmkdI+j6nPo798(%%2c#jeLJecWyd4b*|0^`1c$KPLubSx zB;aQIp1ZQHwSWu4P>E&Wb`KRMUeKtsiX5m9o=t4X3r`=F?_*j>pU7S;+I+R}i7xBl(xD~}H;w%!PI7Ov>HL9sMl>xT4A zUrxKu0#Z;+_8~3E1sgqle{J`cyLDLrGHt-qjuOfG4b#>h(DIM`U>DNjm6lOc!G92p zp)Md&J6*%KS<8Hz_F?mmOF&3rX2`fV$7m0QjTI)nvb46k5gEcTGz7~cOgsvJT_jd@ z3<|!iUEhBpS!|AH{w@0|s@Jyef{7FgI$_7rE;u%BTPxR^LX{*gZI=xnt0 z>ugjX>5uqMs**xj2+mOk3jFgYa8UXvuogaH-kSM8awIZ)+@MhvQ51CllNXMFrAaW~ z>Dn|7!)ClTa=4D{@v<8m`Qr^IHy4JmT@ffe6nXIGwF;B6ZLP(<0CU+|kfA8Lp zKUP`Al)3=07dZ-4ELtzCWa{j>Z&nqwtz!>zHzX_VYGlGdPoSf)qm~y59rL{>2S`P! ztVKuCOANFu-1d3wO&V?&x?=abIu*9(heM+?*j3>NSDdoM(c-4f0WrR#VJf=N>$s$g z@!Fc>Nm$DKPt?PBub{cyKOd2z$u_rO1qM3BzNyNqBQ<4 zB}NcWIAMNinHEp~P=-mG7x@B61T8^fJSX=n_mKvbk|C3rF(Z}1k)BAL=lf>>j{LER zDf|VSSX;}?sL9hG)?Rpd&Ib!oY?Y0usy0_!J^*gYd9i`7atTUmJ&~A>S@UYaIp%In z9<>?j@LgyGC9$};NF=G6yStJJ-Gd(qxXh(i)t7RABV|=L2^FJPyh3Gthwv?;8guW= zOf#yAHp5Llcl=5V6HEM6)J2gsbClE-`1D5@0mlZL0YU58Ic3+aEpLy1GhAl@R>xdH zP>^f9zs>X$#|!1+L7czKVFTB&F#giDBa_3FRGJ=#)4m#Kb<(z15fQSz1EZ-Q{AF|4 zKG@cNwbG9_V(_C3w70C8TVJlu251j%ib3H=WQp~4rEtc^E{TZ7Ohczwn%9NlRLWj^ zfrLM%G)@flPLzO_0g4sWOHX4v-RPG4wJJr0;psZQq>jEd6!n#)Od?O$BS`r@EA4L^ zZ*O<4Qg5Yy^mUna{a2>3f2MqJMr#tMqksL(AJhXdZ^#ED%-Cd-CP1Oz+;l`$p}E5R z?3lWp!%nZCW@e{B&eaI zpataN(<4O#1nLFLLG7&7DPaD>VgQuTVBivBATOFPh0Z-KvW2_)UTtwH-3a9V)1O@IGDl+QnhengxB2I-P%$1vnNW-G)0R zDqZdIt{>vr%&ID1%%f^6cY*olG=rG>XLIWdGyki{f^2R1KgAa2`&Nc_29ulh^Oi!6L$+{MTBujNX# zW@BOP6cr6S;fNHMG?aR|qJ&J!4`V&sFL5igO;XfWX*bWOi0(u3)+yiF!sZGcLuA)d zQ2qP+SCgR)k%s>>=7!1Bj{gFLPCOG;y4}F*UA>2wmBClml*c7j9MA!P1PT^SD{pZA zgZ<)FXkvTeA6mND%+VhggaEyQA{``*#&>Qr#qd9JN~v#a#m$nRY)XxS)5~xF8$LL4 z#*gs-5{ESTjc6IU-jh?TeJJwVc}9x195eYp*LeJT&c=?rIM=A8tGN`ZIxep_HE|wR z-Qgj%txSDu%DCD3JVtsG(BA)Jr6&YXV1KrFy{!3K&6g`h9QoKD9nT0a-)pGN`ef8= z$h?vE7v{f7dBdXxO=vJo8~?XRV2tM+h%B!hu@i`{mO_<%d-Yf|+b;X2WE#%35IO!qOIi8~}Vgh58*)w6N z?rw}JO6Bu<2@%*E6W%^J!^TOIpBV`FWM+QG%8r*+&{nQ?@3AS<{q;JDb8KwP8l8N9 zwoXk$WFu`-ChB-(WWTsLGD}-C81eiN4(j+6L z_fvfeuNw`g`^oCJG>SKzf28G}-n}LwBb!8^LL4n1*grg&3kwUg->NfEQ&CX?hAJp5 z$dK~8JoVgmky*BFAcTX3(pyRoz00PsPVLT!J31yNCb?JDNzw3mUGksWd`U#Pg+hu! zJIK$T@@~AOq{Q=V7gHx#mOMI*&#Pn2?#bC)pIcmC(0A|ImWf;{X!**>CttN4KHOCzzmQGgoqN+W8X4IWPJr@w|6FGk4CGPC;j{GB%o+tAs!5mlR%7FB zV0pQ-%~CivhwPZ3_?P1l3ORq`6-u?KSftc9;!Dk5(W$ZQUq*-!5d7yjY{GH*y?V$;Q}Ev^^T+{~4Yi8&=Dynly=3f1InO)Hp~j&xh~ z8Q=ByO9~zA2YiSyEs#bD*|0z0hG;b#*ZXs%a>#N%Tphwdhm@4$;oZ%~<~lVcWhl-Xx5G4}!_)4@0E*)Ux>st~hMlL)g1NaVp1BRhC1?#~ zWM17Ll96}8wWYV$qEkFcXUNY|vqg$yEI;wFa^~me{%#xjGRx3vy0`5S{S#KduQq4h z`x{tN*tRiD8rd`+?CTW+5fT5!#zu8&4CJG`8szMx3nTa&iCEa~Lfz4VRvne{Zq&xWkLoqHfO>C> z8czfy1YXbc{Fau~K0`M`ex||p#YXR^TJD6w&`=Hry}sUl7kce!=1bH~0;qgupW+oE zIT`uM&96gVTJMCNa9!UnK*pX9d$qKH71x}dC9vN#H8-!-Lf4E+ie7WyQ&lli;kVdR zFwjntc<#oty7+;OYy)^A80_rq?Aq@Lz!dlrDE#=}djWEif5u-k5q&!Nj#5BHCGk9H zfp$DAuuE*>5}g-pUr$jkYV=a(}f^x5F^uZXnxO}?z}-R7~A zj*gBIhy3EQk{Hjv7ntvb6tMJQcr)Kz=&%hsEtr5hS*>fDXZY8ss?k8T~krJch= z8{Ov5Dr!fwbfx zgOUav+5hepVJfc;J%so4aMgMDO1?mu&g!5)7@0kZ*J~`>K8~JUN=iY0CEe>URPHh} zW}i1qB(u7&$D8hLRv3+Ef@gBt#^HFgwR)xA6e=S#y^6y;h@$ z`?scXJ`M~fjOZe?9IRD&UEuuloEx6UYYB8heiup#ia3oQuTl!Qx_H&K4lJ(1@qxFz zL8|neD$Q`}oZU4{uemDsl_;`RMcrzLof{Std0~!Hor?x1&*G%H zZVNS-Pup$2t_`kh6f$l%``Ey3zHmb2#t*kBx=CWM2qRw;GN?XIxhZC7*PL|Y>?Yuf5b^;BjW@T%e)r_$<%B$-Nx}Gt=E1a8^+bq;I<&0p zefYfI3w)#bgERQ*og4v@$hW)slMTCi+`q+df`n}=DnOwQ4GoK`V$MQaUGjRy$jJKg z*qZ_^2p-n=>I5Bn{SF5Wn-D84)_kGW+soQoL0`XWJy5SlkH`IAR2`jd!7N{oF755@ z>GXt$Nd|hKy$3s)7Mhb%Vh|t)DsE{0s_BFE{dMm(=lN^Tr$78OEa+gTQ%g!xv{2S~ zTC;14iaMj~?8J3FCzLMV3w_!8TQbF*X3xEpcT-dBbnPg}b9)14b_2aHpU`Tf@l@YZHCI z+x4BCpf8W-X6w$tvAAYtI#W_e&{x5v1770dT(5L~b-&W2tLggoEiZxqt}Ud_CKGM7 zM=p7KdfMf5x}r5raW#re{eF|qjWii4FokK9Y~l-R1Xg;Uiubd4QMZn~p;6YH1iF26*j`Uqhj|{Cq473_R@Q z#I$%&S5_^4F^HIVf_Jmkjzv?KR?tY5b6It=TrHT6DL86_O;yw?38L^%fP01yw1wVXfM{PtDpE8 z`fJ{pWFDTEK=4RMS-swIoIFFm{%E9qesz_Gj{{I%hxn~A7+7#nm?)gj93LNUoy)@U z#U#gwO)i@MM+Bd?SA>gR_ditBeRT5}l6keXl+KoCl&X205AScxxMrH&u42zA(Sl;> zw7V#2s+=99`}zk5htv2BL=6nqPP?T>b{SUx5CR~7x09dazBDXHipGUPSel=mU6RGp z>jX^Wd3*gl%Gk)5ILV+dc!L#3Mon(m;E!d6-e)S0huS1Rzm5u}yks5G#KeSVuMhO8LC!=a;$}v)xKEIf&lOT(v?I9@@1u>3GSYjWXmh^VB|eFJKxQE^XaS%VCzyhuJE z;Q?YOy3YZd7a7uRb{TC?rbsWXef#icm_IfyPA=)Z>N-HQ3RFA!#}6ok?S{+}q+>ra zgb-lc=cdZK1j8KdaXGwxWkimyZVV`J0;mq_yL=H62QhXS83Mr07Q$vXVG6n2>~mGC znm6B_Q&F23W4=8vuUxgO=XJj_kM-60o^BWvbI5mR1&@>_%W*ZY3DG?YkJa}Ch z#&Fz;lp&v=pU>&O$b%NdI{4?$pLW6hIQ7}egx6c_=1B@~B7xt%c!BJd@_~Yq*yCYS zrYqApT^s~Yc-kcaukm1;;8#;t&ChOb))FWCu4gXVr7ChX&?n{uN3^>%&5|~agoMya zW=Or(oGeUsYJ5t}ARsV&3U8GlG~H7|LL%ct0XZQvKU+91vu>`hTC&I91|(^bjlq#^ zN}MoJ;>bZd+C=~1jW4eVc+Cwh*V*+yE_ppbMEnd`vqjH`u8;X3Xr7m6VsbJkJ9}jR z$2&iQi1aJ3lYQ)n9wk+U8^=R!FfX^k`0=gbv{t)>k2xJvnkVDOD@9W!_DE%*jQiBSH^+FB}M_nPArj?kd>B{k$~|D)A}WkZM9g7 z2^CV)eC6|e`@y2YB(H)bAv5M8lc5#Y~*Hdl1LVS6Czm;r56F~Atxtw7ZsaH41tsZgY!Xf z;_rmAit6LgP^+EZf!V(XsOSZ>pPQR5m)3>ac(wgr6@s*rlS!MmHr6HU_ro6pv7Rb` zJK!XZ_!?<(SkE?|4ELDp9;OkHNy(6+rEt0SU;QoV@PjpshlL^r_B3}#zOeOw@ryE2 zshU+eMas>t=VT>?hvKEl&68%ixf{5+K;w~tJ{{5Nm&L@MPiswrsBgYoI(%rh;y7qzY<>A!~Ob1TPC1WS^MWuz@^)_iwbIwLN zIjb>dQP?@@={~pM9us4*kP|vCw>CQD)44*lf%3AuL7#fZC%vZ%pEikV~c zsoda-UV`KA!yP~%OLtd`t!GcmM}(Nz3CTatj~5>T%a7YeUIQ3agUcR@$F{pswH3Lt z!ZbQ6n8qjaEr8ekq0u>wuY_C%k1A=%DUCZ!L6zP5-X(&oY6BC4yeZ7&$+4o6^F0yM zJOacFBG^au`+L$?yaEPjpy`)$c7BqYuuIW09YY%^D}#A2*ly|qI>K@v1w?3=fM4U% zF(>zW)bYN$&X61z2RrB~B$_;dp88^d#AGLUb1(T}vWI=7iq<3p{IwtRg*;klod@%ROcL$EeANPFUU zA04uz&0@m0Kr|NVwR)JD896TZ>N}!@>C8C`e-Du2POlveax5o@pag01NO7RMv^^!_ zYKLdT{g;)rpA%FklNe3^=D#ZVsqBoR$JRRgm~a11O=V4euYU_zcr&TMqHpq)v?j4nJg&@z(9z+yxV}Et)Y)PO%XM~!NYdT79YUGsPuTQ!1znVpUtN-T(Xu;o(Ik*#m8{|b0!hBlv zL!i$RJG;4$)xwy~Tx0VU%`!7LW9si$R8kyCOfY`nvW=KhJqoL=AmVS$$bf0Kba~`X z8s4JQt;NId9)A;shCZ5hKB4R@06|eSZvGvrwR&2fyp`nd=?UnzWMrj3zGcz$_HY06 z*gD=)L8cqAceui|vL&&)G_f-dv+SEj2*t-IX0Cm@JenBLlXM>~ZMj&7qyF9rR_1tz_s+VbKaZkByJbdg9337;!_{ zIl1(DzpeobY0v8wDv7TTK(?Kook~m$ z$v!^l(~dmvyAJU`h;pGUwVJ$w(zmQR?Cl+T9?Z3F)h&IW`v7?4MhwrJ{uoF=yD~-bRE(I99<`)Lr@<+#USBqqeR6U14Fo7N z7@v?mQ=F7PD_?3Yr{iA}dVe|MJ3(7?$|?EyW3FmpA>ie^^1Hee*M`&(z} z)LAe7vYeEJOmAmB(WSI9b4#5r$H>TxHfXRIW?$9nw7P{ACeHd7sevAU)8!BD;XWA5 z{{A*+x^o(~Qf^e=dNb0i-JLzHtD{!Cp+qpdzO|lL+Y3G2eN?<`2XP536ta&E3x=RK z2utI91EC)AGn&oU6U4^q&eg%S9R;8gE-wAydfvIe|CUx)nmCeRv8i=oz+&Eo!$1lB z$FxXAy9Eo_lvQVb!I&fn5SG@(uIv6ppOeb&v$qRP{H`}+fGz|~9Xd z^XDK*ibW89*nBV&ox8ht1&OizhrEecPXRjl-^s~?>E=1(<7KB#y(az6Z6c0 z4?G=<@42bXJBFiUtK&b2(yISbmzAYw;Bfx67q8S4vGi-LD643ou(Y;zSK{7xRYHmU zXue4doC3(tE<*3*3qBo1ZA(+b^|igZA~_p~h<-+v>W0Mj2tfSp$-KJjd^30)Gxx{! z=K-CgBO~gqW+b42Kf~qq=mL<)_;D9T?+Wpal$JkHo%6w*iREXzn-2dBP$({{9kBtEiQItIwc&Rk)kUp5kzpfZw?~ZZ(7e+c6U!=Lb)=@=S=A_{@GFc{2uCZdTD)z z)^qzCPnQdgGPp1T9*UMd7O1wbL`Pco@$dkpkBf_&CzFh^e6&;}o4eLfXSbk<7IePb z9a2G}qo^2Fw$Lb&A@7z?f8@!Hph-G;71hXq|+}6LQxqyfs_Fqq~#^WC8;SY(gMPo zuTV$Y!H_gyfQ_9UBu6+1aA;d{Y1Ax(t`o1$A_H$XruPNkKjDlaN5U zxag;*266+L^CA&Ete}g!y1rM-FTV~VEiDuj6bF8Ga)3|!CMGaES2IAx!jCj&G5z6U zrfF_xr>KY)^YH*UnM-Nyq$1c-zw-V)Df7%|pM-)!MP?>3Gmo~Qf^B??j<#j7p)g%R zSy@DIASDf5&X_5nr#kYM3PJ4t-c^1B1UxT`jkDp@PJ!D*EkeLOOF1OTT zO3qD2#l`eqS4e4mu|4D0FCGR4ISB;`9JnpEtF2mj%Fv)-YFbKRlf0LBh9vaD zu#u#gn3#Y^WTjR)l?fd(_P)KYsI7GxS#JL>DyoforSvIXoD_%I($rWK(H3Fz?8c0$ zz=5iEUhi6X`UVy7F+A2mGV+YQj&RP z2*()7{uKl&A3%>26BGOR_yx-5s>a4{nu!$^L9yXZRiQFcG9`tDQN{dS7s64$fB$}U zw8X*2hEiPtM2_dLP|%+tG`n4(dB68d?;+W41_2`URVY4`*%4(3a0Xo#xk(#{)!W`J zc>EsMBkNR3d|uaWYp45o`((qka)A+`XH!y9slw;Z25p29zLbQdtc1kdAmIh~^B?oK ze=D;rW^N1^sjfXoTMdOngfdu6bu8gM##teQ~LUrcgu4C z{NkUU(Y50FzW)T6HKtJ}AP)#GeM#6`IXS$*ovJWkSgL=NEBbyD``RP^pBO9d&^~t1 zHRZ!(H99oU!_UCaLegnnUJ?`}VW)@&4c5FRr-7hhZmtfb zQ7{19U+hogZa_49x%t7j$DtJkAiI`2>uzBY4p!D}-0cWEN6aJtr`lzvg(prI>AEW6 zZ~nZ$yj%0j#dURCnv5bEQd7ZYo~jk&Z3dmn=zzl?5>!2k;RBiZCeBBV;r{xQ)D=JK z|7hK3X*n-q50`kyF#nf+{Po89|0y6(k9Gg2F@s6QSGSOaQM^u%_-^oM^`n=l<1X^C z^M|;=wUN<(;KECt5`#HxtO4CltM31Z>hVuU%abqum%{%4{~^nG)b;4CO8$U{(Ov55UrR9=3h zb%iLami7JNbSuRYT^YE}+S(eRbU%=F+_&atnJ@?o3%j_xo0ynbn4AAe<#k`}3OaDp zubQEdOZB)q#E%=m=lrs~Ih4f4%Gx#7W@wx@4!@R!qprh|inj!p`YV#@V9d3bnO+1Oh3F+@Z} z5D^ji`1q=fhs>!ar>4@Fjrvd4dRp~EpPm770!%l8fOl6M1&hVvZ*g`uso}oYQ!!Eg zu9cvC!t+F{IIU{ZC(AkkX{?SNP1)KQAd4#kMJKjmFpYod&!5tnp&E;++FQw5_bY25 zq0Uy4<}WKB#l^*?3YwalLPDgHTu(gT%XZWSZ-5yR5D?_#krry-xU~c@ywgf0`#r;`1lkQ6i7$`muL&fSBDEpT=wKKlK%!&MJg3MZF#vw z#i?wC{NlyM#YNLH{@KvZNc>^IE5h$6xW7MF%r7iBIyjJnMY%mU`^-Qb38;gE?a{2u z%S&`}X_t%L$sg~9%+1YzI1H!q0&6leGJbJ(Heq~MTU$FiI(qPX(w439E#KGn@`{R^ zCsR1kTP^P9j6~&PR(ssg$^*ik5Mm=2DoMxyoPCtH-*NxT34A?eTDXF*~c-cPl~e&Ga7Z;!rEq=F;!@W-(O?)Km#K z?MlPGU#@4svnXVfdp}nsr=-{{G)@-eKiuE*c|V4JwgkHJ>3UFB)*3jCI|&}Dce^Nz ziBYk#vXUA|75H}J9gz=rezfF$zCF4+5QfV^P4&+w(Oq0` nq-@j){0cDASq1E|F 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 /* this means that this section is layed out in "flash" MEMORY */ + + /* assign the current location to a symbol called code_end */ + code_end = .; + + /* this is the output section ".data" which will be located in RAM. + * AT(X) means that this section is *loaded* at position X in memory, in this + * case: after our code in flash. + */ + .data : AT (code_end) + { + /* create a symbol called data_begin at the start of the data section */ + data_begin = .; + + /* include all things that are variables or have linksection(".data") */ + *(.data) + + /* same as above, but now at the end of the section */ + data_end = .; + } >ram /* this means that this section is layed out in "ram" MEMORY */ +} +``` + +This script includes all symbols from `.text` and `.data` in the final executable and assigns addresses from everything in `.text` to the flash memory and from everything in `.data` into the RAM. The section `.data` will have a different *load address* though: It is located directly behind `.text` and is not located at `0x10000000`. + +As most people have never heard of the *load address*, here's a short excourse: When linking a program, two things happen: + +1. Objects get assigned a address in memory (*link address*) +2. Objects get a position where they are loaded (*load address*) + +On a normal desktop program, these two are the same. In our embedded world, we need to "load" the RAM contents into flash though, as RAM cannot store the data. This means that when we link our program, the linker will treat everything in `.data` as it would be stored in RAM (and puts the symbol *address* there), but will actually store the data into the flash (and will store the bits there). + +With this linker script, we now know two things: Everything in `.data` must be at the address of `data_begin` (which is in RAM), but is still located at `code_end`. Thus, we have to copy the memory from flash to RAM: + +```zig +const std = @import("std"); + +// We can access symbols by declaring them as extern c_void +// and taking their address +extern var code_end: c_void; +extern var data_begin: c_void; +extern var data_end: c_void; + +extern fn _start() callconv(.Naked) noreturn { + // first, gather both source and destination addresses: + const src_ptr = @ptrCast([*]const u8, &code_end); + const dst_ptr = @ptrCast([*]u8, &data_begin); + + // then, compute the length of the .data section by + // just subtracting two pointers + const length = @ptrToInt(&data_end) - @ptrToInt(&data_start); + + // and finally, initialize .data: + std.mem.copy(u8, dst_ptr[0..length], src_ptr[0..length]); + + // call your program enty point here: + … +} +``` + +There's two sections i left out: +`.rodata`, which is just made the same way as `.text` and will reside in flash and `.bss` which is similar to `.data`, but doesn't have initial content and can just be set to zero with `std.mem.set(u8, bss_ptr[0..bss_length], 0)`. + +As you might have noticed, we have a function called `_start`. This is our programs entry point and *must never* return, otherwise **bad things** will happen (and arbitrary code will be executed). Make sure to always include some endless loop that disables interrupt for safety here! + +But you might wonder: How is this entry point called? This is very SOC-dependent and is explained in the respective tutorials for each SOC. The same is true for setting up the [stack pointer](https://en.wikipedia.org/wiki/Call_stack) which is required for calling functions and storing temporary variables. + +## Interacting with peripherials + +## Interrupts and how to get them + +## `.text`, `.data` and other curious sections + +## Binary formats you might use + +## Deploying your application \ No newline at end of file From f159621701c56a30f8836dba097ead74be2def7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 15 Mar 2021 18:21:07 +0100 Subject: [PATCH 16/49] Updates koino. --- deps/koino | 2 +- src/main.zig | 56 +++++++++++--------- website/tutorials/02-embedded-programming.md | 4 +- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/deps/koino b/deps/koino index 060611d..bd71384 160000 --- a/deps/koino +++ b/deps/koino @@ -1 +1 @@ -Subproject commit 060611d230cea7996b4518a80137b3e96319a293 +Subproject commit bd71384edb665ee6cfecb573296c23ac6d15a033 diff --git a/src/main.zig b/src/main.zig index e637d1f..3d8514f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -165,24 +165,6 @@ pub fn main() anyerror!void { } } -fn markdownToHtmlInternal(resultAllocator: *std.mem.Allocator, internalAllocator: *std.mem.Allocator, options: koino.Options, markdown: []const u8) ![]u8 { - var p = try koino.parser.Parser.init(internalAllocator, options); - try p.feed(markdown); - - var doc = try p.finish(); - p.deinit(); - - defer doc.deinit(); - - return try koino.html.print(resultAllocator, p.options, doc); -} - -pub fn markdownToHtml(allocator: *std.mem.Allocator, options: koino.Options, markdown: []const u8) ![]u8 { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - return markdownToHtmlInternal(allocator, &arena.allocator, options, markdown); -} - const Date = struct { const Self = @This(); @@ -286,7 +268,12 @@ const Website = struct { } else null; if (heading_or_null) |heading| { - const string = try koino.html.print(&self.arena.allocator, markdown_options, heading); + var list = std.ArrayList(u8).init(&self.arena.allocator); + defer list.deinit(); + + try koino.html.print(list.writer(), &self.arena.allocator, markdown_options, heading); + + const string = list.toOwnedSlice(); std.debug.assert(std.mem.startsWith(u8, string, "

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

\n")); @@ -441,7 +428,7 @@ const Website = struct { } /// 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 { + 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); @@ -451,13 +438,25 @@ const Website = struct { } /// Render the given markdown source into `dst_path`. - fn renderMarkdown(self: Self, source: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void { + fn renderMarkdown(self: *Self, source: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void { std.debug.assert(self.is_prepared); - var rendered_markdown = try markdownToHtml(self.allocator, markdown_options, source); - defer self.allocator.free(rendered_markdown); + var p = try koino.parser.Parser.init(&self.arena.allocator, markdown_options); + try p.feed(source); + + var doc = try p.finish(); + p.deinit(); + + defer doc.deinit(); - try self.renderHtml(rendered_markdown, dst_dir, dst_path); + var output_file = try dst_dir.createFile(dst_path, .{}); + defer output_file.close(); + + var writer = output_file.writer(); + + try self.renderHeader(writer); + try koino.html.print(writer, &self.arena.allocator, p.options, doc); + try self.renderFooter(writer); } /// Render the markdown body into `dst_path`. @@ -609,4 +608,13 @@ const Website = struct { ); } } + + 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(); + } }; diff --git a/website/tutorials/02-embedded-programming.md b/website/tutorials/02-embedded-programming.md index 9b6b7e9..5362598 100644 --- a/website/tutorials/02-embedded-programming.md +++ b/website/tutorials/02-embedded-programming.md @@ -4,6 +4,8 @@ In this tutorial, you'll learn the ways of the embedded programmer and how to ma ## Prerequisites + + - [Embedded Basics](01-embedded-basics.htm) ## Contents @@ -48,7 +50,7 @@ So to get an embedded program up and running, we first need to check out the *me ![Memory Map of LPC1768](../img/memory-map.png) -Here you can see that the memory contains continuous flash memory (*On-chip non-volatile memory*), two sections of SRAM (*On-chip SRAM*), some *Boot ROM*, and peripherials. +Here you can see that the memory contains continuous flash memory (*On-chip [non-volatile memory](https://en.wikipedia.org/wiki/Non-volatile_memory)*), two sections of SRAM (*On-chip [SRAM](https://en.wikipedia.org/wiki/Static_random-access_memory)*), some *Boot ROM*, and peripherials. This memory map tells us how to design the [linker script](https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_chapter/ld_3.html#SEC6) and how to lay out our sections (`.text`, `.data`, …). As sections are quite complex topic for themselves, they [will be explained later](#text-data-and-other-curious-sections). For now, we only need to know that `.text` is all of our code (this is where our functions live), `.rodata` is pre-initialized immutable data, `.data` is the pre-initialized mutable data and `.bss` is zero-initialized mutable data. From 61071f547dadfb06c90965bb231810bc21c178b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 15 Mar 2021 19:17:45 +0100 Subject: [PATCH 17/49] Adds proper TOC generation and HTML comment placeholders. --- src/main.zig | 188 +++++++++++++------ website/tutorials/01-embedded-basics.md | 39 ++-- website/tutorials/02-embedded-programming.md | 11 +- 3 files changed, 156 insertions(+), 82 deletions(-) diff --git a/src/main.zig b/src/main.zig index 3d8514f..96dc05d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -142,7 +142,7 @@ pub fn main() anyerror!void { var root_dir = try std.fs.cwd().makeOpenPath("render", .{}); defer root_dir.close(); - try website.renderIndexFile("website/index.md", root_dir, "index.htm"); + try website.renderMarkdownFile("website/index.md", root_dir, "index.htm"); try website.renderArticleIndex(root_dir, "articles.htm"); @@ -271,7 +271,10 @@ const Website = struct { var list = std.ArrayList(u8).init(&self.arena.allocator); defer list.deinit(); - try koino.html.print(list.writer(), &self.arena.allocator, markdown_options, heading); + var options = markdown_options; + options.render.header_anchors = false; + + try koino.html.print(list.writer(), &self.arena.allocator, options, heading); const string = list.toOwnedSlice(); @@ -373,58 +376,15 @@ const Website = struct { } } - /// Renders the root file and replaces `` with the first 10 articles, - /// in descending order - fn renderIndexFile(self: *Self, src_path: []const u8, dst_dir: std.fs.Dir, file_name: []const u8) !void { - std.debug.assert(self.is_prepared); - - var src_code = try std.fs.cwd().readFileAlloc(self.allocator, src_path, 10_000_000); - defer self.allocator.free(src_code); - - var array_buffer = std.ArrayList(u8).init(self.allocator); - defer array_buffer.deinit(); - - const offset = std.mem.indexOf(u8, src_code, "") orelse return error.MissingArticlesMarker; - - var writer = array_buffer.writer(); - - try writer.writeAll(src_code[0..offset]); - - for (self.articles.items[0..std.math.min(self.articles.items.len, 10)]) |art| { - try writer.print("- [{} - {s}](articles/{s}.htm)\n", .{ - art.date, - art.title, - try self.urlEscape(removeExtension(std.fs.path.basename(art.src_file))), - }); - } - - try writer.writeAll(src_code[offset + 17 ..]); - - try self.renderMarkdown(array_buffer.items, dst_dir, file_name); - } - - /// Renders the root file and replaces `` with the first 10 articles, - /// in descending order + /// 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); - var array_buffer = std.ArrayList(u8).init(self.allocator); - defer array_buffer.deinit(); - - var writer = array_buffer.writer(); - - try writer.writeAll("# Articles\n"); - try writer.writeAll("\n"); - - for (self.articles.items[0..std.math.min(self.articles.items.len, 10)]) |art| { - try writer.print("- [{} - {s}](articles/{s}.htm)\n", .{ - art.date, - art.title, - try self.urlEscape(removeExtension(std.fs.path.basename(art.src_file))), - }); - } - - try self.renderMarkdown(array_buffer.items, dst_dir, file_name); + try self.renderMarkdown( + \\# Articles + \\ + \\ + , dst_dir, file_name); } /// Render a given markdown file into `dst_path`. @@ -438,24 +398,136 @@ const Website = struct { } /// 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 p = try koino.parser.Parser.init(&self.arena.allocator, markdown_options); - try p.feed(source); - - var doc = try p.finish(); - p.deinit(); - + 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); - try koino.html.print(writer, &self.arena.allocator, p.options, doc); + { + 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("
    "); + + var it = item.next; + while (it) |child| : (it = child.next) { + if (child.data.value == .Heading) { + var heading = child.data.value.Heading; + + if (min_heading_level == null) { + min_heading_level = heading.level; + current_heading_level = heading.level; + } + + if (heading.level < min_heading_level.?) + continue; + + while (current_heading_level > heading.level) { + try writer.writeAll("
"); + current_heading_level -= 1; + } + while (current_heading_level < heading.level) { + try writer.writeAll("
    "); + current_heading_level += 1; + } + + try writer.writeAll("
  • "); + + { + var i = child.first_child; + while (i) |c| : (i = c.next) { + try koino.html.print( + writer, + &self.arena.allocator, + heading_options, + c, + ); + } + } + + 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"); + 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, + }); + } + try writer.writeAll("
    \n"); + } else { + std.log.err("Unhandled HTML inline: {s}", .{string}); + } + } else { + try koino.html.print( + writer, + &self.arena.allocator, + markdown_options, + item, + ); + } + } + } try self.renderFooter(writer); } diff --git a/website/tutorials/01-embedded-basics.md b/website/tutorials/01-embedded-basics.md index fe4229f..0bfda5c 100644 --- a/website/tutorials/01-embedded-basics.md +++ b/website/tutorials/01-embedded-basics.md @@ -10,20 +10,7 @@ None! This is your entry point into the embedded world! ## Contents -- [What are embedded systems?](#what-are-embedded-systems) -- Electronics 101 - - Current, voltage, power and all - - Diodes, Resistors, Capacitors - - Ohm's law - - LEDs and pre-resistors - - MosFETs -- Breadboard vs. Soldering -- Digital I/O -- Analog I/O -- Bus Systems - - UART - - SPI - - I²C / TWI + ## What are embedded systems? @@ -45,4 +32,28 @@ An important characteristic that's often required for an embedded system is "rea This is simply the ability for the system to respond to an input within a hard deadline, Eg. automatic breaks for a car. A general operating system like Linux is not suitable for these applications because it uses time sharing when scheduling tasks/programs, and unreliably responds to important signals. +## Electronics 101 +### Current, voltage, power and all + +### Diodes, Resistors, Capacitors + +### Ohm's law + +### LEDs and pre-resistors + +### MosFETs + +## Breadboard vs. Soldering + +## Digital I/O + +## Analog I/O + +## Bus Systems + +### UART + +### SPI + +### I²C / TWI \ No newline at end of file diff --git a/website/tutorials/02-embedded-programming.md b/website/tutorials/02-embedded-programming.md index 5362598..0c9fbde 100644 --- a/website/tutorials/02-embedded-programming.md +++ b/website/tutorials/02-embedded-programming.md @@ -4,20 +4,11 @@ In this tutorial, you'll learn the ways of the embedded programmer and how to ma ## Prerequisites - - - [Embedded Basics](01-embedded-basics.htm) ## Contents -- Differences to desktop programming -- Inventory of an embedded programmer -- The startup procedure -- Interacting with peripherials -- Interrupts and how to get them -- `.text`, `.data` and other curious sections -- Binary formats you might use -- Deploying your application + ## Differences to desktop programming From bcb71bfb644bcb77ab4151275b4881e663ceb0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 15 Mar 2021 19:24:34 +0100 Subject: [PATCH 18/49] Some readme. --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 61d47cc..3649136 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,34 @@ # Zig Embedded Group - Website and Articles +This project both contains the contents and the generation of the ZEG website. +## Folder Structure + +``` +. +├── build.zig +├── deps Contains submodule dependencies +│ └── … +├── LICENCE +├── README.md +├── render Not included in the repo, will contain the rendered website +│ └── … +├── src Source of the website generator and other tools +│ └── main.zig +└── website Contains the raw input data for the website + ├── articles Contains all articles in the format `YYYY-MM-dd - ${TITLE}.md` + │ └── … + ├── img Contains the images used on the website. + │ └── … + ├── index.md Index page of the website + └── tutorials Contains the raw tutorial files + └── … +``` + +## Markdown + +The website uses basic markdown that allows GFM style tables and also supports *some* placeholders: + +- `` will insert a table of contents if alone in a single line. The ToC will be rendered in the same depth as the next heading, so everything higher in the hierarchy will be ignored. +- `` renders a list of all available articles +- `` renders a list of the 10 latest articles From aff9a092bdb3063782a5be90d4ea398b857a86b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 15 Mar 2021 19:49:05 +0100 Subject: [PATCH 19/49] Adds anchor rendering. --- .gitmodules | 2 +- deps/koino | 2 +- src/main.zig | 15 ++++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 3fc6085..9aade95 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "deps/koino"] path = deps/koino - url = https://github.com/kivikakk/koino + url = https://github.com/MasterQ32/koino diff --git a/deps/koino b/deps/koino index bd71384..0be309f 160000 --- a/deps/koino +++ b/deps/koino @@ -1 +1 @@ -Subproject commit bd71384edb665ee6cfecb573296c23ac6d15a033 +Subproject commit 0be309f6c47582ecb8d8ca577c998b72226a1ecd diff --git a/src/main.zig b/src/main.zig index 96dc05d..80b40b3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,12 +9,12 @@ const markdown_options = koino.Options{ }, .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; @@ -596,6 +596,19 @@ const Website = struct { \\ padding-left: 0.5em; \\ margin-left: 0.5em; \\ } + \\ + // Make links in headings invisible + \\ h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { + \\ text-decoration: none; + \\ font-weight: lighter; + \\ color: unset; + \\ opacity: 50%; + \\ visibility: hidden; + \\ margin-left: -1em; + \\ } + \\ h1:hover a, h2:hover a, h3:hover a, h4:hover a, h5:hover a, h6:hover a { + \\ visibility: visible; + \\ } \\ \\ \\ From e49ab53acbc6745cb7015d47fe6fe854ce9fe944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 15 Mar 2021 20:04:24 +0100 Subject: [PATCH 20/49] Adds cross-linked TOC. --- deps/koino | 2 +- src/main.zig | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/deps/koino b/deps/koino index 0be309f..0f243e2 160000 --- a/deps/koino +++ b/deps/koino @@ -1 +1 @@ -Subproject commit 0be309f6c47582ecb8d8ca577c998b72226a1ecd +Subproject commit 0f243e26609be75fd4adb45653c6865a2fcb246e diff --git a/src/main.zig b/src/main.zig index 80b40b3..25260f9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -422,6 +422,9 @@ const Website = struct { 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) { @@ -460,7 +463,9 @@ const Website = struct { current_heading_level += 1; } - try writer.writeAll("
  • "); + try writer.writeAll("
  • "); { var i = child.first_child; @@ -473,6 +478,7 @@ const Website = struct { ); } } + try writer.writeAll(""); while (current_heading_level > heading.level) { try writer.writeAll(""); @@ -519,12 +525,7 @@ const Website = struct { std.log.err("Unhandled HTML inline: {s}", .{string}); } } else { - try koino.html.print( - writer, - &self.arena.allocator, - markdown_options, - item, - ); + try renderer.format(item, false); } } } From 5cd2378bf32ba89380572e9fed059f0f37c0e358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 15 Mar 2021 20:11:26 +0100 Subject: [PATCH 21/49] Improves anchors a bit. --- src/main.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.zig b/src/main.zig index 25260f9..afd88a6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -603,12 +603,12 @@ const Website = struct { \\ text-decoration: none; \\ font-weight: lighter; \\ color: unset; - \\ opacity: 50%; - \\ visibility: hidden; - \\ margin-left: -1em; + \\ opacity: 10%; + \\ margin-left: -1.5em; + \\ padding-left: 0.5em; \\ } \\ h1:hover a, h2:hover a, h3:hover a, h4:hover a, h5:hover a, h6:hover a { - \\ visibility: visible; + \\ opacity: 50%; \\ } \\ \\ From 6545cbd7e065adb04067024d0bde16dd34304a3c Mon Sep 17 00:00:00 2001 From: OBenjaminT <44924248+OBenjaminT@users.noreply.github.com> Date: Tue, 15 Feb 2022 12:50:18 +0100 Subject: [PATCH 22/49] Remove all non-existent content --- website/articles/2021-03-12 - async-await.md | 3 -- website/articles/2021-03-13 - black magic.md | 4 --- website/articles/2021-03-16 - replace-qmk.md | 3 -- website/articles/2021-04-10 - zCOM.md | 3 -- website/index.md | 34 ++++++-------------- website/tutorials/01-embedded-basics.md | 26 +-------------- website/tutorials/02-embedded-programming.md | 10 +----- website/tutorials/03-avr.md | 1 - website/tutorials/03-lpc1768.md | 1 - website/tutorials/03-nrf52.md | 9 ------ website/tutorials/03-pi-pico.md | 1 - website/tutorials/03-stm32.md | 1 - website/tutorials/04-chose-device.md | 7 ---- website/tutorials/05-hal.md | 1 - 14 files changed, 12 insertions(+), 92 deletions(-) delete mode 100644 website/articles/2021-03-12 - async-await.md delete mode 100644 website/articles/2021-03-13 - black magic.md delete mode 100644 website/articles/2021-03-16 - replace-qmk.md delete mode 100644 website/articles/2021-04-10 - zCOM.md delete mode 100644 website/tutorials/03-avr.md delete mode 100644 website/tutorials/03-lpc1768.md delete mode 100644 website/tutorials/03-nrf52.md delete mode 100644 website/tutorials/03-pi-pico.md delete mode 100644 website/tutorials/03-stm32.md delete mode 100644 website/tutorials/04-chose-device.md delete mode 100644 website/tutorials/05-hal.md diff --git a/website/articles/2021-03-12 - async-await.md b/website/articles/2021-03-12 - async-await.md deleted file mode 100644 index 2c6ddd6..0000000 --- a/website/articles/2021-03-12 - async-await.md +++ /dev/null @@ -1,3 +0,0 @@ -# `async`/`await` on embedded platforms - -Blabla this is a stub \ No newline at end of file diff --git a/website/articles/2021-03-13 - black magic.md b/website/articles/2021-03-13 - black magic.md deleted file mode 100644 index e95dcf9..0000000 --- a/website/articles/2021-03-13 - black magic.md +++ /dev/null @@ -1,4 +0,0 @@ -# Creating your own JTAG debugger - -Black Magic Probe: -https://paramaggarwal.medium.com/converting-an-stm32f103-board-to-a-black-magic-probe-c013cf2cc38c \ No newline at end of file diff --git a/website/articles/2021-03-16 - replace-qmk.md b/website/articles/2021-03-16 - replace-qmk.md deleted file mode 100644 index b09b36f..0000000 --- a/website/articles/2021-03-16 - replace-qmk.md +++ /dev/null @@ -1,3 +0,0 @@ -# Make your own keyboard with zig (and replace qmk) - -Blabla this is a stub \ No newline at end of file diff --git a/website/articles/2021-04-10 - zCOM.md b/website/articles/2021-04-10 - zCOM.md deleted file mode 100644 index e61f2c8..0000000 --- a/website/articles/2021-04-10 - zCOM.md +++ /dev/null @@ -1,3 +0,0 @@ -# zCOM, a network stack for embedded devices - -Blabla this is a stub \ No newline at end of file diff --git a/website/index.md b/website/index.md index 4da2466..3c07e4a 100644 --- a/website/index.md +++ b/website/index.md @@ -5,7 +5,8 @@ This group was formed to document and improve the embedded programming experienc ## ![](img/goals.svg) Goals - Provide documents on how to get started with embedded programming (for absolute newbies) -- Provide example snippets for certain architectures (LPC, STM32, AVR, ...) +- Provide example snippets for common opperations on certain architectures (LPC, STM32, AVR, ...) +- Provide example worked through embedded mini-projects - Create register definition libraries - Create a common interface/HAL over several architectures - Create a performant common set of drivers for external platforms @@ -14,19 +15,9 @@ This group was formed to document and improve the embedded programming experienc If you've never done any embedded development before, it's a good point to start with one of our tutorials: -- [Embedded Basics](tutorials/01-embedded-basics.htm) -- [Embedded Programming for Beginners](tutorials/02-embedded-programming.htm) -- Getting started with... - - [Arduino/AVR](tutorials/03-avr.htm) - - [LPC1768](tutorials/03-lpc1768.htm) - - [NRF52](tutorials/03-nrf52.htm) - - [Raspberry PI Pico](tutorials/03-pi-pico.htm) - - [STM32](tutorials/03-stm32.htm) - - [MSP430](#) (*missing*) - - [ESP2866/ESP32](#) (*missing*) - - [Raspberry PI](#) (*missing*) -- [What device to chose?](tutorials/04-chose-device.htm) -- [Introduction to HAL 9001](tutorials/05-hal.htm) +- (*WIP*) [Embedded Basics](tutorials/01-embedded-basics.htm): Aims to provide a basic understanding of the embedded environment. +- (*WIP*) [Embedded Programming for Beginners](tutorials/02-embedded-programming.htm): Aims to provide a basic understanding of embedded programming concepts. +- *Coming soon* ## ![](img/articles.svg) Latest Articles @@ -42,21 +33,16 @@ The latest articles on embedded programming with Zig: Here are some highlighted projects the ZEG provides: -- [HAL9001 - One HAL to rule them all](#) -- [STM32 Binding](#) -- [AVR Binding](#) -- [LPC1768 Binding](#) -- [ESP32 Binding](#) -- [zCOM Network Driver](#) -- [TinySSL](#) +- *Coming soon* -[![](img/read-more.svg) See all repositories...](https://github.com/ZigEmbeddedGroup/) +[![](img/read-more.svg) See all ZEG repositories...](https://github.com/ZigEmbeddedGroup/) ## ![](img/community.svg) Community -This group uses the already existing community infrastructures that exist for Zig: +This group uses the already existing [community infrastructures](https://github.com/ziglang/zig/wiki/Community) that exist for Zig. More specifically: -- [Zig Programming Language - Discord Server](https://discord.gg/TyzJXjser6) +- ZEG members are active on the unofficial Zig [Discord Server](https://discord.gg/TyzJXjser6) +- For more specific embedded advice and ZEG internal development there is a [ZEG discord server](https://discord.gg/zqa3fgv6Ma) ## ![](img/members.svg) Members diff --git a/website/tutorials/01-embedded-basics.md b/website/tutorials/01-embedded-basics.md index 0bfda5c..1ff6a3f 100644 --- a/website/tutorials/01-embedded-basics.md +++ b/website/tutorials/01-embedded-basics.md @@ -32,28 +32,4 @@ An important characteristic that's often required for an embedded system is "rea This is simply the ability for the system to respond to an input within a hard deadline, Eg. automatic breaks for a car. A general operating system like Linux is not suitable for these applications because it uses time sharing when scheduling tasks/programs, and unreliably responds to important signals. -## Electronics 101 - -### Current, voltage, power and all - -### Diodes, Resistors, Capacitors - -### Ohm's law - -### LEDs and pre-resistors - -### MosFETs - -## Breadboard vs. Soldering - -## Digital I/O - -## Analog I/O - -## Bus Systems - -### UART - -### SPI - -### I²C / TWI \ No newline at end of file +## MORE COMING SOON \ No newline at end of file diff --git a/website/tutorials/02-embedded-programming.md b/website/tutorials/02-embedded-programming.md index 0c9fbde..1c67a02 100644 --- a/website/tutorials/02-embedded-programming.md +++ b/website/tutorials/02-embedded-programming.md @@ -138,12 +138,4 @@ As you might have noticed, we have a function called `_start`. This is our progr But you might wonder: How is this entry point called? This is very SOC-dependent and is explained in the respective tutorials for each SOC. The same is true for setting up the [stack pointer](https://en.wikipedia.org/wiki/Call_stack) which is required for calling functions and storing temporary variables. -## Interacting with peripherials - -## Interrupts and how to get them - -## `.text`, `.data` and other curious sections - -## Binary formats you might use - -## Deploying your application \ No newline at end of file +## MORE COMING SOON \ No newline at end of file diff --git a/website/tutorials/03-avr.md b/website/tutorials/03-avr.md deleted file mode 100644 index 5ce4d0a..0000000 --- a/website/tutorials/03-avr.md +++ /dev/null @@ -1 +0,0 @@ -# Getting started with Arduino/AVR \ No newline at end of file diff --git a/website/tutorials/03-lpc1768.md b/website/tutorials/03-lpc1768.md deleted file mode 100644 index 77ef59e..0000000 --- a/website/tutorials/03-lpc1768.md +++ /dev/null @@ -1 +0,0 @@ -# Getting started with LPC1768 \ No newline at end of file diff --git a/website/tutorials/03-nrf52.md b/website/tutorials/03-nrf52.md deleted file mode 100644 index 8a716d2..0000000 --- a/website/tutorials/03-nrf52.md +++ /dev/null @@ -1,9 +0,0 @@ -# Getting started with NRF52 - -Used hardware: nRF52840 Dongle - -https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle/GetStarted - -https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-desktop - -https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nc_programmer%2FUG%2Fnrf_connect_programmer%2Fncp_programming_dongle.html \ No newline at end of file diff --git a/website/tutorials/03-pi-pico.md b/website/tutorials/03-pi-pico.md deleted file mode 100644 index ed46e0a..0000000 --- a/website/tutorials/03-pi-pico.md +++ /dev/null @@ -1 +0,0 @@ -# Getting started with Raspberry Pi Pico \ No newline at end of file diff --git a/website/tutorials/03-stm32.md b/website/tutorials/03-stm32.md deleted file mode 100644 index 0f7d792..0000000 --- a/website/tutorials/03-stm32.md +++ /dev/null @@ -1 +0,0 @@ -# Getting started with STM32 \ No newline at end of file diff --git a/website/tutorials/04-chose-device.md b/website/tutorials/04-chose-device.md deleted file mode 100644 index 1c8509a..0000000 --- a/website/tutorials/04-chose-device.md +++ /dev/null @@ -1,7 +0,0 @@ -# What device to chose? - -Consider: -- Project -- Experience -- Personal taste -- Price \ No newline at end of file diff --git a/website/tutorials/05-hal.md b/website/tutorials/05-hal.md deleted file mode 100644 index 2bddb7d..0000000 --- a/website/tutorials/05-hal.md +++ /dev/null @@ -1 +0,0 @@ -# Introduction to HAL 9001 \ No newline at end of file From c8de6a8668b7fba89b4640a68ef48c892cdeead3 Mon Sep 17 00:00:00 2001 From: OBenjaminT <44924248+OBenjaminT@users.noreply.github.com> Date: Tue, 15 Feb 2022 13:31:50 +0100 Subject: [PATCH 23/49] WIP todo list --- WIP.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 WIP.md diff --git a/WIP.md b/WIP.md new file mode 100644 index 0000000..e9a8dc9 --- /dev/null +++ b/WIP.md @@ -0,0 +1,43 @@ +# Planned/TODO articles + +## Work in progress + +These have either incomplete content on the website or a branch where they are being written. + +- Tutorials + - 01-embedded-basics + - 02-embedded-programming +- Articles + - NONE + +## TODO + +Things that should be written eventually. + +- Tutorials + - Getting started with: + - Arduino/AVR + - LPC1768 + - NRF52 + - Hardware: nRF52840 Dongle + - https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle/GetStarted + - https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-desktop + - https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nc_programmer%2FUG%2Fnrf_connect_programmer%2Fncp_programming_dongle.html + - Raspberry Pi Pico + - STM32 + - What device to chose? + - Introduction to HAL 9001 +- Articles + - `async`/`await` on embedded platforms + - Creating your own JTAG debugger + - Black Magic Probe + - https://paramaggarwal.medium.com/converting-an-stm32f103-board-to-a-black-magic-probe-c013cf2cc38c + - zCOM, a network stack for embedded devices + +## Ideas + +Ideas for things to write that would be great to do eventually or projects to write up about. + +- Tutorials +- Articles + - Make your own keyboard with zig (and replace qmk) From 5cb8cf60e4ae177fdb2cd1d1ff97fc76e464b5d7 Mon Sep 17 00:00:00 2001 From: Leap of Azzam Date: Mon, 14 Mar 2022 23:03:04 +0700 Subject: [PATCH 24/49] make website looks good on mobile --- src/main.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.zig b/src/main.zig index afd88a6..2305960 100644 --- a/src/main.zig +++ b/src/main.zig @@ -565,6 +565,11 @@ const Website = struct { \\ font-family: sans; \\ } \\ + \\ @media screen and (max-width: 600px) { + \\ body { + \\ padding: 2em; + \\ } + \\ } // Align top-level headings \\ h1 { \\ text-align: center; From f1ca55f6c384dbb729e8991c48db84410c241270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Mon, 14 Mar 2022 18:04:01 +0100 Subject: [PATCH 25/49] Update to latest master. --- build.zig | 10 +++++----- deps/koino | 2 +- src/main.zig | 47 +++++++++++++++++++++++++---------------------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/build.zig b/build.zig index 8056841..5c9de42 100644 --- a/build.zig +++ b/build.zig @@ -3,12 +3,12 @@ const std = @import("std"); const pkgs = struct { const koino = std.build.Pkg{ .name = "koino", - .path = "./deps/koino/src/koino.zig", + .path = .{ .path = "./deps/koino/src/koino.zig" }, .dependencies = &[_]std.build.Pkg{ - std.build.Pkg{ .name = "libpcre", .path = "deps/koino/vendor/libpcre.zig/src/main.zig" }, - std.build.Pkg{ .name = "htmlentities", .path = "deps/koino/vendor/htmlentities.zig/src/main.zig" }, - std.build.Pkg{ .name = "clap", .path = "deps/koino/vendor/zig-clap/clap.zig" }, - std.build.Pkg{ .name = "zunicode", .path = "deps/koino/vendor/zunicode/src/zunicode.zig" }, + std.build.Pkg{ .name = "libpcre", .path = .{ .path = "deps/koino/vendor/libpcre.zig/src/main.zig" } }, + std.build.Pkg{ .name = "htmlentities", .path = .{ .path = "deps/koino/vendor/htmlentities.zig/src/main.zig" } }, + std.build.Pkg{ .name = "clap", .path = .{ .path = "deps/koino/vendor/zig-clap/clap.zig" } }, + std.build.Pkg{ .name = "zunicode", .path = .{ .path = "deps/koino/vendor/zunicode/src/zunicode.zig" } }, }, }; }; diff --git a/deps/koino b/deps/koino index 0f243e2..a3179c9 160000 --- a/deps/koino +++ b/deps/koino @@ -1 +1 @@ -Subproject commit 0f243e26609be75fd4adb45653c6865a2fcb246e +Subproject commit a3179c9316926f71582d8278af4e2f6f912cbc71 diff --git a/src/main.zig b/src/main.zig index 2305960..48ce11f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -33,7 +33,7 @@ pub fn main() anyerror!void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); - const allocator = &gpa.allocator; + const allocator = gpa.allocator(); var website = Website{ .allocator = allocator, @@ -91,7 +91,7 @@ pub fn main() anyerror!void { continue; } - const path = try std.fs.path.join(&website.arena.allocator, &[_][]const u8{ + const path = try std.fs.path.join(website.arena.allocator(), &[_][]const u8{ "website", "img", entry.name, @@ -124,7 +124,7 @@ pub fn main() anyerror!void { .date = date, }; - article.src_file = try std.fs.path.join(&website.arena.allocator, &[_][]const u8{ + article.src_file = try std.fs.path.join(website.arena.allocator(), &[_][]const u8{ "website", "articles", entry.name, @@ -185,6 +185,8 @@ const Date = struct { } 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, }); @@ -206,7 +208,7 @@ const Website = struct { const Self = @This(); is_prepared: bool = false, - allocator: *std.mem.Allocator, + allocator: std.mem.Allocator, arena: std.heap.ArenaAllocator, articles: std.ArrayList(Article), tutorials: std.ArrayList(Tutorial), @@ -224,22 +226,22 @@ const Website = struct { 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), + .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), + .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)); + try self.images.append(try self.arena.allocator().dupe(u8, path)); } fn findTitle(self: *Self, file: []const u8) !?[]const u8 { @@ -268,13 +270,13 @@ const Website = struct { } else null; if (heading_or_null) |heading| { - var list = std.ArrayList(u8).init(&self.arena.allocator); + 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); + try koino.html.print(list.writer(), self.arena.allocator(), options, heading); const string = list.toOwnedSlice(); @@ -306,6 +308,7 @@ const Website = struct { } fn sortArticlesDesc(self: Self, lhs: Article, rhs: Article) bool { + _ = self; if (lhs.date.lessThan(rhs.date)) return false; if (rhs.date.lessThan(lhs.date)) @@ -319,7 +322,7 @@ const Website = struct { } fn changeExtension(self: *Self, src_name: []const u8, new_ext: []const u8) ![]const u8 { - return std.mem.join(&self.arena.allocator, "", &[_][]const u8{ + return std.mem.join(self.arena.allocator(), "", &[_][]const u8{ removeExtension(src_name), new_ext, }); @@ -336,7 +339,7 @@ const Website = struct { @as(usize, 1); } - const buf = try self.arena.allocator.alloc(u8, len); + 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) { @@ -406,7 +409,7 @@ const Website = struct { std.debug.assert(self.is_prepared); var doc: *koino.nodes.AstNode = blk: { - var p = try koino.parser.Parser.init(&self.arena.allocator, markdown_options); + var p = try koino.parser.Parser.init(self.arena.allocator(), markdown_options); try p.feed(source); defer p.deinit(); break :blk try p.finish(); @@ -422,7 +425,7 @@ const Website = struct { try self.renderHeader(writer); { - var renderer = koino.html.makeHtmlFormatter(writer, &self.arena.allocator, markdown_options); + var renderer = koino.html.makeHtmlFormatter(writer, self.arena.allocator(), markdown_options); defer renderer.deinit(); var iter = doc.first_child; @@ -472,7 +475,7 @@ const Website = struct { while (i) |c| : (i = c.next) { try koino.html.print( writer, - &self.arena.allocator, + self.arena.allocator(), heading_options, c, ); @@ -700,12 +703,12 @@ const Website = struct { } } - 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(); + // 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); + // try formatter.format(root, false); - return formatter.buffer.toOwnedSlice(); - } + // return formatter.buffer.toOwnedSlice(); + // } }; From b01bb102caace0981f4205ae6b396e8c3da19954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Mon, 14 Mar 2022 18:09:46 +0100 Subject: [PATCH 26/49] Fixes some tiny fixes. --- src/main.zig | 46 ++++++++++++++++++++++-------------------- website/articles/.keep | 0 2 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 website/articles/.keep diff --git a/src/main.zig b/src/main.zig index 48ce11f..5c23322 100644 --- a/src/main.zig +++ b/src/main.zig @@ -56,27 +56,27 @@ pub fn main() anyerror!void { 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", - }); + // 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 { @@ -114,7 +114,8 @@ pub fn main() anyerror!void { } const date = isValidArticleFileName(entry.name) orelse { - std.log.err("Illegal file name in directory website/articles: {s}", .{entry.name}); + if (!std.mem.eql(u8, entry.name, ".keep")) + std.log.err("Illegal file name in directory website/articles: {s}", .{entry.name}); continue; }; @@ -299,6 +300,7 @@ const Website = struct { } for (self.tutorials.items) |*tutorial| { + std.debug.print("{s}\n", .{tutorial.src_file}); if (try self.findTitle(tutorial.src_file)) |title| { tutorial.title = title; } diff --git a/website/articles/.keep b/website/articles/.keep new file mode 100644 index 0000000..e69de29 From efa6921225b4185493d4c6f8116d69f27163ce6e Mon Sep 17 00:00:00 2001 From: Auguste Rame Date: Mon, 30 May 2022 17:41:27 -0400 Subject: [PATCH 27/49] Start of website styling changes (redesigned index) --- build.zig | 10 +-- src/main.zig | 77 ++++--------------- src/style.css | 172 ++++++++++++++++++++++++++++++++++++++++++ website/img/ember.svg | 70 +++++++++++++++++ website/index.htm | 67 ++++++++++++++++ website/index.md | 53 ------------- 6 files changed, 329 insertions(+), 120 deletions(-) create mode 100644 src/style.css create mode 100644 website/img/ember.svg create mode 100644 website/index.htm delete mode 100644 website/index.md diff --git a/build.zig b/build.zig index 5c9de42..b0a0cd2 100644 --- a/build.zig +++ b/build.zig @@ -3,12 +3,12 @@ const std = @import("std"); const pkgs = struct { const koino = std.build.Pkg{ .name = "koino", - .path = .{ .path = "./deps/koino/src/koino.zig" }, + .source = .{ .path = "./deps/koino/src/koino.zig" }, .dependencies = &[_]std.build.Pkg{ - std.build.Pkg{ .name = "libpcre", .path = .{ .path = "deps/koino/vendor/libpcre.zig/src/main.zig" } }, - std.build.Pkg{ .name = "htmlentities", .path = .{ .path = "deps/koino/vendor/htmlentities.zig/src/main.zig" } }, - std.build.Pkg{ .name = "clap", .path = .{ .path = "deps/koino/vendor/zig-clap/clap.zig" } }, - std.build.Pkg{ .name = "zunicode", .path = .{ .path = "deps/koino/vendor/zunicode/src/zunicode.zig" } }, + std.build.Pkg{ .name = "libpcre", .source = .{ .path = "deps/koino/vendor/libpcre.zig/src/main.zig" } }, + std.build.Pkg{ .name = "htmlentities", .source = .{ .path = "deps/koino/vendor/htmlentities.zig/src/main.zig" } }, + std.build.Pkg{ .name = "clap", .source = .{ .path = "deps/koino/vendor/zig-clap/clap.zig" } }, + std.build.Pkg{ .name = "zunicode", .source = .{ .path = "deps/koino/vendor/zunicode/src/zunicode.zig" } }, }, }; }; diff --git a/src/main.zig b/src/main.zig index 5c23322..ff5a043 100644 --- a/src/main.zig +++ b/src/main.zig @@ -143,7 +143,9 @@ pub fn main() anyerror!void { var root_dir = try std.fs.cwd().makeOpenPath("render", .{}); defer root_dir.close(); - try website.renderMarkdownFile("website/index.md", root_dir, "index.htm"); + try root_dir.writeFile("style.css", @embedFile("style.css")); + + try website.renderHtmlFile("website/index.htm", root_dir, "index.htm"); try website.renderArticleIndex(root_dir, "articles.htm"); @@ -537,7 +539,17 @@ const Website = struct { try self.renderFooter(writer); } - /// Render the markdown body into `dst_path`. + /// 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); @@ -561,66 +573,7 @@ const Website = struct { \\ \\ \\ ZEG - \\ + \\ \\ \\ ); diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..6e0aec8 --- /dev/null +++ b/src/style.css @@ -0,0 +1,172 @@ +/* Limit the text width of the body to roughly 40 characters +body { + max-width: 40em; + margin-left: auto; + margin-right: auto; + font-family: sans; +} + +@media screen and (max-width: 600px) { + body { + padding: 2em; + } +} +// Align top-level headings +h1 { + text-align: center; +} + +// Make images in headings and links exactly 1 character high. +h1 img, h2 img, h3 img, h3 img, h4 img, h5 img, h6 img, a img { + width: 1em; + height: 1em; + vertical-align: middle; +} + +// center images in a paragraph and display them as a block +p > img { + display: block; + max-width: 100%; + margin-left: auto; + margin-right: auto; +} + +// Make nice top-level codeblocks +body > pre { + background-color: #EEE; + padding: 0.5em; +} + +// Make nice top-level blockquotes +body > blockquote { + border-left: 3pt solid cornflowerblue; + padding-left: 0.5em; + margin-left: 0.5em; +} + +// Make links in headings invisible +h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { + text-decoration: none; + font-weight: lighter; + color: unset; + opacity: 10%; + margin-left: -1.5em; + padding-left: 0.5em; +} +h1:hover a, h2:hover a, h3:hover a, h4:hover a, h5:hover a, h6:hover a { + opacity: 50%; +}*/ + +@import"https://rsms.me/inter/inter.css"; + +html { + font-family: "Inter", "Arial", sans-serif +} + +@supports(font-variation-settings: normal) { + html { + font-family: "Inter var", "Arial", sans-serif + } +} + +* { + box-sizing: border-box +} + +html, +body { + margin: 0 +} + +h1 { + margin: 0; + font-size: 24pt; + font-weight: 700; + letter-spacing: -2px +} + +h2 { + font-size: 18pt; + font-weight: 600 +} + +ul.bars { + margin: 0; + padding: 0; + list-style: none +} + +ul.bars li { + border-bottom: 1px solid gray; + margin-bottom: 15px; + padding-bottom: 15px; + font-size: 12pt +} + +a { + color: #5c8ebf +} + +strong, +strong a { + color: #f7a41d +} + +#intro { + padding: 80px +} + +#intro-nav { + display: flex; + margin-bottom: 20px; + justify-content: space-between +} + +#intro-grid { + display: grid; + gap: 40px; + grid-auto-rows: 1fr; + grid-template-columns: 1fr 1fr 1fr +} + +@media only screen and (max-width: 1200px) { + #intro-grid { + grid-template-columns: 1fr 1fr + } +} + +@media only screen and (max-width: 700px) { + #intro-grid { + display: block + } + + #intro-grid>* { + display: block; + margin-bottom: 40px + } +} + +#intro-grid a { + text-decoration: none +} + +#docs { + display: flex +} + +#docs-nav { + border-right: 1px solid gray; + padding: 40px 0px; + width: 20% +} + +#docs-nav-header { + display: flex; + align-items: center; + flex-direction: column +} + +#docs-body { + width: 80%; + padding: 40px +} diff --git a/website/img/ember.svg b/website/img/ember.svg new file mode 100644 index 0000000..18aa542 --- /dev/null +++ b/website/img/ember.svg @@ -0,0 +1,70 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/website/index.htm b/website/index.htm new file mode 100644 index 0000000..62b6119 --- /dev/null +++ b/website/index.htm @@ -0,0 +1,67 @@ +
    + + +
    +
    +

    goals

    + +
      +
    • provide documents on how to get started with embedded programming (for absolute newbies)
    • +
    • provide example snippets for common operations on certain architectures (LPC, STM32, AVR, ...)
    • +
    • provide example worked through embedded mini-projects
    • +
    • create register definition libraries
    • +
    • create a common interface/HAL over several architectures
    • +
    • create a performant common set of drivers for external platforms
    • +
    +
    + +
    +

    important links

    + + +
    + + + +
    +

    articles

    + + +
    + + +
    + +
    + vectorized Ember, the awesome zeg mascot! +
    +
    \ No newline at end of file diff --git a/website/index.md b/website/index.md deleted file mode 100644 index 3c07e4a..0000000 --- a/website/index.md +++ /dev/null @@ -1,53 +0,0 @@ -# ![](img/pager.svg) Zig Embedded Group ![](img/battery.svg) - -This group was formed to document and improve the embedded programming experience with the [Zig programming language](https://ziglang.org). - -## ![](img/goals.svg) Goals - -- Provide documents on how to get started with embedded programming (for absolute newbies) -- Provide example snippets for common opperations on certain architectures (LPC, STM32, AVR, ...) -- Provide example worked through embedded mini-projects -- Create register definition libraries -- Create a common interface/HAL over several architectures -- Create a performant common set of drivers for external platforms - -## ![](img/teacher.svg) Introduction to embedded programming - -If you've never done any embedded development before, it's a good point to start with one of our tutorials: - -- (*WIP*) [Embedded Basics](tutorials/01-embedded-basics.htm): Aims to provide a basic understanding of the embedded environment. -- (*WIP*) [Embedded Programming for Beginners](tutorials/02-embedded-programming.htm): Aims to provide a basic understanding of embedded programming concepts. -- *Coming soon* - -## ![](img/articles.svg) Latest Articles - -The latest articles on embedded programming with Zig: - - - -[![](img/read-more.svg) See all articles...](articles.htm) - -[![Atom Feed](img/atom.svg) Subscribe the Atom feed](feed.atom) - -## ![](img/code.svg) Code - -Here are some highlighted projects the ZEG provides: - -- *Coming soon* - -[![](img/read-more.svg) See all ZEG repositories...](https://github.com/ZigEmbeddedGroup/) - -## ![](img/community.svg) Community - -This group uses the already existing [community infrastructures](https://github.com/ziglang/zig/wiki/Community) that exist for Zig. More specifically: - -- ZEG members are active on the unofficial Zig [Discord Server](https://discord.gg/TyzJXjser6) -- For more specific embedded advice and ZEG internal development there is a [ZEG discord server](https://discord.gg/zqa3fgv6Ma) - -## ![](img/members.svg) Members - -- [Felix "xq" Queißner](https://github.com/MasterQ32/) -- [Matthew "mattnite" Knight](https://github.com/mattnite/) -- [Vesim](https://github.com/vesim987/) -- [Timon "FireFox317" Kruiper](https://github.com/FireFox317) -- [Martin "SpexGuy" Wickham](https://github.com/SpexGuy) From 04294682f5b3df084d971011624aee0f8afa3212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 11:07:02 +0200 Subject: [PATCH 28/49] Update to new @SuperAuguste style. --- .gitignore | 3 ++- build.zig | 7 ++--- deps/koino | 2 +- ember-icon.xcf | Bin 0 -> 8932 bytes src/main.zig | 21 ++++++++++++--- src/style.css | 16 ++++++++++- website/favicon.ico | Bin 0 -> 34494 bytes website/getting-started.htm | 51 ++++++++++++++++++++++++++++++++++++ website/index.htm | 32 +++++++++++----------- 9 files changed, 107 insertions(+), 25 deletions(-) create mode 100644 ember-icon.xcf create mode 100644 website/favicon.ico create mode 100644 website/getting-started.htm diff --git a/.gitignore b/.gitignore index b14c7fc..f2d5b30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ zig-cache/ -render/ \ No newline at end of file +render/ +zig-out/ diff --git a/build.zig b/build.zig index b0a0cd2..4b2f483 100644 --- a/build.zig +++ b/build.zig @@ -5,15 +5,15 @@ const pkgs = struct { .name = "koino", .source = .{ .path = "./deps/koino/src/koino.zig" }, .dependencies = &[_]std.build.Pkg{ - std.build.Pkg{ .name = "libpcre", .source = .{ .path = "deps/koino/vendor/libpcre.zig/src/main.zig" } }, - std.build.Pkg{ .name = "htmlentities", .source = .{ .path = "deps/koino/vendor/htmlentities.zig/src/main.zig" } }, + std.build.Pkg{ .name = "libpcre", .source = .{ .path = "deps/koino/vendor/libpcre/src/main.zig" } }, + std.build.Pkg{ .name = "htmlentities", .source = .{ .path = "deps/koino/vendor/htmlentities/src/main.zig" } }, std.build.Pkg{ .name = "clap", .source = .{ .path = "deps/koino/vendor/zig-clap/clap.zig" } }, std.build.Pkg{ .name = "zunicode", .source = .{ .path = "deps/koino/vendor/zunicode/src/zunicode.zig" } }, }, }; }; -const linkPcre = @import("deps/koino/vendor/libpcre.zig/build.zig").linkPcre; +const linkPcre = @import("deps/koino/vendor/libpcre/build.zig").linkPcre; pub fn build(b: *std.build.Builder) !void { const target = b.standardTargetOptions(.{}); @@ -24,6 +24,7 @@ pub fn build(b: *std.build.Builder) !void { render_website.setBuildMode(mode); try linkPcre(render_website); render_website.addPackage(pkgs.koino); + render_website.install(); const gen_cmd = render_website.run(); diff --git a/deps/koino b/deps/koino index a3179c9..5ea268b 160000 --- a/deps/koino +++ b/deps/koino @@ -1 +1 @@ -Subproject commit a3179c9316926f71582d8278af4e2f6f912cbc71 +Subproject commit 5ea268b50116f2f06bfa9b83f24324ec8c211be1 diff --git a/ember-icon.xcf b/ember-icon.xcf new file mode 100644 index 0000000000000000000000000000000000000000..7b1db9fb3541a84cb6f451e2784855181ca38321 GIT binary patch literal 8932 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 literal 0 HcmV?d00001 diff --git a/src/main.zig b/src/main.zig index ff5a043..6a437e4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -80,7 +80,7 @@ pub fn main() anyerror!void { // img articles { - var dir = try root_dir.openDir("img", .{ .iterate = true }); + var dir = try root_dir.openIterableDir("img", .{}); defer dir.close(); var iter = dir.iterate(); @@ -103,7 +103,7 @@ pub fn main() anyerror!void { // gather articles { - var dir = try root_dir.openDir("articles", .{ .iterate = true }); + var dir = try root_dir.openIterableDir("articles", .{}); defer dir.close(); var iter = dir.iterate(); @@ -143,9 +143,24 @@ pub fn main() anyerror!void { var root_dir = try std.fs.cwd().makeOpenPath("render", .{}); defer root_dir.close(); - try root_dir.writeFile("style.css", @embedFile("style.css")); + 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"); diff --git a/src/style.css b/src/style.css index 6e0aec8..72a7fba 100644 --- a/src/style.css +++ b/src/style.css @@ -57,7 +57,8 @@ h1:hover a, h2:hover a, h3:hover a, h4:hover a, h5:hover a, h6:hover a { opacity: 50%; }*/ -@import"https://rsms.me/inter/inter.css"; +@import "https://rsms.me/inter/inter.css"; +@import "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"; html { font-family: "Inter", "Arial", sans-serif @@ -170,3 +171,16 @@ strong a { width: 80%; padding: 40px } + +a[href^="http"]::after, +a[href^="https://"]::after { + content: ""; + width: 11px; + height: 11px; + margin-left: 4px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' stroke='%235c8ebf' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E"); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + display: inline-block; +} \ No newline at end of file diff --git a/website/favicon.ico b/website/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..af56b80080e1ca3ba0f552bd168acc625fd0f511 GIT binary patch literal 34494 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 QVt9sQj66M5?3?}mA1|@KivR!s literal 0 HcmV?d00001 diff --git a/website/getting-started.htm b/website/getting-started.htm new file mode 100644 index 0000000..20468b5 --- /dev/null +++ b/website/getting-started.htm @@ -0,0 +1,51 @@ + diff --git a/website/index.htm b/website/index.htm index 62b6119..6665301 100644 --- a/website/index.htm +++ b/website/index.htm @@ -7,7 +7,7 @@

    goals

    - +
    • provide documents on how to get started with embedded programming (for absolute newbies)
    • provide example snippets for common operations on certain architectures (LPC, STM32, AVR, ...)
    • @@ -17,14 +17,13 @@
    • create a performant common set of drivers for external platforms
    - + @@ -32,25 +31,26 @@

    community

    -

    members

    - +

    core members

    +
    \ No newline at end of file + From 1d22b8cfc6454ba87771efe81f6ba9f6e48a5097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 11:42:24 +0200 Subject: [PATCH 29/49] Swaps the two discord server positions. --- website/index.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/index.htm b/website/index.htm index 6665301..36a5f96 100644 --- a/website/index.htm +++ b/website/index.htm @@ -31,8 +31,8 @@

    community

    From dc0cf23626fa25e6bf903f1180d4fc5806deeb48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 12:21:41 +0200 Subject: [PATCH 30/49] Adds IRC link, and chat logos. --- src/style.css | 7 +++++++ website/img/chat-discord.svg | 10 ++++++++++ website/img/chat-irc.svg | 4 ++++ website/index.htm | 13 +++++++++++-- 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 website/img/chat-discord.svg create mode 100644 website/img/chat-irc.svg diff --git a/src/style.css b/src/style.css index 72a7fba..c61963f 100644 --- a/src/style.css +++ b/src/style.css @@ -183,4 +183,11 @@ a[href^="https://"]::after { background-repeat: no-repeat; background-size: contain; display: inline-block; +} + +img.inline { + display: inline; + max-height: 1.2em; + max-width: 1.2em; + vertical-align: middle; } \ No newline at end of file diff --git a/website/img/chat-discord.svg b/website/img/chat-discord.svg new file mode 100644 index 0000000..3efe1ec --- /dev/null +++ b/website/img/chat-discord.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/website/img/chat-irc.svg b/website/img/chat-irc.svg new file mode 100644 index 0000000..62ff396 --- /dev/null +++ b/website/img/chat-irc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/website/index.htm b/website/index.htm index 36a5f96..81b1b4c 100644 --- a/website/index.htm +++ b/website/index.htm @@ -31,8 +31,17 @@

    community

    From f00ee4199b6d430b9608adb5d2848f5c0ef53da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 17:07:43 +0200 Subject: [PATCH 31/49] Adds some more resources --- website/getting-started.htm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/website/getting-started.htm b/website/getting-started.htm index 20468b5..d273639 100644 --- a/website/getting-started.htm +++ b/website/getting-started.htm @@ -30,6 +30,17 @@ +
    +

    generic zig learning material

    + + +
    +

    external tutorials

    From 96f130e9f2b565cab0b7208f251ec1f7bebd577a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 17:07:49 +0200 Subject: [PATCH 32/49] Adds icons to the front matter. --- src/style.css | 8 ++++++++ website/index.htm | 21 +++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/style.css b/src/style.css index c61963f..73db4ad 100644 --- a/src/style.css +++ b/src/style.css @@ -190,4 +190,12 @@ img.inline { max-height: 1.2em; max-width: 1.2em; vertical-align: middle; +} + +h2 svg { + display: inline; + max-height: 1.1em; + vertical-align: middle; + max-width: 1.1em; + margin-right: 0.25em; } \ No newline at end of file diff --git a/website/index.htm b/website/index.htm index 81b1b4c..097f053 100644 --- a/website/index.htm +++ b/website/index.htm @@ -6,7 +6,10 @@
    -

    goals

    +

    + + goals

    • provide documents on how to get started with embedded programming (for absolute newbies)
    • @@ -19,16 +22,23 @@
    -

    important links

    +

    + + important links

    -

    community

    +

    + + community

    -

    projects

    +

    + + projects

    • microzig - Cross-Device Embedded From f71b45f5f1970ab688280d8f5e53f40b93f6fa14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 17:13:54 +0200 Subject: [PATCH 33/49] Improves mobile view. --- src/style.css | 8 ++++++++ website/getting-started.htm | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/style.css b/src/style.css index 73db4ad..5829711 100644 --- a/src/style.css +++ b/src/style.css @@ -131,12 +131,20 @@ strong a { } @media only screen and (max-width: 1200px) { + #intro { + padding: 50px; + } + #intro-grid { grid-template-columns: 1fr 1fr } } @media only screen and (max-width: 700px) { + #intro { + padding: 20px; + } + #intro-grid { display: block } diff --git a/website/getting-started.htm b/website/getting-started.htm index d273639..1a880e1 100644 --- a/website/getting-started.htm +++ b/website/getting-started.htm @@ -1,6 +1,6 @@
      From 6398b7d0c1cd26c217e554a26d0d62b9dfefb439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 17:15:26 +0200 Subject: [PATCH 34/49] Removes the link underline in the header section. --- src/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/style.css b/src/style.css index 5829711..19c9219 100644 --- a/src/style.css +++ b/src/style.css @@ -206,4 +206,8 @@ h2 svg { vertical-align: middle; max-width: 1.1em; margin-right: 0.25em; +} + +#intro-nav a { + text-decoration: none; } \ No newline at end of file From c42237c1102563bb162ef2c1d25f5081e13ae2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 17:22:13 +0200 Subject: [PATCH 35/49] Adds missing icon. --- website/index.htm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/website/index.htm b/website/index.htm index 097f053..236091a 100644 --- a/website/index.htm +++ b/website/index.htm @@ -71,7 +71,10 @@
      -

      core members

      +

      + + core members

      • Felix "xq" Queißner
      • From b5f7a38a6ec992b38ffdc7430925895f0674a72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 17:28:41 +0200 Subject: [PATCH 36/49] PR play time. --- .github/workflows/pr.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/pr.yml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..4ba3bdb --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,30 @@ +name: Render PR Preview + +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: test + run: | + jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH" + # - uses: actions/checkout@v2 + # with: + # submodules: "recursive" + # - name: Setup Zig + # uses: goto-bus-stop/setup-zig@v1 + # with: + # version: master + #- name: Render website + # run: | + # zig build gen + # - name: Deploy preview with SCP + # uses: noobly314/deploy-with-scp@v1 + # with: + # src: render/* + # dest: zeg/ + # username: generic-ci + # server-ip: random-projects.net + # ssh-key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # From d0388016a6b6fae8b271cda253fb2f1c19942878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 17:32:38 +0200 Subject: [PATCH 37/49] Restricts render workflow. --- .github/workflows/website.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index c42ef11..a0b3e23 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -1,6 +1,10 @@ name: Render Website -on: [push] +on: + push: + branches: + - 'main' + - 'master' jobs: build: @@ -9,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v2 with: - submodules: "recursive" + submodules: 'recursive' - name: Setup Zig uses: goto-bus-stop/setup-zig@v1 From b406f10e29f2afc022f03b8daf8a16a4baadc194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 18:12:35 +0200 Subject: [PATCH 38/49] Implements PR rendering in staging area. --- .github/workflows/pr.yml | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 4ba3bdb..f559ef1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,26 +5,22 @@ on: [pull_request] jobs: build: runs-on: ubuntu-latest - steps: - - name: test + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup Zig + uses: goto-bus-stop/setup-zig@v1 + with: + version: master + - name: Render website run: | - jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH" - # - uses: actions/checkout@v2 - # with: - # submodules: "recursive" - # - name: Setup Zig - # uses: goto-bus-stop/setup-zig@v1 - # with: - # version: master - #- name: Render website - # run: | - # zig build gen - # - name: Deploy preview with SCP - # uses: noobly314/deploy-with-scp@v1 - # with: - # src: render/* - # dest: zeg/ - # username: generic-ci - # server-ip: random-projects.net - # ssh-key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # + zig build gen + - name: Deploy preview with SCP + uses: noobly314/deploy-with-scp@v1 + with: + src: render + dest: 'zeg-pr/${{ github.event.number }}' + username: generic-ci + server-ip: random-projects.net + ssh-key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # From 486ed416ae8b385ca09b4c9fc738b8c1558bd824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 18:59:12 +0200 Subject: [PATCH 39/49] Adds PR comment from the bot with the staging link. Makes style link relative. --- .github/workflows/pr.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f559ef1..4c85357 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -19,8 +19,19 @@ jobs: - name: Deploy preview with SCP uses: noobly314/deploy-with-scp@v1 with: - src: render - dest: 'zeg-pr/${{ github.event.number }}' + src: render/* + dest: 'zeg-pr/${{ github.event.number }}/' username: generic-ci server-ip: random-projects.net ssh-key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # + write_comment: + runs-on: ubuntu-latest + steps: + - uses: mshick/add-pr-comment@v1 + with: + message: | + 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 + allow-repeats: false # This is the default From 6b6b0fe9801c7e2e3cce08d286441fec0e9dcd13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 19:28:23 +0200 Subject: [PATCH 40/49] Makes style.css relative. --- src/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.zig b/src/main.zig index 6a437e4..5cb44b1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -588,7 +588,7 @@ const Website = struct { \\ \\ \\ ZEG - \\ + \\ \\ \\ ); From 24d5fee52b89f5221a9c0eeb0c3de8750847ecd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 19:29:30 +0200 Subject: [PATCH 41/49] Swaps IRC and Zig main discord. --- website/index.htm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/index.htm b/website/index.htm index 236091a..b2c2489 100644 --- a/website/index.htm +++ b/website/index.htm @@ -44,14 +44,14 @@
      •  zig embedded group discord
      • -
      •  zig - language - discord -
      •  microzig irc channel (webchat)
      • +
      •  zig + language + discord +
      From 025a0079af1a8aa896871a96d20d8b36288d70eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 4 Oct 2022 19:32:09 +0200 Subject: [PATCH 42/49] Tries to fix PR stuff again. --- .github/workflows/pr.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 4c85357..86526c1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -17,13 +17,14 @@ jobs: run: | zig build gen - name: Deploy preview with SCP - uses: noobly314/deploy-with-scp@v1 + uses: horochx/deploy-via-scp@1.1.0 with: - src: render/* - dest: 'zeg-pr/${{ github.event.number }}/' - username: generic-ci - server-ip: random-projects.net - ssh-key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # + local: render/* + remote: 'zeg-pr/${{ github.event.number }}/' + ensureRemote: true + user: generic-ci + host: random-projects.net + key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # write_comment: runs-on: ubuntu-latest steps: From 6be22ca95957dd1e483d19a5b5bcc4a4efcec9fb Mon Sep 17 00:00:00 2001 From: Gord Stephen Date: Wed, 31 May 2023 22:07:54 -0700 Subject: [PATCH 43/49] Update example file links --- website/getting-started.htm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/getting-started.htm b/website/getting-started.htm index 1a880e1..ce7c401 100644 --- a/website/getting-started.htm +++ b/website/getting-started.htm @@ -20,12 +20,12 @@

      microzig examples

    From b054bab3f5c7768f08e7d1c09a2a7491c32b94c3 Mon Sep 17 00:00:00 2001 From: Nancy Lau Date: Sun, 16 Jul 2023 14:45:08 -0700 Subject: [PATCH 44/49] example projects link is broken --- website/getting-started.htm | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/website/getting-started.htm b/website/getting-started.htm index ce7c401..f934f0f 100644 --- a/website/getting-started.htm +++ b/website/getting-started.htm @@ -6,16 +6,6 @@
    - -

    microzig examples

    From 9a73dab34d9284fcfdf4883de3a7aa53b403fca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Mon, 17 Jul 2023 08:16:06 +0200 Subject: [PATCH 45/49] Fixes CI by tacking Zig to stable version. --- .github/workflows/pr.yml | 2 +- .github/workflows/website.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 86526c1..a4d41eb 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -12,7 +12,7 @@ jobs: - name: Setup Zig uses: goto-bus-stop/setup-zig@v1 with: - version: master + version: 0.10.1 - name: Render website run: | zig build gen diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index a0b3e23..272b4e2 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -16,9 +16,9 @@ jobs: submodules: 'recursive' - name: Setup Zig - uses: goto-bus-stop/setup-zig@v1 + uses: goto-bus-stop/setup-zig@v2 with: - version: master + version: 0.10.1 - name: Render website run: | From fe9bafc828fbb924a7aaaf53579e698a31eff47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 23 Feb 2024 11:50:37 +0100 Subject: [PATCH 46/49] Refactors website to use zine-ssg.io --- .envrc | 2 + .gitignore | 1 + .gitmodules | 3 - WIP.md | 41 +++-- build.zig | 42 ++--- build.zig.zon | 11 ++ {website => content}/articles/.keep | 0 content/getting-started.md | 11 ++ content/index.md | 11 ++ .../tutorials/01-embedded-basics.md | 11 ++ .../tutorials/02-embedded-programming.md | 18 ++- .../img => content/tutorials}/memory-map.png | Bin deps/koino | 1 - flake.lock | 146 ++++++++++++++++++ flake.nix | 52 +++++++ .../getting-started.html | 6 +- website/index.htm => layouts/index.html | 8 +- layouts/templates/base.html | 28 ++++ layouts/tutorial.html | 17 ++ {src => legacy-ssg}/main.zig | 0 {website => static}/favicon.ico | Bin {website => static}/img/articles.svg | 0 {website => static}/img/atom.svg | 0 {website => static}/img/battery.svg | 0 {website => static}/img/chat-discord.svg | 0 {website => static}/img/chat-irc.svg | 0 {website => static}/img/code.svg | 0 {website => static}/img/community.svg | 0 {website => static}/img/ember.svg | 0 {website => static}/img/goals.svg | 0 {website => static}/img/members.svg | 0 {website => static}/img/pager.svg | 0 {website => static}/img/read-more.svg | 0 {website => static}/img/teacher.svg | 0 {src => static}/style.css | 2 +- 35 files changed, 348 insertions(+), 63 deletions(-) create mode 100644 .envrc create mode 100644 build.zig.zon rename {website => content}/articles/.keep (100%) create mode 100644 content/getting-started.md create mode 100644 content/index.md rename {website => content}/tutorials/01-embedded-basics.md (87%) rename {website => content}/tutorials/02-embedded-programming.md (97%) rename {website/img => content/tutorials}/memory-map.png (100%) delete mode 160000 deps/koino create mode 100644 flake.lock create mode 100644 flake.nix rename website/getting-started.htm => layouts/getting-started.html (95%) rename website/index.htm => layouts/index.html (98%) create mode 100644 layouts/templates/base.html create mode 100644 layouts/tutorial.html rename {src => legacy-ssg}/main.zig (100%) rename {website => static}/favicon.ico (100%) rename {website => static}/img/articles.svg (100%) rename {website => static}/img/atom.svg (100%) rename {website => static}/img/battery.svg (100%) rename {website => static}/img/chat-discord.svg (100%) rename {website => static}/img/chat-irc.svg (100%) rename {website => static}/img/code.svg (100%) rename {website => static}/img/community.svg (100%) rename {website => static}/img/ember.svg (100%) rename {website => static}/img/goals.svg (100%) rename {website => static}/img/members.svg (100%) rename {website => static}/img/pager.svg (100%) rename {website => static}/img/read-more.svg (100%) rename {website => static}/img/teacher.svg (100%) rename {src => static}/style.css (99%) diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..4cc700c --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +# use_nix +use_flake diff --git a/.gitignore b/.gitignore index f2d5b30..06606ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ zig-cache/ render/ zig-out/ +.direnv/ diff --git a/.gitmodules b/.gitmodules index 9aade95..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "deps/koino"] - path = deps/koino - url = https://github.com/MasterQ32/koino diff --git a/WIP.md b/WIP.md index e9a8dc9..1f26e46 100644 --- a/WIP.md +++ b/WIP.md @@ -5,34 +5,33 @@ These have either incomplete content on the website or a branch where they are being written. - Tutorials - - 01-embedded-basics - - 02-embedded-programming + - 01-embedded-basics + - 02-embedded-programming - Articles - - NONE + - NONE ## TODO Things that should be written eventually. - Tutorials - - Getting started with: - - Arduino/AVR - - LPC1768 - - NRF52 - - Hardware: nRF52840 Dongle - - https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle/GetStarted - - https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-desktop - - https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nc_programmer%2FUG%2Fnrf_connect_programmer%2Fncp_programming_dongle.html - - Raspberry Pi Pico - - STM32 - - What device to chose? - - Introduction to HAL 9001 + - Getting started with: + - Arduino/AVR + - LPC1768 + - NRF52 + - Hardware: nRF52840 Dongle + - https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle/GetStarted + - https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-desktop + - https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nc_programmer%2FUG%2Fnrf_connect_programmer%2Fncp_programming_dongle.html + - Raspberry Pi Pico + - STM32 + - What device to chose? + - Introduction to HAL 9001 - Articles - - `async`/`await` on embedded platforms - - Creating your own JTAG debugger - - Black Magic Probe - - https://paramaggarwal.medium.com/converting-an-stm32f103-board-to-a-black-magic-probe-c013cf2cc38c - - zCOM, a network stack for embedded devices + - Creating your own JTAG debugger + - Black Magic Probe + - https://paramaggarwal.medium.com/converting-an-stm32f103-board-to-a-black-magic-probe-c013cf2cc38c + - zCOM, a network stack for embedded devices ## Ideas @@ -40,4 +39,4 @@ Ideas for things to write that would be great to do eventually or projects to wr - Tutorials - Articles - - Make your own keyboard with zig (and replace qmk) + - Make your own keyboard with zig (and replace qmk) diff --git a/build.zig b/build.zig index 4b2f483..f1d8096 100644 --- a/build.zig +++ b/build.zig @@ -1,33 +1,15 @@ const std = @import("std"); - -const pkgs = struct { - const koino = std.build.Pkg{ - .name = "koino", - .source = .{ .path = "./deps/koino/src/koino.zig" }, - .dependencies = &[_]std.build.Pkg{ - std.build.Pkg{ .name = "libpcre", .source = .{ .path = "deps/koino/vendor/libpcre/src/main.zig" } }, - std.build.Pkg{ .name = "htmlentities", .source = .{ .path = "deps/koino/vendor/htmlentities/src/main.zig" } }, - std.build.Pkg{ .name = "clap", .source = .{ .path = "deps/koino/vendor/zig-clap/clap.zig" } }, - std.build.Pkg{ .name = "zunicode", .source = .{ .path = "deps/koino/vendor/zunicode/src/zunicode.zig" } }, +const zine = @import("zine"); + +pub fn build(b: *std.Build) !void { + // zine.scriptyReferenceDocs(b, "content/documentation/scripty/index.md"); + try zine.addWebsite(b, .{ + .layouts_dir_path = "layouts", + .content_dir_path = "content", + .static_dir_path = "static", + .site = .{ + .base_url = "https://microzig.tech", + .title = "Zig Embedded Group", }, - }; -}; - -const linkPcre = @import("deps/koino/vendor/libpcre/build.zig").linkPcre; - -pub fn build(b: *std.build.Builder) !void { - const target = b.standardTargetOptions(.{}); - const mode = b.standardReleaseOptions(); - - const render_website = b.addExecutable("zeg-website", "src/main.zig"); - render_website.setTarget(target); - render_website.setBuildMode(mode); - try linkPcre(render_website); - render_website.addPackage(pkgs.koino); - render_website.install(); - - const gen_cmd = render_website.run(); - - const gen_step = b.step("gen", "Generates the website"); - gen_step.dependOn(&gen_cmd.step); + }); } diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..4ec7868 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = "microzig.tech", + .version = "0.2.0", + .paths = .{"."}, + .dependencies = .{ + .zine = .{ + .url = "https://github.com/kristoff-it/zine/archive/03f80646b83cadb2e693ff5d97445d3e16c8e222.tar.gz", + .hash = "1220e3e4938edf652776349c45b3bb58774d540a050034488ff8dab7dbe410cc2977", + }, + }, +} diff --git a/website/articles/.keep b/content/articles/.keep similarity index 100% rename from website/articles/.keep rename to content/articles/.keep diff --git a/content/getting-started.md b/content/getting-started.md new file mode 100644 index 0000000..d9edbd1 --- /dev/null +++ b/content/getting-started.md @@ -0,0 +1,11 @@ +--- +{ + "title": "Home", + "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 new file mode 100644 index 0000000..af1d6e7 --- /dev/null +++ b/content/index.md @@ -0,0 +1,11 @@ +--- +{ + "title": "Home", + "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/website/tutorials/01-embedded-basics.md b/content/tutorials/01-embedded-basics.md similarity index 87% rename from website/tutorials/01-embedded-basics.md rename to content/tutorials/01-embedded-basics.md index 1ff6a3f..e1a246a 100644 --- a/website/tutorials/01-embedded-basics.md +++ b/content/tutorials/01-embedded-basics.md @@ -1,3 +1,13 @@ +--- +{ + "title": "Embedded Basics", + "date": "2020-07-06T00:00:00", + "author": "Felix Queißner", + "draft": false, + "layout": "tutorial.html", + "tags": [] +} +--- # Embedded Basics In this tutorial, you'll learn the absolute basics of the embedded world. If @@ -21,6 +31,7 @@ Wikipedia does a good job defining embedded systems with this opener: So at the end of the day, if you are adding any sort of computation to some object who's main purpose is not being a computer, it's an embedded system. Some examples of Embedded systems: + - cars - industrial control systems - mars rovers diff --git a/website/tutorials/02-embedded-programming.md b/content/tutorials/02-embedded-programming.md similarity index 97% rename from website/tutorials/02-embedded-programming.md rename to content/tutorials/02-embedded-programming.md index 1c67a02..86e04e1 100644 --- a/website/tutorials/02-embedded-programming.md +++ b/content/tutorials/02-embedded-programming.md @@ -1,3 +1,13 @@ +--- +{ + "title": "Embedded Basics", + "date": "2020-07-06T00:00:00", + "author": "Felix Queißner", + "draft": false, + "layout": "tutorial.html", + "tags": [] +} +--- # Embedded Programming In this tutorial, you'll learn the ways of the embedded programmer and how to master your MCU. @@ -39,9 +49,9 @@ And last, but not least: You need a [text editor of your choice](https://en.wiki So to get an embedded program up and running, we first need to check out the *memory map* in the datasheet. These usually look like this: -![Memory Map of LPC1768](../img/memory-map.png) +![Memory Map of LPC1768](memory-map.png) -Here you can see that the memory contains continuous flash memory (*On-chip [non-volatile memory](https://en.wikipedia.org/wiki/Non-volatile_memory)*), two sections of SRAM (*On-chip [SRAM](https://en.wikipedia.org/wiki/Static_random-access_memory)*), some *Boot ROM*, and peripherials. +Here you can see that the memory contains continuous flash memory (*On-chip [non-volatile memory](https://en.wikipedia.org/wiki/Non-volatile_memory)*), two sections of SRAM (*On-chip [SRAM](https://en.wikipedia.org/wiki/Static_random-access_memory)*), some *Boot ROM*, and peripherials. This memory map tells us how to design the [linker script](https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_chapter/ld_3.html#SEC6) and how to lay out our sections (`.text`, `.data`, …). As sections are quite complex topic for themselves, they [will be explained later](#text-data-and-other-curious-sections). For now, we only need to know that `.text` is all of our code (this is where our functions live), `.rodata` is pre-initialized immutable data, `.data` is the pre-initialized mutable data and `.bss` is zero-initialized mutable data. @@ -127,7 +137,7 @@ extern fn _start() callconv(.Naked) noreturn { std.mem.copy(u8, dst_ptr[0..length], src_ptr[0..length]); // call your program enty point here: - … + // … } ``` @@ -138,4 +148,4 @@ As you might have noticed, we have a function called `_start`. This is our progr But you might wonder: How is this entry point called? This is very SOC-dependent and is explained in the respective tutorials for each SOC. The same is true for setting up the [stack pointer](https://en.wikipedia.org/wiki/Call_stack) which is required for calling functions and storing temporary variables. -## MORE COMING SOON \ No newline at end of file +## MORE COMING SOON diff --git a/website/img/memory-map.png b/content/tutorials/memory-map.png similarity index 100% rename from website/img/memory-map.png rename to content/tutorials/memory-map.png diff --git a/deps/koino b/deps/koino deleted file mode 160000 index 5ea268b..0000000 --- a/deps/koino +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5ea268b50116f2f06bfa9b83f24324ec8c211be1 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..276e5a4 --- /dev/null +++ b/flake.lock @@ -0,0 +1,146 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1708682153, + "narHash": "sha256-5sMDOig3rOe5/2yrhiVjQZnVranorjKHVkzQGmZNNLY=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "33a498b7b1e3af01cb9f99bed64c96c1b4acaa70", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "release-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1702350026, + "narHash": "sha256-A+GNZFZdfl4JdDphYKBJ5Ef1HOiFsP18vQe9mqjmUis=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9463103069725474698139ab10f17a9d125da859", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "zig": "zig" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1708647717, + "narHash": "sha256-iA+MJG6isCog6KIq9uyyTmBMacJCwIuecBbkZol8XiE=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "433ed3117af772faad68a50dcf0199bf273f8721", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c8c8b64 --- /dev/null +++ b/flake.nix @@ -0,0 +1,52 @@ +{ + description = "microzig website environment"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/release-23.11"; + flake-utils.url = "github:numtide/flake-utils"; + + # required for latest zig + zig.url = "github:mitchellh/zig-overlay"; + + # Used for shell.nix + flake-compat = { + url = github:edolstra/flake-compat; + flake = false; + }; + }; + + outputs = + { self + , nixpkgs + , flake-utils + , ... + } @ inputs: + let + overlays = [ + (final: prev: { zigpkgs = inputs.zig.packages.${prev.system}; }) + ]; + + # Our supported systems are the same supported systems as the Zig binaries + systems = builtins.attrNames inputs.zig.packages; + in + flake-utils.lib.eachSystem systems ( + system: + let + pkgs = import nixpkgs { inherit overlays system; }; + in + rec { + devShells.default = pkgs.mkShell { + nativeBuildInputs = [ pkgs.zigpkgs.master ]; + + buildInputs = [ pkgs.bashInteractive ]; + + shellHook = '' + export SHELL=${pkgs.bashInteractive}/bin/bash + ''; + }; + + # For compatibility with older versions of the `nix` binary + devShell = self.devShells.${system}.default; + } + ); +} diff --git a/website/getting-started.htm b/layouts/getting-started.html similarity index 95% rename from website/getting-started.htm rename to layouts/getting-started.html index f934f0f..646f607 100644 --- a/website/getting-started.htm +++ b/layouts/getting-started.html @@ -1,4 +1,8 @@ -
    + + + + +
  •  microzig irc channel (webchat) + href="https://kiwiirc.com/nextclient/irc.libera.chat/#microzig">webchat)
  •  zig language diff --git a/layouts/templates/base.html b/layouts/templates/base.html new file mode 100644 index 0000000..1f4c22b --- /dev/null +++ b/layouts/templates/base.html @@ -0,0 +1,28 @@ + + + + + <super/> - Zig Embedded Group + + + + + + + + + + + + + + + + +
    + + + \ No newline at end of file diff --git a/layouts/tutorial.html b/layouts/tutorial.html new file mode 100644 index 0000000..e3d4dbc --- /dev/null +++ b/layouts/tutorial.html @@ -0,0 +1,17 @@ + + + + +
    + + + +
    + +
    + vectorized Ember, the awesome zeg mascot! +
    +
    diff --git a/src/main.zig b/legacy-ssg/main.zig similarity index 100% rename from src/main.zig rename to legacy-ssg/main.zig diff --git a/website/favicon.ico b/static/favicon.ico similarity index 100% rename from website/favicon.ico rename to static/favicon.ico diff --git a/website/img/articles.svg b/static/img/articles.svg similarity index 100% rename from website/img/articles.svg rename to static/img/articles.svg diff --git a/website/img/atom.svg b/static/img/atom.svg similarity index 100% rename from website/img/atom.svg rename to static/img/atom.svg diff --git a/website/img/battery.svg b/static/img/battery.svg similarity index 100% rename from website/img/battery.svg rename to static/img/battery.svg diff --git a/website/img/chat-discord.svg b/static/img/chat-discord.svg similarity index 100% rename from website/img/chat-discord.svg rename to static/img/chat-discord.svg diff --git a/website/img/chat-irc.svg b/static/img/chat-irc.svg similarity index 100% rename from website/img/chat-irc.svg rename to static/img/chat-irc.svg diff --git a/website/img/code.svg b/static/img/code.svg similarity index 100% rename from website/img/code.svg rename to static/img/code.svg diff --git a/website/img/community.svg b/static/img/community.svg similarity index 100% rename from website/img/community.svg rename to static/img/community.svg diff --git a/website/img/ember.svg b/static/img/ember.svg similarity index 100% rename from website/img/ember.svg rename to static/img/ember.svg diff --git a/website/img/goals.svg b/static/img/goals.svg similarity index 100% rename from website/img/goals.svg rename to static/img/goals.svg diff --git a/website/img/members.svg b/static/img/members.svg similarity index 100% rename from website/img/members.svg rename to static/img/members.svg diff --git a/website/img/pager.svg b/static/img/pager.svg similarity index 100% rename from website/img/pager.svg rename to static/img/pager.svg diff --git a/website/img/read-more.svg b/static/img/read-more.svg similarity index 100% rename from website/img/read-more.svg rename to static/img/read-more.svg diff --git a/website/img/teacher.svg b/static/img/teacher.svg similarity index 100% rename from website/img/teacher.svg rename to static/img/teacher.svg diff --git a/src/style.css b/static/style.css similarity index 99% rename from src/style.css rename to static/style.css index 19c9219..0fa3eb7 100644 --- a/src/style.css +++ b/static/style.css @@ -113,7 +113,7 @@ strong a { color: #f7a41d } -#intro { +#content { padding: 80px } From 848aa8e2c1b499566c9d44631e4b7fdc24536629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 23 Feb 2024 13:59:39 +0100 Subject: [PATCH 47/49] Adjusts CI runs a bit --- .github/workflows/pr.yml | 28 +++++++++++++--------------- .github/workflows/website.yml | 20 ++++++++++---------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a4d41eb..67bc9c5 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -10,24 +10,22 @@ jobs: with: submodules: 'recursive' - name: Setup Zig - uses: goto-bus-stop/setup-zig@v1 + uses: goto-bus-stop/setup-zig@v2 with: - version: 0.10.1 + version: master - name: Render website run: | - zig build gen - - name: Deploy preview with SCP - uses: horochx/deploy-via-scp@1.1.0 - with: - local: render/* - remote: 'zeg-pr/${{ github.event.number }}/' - ensureRemote: true - user: generic-ci - host: random-projects.net - key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # - write_comment: - runs-on: ubuntu-latest - steps: + zig build + # - name: Deploy preview with SCP + # uses: horochx/deploy-via-scp@1.1.0 + # with: + # local: render/* + # remote: 'zeg-pr/${{ github.event.number }}/' + # ensureRemote: true + # user: generic-ci + # host: random-projects.net + # key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # + - uses: mshick/add-pr-comment@v1 with: message: | diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 272b4e2..9799380 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -18,17 +18,17 @@ jobs: - name: Setup Zig uses: goto-bus-stop/setup-zig@v2 with: - version: 0.10.1 + version: master - name: Render website run: | - zig build gen + zig build - - name: Deploy with SCP - uses: noobly314/deploy-with-scp@v1 - with: - src: render/* - dest: zeg/ - username: generic-ci - server-ip: random-projects.net - ssh-key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # + # - name: Deploy with SCP + # uses: noobly314/deploy-with-scp@v1 + # with: + # src: render/* + # dest: zeg/ + # username: generic-ci + # server-ip: random-projects.net + # ssh-key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # From 86489efd892dd7a99282b413eadff40c19cfef3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 23 Feb 2024 19:31:16 +0100 Subject: [PATCH 48/49] Adds deployment test. --- .github/workflows/pr.yml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 67bc9c5..7d6289d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -16,15 +16,17 @@ jobs: - name: Render website run: | zig build - # - name: Deploy preview with SCP - # uses: horochx/deploy-via-scp@1.1.0 - # with: - # local: render/* - # remote: 'zeg-pr/${{ github.event.number }}/' - # ensureRemote: true - # user: generic-ci - # host: random-projects.net - # key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # + + - name: Deploy + uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_PRIVATE_KEY }} + ARGS: "-vzrli" + SOURCE: "zig-out/" + REMOTE_HOST: ${{ secrets.DEPLOY_HOST }} + REMOTE_USER: ${{ secrets.DEPLOY_USER }} + REMOTE_PORT: ${{ secrets.DEPLOY_PORT }} + TARGET: "./staging/pulls/${{ github.event.number }}" - uses: mshick/add-pr-comment@v1 with: From ac10505d276c16fc192346417545bc6a2b255ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 23 Feb 2024 23:22:56 +0100 Subject: [PATCH 49/49] Adapts deployment script --- .github/workflows/website.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 9799380..daa3250 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -24,11 +24,13 @@ jobs: run: | zig build - # - name: Deploy with SCP - # uses: noobly314/deploy-with-scp@v1 - # with: - # src: render/* - # dest: zeg/ - # username: generic-ci - # server-ip: random-projects.net - # ssh-key: ${{ secrets.WEBSITE_PRIVATE_KEY }} # + - name: Deploy + uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_PRIVATE_KEY }} + ARGS: "-vzrli" + SOURCE: "zig-out/" + REMOTE_HOST: ${{ secrets.DEPLOY_HOST }} + REMOTE_USER: ${{ secrets.DEPLOY_USER }} + REMOTE_PORT: ${{ secrets.DEPLOY_PORT }} + TARGET: "./live"