PranitInitial commit439f83c3 months agoHistory
258 lines · 8.2 KB · Zig
RawDownload
const std = @import("std");const tu = @import("text_util.zig"); pub fn codeFenceMarker(line: []const u8) ?u8 {    if (line.len < 3) return null;    const marker = line[0];    if (marker != '`' and marker != '~') return null;    if (line[1] != marker or line[2] != marker) return null;    return marker;} fn isCodeFence(line: []const u8) bool {    return codeFenceMarker(line) != null;} pub fn codeFenceLanguage(line: []const u8) []const u8 {    const info = std.mem.trim(u8, line[3..], " \t");    const end = std.mem.indexOfAny(u8, info, " \t") orelse info.len;    return info[0..end];} pub fn hasIndentedCodePrefix(line: []const u8) bool {    return line.len > 0 and (line[0] == '\t' or (line.len >= 4 and std.mem.eql(u8, line[0..4], "    ")));} pub fn deindentCodeLine(line: []const u8) []const u8 {    if (line[0] == '\t') return line[1..];    return line[4..];} const ParsedHeader = struct {    level: usize,    content: []const u8,}; pub fn parseHeader(line: []const u8) ?ParsedHeader {    if (line.len == 0 or line[0] != '#') return null;    var level: usize = 0;    while (level < 6 and level < line.len and line[level] == '#') : (level += 1) {}    if (level == 0 or level >= line.len) return null;    if (line[level] != ' ') return null;    return .{ .level = level, .content = line[level + 1 ..] };} pub fn parseSetextUnderline(line: []const u8) ?usize {    var level: ?usize = null;    for (line) |byte| {        if (byte == ' ' or byte == '\t') continue;        const next_level: usize = switch (byte) {            '=' => 1,            '-' => 2,            else => return null,        };        if (level) |existing| {            if (existing != next_level) return null;        } else {            level = next_level;        }    }    return level;} pub fn isSetextCandidate(line: []const u8) bool {    if (line.len == 0 or line[0] == ' ' or line[0] == '\t' or line[0] == ':') return false;    if (parseHeader(line) != null or parseBlockquote(line) != null) return false;    if (parseUnorderedList(line) != null or parseOrderedList(line) != null) return false;    return !isCodeFence(line) and        !isPipeLine(line) and        !isHorizontalRule(line) and        parseSetextUnderline(line) == null;} pub fn definitionMarkerBody(line: []const u8) ?[]const u8 {    if (line.len < 2 or line[0] != ':') return null;    var body_start: usize = 1;    while (body_start < line.len and (line[body_start] == ' ' or line[body_start] == '\t')) : (body_start += 1) {}    if (body_start == 1 or body_start == line.len) return null;    return line[body_start..];} pub const ParsedFootnoteDefinition = struct {    label: []const u8,    body: []const u8,}; pub fn parseFootnoteDefinition(line: []const u8) ?ParsedFootnoteDefinition {    if (line.len < 6 or line[0] != '[' or line[1] != '^') return null;    const close = std.mem.indexOfScalarPos(u8, line, 2, ']') orelse return null;    if (close == 2 or close + 1 >= line.len or line[close + 1] != ':') return null;     var body_start = close + 2;    while (body_start < line.len and (line[body_start] == ' ' or line[body_start] == '\t')) : (body_start += 1) {}    if (body_start == line.len) return null;    return .{ .label = line[2..close], .body = line[body_start..] };} pub fn footnoteContinuationBody(line: []const u8) ?[]const u8 {    if (line.len > 0 and line[0] == '\t') return line[1..];    if (line.len >= 2 and line[0] == ' ' and line[1] == ' ') return line[2..];    return null;} const ParsedBlockquote = struct {    indent: []const u8,    depth: usize,    content: []const u8,}; pub const BlockquotePrefix = struct {    indent: usize,    depth: usize,}; pub fn parseBlockquote(line: []const u8) ?ParsedBlockquote {    var i: usize = 0;    while (i < line.len and line[i] == ' ') : (i += 1) {}    const indent_end = i;    if (i == line.len or line[i] != '>') return null;    i += 1;    if (i == line.len) return .{ .indent = line[0..indent_end], .depth = 1, .content = line[i..] };    if (line[i] != ' ') return null;    i += 1;     var depth: usize = 1;    while (i < line.len and line[i] == '>') {        const after_marker = i + 1;        if (after_marker < line.len and line[after_marker] != ' ') break;        depth += 1;        i = after_marker;        if (i == line.len) break;        i += 1;    }    return .{ .indent = line[0..indent_end], .depth = depth, .content = line[i..] };} pub fn isBlockquoteParagraph(line: []const u8) bool {    return isLazyBlockquoteContinuation(line);} pub fn isLazyBlockquoteContinuation(line: []const u8) bool {    if (std.mem.trim(u8, line, " \t").len == 0) return false;    if (parseBlockquote(line) != null or parseHeader(line) != null) return false;    if (parseUnorderedList(line) != null or parseOrderedList(line) != null) return false;    return codeFenceMarker(tu.leftTrim(line)) == null and        !isPipeLine(line) and        !isHorizontalRule(line);} const ParsedUnorderedList = struct {    indent: []const u8,    content: []const u8,}; pub fn parseUnorderedList(line: []const u8) ?ParsedUnorderedList {    var i: usize = 0;    while (i < line.len and (line[i] == ' ' or line[i] == '\t')) : (i += 1) {}    if (i + 1 >= line.len) return null;    if (line[i] != '-' and line[i] != '*') return null;    if (line[i + 1] != ' ') return null;    return .{ .indent = line[0..i], .content = line[i + 2 ..] };} const ParsedOrderedList = struct {    indent: []const u8,    marker: []const u8,    content: []const u8,}; pub fn parseOrderedList(line: []const u8) ?ParsedOrderedList {    var i: usize = 0;    while (i < line.len and (line[i] == ' ' or line[i] == '\t')) : (i += 1) {}    const indent_end = i;    while (i < line.len and line[i] >= '0' and line[i] <= '9') : (i += 1) {}    if (i == indent_end) return null;    if (i + 1 >= line.len) return null;    if (line[i] != '.') return null;    if (line[i + 1] != ' ') return null;    return .{        .indent = line[0..indent_end],        .marker = line[indent_end .. i + 1],        .content = line[i + 2 ..],    };} pub const ParsedTaskListItem = struct {    completed: bool,    has_separator: bool,    content: []const u8,}; pub fn parseTaskListItem(content: []const u8) ?ParsedTaskListItem {    if (content.len < 3 or content[0] != '[' or content[2] != ']') return null;    const completed = switch (content[1]) {        ' ' => false,        'x', 'X' => true,        else => return null,    };    if (content.len == 3) {        return .{ .completed = completed, .has_separator = false, .content = content[3..] };    }    if (content[3] != ' ') return null;    return .{ .completed = completed, .has_separator = true, .content = content[4..] };} pub fn isHorizontalRule(line: []const u8) bool {    const trimmed = tu.leftTrim(line);    if (trimmed.len < 3) return false;    const rule_char = trimmed[0];    if (rule_char != '-' and rule_char != '*' and rule_char != '_') return false;    var count: usize = 0;    for (trimmed) |c| {        if (c == rule_char) {            count += 1;        } else if (c != ' ' and c != '\t') {            return false;        }    }    return count >= 3;} pub fn isPipeLine(line: []const u8) bool {    const trimmed = tu.leftTrim(line);    if (trimmed.len == 0) return false;    for (trimmed, 0..) |byte, index| {        if (byte == '|' and !tu.isEscapedPunctuationAt(trimmed, index)) return true;    }    return false;} fn isSeparatorLine(line: []const u8) bool {    const trimmed = tu.leftTrim(line);    if (trimmed.len == 0) return false;    var seen_dash = false;    var seen_pipe = false;    for (trimmed) |c| {        switch (c) {            '|' => seen_pipe = true,            ':', ' ', '\t' => {},            '-' => seen_dash = true,            else => return false,        }    }    return seen_dash and seen_pipe;} pub fn isValidTable(buf: []const u8) bool {    var line_count: usize = 0;    var saw_separator = false;    var start: usize = 0;    while (start < buf.len) {        const end = std.mem.indexOfScalarPos(u8, buf, start, '\n') orelse buf.len;        const line = buf[start..end];        if (line_count == 1 and isSeparatorLine(line)) saw_separator = true;        line_count += 1;        start = end + 1;    }    return line_count >= 2 and saw_separator;}