chadtmiller.com/knotless

Git hosted in your atproto account, on any stock PDS, fast enough for agents.

git clone knotless://chadtmiller.com/knotless

needs the knotless git helper. set it up

main
103 lines · 3.9 KB Raw History
// How the pages put things into words.

const encoder = new TextEncoder();

/** A path for a URL: each segment percent-encoded, the slashes kept. */
export function urlPath(path: string): string {
	let out = '';
	for (const b of encoder.encode(path)) {
		const c = String.fromCharCode(b);
		out += /[A-Za-z0-9\-._~/:@]/.test(c) ? c : `%${b.toString(16).toUpperCase().padStart(2, '0')}`;
	}
	return out;
}

/** The page of `owner`'s repository `name`, and what follows. */
export const repoHref = (owner: string, name: string, tail = '') =>
	`/${urlPath(owner)}/${urlPath(name)}${tail}`;

const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

/** `2026-10-20T…` as `Oct 20, 2026`; anything else as it is. */
export function date(iso: string): string {
	const [y, m, d] = [iso.slice(0, 4), iso.slice(5, 7), iso.slice(8, 10)];
	const month = MONTHS[Number(m) - 1];
	const day = Number(d);
	if (y.length < 4 || !month || !/^\d\d$/.test(m) || !/^\d+$/.test(d)) return iso;
	return `${month} ${day}, ${y}`;
}

const WEEKDAYS = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
const LONG_MONTHS = [
	'january', 'february', 'march', 'april', 'may', 'june',
	'july', 'august', 'september', 'october', 'november', 'december'
];

/** `2026-10-09` as `friday, october 9`. */
export function weekday(iso: string): string {
	const d = new Date(`${iso.slice(0, 10)}T00:00:00Z`);
	if (isNaN(+d)) return iso;
	return `${WEEKDAYS[d.getUTCDay()]}, ${LONG_MONTHS[d.getUTCMonth()]} ${d.getUTCDate()}`;
}

/** A commit message's first line. */
export const subject = (message: string) => message.split('\n')[0];

export function size(bytes: number): string {
	if (bytes < 1024) return `${bytes} B`;
	if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
	return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

export const plural = (n: number, one: string, many: string) => `${n} ${n === 1 ? one : many}`;

export const capitalized = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);

/** A branch or tag as it is called, without the `refs/…/` before it. */
export const shortRef = (ref: string) => ref.replace(/^refs\/(heads|tags)\//, '');

/** A hue from 0 to 359 that `name` picks, the same each time. */
export function hue(name: string): number {
	let h = 7;
	for (const b of encoder.encode(name)) h = (Math.imul(h, 31) + b) >>> 0;
	return h % 360;
}

/** The first letter or digit of `name`. */
export const initial = (name: string) => /[\p{L}\p{N}]/u.exec(name)?.[0] ?? '?';

/** The first paragraph of Markdown, as plain text, cut short. */
export function excerpt(text: string, max: number): string {
	const plain = text.split('\n\n')[0].replaceAll('\n', ' ').replace(/[`*]/g, '');
	const chars = [...plain];
	return chars.length > max ? `${chars.slice(0, max).join('').trimEnd()}…` : plain;
}

/** The page of the pull or issue at `uri`, on the repository at `repo`. */
export function itemHref(repo: string, uri: string): string {
	const [, , did, , rkey] = uri.split('/');
	return `${repo}/${kindOf(uri)}/${did}/${rkey}`;
}

/** Commits a push is walked for: a push that brought this many brought at least as many. */
const WALKED = 100;

/** How many commits a push brought, in words. */
export function commits(n: number): string {
	if (n === 0) return 'nothing new';
	if (n === 1) return '1 commit';
	return n >= WALKED ? `${WALKED}+ commits` : `${n} commits`;
}

/** What a comment with `verdict` did. */
export function said(verdict?: string | null): string {
	if (verdict === 'approve') return 'approved';
	if (verdict === 'changes') return 'asked for changes on';
	return 'commented on';
}

/** What moving to `state` did. */
export const changed = (state: string) => (state === 'open' ? 'reopened' : state);

/** `pull` or `issue`, for the record at `uri`. */
export const kindOf = (uri: string) => (uri.split('/')[3] === 'dev.knotless.pull' ? 'pull' : 'issue');