blob: c97d178aec4d8ed71c0260ff98a621f18ce57872 [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.
package v23cmd
import (
"v.io/v23"
"v.io/v23/context"
"v.io/x/lib/cmdline"
)
type runner struct {
run func(*context.T, *cmdline.Env, []string) error
init func() (*context.T, v23.Shutdown, error)
}
func (r runner) Run(env *cmdline.Env, args []string) error {
ctx, shutdown, err := r.init()
if err != nil {
return err
}
defer shutdown()
return r.run(ctx, env, args)
}
// RunnerFunc is like cmdline.RunnerFunc, but takes a run function 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(run func(*context.T, *cmdline.Env, []string) error) cmdline.Runner {
return runner{run, v23.TryInit}
}
// RunnerFuncWithInit is like RunnerFunc, but allows specifying the init
// function used to create the context.
//
// This is typically used to set properties on the context before it is passed
// to the run function. E.g. you may use this to set a deadline on the context:
//
// var cmdRoot = &cmdline.Command{
// Runner: v23cmd.RunnerFuncWithInit(runRoot, initWithDeadline)
// ...
// }
//
// func runRoot(ctx *context.T, env *cmdline.Env, args []string) error {
// ...
// }
//
// func initWithDeadline() (*context.T, v23.Shutdown) {
// ctx, shutdown := v23.Init()
// ctx, cancel := context.WithTimeout(ctx, time.Minute)
// return ctx, func(){ cancel(); shutdown() }
// }
//
// func main() {
// cmdline.Main(cmdRoot)
// }
//
// An alternative to the above example is to call context.WithTimeout within
// runRoot. The advantage of using RunnerFuncWithInit is that your regular code
// can use a context with a 1 minute timeout, while your testing code can use
// v23cmd.ParseAndRunForTest to pass a context with a 10 second timeout.
func RunnerFuncWithInit(run func(*context.T, *cmdline.Env, []string) error, init func() (*context.T, v23.Shutdown, error)) cmdline.Runner {
return runner{run, init}
}
// ParseAndRunForTest parses the cmd with the given env and args, and calls Run
// on the returned runner. If the runner was created by the v23cmd package,
// calls the run function directly with the given ctx, env and args.
//
// Doesn't call v23.Init; the context initialization is up to you.
//
// Only meant to be called within tests - if used in non-test code the ordering
// of flag.Parse calls will be wrong. The correct ordering is for cmdline.Parse
// to be called before v23.Init, but this function takes a ctx argument and then
// calls cmdline.Parse.
func ParseAndRunForTest(cmd *cmdline.Command, ctx *context.T, env *cmdline.Env, args []string) error {
r, args, err := cmdline.Parse(cmd, env, args)
if err != nil {
return err
}
if x, ok := r.(runner); ok {
return x.run(ctx, env, args)
}
return r.Run(env, args)
}