Code Monkey home page Code Monkey logo

gosh's Introduction

Gosh - A pluggable interactive shell written Go

Gosh (or Go shell) is a framework that uses Go's plugin system to create for building interactive console-based shell programs. A gosh shell is comprised of a collection of Go plugins which implement one or more commands. When gosh starts, it searches directory ./plugins for available shared object files that implement command plugins.

Getting started

Pre-requisites

  • Go 1.8 or above
  • Linux
  • Mac OSX

Gosh makes it easy to create shell programs. First, download or clone this repository. For a quick start, run the following:

go run shell/gosh.go

This will produce the following output:

                        888
                        888
                        888
 .d88b.  .d88b. .d8888b 88888b.
d88P"88bd88""88b88K     888 "88b
888  888888  888"Y8888b.888  888
Y88b 888Y88..88P     X88888  888
 "Y88888 "Y88P"  88888P'888  888
     888
Y8b d88P
 "Y88P"

No commands found

After the splashscreen is displayed, gosh informs you that no commands found, as expected. Next, exit the gosh shell (Ctrl-C) and let us compile the example plugins that comes with the source code.

go build -buildmode=plugin  -o plugins/sys_command.so plugins/syscmd.go

The previous command will compile plugins/syscmd.go and outputs shared object plugins/sys_command.so, as a Go plugin file. Verify the shared object file was created:

> ls -lh plugins/
total 3.2M
-rw-rw-r-- 1  4.5K Mar 19 18:23 syscmd.go
-rw-rw-r-- 1  3.2M Mar 19 19:14 sys_command.so
-rw-rw-r-- 1  1.4K Mar 19 18:23 testcmd.go

Now, when gosh is restarted, it will dynamically load the commands implemented in the shared object file:

> go run shell/gosh.go
...

Loaded 4 command(s)...
Type help for available commands

gosh>

As indicated, typing help lists all available commands in the shell:

gosh> help

help: prints help information for other commands.

Available commands
------------------
      prompt:	sets a new shell prompt
         sys:	sets a new shell prompt
        help:	prints help information for other commands.
        exit:	exits the interactive shell immediately

Use "help <command-name>" for detail about the specified command

A command

A Gosh Command is represented by type api/Command:

type Command interface {
	Name() string
	Usage() string
	ShortDesc() string
	LongDesc() string
	Exec(context.Context, []string) (context.Context, error)
}

The Gosh framework searches for Go plugin files in the ./plugins directory. Each package plugin must export a variable named Commands which is of type :

type Commands interface {
  ...
	Registry() map[string]Command
}

Type Commands type returns a list of Command via the Registry().

The following shows example command file plugins/testcmd.go. It implements two commands via types helloCmd and goodbyeCmd. The commands are exported via type testCmds using method Registry():

package main

import (
	"context"
	"fmt"
	"io"

	"github.com/vladimirvivien/gosh/api"
)

type helloCmd string

func (t helloCmd) Name() string      { return string(t) }
func (t helloCmd) Usage() string     { return `hello` }
func (t helloCmd) ShortDesc() string { return `prints greeting "hello there"` }
func (t helloCmd) LongDesc() string  { return t.ShortDesc() }
func (t helloCmd) Exec(ctx context.Context, args []string) (context.Context, error) {
	out := ctx.Value("gosh.stdout").(io.Writer)
	fmt.Fprintln(out, "hello there")
	return ctx, nil
}

type goodbyeCmd string

func (t goodbyeCmd) Name() string      { return string(t) }
func (t goodbyeCmd) Usage() string     { return t.Name() }
func (t goodbyeCmd) ShortDesc() string { return `prints message "bye bye"` }
func (t goodbyeCmd) LongDesc() string  { return t.ShortDesc() }
func (t goodbyeCmd) Exec(ctx context.Context, args []string) (context.Context, error) {
	out := ctx.Value("gosh.stdout").(io.Writer)
	fmt.Fprintln(out, "bye bye")
	return ctx, nil
}

// command module
type testCmds struct{}

func (t *testCmds) Init(ctx context.Context) error {
	out := ctx.Value("gosh.stdout").(io.Writer)
	fmt.Fprintln(out, "test module loaded OK")
	return nil
}

