blob: 86154a3a5e7abc649302c484b8d0b178c2133ef9 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package v23cmd implements utilities for running v23 cmdline programs.
//
// The cmdline package requires a Runner that implements Run(env, args), but for
// v23 programs we'd like to implement Run(ctx, env, args) instead. The
// initialization of the ctx is also tricky, since v23.Init() calls flag.Parse,
// but the cmdline package needs to call flag.Parse first.
//
// The RunnerFunc package-level function allows us to write run functions of the
// form Run(ctx, env, args), retaining static type-safety, and also getting the
// flag.Parse ordering right. In addition the Run and ParseAndRun functions may
// be called in tests, to pass your own ctx into your command runners.
package v23cmd
import (
"errors"
"v.io/v23"
"v.io/v23/context"
"v.io/x/lib/cmdline"
)
var (
ErrUnknownRunner = errors.New("v23cmd: unknown runner")
// The strategy behind this package is this slice, which only grows and never
// shrinks. Here we maintain a mapping between an index number and the
// originally registered function, so that we can look the function up in a
// typesafe manner.
funcs []func(*context.T, *cmdline.Env, []string) error
initFn = v23.Init
)
// indexRunner implements cmdline.Runner by looking up the index in the funcs
// slice to retrieve the originally registered function.
type indexRunner uint
func (ix indexRunner) Run(env *cmdline.Env, args []string) error {
if int(ix) < len(funcs) {
ctx, shutdown := initFn()
err := funcs[ix](ctx, env, args)
shutdown()
return err
}
return ErrUnknownRunner
}
// RunnerFunc behaves similarly to cmdline.RunnerFunc, but takes a run function
// fn that includes a context as the first arg. The context is created via
// v23.Init when Run is called on the returned Runner.
func RunnerFunc(fn func(*context.T, *cmdline.Env, []string) error) cmdline.Runner {
ix := indexRunner(len(funcs))
funcs = append(funcs, fn)
return ix
}
// Lookup returns the function registered via RunnerFunc corresponding to
// runner, or nil if it doesn't exist.
func Lookup(runner cmdline.Runner) func(*context.T, *cmdline.Env, []string) error {
if ix, ok := runner.(indexRunner); ok && int(ix) < len(funcs) {
return funcs[ix]
}
return nil
}
// Run performs Lookup, and then runs the function with the given ctx, env and
// args.
//
// Doesn't call v23.Init; the context initialization is up to you.
func Run(runner cmdline.Runner, ctx *context.T, env *cmdline.Env, args []string) error {
if fn := Lookup(runner); fn != nil {
return fn(ctx, env, args)
}
return ErrUnknownRunner
}
// ParseAndRun parses the cmd with the given env and args, and calls Run to run
// the returned runner with the ctx, env and args.
//
// Doesn't call v23.Init; the context initialization is up to you.
func ParseAndRun(cmd *cmdline.Command, ctx *context.T, env *cmdline.Env, args []string) error {
runner, args, err := cmdline.Parse(cmd, env, args)
if err != nil {
return err
}
return Run(runner, ctx, env, args)
}