Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions src/formatters/markdown.zig
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ pub const WriteContext = struct {
write_element_fn: ?WriteElementFunction = null,
/// Optional user data for custom handlers
user_data: ?*anyopaque = null,
/// Optional number of spaces to convert tabs to.
convert_tab_size: ?u8 = null,
};

/// Configuration options for Markdown rendering.
Expand All @@ -98,6 +100,9 @@ pub const Options = struct {
/// This data is available in the WriteContext and can be used by
/// custom element handlers for application-specific rendering logic.
user_data: ?*anyopaque = null,

/// Optional number of spaces to convert tabs to.
convert_tab_size: ?u8 = null,
};

pub const MarkdownElement = enum {
Expand Down Expand Up @@ -153,6 +158,7 @@ pub fn renderDocument(allocator: Allocator, doc: Document, writer: *std.io.Write
.writer = writer,
.write_element_fn = options.write_element_fn,
.user_data = options.user_data,
.convert_tab_size = options.convert_tab_size,
};

try render(doc.root, &ctx);
Expand Down Expand Up @@ -282,16 +288,33 @@ pub fn writeListElement(node: Node, ctx: *const WriteContext) anyerror!void {
}

pub fn writeTextElement(node: Node, ctx: *const WriteContext) anyerror!void {
const text = try std.mem.replaceOwned(
var text: []const u8 = try node.getText();

const replaced_newlines = try std.mem.replaceOwned(
u8,
ctx.allocator,
try node.getText(),
text,
"\n",
"\n\n",
);
defer ctx.allocator.free(text);
defer ctx.allocator.free(replaced_newlines);
text = replaced_newlines;

var replaced_tabs: ?[]const u8 = null;
defer if (replaced_tabs != null) ctx.allocator.free(replaced_tabs.?);
if (ctx.convert_tab_size) |convert_tab_size| {
replaced_tabs = try std.mem.replaceOwned(
u8,
ctx.allocator,
text,
"\t",
spaces[0..convert_tab_size],
);
text = replaced_tabs.?;
}

try ctx.writer.writeAll(text);
try ctx.writer.flush();
}

pub fn writeListItemElement(node: Node, ctx: *const WriteContext) anyerror!void {
Expand Down Expand Up @@ -328,7 +351,22 @@ pub fn writeCodeElement(node: Node, ctx: *const WriteContext) !void {
pub fn writeAllChildrenText(node: Node, ctx: *const WriteContext) !void {
var it = node.iterator(.{ .type = .text });
while (it.next()) |child| {
try ctx.writer.writeAll(try child.getText());
var text: []const u8 = try child.getText();

var replaced_tabs: ?[]const u8 = null;
defer if (replaced_tabs != null) ctx.allocator.free(replaced_tabs.?);
if (ctx.convert_tab_size) |convert_tab_size| {
replaced_tabs = try std.mem.replaceOwned(
u8,
ctx.allocator,
text,
"\t",
spaces[0..convert_tab_size],
);
text = replaced_tabs.?;
}

try ctx.writer.writeAll(text);
}
}

Expand Down Expand Up @@ -503,3 +541,5 @@ const Document = @import("../Document.zig");
const std = @import("std");
const testing = std.testing;
const logger = std.log.scoped(.markdown_formatter);

const spaces: []const u8 = " " ** 255;
33 changes: 25 additions & 8 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub const std_options: Options = .{
var config = struct {
input: ?[]const u8 = null,
output: ?[]const u8 = null,
convert_tab_size: ?[]const u8 = null,
}{};

const StreamSource = enum {
Expand Down Expand Up @@ -34,6 +35,11 @@ pub fn main() !void {
.help = "output file",
.value_ref = r.mkRef(&config.output),
},
.{
.long_name = "convert_tab_size",
.help = "Convert tabs to given number of spaces within [0, 255]",
.value_ref = r.mkRef(&config.convert_tab_size),
},
}),
.target = cli.CommandTarget{
.action = cli.CommandAction{ .exec = processConfig },
Expand All @@ -44,15 +50,22 @@ pub fn main() !void {
}

fn processConfig() !void {
const input_source: StreamSource = if (config.input == null) .stdin else .file;
const output_source: StreamDestination = if (config.output == null) .stdout else .file;
const convert_tab_size: ?u8 = if (config.convert_tab_size == null)
null
else
std.fmt.parseInt(u8, config.convert_tab_size.?, 10) catch {
std.log.err("convert_tab_size must be an integer in range of [0, 255]", .{});
return error.ConvertTabSizeInvalid;
};

var input_file: File = undefined;
defer input_file.close();

var output_file: File = undefined;
defer output_file.close();

const input_source: StreamSource = if (config.input == null) .stdin else .file;
const output_source: StreamDestination = if (config.output == null) .stdout else .file;

switch (input_source) {
.file => {
input_file = try cwd().openFile(config.input.?, .{});
Expand All @@ -64,7 +77,7 @@ fn processConfig() !void {

switch (output_source) {
.file => {
output_file = try cwd().openFile(config.output.?, .{});
output_file = try cwd().createFile(config.output.?, .{});
},
.stdout => {
output_file = std.fs.File.stdout();
Expand All @@ -74,17 +87,21 @@ fn processConfig() !void {
var in_buf: [1024]u8 = undefined;
var out_buf: [1024]u8 = undefined;

var reader = input_file.reader(&in_buf).interface;
var writer = output_file.writer(&out_buf).interface;
var file_reader = input_file.reader(&in_buf);
const reader = &file_reader.interface;
var file_writer = output_file.writer(&out_buf);
const writer = &file_writer.interface;

var arena = ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();

const document = try lib.load(allocator, &reader, .{});
const document = try lib.load(allocator, reader, .{});
defer document.deinit();

try renderDocument(allocator, document, &writer, .{});
try renderDocument(allocator, document, writer, .{
.convert_tab_size = convert_tab_size,
});
}

const cwd = std.fs.cwd;
Expand Down