func (t *testCmds) Registry() map[string]api.Command {
	return map[string]api.Command{
		"hello":   helloCmd("hello"),
		"goodbye": goodbyeCmd("goodbye"),
	}
}

var Commands testCmds

License

MIT

gosh's People

Contributors

atakanyenel avatar gavinwade12 avatar merith-tk avatar philippgille avatar vladimirvivien avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gosh's Issues

More information on plugins?

Is it possible to get more information on this? I dont fully understand the syntax or the concepts behind it,

here is where im at,

package main

import (
	"context"
	"fmt"
	"io"

	"github.com/Merith-TK/gosh/api"
)

type customCmd string

func (t customCmd) Name() string      { return string(t) }
func (t customCmd) Usage() string     { return `custom` }
func (t customCmd) ShortDesc() string { return `prints greeting "custom there"` }
func (t customCmd) LongDesc() string  { return t.ShortDesc() }
func (t customCmd) Exec(ctx context.Context, args []string) (context.Context, error) {
	out := ctx.Value("gosh.stdout").(io.Writer)
	fmt.Fprintln(out, "custom there")
	return ctx, nil
}

// command module
type customCmds struct{}

func (t *customCmds) Init(ctx context.Context) error {
	out := ctx.Value("gosh.stdout").(io.Writer)
	fmt.Fprintln(out, "custom module loaded OK")
	return nil
}

func (t *customCmds) Registry() map[string]api.Command {
	return map[string]api.Command{
		"custom": customCmd("custom"),
	}
}

// Commands just custom
var Commands customCmds

This is a modifcation of testcmd.go
I understand everything up to line 17,

i dont understand how to declare and call upon functions entirely for this plugin in a easy to understand method,

golang 1.10

I know gosh is not yet supporting Mac but go version 1.10 now able to compile plugins for MacOS; however after trying to execute gosh I got the next output:

gosh> sys
runtime: bad pointer in frame main.main.func1 at 0xc420063f88: 0x4
fatal error: invalid pointer found on stack

runtime stack:
runtime.throw(0x50f0288, 0x1e)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/panic.go:619 +0x81 fp=0x7ffeefbfeba0 sp=0x7ffeefbfeb80 pc=0x5028c61
runtime.adjustpointers(0xc420063ea8, 0x7ffeefbfec98, 0x7ffeefbff068, 0x41b7c18, 0x41c0840)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/stack.go:592 +0x23e fp=0x7ffeefbfec10 sp=0x7ffeefbfeba0 pc=0x503c6be
runtime.adjustframe(0x7ffeefbfef78, 0x7ffeefbff068, 0x41c0840)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/stack.go:663 +0x32c fp=0x7ffeefbfecc8 sp=0x7ffeefbfec10 pc=0x503ca0c
runtime.gentraceback(0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0xc420078780, 0x0, 0x0, 0x7fffffff, 0x50f34c8, 0x7ffeefbff068, 0x0, ...)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/traceback.go:355 +0x136c fp=0x7ffeefbfefe0 sp=0x7ffeefbfecc8 pc=0x504576c
runtime.copystack(0xc420078780, 0x2000, 0x7ffeefbff101)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/stack.go:891 +0x26e fp=0x7ffeefbff198 sp=0x7ffeefbfefe0 pc=0x503d4fe
runtime.newstack()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/stack.go:1063 +0x310 fp=0x7ffeefbff328 sp=0x7ffeefbff198 pc=0x503d910
runtime: unexpected return pc for runtime.morestack called from 0x0
stack: frame={sp:0x7ffeefbff328, fp:0x7ffeefbff330} stack=[0x7ffeefb7fed0,0x7ffeefbff350)
00007ffeefbff228:  00000000041d1460  00007ffeefbff278
00007ffeefbff238:  00000000040157ac <runtime.(*mcentral).grow+236>  000000c41fff79ff
00007ffeefbff248:  000000c400000000  000000000426ba28
00007ffeefbff258:  000000000426ba28  000000000402869a <runtime.mach_semrelease+26>
00007ffeefbff268:  000000c420078780  000000c420052cf8
00007ffeefbff278:  00000000050af2dc <plugin/unnamed-0069ea828d7ffa11ef4c6308dd62ff6f39b2904d.(*sysinfoCmd).Exec+140>  000000c420078780
00007ffeefbff288:  0000000000000000  0000000000000000
00007ffeefbff298:  0000000000000000  0000000000000000
00007ffeefbff2a8:  00007ffeefbff2c0  0000000004035feb <runtime.acquirep+43>
00007ffeefbff2b8:  000000c420026500  00007ffeefbff2e0
00007ffeefbff2c8:  0000000004033fdf <runtime.exitsyscallfast_pidle+143>  000000c420026500
00007ffeefbff2d8:  000000c420026500  00007ffeefbff310
00007ffeefbff2e8:  0000000004052511 <runtime.exitsyscallfast.func1+65>  0000000000000001
00007ffeefbff2f8:  000000c420026500  000000c420078780
00007ffeefbff308:  000000c420052a7f  000000c420052aa0
00007ffeefbff318:  000000c420052d50  000000000504b739 <runtime.morestack+137>
00007ffeefbff328: <0000000000000000 >00007ffeefbff380
00007ffeefbff338:  0000000004052e03 <runtime.rt0_go+515>  00007ffeefbff380
00007ffeefbff348:  0000000004052e0a <runtime.rt0_go+522>
runtime.morestack()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/asm_amd64.s:480 +0x89 fp=0x7ffeefbff330 sp=0x7ffeefbff328 pc=0x504b739

