x-1/tour.go

226 lines
4.9 KiB
Go

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, targets []string) error {
newurls := []*url.URL{}
for _, target := range targets {
urls, err := parseURLs(state, state.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)
}
state.Modal = []byte(fmt.Sprintf("Added %d urls to the tour\n", len(newurls)))
return Print(state)
}
func TourAddNext(state *BrowserState, targets []string) error {
newurls := []*url.URL{}
for _, target := range targets {
urls, err := parseURLs(state, state.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)
}
state.Modal = []byte(fmt.Sprintf("Added %d urls to go next on the tour\n", len(newurls)))
return Print(state)
}
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) 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)
}
func TourPrevious(state *BrowserState) 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)
}
func TourClear(state *BrowserState) error {
state.CurrentTour.Index = 0
state.CurrentTour.Links = nil
if state.CurrentTour != &state.DefaultTour {
return saveTours(state.NamedTours)
}
state.Modal = []byte("Tour is cleared\n")
return Print(state)
}
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, 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)
}
func TourSelect(state *BrowserState, name string) error {
tourName, tour, err := findTour(state, name)
if err != nil {
return err
}
state.CurrentTour = tour
state.Modal = []byte(fmt.Sprintf("Tour %s is now active\n", tourName))
return Print(state)
}
func findTour(state *BrowserState, prefix string) (string, *Tour, error) {
if prefix == "" {
return "(default)", &state.DefaultTour, nil
}
found := 0
var value *Tour
var tourName string
for name, tour := range state.NamedTours {
if strings.HasPrefix(name, prefix) {
found += 1
value = tour
tourName = name
}
}
switch found {
case 0:
tour := &Tour{}
state.NamedTours[prefix] = tour
return prefix, tour, nil
case 1:
return tourName, value, nil
default:
return "", nil, fmt.Errorf("too ambiguous - found %d matching tours", found)
}
}