v0.3.2 a514042

config.go RawHistoryFolder

1// Package config reads bounded transactional flat key=value configuration.
2package config
3
4import (
5	"bufio"
6	"fmt"
7	"io"
8	"os"
9	"path/filepath"
10	"strings"
11	"time"
12	"unicode/utf8"
13
14	"github.com/bnema/neferafk/internal/ports"
15)
16
17const MaxBytes = 64 << 10
18const MaxLine = 4 << 10
19
20func DefaultPath() string {
21	base := os.Getenv("XDG_CONFIG_HOME")
22	if base == "" {
23		if home, err := os.UserHomeDir(); err == nil {
24			base = filepath.Join(home, ".config")
25		}
26	}
27	return filepath.Join(base, "neferafk", "config")
28}
29func Load(path string) (ports.Config, error) {
30	f, err := os.Open(path)
31	if err != nil {
32		return ports.Config{}, err
33	}
34	defer f.Close()
35	return Parse(f)
36}
37
38// Parse never returns partial configuration and never resolves PIN references.
39func Parse(r io.Reader) (ports.Config, error) {
40	data, err := io.ReadAll(io.LimitReader(r, MaxBytes+1))
41	if err != nil {
42		return ports.Config{}, err
43	}
44	if !utf8.Valid(data) {
45		return ports.Config{}, fmt.Errorf("config is not valid UTF-8")
46	}
47	if len(data) > MaxBytes {
48		return ports.Config{}, fmt.Errorf("config exceeds 64KiB")
49	}
50	cfg := ports.Defaults()
51	seen := map[string]bool{}
52	scanner := bufio.NewScanner(strings.NewReader(string(data)))
53	scanner.Buffer(make([]byte, MaxLine+1), MaxLine+2)
54	line := 0
55	for scanner.Scan() {
56		line++
57		text := scanner.Text()
58		if len(text) > MaxLine {
59			return ports.Config{}, fmt.Errorf("line %d exceeds 4KiB", line)
60		}
61		text = strings.TrimSpace(strings.SplitN(text, "#", 2)[0])
62		if text == "" {
63			continue
64		}
65		key, value, ok := strings.Cut(text, "=")
66		key, value = strings.TrimSpace(key), strings.TrimSpace(value)
67		if !ok || value == "" || seen[key] {
68			return ports.Config{}, fmt.Errorf("line %d: missing value or duplicate key", line)
69		}
70		seen[key] = true
71		var target *time.Duration
72		switch key {
73		case "lock.after":
74			target = &cfg.LockAfter
75		case "fade.after":
76			target = &cfg.FadeAfter
77		case "fade.duration":
78			target = &cfg.FadeDuration
79		case "screens.off-after":
80			target = &cfg.OffAfter
81		case "sleep.after":
82			target = &cfg.SleepAfter
83		case "auth.mode":
84			cfg.Auth.Mode = ports.AuthMode(value)
85		case "auth.pin-source":
86			cfg.Auth.PINSource = ports.PINSource(value)
87		case "auth.pin-env":
88			cfg.Auth.PINEnv = value
89		case "auth.pin-entry":
90			cfg.Auth.PINEntry = value
91		default:
92			return ports.Config{}, fmt.Errorf("line %d: unknown key", line)
93		}
94		if target != nil {
95			if value == "off" && key != "fade.duration" {
96				*target = 0
97				continue
98			}
99			d, e := time.ParseDuration(value)
100			if e != nil || d < 0 || d > ports.MaxDuration || d == 0 && key != "fade.duration" {
101				return ports.Config{}, fmt.Errorf("line %d: invalid duration", line)
102			}
103			*target = d
104		}
105	}
106	if err := scanner.Err(); err != nil {
107		return ports.Config{}, fmt.Errorf("config line exceeds bound")
108	}
109	if err := cfg.Validate(); err != nil {
110		return ports.Config{}, err
111	}
112	return cfg, nil
113}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 9

↑↓ moveenter openesc close