goroutine 19 [copystack]:
plugin/unnamed-0069ea828d7ffa11ef4c6308dd62ff6f39b2904d.sysinfoCmd.Exec(0x50ec1aa, 0x3, 0x4140460, 0xc420098660, 0xc4200c0640, 0x1, 0xa, 0x40de290, 0xc4200cc900, 0xc4200cc938, ...)
	/Users/carlos.morales/Code/gosh/plugins/syscmd.go:119 +0x63c fp=0xc420063cf8 sp=0xc420063cf0 pc=0x50ae6ac
plugin/unnamed-0069ea828d7ffa11ef4c6308dd62ff6f39b2904d.(*sysinfoCmd).Exec(0x50fff20, 0x4140460, 0xc420098660, 0xc4200c0640, 0x1, 0xa, 0xa, 0x0, 0x4, 0x180)
	<autogenerated>:1 +0x8c fp=0xc420063d60 sp=0xc420063cf8 pc=0x50af2dc
main.(*Goshell).handle(0xc420098360, 0x4140460, 0xc420098660, 0xc4200a8160, 0x4, 0x0, 0x0, 0x0, 0x0)
	/Users/carlos.morales/Code/gosh/shell/gosh.go:111 +0x297 fp=0xc420063e58 sp=0xc420063d60 pc=0x40dfe67
main.main.func1(0x4140460, 0xc420098660, 0xc420098360)
	/Users/carlos.morales/Code/gosh/shell/gosh.go:179 +0x270 fp=0xc420063fc8 sp=0xc420063e58 pc=0x40e0a80
runtime.goexit()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc420063fd0 sp=0xc420063fc8 pc=0x40558b1
created by main.main
	/Users/carlos.morales/Code/gosh/shell/gosh.go:166 +0x4d5

goroutine 1 [select (no cases)]:
runtime.gopark(0x0, 0x0, 0x4129708, 0x11, 0x10, 0x1)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:291 +0x11a fp=0xc420067e68 sp=0xc420067e48 pc=0x402cd4a
runtime.block()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/select.go:192 +0x4c fp=0xc420067ea8 sp=0xc420067e68 pc=0x403addc
main.main()
	/Users/carlos.morales/Code/gosh/shell/gosh.go:189 +0x4da fp=0xc420067f88 sp=0xc420067ea8 pc=0x40e07fa
runtime.main()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:198 +0x212 fp=0xc420067fe0 sp=0xc420067f88 pc=0x402c8f2
runtime.goexit()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc420067fe8 sp=0xc420067fe0 pc=0x40558b1

goroutine 2 [force gc (idle)]:
runtime.gopark(0x412fe90, 0x41d0fa0, 0x4128ff7, 0xf, 0x412fd14, 0x1)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:291 +0x11a fp=0xc420044768 sp=0xc420044748 pc=0x402cd4a
runtime.goparkunlock(0x41d0fa0, 0x4128ff7, 0xf, 0x14, 0x1)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:297 +0x5e fp=0xc4200447a8 sp=0xc420044768 pc=0x402cdfe
runtime.forcegchelper()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:248 +0xcc fp=0xc4200447e0 sp=0xc4200447a8 pc=0x402cb8c
runtime.goexit()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc4200447e8 sp=0xc4200447e0 pc=0x40558b1
created by runtime.init.4
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:237 +0x35

