github.com/koron/hk@v0.0.0-20150303213137-b8aeaa3ab34c/plugin.go (about)

     1  package main
     2  
     3  import (
     4  	"net/url"
     5  	"os"
     6  	"os/exec"
     7  	"strings"
     8  )
     9  
    10  var (
    11  	hkPath string
    12  )
    13  
    14  var helpPlugins = &Command{
    15  	Usage:    "plugins",
    16  	Category: "hk",
    17  	Short:    "interface to plugin commands",
    18  	Long: `
    19  Plugin commands extend hk's functionality.
    20  
    21  Plugins are located in one of the directories in hk's search path,
    22  HKPATH, and executed for unrecognized hk commands. If a plugin named
    23  "default" exists, it will be run when no suitably-named plugin can
    24  be found. (Run 'hk help environ' for details on HKPATH.)
    25  
    26  The arguments to the plugin are the arguments to hk, not including
    27  "hk" itself.
    28  
    29  Several environment variables will also be set:
    30  
    31  HEROKU_API_URL
    32  
    33    This follows the same format as the variable read by hk. For a
    34    plugin, this variable is always set and it always includes a
    35    username and password.
    36  
    37    (Run 'hk help environ' for details of the format.)
    38  
    39  HKUSER
    40  
    41    The username from HEROKU_API_URL, for convenience.
    42  
    43  HKPASS
    44  
    45    The password from HEROKU_API_URL, for convenience.
    46  
    47  HKHOST
    48  
    49    The hostname (and port, if any) from HEROKU_API_URL, for
    50    convenience.
    51  
    52  HKAPP
    53  
    54    The name of the heroku app in the current directory, if there is a
    55    git remote named "heroku" with the proper URL format.
    56  
    57  HKVERSION
    58  
    59    The version string of hk that executed the plugin.
    60  
    61  HKPLUGINMODE
    62  
    63    Either unset or it takes the value "info". If set to info, the
    64    plugin should print out a summary of itself in the following
    65    format:
    66  
    67      name version: short help
    68  
    69      long help
    70  
    71    Where name is the plugin's file name, version is the plugin's
    72    version string, short help is a one-line help message at most 50 chars,
    73    and long help is a complete help text including usage line, prose
    74    description, and list of options. Plugins are encouraged to follow the
    75    example set by built-in hk commands for the style of this documentation.
    76  `,
    77  }
    78  
    79  func init() {
    80  	const defaultPluginPath = "/usr/local/lib/hk/plugin"
    81  	hkPath = os.Getenv("HKPATH")
    82  	if hkPath == "" {
    83  		hkPath = defaultPluginPath
    84  	}
    85  
    86  }
    87  
    88  func execPlugin(path string, args []string) error {
    89  	u, err := url.Parse(apiURL)
    90  	if err != nil {
    91  		printFatal(err.Error())
    92  	}
    93  
    94  	hkuser, hkpass := getCreds(apiURL)
    95  	u.User = url.UserPassword("", hkpass)
    96  	hkapp, _ := app()
    97  	env := []string{
    98  		"HEROKU_API_URL=" + u.String(),
    99  		"HKAPP=" + hkapp,
   100  		"HKUSER=" + hkuser,
   101  		"HKPASS=" + hkpass,
   102  		"HKHOST=" + u.Host,
   103  		"HKVERSION=" + Version,
   104  	}
   105  
   106  	return sysExec(path, args, append(env, os.Environ()...))
   107  }
   108  
   109  func findPlugin(name string) (path string) {
   110  	path = lookupPlugin(name)
   111  	if path == "" {
   112  		path = lookupPlugin("default")
   113  	}
   114  	return path
   115  }
   116  
   117  // NOTE: lookupPlugin is not threadsafe for anything needing the PATH env var.
   118  func lookupPlugin(name string) string {
   119  	opath := os.Getenv("PATH")
   120  	defer os.Setenv("PATH", opath)
   121  	os.Setenv("PATH", hkPath)
   122  
   123  	path, err := exec.LookPath(name)
   124  	if err != nil {
   125  		if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
   126  			return ""
   127  		}
   128  		printFatal(err.Error())
   129  	}
   130  	return path
   131  }
   132  
   133  type plugin string
   134  
   135  func (p plugin) Name() string {
   136  	return string(p)
   137  }
   138  
   139  func (p plugin) Short() string {
   140  	_, short, _ := pluginInfo(string(p))
   141  	return short
   142  }
   143  
   144  func pluginInfo(name string) (ver, short, long string) {
   145  	if os.Getenv("HKPLUGINMODE") == "info" {
   146  		return "", "plugin exec loop", "plugin exec loop"
   147  	}
   148  	short = "no description"
   149  	long = name + ": unknown description"
   150  	var cmd exec.Cmd
   151  	cmd.Args = []string{name}
   152  	cmd.Path = lookupPlugin(name)
   153  	cmd.Env = append([]string{"HKPLUGINMODE=info"}, os.Environ()...)
   154  	buf, err := cmd.Output()
   155  	if err != nil {
   156  		return
   157  	}
   158  	info := string(buf)
   159  	if !strings.HasPrefix(info, name+" ") {
   160  		return
   161  	}
   162  	info = info[len(name)+1:]
   163  	i := strings.Index(info, ": ")
   164  	if i < 0 {
   165  		return
   166  	}
   167  	ver, info = info[:i], info[i+2:]
   168  	i = strings.Index(info, "\n\n")
   169  	if i < 0 || 50 < i || strings.Contains(info[:i], "\n") {
   170  		return
   171  	}
   172  	short, long = info[:i], info[i+2:]
   173  	return ver, strings.TrimSpace(short), strings.TrimSpace(long)
   174  }