diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69fadfd0..f08c9c47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ # This is a basic workflow that is manually triggered -name: CI +name: Compilation and Format Check on: push: @@ -20,15 +20,4 @@ jobs: - run: sudo apt install libgl-dev libasound2-dev libx11-dev - run: zig build - run: zig build -Dtarget=x86_64-windows-gnu - - format_check: - runs-on: ubuntu-latest - name: Format Check - - steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 - with: - python-version: "3.10" - - run: python3 "format_check.py" + - run: zig build format --summary none diff --git a/build.zig b/build.zig index f9826905..7bf66aa0 100644 --- a/build.zig +++ b/build.zig @@ -112,4 +112,24 @@ pub fn build(b: *std.Build) !void { const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_exe_tests.step); + + // MARK: Formatter + + const formatter = b.addExecutable(.{ + .name = "CubyzigFormatter", + .root_source_file = b.path("src/formatter/format.zig"), + .target = target, + .optimize = optimize, + }); + + const formatter_install = b.addInstallArtifact(formatter, .{}); + + const formatter_cmd = b.addRunArtifact(formatter); + formatter_cmd.step.dependOn(&formatter_install.step); + if (b.args) |args| { + formatter_cmd.addArgs(args); + } + + const formatter_step = b.step("format", "Check the formatting of the code"); + formatter_step.dependOn(&formatter_cmd.step); } diff --git a/format_check.py b/format_check.py deleted file mode 100644 index 0c88c680..00000000 --- a/format_check.py +++ /dev/null @@ -1,44 +0,0 @@ -import os - -import sys - -directory = os.fsencode(".") - -fails = 0 - -for subdir, dirs, files in os.walk("."): - for file in files: - #print os.path.join(subdir, file) - filepath = subdir + os.sep + file - if filepath.startswith(f".{os.sep}compiler"): continue - if filepath.startswith(f".{os.sep}saves"): continue - if filepath.startswith(f".{os.sep}serverAssets"): continue - if filepath.startswith(f".{os.sep}zig-cache"): continue - if filepath.startswith(f".{os.sep}.zig-cache"): continue - - if filepath.endswith(".json") or filepath.endswith(".zig") or filepath.endswith(".py") or filepath.endswith(".zon") or filepath.endswith(".vs") or filepath.endswith(".fs") or filepath.endswith(".glsl"): - with open(filepath, "r", newline = '', encoding="utf-8") as f: - string = f.read() - line = 1 - lineStart = True - for i, c in enumerate(string): - if(c == '\r'): - print("Incorrect line ending \\r in file ", filepath, " in line ", line, ". Please configure your editor to use LF instead of CRLF.") - fails += 1 - elif(c == '\n'): - line += 1 - lineStart = True - elif(c == ' '): - if(lineStart): - print("Incorrect indentation in file ", filepath, " in line ", line, ". Please use tabs instead of spaces.") - fails += 1 - lineStart = False # avoid repeating this error multiple times - elif(c == '\t'): - continue - elif(lineStart): - lineStart = False - else: - continue -if(fails != 0): - sys.exit(1) -sys.exit(0) diff --git a/src/formatter/format.zig b/src/formatter/format.zig new file mode 100644 index 00000000..0fecc2a0 --- /dev/null +++ b/src/formatter/format.zig @@ -0,0 +1,101 @@ +const std = @import("std"); + +var global_gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=true}){}; +pub const globalAllocator = global_gpa.allocator(); + +var failed: bool = false; + +fn printError(msg: []const u8, filePath: []const u8, data: []const u8, charIndex: usize) void { + var lineStart: usize = 0; + var lineNumber: usize = 1; + var lineEnd: usize = 0; + for(data[0..charIndex], 0..) |c, i| { + if(c == '\n') { + lineStart = i + 1; + lineNumber += 1; + } + } + for(data[charIndex..], charIndex..) |c, i| { + if(c == '\n') { + lineEnd = i; + break; + } + } + + var startLineChars = std.ArrayList(u8).init(globalAllocator); + defer startLineChars.deinit(); + for(data[lineStart..charIndex]) |c| { + if(c == '\t') { + startLineChars.append('\t') catch {}; + } else { + startLineChars.append(' ') catch {}; + } + } + + failed = true; + + std.log.err("Found formatting error in line {} of file {s}: {s}\n{s}\n{s}^", .{lineNumber, filePath, msg, data[lineStart..lineEnd], startLineChars.items}); +} + +fn checkFile(dir: std.fs.Dir, filePath: []const u8) !void { + const data = try dir.readFileAlloc(globalAllocator, filePath, std.math.maxInt(usize)); + defer globalAllocator.free(data); + + var lineStart: bool = true; + + for(data, 0..) |c, i| { + switch(c) { + '\n' => { + lineStart = true; + }, + '\r' => { + printError("Incorrect line ending \\r. Please configure your editor to use LF instead CRLF.", filePath, data, i); + }, + ' ' => { + if(lineStart) { + printError("Incorrect indentation. Please use tabs instead of spaces.", filePath, data, i); + } + }, + '\t' => {}, + else => { + lineStart = false; + } + } + } +} + +fn checkDirectory(dir: std.fs.Dir) !void { + var walker = try dir.walk(globalAllocator); + defer walker.deinit(); + while(try walker.next()) |child| { + if(std.mem.endsWith(u8, child.basename, ".zon") and !std.mem.endsWith(u8, child.basename, ".zig.zon")) { + std.log.err("File name should end with .zig.zon so it gets syntax highlighting on github.", .{}); + failed = true; + } + if(child.kind == .file and ( + std.mem.endsWith(u8, child.basename, ".zig") + or std.mem.endsWith(u8, child.basename, ".zon") + or std.mem.endsWith(u8, child.basename, ".vs") + or std.mem.endsWith(u8, child.basename, ".fs") + or std.mem.endsWith(u8, child.basename, ".glsl") + )) { + try checkFile(dir, child.path); + } + } +} + +pub fn main() !void { + defer _ = global_gpa.deinit(); + + var dir = try std.fs.cwd().openDir("src", .{.iterate = true}); + defer dir.close(); + try checkDirectory(dir); + dir.close(); + dir = try std.fs.cwd().openDir("assets", .{.iterate = true}); + try checkDirectory(dir); + + try checkFile(std.fs.cwd(), "build.zig"); + try checkFile(std.fs.cwd(), "build.zig.zon"); + + if(failed) std.posix.exit(1); +}