Expand description
GitHub’s contribution chart, in the terminal.
mossaic draws the year the way github.com draws it: Primer’s own
colours, read out of the stylesheets GitHub serves; cells at its own geometry
— an 11px square on a 14px pitch, rounded by 2px; and, on a terminal that
draws pixels, actual anti-aliased rounded squares rather than characters
shaped like them.
This is the library the three binaries share:
| binary | what it is |
|---|---|
mossaic | the chart |
mossaic-art | writes text into a contribution graph by dating commits |
mossaic-glyphs | shows the fallback cells, to check what a terminal renders |
§The chart, as pixels
graphics rasterises a year and hands it to whichever protocol the
terminal speaks — graphics::kitty for RGBA over the kitty graphics
protocol, graphics::sixel for a palette and six pixels to a byte. The
same image goes to png for a terminal that draws neither.
use mossaic::graphics;
use mossaic::primer::{Appearance, Palette, Season};
// `levels[week][weekday]`: GitHub's shade, or None for a day it does not draw.
let mut levels = vec![[None; 7]; 53];
levels[20][3] = Some(4);
let palette = Palette::new(Appearance::Dark, Season::Default, true);
assert_eq!(palette.levels[4], mossaic::primer::Rgb::hex(0x56d364));
// One character cell is 10x20 pixels here; a day gets two columns and one row.
let image = graphics::grid(&levels, &palette, (10, 20));
assert_eq!((image.width, image.height), (53 * 2 * 10, 7 * 20));
let escape = graphics::sixel(&image, palette.canvas);
assert!(escape.starts_with("\x1bP0;1;0q"));§Writing text into a year
art draws characters as pixels on the calendar and maps every lit one to
a date. Letters are five columns wide with one between, and five rows tall on
Mon–Fri, so eight characters is what a year holds: nine need all 53
columns, and the first and last are partial weeks.
use mossaic::art::{self, Grid, Ink, Shades};
let grid = Grid::new(2027).unwrap();
let columns = art::bitmap("VYNCINT")?;
assert_eq!(columns.len(), 41); // 6N - 1
// Letters against an empty graph: the classic look.
let placed = art::place(&columns, &grid, 1, None, Ink { lit: 4, field: 0 })?;
assert_eq!(placed.skipped, 0, "centred, so nothing falls outside the year");
assert_eq!(placed.lit.len(), 75);
assert!(placed.field.is_empty());Leaving the background empty means not contributing on the other three
hundred days. art::Shades draws it as a colour instead, so the letters
are the difference between two greens and the year stays busy:
use mossaic::art::{self, Grid, Shades};
use mossaic::primer::Legibility;
let grid = Grid::new(2027).unwrap();
let columns = art::bitmap("VYNCINT")?;
let shades = Shades { ink: 4, field: 1 };
shades.check()?;
// Level 1 under level 4 is plain in every palette GitHub ships.
assert_eq!(shades.worst().0, Legibility::Clear);
// Four commits on a letter day makes four the year'"'"'s peak; one commit is
// then level 1, and the background costs a commit a day.
let ink = shades.commits(4);
assert_eq!((ink.lit, ink.field), (4, 1));
let placed = art::place(&columns, &grid, 1, None, ink)?;
assert_eq!(placed.lit.len(), 75);
assert_eq!(placed.field.len(), 365 - 75, "every other day of the year");§Asking the terminal
term::probe asks rather than guesses: one write, four questions, one round
trip, and a deadline for the terminals that stay silent. What comes back
decides the protocol, the image scale, and whether the palette is the light
one or the dark one.
Modules§
- app
- Application state, key and mouse handling, and the background fetch plumbing.
- art
- Writing text into a contribution graph by dating commits.
- calendar
- Contribution calendar model: a Sunday-aligned grid of days plus derived stats.
- cli
- The small amount of argument parsing every binary here needs.
- github
- Fetches the contribution calendar through the
ghCLI, so authentication is whatevergh auth loginalready set up — no token handling here. - graphics
- The chart as pixels, for terminals that draw them.
- plan
- Tracking a contribution-art plan against what actually happened.
- png
- A PNG encoder, so a chart can be looked at without a terminal that draws pixels — the same rasteriser output, in a file anything can open.
- primer
- Primer colours, read out of the stylesheets github.com actually serves rather
than transcribed from memory. Every value here is a
--contribution-*/--fgColor-*/--bgColor-*custom property lifted fromlight-*.css,dark-*.cssanddark_dimmed-*.css, so the chart is the same green as the one in the browser. - term
- What the terminal can actually do — asked, not guessed.
- ui
- Rendering. The whole screen is one paragraph of pre-built lines, so the chart
never needs layout maths beyond choosing a cell style that fits — and when the
terminal draws pixels, the seven weekday rows are left blank for
crate::graphicsto paint into, which is whyLayouttravels back out to the app: the painter and the mouse both need to know where the grid landed.
Enums§
- Colour
- When to colour output.