Skip to content

Commit

Permalink
implemented check and interface, lsp accepts languageid aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
kristoff-it committed Jul 26, 2024
1 parent aab3c01 commit 8b1c0fa
Show file tree
Hide file tree
Showing 6 changed files with 538 additions and 4 deletions.
75 changes: 75 additions & 0 deletions src/Ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,81 @@ const Parser = struct {
}
};

pub fn printInterfaceAsHtml(
ast: Ast,
html_ast: html.Ast,
path: ?[]const u8,
out: anytype,
) !void {
if (path) |p| {
try out.print("<extend template=\"{s}\">\n", .{p});
}
var it = ast.interface.iterator();
var at_least_one = false;
while (it.next()) |kv| {
at_least_one = true;
const id = kv.key_ptr.*;
const parent_idx = kv.value_ptr.*;
const tag_name = ast.nodes[parent_idx].superBlock(
ast.src,
html_ast,
).parent_tag_name.slice(ast.src);
try out.print("<{s} id=\"{s}\"></{s}>\n", .{
tag_name,
id,
tag_name,
});
}

if (!at_least_one) {
try out.print(
\\
\\<!--
\\The extended template has no interface!
\\Add <super> tags to it to make it extensible.
\\-->
\\
, .{});
}
}

pub fn printErrors(ast: Ast, src: []const u8, path: ?[]const u8) void {
for (ast.errors) |err| {
const range = err.main_location.range(src);
std.debug.print("{s}:{}:{}: {s}\n", .{
path orelse "<stdin>",
range.start.row,
range.start.col,
@tagName(err.kind),
});
}
}

pub fn interfaceFormatter(
ast: Ast,
html_ast: html.Ast,
path: ?[]const u8,
) Formatter {
return .{ .ast = ast, .html = html_ast, .path = path };
}
const Formatter = struct {
ast: Ast,
html: html.Ast,
path: ?[]const u8,

pub fn format(
f: Formatter,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
out_stream: anytype,
) !void {
_ = fmt;
_ = options;

try f.ast.printInterfaceAsHtml(f.html, f.path, out_stream);
}
};

fn is(str1: []const u8, str2: []const u8) bool {
return std.ascii.eqlIgnoreCase(str1, str2);
}
Expand Down
7 changes: 4 additions & 3 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const builtin = @import("builtin");
const build_options = @import("build_options");
const super = @import("super");
const logging = @import("cli/logging.zig");
const interface_exe = @import("cli/interface.zig");
const check_exe = @import("cli/check.zig");
const fmt_exe = @import("cli/fmt.zig");
const lsp_exe = @import("cli/lsp.zig");

Expand Down Expand Up @@ -80,13 +82,12 @@ pub fn main() !void {
if (cmd == .lsp) lsp_mode = true;

_ = switch (cmd) {
// .check => check_exe.run(gpa, args[2..]),
// .interface, .i => interface_exe.run(gpa, args[2..]),
.check => check_exe.run(gpa, args[2..]),
.interface, .i => interface_exe.run(gpa, args[2..]),
.fmt => fmt_exe.run(gpa, args[2..]),
.lsp => lsp_exe.run(gpa, args[2..]),
.help => fatalHelp(),
.version => printVersion(),
else => fatalHelp(),
} catch |err| fatal("unexpected error: {s}\n", .{@errorName(err)});
}

Expand Down
274 changes: 274 additions & 0 deletions src/cli/check.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
const std = @import("std");
const super = @import("superhtml");

const FileType = enum { html, super };

pub fn run(gpa: std.mem.Allocator, args: []const []const u8) !void {
const cmd = Command.parse(args);
var any_error = false;
switch (cmd.mode) {
.stdin => {
var buf = std.ArrayList(u8).init(gpa);
try std.io.getStdIn().reader().readAllArrayList(&buf, super.max_size);
const in_bytes = try buf.toOwnedSliceSentinel(0);

try checkHtml(gpa, null, in_bytes);
},
.stdin_super => {
var buf = std.ArrayList(u8).init(gpa);
try std.io.getStdIn().reader().readAllArrayList(&buf, super.max_size);
const in_bytes = try buf.toOwnedSliceSentinel(0);

try checkSuper(gpa, null, in_bytes);
},
.paths => |paths| {
// checkFile will reset the arena at the end of each call
var arena_impl = std.heap.ArenaAllocator.init(gpa);
for (paths) |path| {
checkFile(
&arena_impl,
std.fs.cwd(),
path,
path,
&any_error,
) catch |err| switch (err) {
error.IsDir, error.AccessDenied => {
checkDir(
gpa,
&arena_impl,
path,
&any_error,
) catch |dir_err| {
std.debug.print("Error walking dir '{s}': {s}\n", .{
path,
@errorName(dir_err),
});
std.process.exit(1);
};
},
else => {
std.debug.print("Error while accessing '{s}': {s}\n", .{
path, @errorName(err),
});
std.process.exit(1);
},
};
}
},
}

if (any_error) {
std.process.exit(1);
}
}

