summaryrefslogtreecommitdiff
path: root/tour.go
diff options
context:
space:
mode:
authortjp <tjp@ctrl-c.club>2024-01-03 12:17:37 -0700
committertjp <tjp@ctrl-c.club>2024-01-03 12:17:37 -0700
commit859f74231f2b48d2dcf6a29682e7651b504fda12 (patch)
treee03126c48c8385d98ba81525e7b628d5ca2257ca /tour.go
parent6c9558c0d2201d933b1d396febeb6e70ceaad058 (diff)
tours
Diffstat (limited to 'tour.go')
-rw-r--r--tour.go214
1 files changed, 214 insertions, 0 deletions
diff --git a/tour.go b/tour.go
new file mode 100644
index 0000000..d74bef4
--- /dev/null
+++ b/tour.go
@@ -0,0 +1,214 @@
+package main
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "net/url"
+ "slices"
+ "strconv"
+ "strings"
+)
+
+var (
+ ErrEndOfTour = errors.New("you've hit the end of the tour")
+ ErrStartOfTour = errors.New("you're at the start of the tour")
+ ErrInvalidTourPos = errors.New("that's not a valid tour link")
+)
+
+type Tour struct {
+ Index int
+ Links []*url.URL
+}
+
+func parseURLs(state *BrowserState, defaultScheme, str string) ([]*url.URL, error) {
+ urls := []*url.URL{}
+
+ if str == "*" {
+ for _, link := range state.Links {
+ urls = append(urls, state.Url.ResolveReference(link.Target))
+ }
+ return urls, nil
+ }
+
+ if i := strings.IndexByte(str, '-'); i > 0 {
+ start, e1 := strconv.Atoi(str[:i])
+ end, e2 := strconv.Atoi(str[i+1:])
+ if e1 == nil && e2 == nil && end >= start && start >= 0 && start < len(state.Links) && end < len(state.Links) {
+ for _, link := range state.Links[start : end+1] {
+ urls = append(urls, state.Url.ResolveReference(link.Target))
+ }
+ return urls, nil
+ }
+ }
+
+ u, _, err := parseURL(str, state, defaultScheme)
+ if err != nil {
+ return nil, err
+ }
+ return []*url.URL{u}, nil
+}
+
+func TourAdd(state *BrowserState, conf *Config, targets []string) error {
+ newurls := []*url.URL{}
+ for _, target := range targets {
+ urls, err := parseURLs(state, conf.DefaultScheme, target)
+ if err != nil {
+ return err
+ }
+ newurls = append(newurls, urls...)
+ }
+
+ state.CurrentTour.Links = append(state.CurrentTour.Links, newurls...)
+
+ if state.CurrentTour != &state.DefaultTour {
+ return saveTours(state.NamedTours)
+ }
+ return nil
+}
+
+func TourAddNext(state *BrowserState, conf *Config, targets []string) error {
+ newurls := []*url.URL{}
+ for _, target := range targets {
+ urls, err := parseURLs(state, conf.DefaultScheme, target)
+ if err != nil {
+ return err
+ }
+ newurls = append(newurls, urls...)
+ }
+
+ state.CurrentTour.Links = slices.Insert(
+ state.CurrentTour.Links,
+ state.CurrentTour.Index,
+ newurls...,
+ )
+
+ if state.CurrentTour != &state.DefaultTour {
+ return saveTours(state.NamedTours)
+ }
+ return nil
+}
+
+func TourShow(state *BrowserState) error {
+ tour := state.CurrentTour
+ buf := &bytes.Buffer{}
+ for i, link := range tour.Links {
+ mark := ""
+ if i == tour.Index-1 {
+ mark = "* "
+ }
+ if _, err := fmt.Fprintf(buf, "%s%d %s\n", mark, i, link.String()); err != nil {
+ return err
+ }
+ }
+
+ state.Modal = buf.Bytes()
+ if len(state.Modal) == 0 {
+ state.Modal = []byte("(empty)\n")
+ }
+ return Print(state)
+}
+
+func TourNext(state *BrowserState, conf *Config) error {
+ tour := state.CurrentTour
+ if tour.Index >= len(tour.Links) || len(tour.Links) == 0 {
+ return ErrEndOfTour
+ }
+ page := tour.Links[tour.Index]
+ tour.Index += 1
+
+ return Navigate(state, page, -1, conf)
+}
+
+func TourPrevious(state *BrowserState, conf *Config) error {
+ tour := state.CurrentTour
+ if tour.Index <= 0 {
+ return ErrStartOfTour
+ }
+ tour.Index -= 1
+ if tour.Index <= 0 {
+ return ErrStartOfTour
+ }
+ page := tour.Links[tour.Index-1]
+
+ return Navigate(state, page, -1, conf)
+}
+
+func TourClear(state *BrowserState) error {
+ state.CurrentTour.Index = 0
+ state.CurrentTour.Links = nil
+
+ if state.CurrentTour != &state.DefaultTour {
+ return saveTours(state.NamedTours)
+ }
+ return nil
+}
+
+func TourList(state *BrowserState) error {
+ buf := &bytes.Buffer{}
+ mark := ""
+ if state.CurrentTour == &state.DefaultTour {
+ mark = "* "
+ }
+ if _, err := fmt.Fprintf(buf, "%s(default): %d links\n", mark, len(state.DefaultTour.Links)); err != nil {
+ return err
+ }
+ for name, tour := range state.NamedTours {
+ mark = ""
+ if tour == state.CurrentTour {
+ mark = "* "
+ }
+ if _, err := fmt.Fprintf(buf, "%s%s: %d links\n", mark, name, len(tour.Links)); err != nil {
+ return err
+ }
+ }
+
+ state.Modal = buf.Bytes()
+ return Print(state)
+}
+
+func TourGo(state *BrowserState, conf *Config, pos string) error {
+ tour := state.CurrentTour
+
+ i, _ := strconv.Atoi(pos)
+ if i < 0 || i >= len(tour.Links) {
+ return ErrInvalidTourPos
+ }
+
+ tour.Index = i + 1
+ return Navigate(state, tour.Links[i], -1, conf)
+}
+
+func TourSet(state *BrowserState, name string) error {
+ tour, err := findTour(state, name)
+ if err == nil {
+ state.CurrentTour = tour
+ }
+ return err
+}
+
+func findTour(state *BrowserState, prefix string) (*Tour, error) {
+ if prefix == "" {
+ return &state.DefaultTour, nil
+ }
+
+ found := 0
+ var value *Tour
+ for name, tour := range state.NamedTours {
+ if strings.HasPrefix(name, prefix) {
+ found += 1
+ value = tour
+ }
+ }
+
+ switch found {
+ case 0:
+ tour := &Tour{}
+ state.NamedTours[prefix] = tour
+ return tour, nil
+ case 1:
+ return value, nil
+ default:
+ return nil, fmt.Errorf("too ambiguous - found %d matching tours", found)
+ }
+}