Skip to content

Commit ecfb063

Browse files
committed
release 0.4.0
1 parent d0a9928 commit ecfb063

6 files changed

Lines changed: 535 additions & 122 deletions

File tree

README.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,6 @@ You need to have Go >=1.19 installed.
1818

1919
`git clone https://github.com/h2337/rssnix --depth=1 && cd rssnix && go install`
2020

21-
## Packages
22-
23-
<a href="https://aur.archlinux.org/packages/rssnix">Arch Linux AUR Package (build newest version)</a> <img src="https://img.shields.io/aur/version/rssnix?color=green" alt="AUR">
24-
25-
<a href="https://aur.archlinux.org/packages/rssnix-bin">Arch Linux AUR Package (binary newest version)</a> <img src="https://img.shields.io/aur/version/rssnix-bin?color=green" alt="AUR">
26-
27-
<a href="https://aur.archlinux.org/packages/rssnix-git">Arch Linux AUR Package (build from git)</a> <img src="https://img.shields.io/aur/version/rssnix-git?color=green" alt="AUR">
28-
2921
## Flags
3022

3123
`config`

config.go

Lines changed: 119 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@ package main
22

33
import (
44
"errors"
5+
"fmt"
56
"os"
7+
"path/filepath"
68
"strings"
79

810
"github.com/go-ini/ini"
911
log "github.com/sirupsen/logrus"
1012
)
1113

12-
var defaultConfigContent = `[settings]
13-
viewer = vim
14-
feed_directory = ~/rssnix
15-
16-
[feeds]`
14+
const (
15+
configEnvVar = "RSSNIX_CONFIG_HOME"
16+
defaultConfigDir = ".config/rssnix"
17+
configFileName = "config.ini"
18+
defaultViewer = "vim"
19+
defaultFeedDirectory = "~/rssnix"
20+
)
1721

