package main import ( "bufio" "errors" "fmt" "net/url" "os" "path/filepath" "strings" "syscall" "github.com/BurntSushi/toml" ) type Config struct { DefaultScheme string `toml:"default_scheme"` SoftWrap int `toml:"soft_wrap"` DownloadFolder string `toml:"download_folder"` VimKeys bool `toml:"vim_keys"` } func getConfig() (*Config, error) { home := os.Getenv("HOME") path := os.Getenv("XDG_CONFIG_HOME") if path == "" { path = filepath.Join(home, ".config") } path = filepath.Join(path, "x-1", "config.toml") if err := ensurePath(path); err != nil { return nil, err } c := Config{ VimKeys: true, DefaultScheme: "gemini", SoftWrap: 80, DownloadFolder: home, } if _, err := toml.DecodeFile(path, &c); err != nil { return nil, err } if strings.HasPrefix(c.DownloadFolder, "~") { c.DownloadFolder = home + c.DownloadFolder[1:] } return &c, nil } func getMarks() (map[string]string, error) { path, err := marksFilePath() if err != nil { return nil, err } marks := make(map[string]string) f, err := os.Open(path) if err != nil { return nil, err } defer func() { _ = f.Close() }() rdr := bufio.NewScanner(f) for rdr.Scan() { line := rdr.Text() name, target, _ := strings.Cut(line, ":") marks[name] = target } if err := rdr.Err(); err != nil { return nil, err } return marks, nil } func saveMarks(marks map[string]string) error { path, err := marksFilePath() if err != nil { return err } f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0o600) if err != nil { return err } defer func() { _ = f.Close() }() for name, target := range marks { _, err := fmt.Fprintf(f, "%s:%s\n", name, target) if err != nil { return err } } return nil } func marksFilePath() (string, error) { return dataFilePath("marks") } func getTours() (map[string]*Tour, error) { path, err := toursFilePath() if err != nil { return nil, err } tours := make(map[string]*Tour) var current Tour var currentName string f, err := os.Open(path) if err != nil { return nil, err } defer func() { _ = f.Close() }() rdr := bufio.NewScanner(f) for rdr.Scan() { line := rdr.Text() if strings.HasSuffix(line, ":") { if currentName != "" { tours[currentName] = ¤t } currentName = strings.TrimSuffix(line, ":") current = Tour{} } else { u, err := url.Parse(line) if err != nil { return nil, err } current.Links = append(current.Links, u) } } if err := rdr.Err(); err != nil { return nil, err } if currentName != "" { tours[currentName] = ¤t } return tours, nil } func saveTours(tours map[string]*Tour) error { path, err := toursFilePath() if err != nil { return err } f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0o600) if err != nil { return err } defer func() { _ = f.Close() }() for name, tour := range tours { if len(tour.Links) == 0 { continue } if _, err := fmt.Fprintf(f, "%s:\n", name); err != nil { return err } for _, link := range tour.Links { if _, err := fmt.Fprintf(f, "%s\n", link.String()); err != nil { return err } } } return nil } func toursFilePath() (string, error) { return dataFilePath("tours") } func dataFilePath(filename string) (string, error) { home := os.Getenv("HOME") path := os.Getenv("XDG_DATA_HOME") if path == "" { path = filepath.Join(home, ".local", "share") } path = filepath.Join(path, "x-1", filename) if err := ensurePath(path); err != nil { return "", err } return path, nil } func ensurePath(fpath string) error { if _, err := os.Stat(fpath); errors.Is(err, syscall.ENOENT) { if err := os.MkdirAll(filepath.Dir(fpath), 0o700); err != nil { return err } f, err := os.OpenFile(fpath, os.O_RDWR|os.O_CREATE, 0o600) if err != nil { return err } _ = f.Close() } return nil }