Initial v2 commit, deep in restructuring... maybe not for the better?
This commit is contained in:
parent
be34a9a809
commit
da45f627e0
|
@ -0,0 +1,68 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + T Y P E S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
type Bookmarks struct {
|
||||||
|
IsOpen bool
|
||||||
|
IsFocused bool
|
||||||
|
Position int
|
||||||
|
Length int
|
||||||
|
Titles []string
|
||||||
|
Links []string
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + R E C E I V E R S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func (b *Bookmarks) Add([]string) error {
|
||||||
|
// TODO add a bookmark
|
||||||
|
return fmt.Errorf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bookmarks) Delete(int) error {
|
||||||
|
// TODO delete a bookmark
|
||||||
|
return fmt.Errorf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bookmarks) ToggleOpen() {
|
||||||
|
b.IsOpen = !b.IsOpen
|
||||||
|
if b.IsOpen {
|
||||||
|
b.IsFocused = true
|
||||||
|
} else {
|
||||||
|
b.IsFocused = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bookmarks) ToggleFocused() {
|
||||||
|
if b.IsOpen {
|
||||||
|
b.IsFocused = !b.IsFocused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bookmarks) IniDump() string {
|
||||||
|
// TODO create dump of values for INI file
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bookmarks) Render() ([]string, error) {
|
||||||
|
// TODO grab all of the bookmarks as a fixed
|
||||||
|
// width string including border and spacing
|
||||||
|
return []string{}, fmt.Errorf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + F U N C T I O N S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func MakeBookmarks() Bookmarks {
|
||||||
|
return Bookmarks{false, false, 0, 0, make([]string, 0), make([]string, 0)}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,541 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tildegit.org/sloum/bombadillo/cmdparse"
|
||||||
|
"tildegit.org/sloum/bombadillo/cui"
|
||||||
|
"tildegit.org/sloum/bombadillo/gopher"
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + T Y P E S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
Height int
|
||||||
|
Width int
|
||||||
|
Options map[string]string
|
||||||
|
Message string
|
||||||
|
PageState Pages
|
||||||
|
BookMarks Bookmarks
|
||||||
|
TopBar Headbar
|
||||||
|
FootBar Footbar
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + R E C E I V E R S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func (c *client) GetSize() {
|
||||||
|
for {
|
||||||
|
redraw := false
|
||||||
|
cmd := exec.Command("stty", "size")
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Fatal error: Unable to retrieve terminal size")
|
||||||
|
os.Exit(5)
|
||||||
|
}
|
||||||
|
var h, w int
|
||||||
|
fmt.Sscan(string(out), &h, &w)
|
||||||
|
if h != c.Height || w != c.Width {
|
||||||
|
redraw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Height = h
|
||||||
|
c.Width = w
|
||||||
|
|
||||||
|
if redraw {
|
||||||
|
c.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Draw() {
|
||||||
|
// TODO build this out.
|
||||||
|
// It should call all of the renders
|
||||||
|
// and add them to the a string buffer
|
||||||
|
// It should then print the buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) TakeControlInput() {
|
||||||
|
input := cui.Getch()
|
||||||
|
|
||||||
|
switch input {
|
||||||
|
case 'j', 'J':
|
||||||
|
// scroll down one line
|
||||||
|
c.Scroll(1)
|
||||||
|
case 'k', 'K':
|
||||||
|
// scroll up one line
|
||||||
|
c.Scroll(-1)
|
||||||
|
case 'q', 'Q':
|
||||||
|
// quite bombadillo
|
||||||
|
cui.Exit()
|
||||||
|
case 'g':
|
||||||
|
// scroll to top
|
||||||
|
c.Scroll(-len(c.PageState.History[c.PageState.Position].WrappedContent))
|
||||||
|
case 'G':
|
||||||
|
// scroll to bottom
|
||||||
|
c.Scroll(len(c.PageState.History[c.PageState.Position].WrappedContent))
|
||||||
|
case 'd':
|
||||||
|
// scroll down 75%
|
||||||
|
distance := c.Height - c.Height / 4
|
||||||
|
c.Scroll(distance)
|
||||||
|
case 'u':
|
||||||
|
// scroll up 75%
|
||||||
|
distance := c.Height - c.Height / 4
|
||||||
|
c.Scroll(-distance)
|
||||||
|
case 'b':
|
||||||
|
// go back
|
||||||
|
err := c.PageState.NavigateHistory(-1)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), false)
|
||||||
|
c.DrawMessage()
|
||||||
|
} else {
|
||||||
|
c.Draw()
|
||||||
|
}
|
||||||
|
case 'B':
|
||||||
|
// open the bookmarks browser
|
||||||
|
c.BookMarks.ToggleOpen()
|
||||||
|
c.Draw()
|
||||||
|
case 'f', 'F':
|
||||||
|
// go forward
|
||||||
|
err := c.PageState.NavigateHistory(1)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), false)
|
||||||
|
c.DrawMessage()
|
||||||
|
} else {
|
||||||
|
c.Draw()
|
||||||
|
}
|
||||||
|
case '\t':
|
||||||
|
// Toggle bookmark browser focus on/off
|
||||||
|
c.BookMarks.ToggleFocused()
|
||||||
|
c.Draw()
|
||||||
|
case ':', ' ':
|
||||||
|
// Process a command
|
||||||
|
c.ClearMessage()
|
||||||
|
c.ClearMessageLine()
|
||||||
|
entry, err := cui.GetLine()
|
||||||
|
c.ClearMessageLine()
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
break
|
||||||
|
} else if strings.TrimSpace(entry) == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := cmdparse.NewParser(strings.NewReader(entry))
|
||||||
|
p, err := parser.Parse()
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
} else {
|
||||||
|
err := c.routeCommandInput(p)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (c *client) routeCommandInput(com *cmdparse.Command) error {
|
||||||
|
var err error
|
||||||
|
switch com.Type {
|
||||||
|
case cmdparse.SIMPLE:
|
||||||
|
c.simpleCommand(com.Action)
|
||||||
|
case cmdparse.GOURL:
|
||||||
|
c.goToURL(com.Target)
|
||||||
|
case cmdparse.GOLINK:
|
||||||
|
c.goToLink(com.Target)
|
||||||
|
case cmdparse.DO:
|
||||||
|
c.doCommand(com.Action, com.Value)
|
||||||
|
case cmdparse.DOLINK:
|
||||||
|
// err = doLinkCommand(com.Action, com.Target)
|
||||||
|
case cmdparse.DOAS:
|
||||||
|
c.doCommandAs(com.Action, com.Value)
|
||||||
|
case cmdparse.DOLINKAS:
|
||||||
|
// err = doLinkCommandAs(com.Action, com.Target, com.Value)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknown command entry!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) simpleCommand(action string) {
|
||||||
|
action = strings.ToUpper(action)
|
||||||
|
switch action {
|
||||||
|
case "Q", "QUIT":
|
||||||
|
cui.Exit()
|
||||||
|
case "H", "HOME":
|
||||||
|
if c.Options["homeurl"] != "unset" {
|
||||||
|
go c.Visit(c.Options["homeurl"])
|
||||||
|
} else {
|
||||||
|
c.SetMessage(fmt.Sprintf("No home address has been set"), false)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
case "B", "BOOKMARKS":
|
||||||
|
c.BookMarks.ToggleOpen()
|
||||||
|
case "SEARCH":
|
||||||
|
c.search()
|
||||||
|
case "HELP", "?":
|
||||||
|
go c.Visit(helplocation)
|
||||||
|
default:
|
||||||
|
c.SetMessage(fmt.Sprintf("Unknown action %q", action), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) doCommand(action string, values []string) {
|
||||||
|
if length := len(values); length != 1 {
|
||||||
|
c.SetMessage(fmt.Sprintf("Expected 1 argument, received %d", len(values)), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "CHECK", "C":
|
||||||
|
c.displayConfigValue(values[0])
|
||||||
|
default:
|
||||||
|
c.SetMessage(fmt.Sprintf("Unknown action %q", action), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) doCommandAs(action string, values []string) {
|
||||||
|
if len(values) < 2 {
|
||||||
|
c.SetMessage(fmt.Sprintf("Expected 1 argument, received %d", len(values)), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if values[0] == "." {
|
||||||
|
values[0] = c.PageState.History[c.PageState.Position].Location.Full
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "ADD", "A":
|
||||||
|
err := c.BookMarks.Add(values)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveConfig()
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage("Error saving bookmark to file", true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
if c.BookMarks.IsOpen {
|
||||||
|
c.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
case "WRITE", "W":
|
||||||
|
// TODO figure out how best to handle file
|
||||||
|
// writing... it will depend on request model
|
||||||
|
// using fetch would be best
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
// var data []byte
|
||||||
|
// if values[0] == "." {
|
||||||
|
// d, err := c.getCurrentPageRawData()
|
||||||
|
// if err != nil {
|
||||||
|
// c.SetMessage(err.Error(), true)
|
||||||
|
// c.DrawMessage()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// data = []byte(d)
|
||||||
|
// }
|
||||||
|
// fp, err := c.saveFile(data, strings.Join(values[1:], " "))
|
||||||
|
// if err != nil {
|
||||||
|
// c.SetMessage(err.Error(), true)
|
||||||
|
// c.DrawMessage()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// c.SetMessage(fmt.Sprintf("File saved to: %s", fp), false)
|
||||||
|
// c.DrawMessage()
|
||||||
|
|
||||||
|
case "SET", "S":
|
||||||
|
if _, ok := c.Options[values[0]]; ok {
|
||||||
|
c.Options[values[0]] = strings.Join(values[1:], " ")
|
||||||
|
err := saveConfig()
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage("Value set, but error saving config to file", true)
|
||||||
|
c.DrawMessage()
|
||||||
|
} else {
|
||||||
|
c.SetMessage(fmt.Sprintf("%s is now set to %q", values[0], c.Options[values[0]]), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.SetMessage(fmt.Sprintf("Unable to set %s, it does not exist", values[0]), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.SetMessage(fmt.Sprintf("Unknown command structure"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) getCurrentPageUrl() (string, error) {
|
||||||
|
if c.PageState.Length < 1 {
|
||||||
|
return "", fmt.Errorf("There are no pages in history")
|
||||||
|
}
|
||||||
|
return c.PageState.History[c.PageState.Position].Location.Full, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) getCurrentPageRawData() (string, error) {
|
||||||
|
if c.PageState.Length < 1 {
|
||||||
|
return "", fmt.Errorf("There are no pages in history")
|
||||||
|
}
|
||||||
|
return c.PageState.History[c.PageState.Position].RawContent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) saveFile(data []byte, name string) (string, error) {
|
||||||
|
savePath := c.Options["savelocation"] + name
|
||||||
|
err := ioutil.WriteFile(savePath, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return savePath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) search() {
|
||||||
|
c.ClearMessage()
|
||||||
|
c.ClearMessageLine()
|
||||||
|
fmt.Print("?")
|
||||||
|
entry, err := cui.GetLine()
|
||||||
|
c.ClearMessageLine()
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
} else if strings.TrimSpace(entry) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u, err := MakeUrl(c.Options["searchurl"])
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage("'searchurl' is not set to a valid url", true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch u.Scheme {
|
||||||
|
case "gopher":
|
||||||
|
go c.Visit(fmt.Sprintf("%s\t%s",u.Full,entry))
|
||||||
|
case "gemini":
|
||||||
|
// TODO url escape the entry variable
|
||||||
|
escapedEntry := entry
|
||||||
|
go c.Visit(fmt.Sprintf("%s?%s",u.Full,escapedEntry))
|
||||||
|
case "http", "https":
|
||||||
|
c.SetMessage("Attempting to open in web browser", false)
|
||||||
|
c.DrawMessage()
|
||||||
|
err := gopher.OpenBrowser(u.Full)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
} else {
|
||||||
|
c.SetMessage("Opened in web browser", false)
|
||||||
|
}
|
||||||
|
c.DrawMessage()
|
||||||
|
default:
|
||||||
|
c.SetMessage(fmt.Sprintf("%q is not a supported protocol", u.Scheme), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Scroll(amount int) {
|
||||||
|
page := c.PageState.History[c.PageState.Position]
|
||||||
|
bottom := len(page.WrappedContent) - c.Height
|
||||||
|
if amount < 0 && page.ScrollPosition == 0 {
|
||||||
|
c.SetMessage("You are already at the top", false)
|
||||||
|
c.DrawMessage()
|
||||||
|
fmt.Print("\a")
|
||||||
|
return
|
||||||
|
} else if amount > 0 && page.ScrollPosition == bottom || bottom < 0 {
|
||||||
|
c.SetMessage("You are already at the bottom", false)
|
||||||
|
c.DrawMessage()
|
||||||
|
fmt.Print("\a")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newScrollPosition := page.ScrollPosition + amount
|
||||||
|
if newScrollPosition < 0 {
|
||||||
|
newScrollPosition = 0
|
||||||
|
} else if newScrollPosition > bottom {
|
||||||
|
newScrollPosition = bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
page.ScrollPosition = newScrollPosition
|
||||||
|
c.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) displayConfigValue(setting string) {
|
||||||
|
if val, ok := c.Options[setting]; ok {
|
||||||
|
c.SetMessage(fmt.Sprintf("%s is set to: %q", setting, val), false)
|
||||||
|
c.DrawMessage()
|
||||||
|
} else {
|
||||||
|
c.SetMessage(fmt.Sprintf("Invalid: %q does not exist", setting), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) SetMessage(msg string, isError bool) {
|
||||||
|
leadIn, leadOut := "", ""
|
||||||
|
if isError {
|
||||||
|
leadIn = "\033[31m"
|
||||||
|
leadOut = "\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Message = fmt.Sprintf("%s%s%s", leadIn, msg, leadOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) DrawMessage() {
|
||||||
|
c.ClearMessageLine()
|
||||||
|
cui.MoveCursorTo(c.Height-1, 0)
|
||||||
|
fmt.Print(c.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) ClearMessage() {
|
||||||
|
c.Message = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) ClearMessageLine() {
|
||||||
|
cui.MoveCursorTo(c.Height-1, 0)
|
||||||
|
cui.Clear("line")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) goToURL(u string) {
|
||||||
|
if num, _ := regexp.MatchString(`^-?\d+.?\d*$`, u); num {
|
||||||
|
c.goToLink(u)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go c.Visit(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) goToLink(l string) {
|
||||||
|
if num, _ := regexp.MatchString(`^-?\d+$`, l); num && c.PageState.Length > 0 {
|
||||||
|
linkcount := len(c.PageState.History[c.PageState.Position].Links)
|
||||||
|
item, err := strconv.Atoi(l)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(fmt.Sprintf("Invalid link id: %s", l), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if item <= linkcount && item > 0 {
|
||||||
|
linkurl := c.PageState.History[c.PageState.Position].Links[item-1]
|
||||||
|
c.Visit(linkurl)
|
||||||
|
} else {
|
||||||
|
c.SetMessage(fmt.Sprintf("Invalid link id: %s", l), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SetMessage(fmt.Sprintf("Invalid link id: %s", l), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Visit(url string) {
|
||||||
|
u, err := MakeUrl(url)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "gopher":
|
||||||
|
// TODO send over to gopher request
|
||||||
|
case "gemini":
|
||||||
|
// TODO send over to gemini request
|
||||||
|
case "http", "https":
|
||||||
|
c.SetMessage("Attempting to open in web browser", false)
|
||||||
|
c.DrawMessage()
|
||||||
|
if strings.ToUpper(c.Options["openhttp"]) == "TRUE" {
|
||||||
|
err := gopher.OpenBrowser(u.Full)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
} else {
|
||||||
|
c.SetMessage("Opened in web browser", false)
|
||||||
|
}
|
||||||
|
c.DrawMessage()
|
||||||
|
} else {
|
||||||
|
c.SetMessage("'openhttp' is not set to true, aborting opening web link", false)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
c.SetMessage(fmt.Sprintf("%q is not a supported protocol", u.Scheme), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + F U N C T I O N S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func MakeClient(name string) *client {
|
||||||
|
var userinfo, _ = user.Current()
|
||||||
|
var options = map[string]string{
|
||||||
|
"homeurl": "gopher://colorfield.space:70/1/bombadillo-info",
|
||||||
|
"savelocation": userinfo.HomeDir,
|
||||||
|
"searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs",
|
||||||
|
"openhttp": "false",
|
||||||
|
"httpbrowser": "lynx",
|
||||||
|
"configlocation": userinfo.HomeDir,
|
||||||
|
}
|
||||||
|
c := client{0, 0, options, "", MakePages(), MakeBookmarks(), MakeHeadbar(name), MakeFootbar()}
|
||||||
|
c.GetSize()
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve a byte slice of raw response dataa
|
||||||
|
// from a url string
|
||||||
|
func Fetch(url string) ([]byte, error) {
|
||||||
|
u, err := MakeUrl(url)
|
||||||
|
if err != nil {
|
||||||
|
return []byte(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeOut := time.Duration(5) * time.Second
|
||||||
|
|
||||||
|
if u.Host == "" || u.Port == "" {
|
||||||
|
return []byte(""), fmt.Errorf("Incomplete request url")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := u.Host + ":" + u.Port
|
||||||
|
|
||||||
|
conn, err := net.DialTimeout("tcp", addr, timeOut)
|
||||||
|
if err != nil {
|
||||||
|
return []byte(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
send := u.Resource + "\n"
|
||||||
|
|
||||||
|
_, err = conn.Write([]byte(send))
|
||||||
|
if err != nil {
|
||||||
|
return []byte(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := ioutil.ReadAll(conn)
|
||||||
|
if err != nil {
|
||||||
|
return []byte(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + T Y P E S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
type Footbar struct {
|
||||||
|
PercentRead string
|
||||||
|
PageType string
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + R E C E I V E R S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func (f *Footbar) SetPercentRead(p int) {
|
||||||
|
f.PercentRead = fmt.Sprintf("%d%%", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Footbar) SetPageType(t string) {
|
||||||
|
f.PageType = t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Footbar) Draw() {
|
||||||
|
// TODO this will actually draw the bar
|
||||||
|
// without having to redraw everything else
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Footbar) Build(width string) string {
|
||||||
|
// TODO Build out header to specified width
|
||||||
|
f.Content = "" // This is a temp value to show intention
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Footbar) Render() string {
|
||||||
|
// TODO returns a full line
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + F U N C T I O N S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func MakeFootbar() Footbar {
|
||||||
|
return Footbar{"", "N/A", ""}
|
||||||
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ func Visit(addr, openhttp string) (View, error) {
|
||||||
|
|
||||||
if u.Gophertype == "h" {
|
if u.Gophertype == "h" {
|
||||||
if res, tf := isWebLink(u.Resource); tf && strings.ToUpper(openhttp) == "TRUE" {
|
if res, tf := isWebLink(u.Resource); tf && strings.ToUpper(openhttp) == "TRUE" {
|
||||||
err := openBrowser(res)
|
err := OpenBrowser(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return View{}, err
|
return View{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@ package gopher
|
||||||
|
|
||||||
import "os/exec"
|
import "os/exec"
|
||||||
|
|
||||||
func openBrowser(url string) error {
|
func OpenBrowser(url string) error {
|
||||||
return exec.Command("open", url).Start()
|
return exec.Command("open", url).Start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@ package gopher
|
||||||
|
|
||||||
import "os/exec"
|
import "os/exec"
|
||||||
|
|
||||||
func openBrowser(url string) error {
|
func OpenBrowser(url string) error {
|
||||||
return exec.Command("xdg-open", url).Start()
|
return exec.Command("xdg-open", url).Start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,6 @@ package gopher
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func openBrowser(url string) error {
|
func OpenBrowser(url string) error {
|
||||||
return fmt.Errorf("Unsupported os for browser detection")
|
return fmt.Errorf("Unsupported os for browser detection")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@ package gopher
|
||||||
|
|
||||||
import "os/exec"
|
import "os/exec"
|
||||||
|
|
||||||
func openBrowser(url string) error {
|
func OpenBrowser(url string) error {
|
||||||
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + T Y P E S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
type Headbar struct {
|
||||||
|
title string
|
||||||
|
url string
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + R E C E I V E R S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func (h *Headbar) SetUrl(u string) {
|
||||||
|
h.url = u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headbar) Build(width string) string {
|
||||||
|
// TODO Build out header to specified width
|
||||||
|
h.content = "" // This is a temp value to show intention
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headbar) Draw() {
|
||||||
|
// TODO this will actually draw the bar
|
||||||
|
// without having to redraw everything else
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headbar) Render() string {
|
||||||
|
// TODO returns the content value
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + F U N C T I O N S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func MakeHeadbar(title string) Headbar {
|
||||||
|
return Headbar{title, "", title}
|
||||||
|
}
|
||||||
|
|
606
main.go
606
main.go
|
@ -1,402 +1,143 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
// "strconv"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"tildegit.org/sloum/bombadillo/cmdparse"
|
|
||||||
"tildegit.org/sloum/bombadillo/config"
|
"tildegit.org/sloum/bombadillo/config"
|
||||||
"tildegit.org/sloum/bombadillo/cui"
|
"tildegit.org/sloum/bombadillo/cui"
|
||||||
"tildegit.org/sloum/bombadillo/gopher"
|
// "tildegit.org/sloum/bombadillo/gopher"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var bombadillo *client
|
||||||
var helplocation string = "gopher://colorfield.space:70/1/bombadillo-info"
|
var helplocation string = "gopher://colorfield.space:70/1/bombadillo-info"
|
||||||
var history gopher.History = gopher.MakeHistory()
|
|
||||||
var screen *cui.Screen
|
|
||||||
var userinfo, _ = user.Current()
|
|
||||||
var settings config.Config
|
var settings config.Config
|
||||||
var options = map[string]string{
|
|
||||||
"homeurl": "gopher://colorfield.space:70/1/bombadillo-info",
|
|
||||||
"savelocation": userinfo.HomeDir,
|
|
||||||
"searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs",
|
|
||||||
"openhttp": "false",
|
|
||||||
"httpbrowser": "lynx",
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveFile(address, name string) error {
|
|
||||||
quickMessage("Saving file...", false)
|
|
||||||
|
|
||||||
url, err := gopher.MakeUrl(address)
|
// func saveFileFromData(v gopher.View) error {
|
||||||
if err != nil {
|
// quickMessage("Saving file...", false)
|
||||||
quickMessage("Saving file...", true)
|
// urlsplit := strings.Split(v.Address.Full, "/")
|
||||||
return err
|
// filename := urlsplit[len(urlsplit)-1]
|
||||||
}
|
// saveMsg := fmt.Sprintf("Saved file as %q", options["savelocation"]+filename)
|
||||||
|
// err := ioutil.WriteFile(options["savelocation"]+filename, []byte(strings.Join(v.Content, "")), 0644)
|
||||||
|
// if err != nil {
|
||||||
|
// quickMessage("Saving file...", true)
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
data, err := gopher.Retrieve(url)
|
// quickMessage(saveMsg, false)
|
||||||
if err != nil {
|
// return nil
|
||||||
quickMessage("Saving file...", true)
|
// }
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(options["savelocation"]+name, data, 0644)
|
|
||||||
if err != nil {
|
|
||||||
quickMessage("Saving file...", true)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
quickMessage(fmt.Sprintf("Saved file to %s%s", options["savelocation"], name), false)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveFileFromData(v gopher.View) error {
|
// func doLinkCommand(action, target string) error {
|
||||||
quickMessage("Saving file...", false)
|
// num, err := strconv.Atoi(target)
|
||||||
urlsplit := strings.Split(v.Address.Full, "/")
|
// if err != nil {
|
||||||
filename := urlsplit[len(urlsplit)-1]
|
// return fmt.Errorf("Expected number, got %q", target)
|
||||||
saveMsg := fmt.Sprintf("Saved file as %q", options["savelocation"]+filename)
|
// }
|
||||||
err := ioutil.WriteFile(options["savelocation"]+filename, []byte(strings.Join(v.Content, "")), 0644)
|
|
||||||
if err != nil {
|
|
||||||
quickMessage("Saving file...", true)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
quickMessage(saveMsg, false)
|
// switch action {
|
||||||
return nil
|
// case "DELETE", "D":
|
||||||
}
|
// err := settings.Bookmarks.Del(num)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
func search(u string) error {
|
// screen.Windows[1].Content = settings.Bookmarks.List()
|
||||||
cui.MoveCursorTo(screen.Height-1, 0)
|
// err = saveConfig()
|
||||||
cui.Clear("line")
|
// if err != nil {
|
||||||
fmt.Print("Enter form input: ")
|
// return err
|
||||||
cui.MoveCursorTo(screen.Height-1, 17)
|
// }
|
||||||
|
|
||||||
entry, err := cui.GetLine()
|
// screen.ReflashScreen(false)
|
||||||
if err != nil {
|
// return nil
|
||||||
return err
|
// case "BOOKMARKS", "B":
|
||||||
}
|
// if num > len(settings.Bookmarks.Links)-1 {
|
||||||
|
// return fmt.Errorf("There is no bookmark with ID %d", num)
|
||||||
|
// }
|
||||||
|
// err := goToURL(settings.Bookmarks.Links[num])
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
quickMessage("Searching...", false)
|
// return fmt.Errorf("This method has not been built")
|
||||||
searchurl := fmt.Sprintf("%s\t%s", u, entry)
|
// }
|
||||||
sv, err := gopher.Visit(searchurl, options["openhttp"])
|
|
||||||
if err != nil {
|
|
||||||
quickMessage("Searching...", true)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
history.Add(sv)
|
|
||||||
quickMessage("Searching...", true)
|
|
||||||
updateMainContent()
|
|
||||||
screen.Windows[0].Scrollposition = 0
|
|
||||||
screen.ReflashScreen(true)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func routeInput(com *cmdparse.Command) error {
|
|
||||||
var err error
|
|
||||||
switch com.Type {
|
|
||||||
case cmdparse.SIMPLE:
|
|
||||||
err = simpleCommand(com.Action)
|
|
||||||
case cmdparse.GOURL:
|
|
||||||
err = goToURL(com.Target)
|
|
||||||
case cmdparse.GOLINK:
|
|
||||||
err = goToLink(com.Target)
|
|
||||||
case cmdparse.DO:
|
|
||||||
err = doCommand(com.Action, com.Value)
|
|
||||||
case cmdparse.DOLINK:
|
|
||||||
err = doLinkCommand(com.Action, com.Target)
|
|
||||||
case cmdparse.DOAS:
|
|
||||||
err = doCommandAs(com.Action, com.Value)
|
|
||||||
case cmdparse.DOLINKAS:
|
|
||||||
err = doLinkCommandAs(com.Action, com.Target, com.Value)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unknown command entry!")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
// func doCommand(action string, values []string) error {
|
||||||
}
|
// if length := len(values); length != 1 {
|
||||||
|
// return fmt.Errorf("Expected 1 argument, received %d", length)
|
||||||
|
// }
|
||||||
|
|
||||||
func toggleBookmarks() {
|
// switch action {
|
||||||
bookmarks := screen.Windows[1]
|
// case "CHECK", "C":
|
||||||
main := screen.Windows[0]
|
// err := checkConfigValue(values[0])
|
||||||
if bookmarks.Show {
|
// if err != nil {
|
||||||
bookmarks.Show = false
|
// return err
|
||||||
screen.Activewindow = 0
|
// }
|
||||||
main.Active = true
|
// return nil
|
||||||
bookmarks.Active = false
|
// }
|
||||||
} else {
|
// return fmt.Errorf("Unknown command structure")
|
||||||
bookmarks.Show = true
|
// }
|
||||||
screen.Activewindow = 1
|
|
||||||
main.Active = false
|
|
||||||
bookmarks.Active = true
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.ReflashScreen(false)
|
// func doLinkCommandAs(action, target string, values []string) error {
|
||||||
}
|
// num, err := strconv.Atoi(target)
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("Expected number, got %q", target)
|
||||||
|
// }
|
||||||
|
|
||||||
func simpleCommand(a string) error {
|
// links := history.Collection[history.Position].Links
|
||||||
a = strings.ToUpper(a)
|
// if num >= len(links) {
|
||||||
switch a {
|
// return fmt.Errorf("Invalid link id: %s", target)
|
||||||
case "Q", "QUIT":
|
// }
|
||||||
cui.Exit()
|
|
||||||
case "H", "HOME":
|
|
||||||
return goHome()
|
|
||||||
case "B", "BOOKMARKS":
|
|
||||||
toggleBookmarks()
|
|
||||||
case "SEARCH":
|
|
||||||
return search(options["searchengine"])
|
|
||||||
case "HELP", "?":
|
|
||||||
return goToURL(helplocation)
|
|
||||||
|
|
||||||
default:
|
// switch action {
|
||||||
return fmt.Errorf("Unknown action %q", a)
|
// case "ADD", "A":
|
||||||
}
|
// newBookmark := append([]string{links[num-1]}, values...)
|
||||||
return nil
|
// err := settings.Bookmarks.Add(newBookmark)
|
||||||
}
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
func goToURL(u string) error {
|
// screen.Windows[1].Content = settings.Bookmarks.List()
|
||||||
if num, _ := regexp.MatchString(`^-?\d+.?\d*$`, u); num {
|
|
||||||
return goToLink(u)
|
|
||||||
}
|
|
||||||
quickMessage("Loading...", false)
|
|
||||||
v, err := gopher.Visit(u, options["openhttp"])
|
|
||||||
if err != nil {
|
|
||||||
quickMessage("Loading...", true)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
quickMessage("Loading...", true)
|
|
||||||
|
|
||||||
if v.Address.Gophertype == "7" {
|
// err = saveConfig()
|
||||||
err := search(v.Address.Full)
|
// if err != nil {
|
||||||
if err != nil {
|
// return err
|
||||||
return err
|
// }
|
||||||
}
|
|
||||||
} else if v.Address.IsBinary {
|
|
||||||
return saveFileFromData(v)
|
|
||||||
} else {
|
|
||||||
history.Add(v)
|
|
||||||
}
|
|
||||||
updateMainContent()
|
|
||||||
screen.Windows[0].Scrollposition = 0
|
|
||||||
screen.ReflashScreen(true)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func goToLink(l string) error {
|
// screen.ReflashScreen(false)
|
||||||
if num, _ := regexp.MatchString(`^-?\d+$`, l); num && history.Length > 0 {
|
// return nil
|
||||||
linkcount := len(history.Collection[history.Position].Links)
|
// case "WRITE", "W":
|
||||||
item, _ := strconv.Atoi(l)
|
// return saveFile(links[num-1], strings.Join(values, " "))
|
||||||
if item <= linkcount && item > 0 {
|
// }
|
||||||
linkurl := history.Collection[history.Position].Links[item-1]
|
|
||||||
quickMessage("Loading...", false)
|
|
||||||
v, err := gopher.Visit(linkurl, options["openhttp"])
|
|
||||||
if err != nil {
|
|
||||||
quickMessage("Loading...", true)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
quickMessage("Loading...", true)
|
|
||||||
|
|
||||||
if v.Address.Gophertype == "7" {
|
// return fmt.Errorf("This method has not been built")
|
||||||
err := search(linkurl)
|
// }
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if v.Address.IsBinary {
|
|
||||||
return saveFileFromData(v)
|
|
||||||
} else {
|
|
||||||
history.Add(v)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Invalid link id: %s", l)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Invalid link id: %s", l)
|
|
||||||
}
|
|
||||||
updateMainContent()
|
|
||||||
screen.Windows[0].Scrollposition = 0
|
|
||||||
screen.ReflashScreen(true)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func goHome() error {
|
// func updateMainContent() {
|
||||||
if options["homeurl"] != "unset" {
|
// screen.Windows[0].Content = history.Collection[history.Position].Content
|
||||||
return goToURL(options["homeurl"])
|
// screen.Bars[0].SetMessage(history.Collection[history.Position].Address.Full)
|
||||||
}
|
// }
|
||||||
return fmt.Errorf("No home address has been set")
|
|
||||||
}
|
|
||||||
|
|
||||||
func doLinkCommand(action, target string) error {
|
|
||||||
num, err := strconv.Atoi(target)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Expected number, got %q", target)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case "DELETE", "D":
|
|
||||||
err := settings.Bookmarks.Del(num)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.Windows[1].Content = settings.Bookmarks.List()
|
|
||||||
err = saveConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.ReflashScreen(false)
|
|
||||||
return nil
|
|
||||||
case "BOOKMARKS", "B":
|
|
||||||
if num > len(settings.Bookmarks.Links)-1 {
|
|
||||||
return fmt.Errorf("There is no bookmark with ID %d", num)
|
|
||||||
}
|
|
||||||
err := goToURL(settings.Bookmarks.Links[num])
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("This method has not been built")
|
|
||||||
}
|
|
||||||
|
|
||||||
func doCommandAs(action string, values []string) error {
|
|
||||||
if len(values) < 2 {
|
|
||||||
return fmt.Errorf("%q", values)
|
|
||||||
}
|
|
||||||
|
|
||||||
if values[0] == "." {
|
|
||||||
values[0] = history.Collection[history.Position].Address.Full
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case "ADD", "A":
|
|
||||||
err := settings.Bookmarks.Add(values)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.Windows[1].Content = settings.Bookmarks.List()
|
|
||||||
err = saveConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.ReflashScreen(false)
|
|
||||||
return nil
|
|
||||||
case "WRITE", "W":
|
|
||||||
return saveFile(values[0], strings.Join(values[1:], " "))
|
|
||||||
case "SET", "S":
|
|
||||||
if _, ok := options[values[0]]; ok {
|
|
||||||
options[values[0]] = strings.Join(values[1:], " ")
|
|
||||||
return saveConfig()
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Unable to set %s, it does not exist", values[0])
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Unknown command structure")
|
|
||||||
}
|
|
||||||
|
|
||||||
func doCommand(action string, values []string) error {
|
|
||||||
if length := len(values); length != 1 {
|
|
||||||
return fmt.Errorf("Expected 1 argument, received %d", length)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case "CHECK", "C":
|
|
||||||
err := checkConfigValue(values[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Unknown command structure")
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkConfigValue(setting string) error {
|
|
||||||
if val, ok := options[setting]; ok {
|
|
||||||
quickMessage(fmt.Sprintf("%s is set to: %q", setting, val), false)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Unable to check %q, it does not exist", setting)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doLinkCommandAs(action, target string, values []string) error {
|
|
||||||
num, err := strconv.Atoi(target)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Expected number, got %q", target)
|
|
||||||
}
|
|
||||||
|
|
||||||
links := history.Collection[history.Position].Links
|
|
||||||
if num >= len(links) {
|
|
||||||
return fmt.Errorf("Invalid link id: %s", target)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case "ADD", "A":
|
|
||||||
newBookmark := append([]string{links[num-1]}, values...)
|
|
||||||
err := settings.Bookmarks.Add(newBookmark)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.Windows[1].Content = settings.Bookmarks.List()
|
|
||||||
|
|
||||||
err = saveConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.ReflashScreen(false)
|
|
||||||
return nil
|
|
||||||
case "WRITE", "W":
|
|
||||||
return saveFile(links[num-1], strings.Join(values, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("This method has not been built")
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateMainContent() {
|
|
||||||
screen.Windows[0].Content = history.Collection[history.Position].Content
|
|
||||||
screen.Bars[0].SetMessage(history.Collection[history.Position].Address.Full)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearInput(incError bool) {
|
|
||||||
cui.MoveCursorTo(screen.Height-1, 0)
|
|
||||||
cui.Clear("line")
|
|
||||||
if incError {
|
|
||||||
cui.MoveCursorTo(screen.Height, 0)
|
|
||||||
cui.Clear("line")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func quickMessage(msg string, clearMsg bool) {
|
|
||||||
xPos := screen.Width - 2 - len(msg)
|
|
||||||
if xPos < 2 {
|
|
||||||
xPos = 2
|
|
||||||
}
|
|
||||||
cui.MoveCursorTo(screen.Height, xPos)
|
|
||||||
if clearMsg {
|
|
||||||
cui.Clear("right")
|
|
||||||
} else {
|
|
||||||
fmt.Print("\033[48;5;21m\033[38;5;15m", msg, "\033[0m")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveConfig() error {
|
func saveConfig() error {
|
||||||
bkmrks := settings.Bookmarks.IniDump()
|
bkmrks := bombadillo.BookMarks.IniDump()
|
||||||
|
// TODO opts becomes a string builder rather than concat
|
||||||
opts := "\n[SETTINGS]\n"
|
opts := "\n[SETTINGS]\n"
|
||||||
for k, v := range options {
|
for k, v := range bombadillo.Options {
|
||||||
opts += k
|
opts += k
|
||||||
opts += "="
|
opts += "="
|
||||||
opts += v
|
opts += v
|
||||||
opts += "\n"
|
opts += "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(userinfo.HomeDir+"/.bombadillo.ini", []byte(bkmrks+opts), 0644)
|
return ioutil.WriteFile(bombadillo.Options["configlocation"] + "/.bombadillo.ini", []byte(bkmrks+opts), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig() error {
|
func loadConfig() error {
|
||||||
file, err := os.Open(userinfo.HomeDir + "/.bombadillo.ini")
|
file, err := os.Open(bombadillo.Options["configlocation"] + "/.bombadillo.ini")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = saveConfig()
|
err = saveConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -407,72 +148,29 @@ func loadConfig() error {
|
||||||
confparser := config.NewParser(file)
|
confparser := config.NewParser(file)
|
||||||
settings, _ = confparser.Parse()
|
settings, _ = confparser.Parse()
|
||||||
file.Close()
|
file.Close()
|
||||||
screen.Windows[1].Content = settings.Bookmarks.List()
|
|
||||||
for _, v := range settings.Settings {
|
for _, v := range settings.Settings {
|
||||||
lowerkey := strings.ToLower(v.Key)
|
lowerkey := strings.ToLower(v.Key)
|
||||||
if _, ok := options[lowerkey]; ok {
|
if lowerkey == "configlocation" {
|
||||||
options[lowerkey] = v.Value
|
// The config should always be stored in home
|
||||||
|
// folder. Users cannot really edit this value.
|
||||||
|
// It is still stored in the ini and as a part
|
||||||
|
// of the options map.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := bombadillo.Options[lowerkey]; ok {
|
||||||
|
bombadillo.Options[lowerkey] = v.Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleActiveWindow() {
|
|
||||||
if screen.Windows[1].Show {
|
|
||||||
if screen.Windows[0].Active {
|
|
||||||
screen.Windows[0].Active = false
|
|
||||||
screen.Windows[1].Active = true
|
|
||||||
screen.Activewindow = 1
|
|
||||||
} else {
|
|
||||||
screen.Windows[0].Active = true
|
|
||||||
screen.Windows[1].Active = false
|
|
||||||
screen.Activewindow = 0
|
|
||||||
}
|
|
||||||
screen.Windows[1].DrawWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func displayError(err error) {
|
|
||||||
cui.MoveCursorTo(screen.Height, 0)
|
|
||||||
fmt.Print("\033[41m\033[37m", err, "\033[0m")
|
|
||||||
}
|
|
||||||
|
|
||||||
func initClient() error {
|
func initClient() error {
|
||||||
history.Position = -1
|
bombadillo = MakeClient(" ((( Bombadillo ))) ")
|
||||||
|
|
||||||
screen = cui.NewScreen()
|
|
||||||
cui.SetCharMode()
|
cui.SetCharMode()
|
||||||
|
err := loadConfig()
|
||||||
screen.AddWindow(2, 1, screen.Height-2, screen.Width, false, false, true)
|
return err
|
||||||
screen.Windows[0].Active = true
|
|
||||||
screen.AddMsgBar(1, " ((( Bombadillo ))) ", " A fun gopher client!", true)
|
|
||||||
bookmarksWidth := 40
|
|
||||||
if screen.Width < 40 {
|
|
||||||
bookmarksWidth = screen.Width
|
|
||||||
}
|
|
||||||
screen.AddWindow(2, screen.Width-bookmarksWidth, screen.Height-2, screen.Width, false, true, false)
|
|
||||||
return loadConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleResize() {
|
|
||||||
oldh, oldw := screen.Height, screen.Width
|
|
||||||
screen.GetSize()
|
|
||||||
if screen.Height != oldh || screen.Width != oldw {
|
|
||||||
screen.Windows[0].Box.Row2 = screen.Height - 2
|
|
||||||
screen.Windows[0].Box.Col2 = screen.Width
|
|
||||||
bookmarksWidth := 40
|
|
||||||
if screen.Width < 40 {
|
|
||||||
bookmarksWidth = screen.Width
|
|
||||||
}
|
|
||||||
screen.Windows[1].Box.Row2 = screen.Height - 2
|
|
||||||
screen.Windows[1].Box.Col1 = screen.Width - bookmarksWidth
|
|
||||||
screen.Windows[1].Box.Col2 = screen.Width
|
|
||||||
|
|
||||||
screen.DrawAllWindows()
|
|
||||||
screen.DrawMsgBars()
|
|
||||||
screen.ClearCommandArea()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -480,91 +178,27 @@ func main() {
|
||||||
defer cui.Exit()
|
defer cui.Exit()
|
||||||
err := initClient()
|
err := initClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if we can't initialize the window,
|
// if we can't initialize we should bail out
|
||||||
// we can't do anything!
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mainWindow := screen.Windows[0]
|
// Start polling for terminal size changes
|
||||||
|
go bombadillo.GetSize()
|
||||||
|
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
err = goToURL(os.Args[1])
|
// If a url was passed, move it down the line
|
||||||
|
// Goroutine so keypresses can be made during
|
||||||
|
// page load
|
||||||
|
go bombadillo.Visit(os.Args[1])
|
||||||
} else {
|
} else {
|
||||||
err = goHome()
|
// Otherwise, load the homeurl
|
||||||
}
|
// Goroutine so keypresses can be made during
|
||||||
|
// page load
|
||||||
if err != nil {
|
go bombadillo.Visit(bombadillo.Options["homeurl"])
|
||||||
displayError(err)
|
|
||||||
} else {
|
|
||||||
updateMainContent()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loop indefinitely on user input
|
||||||
for {
|
for {
|
||||||
c := cui.Getch()
|
bombadillo.TakeControlInput()
|
||||||
|
|
||||||
handleResize()
|
|
||||||
|
|
||||||
switch c {
|
|
||||||
case 'j', 'J':
|
|
||||||
screen.Windows[screen.Activewindow].ScrollDown()
|
|
||||||
screen.ReflashScreen(false)
|
|
||||||
case 'k', 'K':
|
|
||||||
screen.Windows[screen.Activewindow].ScrollUp()
|
|
||||||
screen.ReflashScreen(false)
|
|
||||||
case 'q', 'Q':
|
|
||||||
cui.Exit()
|
|
||||||
case 'g':
|
|
||||||
screen.Windows[screen.Activewindow].ScrollHome()
|
|
||||||
screen.ReflashScreen(false)
|
|
||||||
case 'G':
|
|
||||||
screen.Windows[screen.Activewindow].ScrollEnd()
|
|
||||||
screen.ReflashScreen(false)
|
|
||||||
case 'd':
|
|
||||||
screen.Windows[screen.Activewindow].PageDown()
|
|
||||||
screen.ReflashScreen(false)
|
|
||||||
case 'u':
|
|
||||||
screen.Windows[screen.Activewindow].PageUp()
|
|
||||||
screen.ReflashScreen(false)
|
|
||||||
case 'b':
|
|
||||||
success := history.GoBack()
|
|
||||||
if success {
|
|
||||||
mainWindow.Scrollposition = 0
|
|
||||||
updateMainContent()
|
|
||||||
screen.ReflashScreen(true)
|
|
||||||
}
|
|
||||||
case 'B':
|
|
||||||
toggleBookmarks()
|
|
||||||
case 'f', 'F':
|
|
||||||
success := history.GoForward()
|
|
||||||
if success {
|
|
||||||
mainWindow.Scrollposition = 0
|
|
||||||
updateMainContent()
|
|
||||||
screen.ReflashScreen(true)
|
|
||||||
}
|
|
||||||
case '\t':
|
|
||||||
toggleActiveWindow()
|
|
||||||
case ':', ' ':
|
|
||||||
cui.MoveCursorTo(screen.Height-1, 0)
|
|
||||||
entry, err := cui.GetLine()
|
|
||||||
if err != nil {
|
|
||||||
displayError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear entry line and error line
|
|
||||||
clearInput(true)
|
|
||||||
if entry == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parser := cmdparse.NewParser(strings.NewReader(entry))
|
|
||||||
p, err := parser.Parse()
|
|
||||||
if err != nil {
|
|
||||||
displayError(err)
|
|
||||||
} else {
|
|
||||||
err := routeInput(p)
|
|
||||||
if err != nil {
|
|
||||||
displayError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + T Y P E S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
type Page struct {
|
||||||
|
WrappedContent []string
|
||||||
|
RawContent string
|
||||||
|
Links []string
|
||||||
|
Location Url
|
||||||
|
ScrollPosition int
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + R E C E I V E R S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + F U N C T I O N S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func MakePage(url Url, content string) Page {
|
||||||
|
p := Page{make([]string, 0), content, make([]string, 0), url, 0}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + T Y P E S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
type Pages struct {
|
||||||
|
Position int
|
||||||
|
Length int
|
||||||
|
History [20]Page
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + R E C E I V E R S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func (p *Pages) NavigateHistory(qty int) error {
|
||||||
|
newPosition := p.Position + qty
|
||||||
|
if newPosition < 0 {
|
||||||
|
return fmt.Errorf("You are already at the beginning of history")
|
||||||
|
} else if newPosition > p.Length - 1 {
|
||||||
|
return fmt.Errorf("Your way is blocked by void, there is nothing forward")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Position = newPosition
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pages) Add(pg Page) error {
|
||||||
|
// TODO add the given page onto the pages struct
|
||||||
|
// handling truncation of the history as needed.
|
||||||
|
return fmt.Errorf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pages) Render() ([]string, error) {
|
||||||
|
// TODO grab the current page as wrappedContent
|
||||||
|
// May need to handle spacing at end of lines.
|
||||||
|
return []string{}, fmt.Errorf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + F U N C T I O N S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
func MakePages() Pages {
|
||||||
|
return Pages{-1, 0, [20]Page{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + T Y P E S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
type Url struct {
|
||||||
|
Scheme string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Resource string
|
||||||
|
Full string
|
||||||
|
Mime string
|
||||||
|
DownloadOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + R E C E I V E R S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
// There are currently no receivers for the Url struct
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------\\
|
||||||
|
// + + + F U N C T I O N S + + + \\
|
||||||
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
|
|
||||||
|
// MakeUrl is a Url constructor that takes in a string
|
||||||
|
// representation of a url and returns a Url struct and
|
||||||
|
// an error (or nil).
|
||||||
|
func MakeUrl(u string) (Url, error) {
|
||||||
|
var out Url
|
||||||
|
re := regexp.MustCompile(`^((?P<scheme>gopher|http|https|gemini):\/\/)?(?P<host>[\w\-\.\d]+)(?::(?P<port>\d+)?)?(?:/(?P<type>[01345679gIhisp])?)?(?P<resource>.*)?$`)
|
||||||
|
match := re.FindStringSubmatch(u)
|
||||||
|
|
||||||
|
if valid := re.MatchString(u); !valid {
|
||||||
|
return out, fmt.Errorf("Invalid url/unable to parse")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, name := range re.SubexpNames() {
|
||||||
|
switch name {
|
||||||
|
case "scheme":
|
||||||
|
out.Scheme = match[i]
|
||||||
|
case "host":
|
||||||
|
out.Host = match[i]
|
||||||
|
case "port":
|
||||||
|
out.Port = match[i]
|
||||||
|
case "type":
|
||||||
|
out.Mime = match[i]
|
||||||
|
case "resource":
|
||||||
|
out.Resource = match[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Scheme == "" {
|
||||||
|
out.Scheme = "gopher"
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Host == "" {
|
||||||
|
return out, fmt.Errorf("no host")
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Scheme == "gopher" && out.Port == "" {
|
||||||
|
out.Port = "70"
|
||||||
|
} else if out.Scheme == "http" && out.Port == "" {
|
||||||
|
out.Port = "80"
|
||||||
|
} else if out.Scheme == "https" && out.Port == "" {
|
||||||
|
out.Port = "443"
|
||||||
|
} else if out.Scheme == "gemini" && out.Port == "" {
|
||||||
|
out.Port = "1965"
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Scheme == "gopher" && out.Mime == "" {
|
||||||
|
out.Mime = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Mime == "" && (out.Resource == "" || out.Resource == "/") && out.Scheme == "gopher" {
|
||||||
|
out.Mime = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Mime == "7" && strings.Contains(out.Resource, "\t") {
|
||||||
|
out.Mime = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Scheme == "gopher" {
|
||||||
|
switch out.Mime {
|
||||||
|
case "1", "0", "h", "7":
|
||||||
|
out.DownloadOnly = false
|
||||||
|
default:
|
||||||
|
out.DownloadOnly = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.Resource = fmt.Sprintf("%s%s", out.Mime, out.Resource)
|
||||||
|
out.Mime = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Full = out.Scheme + "://" + out.Host + ":" + out.Port + "/" + out.Mime + out.Resource
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
Loading…
Reference in New Issue