Skip to main content

Crate mossaic

Crate mossaic 

Source
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:

binarywhat it is
mossaicthe chart
mossaic-artwrites text into a contribution graph by dating commits
mossaic-glyphsshows 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 gh CLI, so authentication is whatever gh auth login already 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 from light-*.css, dark-*.css and dark_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::graphics to paint into, which is why Layout travels back out to the app: the painter and the mouse both need to know where the grid landed.

Enums§

Colour
When to colour output.

Functions§

printable
Text with its control characters removed, for anything that reaches a terminal.
thousands
A count with thousands separators, the way github.com writes one.