226 lines
4.9 KiB
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)
|
|
}
|
|
}
|