Changes
3 changed files (+286/-2)
-
-
@@ -45,7 +45,7 @@ compile.addElmSourceFile(b.path("src/Template/Layout/Item.elm"));compile.addElmSourceFile(b.path("src/Svg/Path.elm")); compile.addElmSourceFile(b.path("src/Url/SearchParams.elm")); break :elm_main compile.output_js; break :elm_main compile; }; const elements_js = elements_js: {
-
@@ -67,11 +67,13 @@ const dist = dist: {const dir = b.addWriteFiles(); _ = dir.addCopyFile(index_html, "index.html"); _ = dir.addCopyFile(elm_main, "main.js"); _ = dir.addCopyFile(elm_main.output_js, "main.js"); _ = dir.addCopyFile(elements_js, "elements.js"); _ = dir.addCopyDirectory(b.path("vendor/barlow"), "vendor/barlow", .{}); _ = dir.addCopyDirectory(b.path("vendor/Inter"), "vendor/Inter", .{}); _ = dir.addCopyFile(elm_main.generateThirdPartyLicenseText(), "licenses/third-party.txt"); break :dist dir; };
-
-
-
@@ -10,6 +10,7 @@ const Elm = @This();const std = @import("std"); b: *std.Build, elm_make: *std.Build.Step.Run, output_js: std.Build.LazyPath,
-
@@ -34,6 +35,7 @@ const output_js = elm_make.addPrefixedOutputFileArg("--output=", "elm.js");if (opts.optimize == .Debug) { return .{ .b = b, .elm_make = elm_make, .output_js = output_js, };
-
@@ -44,11 +46,13 @@ closure_compiler.addPrefixedFileArg("--js=", output_js);const minified_js = closure_compiler.addPrefixedOutputFileArg("--js_output_file=", "elm.min.js"); return .{ .b = b, .elm_make = elm_make, .output_js = minified_js, }; } } /// Add main module. pub fn addMainElmSourceFile(self: Elm, path: std.Build.LazyPath) void { self.elm_make.addFileArg(path);
-
@@ -58,3 +62,26 @@ /// Add non-main Elm source code file. You have to call this for every source files but main module.pub fn addElmSourceFile(self: Elm, path: std.Build.LazyPath) void { self.elm_make.addFileInput(path); } pub fn generateThirdPartyLicenseText(self: Elm) std.Build.LazyPath { const mod = self.b.createModule(.{ .root_source_file = self.b.path("build/third_party_licenses.zig"), .target = self.b.graph.host, .optimize = .ReleaseSafe, }); const exe = self.b.addExecutable(.{ .name = "third_party_licenses", .root_module = mod, .use_llvm = false, }); const run = self.b.addRunArtifact(exe); run.step.dependOn(&self.elm_make.step); run.addArg("--elm-json"); run.addFileArg(self.b.path("elm.json")); run.addArg("--out"); return run.addOutputFileArg("third-party-licenses.txt"); }
-
-
-
@@ -0,0 +1,255 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 const builtin = @import("builtin"); const std = @import("std"); const Options = struct { allocator: std.mem.Allocator, elm_json: []const u8, outfile: []const u8, const ArgsParseError = std.mem.Allocator.Error || error{ UsageError, }; fn fromArgs(allocator: std.mem.Allocator) ArgsParseError!Options { var args = try std.process.ArgIterator.initWithAllocator(allocator); defer args.deinit(); // Skip program name. _ = args.skip(); var elm_json: ?[]const u8 = null; var outfile: ?[]const u8 = null; while (args.next()) |arg| { if (std.mem.eql(u8, "--elm-json", arg)) { const value = args.next() orelse { std.log.err("{s} option requires a value", .{arg}); return ArgsParseError.UsageError; }; if (elm_json != null) { std.log.err("{s} option accepts a single value", .{arg}); return ArgsParseError.UsageError; } elm_json = value; continue; } if (std.mem.eql(u8, "--out", arg)) { const value = args.next() orelse { std.log.err("{s} option requires a value", .{arg}); return ArgsParseError.UsageError; }; if (outfile != null) { std.log.err("{s} option accepts a single value", .{arg}); return ArgsParseError.UsageError; } outfile = value; continue; } std.log.err("Unknown option: {s}", .{arg}); return ArgsParseError.UsageError; } return try init( allocator, elm_json orelse { std.log.err("--elm-json is required.", .{}); return ArgsParseError.UsageError; }, outfile orelse { std.log.err("--out is required", .{}); return ArgsParseError.UsageError; }, ); } fn init(allocator: std.mem.Allocator, elm_json: []const u8, outfile: []const u8) std.mem.Allocator.Error!Options { return .{ .allocator = allocator, .elm_json = try allocator.dupe(u8, elm_json), .outfile = try allocator.dupe(u8, outfile), }; } fn deinit(self: *const Options) void { self.allocator.free(self.elm_json); self.allocator.free(self.outfile); } }; const LicenseText = struct { const BuildError = error{ UnsupportedElmVersion, }; const ProjectElmJson = struct { @"elm-version": []const u8, dependencies: struct { direct: std.json.ArrayHashMap([]const u8), indirect: std.json.ArrayHashMap([]const u8), }, }; const Library = struct { const ElmJson = struct { name: []const u8, summary: []const u8, license: []const u8, version: []const u8, fn parse(allocator: std.mem.Allocator, reader: *std.Io.Reader) !std.json.Parsed(ElmJson) { var jr = std.json.Reader.init(allocator, reader); return std.json.parseFromTokenSource(ElmJson, allocator, &jr, .{ .allocate = .alloc_always, .ignore_unknown_fields = true, }); } }; allocator: std.mem.Allocator, elm_json: std.json.Parsed(ElmJson), license: []const u8, fn init(allocator: std.mem.Allocator, homeDir: *const std.fs.Dir, name: []const u8, version: []const u8) !Library { var read_buf: [1024]u8 = undefined; std.log.debug("Reading {s}@{s}", .{ name, version }); const path = try std.fs.path.join(allocator, &.{ ".elm/0.19.1/packages", name, version }); defer allocator.free(path); var project_dir = try homeDir.openDir(path, .{}); defer project_dir.close(); const elm_json = elm_json: { const file = try project_dir.openFile("elm.json", .{}); defer file.close(); var reader = file.reader(&read_buf); break :elm_json try ElmJson.parse(allocator, &reader.interface); }; errdefer elm_json.deinit(); const license = license: { const file = try project_dir.openFile("LICENSE", .{}); defer file.close(); var reader = file.reader(&read_buf); const stat = try file.stat(); break :license try reader.interface.readAlloc(allocator, stat.size); }; errdefer allocator.free(license); return .{ .allocator = allocator, .elm_json = elm_json, .license = license, }; } fn write(self: *const Library, writer: *std.Io.Writer) !void { try writer.print( "=====\n\n{s} v{s} ({s})\n{s}\n-----\n\n{s}\n", .{ self.elm_json.value.name, self.elm_json.value.version, self.elm_json.value.license, self.elm_json.value.summary, self.license, }, ); } fn deinit(self: Library) void { self.elm_json.deinit(); self.allocator.free(self.license); } }; fn build( allocator: std.mem.Allocator, project_elm_json: *std.Io.Reader, out: *std.Io.Writer, ) !void { var reader = std.json.Reader.init(allocator, project_elm_json); const project_elm_json_parsed: std.json.Parsed(ProjectElmJson) = try std.json.parseFromTokenSource(ProjectElmJson, allocator, &reader, .{ .allocate = .alloc_if_needed, .ignore_unknown_fields = true, }); defer project_elm_json_parsed.deinit(); if (!std.mem.eql(u8, project_elm_json_parsed.value.@"elm-version", "0.19.1")) { std.log.err("Unsupported Elm version: {s}", .{project_elm_json_parsed.value.@"elm-version"}); return BuildError.UnsupportedElmVersion; } const homeDirPath = switch (builtin.os.tag) { .windows => try std.process.getEnvVarOwned(allocator, "APPDATA"), else => try std.process.getEnvVarOwned(allocator, "HOME"), }; defer allocator.free(homeDirPath); std.log.debug("Opening home directory ({s})", .{homeDirPath}); var homeDir = try std.fs.openDirAbsolute(homeDirPath, .{}); defer homeDir.close(); // TODO: Sort dependencies var direct_deps = project_elm_json_parsed.value.dependencies.direct.map.iterator(); while (direct_deps.next()) |dep| { const lib = try Library.init(allocator, &homeDir, dep.key_ptr.*, dep.value_ptr.*); defer lib.deinit(); try lib.write(out); } var indirect_deps = project_elm_json_parsed.value.dependencies.indirect.map.iterator(); while (indirect_deps.next()) |dep| { const lib = try Library.init(allocator, &homeDir, dep.key_ptr.*, dep.value_ptr.*); defer lib.deinit(); try lib.write(out); } try out.flush(); } }; pub fn main() !u8 { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const options = try Options.fromArgs(allocator); defer options.deinit(); std.log.debug("Opening {s} for reading", .{options.elm_json}); const elm_json_file = try std.fs.cwd().openFile(options.elm_json, .{}); defer elm_json_file.close(); var in_buffer: [1024]u8 = undefined; var reader = elm_json_file.reader(&in_buffer); std.log.debug("Creating {s} for writing", .{options.outfile}); const outfile = try std.fs.cwd().createFile(options.outfile, .{}); var out_buffer: [1024]u8 = undefined; var writer = outfile.writer(&out_buffer); try LicenseText.build(allocator, &reader.interface, &writer.interface); return 0; }
-