//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"); }