1822
type Configuration struct {
1923
FeedDirectory string
@@ -23,43 +27,129 @@ type Configuration struct {
2327

2428
var Config Configuration
2529

26-
func LoadConfig() {
30+
func LoadConfig() error {
2731
homePath, err := os.UserHomeDir()
2832
if err != nil {
29-
log.Error("Failed to get home path")
30-
os.Exit(1)
33+
return fmt.Errorf("resolve home directory: %w", err)
34+
}
35+
36+
cfgDir, err := resolveConfigDir(homePath)
37+
if err != nil {
38+
return err
3139
}
3240

33-
if _, err := os.Stat(homePath + "/.config/rssnix/config.ini"); errors.Is(err, os.ErrNotExist) {
41+
if err := os.MkdirAll(cfgDir, 0o755); err != nil {
42+
return fmt.Errorf("ensure config directory %q: %w", cfgDir, err)
43+
}
44+
45+
cfgPath := filepath.Join(cfgDir, configFileName)
46+
if _, err := os.Stat(cfgPath); errors.Is(err, os.ErrNotExist) {
3447
log.Warn("Config file does not exist, creating...")
48+
if err := createDefaultConfig(cfgPath); err != nil {
49+
return fmt.Errorf("create default config: %w", err)
50+
}
51+
log.Infof("Config file created at %s", cfgPath)
52+
} else if err != nil {
53+
return fmt.Errorf("stat config file: %w", err)
54+
}
55+
56+
cfg, err := ini.Load(cfgPath)
57+
if err != nil {
58+
return fmt.Errorf("load config: %w", err)
59+
}
60+
61+
Config = Configuration{}
62+
settings := cfg.Section("settings")
63+
64+
feedDirValue := strings.TrimSpace(settings.Key("feed_directory").String())
65+
if feedDirValue == "" {
66+
feedDirValue = defaultFeedDirectory
67+
}
68+
Config.FeedDirectory = expandPath(feedDirValue, homePath)
69+
if err := os.MkdirAll(Config.FeedDirectory, 0o755); err != nil {
70+
return fmt.Errorf("ensure feed directory %q: %w", Config.FeedDirectory, err)
71+
}
3572

36-
os.MkdirAll(homePath+"/.config/rssnix", 0777)
37-
file, err := os.Create(homePath + "/.config/rssnix/config.ini")
38-
if err != nil {
39-
log.Error("Failed to create a config file")
40-
os.Exit(1)
73+
viewer := strings.TrimSpace(settings.Key("viewer").String())
74+
if viewer == "" {
75+
viewer = defaultViewer
76+
}
77+
Config.Viewer = viewer
78+
79+
feedsSection := cfg.Section("feeds")
80+
for _, key := range feedsSection.Keys() {
81+
name := strings.TrimSpace(key.Name())
82+
if name == "" {
83+
continue
4184
}
42-
defer file.Close()
4385

44-
_, err = file.WriteString(defaultConfigContent)
45-
if err != nil {
46-
log.Error("Failed to create a config file")
47-
os.Exit(1)
86+
url := strings.TrimSpace(key.String())
87+
if url == "" {
88+
log.WithField("feed", name).Warn("Feed has empty URL; skipping")
89+
continue
4890
}
4991

50-
log.Info("Config file created at " + homePath + "/.config/rssnix/config.ini")
92+
Config.Feeds = append(Config.Feeds, Feed{Name: name, URL: url})
5193
}
5294

53-
cfg, err := ini.Load(homePath + "/.config/rssnix/config.ini")
95+
if len(Config.Feeds) == 0 {
96+
log.Warn("No feeds configured; use `rssnix add` or `rssnix import` to add feeds")
97+
}
5498

55-
Config = Configuration{}
56-
Config.FeedDirectory = cfg.Section("settings").Key("feed_directory").String()
57-
if strings.HasPrefix(Config.FeedDirectory, "~") {
58-
Config.FeedDirectory = homePath + Config.FeedDirectory[1:]
99+
return nil
100+
}
101+
102+
func resolveConfigDir(home string) (string, error) {
103+
override := strings.TrimSpace(os.Getenv(configEnvVar))
104+
if override == "" {
105+
return filepath.Join(home, defaultConfigDir), nil
106+
}
107+
108+
override = expandPath(override, home)
109+
if !filepath.IsAbs(override) {
110+
override = filepath.Join(home, override)
111+
}
112+
return override, nil
113+
}
114+
115+
func expandPath(path, home string) string {
116+
if path == "~" {
117+
return home
59118
}
60-
os.MkdirAll(Config.FeedDirectory, 0777)
61-
Config.Viewer = cfg.Section("settings").Key("viewer").String()
62-
for _, key := range cfg.Section("feeds").Keys() {
63-
Config.Feeds = append(Config.Feeds, Feed{key.Name(), key.String()})
119+
if strings.HasPrefix(path, "~/") {
120+
return filepath.Join(home, path[2:])
121+
}
122+
return path
123+
}
124+
125+
func createDefaultConfig(path string) error {
126+
cfg := ini.Empty()
127+
settings := cfg.Section("settings")
128+
settings.Key("viewer").SetValue(defaultViewer)
129+
settings.Key("feed_directory").SetValue(defaultFeedDirectory)
130+
cfg.Section("feeds")
131+
return cfg.SaveTo(path)
132+
}
133+
134+
func configFilePath() (string, error) {
135+
homePath, err := os.UserHomeDir()
136+
if err != nil {
137+
return "", fmt.Errorf("resolve home directory: %w", err)
138+
}
139+
140+
cfgDir, err := resolveConfigDir(homePath)
141+
if err != nil {
142+
return "", err
143+
}
144+
145+
return filepath.Join(cfgDir, configFileName), nil
146+
}
147+
148+
func (c *Configuration) FeedByName(name string) (Feed, bool) {
149+
for _, feed := range c.Feeds {
150+
if feed.Name == name {
151+
return feed, true
152+
}
64153
}
154+
return Feed{}, false
65155
}

config_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/go-ini/ini"
9+
)
10+
11+
func TestLoadConfigCreatesDefaults(t *testing.T) {
12+
home := t.TempDir()
13+
t.Setenv("HOME", home)
14+
t.Setenv(configEnvVar, "")
15+
16+
if err := LoadConfig(); err != nil {
17+
t.Fatalf("LoadConfig returned error: %v", err)
18+
}
19+
20+
cfgPath, err := configFilePath()
21+
if err != nil {
22+
t.Fatalf("configFilePath returned error: %v", err)
23+
}
24+
25+
if _, err := os.Stat(cfgPath); err != nil {
26+
t.Fatalf("config file not created at %s: %v", cfgPath, err)
27+
}
28+
29+
expectedFeedDir := filepath.Join(home, "rssnix")
30+
if Config.FeedDirectory != expectedFeedDir {
31+
t.Fatalf("expected feed directory %s, got %s", expectedFeedDir, Config.FeedDirectory)
32+
}
33+
34+
if _, err := os.Stat(Config.FeedDirectory); err != nil {
35+
t.Fatalf("feed directory not created: %v", err)
36+
}
37+
38+
if Config.Viewer != defaultViewer {
39+
t.Fatalf("expected viewer %s, got %s", defaultViewer, Config.Viewer)
40+
}
41+
42+
if len(Config.Feeds) != 0 {
43+
t.Fatalf("expected no feeds configured by default, got %d", len(Config.Feeds))
44+
}
45+
}
46+
47+
func TestLoadConfigRespectsEnvOverride(t *testing.T) {
48+
home := t.TempDir()
49+
t.Setenv("HOME", home)
50+
overrideDir := filepath.Join(home, "custom-config")
51+
t.Setenv(configEnvVar, overrideDir)
52+
53+
if err := LoadConfig(); err != nil {
54+
t.Fatalf("LoadConfig returned error: %v", err)
55+
}
56+
57+
cfgPath, err := configFilePath()
58+
if err != nil {
59+
t.Fatalf("configFilePath returned error: %v", err)
60+
}
61+
62+
expectedPath := filepath.Join(overrideDir, configFileName)
63+
if cfgPath != expectedPath {
64+
t.Fatalf("expected config file path %s, got %s", expectedPath, cfgPath)
65+
}
66+
67+
if _, err := os.Stat(expectedPath); err != nil {
68+
t.Fatalf("config file not created at %s: %v", expectedPath, err)
69+
}
70+
}
71+
72+
func TestAddFeedPersistsConfig(t *testing.T) {
73+
home := t.TempDir()
74+
t.Setenv("HOME", home)
75+
t.Setenv(configEnvVar, "")
76+
77+
if err := LoadConfig(); err != nil {
78+
t.Fatalf("LoadConfig returned error: %v", err)
79+
}
80+
81+
const name = "test-feed"
82+
const url = "https://example.com/feed"
83+
84+
if err := addFeed(name, url); err != nil {
85+
t.Fatalf("addFeed returned error: %v", err)
86+
}
87+
88+
if len(Config.Feeds) != 1 {
89+
t.Fatalf("expected 1 feed in memory, got %d", len(Config.Feeds))
90+
}
91+
92+
if Config.Feeds[0].Name != name || Config.Feeds[0].URL != url {
93+
t.Fatalf("unexpected feed stored in memory: %+v", Config.Feeds[0])
94+
}
95+
96+
cfgPath, err := configFilePath()
97+
if err != nil {
98+
t.Fatalf("configFilePath returned error: %v", err)
99+
}
100+
101+
cfg, err := ini.Load(cfgPath)
102+
if err != nil {
103+
t.Fatalf("failed to reload config: %v", err)
104+
}
105+
106+
if got := cfg.Section("feeds").Key(name).String(); got != url {
107+
t.Fatalf("expected config to persist feed URL %s, got %s", url, got)
108+
}
109+
110+
if err := addFeed(name, url); err == nil {
111+
t.Fatalf("expected adding duplicate feed to fail")
112+
}
113+
}

0 commit comments

Comments
 (0)