~kota/nnn-select

nnn-select/main.go -rw-r--r-- 1.9 KiB
286d7480Dakota Walsh accept relative paths as input 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/adrg/xdg"
)

// wanted reads the passed list of paths and returns them as a slice of absolute
// paths or exits with a usage message.
func wanted() []string {
	flag.Parse()
	if len(flag.Args()) == 0 {
		fmt.Fprintf(os.Stderr, "usage: nnn-select [paths...]")
		os.Exit(1)
	}

	var s []string
	for _, v := range flag.Args() {
		absolute, err := filepath.Abs(v)
		if err != nil {
			fmt.Fprintf(
				os.Stderr,
				"failed parsing %s into absabsolute path: %v",
				v,
				err,
			)
		}
		s = append(s, absolute)
	}
	return s
}

// existing selections from the nnn .selection file.
func existing(path string) []string {
	b, err := os.ReadFile(path)
	if errors.Is(err, os.ErrNotExist) {
		return []string{}
	} else if err != nil {
		fmt.Fprintf(os.Stderr, "failed reading current selections: %v", err)
		os.Exit(1)
	}
	if len(b) == 0 {
		return []string{}
	}
	return strings.Split(string(b), "\x00")
}

// selection takes a list of wants and exists paths and returns a null character
// separated byte slice. If a path was found in one list but not the other it's
// included, but if it's found in both lists it's not included.
func selection(wants, exists []string) []byte {
	set := make(map[string]struct{})
	for _, e := range exists {
		set[e] = struct{}{}
	}
	for _, w := range wants {
		if _, ok := set[w]; ok {
			delete(set, w)
			continue
		}
		set[w] = struct{}{}
	}

	selections := make([]string, 0, len(set))
	for k := range set {
		selections = append(selections, k)
	}
	return []byte(strings.Join(selections, "\x00"))
}

// save the selections to path.
func save(path string, selections []byte) {
	if err := os.WriteFile(path, selections, 0644); err != nil {
		fmt.Fprintf(os.Stderr, "failed writing selections: %v", err)
	}
}

func main() {
	wants := wanted()
	path := filepath.Join(xdg.ConfigHome, "nnn", ".selection")
	exists := existing(path)
	save(path, selection(wants, exists))
}