progress.v

3.2 KB · raw

1//! The line a long action draws while it works (§4, "never leave the person2//! wondering"): one line on stderr, redrawn in place and cleared when the step3//! ends, and only when stderr is a terminal, so pipes, logs and `--json` never4//! see it.56module cli78import os9import src.fmt10import src.remote11import src.snapshot12import src.view1314fn working(text string) {15	if os.is_atty(2) == 0 {16		return17	}18	width := line_width()19	shown := fmt.fit('  ${text}', width)20	// Padded with spaces, not cleared with an escape code, so a plain terminal21	// and NO_COLOR stay free of control sequences.22	eprint('\r${fmt.dim(shown)}${' '.repeat(width - fmt.visible_width(shown))}')23	flush_stderr()24}2526fn done_working() {27	if os.is_atty(2) != 0 {28		eprint('\r${' '.repeat(line_width())}\r')29		flush_stderr()30	}31}3233// line_width stays one column short of the terminal, so the line never wraps.34fn line_width() int {35	_, columns := view.terminal_size()36	return if columns > 1 { columns - 1 } else { 79 }37}3839// reading is the walk's line: how many files it has had to read and hash.40fn reading() snapshot.Hashing {41	return snapshot.Hashing{42		on: fn (done int, total int) {43			if done % 100 == 1 {44				working('reading files · ${done}')45			}46		}47	}48}4950// Watched counts what an offer, steal or strip moves through a backend, so the51// line moves with the transfer. Exchange stays unaware of it.52struct Watched {53	label string54	// whole is a steal of a whole repo, which fetches every key it lists, so a55	// listing is the total for that kind: files, saves or events.56	whole bool57mut:58	inner    remote.Backend59	received int60	sent     int61	listed   map[string]int62	fetched  map[string]int63	kind     string64}6566// watched_backend is this machine's backend, counted on the line under `label`.67fn watched_backend(label string) !remote.Backend {68	return remote.Backend(watch(label, this_machine_backend()!))69}7071fn watch(label string, backend remote.Backend) Watched {72	return Watched{73		label: label74		inner: backend75	}76}7778fn (mut w Watched) show() {79	mut text := w.label80	if w.kind != '' {81		text += ' · ${w.kind} ${w.fetched[w.kind]} of ${w.listed[w.kind]}'82	}83	if w.sent > 0 {84		text += ' · ${w.sent} sent'85	}86	if w.kind == '' && w.received > 0 {87		text += ' · ${w.received} received'88	}89	working(text)90}9192fn (mut w Watched) get(key string) !remote.Object {93	object := w.inner.get(key)!94	kind := key_kind(os.dir(key))95	if kind in w.listed {96		w.kind = kind97		w.fetched[kind]++98	} else {99		w.received++100	}101	w.show()102	return object103}104105fn (mut w Watched) list(prefix string) ![]string {106	keys := w.inner.list(prefix)!107	if w.whole {108		w.listed[key_kind(prefix)] += keys.len109	}110	return keys111}112113// key_kind names what a key folder such as `repo/objects` holds, in the words114// the person knows: an object is a file's contents.115fn key_kind(folder string) string {116	kind := os.base(folder)117	return if kind == 'objects' { 'files' } else { kind }118}119120fn (mut w Watched) put_if_absent(key string, body []u8) !bool {121	put := w.inner.put_if_absent(key, body)!122	w.sent++123	w.show()124	return put125}126127fn (mut w Watched) put_if_match(key string, body []u8, tag string) !bool {128	put := w.inner.put_if_match(key, body, tag)!129	w.sent++130	w.show()131	return put132}133134fn (mut w Watched) delete(key string) ! {135	w.inner.delete(key)!136	w.sent++137	w.show()138}