kh

package module
v0.0.0-...-f74817c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2016 License: MIT Imports: 11 Imported by: 1

README

The King's Hand

The King's Hand, or kh for short, is a tool for organizing and executing shellish scripts written in Go. As the name suggests, hand gets common tasks done for you w/out fuss and rarely with errors.

In essence, King's Hand make is easy to write small scripts in Go rather than x scripting language. Note that King's Hand does not dynamically execute go code like gorun. All scripts must be compiled to go binaries using the kh update command. More on this later.

King's Hand is partially inspired by sub from the great folks at Basecamp. The abbreviated name of King's Hand, kh, is also an oblique reference to the great computer scientist and author Brian KernigHan.

Installation

King's Hand assumes that you have a working Go development environment. First execute,

go get -u github.com/bryanwb/kh

To install the default fingers and initialize your ~/.kh directory

kh init

You can install additional fingers using the install subcommand

kh install github.com/bryanwb/example

The install subcommand uses go get under the covers. It requires valid go import paths.

Rationale

If you're like me, you have to write a fair number of shell scripts as part of your daily work as a developer or sysadmin. It is tempting to write those scripts in Bash but you think better of it, as Bash is a fucking mess that is completely unmanageable once the script is longer than one hundred lines. Further, managing command-line flags to bash scripts is a nightmare. You could write those scripts in a higher-level language like Python or Ruby but something still isn't right. Your scripts have zero type safety and debugging them is a chore for finding even minor typos.

Just as important, I want to organize my scripts logically as subcommands. Once I have written scripts, it can be very hard for me to find them again later and even recall how they work. I need an organizing structure for these tasks. For our first example, let's write a bunch of scripts for git. Our top-level program, let's call it kh has a subcommand git off of which all git-related scripts hang.

kh git gerrit-hook  # download the gerrit pre-commit hook into the current project
kh git add-ignores  # add commonly ignored file globs to .gitignore

GPG is another program that I cannot use w/out looking up a cheatsheet despite using for several years.

kh gpg decrypt foo.asc  # decrypted contents of foo.asc are output to foo.asc.plain
kh gpg encrypt foo.plain # encrypt contents writtent to foo.plain.gpg w/out overwriting original!

Note that the above scripts might be better accomplished through scripting vim, emacs, or sublimeText. However, in my experience there is zero consistency in editor usage across a development team.

Let's call these subcommands fingers rather than scripts so we don't confuse them with Bash.

I have been interested in writing shell(ish) scripts and further writing tools that are easy to up-to-date in Google's Go programming language. The primary benefit here is that you can use Go's higher level tooling and libraries AND take advantage of static typing to catch common errors.

However, using Go for this purpose presents a couple problems. Firstly, where do all these fingers(plugins) live? Go is pretty inflexible in how it expects your code to be organized and further it can't really be used to execute code on the fly. In fact, we don't really want or need our code to be executed on the fly.

This tool requires that you have a code organization for Go present on your machine.

User-defined fingers live in ~/.kh/

~/.kh/
          git/
              main.go
          gpg/
              main.go
          ruby/
              main.go      # your awesome scripts for manipulating ruby-related stuff
          python/
                main.go    # your awesome scripts for manipulating python-related stuff 

These plugins are not dynamically loaded! In fact, to use them you must first update the hand binary. To do this just execute kh update. After updating, the new fingers (plugins) will be avaiable for use.

For most practical purposes, the fingers listed in ~/.kh/ should be symlinks to directories in your Go code repository however this is not a hard requirement. For example, the built-in hello-world finger is actually a symlink to its path inside the kh package. This allows you to use standard Go development tools when writing fingers. Alternately, you can hack on a new finger in ~/.kh/foobar/main.go in good-old vi and kh won't complain.

~/.kh/hello-world  --> $GOPATH/src/github.com/bryanwb/kh/fingers/hello-world

Finger Development

A finger must have the following directory structure:

FINGERNAME/
          DESCRIPTION  # contains short text description of the command, less than 80 chars
          main.go
          Makefile  (optional)

go build is used to build the finger if a Makefile is not present.

main.go must contain struct that satisfies the Finger interface found in finger.go and execute the kh.Register and kh.Run methods in the body of its main method. See the hello-world/main.go example for more details.

King's Hand passed a FingerArgs object that has the flag Verbose which if present indicates that Verbose mode should be turned on. The FingerArgs object also has a Stdin field that holds any data received on Stdin by the kh binary. Note that this is not a buffered input stream but just a dumb byte array. At some point this should be implemented as a real buffered input stream.

With your keen eye, you have likely noticed that a Finger doesn't look like a typical procedural script. That's because it is actually an RPC Server that receives arguments from the kh binary and returns a response.

