216 lines
7.8 KiB
Zig
216 lines
7.8 KiB
Zig
//TODO:
|
|
const std = @import("std");
|
|
const clap = @import("clap");
|
|
const print = std.debug.print;
|
|
const io = std.io;
|
|
const fs = std.fs;
|
|
const json = std.json;
|
|
const cwd = fs.cwd();
|
|
const indexOf = std.mem.indexOf;
|
|
const expect = std.testing.expect;
|
|
|
|
const Card = struct {
|
|
name: []const u8 = "",//string
|
|
mana_cost: []const u8 = "",//string
|
|
cmc: f32 = 0,//technically a float? but I think we can always cast safely cast. EDIT: NOPE
|
|
type_line: []const u8 = "",//string
|
|
oracle_text: []const u8 = "",//string
|
|
card_faces: ?[]Card = null,//array of cards
|
|
};
|
|
|
|
const TextCard = struct {
|
|
text: [][]const u8,
|
|
};
|
|
|
|
const PandocOptions = &[_][]const u8{
|
|
"pandoc",
|
|
"out.md",
|
|
"-o", "out.pdf",
|
|
"--pdf-engine", "xelatex",
|
|
"-V", "mainfont:Liberation Mono",
|
|
"-V", "geometry:margin=0cm"
|
|
};
|
|
|
|
const cardWidth = 30;
|
|
const cardHeight = 32;
|
|
const pageHeight = 66;
|
|
var heightMayVary = false;
|
|
const formatString = "{s: <" ++ std.fmt.digits2(cardWidth) ++ "}";
|
|
//kind of ugly to look at but I wanted to emphasize that there is one space on either side of the formatted string
|
|
const lineFormatter = "|" ++ " " ++ formatString ++ " ";
|
|
const spacer: []const u8 = "|" ++ (" " ** (cardWidth + 2));
|
|
const oracleFileName = "oracle-cards-20240205220208.js";
|
|
|
|
pub fn main() !void {
|
|
var args = try std.process.argsWithAllocator(std.heap.page_allocator);
|
|
|
|
//handle program name argument
|
|
_ = args.next();
|
|
const listFileName: []const u8 = args.next() orelse {return error.ExpectedArgument;};
|
|
heightMayVary = stringToBool(args.next());
|
|
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const oracleFile = try cwd.openFile(oracleFileName, .{});
|
|
var jsonReader = json.reader(allocator, oracleFile.reader());
|
|
const parsedJson = try json.parseFromTokenSource([]Card, allocator, &jsonReader, .{.ignore_unknown_fields = true});
|
|
|
|
var line = std.ArrayList(u8).init(allocator);
|
|
var cards = std.ArrayList(Card).init(allocator);
|
|
var rowToPrint = std.ArrayList(Card).init(allocator);
|
|
|
|
const listReader = (try cwd.openFile(listFileName, .{})).reader();
|
|
|
|
var allPrinted = std.ArrayList(u8).init(allocator);
|
|
|
|
while (listReader.streamUntilDelimiter(line.writer(), '\n', null)) {
|
|
defer line.clearRetainingCapacity();
|
|
const cardName = line.items[indexOf(u8, line.items, " ").? + 1..indexOf(u8, line.items, "(").? - 1];
|
|
|
|
// TODO: this seems rather pointlessly time-intensive
|
|
for(parsedJson.value) |cardObj| {
|
|
if (std.mem.eql(u8, cardObj.name, cardName)) {
|
|
try cards.append(cardObj);
|
|
}
|
|
}
|
|
} else |err| switch(err) {
|
|
error.EndOfStream => {},
|
|
else => return err,
|
|
}
|
|
|
|
|
|
try sortCards(&cards);
|
|
//TODO (fixme): absolutely GARBAGE hack to get pandoc to preserve whitespace
|
|
try allPrinted.appendSlice("```\n");
|
|
|
|
for(cards.items) |cardObj| {
|
|
try rowToPrint.append(cardObj);
|
|
if(rowToPrint.items.len >= 3) {
|
|
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
|
|
try allPrinted.append('\n');
|
|
rowToPrint.clearAndFree();
|
|
}
|
|
} else {
|
|
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
|
|
try allPrinted.append('\n');
|
|
try allPrinted.appendSlice("```\n");
|
|
std.debug.print("{s}", .{allPrinted.items});
|
|
try cwd.writeFile2(.{.sub_path = "out.md", .data = allPrinted.items});
|
|
rowToPrint.clearAndFree();
|
|
var pandocProcess = std.ChildProcess.init(PandocOptions, allocator);
|
|
_ = try pandocProcess.spawnAndWait();
|
|
}
|
|
}
|
|
|
|
//TODO (fixme): card() needs an allocator...
|
|
fn compareTwo(_: void, a: Card, b: Card) bool {
|
|
return card(a).len > card(b).len;
|
|
}
|
|
fn sortCards(cards: *std.ArrayList(Card)) !void {
|
|
std.mem.sort(Card, cards.items, {}, compareTwo);
|
|
}
|
|
|
|
fn card(allocator: std.mem.Allocator, cardObj: Card,) ![][]const u8 {
|
|
var cardText = std.ArrayList([]const u8).init(allocator);
|
|
|
|
var fullUnformattedText = std.ArrayList(u8).init(allocator);
|
|
try fullUnformattedText.appendSlice(cardObj.name);
|
|
if(cardObj.mana_cost.len > 0) {
|
|
try fullUnformattedText.append(' ');
|
|
try fullUnformattedText.appendSlice(cardObj.mana_cost);
|
|
}
|
|
try fullUnformattedText.appendSlice(try std.mem.concat(allocator, u8, &[_][]const u8{
|
|
" (",
|
|
cardObj.type_line,
|
|
") >> "
|
|
}));
|
|
|
|
try fullUnformattedText.appendSlice(cardObj.oracle_text);
|
|
|
|
if(cardObj.card_faces) |faces| {
|
|
for(faces, 0..) |face, idx| {
|
|
try fullUnformattedText.appendSlice(face.oracle_text);
|
|
if(idx == 0) try fullUnformattedText.appendSlice(" // ");
|
|
}
|
|
}
|
|
|
|
var line = std.ArrayList(u8).init(allocator);
|
|
var word = std.ArrayList(u8).init(allocator);
|
|
|
|
for(fullUnformattedText.items) |char| {
|
|
try switch(char) {
|
|
'\n', ' ' => addWord(&word, &line, &cardText),
|
|
else => word.append(char)
|
|
};
|
|
} else {
|
|
try addWord(&word, &line, &cardText);
|
|
try cardText.append(try line.toOwnedSlice());
|
|
line.clearAndFree();
|
|
}
|
|
while(!heightMayVary and cardText.items.len < cardHeight) {
|
|
try cardText.append(" " ** cardWidth);
|
|
}
|
|
|
|
return cardText.items;
|
|
}
|
|
|
|
fn addWord(word: *std.ArrayList(u8), line: *std.ArrayList(u8), cardText: *std.ArrayList([]const u8)) !void {
|
|
if(line.items.len + word.items.len >= cardWidth) {
|
|
try cardText.append(try line.toOwnedSlice());
|
|
line.clearAndFree();
|
|
}
|
|
try line.appendSlice(word.items);
|
|
try line.append(' ');
|
|
word.clearAndFree();
|
|
}
|
|
|
|
const linesList = std.MultiArrayList(cardRow);
|
|
const cardRow = struct{
|
|
first: []const u8 = spacer,
|
|
second: []const u8 = spacer,
|
|
third: []const u8 = spacer,
|
|
last: []const u8 = "\n",
|
|
fn print(allocator: std.mem.Allocator, cards: []Card, allPrinted: *std.ArrayList(u8)) !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
|
|
var lines = linesList{};
|
|
defer lines.deinit(gpa.allocator());
|
|
|
|
for(cards, 0..) |cardObj, cardNo| {
|
|
const cardText = try card(allocator, cardObj);
|
|
for(cardText, 0..) |line, idx| {
|
|
const paddedLine = try std.fmt.allocPrint(gpa.allocator(), lineFormatter, .{line});
|
|
const placeholder = if(idx < lines.items(.first).len) lines.get(idx) else cardRow{};
|
|
const new: cardRow = switch(cardNo) {
|
|
0 => .{.first = paddedLine},
|
|
1 => .{.first = placeholder.first, .second = paddedLine},
|
|
2 => .{.first = placeholder.first, .second = placeholder.second, .third = paddedLine},
|
|
else => unreachable
|
|
};
|
|
if(idx < lines.items(.first).len) {
|
|
lines.set(idx, new);
|
|
} else {
|
|
try lines.append(gpa.allocator(), new);
|
|
}
|
|
}
|
|
}
|
|
// const numRows = @mod(allPrinted.items.len, cardHeight);
|
|
// if((numRows + cardHeight) >
|
|
for(lines.items(.first), 0..) |_,idx| {
|
|
_ = lines.get(idx);
|
|
const line = lines.get(idx);
|
|
// std.debug.print("{s}{s}{s}\n", .{line.first, line.second, line.third});
|
|
try allPrinted.appendSlice(line.first);
|
|
try allPrinted.appendSlice(line.second);
|
|
try allPrinted.appendSlice(line.third);
|
|
try allPrinted.appendSlice(line.last);
|
|
}
|
|
}
|
|
};
|
|
|
|
fn stringToBool(str: ?[]const u8) bool {
|
|
return std.mem.eql(u8, (str orelse "false"), "true");
|
|
}
|