diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 11:44:09 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 11:44:09 +0200 |
| commit | 629ec29e33506a9bf96f552dcc2e6b6cd0560916 (patch) | |
| tree | 5d7ff9fd4b938764a4f9a98cc09b03b5fb2fdc6c | |
| parent | 0e56395aaa46359974a5eb21acaab32b2ea02a0f (diff) | |
| download | krino-629ec29e33506a9bf96f552dcc2e6b6cd0560916.tar.gz krino-629ec29e33506a9bf96f552dcc2e6b6cd0560916.zip | |
gui: the toolbar in the order things happen, and an About block
The Plan bar now reads left to right as the work does: which
directory, how it should be listed, what of it, where it is on disk -
then Scan, and only then what to do with what came back. Scan carries
the theme's accent, being the button that starts everything.
Settings ends with About: the program and the version it was built
as, what it is in a sentence, the licence, and who to write to. The
text is model.About so it is tested; ui.Version is stamped by main.
leak-check knows krino's own contact address, so the address check
stays useful without a per-clone setting.
| -rw-r--r-- | gui/cmd/krino-gui/main.go | 1 | ||||
| -rw-r--r-- | gui/internal/model/about.go | 27 | ||||
| -rw-r--r-- | gui/internal/model/about_test.go | 43 | ||||
| -rw-r--r-- | gui/internal/ui/plan.go | 20 | ||||
| -rw-r--r-- | gui/internal/ui/settings.go | 13 | ||||
| -rw-r--r-- | gui/internal/ui/window.go | 4 | ||||
| -rwxr-xr-x | scripts/leak-check | 16 |
7 files changed, 115 insertions, 9 deletions
diff --git a/gui/cmd/krino-gui/main.go b/gui/cmd/krino-gui/main.go index f9a9759..25f4837 100644 --- a/gui/cmd/krino-gui/main.go +++ b/gui/cmd/krino-gui/main.go @@ -35,6 +35,7 @@ func main() { } e.CacheDir = cacheDir() + ui.Version = version app := gtk.NewApplication("xyz.labunix.krino", 0) app.ConnectActivate(func() { ui.NewWindow(app, e).Show() }) if code := app.Run(nil); code != 0 { diff --git a/gui/internal/model/about.go b/gui/internal/model/about.go new file mode 100644 index 0000000..e92a0f4 --- /dev/null +++ b/gui/internal/model/about.go @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package model + +// The people and terms behind the program, in one place so About and any +// later --about agree. The repository line joins these once there is a +// public one to name. +const ( + Author = "Lukasz Kasprzak" + Contact = "contact@labunix.xyz" + Licence = "GPL-3.0-or-later" +) + +// About is the block Settings shows at its foot: what this is, which build +// it is, and under what terms. The first line names the program and its +// version; the rest are the small print. +func About(version string) []string { + if version == "" { + version = "dev" + } + return []string{ + "krino " + version, + "A rule-based file sorter: it reads a directory, works out what belongs where, shows you the plan, and only then moves anything.", + "Licence: " + Licence + " - free software, with no warranty.", + "By " + Author + ", " + Contact, + } +} diff --git a/gui/internal/model/about_test.go b/gui/internal/model/about_test.go new file mode 100644 index 0000000..00a6718 --- /dev/null +++ b/gui/internal/model/about_test.go @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package model_test + +import ( + "strings" + "testing" + + "krino/gui/internal/model" +) + +// TestAboutSaysWhatItIs: the About block names the program, the version it +// was built as, the licence, who wrote it and how to reach them. +func TestAboutSaysWhatItIs(t *testing.T) { + lines := model.About("0.0.12") + if len(lines) == 0 { + t.Fatal("About returned nothing") + } + whole := strings.Join(lines, "\n") + for _, want := range []string{ + "krino", + "0.0.12", + "GPL-3.0-or-later", + "Lukasz Kasprzak", + "contact@labunix.xyz", + } { + if !strings.Contains(whole, want) { + t.Errorf("About does not mention %q:\n%s", want, whole) + } + } + if !strings.HasPrefix(lines[0], "krino ") { + t.Errorf("the first line should name the program and version, got %q", lines[0]) + } +} + +// TestAboutWithoutAVersion: a plain `go build` leaves "dev", and About says +// so rather than printing an empty version. +func TestAboutWithoutAVersion(t *testing.T) { + lines := model.About("") + if !strings.Contains(lines[0], "dev") { + t.Errorf("an unstamped build should read as dev, got %q", lines[0]) + } +} diff --git a/gui/internal/ui/plan.go b/gui/internal/ui/plan.go index 5daba6a..fe99a18 100644 --- a/gui/internal/ui/plan.go +++ b/gui/internal/ui/plan.go @@ -92,6 +92,10 @@ func newPlanView(w *Window) *planView { p.path.SetMaxWidthChars(20) p.dirs.Connect("notify::selected", p.showPath) p.scan = gtk.NewButtonWithLabel("Scan") + // The one button that starts everything, so it carries the theme's + // accent like Apply does (his request, 2026-09-17). + p.scan.AddCSSClass("suggested-action") + p.scan.SetTooltipText("read the directory and work out what would happen to each file; nothing is touched until Apply") p.selAll = gtk.NewButtonWithLabel("Select all") p.selNone = gtk.NewButtonWithLabel("None") p.apply = gtk.NewButtonWithLabel("Apply") @@ -104,26 +108,28 @@ func newPlanView(w *Window) *planView { bar.SetMarginStart(6) bar.SetMarginEnd(6) bar.SetMarginBottom(6) - bar.Append(gtk.NewLabel("Directory")) - bar.Append(p.dirs) - bar.Append(p.scan) // A filter over the plan: type a few letters of a name, as fzf does, - // and act on what is left. It sits by the Scan button, where the eye - // starts (his request, 2026-09-17). + // and act on what is left. p.filter = gtk.NewSearchEntry() p.filter.SetPlaceholderText("filter") p.filter.SetTooltipText("show only the files whose name or rule has these letters, in order; Select all then checks those") p.filter.SetSizeRequest(200, -1) - bar.Append(p.filter) // The order the plan is read in. It starts as the settings say and can // be changed for this window alone (his request, 2026-09-17). p.sort = gtk.NewDropDownFromStrings(sortItems()) p.sort.SetTooltipText("the order the plan is listed in; Settings has the one a new window starts with") + + // The bar reads as the order of operations: which directory, how it + // will be listed, what of it, where it is on disk - then Scan, and only + // then what to do with what comes back (his request, 2026-09-17). + bar.Append(gtk.NewLabel("Directory")) + bar.Append(p.dirs) bar.Append(gtk.NewLabel("sort")) bar.Append(p.sort) - + bar.Append(p.filter) bar.Append(p.path) + bar.Append(p.scan) bar.Append(p.selAll) bar.Append(p.selNone) bar.Append(p.checkedMenu()) diff --git a/gui/internal/ui/settings.go b/gui/internal/ui/settings.go index e15f60d..73c4072 100644 --- a/gui/internal/ui/settings.go +++ b/gui/internal/ui/settings.go @@ -124,6 +124,19 @@ func (w *Window) showSettings() { box.Append(colours) box.Append(preview) + box.Append(heading("About")) + for i, line := range model.About(Version) { + l := gtk.NewLabel(line) + l.SetXAlign(0) + l.SetWrap(true) + // Selectable so the address can be copied out of the window. + l.SetSelectable(true) + if i > 0 { + l.AddCSSClass("dim-label") + } + box.Append(l) + } + apply := func() { which := model.LayoutSide if layout.Selected() == 1 { diff --git a/gui/internal/ui/window.go b/gui/internal/ui/window.go index ce991d8..5132c12 100644 --- a/gui/internal/ui/window.go +++ b/gui/internal/ui/window.go @@ -20,6 +20,10 @@ import ( "krino/internal/xdg" ) +// Version is what About shows. main stamps it at build time; "dev" is +// what a plain `go build` leaves. +var Version = "dev" + // Window is krino-gui's one window: three tabs, and a status bar. type Window struct { app *gtk.Application diff --git a/scripts/leak-check b/scripts/leak-check index fbbd5d5..1a27eec 100755 --- a/scripts/leak-check +++ b/scripts/leak-check @@ -13,7 +13,8 @@ # Keep that file outside the repository so the list is never published. # # A line matching the regex in `git config krino.leakallow` is never -# reported (for example a public contact address in the README). +# reported (for example a second address of your own). krino's own public +# contact address is exempt from the address check without any such setting. # # Prints file:line for each hit, never the matched text, so a hit on a # private pattern does not echo the secret. Exits 0 when clean, 1 when @@ -24,13 +25,22 @@ set -eu cd "$(git rev-parse --show-toplevel)" allow=$(git config --get krino.leakallow || true) +# krino's own contact address, published on purpose in README and About. +# Exempt from the address check only, so any clone's `make ci` is clean. +public='contact@labunix\.xyz' +exempt= hits=0 # check LABEL GREP-ARGS...: report staged lines that match, as file:line. +# $exempt, when set, widens $allow for this one call. check() { label=$1 shift - out=$(git grep --cached -n -I "$@" | { if [ -n "$allow" ]; then grep -v -E -i -e "$allow"; else cat; fi; } | cut -d: -f1,2 || true) + filter=$allow + if [ -n "$exempt" ]; then + filter=${filter:+$filter|}$exempt + fi + out=$(git grep --cached -n -I "$@" | { if [ -n "$filter" ]; then grep -v -E -i -e "$filter"; else cat; fi; } | cut -d: -f1,2 || true) if [ -n "$out" ]; then printf 'leak-check: %s:\n%s\n' "$label" "$out" | sed '2,$s/^/ /' >&2 hits=1 @@ -42,7 +52,9 @@ if [ -n "$home" ]; then check "your home directory ($home)" -F -e "$home" -- . fi +exempt=$public check "an email address" -E -e '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' -- . ':(exclude)LICENSE' +exempt= list=$(git config --type=path --get krino.leakpatterns || true) if [ -z "$list" ]; then |
