const std = @import("std");const Allocator = std.mem.Allocator; pub fn leftTrim(line: []const u8) []const u8 { var i: usize = 0; while (i < line.len and (line[i] == ' ' or line[i] == '\t')) : (i += 1) {} return line[i..];} pub fn isSpace(c: u8) bool { return c == ' ' or c == '\t';} pub fn isBlankMarkdownLine(line: []const u8) bool { return std.mem.trim(u8, line, " \t").len == 0;} pub fn isAsciiAlpha(c: u8) bool { return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z');} pub fn isAsciiAlphaNumeric(c: u8) bool { return isAsciiAlpha(c) or (c >= '0' and c <= '9');} pub fn isAsciiWordByte(c: u8) bool { return isAsciiAlphaNumeric(c) or c == '_';} pub fn isAsciiWhitespace(c: u8) bool { return c == ' ' or c == '\t' or c == '\n' or c == '\r' or c == 0x0b or c == 0x0c;} pub fn isTrailingUrlPunctuation(c: u8) bool { return c == '.' or c == ',' or c == ';' or c == ':' or c == '!' or c == '?';} fn isEscapablePunctuation(byte: u8) bool { return switch (byte) { '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~' => true, else => false, };} pub fn isEscapedPunctuationAt(text: []const u8, index: usize) bool { if (index == 0 or index >= text.len or !isEscapablePunctuation(text[index])) return false; var slash_count: usize = 0; var cursor = index; while (cursor > 0 and text[cursor - 1] == '\\') : (cursor -= 1) { slash_count += 1; } return slash_count % 2 == 1;} pub fn appendEscapedPunctuation(alloc: Allocator, out: *std.ArrayList(u8), text: []const u8) !void { var i: usize = 0; while (i < text.len) { if (text[i] == '\\' and i + 1 < text.len and isEscapedPunctuationAt(text, i + 1)) { try out.append(alloc, text[i + 1]); i += 2; continue; } try out.append(alloc, text[i]); i += 1; }} pub fn withoutTerminalHardBreakMarker(line: []const u8, line_has_lf: bool) []const u8 { if (!line_has_lf or line.len == 0 or line[line.len - 1] != '\\') return line; var slash_start = line.len; while (slash_start > 0 and line[slash_start - 1] == '\\') : (slash_start -= 1) {} if ((line.len - slash_start) % 2 == 0) return line; return line[0 .. line.len - 1];} fn isExactUnderscoreRun(text: []const u8, index: usize, marker_len: usize) bool { if (marker_len != 1 and marker_len != 2) return false; if (index + marker_len > text.len) return false; if (index > 0 and text[index - 1] == '_') return false; if (index + marker_len < text.len and text[index + marker_len] == '_') return false; for (text[index .. index + marker_len]) |c| if (c != '_') return false; return true;} pub fn isValidUnderscoreOpen(text: []const u8, index: usize, marker_len: usize) bool { if (!isExactUnderscoreRun(text, index, marker_len)) return false; const after = index + marker_len; if (after >= text.len or isSpace(text[after])) return false; return index == 0 or !isAsciiWordByte(text[index - 1]);} pub fn isValidUnderscoreClose(text: []const u8, index: usize, marker_len: usize) bool { if (!isExactUnderscoreRun(text, index, marker_len)) return false; if (index == 0 or isSpace(text[index - 1])) return false; const after = index + marker_len; return after >= text.len or !isAsciiWordByte(text[after]);} pub fn findUnderscoreCloser(text: []const u8, start: usize, marker_len: usize) ?usize { var i = start; var in_code = false; while (i < text.len) : (i += 1) { if (text[i] == '`') { in_code = !in_code; continue; } if (!in_code and isValidUnderscoreClose(text, i, marker_len)) return i; } return null;} pub fn nthLine(buf: []const u8, n: usize) ?[]const u8 { var idx: usize = 0; var start: usize = 0; while (start < buf.len) { const end = std.mem.indexOfScalarPos(u8, buf, start, '\n') orelse buf.len; if (idx == n) return buf[start..end]; idx += 1; start = end + 1; } return null;}
const std = @import("std");
const Allocator = std.mem.Allocator;
pub fn leftTrim(line: []const u8) []const u8 {
var i: usize = 0;
while (i < line.len and (line[i] == ' ' or line[i] == '\t')) : (i += 1) {}
return line[i..];
}
pub fn isSpace(c: u8) bool {
return c == ' ' or c == '\t';
}
pub fn isBlankMarkdownLine(line: []const u8) bool {
return std.mem.trim(u8, line, " \t").len == 0;
}
pub fn isAsciiAlpha(c: u8) bool {
return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z');
}
pub fn isAsciiAlphaNumeric(c: u8) bool {
return isAsciiAlpha(c) or (c >= '0' and c <= '9');
}
pub fn isAsciiWordByte(c: u8) bool {
return isAsciiAlphaNumeric(c) or c == '_';
}
pub fn isAsciiWhitespace(c: u8) bool {
return c == ' ' or c == '\t' or c == '\n' or c == '\r' or c == 0x0b or c == 0x0c;
}
pub fn isTrailingUrlPunctuation(c: u8) bool {
return c == '.' or c == ',' or c == ';' or c == ':' or c == '!' or c == '?';
}
fn isEscapablePunctuation(byte: u8) bool {
return switch (byte) {
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~' => true,
else => false,
};
}
pub fn isEscapedPunctuationAt(text: []const u8, index: usize) bool {
if (index == 0 or index >= text.len or !isEscapablePunctuation(text[index])) return false;
var slash_count: usize = 0;
var cursor = index;
while (cursor > 0 and text[cursor - 1] == '\\') : (cursor -= 1) {
slash_count += 1;
}
return slash_count % 2 == 1;
}
pub fn appendEscapedPunctuation(alloc: Allocator, out: *std.ArrayList(u8), text: []const u8) !void {
var i: usize = 0;
while (i < text.len) {
if (text[i] == '\\' and i + 1 < text.len and isEscapedPunctuationAt(text, i + 1)) {
try out.append(alloc, text[i + 1]);
i += 2;
continue;
}
try out.append(alloc, text[i]);
i += 1;
}
}
pub fn withoutTerminalHardBreakMarker(line: []const u8, line_has_lf: bool) []const u8 {
if (!line_has_lf or line.len == 0 or line[line.len - 1] != '\\') return line;
var slash_start = line.len;
while (slash_start > 0 and line[slash_start - 1] == '\\') : (slash_start -= 1) {}
if ((line.len - slash_start) % 2 == 0) return line;
return line[0 .. line.len - 1];
}
fn isExactUnderscoreRun(text: []const u8, index: usize, marker_len: usize) bool {
if (marker_len != 1 and marker_len != 2) return false;
if (index + marker_len > text.len) return false;
if (index > 0 and text[index - 1] == '_') return false;
if (index + marker_len < text.len and text[index + marker_len] == '_') return false;
for (text[index .. index + marker_len]) |c| if (c != '_') return false;
return true;
}
pub fn isValidUnderscoreOpen(text: []const u8, index: usize, marker_len: usize) bool {
if (!isExactUnderscoreRun(text, index, marker_len)) return false;
const after = index + marker_len;
if (after >= text.len or isSpace(text[after])) return false;
return index == 0 or !isAsciiWordByte(text[index - 1]);
}
pub fn isValidUnderscoreClose(text: []const u8, index: usize, marker_len: usize) bool {
if (!isExactUnderscoreRun(text, index, marker_len)) return false;
if (index == 0 or isSpace(text[index - 1])) return false;
const after = index + marker_len;
return after >= text.len or !isAsciiWordByte(text[after]);
}
pub fn findUnderscoreCloser(text: []const u8, start: usize, marker_len: usize) ?usize {
var i = start;
var in_code = false;
while (i < text.len) : (i += 1) {
if (text[i] == '`') {
in_code = !in_code;
continue;
}
if (!in_code and isValidUnderscoreClose(text, i, marker_len)) return i;
}
return null;
}
pub fn nthLine(buf: []const u8, n: usize) ?[]const u8 {
var idx: usize = 0;
var start: usize = 0;
while (start < buf.len) {
const end = std.mem.indexOfScalarPos(u8, buf, start, '\n') orelse buf.len;
if (idx == n) return buf[start..end];
idx += 1;
start = end + 1;
}
return null;
}