Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import (
- "bufio"
- "errors"
- "fmt"
- "net"
- "os"
- "strconv"
- "strings"
- )
- type resource struct {
- itemType rune
- name string
- selector string
- host string
- port int
- accessor string
- }
- type context struct {
- history []resource
- home resource
- }
- // Create new context
- func NewContext() context {
- var ctx context
- ctx.home = resource{itemType: '1', name: "floodgap", host: "gopher.floodgap.com", selector: "", port: 70}
- ctx.history = append(ctx.history, ctx.home)
- return ctx
- }
- // Set context to specific resource
- func (ctx *context) Navigate(res resource) {
- ctx.history = append(ctx.history, res)
- }
- // Navigate to host
- func (ctx *context) NavigateHost(host string) {
- res := resource{itemType: '1', name: "", host: host, selector: "", port: 70}
- ctx.history = append(ctx.history, res)
- }
- // Get current Resource
- func (ctx *context) Resource() resource {
- depth := len(ctx.history)
- if depth == 0 {
- return ctx.home
- }
- return ctx.history[depth-1]
- }
- // Navigate to home page
- func (ctx *context) Home() {
- ctx.history = append(ctx.history, ctx.home)
- }
- // Go back to the previous resource
- func (ctx *context) Back() {
- depth := len(ctx.history)
- if depth == 0 {
- return
- }
- ctx.history = ctx.history[:depth-1]
- }
- type AccessorIterator struct {
- current int
- }
- func NewAccessorIterator() AccessorIterator {
- return AccessorIterator{current: 0}
- }
- func max(a, b int) int {
- if a > b {
- return a
- }
- return b
- }
- // Get next accessor in resource
- func (i *AccessorIterator) Next() string {
- const accessorSpace string = "abcdefghijklmnopqrstuvwxyz"
- var n, r, figures int = i.current, 0, 0
- var out string = ""
- for n > 0 {
- n, r = n/26, n%26
- out = string(accessorSpace[r]) + out
- figures++
- }
- out = "aa" + out
- i.current++
- // return at least two characters
- return out[len(out)-max(figures, 2):]
- }
- type parseError struct {
- }
- func (e *parseError) Error() string {
- return "parse error"
- }
- // Parse single menu resource
- func parseMenuResource(reader *bufio.Reader) (resource, error) {
- res := resource{}
- // read one rune for item type
- itemType, _, err := reader.ReadRune()
- if err != nil {
- return res, &parseError{}
- }
- res.itemType = itemType
- // read name until tab
- name, err := reader.ReadString('\t')
- if err != nil {
- return res, &parseError{}
- }
- res.name = strings.TrimSuffix(name, "\t")
- // read selector until tab
- selector, err := reader.ReadString('\t')
- if err != nil {
- return res, &parseError{}
- }
- res.selector = strings.TrimSuffix(selector, "\t")
- // read host until tab
- host, err := reader.ReadString('\t')
- if err != nil {
- return res, &parseError{}
- }
- res.host = strings.TrimSuffix(host, "\t")
- // read port until crlf
- port, err := reader.ReadString('\r')
- if err != nil {
- return res, &parseError{}
- }
- port = strings.TrimSuffix(port, "\r")
- // convert port to integer
- pi, err := strconv.Atoi(port)
- if err != nil {
- fmt.Println(err)
- return res, &parseError{}
- }
- res.port = pi
- // read newline
- reader.ReadByte()
- return res, nil
- }
- // Print menu resource
- func printMenuResource(res resource) {
- switch res.itemType {
- case 'i', '3':
- fmt.Printf(" %s\n", res.name)
- default:
- fmt.Printf("[\u001b[32;1m%s\u001b[0m] \u001b[36m%s\u001b[0m\n", res.accessor, res.name)
- }
- }
- // handle a menu response and return a selector dict
- func handleMenu(reader *bufio.Reader) map[string]resource {
- iter := NewAccessorIterator()
- accessors := make(map[string]resource)
- for {
- // parse resource
- res, err := parseMenuResource(reader)
- if err != nil {
- return accessors
- }
- // add accessor if required and optimise retrieval
- if res.itemType != 'i' {
- res.accessor = iter.Next()
- accessors[res.accessor] = res
- }
- printMenuResource(res)
- }
- }
- func handleTextFile(reader *bufio.Reader) map[string]resource {
- for {
- line, _, err := reader.ReadLine()
- if err != nil {
- return nil
- }
- fmt.Printf("%s\n", string(line))
- }
- return nil
- }
- // handle a resource request
- func handle(res resource) (map[string]resource, error) {
- // dial out to remote server
- conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", res.host, res.port))
- if err != nil {
- fmt.Printf("could not connect to %s:%d: %s\n", res.host, res.port, err)
- return nil, err
- }
- defer conn.Close()
- // send selector and get a connection reader
- conn.Write([]byte(fmt.Sprintf("%s\r\n", res.selector)))
- reader := bufio.NewReader(conn)
- // dispatch to handler
- switch res.itemType {
- case '0':
- return handleTextFile(reader), nil
- case '1':
- return handleMenu(reader), nil
- default:
- fmt.Printf("no handler for item type '%s'\n", string(res.itemType))
- return nil, errors.New("no handler")
- }
- return nil, nil
- }
- func repl(ctx context) {
- m := make(map[string]resource)
- reader := bufio.NewReader(os.Stdin)
- for {
- res := ctx.Resource()
- lastMap, err := handle(res)
- if err != nil {
- ctx.Back()
- res = ctx.Resource()
- } else {
- m = lastMap
- }
- fmt.Printf("\n[\u001b[32;1mh\u001b[0m]=home [\u001b[32;1mq\u001b[0m]=quit [\u001b[32;1m.\u001b[0m]=back [\u001b[32;1mg host\u001b[0m]=goto\n\u001b[36m%s:%s\u001b[0m > ", res.host, res.selector)
- command, err := reader.ReadString('\n')
- // exit if we receive an EOF from ctrl+d
- if err != nil {
- return
- }
- // dispatch command
- command = strings.TrimSuffix(command, "\n")
- switch command {
- // home
- case "h":
- ctx.Home()
- // quit
- case "q":
- return
- // refresh
- case "*":
- break
- // back
- case ".":
- ctx.Back()
- // accessor
- default:
- // handle goto
- if strings.HasPrefix(command, "g ") {
- ctx.NavigateHost(command[2:])
- break
- }
- // handle accessor
- if val, ok := m[command]; ok {
- ctx.Navigate(val)
- } else {
- fmt.Printf("Unknown accessor %s\n", command)
- }
- }
- }
- }
- func main() {
- ctx := NewContext()
- repl(ctx)
- }
Advertisement
Add Comment
Please, Sign In to add comment