|
| 1 | +package dev.dochia.cli.core.command; |
| 2 | + |
| 3 | +import picocli.CommandLine.Command; |
| 4 | +import picocli.CommandLine.Option; |
| 5 | +import picocli.CommandLine.ParentCommand; |
| 6 | + |
| 7 | +import java.nio.charset.StandardCharsets; |
| 8 | +import java.time.Instant; |
| 9 | +import java.util.Random; |
| 10 | +import java.util.concurrent.Callable; |
| 11 | + |
| 12 | +@Command( |
| 13 | + name = "legend", |
| 14 | + description = "Tells the Dochia legend. Short version.", |
| 15 | + mixinStandardHelpOptions = true, |
| 16 | + hidden = true, |
| 17 | + showDefaultValues = true |
| 18 | +) |
| 19 | +public class LegendCommand implements Callable<Integer> { |
| 20 | + |
| 21 | + @Option(names = "--haiku", description = "Force haiku output.") |
| 22 | + boolean haiku; |
| 23 | + |
| 24 | + @Option(names = "--ascii", description = "Force ASCII mini output.") |
| 25 | + boolean ascii; |
| 26 | + |
| 27 | + @Option(names = "--seed", description = "Deterministic selection seed.") |
| 28 | + Long seed; |
| 29 | + |
| 30 | + @Option(names = "--no-color", description = "Disable ANSI colors (or set NO_COLOR env).") |
| 31 | + boolean noColor; |
| 32 | + |
| 33 | + @Option(names = "--width", description = "Target output width (for boxes).", defaultValue = "60") |
| 34 | + int width; |
| 35 | + |
| 36 | + @ParentCommand |
| 37 | + Object parent; // optional: access parent config if needed later |
| 38 | + |
| 39 | + private static final String[][] STORY_EN = new String[][]{ |
| 40 | + { |
| 41 | + "─────────────────────────────", |
| 42 | + " Dochia’s Legend", |
| 43 | + "─────────────────────────────", |
| 44 | + "Baba Dochia climbed the mountain,", |
| 45 | + "wearing nine coats, one on top of another.", |
| 46 | + "She thought spring had come,", |
| 47 | + "so she shed them one by one.", |
| 48 | + "", |
| 49 | + "Winter came back. She froze.", |
| 50 | + "… and turned to stone. 🪨", |
| 51 | + "", |
| 52 | + "Moral of the story?", |
| 53 | + "Never trust the weather forecast,", |
| 54 | + "and always test your edge cases. 😉", |
| 55 | + "", |
| 56 | + "─────────────────────────────", |
| 57 | + "Tip: Run `dochia --chaos` to shed", |
| 58 | + "some coats of your own.", |
| 59 | + "─────────────────────────────" |
| 60 | + } |
| 61 | + }; |
| 62 | + |
| 63 | + private static final String[][] HAIKU_EN = new String[][]{ |
| 64 | + {"Nine coats on the hill,", "Spring fooled her, winter returned —", "Stone remembers all."} |
| 65 | + }; |
| 66 | + |
| 67 | + private static final String[][] ASCII_EN = new String[][]{ |
| 68 | + { |
| 69 | + " /\\ Baba Dochia climbed high,", |
| 70 | + " / \\ dropping her nine coats…", |
| 71 | + " / ❄ \\ …then winter laughed.", |
| 72 | + "/______\\ Moral: never trust inputs. 😉" |
| 73 | + } |
| 74 | + }; |
| 75 | + |
| 76 | + @Override |
| 77 | + public Integer call() throws Exception { |
| 78 | + final boolean ansi = !noColor && System.getenv("NO_COLOR") == null; |
| 79 | + |
| 80 | + |
| 81 | + Variant chosen = chooseVariant(); |
| 82 | + String[][] lines = resolveLines(chosen); |
| 83 | + |
| 84 | + printBoxed(lines, ansi); |
| 85 | + return 0; |
| 86 | + } |
| 87 | + |
| 88 | + private enum Variant {STORY, HAIKU, ASCII} |
| 89 | + |
| 90 | + private Variant chooseVariant() { |
| 91 | + if (haiku && ascii) return Variant.HAIKU; // prefer explicit & deterministic |
| 92 | + if (haiku) return Variant.HAIKU; |
| 93 | + if (ascii) return Variant.ASCII; |
| 94 | + |
| 95 | + // Random choice for the easter egg when no explicit style is set |
| 96 | + Random rnd = (seed != null) ? new Random(seed) : seededRandom(); |
| 97 | + int pick = rnd.nextInt(3); |
| 98 | + return pick == 0 ? Variant.STORY : (pick == 1 ? Variant.HAIKU : Variant.ASCII); |
| 99 | + } |
| 100 | + |
| 101 | + private Random seededRandom() { |
| 102 | + // Derive a seed from current time, PID hash & some bytes to keep variety but reproducible per run |
| 103 | + long t = Instant.now().toEpochMilli(); |
| 104 | + long mix = t ^ System.identityHashCode(this); |
| 105 | + return new Random(mix); |
| 106 | + } |
| 107 | + |
| 108 | + private String[][] resolveLines(Variant v) { |
| 109 | + return switch (v) { |
| 110 | + case STORY -> STORY_EN; |
| 111 | + case HAIKU -> HAIKU_EN; |
| 112 | + case ASCII -> ASCII_EN; |
| 113 | + }; |
| 114 | + } |
| 115 | + |
| 116 | + private void printBoxed(String[][] content, boolean ansi) { |
| 117 | + final String top = repeat("─", Math.max(20, Math.min(120, width))); |
| 118 | + // Only add colored title bars for STORY; keep HAIKU/ASCII clean |
| 119 | + boolean banner = content[0].length > 8; // crude but effective |
| 120 | + |
| 121 | + if (banner && ansi) { |
| 122 | + System.out.println("\u001B[2m" + top + "\u001B[0m"); |
| 123 | + } else if (banner) { |
| 124 | + System.out.println(top); |
| 125 | + } |
| 126 | + |
| 127 | + for (String line : content[0]) { |
| 128 | + System.out.println(line); |
| 129 | + } |
| 130 | + |
| 131 | + if (banner && ansi) { |
| 132 | + System.out.println("\u001B[2m" + top + "\u001B[0m"); |
| 133 | + } else if (banner) { |
| 134 | + System.out.println(top); |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + private static String repeat(String s, int n) { |
| 139 | + byte[] unit = s.getBytes(StandardCharsets.UTF_8); |
| 140 | + byte[] out = new byte[unit.length * n]; |
| 141 | + for (int i = 0; i < n; i++) { |
| 142 | + System.arraycopy(unit, 0, out, i * unit.length, unit.length); |
| 143 | + } |
| 144 | + return new String(out, StandardCharsets.UTF_8); |
| 145 | + } |
| 146 | +} |
| 147 | + |
0 commit comments