From b15e874d00e113813a928ef4769e8a73fd6090a5 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 24 Dec 2022 23:05:34 -0700 Subject: textui: Add some utilities for human-friendly text --- lib/textui/text.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/textui/text_test.go | 39 +++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 lib/textui/text.go create mode 100644 lib/textui/text_test.go diff --git a/lib/textui/text.go b/lib/textui/text.go new file mode 100644 index 0000000..f628eab --- /dev/null +++ b/lib/textui/text.go @@ -0,0 +1,85 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package textui + +import ( + "fmt" + "io" + + "golang.org/x/exp/constraints" + "golang.org/x/text/language" + "golang.org/x/text/message" + "golang.org/x/text/number" + + "git.lukeshu.com/btrfs-progs-ng/lib/fmtutil" +) + +var printer = message.NewPrinter(language.English) + +// Fprintf is like `fmt.Fprintf`, but (1) includes the extensions of +// `golang.org/x/text/message.Printer`, and (2) is useful for marking +// when a print call is part of the UI, rather than something +// internal. +func Fprintf(w io.Writer, key string, a ...any) (n int, err error) { + return printer.Fprintf(w, key, a...) +} + +// Sprintf is like `fmt.Sprintf`, but (1) includes the extensions of +// `golang.org/x/text/message.Printer`, and (2) is useful for marking +// when a sprint call is part of the UI, rather than something +// internal. +func Sprintf(key string, a ...any) string { + return printer.Sprintf(key, a...) +} + +// Humanized wraps a value such that formatting of it can make use of +// the `golang.org/x/text/message.Printer` extensions even when used +// with plain-old `fmt`. +func Humanized(x any) any { + return humanized{val: x} +} + +type humanized struct { + val any +} + +var ( + _ fmt.Formatter = humanized{} + _ fmt.Stringer = humanized{} +) + +// String implements fmt.Formatter. +func (h humanized) Format(f fmt.State, verb rune) { + printer.Fprintf(f, fmtutil.FmtStateString(f, verb), h.val) +} + +// String implements fmt.Stringer. +func (h humanized) String() string { + return fmt.Sprint(h) +} + +// Portion renders a fraction N/D as both a percentage and +// parenthetically as the exact fractional value, rendered with +// human-friendly commas. +// +// For example: +// +// fmt.Sprint(Portion[int]{N: 1, D: 12345}) ⇒ "0% (1/12,345)" +type Portion[T constraints.Integer] struct { + N, D T +} + +var ( + _ fmt.Stringer = Portion[int]{} +) + +// String implements fmt.Stringer. +func (p Portion[T]) String() string { + pct := float64(1) + if p.D > 0 { + pct = float64(p.N) / float64(p.D) + } + return printer.Sprintf("%v (%v/%v)", number.Percent(pct), uint64(p.N), uint64(p.D)) +} diff --git a/lib/textui/text_test.go b/lib/textui/text_test.go new file mode 100644 index 0000000..c4b42f6 --- /dev/null +++ b/lib/textui/text_test.go @@ -0,0 +1,39 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package textui_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" + "git.lukeshu.com/btrfs-progs-ng/lib/textui" +) + +func TestFprintf(t *testing.T) { + var out strings.Builder + textui.Fprintf(&out, "%d", 12345) + assert.Equal(t, "12,345", out.String()) +} + +func TestHumanized(t *testing.T) { + assert.Equal(t, "12,345", fmt.Sprint(textui.Humanized(12345))) + assert.Equal(t, "12,345 ", fmt.Sprintf("%-8d", textui.Humanized(12345))) + + laddr := btrfsvol.LogicalAddr(345243543) + assert.Equal(t, "0x000000001493ff97", fmt.Sprintf("%v", textui.Humanized(laddr))) + assert.Equal(t, "345243543", fmt.Sprintf("%d", textui.Humanized(laddr))) + assert.Equal(t, "345,243,543", fmt.Sprintf("%d", textui.Humanized(uint64(laddr)))) +} + +func TestPortion(t *testing.T) { + assert.Equal(t, "100% (0/0)", fmt.Sprint(textui.Portion[int]{})) + assert.Equal(t, "0% (1/12,345)", fmt.Sprint(textui.Portion[int]{N: 1, D: 12345})) + assert.Equal(t, "100% (0/0)", fmt.Sprint(textui.Portion[btrfsvol.PhysicalAddr]{})) + assert.Equal(t, "0% (1/12,345)", fmt.Sprint(textui.Portion[btrfsvol.PhysicalAddr]{N: 1, D: 12345})) +} -- cgit v1.2.3-2-g168b