The Finger receives a Response object that has the fields Stdout, Stderr, and Log. These fields are, like Stdin, just dumb byte arrays and not real buffered I/O streams. Since the Finger runs in a separate process, any debugging you try to do using log.Debug or fmt.Println will disappear into the ether.

Stdout and Stderr should be used for output you wish to be printed to the parent kh command's Stdout and Stderr file handles. The Log field is for debugging output you wish to be printed to Stderr when the Verbose flag is set.

Naming your Finger

The commands list, install, update, init, version, and help are reserved for use by the kh binary itself.

Option Parsing

The flags and args intended for the finger are stored in FingerArgs.Args. Note that that the --verbose, -v, --help, -h flags are stripped from FingerArgs and stored in FingerArgs.Flags.

TODO

  • Find and list fingers present on github based on repo metadata
  • How to support shell completions?
  • Make it easier for fingers to manage their own flags and arguments, should be able to wrap pflag for this purpose

I would love some feedback on these ideas! Please let me know of any gotchas i have not considered

Documentation

Index

Constants

This section is empty.

Variables

View Source
var HandHome string
View Source
var Logger *log.Logger
View Source
var Verbose bool
View Source
var Version = "0.0.1"

Functions

func Contains

func Contains(l []string, s string) bool

func ContainsAny

func ContainsAny(list1 []string, list2 []string) bool

func EmptyArgs

func EmptyArgs(args []string) bool

wouldn't need to do this shit if Golang supported Monads

func HandHasKey

func HandHasKey(m map[string]*FingerDescriptor, s string) bool

func HelpCmdInvoked

func HelpCmdInvoked() bool

func Init

func Init() error

Initializes the HandHome w/ default fingers

func IsHelpCommand

func IsHelpCommand(args []string) bool

this intended for use by Fingers

func MapHasKey

func MapHasKey(m map[string]string, s string) bool

func Register

func Register(f Finger)

Registers a Finger w/ our plugin system

func Run

func Run() error

Run the finger subsystem

func StripFlags

func StripFlags(args []string) []string

func SubcommandInvoked

func SubcommandInvoked(args []string) string

Types

type Empty

type Empty struct{}

This is a hack to find the filesystem location of the kh package at runtime

type Finger

type Finger interface {
	Help(*FingerArgs, *Response) error
	Execute(*FingerArgs, *Response) error
}

This interface defines what a Finger must implement Currently not much, but more than pingo requires

type FingerArgs

type FingerArgs struct {
	Flags *flagSet
	Args  []string
	Stdin []byte
}

A finger takes a set of flags plus an arbitrary list of arguments

type FingerClient

type FingerClient struct {
	Path string
	// contains filtered or unexported fields
}

func (*FingerClient) Execute

func (f *FingerClient) Execute() error

type FingerDescriptor

type FingerDescriptor struct {
	Path        string
	Description string
}

type Hand

type Hand struct {
	Home    string
	Fingers map[string]*FingerDescriptor
}

func MakeHand

func MakeHand(home string) (*Hand, error)

func (*Hand) ExecuteFinger

func (h *Hand) ExecuteFinger(name string, flags map[string]bool, args []string) error

func (*Hand) FindFingers

func (h *Hand) FindFingers() error

func (*Hand) FingerDescriptions

func (h *Hand) FingerDescriptions() []string

func (*Hand) FingerNames

func (h *Hand) FingerNames() []string

func (*Hand) InstallFingers

func (h *Hand) InstallFingers(packages []string) error

Installs one or more fingers using go get and some symlinks

func (*Hand) MakeFinger

func (h *Hand) MakeFinger(name string, flags map[string]bool, args []string) (*FingerClient, error)

func (*Hand) Update

func (h *Hand) Update(fingers []string) error

func (*Hand) UpdateFinger

func (h *Hand) UpdateFinger(finger string) error

type Response

type Response struct {
	Stdout  []string
	Stderr  []string
	Log     []string
	Verbose bool
}

func (*Response) Debug

func (r *Response) Debug(msg string)

func (*Response) Debugf

func (r *Response) Debugf(format string, args ...interface{})

func (*Response) SetVerbose

func (r *Response) SetVerbose(verbose bool)

func (*Response) SprintLog

func (r *Response) SprintLog() string

func (*Response) SprintStderr

func (r *Response) SprintStderr() string

func (*Response) SprintStdout

func (r *Response) SprintStdout() string

func (*Response) WriteLog

func (r *Response) WriteLog(entry string)

func (*Response) WriteStderr

func (r *Response) WriteStderr(entry string)

func (*Response) WriteStdout

func (r *Response) WriteStdout(entry string)

Directories

Path Synopsis
cmd
kh
fingers

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL