This commit is contained in:
tjp 2024-01-14 19:54:15 -07:00
parent b173ca9cb2
commit dfebc9013b
8 changed files with 88 additions and 34 deletions

View File

@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
@ -14,6 +15,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time"
"tildegit.org/tjp/sliderule" "tildegit.org/tjp/sliderule"
"tildegit.org/tjp/sliderule/gemini" "tildegit.org/tjp/sliderule/gemini"
@ -72,22 +74,25 @@ It was written by TJP and released to the public domain.
} }
func Navigate(state *BrowserState, target *url.URL, navIndex int, conf *Config) error { func Navigate(state *BrowserState, target *url.URL, navIndex int, conf *Config) error {
hist := state.History if state.Url == nil || target.String() != state.Url.String() {
pushHistory(state, target, navIndex)
if hist.Url == nil || target.String() != hist.Url.String() {
state.History = &History{
Url: target,
Depth: hist.Depth + 1,
Back: hist,
NavIndex: navIndex,
}
hist.Forward = state.History
} }
state.Modal = nil state.Modal = nil
return Reload(state, conf) return Reload(state, conf)
} }
func pushHistory(state *BrowserState, target *url.URL, navIndex int) {
hist := state.History
state.History = &History{
Url: target,
Depth: hist.Depth + 1,
Back: hist,
NavIndex: navIndex,
}
hist.Forward = state.History
}
func gopherURL(u *url.URL) (string, sliderule.Status) { func gopherURL(u *url.URL) (string, sliderule.Status) {
if u.Scheme != "gopher" || len(u.Path) < 2 || !strings.HasPrefix(u.Path, "/") { if u.Scheme != "gopher" || len(u.Path) < 2 || !strings.HasPrefix(u.Path, "/") {
return u.String(), 0 return u.String(), 0
@ -173,6 +178,8 @@ outer:
default: default:
return fmt.Errorf("gemini response %s: %s", gemini.StatusName(response.Status), response.Meta.(string)) return fmt.Errorf("gemini response %s: %s", gemini.StatusName(response.Status), response.Meta.(string))
} }
} else {
break
} }
} }
@ -190,10 +197,22 @@ outer:
return HandleResource(state, conf) return HandleResource(state, conf)
} }
func requestCtx(timeout time.Duration) (context.Context, context.CancelFunc) {
ctx := context.Background()
if timeout > 0 {
return context.WithTimeout(ctx, timeout)
}
return ctx, func() {}
}
func fetch(state *BrowserState, u string, tlsConf *tls.Config) (*sliderule.Response, error) { func fetch(state *BrowserState, u string, tlsConf *tls.Config) (*sliderule.Response, error) {
ctx, cancel := requestCtx(state.Timeout)
defer cancel()
tlsConf.ClientSessionCache = nil tlsConf.ClientSessionCache = nil
response, err := sliderule.NewClient(tlsConf).Fetch(u) response, err := sliderule.NewClient(tlsConf).Fetch(ctx, u)
var tofuErr *TOFUViolation var tofuErr *TOFUViolation
if errors.As(err, &tofuErr) { if errors.As(err, &tofuErr) {
writeError(err.Error()) writeError(err.Error())
state.Readline.SetPrompt("Trust new certificate instead (y/n)? [n] ") state.Readline.SetPrompt("Trust new certificate instead (y/n)? [n] ")
@ -210,7 +229,9 @@ func fetch(state *BrowserState, u string, tlsConf *tls.Config) (*sliderule.Respo
return nil, err return nil, err
} }
return sliderule.NewClient(tlsConf).Fetch(u) ctx, cancel = requestCtx(state.Timeout)
defer cancel()
return sliderule.NewClient(tlsConf).Fetch(ctx, u)
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
@ -218,8 +239,11 @@ func fetch(state *BrowserState, u string, tlsConf *tls.Config) (*sliderule.Respo
} }
func upload(state *BrowserState, u string, body io.Reader, tlsConf *tls.Config) (*sliderule.Response, error) { func upload(state *BrowserState, u string, body io.Reader, tlsConf *tls.Config) (*sliderule.Response, error) {
ctx, cancel := requestCtx(state.Timeout)
defer cancel()
tlsConf.ClientSessionCache = nil tlsConf.ClientSessionCache = nil
response, err := sliderule.NewClient(tlsConf).Upload(u, body) response, err := sliderule.NewClient(tlsConf).Upload(ctx, u, body)
var tofuErr *TOFUViolation var tofuErr *TOFUViolation
if errors.As(err, &tofuErr) { if errors.As(err, &tofuErr) {
writeError(err.Error()) writeError(err.Error())
@ -237,7 +261,9 @@ func upload(state *BrowserState, u string, body io.Reader, tlsConf *tls.Config)
return nil, err return nil, err
} }
return sliderule.NewClient(tlsConf).Upload(u, body) ctx, cancel = requestCtx(state.Timeout)
defer cancel()
return sliderule.NewClient(tlsConf).Upload(ctx, u, body)
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
@ -306,16 +332,16 @@ func back(state *BrowserState) error {
return nil return nil
} }
func Back(state *BrowserState, num int) error { func Back(state *BrowserState, conf *Config, num int) error {
for i := 0; i < num; i += 1 { for i := 0; i < num; i += 1 {
if err := back(state); err != nil { if err := back(state); err != nil {
return err return err
} }
} }
return print(state) return HandleResource(state, conf)
} }
func Forward(state *BrowserState, num int) error { func Forward(state *BrowserState, conf *Config, num int) error {
for i := 0; i < num; i += 1 { for i := 0; i < num; i += 1 {
if state.Forward == nil { if state.Forward == nil {
return ErrNoNextHistory return ErrNoNextHistory
@ -324,7 +350,7 @@ func Forward(state *BrowserState, num int) error {
} }
state.Modal = nil state.Modal = nil
return print(state) return HandleResource(state, conf)
} }
func Next(state *BrowserState, conf *Config) error { func Next(state *BrowserState, conf *Config) error {

View File

@ -383,13 +383,13 @@ func RunCommand(conf *Config, cmd *Command, state *BrowserState) error {
if len(cmd.Args) == 1 { if len(cmd.Args) == 1 {
num, _ = strconv.Atoi(cmd.Args[0]) num, _ = strconv.Atoi(cmd.Args[0])
} }
return Back(state, num) return Back(state, conf, num)
case "forward": case "forward":
num := 1 num := 1
if len(cmd.Args) == 1 { if len(cmd.Args) == 1 {
num, _ = strconv.Atoi(cmd.Args[0]) num, _ = strconv.Atoi(cmd.Args[0])
} }
return Forward(state, num) return Forward(state, conf, num)
case "next": case "next":
return Next(state, conf) return Next(state, conf)
case "previous": case "previous":

View File

@ -11,17 +11,19 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"syscall" "syscall"
"time"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
type ConfigMain struct { type ConfigMain struct {
DefaultScheme string `toml:"default_scheme"` DefaultScheme string `toml:"default_scheme"`
SoftWrap int `toml:"soft_wrap"` SoftWrap int `toml:"soft_wrap"`
DownloadFolder string `toml:"download_folder"` DownloadFolder string `toml:"download_folder"`
VimKeys bool `toml:"vim_keys"` VimKeys bool `toml:"vim_keys"`
Quiet bool `toml:"quiet"` Quiet bool `toml:"quiet"`
Pager string `toml:"pager"` Pager string `toml:"pager"`
Timeout duration `toml:"duration"`
} }
type Config struct { type Config struct {
@ -30,6 +32,14 @@ type Config struct {
Handlers map[string]string `toml:"handlers"` Handlers map[string]string `toml:"handlers"`
} }
type duration struct{ time.Duration }
func (d *duration) UnmarshalText(text []byte) error {
var err error
d.Duration, err = time.ParseDuration(string(text))
return err
}
func getConfig() (*Config, error) { func getConfig() (*Config, error) {
home := os.Getenv("HOME") home := os.Getenv("HOME")
path := os.Getenv("XDG_CONFIG_HOME") path := os.Getenv("XDG_CONFIG_HOME")
@ -50,6 +60,9 @@ func getConfig() (*Config, error) {
DownloadFolder: home, DownloadFolder: home,
Quiet: false, Quiet: false,
Pager: "auto", Pager: "auto",
Timeout: duration{
time.Duration(10 * time.Second),
},
}, },
Handlers: map[string]string{}, Handlers: map[string]string{},
} }

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.21.0
require ( require (
github.com/BurntSushi/toml v1.3.2 github.com/BurntSushi/toml v1.3.2
github.com/chzyer/readline v1.5.1 github.com/chzyer/readline v1.5.1
tildegit.org/tjp/sliderule v1.6.2-0.20240110181009-de1490808fa6 tildegit.org/tjp/sliderule v1.6.2-0.20240115025310-751f423f11bf
) )
require ( require (

4
go.sum
View File

@ -20,5 +20,5 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40W
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
tildegit.org/tjp/sliderule v1.6.2-0.20240110181009-de1490808fa6 h1:nVGF/L3NI+dqmlEdagFFjZZabPMPOocr1zVW1qBfGVg= tildegit.org/tjp/sliderule v1.6.2-0.20240115025310-751f423f11bf h1:p0MqM4m/LcgLjRH24OOV+oNOu/8+alABAdI6kLantvE=
tildegit.org/tjp/sliderule v1.6.2-0.20240110181009-de1490808fa6/go.mod h1:opdo8E25iS9X9pNismM8U7pCH8XO0PdRIIhdADn8Uik= tildegit.org/tjp/sliderule v1.6.2-0.20240115025310-751f423f11bf/go.mod h1:opdo8E25iS9X9pNismM8U7pCH8XO0PdRIIhdADn8Uik=

View File

@ -64,7 +64,8 @@ Consult "help COMMAND" for more information on any single command.
`[1:], `[1:],
"cli": ` "cli": `
x-1 [-c COMMANDS] [URL] x-1 -h
x-1 [-q] [-c COMMANDS] [URL]
----------------------- -----------------------
With no arguments or flags, x-1 will just display the prompt and begin With no arguments or flags, x-1 will just display the prompt and begin
executing your commands. Use the "help" command to begin exploring this executing your commands. Use the "help" command to begin exploring this
@ -75,6 +76,9 @@ provided by separating them with a semi-colon ';') and then exit. In
this mode it also forces quiet mode, in which it doesn't automatically this mode it also forces quiet mode, in which it doesn't automatically
print loaded pages. print loaded pages.
The -q flag will force quiet mode, overriding "quiet" in the
configuration file.
With a URL argument, it will begin an interactive prompt session by With a URL argument, it will begin an interactive prompt session by
loading the requested url. loading the requested url.
`[1:], `[1:],
@ -122,6 +126,9 @@ The section "[main]" contains general configuration options:
will pipe every page printed through less(1), "never" will not, and will pipe every page printed through less(1), "never" will not, and
"auto" will pipe it through "less -F", which skips the pager when "auto" will pipe it through "less -F", which skips the pager when
the output fits on a single screen anyway. the output fits on a single screen anyway.
* timeout (string): Maximum time to wait trying to make a connection
to the host. Should be in the form with a unit suffix, like "15s" or
"500ms". The default is "10s".
`[1:], `[1:],

View File

@ -13,6 +13,7 @@ import (
var cmdMode = flag.String("c", "", "") var cmdMode = flag.String("c", "", "")
var helpMode = flag.Bool("h", false, "") var helpMode = flag.Bool("h", false, "")
var quietMode = flag.Bool("q", false, "")
func main() { func main() {
conf, err := getConfig() conf, err := getConfig()
@ -62,6 +63,10 @@ func main() {
return return
} }
if *quietMode {
state.Quiet = true
}
rl, err := readline.New(Prompt) rl, err := readline.New(Prompt)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -2,6 +2,7 @@ package main
import ( import (
"net/url" "net/url"
"time"
"github.com/chzyer/readline" "github.com/chzyer/readline"
) )
@ -19,8 +20,9 @@ type BrowserState struct {
DefaultTour Tour DefaultTour Tour
CurrentTour *Tour CurrentTour *Tour
Quiet bool Quiet bool
Pager string Pager string
Timeout time.Duration
Readline *readline.Instance Readline *readline.Instance
} }
@ -58,8 +60,9 @@ func NewBrowserState(conf *Config) *BrowserState {
NavIndex: -1, NavIndex: -1,
}, },
Quiet: conf.Quiet, Quiet: conf.Quiet,
Pager: conf.Pager, Pager: conf.Pager,
Timeout: conf.Timeout.Duration,
} }
state.CurrentTour = &state.DefaultTour state.CurrentTour = &state.DefaultTour
return state return state