fn checkDir(
gpa: std.mem.Allocator,
arena_impl: *std.heap.ArenaAllocator,
path: []const u8,
any_error: *bool,
) !void {
var dir = try std.fs.cwd().openDir(path, .{ .iterate = true });
defer dir.close();
var walker = dir.walk(gpa) catch oom();
defer walker.deinit();
while (try walker.next()) |item| {
switch (item.kind) {
.file => {
try checkFile(
arena_impl,
item.dir,
item.basename,
item.path,
any_error,
);
},
else => {},
}
}
}

fn checkFile(
arena_impl: *std.heap.ArenaAllocator,
base_dir: std.fs.Dir,
sub_path: []const u8,
full_path: []const u8,
any_error: *bool,
) !void {
_ = any_error;
defer _ = arena_impl.reset(.retain_capacity);
const arena = arena_impl.allocator();

const file = try base_dir.openFile(sub_path, .{});
defer file.close();

const stat = try file.stat();
if (stat.kind == .directory)
return error.IsDir;

const file_type: FileType = blk: {
const ext = std.fs.path.extension(sub_path);
if (std.mem.eql(u8, ext, ".html") or
std.mem.eql(u8, ext, ".htm"))
{
break :blk .html;
}

if (std.mem.eql(u8, ext, ".shtml")) {
break :blk .super;
}
return;
};

var buf = std.ArrayList(u8).init(arena);
defer buf.deinit();

try file.reader().readAllArrayList(&buf, super.max_size);

const in_bytes = try buf.toOwnedSliceSentinel(0);

switch (file_type) {
.html => try checkHtml(
arena,
full_path,
in_bytes,
),
.super => try checkSuper(
arena,
full_path,
in_bytes,
),
}
}

pub fn checkHtml(
arena: std.mem.Allocator,
path: ?[]const u8,
code: [:0]const u8,
) !void {
const ast = try super.html.Ast.init(arena, code, .html);
if (ast.errors.len > 0) {
ast.printErrors(code, path);
std.process.exit(1);
}
}

fn checkSuper(
arena: std.mem.Allocator,
path: ?[]const u8,
code: [:0]const u8,
) !void {
const html = try super.html.Ast.init(arena, code, .superhtml);
if (html.errors.len > 0) {
html.printErrors(code, path);
std.process.exit(1);
}

const s = try super.Ast.init(arena, html, code);
if (s.errors.len > 0) {
s.printErrors(code, path);
std.process.exit(1);
}
}

fn oom() noreturn {
std.debug.print("Out of memory\n", .{});
std.process.exit(1);
}

const Command = struct {
mode: Mode,

const Mode = union(enum) {
stdin,
stdin_super,
paths: []const []const u8,
};

fn parse(args: []const []const u8) Command {
var mode: ?Mode = null;

var idx: usize = 0;
while (idx < args.len) : (idx += 1) {
const arg = args[idx];
if (std.mem.eql(u8, arg, "--help") or
std.mem.eql(u8, arg, "-h"))
{
fatalHelp();
}

if (std.mem.startsWith(u8, arg, "-")) {
if (std.mem.eql(u8, arg, "--stdin") or
std.mem.eql(u8, arg, "-"))
{
if (mode != null) {
std.debug.print("unexpected flag: '{s}'\n", .{arg});
std.process.exit(1);
}

mode = .stdin;
} else if (std.mem.eql(u8, arg, "--stdin-super")) {
if (mode != null) {
std.debug.print("unexpected flag: '{s}'\n", .{arg});
std.process.exit(1);
}

mode = .stdin_super;
} else {
std.debug.print("unexpected flag: '{s}'\n", .{arg});
std.process.exit(1);
}
} else {
const paths_start = idx;
while (idx < args.len) : (idx += 1) {
if (std.mem.startsWith(u8, args[idx], "-")) {
break;
}
}
idx -= 1;

if (mode != null) {
std.debug.print(
"unexpected path argument(s): '{s}'...\n",
.{args[paths_start]},
);
std.process.exit(1);
}

const paths = args[paths_start .. idx + 1];
mode = .{ .paths = paths };
}
}

const m = mode orelse {
std.debug.print("missing argument(s)\n\n", .{});
fatalHelp();
};

return .{ .mode = m };
}

fn fatalHelp() noreturn {
std.debug.print(
\\Usage: super check PATH [PATH...] [OPTIONS]
\\
\\ Checks for syntax errors. If PATH is a directory, it will
\\ be searched recursively for HTML and SuperHTML files.
\\
\\ Detected extensions:
\\ HTML .html, .htm
\\ SuperHTML .shtml
\\
\\Options:
\\
\\ --stdin Format bytes from stdin and ouptut to stdout.
\\ Mutually exclusive with other input aguments.
\\
\\ --stdin-super Same as --stdin but for SuperHTML files.
\\
\\ --help, -h Prints this help and exits.
, .{});

std.process.exit(1);
}
};
Loading

0 comments on commit 8b1c0fa

Please sign in to comment.