goroutine 3 [GC sweep wait]:
runtime.gopark(0x412fe90, 0x41d1080, 0x4128b4e, 0xd, 0x401ee14, 0x1)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:291 +0x11a fp=0xc420044f60 sp=0xc420044f40 pc=0x402cd4a
runtime.goparkunlock(0x41d1080, 0x4128b4e, 0xd, 0x14, 0x1)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:297 +0x5e fp=0xc420044fa0 sp=0xc420044f60 pc=0x402cdfe
runtime.bgsweep(0xc420072000)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/mgcsweep.go:52 +0xa3 fp=0xc420044fd8 sp=0xc420044fa0 pc=0x401ee63
runtime.goexit()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc420044fe0 sp=0xc420044fd8 pc=0x40558b1
created by runtime.gcenable
	/usr/local/Cellar/go/1.10/libexec/src/runtime/mgc.go:216 +0x58

goroutine 18 [finalizer wait]:
runtime.gopark(0x412fe90, 0x41ed3b8, 0x4128df2, 0xe, 0x14, 0x1)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:291 +0x11a fp=0xc420040718 sp=0xc4200406f8 pc=0x402cd4a
runtime.goparkunlock(0x41ed3b8, 0x4128df2, 0xe, 0x14, 0x1)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/proc.go:297 +0x5e fp=0xc420040758 sp=0xc420040718 pc=0x402cdfe
runtime.runfinq()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/mfinal.go:175 +0xad fp=0xc4200407e0 sp=0xc420040758 pc=0x4015f0d
runtime.goexit()
	/usr/local/Cellar/go/1.10/libexec/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc4200407e8 sp=0xc4200407e0 pc=0x40558b1
created by runtime.createfing
	/usr/local/Cellar/go/1.10/libexec/src/runtime/mfinal.go:156 +0x62
exit status 2

DONE! TODO delegate splash to a plugin

here you go! I figured this would be easier for the end user to config in my program if i did this, so i went ahead and did it, and took advantage of the load order, hence the file name

./plugins/00_splash.go

package main

import (
	"context"
	"fmt"

	"github.com/vladimirvivien/gosh/api"
)

type splashCmd string
type splashCmds struct{}

func (t *splashCmds) Init(ctx context.Context) error {
	// to set your splash, modify the text in the println statement below, multiline is supported
	fmt.Println(`-------------------------`)

	return nil
}

func (t *splashCmds) Registry() map[string]api.Command {
	return map[string]api.Command{}
}

var Commands splashCmds

./gosh.go
remove these lines

// TODO delegate splash to a plugin
func (gosh *Goshell) printSplash() {
	fmt.Println(`	
                        888      
                        888      
                        888      
 .d88b.  .d88b. .d8888b 88888b.  
d88P"88bd88""88b88K     888 "88b 
888  888888  888"Y8888b.888  888 
Y88b 888Y88..88P     X88888  888 
 "Y88888 "Y88P"  88888P'888  888 
     888                         
Y8b d88P                         
 "Y88P"
 
 `)
}

https://github.com/vladimirvivien/gosh/blob/master/gosh.go#L44

cannot find package "github.com/vladimirvivien/gosh/api"

I got the error when run example by: go run shell/gosh.go. The error:

shell/gosh.go:15:2: cannot find package "github.com/vladimirvivien/gosh/api" in any of:
	/usr/local/go/src/github.com/vladimirvivien/gosh/api (from $GOROOT)
	/Users/nhatle/go/src/github.com/vladimirvivien/gosh/api (from $GOPATH)

The link github.com/vladimirvivien/gosh/api have error 404 when run on browser.

How to use it in MY application and not by running gosh.go?

At the moment it only seems to provide way to run applications by adding them to plugins directory here. While I like general approach of commands-plugins -- I see no any documented way currently to easily include it in my [abstract] application without copying 100+ lines of code from gosh.go and then rewriting it to fit into my code.

I'd think providing this as a library would be a much better approach than current one.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.