Skip to content
/ kh Public

The King's Hand (kh) is a tool for organizing and executing shell(ish) scripts written in Go

License

Notifications You must be signed in to change notification settings

bryanwb/kh

Repository files navigation

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

About

The King's Hand (kh) is a tool for organizing and executing shell(ish) scripts written in Go

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published