// Show help. // This command is useful for placing at the front of a CLI "subcommand" to have it output // help information. It will only trigger when "show" is set to true, so another command // can, for example, check for a "-h" or "-help" flag and set "show" based on that. // // Params: // - show (bool): If `true`, show help. // - summary (string): A one-line summary of the command. // - description (string): A short description of what the command does. // - usage (string): usage information. // - flags (FlagSet): Flags that are supported. The FlagSet will be converted to help text. // - writer (Writer): The location that this will write to. Default is os.Stdout // - subcommands ([]string): A list of subcommands. This will be formatted as help text. func ShowHelp(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { showHelp := false showHelpO := params.Get("show", false) switch showHelpO.(type) { case string: showHelp = strings.ToLower(showHelpO.(string)) == "true" case bool: showHelp = showHelpO.(bool) } writer := params.Get("writer", os.Stdout).(io.Writer) pmap := params.AsMap() // Last resort: If no summary, pull it from the route description. if summary, ok := pmap["summary"]; !ok || len(summary.(string)) == 0 { pmap["summary"] = cxt.Get("route.Description", "").(string) } sections := []string{"summary", "description", "usage"} if _, ok := params.Has("subcommands"); ok { sections = append(sections, "subcommands") } if showHelp { displayHelp(sections, pmap, writer) return true, new(cookoo.Stop) } return false, nil }
// Template is a template-based text formatter. // // This uses the core `text/template` to process a given string template. // // Params // - template (string): A template string. // - template.Context (bool): If true, the context will be placed into the // template renderer as 'Cxt', and can be used as `{{.Cxt.Foo}}`. False // by default. // - ... (interface{}): Values passed into the template. // // Conventionally, template variables should start with an initial capital. // // Returns a formatted string. func Template(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { format := cookoo.GetString("template", "", p) withCxt := cookoo.GetBool("template.Context", false, p) name := fmt.Sprintf("%x", md5.Sum([]byte(format))) //c.Logf("debug", "Template %s is '%s'\n", name, format) tpl, err := template.New(name).Parse(format) if err != nil { return "", err } data := p.AsMap() if withCxt { //c.Logf("debug", "Adding context.") data["Cxt"] = c.AsMap() } var out bytes.Buffer if err := tpl.Execute(&out, data); err != nil { return "", err } return out.String(), nil }
// Get gets one or more environment variables and puts them into the context. // // Parameters passed in are of the form varname => defaultValue. // // r.Route("foo", "example").Does(envvar.Get).Using("HOME").WithDefault(".") // // As with all environment variables, the default value must be a string. // // WARNING: Since parameters are a map, order of processing is not // guaranteed. If order is important, you'll need to call this command // multiple times. // // For each parameter (`Using` clause), this command will look into the // environment for a matching variable. If it finds one, it will add that // variable to the context. If it does not find one, it will expand the // default value (so you can set a default to something like "$HOST:$PORT") // and also put the (unexpanded) default value back into the context in case // any subsequent call to `os.Getenv` occurs. func Get(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { for name, def := range params.AsMap() { var val string if val = os.Getenv(name); len(val) == 0 { def := def.(string) val = os.ExpandEnv(def) // We want to make sure that any subsequent calls to Getenv // return the same default. os.Setenv(name, val) } c.Put(name, val) log.Debugf(c, "Name: %s, Val: %s", name, val) } return true, nil }
// Set takes the given names and values and puts them into both the context // and the environment. // // Unlike Get, it does not try to retrieve the values from the environment // first. // // Values are passed through os.ExpandEnv() // // There is no guarantee of insertion order. If multiple name/value pairs // are given, they will be put into the context in whatever order they // are retrieved from the underlying map. // // Params: // accessed as map[string]string // Returns: // nothing, but inserts all name/value pairs into the context and the // environment. func Set(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { for name, def := range p.AsMap() { // Assume Nil means unset the value. if def == nil { def = "" } val := fmt.Sprintf("%v", def) val = os.ExpandEnv(val) log.Debugf(c, "Name: %s, Val: %s", name, val) os.Setenv(name, val) c.Put(name, val) } return true, nil }
// Sprintf formats a string and then returns the result to the context. // // This is a command wrapper for the core `fmt.Sprintf` function, but tooled // to work the Cookoo way. // // The following returns 'Hello World' to the context. // // //... // Does(Sprintf, "out"). // Using("format").WithDefault("%s %s\n") // Using("0").WithDefault("Hello") // Using("1").WithDefault("World") // // Params: // - format (string): The format string // - "0"... (string): String representation of an integer ascending from 0. // These are treated as positional. func Sprintf(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { msg := params.Get("format", "").(string) maxP := len(params.AsMap()) vals := make([]interface{}, 0, maxP-1) var istr string var i = 0 for i < maxP { istr = fmt.Sprintf("%d", i) // FIXME if v, ok := params.Has(istr); ok { //fmt.Printf("%d: Found %v\n", i, v) vals = append(vals, v) i++ } else { break } } return fmt.Sprintf(msg, vals...), nil }
// Get gets one or more environment variables and puts them into the context. // // Parameters passed in are of the form varname => defaultValue. // // r.Route("foo", "example").Does(envvar.Get).Using("HOME").WithDefault(".") // // As with all environment variables, the default value must be a string. // // WARNING: Since parameters are a map, order of processing is not // guaranteed. If order is important, you'll need to call this command // multiple times. // // For each parameter (`Using` clause), this command will look into the // environment for a matching variable. If it finds one, it will add that // variable to the context. If it does not find one, it will expand the // default value (so you can set a default to something like "$HOST:$PORT") // and also put the (unexpanded) default value back into the context in case // any subsequent call to `os.Getenv` occurs. func Get(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { for name, def := range params.AsMap() { var val string if val = os.Getenv(name); len(val) == 0 { if def == nil { def = "" } def, ok := def.(string) if !ok { log.Warnf(c, "Could not convert %s. Type is %T", name, def) } val = os.ExpandEnv(def) // We want to make sure that any subsequent calls to Getenv // return the same default. os.Setenv(name, val) } c.Put(name, val) log.Debugf(c, "Name: %s, Val: %s", name, val) } return true, nil }
// KillOnExit kills PIDs when the program exits. // // Otherwise, this blocks until an os.Interrupt or os.Kill is received. // // Params: // This treats Params as a map of process names (unimportant) to PIDs. It then // attempts to kill all of the pids that it receives. func KillOnExit(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { sigs := make(chan os.Signal, 1) signal.Notify(sigs, os.Interrupt, os.Kill) safely.GoDo(c, func() { log.Info(c, "Builder is running.") <-sigs c.Log("info", "Builder received signal to stop.") pids := p.AsMap() killed := 0 for name, pid := range pids { if pid, ok := pid.(int); ok { if proc, err := os.FindProcess(pid); err == nil { log.Infof(c, "Killing %s (pid=%d)", name, pid) proc.Kill() killed++ } } } }) return nil, nil }