Interfacor

Streamlining MMI/Console Interface Development with Go

In the realm of engineering, early career experiences can significantly shape our problem-solving skills and technical approaches. Reflecting on my journey, the lessons learned from my first boss in engineering continue to influence my work, even two decades later. His unique perspective on tackling challenges and implementing solutions has been invaluable.

I started as an embedded engineer, working on 8 bit micros. A key component and accelerator on every project was the Man Machine Interface (MMI), essentially a dynamic and efficient terminal/console interface for these devices. This approach not only simplified implementation but also enhanced our ability to scale and adapt projects over time.

Inspired by these early experiences, I’ve recently developed a Go module aimed at simplifying the creation of MMI or console interfaces. This tool has proven to be an accelerator in my projects, offering a level of dynamism and ease of use comparable to the Fire Python module and facilitating the development of line protocols for RPC, configuration scripts, and developer debug consoles.

Implementing a Console/Terminal Interface

Let’s explore how to implement a console/terminal interface in an application, allowing users to input commands and arguments in a manner similar to the console in Quake 3.

Step 1: Define the Interface

First, we define our interface to include essential functionalities:

  • Echoing (returning) an argument.
  • Setting and storing a value.
  • Retrieving (getting) a stored value.

myInterface.go

package main

type MyInterface interface {
	Set(str string)
	Get() string
	Echo(str string) string
}

type myStruct struct {
	val string
}

func (m *myStruct) Echo(str string) string {
	return str
}

func (m *myStruct) Set(str string) {
	m.val = str
}

func (m myStruct) Get() string {
	return m.val
}

Step 2: Handle User Input

Next, we process user input, executing the corresponding interface method:

  • Parse input keys/chars to functions.
  • Append alphanumeric characters to a command line string.
  • Execute the relevant method upon pressing enter, or suggest the closest match if the method is not found.

main.go

package main

import (
	"fmt"
	"os"
	"os/exec"
	"strings"

	"gitlab.com/libsToGo/interfacor"
)

var (
	command  string
	commands MyInterface = &myStruct{}
)

func GetKey(f func(b byte)) {
	exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
	exec.Command("stty", "-F", "/dev/tty", "-echo").Run()

	var b []byte = make([]byte, 1)

	fmt.Print(">")
	for {
		os.Stdin.Read(b)
		f(b[0])
	}
}

func ProcKey(b byte) {
	switch {
	case b == 10:
		args := strings.Split(command, " ")
		resp, err := interfacor.ExecMethod(commands, args)

		fmt.Println()

		if err != nil {
			fmt.Println("err: ", err.Error())
			fmt.Println("The following methods are avaiable")

			for _, m := range interfacor.GetMethods(&commands, "E") {
				fmt.Println(m)
			}

		} else {
			for _, r := range resp {
				if len(r.String()) > 0 {
					fmt.Printf("%v\n", r)
				}
			}
		}
		command = ""
	case b >= 65 && b <= 122:
		command += string(b)
	case b >= 48 && b <= 57:
		command += string(b)
	case b == 32:
		command += " "
	case b == 127:
		if len(command) > 0 {
			command = command[:len(command)-1]
		}
	}

	fmt.Printf("\033[2K\r> %s", command)
}

func main() {
	GetKey(ProcKey)
}

Demonstration

bash
$ go run .
> Echo hello
hello
> Set a_Value
> Get
a_Value
> et
err: method does not exist in the interface
The following methods are available:
Echo(string) string
Get() string
Set(string)
>

This Go module facilitates the creation of dynamic, scalable console interfaces in applications, drawing inspiration from foundational engineering practices to enhance modern development workflows. For more details and examples, visit the module’s repository.