232 lines
5.8 KiB
Go
232 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/fatih/color"
|
|
ln "github.com/peterh/liner"
|
|
flag "github.com/spf13/pflag"
|
|
)
|
|
|
|
// flags
|
|
var (
|
|
noInteractive = flag.BoolP("no-interactive", "I", false, "don't go to the line-mode interface\n")
|
|
appendInput = flag.StringP("input", "i", "", "append input to URL ('?' + percent-encoded input)\n")
|
|
helpFlag = flag.BoolP("help", "h", false, "get help on the cli")
|
|
searchFlag = flag.StringP("search", "s", "", "search with the search engine (this takes priority over URL and --input)\n")
|
|
versionFlag = flag.BoolP("version", "v", false, "print the version and exit\n")
|
|
)
|
|
|
|
var (
|
|
// quoteFieldRe greedily matches between matching pairs of '', "", or
|
|
// non-word characters.
|
|
quoteFieldRe = regexp.MustCompile("'(.*)'|\"(.*)\"|(\\S*)")
|
|
)
|
|
|
|
// Pager uses `less` to display body
|
|
// falls back to fmt.Print if errors encountered
|
|
func Pager(body string, conf *Config) {
|
|
cmd := exec.Command("less")
|
|
stdin, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
fmt.Print(body)
|
|
return
|
|
}
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Env = append(os.Environ(), "LESS="+conf.LessOpts)
|
|
if err := cmd.Start(); err != nil {
|
|
fmt.Print(body)
|
|
return
|
|
}
|
|
io.WriteString(stdin, body)
|
|
stdin.Close()
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Wait()
|
|
}
|
|
|
|
func queryEscape(s string) string {
|
|
return strings.ReplaceAll(url.QueryEscape(s), "+", "%20")
|
|
}
|
|
|
|
var (
|
|
Version string = "version unknown"
|
|
)
|
|
|
|
func main() {
|
|
// command-line stuff
|
|
flag.Usage = func() { // Usage override
|
|
fmt.Fprintf(os.Stderr, "Usage: %s [FLAGS] [URL]\n", os.Args[0])
|
|
fmt.Fprintf(os.Stderr, "\nFlags:\n")
|
|
flag.PrintDefaults()
|
|
fmt.Fprintf(os.Stderr, "For help on the TUI client, type ? at interactive prompt, or see gelim(1)\n")
|
|
}
|
|
flag.Parse()
|
|
if *helpFlag { // Handling --help myself since pflag prints an ugly ErrHelp
|
|
flag.Usage()
|
|
return
|
|
}
|
|
|
|
if *versionFlag {
|
|
fmt.Println("gelim", Version)
|
|
return
|
|
}
|
|
|
|
u := ""
|
|
cliURL := false // this is to avoid going to c.conf.StartURL if URL is visited from CLI
|
|
|
|
c, err := NewClient()
|
|
if err != nil {
|
|
c.style.ErrorMsg(err.Error())
|
|
os.Exit(1)
|
|
}
|
|
if *searchFlag != "" {
|
|
c.Search(*searchFlag) // it's "searchQuery" more like
|
|
cliURL = true
|
|
} else { // need else because when user use --search we should ignore URL and --input
|
|
u = flag.Arg(0) // URL
|
|
if u != "" {
|
|
if *appendInput != "" {
|
|
u = u + "?" + queryEscape(*appendInput)
|
|
}
|
|
c.HandleURLWrapper(u)
|
|
cliURL = true
|
|
} else {
|
|
// if --input used but url arg is not present
|
|
if *appendInput != "" {
|
|
c.style.ErrorMsg("ERROR: --input used without an URL argument")
|
|
// should we print usage?
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|
|
if *noInteractive {
|
|
return
|
|
}
|
|
|
|
if c.conf.StartURL != "" && !cliURL {
|
|
c.HandleURLWrapper(c.conf.StartURL)
|
|
}
|
|
|
|
// and now here comes the line-mode prompts and stuff
|
|
rl := c.mainReader
|
|
|
|
for {
|
|
var line string
|
|
var err error
|
|
|
|
color.Set(c.style.Prompt)
|
|
if c.promptSuggestion != "" {
|
|
line, err = rl.PromptWithSuggestion(c.parsePrompt()+" ", c.promptSuggestion, -1)
|
|
c.promptSuggestion = ""
|
|
} else {
|
|
line, err = rl.Prompt(c.parsePrompt() + " ")
|
|
}
|
|
color.Unset()
|
|
|
|
if err != nil {
|
|
if err == ln.ErrPromptAborted || err == io.EOF {
|
|
// Exit by ^C or ^D
|
|
c.QuitClient(0)
|
|
if err == io.EOF {
|
|
fmt.Println()
|
|
}
|
|
}
|
|
c.style.ErrorMsg("Error reading input: " + err.Error())
|
|
// Exiting because it will cause an infinite loop of error if used 'continue' here
|
|
c.QuitClient(1)
|
|
}
|
|
|
|
rl.AppendHistory(line)
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
// Get our command and args! ✨
|
|
cmd, cmdStr, args, ok := c.GetCommandAndArgs(line)
|
|
// Vamos
|
|
if ok {
|
|
cmd.do(c, args...)
|
|
continue
|
|
}
|
|
// Reaches here only if it was not a valid command
|
|
if strings.Contains(cmdStr, ".") || strings.Contains(cmdStr, "/") {
|
|
// looks like an URL
|
|
|
|
var parsed *url.URL
|
|
|
|
u = cmdStr
|
|
parsed, err = url.Parse(u)
|
|
if err != nil {
|
|
c.style.ErrorMsg("Invalid url")
|
|
continue
|
|
}
|
|
// Adding default scheme
|
|
// Example:
|
|
// If current url is example.com, and user would like to visit
|
|
// example.com/foo.txt they can type "/foo.txt", and if they use
|
|
// "foo.txt" it would lead to gemini://foo.txt which means if
|
|
// current url is example.com/bar/ and user wants
|
|
// example.com/bar/foo.txt, they can either use "/bar/foo.txt" or
|
|
// "./foo.txt" so if user want to do relative path it has to start
|
|
// with / or .
|
|
//
|
|
// TLDR
|
|
// ----
|
|
// "foo.txt" -> "gemini://foo.txt"
|
|
// "./foo.txt" -> "gemini://current-url.org/foo.txt"
|
|
if (parsed.Scheme == "" || parsed.Host == "") &&
|
|
(!strings.HasPrefix(u, ".")) && (!strings.HasPrefix(u, "/")) {
|
|
parsed, err = url.Parse("gemini://" + u)
|
|
if err != nil {
|
|
// Haven't actually encountered this case before (not
|
|
// sure if it's even possible) but I'll put it here
|
|
// just in case
|
|
c.style.ErrorMsg("Invalid url")
|
|
continue
|
|
}
|
|
}
|
|
// this allows users to use relative urls at the prompt
|
|
if len(c.history) != 0 {
|
|
parsed = c.history[len(c.history)-1].ResolveReference(parsed)
|
|
} else {
|
|
if strings.HasPrefix(u, ".") || strings.HasPrefix(u, "/") {
|
|
c.style.ErrorMsg("No history yet, cannot use relative URLs")
|
|
continue
|
|
}
|
|
}
|
|
c.HandleParsedURL(parsed)
|
|
continue
|
|
}
|
|
// at this point the user input is probably not an url
|
|
index, err := strconv.Atoi(cmdStr)
|
|
if err != nil {
|
|
// looks like an unknown command
|
|
c.style.ErrorMsg("Unknown command. Hint: try typing ? and hit enter")
|
|
continue
|
|
}
|
|
// link index lookup
|
|
if len(c.history) == 0 {
|
|
c.style.ErrorMsg("No history yet, cannot use link indexing")
|
|
continue
|
|
}
|
|
u, spartanInput := c.GetLinkFromIndex(index)
|
|
if u == "" {
|
|
// TODO: When is this reached? Add message?
|
|
continue
|
|
}
|
|
if spartanInput {
|
|
c.Input(u, false)
|
|
continue
|
|
}
|
|
c.HandleURLWrapper(u)
|
|
}
|
|
}
|