github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/util/search.go (about)

     1  package util
     2  
     3  import (
     4  	"errors"
     5  	"io/ioutil"
     6  	"os"
     7  	"strings"
     8  )
     9  
    10  var (
    11  	ErrNotExecutable = errors.New("not executable")
    12  	ErrNotFound      = errors.New("not found")
    13  )
    14  
    15  // Search tries to resolve an external command and return the full (possibly
    16  // relative) path.
    17  func Search(paths []string, exe string) (string, error) {
    18  	if DontSearch(exe) {
    19  		if IsExecutable(exe) {
    20  			return exe, nil
    21  		}
    22  		return "", ErrNotExecutable
    23  	}
    24  	for _, p := range paths {
    25  		full := p + "/" + exe
    26  		if IsExecutable(full) {
    27  			return full, nil
    28  		}
    29  	}
    30  	return "", ErrNotFound
    31  }
    32  
    33  // EachExecutable calls f for each executable file in paths.
    34  func EachExecutable(paths []string, f func(string)) {
    35  	for _, dir := range paths {
    36  		// XXX Ignore error
    37  		infos, _ := ioutil.ReadDir(dir)
    38  		for _, info := range infos {
    39  			if !info.IsDir() && (info.Mode()&0111 != 0) {
    40  				f(info.Name())
    41  			}
    42  		}
    43  	}
    44  }
    45  
    46  // DontSearch determines whether the path to an external command should be
    47  // taken literally and not searched.
    48  func DontSearch(exe string) bool {
    49  	return exe == ".." || strings.ContainsRune(exe, '/')
    50  }
    51  
    52  // IsExecutable determines whether path refers to an executable file.
    53  func IsExecutable(path string) bool {
    54  	fi, err := os.Stat(path)
    55  	if err != nil {
    56  		return false
    57  	}
    58  	fm := fi.Mode()
    59  	return !fm.IsDir() && (fm&0111 != 0)
    60  }