v0.33.0 62a09f5

xdg.go RawHistoryFolder

1// Package config provides configuration management for dumber with Viper integration.
2package config
3
4import (
5	"os"
6	"path/filepath"
7
8	"github.com/bnema/dumber/internal/infrastructure/runtimeprofile"
9)
10
11// File permission constants
12const (
13	dirPerm  = 0755 // Standard directory permissions (rwxr-xr-x)
14	filePerm = 0644 // Standard file permissions (rw-r--r--)
15)
16
17const (
18	appName      = "dumber"
19	databaseName = "dumber.db"
20)
21
22// XDGDirs holds the XDG Base Directory paths for the application.
23type XDGDirs struct {
24	ConfigHome string
25	DataHome   string
26	StateHome  string
27	CacheHome  string
28}
29
30// GetXDGDirs returns the dumber-specific XDG directories.
31// In prod they match the current XDG layout exactly.
32// In dev they resolve into the shared sandbox under .dev/dumber/{config,data,state,cache}.
33func GetXDGDirs() (*XDGDirs, error) {
34	profile, err := resolveSharedProfile()
35	if err != nil {
36		return nil, err
37	}
38	return &XDGDirs{
39		ConfigHome: profile.Shared.ConfigDir,
40		DataHome:   profile.Shared.DataDir,
41		StateHome:  profile.Shared.StateDir,
42		CacheHome:  profile.Shared.CacheDir,
43	}, nil
44}
45
46func resolveSharedProfile() (runtimeprofile.Profile, error) {
47	base, err := getBaseXDGDirs()
48	if err != nil {
49		return runtimeprofile.Profile{}, err
50	}
51	return runtimeprofile.Resolve(runtimeprofile.ResolveInput{
52		Env: os.Getenv,
53		CWD: os.Getwd,
54		Base: runtimeprofile.BasePaths{
55			ConfigHome: base.ConfigHome,
56			DataHome:   base.DataHome,
57			StateHome:  base.StateHome,
58			CacheHome:  base.CacheHome,
59		},
60	})
61}
62
63func getBaseXDGDirs() (*XDGDirs, error) {
64	if os.Getenv("ENV") == "dev" {
65		return &XDGDirs{}, nil
66	}
67
68	homeDir, err := os.UserHomeDir()
69	if err != nil {
70		return nil, err
71	}
72
73	configHome := os.Getenv("XDG_CONFIG_HOME")
74	if configHome == "" {
75		configHome = filepath.Join(homeDir, ".config")
76	}
77	configHome = filepath.Join(configHome, appName)
78
79	dataHome := os.Getenv("XDG_DATA_HOME")
80	if dataHome == "" {
81		dataHome = filepath.Join(homeDir, ".local", "share")
82	}
83	dataHome = filepath.Join(dataHome, appName)
84
85	stateHome := os.Getenv("XDG_STATE_HOME")
86	if stateHome == "" {
87		stateHome = filepath.Join(homeDir, ".local", "state")
88	}
89	stateHome = filepath.Join(stateHome, appName)
90
91	cacheHome := os.Getenv("XDG_CACHE_HOME")
92	if cacheHome == "" {
93		cacheHome = filepath.Join(homeDir, ".cache")
94	}
95	cacheHome = filepath.Join(cacheHome, appName)
96
97	return &XDGDirs{
98		ConfigHome: configHome,
99		DataHome:   dataHome,
100		StateHome:  stateHome,
101		CacheHome:  cacheHome,
102	}, nil
103}
104
105// GetConfigDir returns the XDG config directory for dumber.
106func GetConfigDir() (string, error) {
107	dirs, err := GetXDGDirs()
108	if err != nil {
109		return "", err
110	}
111	return dirs.ConfigHome, nil
112}
113
114// GetDataDir returns the XDG data directory for dumber.
115func GetDataDir() (string, error) {
116	dirs, err := GetXDGDirs()
117	if err != nil {
118		return "", err
119	}
120	return dirs.DataHome, nil
121}
122
123// GetStateDir returns the XDG state directory for dumber.
124func GetStateDir() (string, error) {
125	dirs, err := GetXDGDirs()
126	if err != nil {
127		return "", err
128	}
129	return dirs.StateHome, nil
130}
131
132// GetLogDir returns the shared application log directory for dumber.
133func GetLogDir() (string, error) {
134	profile, err := resolveSharedProfile()
135	if err != nil {
136		return "", err
137	}
138	return profile.Shared.LogDir, nil
139}
140
141// GetConfigFile returns the path to the main configuration file.
142func GetConfigFile() (string, error) {
143	configDir, err := GetConfigDir()
144	if err != nil {
145		return "", err
146	}
147	return filepath.Join(configDir, "config.toml"), nil
148}
149
150// GetDatabaseFile returns the path to the database file in the data directory.
151// The database contains user data (history, favorites, preferences) that should
152// be backed up, so it belongs in XDG_DATA_HOME per the XDG specification.
153func GetDatabaseFile() (string, error) {
154	dataDir, err := GetDataDir()
155	if err != nil {
156		return "", err
157	}
158	return filepath.Join(dataDir, databaseName), nil
159}
160
161// GetFilterCacheDir returns the XDG-compliant filter cache directory for dumber.
162// Filter cache is stored in XDG_STATE_HOME as it's transient data that can be regenerated.
163func GetFilterCacheDir() (string, error) {
164	stateDir, err := GetStateDir()
165	if err != nil {
166		return "", err
167	}
168	return filepath.Join(stateDir, "filter-cache"), nil
169}
170
171// GetFilterCacheFile returns the path to the main filter cache file.
172func GetFilterCacheFile() (string, error) {
173	cacheDir, err := GetFilterCacheDir()
174	if err != nil {
175		return "", err
176	}
177	return filepath.Join(cacheDir, "filters.cache"), nil
178}
179
180// GetFaviconCacheDir returns the XDG-compliant favicon cache directory for dumber.
181// Favicon cache is stored in XDG_CACHE_HOME as it's transient data that can be refetched.
182func GetFaviconCacheDir() (string, error) {
183	dirs, err := GetXDGDirs()
184	if err != nil {
185		return "", err
186	}
187	return filepath.Join(dirs.CacheHome, "favicons"), nil
188}
189
190// EnsureDirectories creates the XDG directories if they don't exist.
191func EnsureDirectories() error {
192	dirs, err := GetXDGDirs()
193	if err != nil {
194		return err
195	}
196
197	// Create all directories with proper permissions
198	directories := []string{
199		dirs.ConfigHome,
200		dirs.DataHome,
201		dirs.StateHome,
202		dirs.CacheHome,
203	}
204
205	for _, dir := range directories {
206		if err := os.MkdirAll(dir, dirPerm); err != nil {
207			return err
208		}
209	}
210
211	return nil
212}

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 8

Tags 42

↑↓ moveenter openesc close