refac: cleanup and comment code
This commit is contained in:
parent
08a798e7f3
commit
68cedcea0c
3 changed files with 75 additions and 95 deletions
39
main.go
39
main.go
|
@ -16,15 +16,18 @@ import (
|
|||
var (
|
||||
app = kingpin.New("clipman", "A clipboard manager for Wayland")
|
||||
histpath = app.Flag("histpath", "Path of history file").Default("~/.local/share/clipman.json").String()
|
||||
storer = app.Command("store", "Run from `wl-paste --watch` to record clipboard events")
|
||||
picker = app.Command("pick", "Pick an item from clipboard history")
|
||||
clearer = app.Command("clear", "Remove an item from history")
|
||||
noPersist = storer.Flag("no-persist", "Don't persist a copy buffer after a program exits").Short('P').Default("false").Bool()
|
||||
|
||||
storer = app.Command("store", "Record clipboard events (run as argument to `wl-paste --watch`)")
|
||||
maxDemon = storer.Flag("max-items", "history size").Default("15").Int()
|
||||
noPersist = storer.Flag("no-persist", "Don't persist a copy buffer after a program exits").Short('P').Default("false").Bool()
|
||||
|
||||
picker = app.Command("pick", "Pick an item from clipboard history")
|
||||
maxPicker = picker.Flag("max-items", "scrollview length").Default("15").Int()
|
||||
pickTool = picker.Flag("selector", "Which selector to use: dmenu/rofi/-").Default("dmenu").String()
|
||||
clearTool = clearer.Flag("selector", "Which selector to use: dmenu/rofi/-").Default("dmenu").String()
|
||||
|
||||
clearer = app.Command("clear", "Remove item(s) from history")
|
||||
maxClearer = clearer.Flag("max-items", "scrollview length").Default("15").Int()
|
||||
clearTool = clearer.Flag("selector", "Which selector to use: dmenu/rofi/-").Default("dmenu").String()
|
||||
clearAll = clearer.Flag("all", "Remove all items").Short('a').Default("false").Bool()
|
||||
)
|
||||
|
||||
|
@ -32,9 +35,7 @@ func main() {
|
|||
app.HelpFlag.Short('h')
|
||||
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
|
||||
case "store":
|
||||
persist := !*noPersist
|
||||
|
||||
histfile, history, err := getHistory()
|
||||
histfile, history, err := getHistory(*histpath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -50,9 +51,12 @@ func main() {
|
|||
}
|
||||
text := strings.Join(stdin, "\n")
|
||||
|
||||
store(text, history, histfile, *maxDemon, persist)
|
||||
persist := !*noPersist
|
||||
if err := store(text, history, histfile, *maxDemon, persist); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
case "pick":
|
||||
_, history, err := getHistory()
|
||||
_, history, err := getHistory(*histpath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -65,11 +69,12 @@ func main() {
|
|||
// serve selection to the OS
|
||||
err = exec.Command("wl-copy", []string{"--", selection}...).Run()
|
||||
case "clear":
|
||||
histfile, history, err := getHistory()
|
||||
histfile, history, err := getHistory(*histpath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// remove all history
|
||||
if *clearAll {
|
||||
if err := os.Remove(histfile); err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -88,9 +93,9 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func getHistory() (string, []string, error) {
|
||||
// set histfile
|
||||
histfile := *histpath
|
||||
func getHistory(rawPath string) (string, []string, error) {
|
||||
// set histfile; expand user home
|
||||
histfile := rawPath
|
||||
if strings.HasPrefix(histfile, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
|
@ -99,16 +104,16 @@ func getHistory() (string, []string, error) {
|
|||
histfile = strings.Replace(histfile, "~", home, 1)
|
||||
}
|
||||
|
||||
// read existing history
|
||||
// read history if it exists
|
||||
var history []string
|
||||
b, err := ioutil.ReadFile(histfile)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return "", nil, fmt.Errorf("Failure reading history file: %s", err)
|
||||
return "", nil, fmt.Errorf("failure reading history file: %s", err)
|
||||
}
|
||||
} else {
|
||||
if err := json.Unmarshal(b, &history); err != nil {
|
||||
return "", nil, fmt.Errorf("Failure parsing history: %s", err)
|
||||
return "", nil, fmt.Errorf("failure parsing history: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
68
selector.go
68
selector.go
|
@ -1,36 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func selector(history []string, max int, tool string) (string, error) {
|
||||
if len(history) == 0 {
|
||||
log.Fatal("No history available")
|
||||
func selector(data []string, max int, tool string) (string, error) {
|
||||
if len(data) == 0 {
|
||||
return "", errors.New("No history available")
|
||||
}
|
||||
|
||||
// don't modify in-place!
|
||||
tmp := make([]string, len(history))
|
||||
copy(tmp, history)
|
||||
|
||||
// reverse the history
|
||||
for i, j := 0, len(history)-1; i < j; i, j = i+1, j-1 {
|
||||
tmp[i], tmp[j] = tmp[j], tmp[i]
|
||||
}
|
||||
|
||||
selected, err := dmenu(tmp, max, tool)
|
||||
|
||||
return selected, err
|
||||
}
|
||||
|
||||
func dmenu(list []string, max int, tool string) (string, error) {
|
||||
// output to stdout and return
|
||||
if tool == "-" {
|
||||
escaped, _ := preprocessHistory(list, false)
|
||||
escaped, _ := preprocessData(data, false)
|
||||
os.Stdout.WriteString(strings.Join(escaped, "\n"))
|
||||
return "", nil
|
||||
}
|
||||
|
@ -53,50 +39,52 @@ func dmenu(list []string, max int, tool string) (string, error) {
|
|||
"-lines",
|
||||
strconv.Itoa(max)}
|
||||
default:
|
||||
return "", fmt.Errorf("Unsupported tool")
|
||||
return "", fmt.Errorf("Unsupported tool: %s", tool)
|
||||
}
|
||||
|
||||
escaped, guide := preprocessHistory(list, true)
|
||||
input := strings.NewReader(strings.Join(escaped, "\n"))
|
||||
processed, guide := preprocessData(data, true)
|
||||
|
||||
cmd := exec.Cmd{Path: bin, Args: args, Stdin: input}
|
||||
selected, err := cmd.Output()
|
||||
cmd := exec.Cmd{Path: bin, Args: args, Stdin: strings.NewReader(strings.Join(processed, "\n"))}
|
||||
b, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if err.Error() == "exit status 1" {
|
||||
// dmenu exits with this error when no selection done
|
||||
// dmenu/rofi exits with this error when no selection done
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
trimmed := selected[:len(selected)-1] // drop newline added by dmenu
|
||||
selected := string(b[:len(b)-1]) // drop newline added by dmenu/rofi
|
||||
|
||||
sel, ok := guide[string(trimmed)]
|
||||
sel, ok := guide[selected]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't recover original string; please report this bug along with a copy of your clipman.json")
|
||||
return "", errors.New("couldn't recover original string")
|
||||
}
|
||||
|
||||
return sel, nil
|
||||
}
|
||||
|
||||
func preprocessHistory(list []string, cutting bool) ([]string, map[string]string) {
|
||||
// dmenu will break if items contain newlines, so we must pass them as literals.
|
||||
// however, when it sends them back, we need a way to restore them
|
||||
// preprocessData:
|
||||
// - reverses the data
|
||||
// - escapes special characters (like newlines) that would break external selectors;
|
||||
// - optionally it cuts items longer than 400 bytes (dmenu doesn't allow more than ~1200).
|
||||
// A guide is created to allow restoring the selected item.
|
||||
func preprocessData(data []string, cutting bool) ([]string, map[string]string) {
|
||||
var escaped []string
|
||||
guide := make(map[string]string)
|
||||
|
||||
for _, original := range list {
|
||||
for i := len(data) - 1; i >= 0; i-- { // reverse slice
|
||||
original := data[i]
|
||||
|
||||
repr := fmt.Sprintf("%#v", original)
|
||||
max := len(repr) - 1 // drop right quote
|
||||
|
||||
// dmenu will split lines longer than 1200 something; we cut at 400 to spare memory
|
||||
size := len(repr) - 1
|
||||
if cutting {
|
||||
maxChars := 400
|
||||
if max > maxChars {
|
||||
max = maxChars
|
||||
const maxChars = 400
|
||||
if size > maxChars {
|
||||
size = maxChars
|
||||
}
|
||||
}
|
||||
repr = repr[1:size] // drop left and right quotes
|
||||
|
||||
repr = repr[1:max] // drop left quote
|
||||
guide[repr] = original
|
||||
escaped = append(escaped, repr)
|
||||
}
|
||||
|
|
51
storer.go
51
storer.go
|
@ -2,24 +2,22 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func store(text string, history []string, histfile string, max int, persist bool) {
|
||||
func store(text string, history []string, histfile string, max int, persist bool) error {
|
||||
if text == "" {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
l := len(history)
|
||||
if l > 0 {
|
||||
if history[l-1] == text {
|
||||
return
|
||||
}
|
||||
|
||||
// drop oldest items that exceed max list size
|
||||
if l >= max {
|
||||
// usually just one item, but more if we reduce our --max-items value
|
||||
// usually just one item, but more if we suddenly reduce our --max-items
|
||||
history = history[l-max+1:]
|
||||
}
|
||||
|
||||
|
@ -31,49 +29,38 @@ func store(text string, history []string, histfile string, max int, persist bool
|
|||
|
||||
// dump history to file so that other apps can query it
|
||||
if err := write(history, histfile); err != nil {
|
||||
log.Fatalf("Fatal error writing history: %s", err)
|
||||
return fmt.Errorf("error writing history: %s", err)
|
||||
}
|
||||
|
||||
if persist {
|
||||
// make the copy buffer available to all applications,
|
||||
// even when the source has disappeared
|
||||
if persist {
|
||||
if err := exec.Command("wl-copy", []string{"--", text}...).Run(); err != nil {
|
||||
log.Printf("Error running wl-copy: %s", err)
|
||||
log.Printf("Error running wl-copy: %s", err) // don't abort, minor error
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func filter(history []string, text string) []string {
|
||||
var (
|
||||
found bool
|
||||
idx int
|
||||
)
|
||||
|
||||
for i, el := range history {
|
||||
if el == text {
|
||||
found = true
|
||||
idx = i
|
||||
break
|
||||
// filter removes all occurrences of text
|
||||
func filter(slice []string, text string) []string {
|
||||
var filtered []string
|
||||
for _, s := range slice {
|
||||
if s != text {
|
||||
filtered = append(filtered, s)
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
// we know that idx can't be the last element, because
|
||||
// we never get to call this function if that's the case
|
||||
history = append(history[:idx], history[idx+1:]...)
|
||||
}
|
||||
|
||||
return history
|
||||
return filtered
|
||||
}
|
||||
|
||||
// write dumps history to json file
|
||||
func write(history []string, histfile string) error {
|
||||
histlog, err := json.Marshal(history)
|
||||
b, err := json.Marshal(history)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(histfile, histlog, 0644)
|
||||
|
||||
return err
|
||||
return ioutil.WriteFile(histfile, b, 0644)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue