Apparently added a ridiculous mess since last commit

This commit is contained in:
sloumdrone 2019-03-17 09:58:39 -07:00
parent a28fc00550
commit 80ab652448
15 changed files with 1590 additions and 479 deletions

170
cmdparse/lexer.go Normal file
View File

@ -0,0 +1,170 @@
package cmdparse
import (
"bufio"
"strings"
"io"
"bytes"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
type Token struct {
kind tok
val string
}
type scanner struct {
r *bufio.Reader
}
type tok int
//------------------------------------------------\\
// + + + V A R I A B L E S + + + \\
//--------------------------------------------------\\
var eof rune = rune(0)
const (
Word tok = iota
Action
Value
End
Whitespace
number
letter
ws
illegal
)
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
func (s *scanner) read() rune {
ch, _, err := s.r.ReadRune()
if err != nil {
return eof
}
return ch
}
func (s *scanner) scanText() Token {
var buf bytes.Buffer
buf.WriteRune(s.read())
for {
if ch := s.read(); ch == eof {
s.unread()
break
} else if !isLetter(ch) && !isDigit(ch) {
s.unread()
break
} else {
_, _ = buf.WriteRune(ch)
}
}
capInput := strings.ToUpper(buf.String())
switch capInput {
case "DELETE", "ADD", "WRITE", "SET", "RECALL", "R",
"W", "A", "D", "S", "Q", "QUIT", "B", "BOOKMARKS", "H", "HOME":
return Token{Action, capInput}
}
return Token{Word, buf.String()}
}
func (s *scanner) scanWhitespace() Token {
var buf bytes.Buffer
buf.WriteRune(s.read())
for {
if ch := s.read(); ch == eof {
s.unread()
break
} else if !isWhitespace(ch) {
s.unread()
break
} else {
_,_ = buf.WriteRune(ch)
}
}
return Token{Whitespace, buf.String()}
}
func (s *scanner) scanNumber() Token {
var buf bytes.Buffer
buf.WriteRune(s.read())
for {
if ch := s.read(); ch == eof {
break
} else if !isDigit(ch) {
s.unread()
break
} else {
_,_ = buf.WriteRune(ch)
}
}
return Token{Value, buf.String()}
}
func (s *scanner) unread() { _ = s.r.UnreadRune() }
func (s *scanner) scan() Token {
char := s.read()
if isWhitespace(char) {
s.unread()
return s.scanWhitespace()
} else if isDigit(char) {
s.unread()
return s.scanNumber()
} else if isLetter(char) {
s.unread()
return s.scanText()
}
if char == eof {
return Token{End, ""}
}
return Token{illegal, string(char)}
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
func NewScanner(r io.Reader) *scanner {
return &scanner{r: bufio.NewReader(r)}
}
func isWhitespace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n'
}
func isLetter(ch rune) bool {
return ch >= '!' && ch <= '~'
}
func isDigit(ch rune) bool {
return ch >= '0' && ch <= '9'
}
func isEOF(ch rune) bool {
return ch == rune(0)
}

142
cmdparse/parser.go Normal file
View File

@ -0,0 +1,142 @@
package cmdparse
import (
"io"
"fmt"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
type Parser struct {
s *scanner
buffer struct {
token Token
size int
}
}
type Command struct {
Action string
Target string
Value []string
Type Comtype
}
type Comtype int
const (
GOURL Comtype = iota
GOLINK
SIMPLE
DOLINK
DOLINKAS
DOAS
)
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
func (p *Parser) scan() (current Token) {
if p.buffer.size != 0 {
p.buffer.size = 0
return p.buffer.token
}
current = p.s.scan()
for {
if current.kind != Whitespace {
break
}
current = p.s.scan()
}
p.buffer.token = current
return
}
func (p *Parser) unscan() { p.buffer.size = 1 }
func (p *Parser) parseNonAction() (*Command, error) {
p.unscan()
t := p.scan()
cm := &Command{}
if t.kind == Value {
cm.Target = t.val
cm.Type = GOLINK
} else if t.kind == Word {
cm.Target = t.val
cm.Type = GOURL
} else {
return nil, fmt.Errorf("Found %q, expected action, url, or link number", t.val)
}
if u := p.scan(); u.kind != End {
return nil, fmt.Errorf("Found %q, expected EOF", u.val)
}
return cm, nil
}
func (p *Parser) parseAction() (*Command, error) {
p.unscan()
t := p.scan()
cm := &Command{}
cm.Action = t.val
t = p.scan()
switch t.kind {
case End:
cm.Type = SIMPLE
return cm, nil
case Value:
cm.Target = t.val
cm.Type = DOLINK
case Word:
cm.Value = append(cm.Value, t.val)
cm.Type = DOAS
case Action, Whitespace:
return nil, fmt.Errorf("Found %q (%d), expected value", t.val, t.kind)
}
t = p.scan()
if t.kind == End {
return cm, nil
} else {
if cm.Type == DOLINK {
cm.Type = DOLINKAS
} else {
cm.Type = DOAS
}
cm.Value = append(cm.Value, t.val)
for {
token := p.scan()
if token.kind == End {
break
} else if token.kind == Whitespace {
continue
}
cm.Value = append(cm.Value, token.val)
}
}
return cm, nil
}
func (p *Parser) Parse() (*Command, error) {
if t := p.scan(); t.kind != Action {
return p.parseNonAction()
} else {
return p.parseAction()
}
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
func NewParser(r io.Reader) *Parser {
return &Parser{s: NewScanner(r)}
}

206
config/lexer.go Normal file
View File

@ -0,0 +1,206 @@
package config
import (
"bufio"
"io"
"bytes"
"fmt"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
type Token struct {
kind TokenType
val string
}
type scanner struct {
r *bufio.Reader
}
type TokenType int
//------------------------------------------------\\
// + + + V A R I A B L E S + + + \\
//--------------------------------------------------\\
var eof rune = rune(0)
var l_brace rune = '['
var r_brace rune = ']'
var newline rune = '\n'
var equal rune = '='
const (
TOK_SECTION TokenType = iota
TOK_KEY
TOK_VALUE
TOK_EOF
TOK_NEWLINE
TOK_ERROR
TOK_WHITESPACE
)
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
func (s *scanner) read() rune {
ch, _, err := s.r.ReadRune()
if err != nil {
return eof
}
return ch
}
func (s *scanner) skipWhitespace() {
for {
if ch := s.read(); ch == eof {
s.unread()
break
} else if !isWhitespace(ch) {
s.unread()
break
}
}
}
func (s *scanner) skipToEndOfLine() {
for {
if ch := s.read(); ch == eof {
s.unread()
break
} else if ch == newline {
s.unread()
break
}
}
}
func (s *scanner) scanSection() Token {
var buf bytes.Buffer
buf.WriteRune(s.read())
for {
if ch := s.read(); ch == eof {
s.unread()
return Token{TOK_ERROR, "Reached end of feed without closing section"}
} else if ch == r_brace {
break
} else if ch == newline {
s.unread()
return Token{TOK_ERROR, "No closing brace for section before newline"}
} else if ch == equal {
return Token{TOK_ERROR, "Illegal character"}
} else if ch == l_brace {
s.skipToEndOfLine()
return Token{TOK_ERROR, "Second left brace encountered before closing right brace in section"}
} else {
_,_ = buf.WriteRune(ch)
}
}
return Token{TOK_SECTION, buf.String()}
}
func (s *scanner) scanKey() Token {
var buf bytes.Buffer
for {
if ch := s.read(); ch == eof {
s.unread()
return Token{TOK_ERROR, "Reached end of feed without assigning value to key"}
} else if ch == equal {
s.unread()
break
} else if ch == newline {
s.unread()
return Token{TOK_ERROR, "No value assigned to key"}
} else if ch == r_brace || ch == l_brace {
s.skipToEndOfLine()
return Token{TOK_ERROR, "Illegal brace character in key"}
} else {
_,_ = buf.WriteRune(ch)
}
}
return Token{TOK_KEY, buf.String()}
}
func (s *scanner) scanValue() Token {
var buf bytes.Buffer
for {
if ch := s.read(); ch == eof {
s.unread()
break
} else if ch == equal {
_,_ = buf.WriteRune(ch)
} else if ch == newline {
s.unread()
break
} else if ch == r_brace || ch == l_brace {
s.skipToEndOfLine()
return Token{TOK_ERROR, "Illegal brace character in key"}
} else {
_,_ = buf.WriteRune(ch)
}
}
return Token{TOK_VALUE, buf.String()}
}
func (s *scanner) unread() { _ = s.r.UnreadRune() }
func (s *scanner) scan() Token {
char := s.read()
if isWhitespace(char) {
s.skipWhitespace()
}
if char == l_brace {
return s.scanSection()
} else if isText(char) {
s.unread()
return s.scanKey()
} else if char == equal {
return s.scanValue()
} else if char == newline {
return Token{TOK_NEWLINE, "New line"}
}
if char == eof {
return Token{TOK_EOF, "Reached end of feed"}
}
return Token{TOK_ERROR, fmt.Sprintf("Error on character %q", char)}
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
func NewScanner(r io.Reader) *scanner {
return &scanner{r: bufio.NewReader(r)}
}
func isWhitespace(ch rune) bool {
return ch == ' ' || ch == '\t'
}
func isText(ch rune) bool {
return ch >= '!' && ch <= '~' && ch != equal && ch != l_brace && ch != r_brace
}
func isEOF(ch rune) bool {
return ch == eof
}

113
config/parser.go Normal file
View File

@ -0,0 +1,113 @@
package config
import (
"io"
"fmt"
"strings"
"gsock/gopher"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
type Parser struct {
s *scanner
row int
buffer struct {
token Token
size int
}
}
type Config struct {
Bookmarks gopher.Bookmarks
Colors []KeyValue
Settings []KeyValue
}
type KeyValue struct {
Key string
Value string
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
func (p *Parser) scan() (current Token) {
if p.buffer.size != 0 {
p.buffer.size = 0
return p.buffer.token
}
current = p.s.scan()
p.buffer.token = current
return
}
func (p *Parser) parseKeyValue() (KeyValue, error) {
kv := KeyValue{}
t1 := p.scan()
kv.Key = strings.TrimSpace(t1.val)
if t := p.scan(); t.kind == TOK_VALUE {
kv.Value = strings.TrimSpace(t.val)
} else {
return kv, fmt.Errorf("Got non-value expected VALUE on row %d", p.row)
}
if t := p.scan(); t.kind != TOK_NEWLINE {
return kv, fmt.Errorf("Expected NEWLINE, got %q on row %d", t.kind, p.row)
}
return kv, nil
}
func (p *Parser) unscan() { p.buffer.size = 1 }
func (p *Parser) Parse() (Config, error) {
p.row = 1
section := ""
c := Config{}
for {
if t := p.scan(); t.kind == TOK_NEWLINE {
p.row++
} else if t.kind == TOK_SECTION {
section = strings.ToUpper(t.val)
} else if t.kind == TOK_EOF {
break
} else if t.kind == TOK_KEY {
p.unscan()
keyval, err := p.parseKeyValue()
if err != nil {
return Config{}, err
}
switch section {
case "BOOKMARKS":
c.Bookmarks.Add([]string{keyval.Value, keyval.Key})
case "COLORS":
c.Colors = append(c.Colors, keyval)
case "SETTINGS":
c.Settings = append(c.Settings, keyval)
}
} else if t.kind == TOK_ERROR {
return Config{}, fmt.Errorf("Error on row %d: %s", p.row, t.val)
}
}
return c, nil
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
func NewParser(r io.Reader) *Parser {
return &Parser{s: NewScanner(r)}
}

View File

@ -20,165 +20,6 @@ var shapes = map[string]string{
"scroll-track": "░",
}
var screenInit bool = false
type Screen struct {
Height int
Width int
Windows []*Window
Activewindow int
}
type box struct {
row1 int
col1 int
row2 int
col2 int
}
type Window struct {
Box box
Scrollbar bool
Scrollposition int
Content []string
drawBox bool
Active bool
}
func (s *Screen) AddWindow(r1, c1, r2, c2 int, scroll, border bool) {
w := Window{box{r1, c1, r2, c2}, scroll, 0, []string{}, border, false}
s.Windows = append(s.Windows, &w)
}
func (s Screen) DrawFullScreen() {
s.Clear()
// w := s.Windows[s.Activewindow]
for _, w := range s.Windows {
if w.drawBox {
w.DrawBox()
}
w.DrawContent()
}
MoveCursorTo(s.Height - 1, 1)
}
func (s Screen) Clear() {
fill := strings.Repeat(" ", s.Width)
for i := 0; i <= s.Height; i++ {
MoveCursorTo(i, 0)
fmt.Print(fill)
}
}
func (s Screen) SetCharMode() {
exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
fmt.Print("\033[?25l")
}
// Checks for a screen resize and resizes windows if needed
// Then redraws the screen. Takes a bool to decide whether
// to redraw the full screen or just the content. On a resize
// event, the full screen will always be redrawn.
func (s *Screen) ReflashScreen(clearScreen bool) {
oldh, oldw := s.Height, s.Width
s.GetSize()
if s.Height != oldh || s.Width != oldw {
for _, w := range s.Windows {
w.Box.row2 = s.Height - 2
w.Box.col2 = s.Width
}
s.DrawFullScreen()
} else if clearScreen {
s.DrawFullScreen()
} else {
s.Windows[s.Activewindow].DrawContent()
}
}
func (s *Screen) GetSize() {
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(1)
}
var h, w int
fmt.Sscan(string(out), &h, &w)
s.Height = h
s.Width = w
}
func (w *Window) DrawBox(){
moveThenDrawShape(w.Box.row1, w.Box.col1, "tl")
moveThenDrawShape(w.Box.row1, w.Box.col2, "tr")
moveThenDrawShape(w.Box.row2, w.Box.col1, "bl")
moveThenDrawShape(w.Box.row2, w.Box.col2, "br")
for i := w.Box.col1 + 1; i < w.Box.col2; i++ {
moveThenDrawShape(w.Box.row1, i, "ceiling")
moveThenDrawShape(w.Box.row2, i, "ceiling")
}
for i:= w.Box.row1 + 1; i < w.Box.row2; i++ {
moveThenDrawShape(i, w.Box.col1, "wall")
moveThenDrawShape(i, w.Box.col2, "wall")
}
}
func (w *Window) DrawContent(){
var maxlines, borderw, contenth int
if w.drawBox {
borderw, contenth = -1, 1
} else {
borderw, contenth = 1, 0
}
height, width := w.Box.row2 - w.Box.row1 + borderw, w.Box.col2 - w.Box.col1 + borderw
content := WrapLines(w.Content, width)
if len(content) < w.Scrollposition + height {
maxlines = len(content)
} else {
maxlines = w.Scrollposition + height
}
for i := w.Scrollposition; i < maxlines; i++ {
MoveCursorTo(w.Box.row1 + contenth + i - w.Scrollposition, w.Box.col1 + contenth)
fmt.Print( strings.Repeat(" ", width) )
MoveCursorTo(w.Box.row1 + contenth + i - w.Scrollposition, w.Box.col1 + contenth)
fmt.Print(content[i])
}
}
func (w *Window) ScrollDown() {
height := w.Box.row2 - w.Box.row1 - 1
contentLength := len(w.Content)
if w.Scrollposition < contentLength - height {
w.Scrollposition++
} else {
fmt.Print("\a")
}
}
func (w *Window) ScrollUp() {
if w.Scrollposition > 0 {
w.Scrollposition--
} else {
fmt.Print("\a")
}
}
//--------------------------------------------------------------------------//
// //
// F U N C T I O N S //
// //
//--------------------------------------------------------------------------//
func drawShape(shape string) {
if val, ok := shapes[shape]; ok {
fmt.Printf("%s", val)
@ -267,23 +108,6 @@ func WrapLines(s []string, length int) []string {
return out
}
func NewScreen() *Screen {
if screenInit {
fmt.Println("Fatal error: Cannot create multiple screens")
os.Exit(1)
}
var s Screen
s.GetSize()
for i := 0; i < s.Height; i++ {
fmt.Println()
}
SetCharMode()
Clear("screen")
screenInit = true
return &s
}
func Getch() rune {
reader := bufio.NewReader(os.Stdin)
char, _, err := reader.ReadRune()

31
cui/msgbar.go Normal file
View File

@ -0,0 +1,31 @@
package cui
import (
)
type MsgBar struct {
row int
title string
message string
showTitle bool
}
func (m *MsgBar) SetTitle(s string) {
m.title = s
}
func (m *MsgBar) SetMessage(s string) {
m.message = s
}
func (m MsgBar) ClearAll() {
MoveCursorTo(m.row, 1)
Clear("line")
}
func (m *MsgBar) ClearMessage() {
MoveCursorTo(m.row, len(m.title) + 1)
Clear("right")
}

153
cui/screen.go Normal file
View File

@ -0,0 +1,153 @@
package cui
import (
"strings"
"fmt"
"os"
"os/exec"
"bytes"
)
var screenInit bool = false
type Screen struct {
Height int
Width int
Windows []*Window
Activewindow int
Bars []*MsgBar
}
func (s *Screen) AddWindow(r1, c1, r2, c2 int, scroll, border, show bool) {
w := Window{box{r1, c1, r2, c2}, scroll, 0, []string{}, border, false, show}
s.Windows = append(s.Windows, &w)
}
func (s *Screen) AddMsgBar(row int, title, msg string, showTitle bool) {
b := MsgBar{row, title, msg, showTitle}
s.Bars = append(s.Bars, &b)
}
func (s Screen) DrawAllWindows() {
s.Clear()
for _, w := range s.Windows {
if w.Show {
w.DrawWindow()
}
}
MoveCursorTo(s.Height - 1, 1)
}
func (s Screen) Clear() {
fill := strings.Repeat(" ", s.Width)
for i := 0; i <= s.Height; i++ {
MoveCursorTo(i, 0)
fmt.Print(fill)
}
}
func (s Screen) SetCharMode() {
exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
fmt.Print("\033[?25l")
}
// Checks for a screen resize and resizes windows if needed
// Then redraws the screen. Takes a bool to decide whether
// to redraw the full screen or just the content. On a resize
// event, the full screen will always be redrawn.
func (s *Screen) ReflashScreen(clearScreen bool) {
oldh, oldw := s.Height, s.Width
s.GetSize()
if s.Height != oldh || s.Width != oldw {
// TODO this should be pure library code and not rely on
// specific windows being present with specific behaviors.
// Maybe allow windows to have a resize function that can
// be declared within the application?
// For now this will be ok though.
s.Windows[0].Box.row2 = s.Height - 2
s.Windows[0].Box.col2 = s.Width
bookmarksWidth := 40
if s.Width < 40 {
bookmarksWidth = s.Width
}
s.Windows[1].Box.row2 = s.Height - 2
s.Windows[1].Box.col1 = s.Width - bookmarksWidth
s.Windows[1].Box.col2 = s.Width
s.DrawAllWindows()
s.DrawMsgBars()
} else if clearScreen {
s.DrawAllWindows()
s.DrawMsgBars()
} else {
for _, w := range s.Windows {
if w.Show {
w.DrawWindow()
}
}
}
}
func (s *Screen) DrawMsgBars() {
for _, bar := range s.Bars {
MoveCursorTo(bar.row, 1)
Clear("line")
fmt.Print("\033[7m")
fmt.Print(strings.Repeat(" ", s.Width))
MoveCursorTo(bar.row, 1)
var buf bytes.Buffer
title := bar.title
fmt.Print(title)
if len(bar.title) > s.Width {
title = string(bar.title[:s.Width - 3]) + "..."
}
_, _ = buf.WriteString(title)
msg := bar.message
if len(bar.message) > s.Width - len(title) {
msg = string(bar.message[:s.Width - len(title) - 3]) + "..."
}
_, _ = buf.WriteString(msg)
MoveCursorTo(bar.row, 1)
fmt.Print(buf.String())
fmt.Print("\033[0m")
}
}
func (s *Screen) GetSize() {
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(1)
}
var h, w int
fmt.Sscan(string(out), &h, &w)
s.Height = h
s.Width = w
}
// - - - - - - - - - - - - - - - - - - - - - - - - - -
func NewScreen() *Screen {
if screenInit {
fmt.Println("Fatal error: Cannot create multiple screens")
os.Exit(1)
}
var s Screen
s.GetSize()
for i := 0; i < s.Height; i++ {
fmt.Println()
}
SetCharMode()
Clear("screen")
screenInit = true
return &s
}

104
cui/window.go Normal file
View File

@ -0,0 +1,104 @@
package cui
import (
"fmt"
"strings"
)
type box struct {
row1 int
col1 int
row2 int
col2 int
}
// TODO add coloring
type Window struct {
Box box
Scrollbar bool
Scrollposition int
Content []string
drawBox bool
Active bool
Show bool
}
func (w *Window) DrawWindow() {
w.DrawContent()
if w.drawBox {
w.DrawBox()
}
}
func (w *Window) DrawBox(){
moveThenDrawShape(w.Box.row1, w.Box.col1, "tl")
moveThenDrawShape(w.Box.row1, w.Box.col2, "tr")
moveThenDrawShape(w.Box.row2, w.Box.col1, "bl")
moveThenDrawShape(w.Box.row2, w.Box.col2, "br")
for i := w.Box.col1 + 1; i < w.Box.col2; i++ {
moveThenDrawShape(w.Box.row1, i, "ceiling")
moveThenDrawShape(w.Box.row2, i, "ceiling")
}
for i:= w.Box.row1 + 1; i < w.Box.row2; i++ {
moveThenDrawShape(i, w.Box.col1, "wall")
moveThenDrawShape(i, w.Box.col2, "wall")
}
}
func (w *Window) DrawContent(){
var maxlines, border_thickness, contenth int
var short_content bool = false
if w.drawBox {
border_thickness, contenth = -1, 1
} else {
border_thickness, contenth = 1, 0
}
height := w.Box.row2 - w.Box.row1 + border_thickness
width := w.Box.col2 - w.Box.col1 + border_thickness
content := WrapLines(w.Content, width)
if len(content) < w.Scrollposition + height {
maxlines = len(content)
short_content = true
} else {
maxlines = w.Scrollposition + height
}
for i := w.Scrollposition; i < maxlines; i++ {
MoveCursorTo(w.Box.row1 + contenth + i - w.Scrollposition, w.Box.col1 + contenth)
fmt.Print( strings.Repeat(" ", width) )
MoveCursorTo(w.Box.row1 + contenth + i - w.Scrollposition, w.Box.col1 + contenth)
fmt.Print(content[i])
}
if short_content {
for i := len(content); i <= height; i++ {
MoveCursorTo(w.Box.row1 + contenth + i - w.Scrollposition, w.Box.col1 + contenth)
fmt.Print( strings.Repeat(" ", width) )
}
}
}
func (w *Window) ScrollDown() {
height := w.Box.row2 - w.Box.row1 - 1
contentLength := len(w.Content)
if w.Scrollposition < contentLength - height {
w.Scrollposition++
} else {
fmt.Print("\a")
}
}
func (w *Window) ScrollUp() {
if w.Scrollposition > 0 {
w.Scrollposition--
} else {
fmt.Print("\a")
}
}

View File

@ -1,28 +1,56 @@
package main
import (
"fmt"
"gsock/gopher"
"os"
"gsock/cmdparse"
"gsock/config"
"gsock/cui"
"os/user"
"io/ioutil"
"os"
"fmt"
"strings"
"regexp"
"strconv"
"gsock/cui"
"errors"
)
var history gopher.History = gopher.MakeHistory()
var screen *cui.Screen
var userinfo, _ = user.Current()
var settings config.Config
var options = map[string]string{
"homeurl": "",
"savelocation": userinfo.HomeDir + "/Downloads/",
"searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs",
"openhttp": "false",
"httpbrowser": "lynx",
}
func err_exit(err string, code int) {
fmt.Println(err)
os.Exit(code)
}
func save_file() {
//TODO add a way to save a file...
//eg. :save 5 test.txt
func save_file(address, name string) error {
quickMessage("Saving file...", false)
defer quickMessage("Saving file...", true)
url, err := gopher.MakeUrl(address)
if err != nil {
return err
}
data, err := gopher.Retrieve(url)
if err != nil {
return err
}
err = ioutil.WriteFile(userinfo.HomeDir + "/" + name, data, 0644)
if err != nil {
return err
}
return nil
}
func search(u string) error {
@ -42,16 +70,93 @@ func search(u string) error {
}
func route_input(s string) error {
if num, _ := regexp.MatchString(`^\d+$`, s); num && history.Length > 0 {
linkcount := len(history.Collection[history.Position].Links)
item, _ := strconv.Atoi(s)
if item <= linkcount {
linkurl := history.Collection[history.Position].Links[item - 1]
v, err := gopher.Visit(linkurl)
func route_input(com *cmdparse.Command) error {
var err error
switch com.Type {
case cmdparse.SIMPLE:
err = simple_command(com.Action)
case cmdparse.GOURL:
err = go_to_url(com.Target)
case cmdparse.GOLINK:
err = go_to_link(com.Target)
case cmdparse.DOLINK:
err = do_link_command(com.Action, com.Target)
case cmdparse.DOAS:
err = do_command_as(com.Action, com.Value)
case cmdparse.DOLINKAS:
err = do_link_command_as(com.Action, com.Target, com.Value)
default:
return fmt.Errorf("Unknown command entry!")
}
return err
}
func toggle_bookmarks() {
bookmarks := screen.Windows[1]
if bookmarks.Show {
bookmarks.Show = false
} else {
bookmarks.Show = true
}
if screen.Activewindow == 0 {
screen.Activewindow = 1
} else {
screen.Activewindow = 0
}
}
func simple_command(a string) error {
a = strings.ToUpper(a)
switch a {
case "Q", "QUIT":
cui.Exit()
case "H", "HOME":
return go_home()
case "B", "BOOKMARKS":
toggle_bookmarks()
default:
return fmt.Errorf("Unknown action %q", a)
}
return nil
}
func go_to_url(u string) error {
quickMessage("Loading...", false)
v, err := gopher.Visit(u)
if err != nil {
quickMessage("Loading...", true)
return err
}
quickMessage("Loading...", true)
if v.Address.Gophertype == "7" {
err := search(v.Address.Full)
if err != nil {
return err
}
} else if v.Address.IsBinary {
// TO DO: run this into the write to file method
} else {
history.Add(v)
}
return nil
}
func go_to_link(l string) error {
if num, _ := regexp.MatchString(`^\d+$`, l); num && history.Length > 0 {
linkcount := len(history.Collection[history.Position].Links)
item, _ := strconv.Atoi(l)
if item <= linkcount {
linkurl := history.Collection[history.Position].Links[item - 1]
quickMessage("Loading...", false)
v, err := gopher.Visit(linkurl)
if err != nil {
quickMessage("Loading...", true)
return err
}
quickMessage("Loading...", true)
if v.Address.Gophertype == "7" {
err := search(linkurl)
@ -59,61 +164,200 @@ func route_input(s string) error {
return err
}
} else if v.Address.IsBinary {
// TODO add download querying here
// TO DO: run this into the write to file method
} else {
history.Add(v)
}
} else {
errname := fmt.Sprintf("Invalid link id: %s", s)
return errors.New(errname)
return fmt.Errorf("Invalid link id: %s", l)
}
} else {
v, err := gopher.Visit(s)
if err != nil {
return err
}
if v.Address.Gophertype == "7" {
err := search(v.Address.Full)
if err != nil {
return err
}
} else if v.Address.IsBinary {
// TODO add download querying here
} else {
history.Add(v)
}
return fmt.Errorf("Invalid link id: %s", l)
}
return nil
}
func go_home() error {
if options["homeurl"] != "" {
return go_to_url(options["homeurl"])
}
return fmt.Errorf("No home address has been set")
}
func main() {
// fmt.Println(userinfo.HomeDir)
func do_link_command(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)
screen.Windows[1].Content = settings.Bookmarks.List()
save_config()
return err
case "BOOKMARKS", "B":
if num > len(settings.Bookmarks.Links) - 1 {
return fmt.Errorf("There is no bookmark with ID %d", num)
}
err := go_to_url(settings.Bookmarks.Links[num])
return err
}
return fmt.Errorf("This method has not been built")
}
func do_command_as(action string, values []string) error {
if len(values) < 2 {
return fmt.Errorf("%q", values)
}
switch action {
case "ADD", "A":
if values[0] == "." {
values[0] = history.Collection[history.Position].Address.Full
}
err := settings.Bookmarks.Add(values)
if err != nil {
return err
}
screen.Windows[1].Content = settings.Bookmarks.List()
save_config()
return nil
case "WRITE", "W":
return save_file(values[0], strings.Join(values[1:], " "))
case "SET", "S":
if _, ok := options[values[0]]; ok {
options[values[0]] = strings.Join(values[1:], " ")
save_config()
return nil
}
return fmt.Errorf("Unable to set %s, it does not exist",values[0])
}
return fmt.Errorf("This method has not been built")
}
func do_link_command_as(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()
save_config()
return nil
case "WRITE", "W":
return save_file(links[num - 1], strings.Join(values, " "))
}
return fmt.Errorf("This method has not been built")
}
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) {
cui.MoveCursorTo(screen.Height, screen.Width - 2 - len(msg))
if clearMsg {
cui.Clear("right")
} else {
fmt.Print("\033[48;5;21m\033[38;5;15m", msg, "\033[0m")
}
}
func save_config() {
bkmrks := settings.Bookmarks.IniDump()
opts := "\n[SETTINGS]\n"
for k, v := range options {
opts += k
opts += "="
opts += v
opts += "\n"
}
ioutil.WriteFile(userinfo.HomeDir + "/.badger.ini", []byte(bkmrks+opts), 0644)
}
func load_config() {
file, _ := os.Open(userinfo.HomeDir + "/.badger.ini")
confparser := config.NewParser(file)
settings, _ = confparser.Parse()
file.Close()
screen.Windows[1].Content = settings.Bookmarks.List()
for _, v := range settings.Settings {
lowerkey := strings.ToLower(v.Key)
if _, ok := options[lowerkey]; ok {
options[lowerkey] = v.Value
}
}
}
func initClient() {
history.Position = -1
screen = cui.NewScreen()
screen.SetCharMode()
screen.AddWindow(2, 1, screen.Height - 2, screen.Width, false, false, true)
screen.AddMsgBar(1, " ((( Badger ))) ", " 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)
load_config()
}
func main() {
defer cui.Exit()
screen.AddWindow(1, 1, screen.Height - 2, screen.Width, false, false)
initClient()
mainWindow := screen.Windows[0]
first_load := true
redrawScreen := true
for {
screen.ReflashScreen(redrawScreen)
if first_load {
go_home()
first_load = false
mainWindow.Content = history.Collection[history.Position].Content
screen.Bars[0].SetMessage(history.Collection[history.Position].Address.Full)
continue
}
redrawScreen = false
c := cui.Getch()
switch c {
case 'j', 'J':
mainWindow.ScrollDown()
screen.Windows[screen.Activewindow].ScrollDown()
case 'k', 'K':
mainWindow.ScrollUp()
screen.Windows[screen.Activewindow].ScrollUp()
case 'q', 'Q':
cui.Exit()
case 'b', 'B':
case 'b':
history.GoBack()
mainWindow.Scrollposition = 0
redrawScreen = true
case 'B':
toggle_bookmarks()
case 'f', 'F':
history.GoForward()
mainWindow.Scrollposition = 0
@ -122,28 +366,33 @@ func main() {
redrawScreen = true
cui.MoveCursorTo(screen.Height - 1, 0)
entry := cui.GetLine()
// Clear entry line
cui.MoveCursorTo(screen.Height - 1, 0)
cui.Clear("line")
// Clear entry line and error line
clearInput(true)
if entry == "" {
cui.MoveCursorTo(screen.Height - 1, 0)
fmt.Print(" ")
redrawScreen = false
continue
}
err := route_input(entry)
parser := cmdparse.NewParser(strings.NewReader(entry))
p, err := parser.Parse()
if err != nil {
// Display error
cui.MoveCursorTo(screen.Height, 0)
fmt.Print("\033[41m\033[37m", err, "\033[0m")
// Set screen to not reflash
redrawScreen = false