Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
e8ea3f00ee | |||
12e96454ff | |||
c88a2815fa | |||
013f468703 | |||
e8e4cd30e3 |
24
README.md
24
README.md
@ -18,4 +18,26 @@ the following are currently supported:
|
|||||||
- lists with `- list item` or `1. list item` (but numbered lists dont render as numbered yet)
|
- lists with `- list item` or `1. list item` (but numbered lists dont render as numbered yet)
|
||||||
- task lists with `- [x] list item` or `- [ ] list item` where a checkbox is ticked if the `x` is present.
|
- task lists with `- [x] list item` or `- [ ] list item` where a checkbox is ticked if the `x` is present.
|
||||||
- horizontal rules with `***` which span the entire terminal width.
|
- horizontal rules with `***` which span the entire terminal width.
|
||||||
- strikethrough with `~~text~~`
|
- strikethrough with `~~text~~`
|
||||||
|
|
||||||
|
## dependencies
|
||||||
|
- [md4c](https://github.com/mity/md4c) for markdown parsing
|
||||||
|
- [ansi-term](https://github.com/ziglibs/ansi-term) wraps ansi styling
|
||||||
|
- [zig-clap](https://github.com/Hejsil/zig-clap) for arg parsing
|
||||||
|
|
||||||
|
## building
|
||||||
|
1. install [zig 0.14.0](https://ziglang.org/download/#release-0.14.0)
|
||||||
|
2. clone repo
|
||||||
|
3. run `zig build`
|
||||||
|
|
||||||
|
if you want a release build:
|
||||||
|
|
||||||
|
4. run `zig build --release=small`
|
||||||
|
|
||||||
|
release optimize modes are:
|
||||||
|
|
||||||
|
- fast
|
||||||
|
- safe
|
||||||
|
- small
|
||||||
|
|
||||||
|
the mdcat binary will be in `zig-out/bin/mdcat`
|
||||||
|
@ -28,8 +28,8 @@ pub fn build(b: *std.Build) void {
|
|||||||
const clap = b.dependency("clap", .{});
|
const clap = b.dependency("clap", .{});
|
||||||
exe.root_module.addImport("clap", clap.module("clap"));
|
exe.root_module.addImport("clap", clap.module("clap"));
|
||||||
|
|
||||||
const ansiterm = b.dependency("ansi-term", .{});
|
const ansiterm = b.dependency("ansi_term", .{});
|
||||||
exe.root_module.addImport("ansi-term", ansiterm.module("ansi-term"));
|
exe.root_module.addImport("ansi-term", ansiterm.module("ansi_term"));
|
||||||
|
|
||||||
// This declares intent for the executable to be installed into the
|
// This declares intent for the executable to be installed into the
|
||||||
// standard location when the user invokes the "install" step (the default
|
// standard location when the user invokes the "install" step (the default
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
// It is redundant to include "zig" in this name because it is already
|
// It is redundant to include "zig" in this name because it is already
|
||||||
// within the Zig package namespace.
|
// within the Zig package namespace.
|
||||||
.name = "mdcat",
|
.name = .mdcat,
|
||||||
|
|
||||||
// This is a [Semantic Version](https://semver.org/).
|
// This is a [Semantic Version](https://semver.org/).
|
||||||
// In a future version of Zig it will be used for package deduplication.
|
// In a future version of Zig it will be used for package deduplication.
|
||||||
@ -24,12 +24,12 @@
|
|||||||
// internet connectivity.
|
// internet connectivity.
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.clap = .{
|
.clap = .{
|
||||||
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.9.1.tar.gz",
|
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.10.0.tar.gz",
|
||||||
.hash = "122062d301a203d003547b414237229b09a7980095061697349f8bef41be9c30266b",
|
.hash = "clap-0.10.0-oBajB434AQBDh-Ei3YtoKIRxZacVPF1iSwp3IX_ZB8f0",
|
||||||
},
|
},
|
||||||
.@"ansi-term" = .{
|
.ansi_term = .{
|
||||||
.url = "https://github.com/ziglibs/ansi-term/archive/refs/heads/master.zip",
|
.url = "git+https://github.com/ziglibs/ansi_term.git#c36f75d2b49c5bed2a5bb4d71e92f6f4591b1887",
|
||||||
.hash = "12203a64b02d46e6c2e268b7da60cb11b2fa83dc580cad77cf330d1438e2280de46f",
|
.hash = "ansi_term-0.1.0-_baAy-hjAABPPaH9jO03ipMXYOWQBzOJhaj2vsDpc0MK",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
@ -40,4 +40,6 @@
|
|||||||
//"LICENSE",
|
//"LICENSE",
|
||||||
//"README.md",
|
//"README.md",
|
||||||
},
|
},
|
||||||
|
.fingerprint = 0x72dbbe0f3b46658e,
|
||||||
|
.minimum_zig_version = "0.14.0",
|
||||||
}
|
}
|
||||||
|
62
src/main.zig
62
src/main.zig
@ -2,9 +2,6 @@ const std = @import("std");
|
|||||||
const clap = @import("clap");
|
const clap = @import("clap");
|
||||||
const md4c = @import("md4c_h.zig");
|
const md4c = @import("md4c_h.zig");
|
||||||
const ansi = @import("ansi-term");
|
const ansi = @import("ansi-term");
|
||||||
const cstdio = @cImport({
|
|
||||||
@cInclude("stdio.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
@ -12,7 +9,8 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const params = comptime clap.parseParamsComptime(
|
const params = comptime clap.parseParamsComptime(
|
||||||
\\-h, --help display this help and exit
|
\\-h, --help display this help and exit
|
||||||
\\<FILE> file to read from, if empty or "-" use stdin.
|
\\-v, --version display the version and exit
|
||||||
|
\\<FILE> file to read from, empty or "-" use stdin
|
||||||
\\
|
\\
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -30,11 +28,17 @@ pub fn main() !void {
|
|||||||
};
|
};
|
||||||
defer res.deinit();
|
defer res.deinit();
|
||||||
|
|
||||||
|
if (res.args.help != 0) return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{});
|
||||||
|
|
||||||
|
const version_string = "0.1.1\n";
|
||||||
|
|
||||||
|
if (res.args.version != 0) return std.debug.print(version_string, .{});
|
||||||
|
|
||||||
var reader: std.io.AnyReader = undefined;
|
var reader: std.io.AnyReader = undefined;
|
||||||
if (res.positionals.len < 1) {
|
if (res.positionals.len < 1 or std.mem.eql(u8, res.positionals[0].?, "-")) {
|
||||||
reader = std.io.getStdIn().reader().any();
|
reader = std.io.getStdIn().reader().any();
|
||||||
} else {
|
} else {
|
||||||
const filename = res.positionals[0];
|
const filename = res.positionals[0].?;
|
||||||
const cwd = std.fs.cwd();
|
const cwd = std.fs.cwd();
|
||||||
reader = (cwd.openFile(filename, .{}) catch errorexit(13, "failed to open file D:\n")).reader().any();
|
reader = (cwd.openFile(filename, .{}) catch errorexit(13, "failed to open file D:\n")).reader().any();
|
||||||
}
|
}
|
||||||
@ -53,7 +57,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
gpa.allocator().free(data);
|
gpa.allocator().free(data);
|
||||||
|
|
||||||
try stdout.writeByte('\n');
|
//try stdout.writeByte('\n');
|
||||||
|
|
||||||
try bw.flush();
|
try bw.flush();
|
||||||
}
|
}
|
||||||
@ -97,30 +101,36 @@ const State = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn enter_block(blocktype: md4c.MD_BLOCKTYPE, detail: ?*anyopaque, userdata: ?*anyopaque) callconv(.C) c_int {
|
fn enter_block(blocktype: md4c.MD_BLOCKTYPE, detail: ?*anyopaque, userdata: ?*anyopaque) callconv(.C) c_int {
|
||||||
//std.debug.print("enter {d}\n", .{blocktype});
|
|
||||||
const state: *State = @ptrCast(@alignCast(userdata));
|
const state: *State = @ptrCast(@alignCast(userdata));
|
||||||
|
|
||||||
if (blocktype == md4c.MD_BLOCK_H) {
|
if (blocktype == md4c.MD_BLOCK_H) {
|
||||||
just_write(state.writer, "\n");
|
just_write(state.writer, "\n");
|
||||||
|
|
||||||
const h_detail: *md4c.MD_BLOCK_H_DETAIL = @ptrCast(@alignCast(detail));
|
const h_detail: *md4c.MD_BLOCK_H_DETAIL = @ptrCast(@alignCast(detail));
|
||||||
|
|
||||||
var temp_style = state.last_style;
|
var temp_style = state.last_style;
|
||||||
temp_style.font_style.dim = true;
|
temp_style.font_style.dim = true;
|
||||||
temp_style.font_style.bold = true;
|
temp_style.font_style.bold = true;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
|
|
||||||
for (0..h_detail.level) |_| {
|
for (0..h_detail.level) |_| {
|
||||||
just_write(state.writer, "#");
|
just_write(state.writer, "#");
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_style.font_style.dim = false;
|
temp_style.font_style.dim = false;
|
||||||
temp_style.font_style.bold = state.nested_bold_count > 0;
|
temp_style.font_style.bold = state.nested_bold_count > 0;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
|
|
||||||
just_write(state.writer, " ");
|
just_write(state.writer, " ");
|
||||||
}
|
}
|
||||||
if (blocktype == md4c.MD_BLOCK_LI) {
|
if (blocktype == md4c.MD_BLOCK_LI) {
|
||||||
const li_detail: *md4c.MD_BLOCK_LI_DETAIL = @ptrCast(@alignCast(detail));
|
const li_detail: *md4c.MD_BLOCK_LI_DETAIL = @ptrCast(@alignCast(detail));
|
||||||
|
|
||||||
var temp_style = state.last_style;
|
var temp_style = state.last_style;
|
||||||
if (li_detail.is_task == 0) temp_style.font_style.dim = true;
|
if (li_detail.is_task == 0) temp_style.font_style.dim = true;
|
||||||
temp_style.font_style.bold = true;
|
temp_style.font_style.bold = true;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
|
|
||||||
if (li_detail.is_task == 0) {
|
if (li_detail.is_task == 0) {
|
||||||
just_write(state.writer, "• ");
|
just_write(state.writer, "• ");
|
||||||
} else {
|
} else {
|
||||||
@ -130,49 +140,52 @@ fn enter_block(blocktype: md4c.MD_BLOCKTYPE, detail: ?*anyopaque, userdata: ?*an
|
|||||||
just_write(state.writer, "☐ ");
|
just_write(state.writer, "☐ ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_style.font_style.dim = false;
|
temp_style.font_style.dim = false;
|
||||||
temp_style.font_style.bold = state.nested_bold_count > 0;
|
temp_style.font_style.bold = state.nested_bold_count > 0;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
}
|
}
|
||||||
if (blocktype == md4c.MD_BLOCK_UL or blocktype == md4c.MD_BLOCK_OL) {
|
if (blocktype == md4c.MD_BLOCK_UL or blocktype == md4c.MD_BLOCK_OL) {
|
||||||
just_write(state.writer, "\n");
|
//just_write(state.writer, "\n");
|
||||||
}
|
}
|
||||||
return 0; // TODO what does the return value represent ????
|
return 0; // TODO what does the return value represent ????
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leave_block(blocktype: md4c.MD_BLOCKTYPE, detail: ?*anyopaque, userdata: ?*anyopaque) callconv(.C) c_int {
|
fn leave_block(blocktype: md4c.MD_BLOCKTYPE, detail: ?*anyopaque, userdata: ?*anyopaque) callconv(.C) c_int {
|
||||||
//std.debug.print("leave {d}\n", .{blocktype});
|
|
||||||
const state: *State = @ptrCast(@alignCast(userdata));
|
const state: *State = @ptrCast(@alignCast(userdata));
|
||||||
|
|
||||||
if (blocktype == md4c.MD_BLOCK_H) {
|
if (blocktype == md4c.MD_BLOCK_H) {
|
||||||
just_write(state.writer, "\n\n");
|
just_write(state.writer, "\n\n");
|
||||||
}
|
}
|
||||||
if (blocktype == md4c.MD_BLOCK_P or blocktype == md4c.MD_BLOCK_LI) {
|
if (blocktype == md4c.MD_BLOCK_P or blocktype == md4c.MD_BLOCK_LI) {
|
||||||
|
//if (blocktype == md4c.MD_BLOCK_P) {
|
||||||
just_write(state.writer, "\n");
|
just_write(state.writer, "\n");
|
||||||
}
|
}
|
||||||
if (blocktype == md4c.MD_BLOCK_HR) {
|
if (blocktype == md4c.MD_BLOCK_HR) {
|
||||||
var winsize: std.c.winsize = undefined;
|
|
||||||
var temp_style = state.last_style;
|
var temp_style = state.last_style;
|
||||||
temp_style.font_style.dim = true;
|
temp_style.font_style.dim = true;
|
||||||
temp_style.font_style.bold = true;
|
temp_style.font_style.bold = true;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
const term_name_c = cstdio.ctermid(null);
|
|
||||||
const term_name = std.mem.sliceTo(term_name_c, 0);
|
const term_name = "/dev/tty";
|
||||||
const term_fd = std.posix.open(term_name, .{}, 0) catch errorexit(5, "failed to open terminal D:\n");
|
const term_fd = std.posix.open(term_name, .{}, 0) catch errorexit(5, "failed to open terminal D:\n");
|
||||||
|
|
||||||
|
var winsize: std.c.winsize = undefined;
|
||||||
if (std.c.ioctl(term_fd, std.c.T.IOCGWINSZ, &winsize) != -1) {
|
if (std.c.ioctl(term_fd, std.c.T.IOCGWINSZ, &winsize) != -1) {
|
||||||
for (0..winsize.ws_col) |_| {
|
for (0..winsize.col) |_| {
|
||||||
just_write(state.writer, "-");
|
just_write(state.writer, "-");
|
||||||
}
|
}
|
||||||
just_write(state.writer, "\n");
|
just_write(state.writer, "\n");
|
||||||
} else {
|
} else {
|
||||||
just_write(state.writer, "---\n");
|
just_write(state.writer, "---\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_style.font_style.dim = false;
|
temp_style.font_style.dim = false;
|
||||||
temp_style.font_style.bold = state.nested_bold_count > 0;
|
temp_style.font_style.bold = state.nested_bold_count > 0;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
}
|
}
|
||||||
if (blocktype == md4c.MD_BLOCK_UL or blocktype == md4c.MD_BLOCK_OL) {
|
if (blocktype == md4c.MD_BLOCK_UL or blocktype == md4c.MD_BLOCK_OL) {
|
||||||
just_write(state.writer, "\n");
|
//just_write(state.writer, "\n");
|
||||||
}
|
}
|
||||||
_ = detail;
|
_ = detail;
|
||||||
return 0; // TODO what does the return value represent ????
|
return 0; // TODO what does the return value represent ????
|
||||||
@ -201,14 +214,16 @@ fn enter_span(spantype: md4c.MD_SPANTYPE, detail: ?*anyopaque, userdata: ?*anyop
|
|||||||
temp_style.foreground = .Blue;
|
temp_style.foreground = .Blue;
|
||||||
temp_style.font_style.underline = true;
|
temp_style.font_style.underline = true;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
just_write(state.writer, "\x1b]8;;");
|
|
||||||
|
just_write(state.writer, "\x1b]8;;"); // OSC 8 link begin
|
||||||
|
|
||||||
const detail_a: *md4c.MD_SPAN_A_DETAIL = @ptrCast(@alignCast(detail));
|
const detail_a: *md4c.MD_SPAN_A_DETAIL = @ptrCast(@alignCast(detail));
|
||||||
const link_c = detail_a.href.text;
|
const link_c = detail_a.href.text;
|
||||||
const link = link_c[0..detail_a.href.size];
|
const link = link_c[0..detail_a.href.size];
|
||||||
|
|
||||||
just_write(state.writer, link);
|
just_write(state.writer, link);
|
||||||
just_write(state.writer, "\x07");
|
just_write(state.writer, "\x07"); // OSC 8 link seperate url and text
|
||||||
}
|
}
|
||||||
// TODO emit ansi to the writer
|
|
||||||
return 0; // TODO what does the return value represent ????
|
return 0; // TODO what does the return value represent ????
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,13 +247,13 @@ fn leave_span(spantype: md4c.MD_SPANTYPE, detail: ?*anyopaque, userdata: ?*anyop
|
|||||||
state.update_underline();
|
state.update_underline();
|
||||||
}
|
}
|
||||||
if (spantype == md4c.MD_SPAN_A) {
|
if (spantype == md4c.MD_SPAN_A) {
|
||||||
just_write(state.writer, "\x1b]8;;\x07");
|
just_write(state.writer, "\x1b]8;;\x07"); // OSC 8 link end
|
||||||
|
|
||||||
var temp_style = state.last_style;
|
var temp_style = state.last_style;
|
||||||
temp_style.foreground = .Default;
|
temp_style.foreground = .Default;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
state.update_underline();
|
state.update_underline();
|
||||||
}
|
}
|
||||||
// TODO emit ansi to the writer
|
|
||||||
return 0; // TODO what does the return value represent ????
|
return 0; // TODO what does the return value represent ????
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,14 +268,16 @@ fn on_text(texttype: md4c.MD_TEXTTYPE, text: [*c]const md4c.MD_CHAR, size: md4c.
|
|||||||
if (texttype == md4c.MD_TEXT_NULLCHAR) {
|
if (texttype == md4c.MD_TEXT_NULLCHAR) {
|
||||||
just_write(state.writer, "<EFBFBD>");
|
just_write(state.writer, "<EFBFBD>");
|
||||||
}
|
}
|
||||||
if (texttype == md4c.MD_TEXT_BR) {
|
if (texttype == md4c.MD_TEXT_BR or texttype == md4c.MD_TEXT_SOFTBR) {
|
||||||
just_write(state.writer, "\n");
|
just_write(state.writer, "\n");
|
||||||
}
|
}
|
||||||
if (texttype == md4c.MD_TEXT_CODE) {
|
if (texttype == md4c.MD_TEXT_CODE) {
|
||||||
var temp_style = state.last_style;
|
var temp_style = state.last_style;
|
||||||
temp_style.font_style.dim = true;
|
temp_style.font_style.dim = true;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
|
|
||||||
just_write(state.writer, text_data);
|
just_write(state.writer, text_data);
|
||||||
|
|
||||||
temp_style.font_style.dim = false;
|
temp_style.font_style.dim = false;
|
||||||
state.update_style(temp_style);
|
state.update_style(temp_style);
|
||||||
}
|
}
|
||||||
@ -269,7 +286,7 @@ fn on_text(texttype: md4c.MD_TEXTTYPE, text: [*c]const md4c.MD_CHAR, size: md4c.
|
|||||||
|
|
||||||
const parser: md4c.MD_PARSER = .{
|
const parser: md4c.MD_PARSER = .{
|
||||||
.abi_version = @intCast(0),
|
.abi_version = @intCast(0),
|
||||||
.flags = md4c.MD_FLAG_UNDERLINE | md4c.MD_FLAG_STRIKETHROUGH | md4c.MD_FLAG_COLLAPSEWHITESPACE | md4c.MD_FLAG_TASKLISTS | md4c.MD_FLAG_UNDERLINE,
|
.flags = md4c.MD_FLAG_UNDERLINE | md4c.MD_FLAG_STRIKETHROUGH | md4c.MD_FLAG_TASKLISTS | md4c.MD_FLAG_UNDERLINE,
|
||||||
|
|
||||||
.enter_block = &enter_block,
|
.enter_block = &enter_block,
|
||||||
.leave_block = &leave_block,
|
.leave_block = &leave_block,
|
||||||
@ -282,6 +299,7 @@ const parser: md4c.MD_PARSER = .{
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn errorexit(code: u8, comptime msg: []const u8) noreturn {
|
fn errorexit(code: u8, comptime msg: []const u8) noreturn {
|
||||||
|
std.debug.print("mdcat exiting with error: ", .{});
|
||||||
std.debug.print(msg, .{});
|
std.debug.print(msg, .{});
|
||||||
std.process.exit(code);
|
std.process.exit(code);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user