github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/mods/path/path.go (about)

     1  // Package path provides functions for manipulating filesystem path names.
     2  package path
     3  
     4  import (
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"src.elv.sh/pkg/eval"
    10  	"src.elv.sh/pkg/eval/errs"
    11  )
    12  
    13  // Ns is the namespace for the re: module.
    14  var Ns = eval.NsBuilder{}.AddGoFns("path:", fns).Ns()
    15  
    16  var fns = map[string]interface{}{
    17  	"abs":           filepath.Abs,
    18  	"base":          filepath.Base,
    19  	"clean":         filepath.Clean,
    20  	"dir":           filepath.Dir,
    21  	"ext":           filepath.Ext,
    22  	"eval-symlinks": filepath.EvalSymlinks,
    23  	"is-abs":        filepath.IsAbs,
    24  	"is-dir":        isDir,
    25  	"is-regular":    isRegular,
    26  	"temp-dir":      tempDir,
    27  	"temp-file":     tempFile,
    28  }
    29  
    30  //elvdoc:fn abs
    31  //
    32  // ```elvish
    33  // path:abs $path
    34  // ```
    35  //
    36  // Outputs `$path` converted to an absolute path.
    37  //
    38  // ```elvish-transcript
    39  // ~> cd ~
    40  // ~> path:abs bin
    41  // ▶ /home/user/bin
    42  // ```
    43  
    44  //elvdoc:fn base
    45  //
    46  // ```elvish
    47  // path:base $path
    48  // ```
    49  //
    50  // Outputs the last element of `$path`. This is analogous to the POSIX `basename` command. See the
    51  // [Go documentation](https://pkg.go.dev/path/filepath#Base) for more details.
    52  //
    53  // ```elvish-transcript
    54  // ~> path:base ~/bin
    55  // ▶ bin
    56  // ```
    57  
    58  //elvdoc:fn clean
    59  //
    60  // ```elvish
    61  // path:clean $path
    62  // ```
    63  //
    64  // Outputs the shortest version of `$path` equivalent to `$path` by purely lexical processing. This
    65  // is most useful for eliminating unnecessary relative path elements such as `.` and `..` without
    66  // asking the OS to evaluate the path name. See the [Go
    67  // documentation](https://pkg.go.dev/path/filepath#Clean) for more details.
    68  //
    69  // ```elvish-transcript
    70  // ~> path:clean ./../bin
    71  // ▶ ../bin
    72  // ```
    73  
    74  //elvdoc:fn dir
    75  //
    76  // ```elvish
    77  // path:dir $path
    78  // ```
    79  //
    80  // Outputs all but the last element of `$path`, typically the path's enclosing directory. See the
    81  // [Go documentation](https://pkg.go.dev/path/filepath#Dir) for more details. This is analogous to
    82  // the POSIX `dirname` command.
    83  //
    84  // ```elvish-transcript
    85  // ~> path:dir /a/b/c/something
    86  // ▶ /a/b/c
    87  // ```
    88  
    89  //elvdoc:fn ext
    90  //
    91  // ```elvish
    92  // ext $path
    93  // ```
    94  //
    95  // Outputs the file name extension used by `$path` (including the separating period). If there is no
    96  // extension the empty string is output. See the [Go
    97  // documentation](https://pkg.go.dev/path/filepath#Ext) for more details.
    98  //
    99  // ```elvish-transcript
   100  // ~> path:ext hello.elv
   101  // ▶ .elv
   102  // ```
   103  
   104  //elvdoc:fn is-abs
   105  //
   106  // ```elvish
   107  // is-abs $path
   108  // ```
   109  //
   110  // Outputs `$true` if the path is an abolute path. Note that platforms like Windows have different
   111  // rules than UNIX like platforms for what constitutes an absolute path. See the [Go
   112  // documentation](https://pkg.go.dev/path/filepath#IsAbs) for more details.
   113  //
   114  // ```elvish-transcript
   115  // ~> path:is-abs hello.elv
   116  // ▶ false
   117  // ~> path:is-abs /hello.elv
   118  // ▶ true
   119  // ```
   120  
   121  //elvdoc:fn eval-symlinks
   122  //
   123  // ```elvish-transcript
   124  // ~> mkdir bin
   125  // ~> ln -s bin sbin
   126  // ~> path:eval-symlinks ./sbin/a_command
   127  // ▶ bin/a_command
   128  // ```
   129  //
   130  // Outputs `$path` after resolving any symbolic links. If `$path` is relative the result will be
   131  // relative to the current directory, unless one of the components is an absolute symbolic link.
   132  // This function calls `path:clean` on the result before outputing it. This is analogous to the
   133  // external `realpath` or `readlink` command found on many systems. See the [Go
   134  // documentation](https://pkg.go.dev/path/filepath#EvalSymlinks) for more details.
   135  
   136  //elvdoc:fn is-dir
   137  //
   138  // ```elvish
   139  // is-dir &follow-symlink=$false $path
   140  // ```
   141  //
   142  // Outputs `$true` if the path resolves to a directory. If the final element of the path is a
   143  // symlink, even if it points to a directory, it still outputs `$false` since a symlink is not a
   144  // directory. Setting option `&follow-symlink` to true will cause the last element of the path, if
   145  // it is a symlink, to be resolved before doing the test.
   146  //
   147  // @cf eval-symlinks
   148  //
   149  // ```elvish-transcript
   150  // ~> touch not-a-dir
   151  // ~> path:is-dir not-a-dir
   152  // ▶ false
   153  // ~> path:is-dir /tmp
   154  // ▶ true
   155  // ```
   156  
   157  type isOpts struct{ FollowSymlink bool }
   158  
   159  func (opts *isOpts) SetDefaultOptions() {}
   160  
   161  func isDir(opts isOpts, path string) bool {
   162  	var fi os.FileInfo
   163  	var err error
   164  	if opts.FollowSymlink {
   165  		fi, err = os.Stat(path)
   166  	} else {
   167  		fi, err = os.Lstat(path)
   168  	}
   169  	return err == nil && fi.Mode().IsDir()
   170  }
   171  
   172  //elvdoc:fn is-regular
   173  //
   174  // ```elvish
   175  // is-regular &follow-symlink=$false $path
   176  // ```
   177  //
   178  // Outputs `$true` if the path resolves to a regular file. If the final element of the path is a
   179  // symlink, even if it points to a regular file, it still outputs `$false` since a symlink is not a
   180  // regular file. Setting option `&follow-symlink` to true will cause the last element of the path,
   181  // if it is a symlink, to be resolved before doing the test.
   182  //
   183  // @cf eval-symlinks
   184  //
   185  // ```elvish-transcript
   186  // ~> touch not-a-dir
   187  // ~> path:is-regular not-a-dir
   188  // ▶ true
   189  // ~> path:is-dir /tmp
   190  // ▶ false
   191  // ```
   192  
   193  func isRegular(opts isOpts, path string) bool {
   194  	var fi os.FileInfo
   195  	var err error
   196  	if opts.FollowSymlink {
   197  		fi, err = os.Stat(path)
   198  	} else {
   199  		fi, err = os.Lstat(path)
   200  	}
   201  	return err == nil && fi.Mode().IsRegular()
   202  }
   203  
   204  //elvdoc:fn temp-dir
   205  //
   206  // ```elvish
   207  // temp-dir &dir='' $pattern?
   208  // ```
   209  //
   210  // Creates a new directory and outputs its name.
   211  //
   212  // The &dir option determines where the directory will be created; if it is an
   213  // empty string (the default), a system-dependent directory suitable for storing
   214  // temporary files will be used. The `$pattern` argument determins the name of
   215  // the directory, where the last star will be replaced by a random string; it
   216  // defaults to `elvish-*`.
   217  //
   218  // It is the caller's responsibility to remove the directory if it is intended
   219  // to be temporary.
   220  //
   221  // ```elvish-transcript
   222  // ~> path:temp-dir
   223  // ▶ /tmp/elvish-RANDOMSTR
   224  // ~> path:temp-dir x-
   225  // ▶ /tmp/x-RANDOMSTR
   226  // ~> path:temp-dir 'x-*.y'
   227  // ▶ /tmp/x-RANDOMSTR.y
   228  // ~> path:temp-dir &dir=.
   229  // ▶ elvish-RANDOMSTR
   230  // ~> path:temp-dir &dir=/some/dir
   231  // ▶ /some/dir/elvish-RANDOMSTR
   232  // ```
   233  
   234  type mktempOpt struct{ Dir string }
   235  
   236  func (o *mktempOpt) SetDefaultOptions() {}
   237  
   238  func tempDir(opts mktempOpt, args ...string) (string, error) {
   239  	var pattern string
   240  	switch len(args) {
   241  	case 0:
   242  		pattern = "elvish-*"
   243  	case 1:
   244  		pattern = args[0]
   245  	default:
   246  		return "", errs.ArityMismatch{
   247  			What:     "arguments here",
   248  			ValidLow: 0, ValidHigh: 1, Actual: len(args)}
   249  	}
   250  
   251  	return ioutil.TempDir(opts.Dir, pattern)
   252  }
   253  
   254  //elvdoc:fn temp-file
   255  //
   256  // ```elvish
   257  // temp-file &dir='' $pattern?
   258  // ```
   259  //
   260  // Creates a new file and outputs a [file](language.html#file) object opened
   261  // for reading and writing.
   262  //
   263  // The &dir option determines where the file will be created; if it is an
   264  // empty string (the default), a system-dependent directory suitable for storing
   265  // temporary files will be used. The `$pattern` argument determins the name of
   266  // the file, where the last star will be replaced by a random string; it
   267  // defaults to `elvish-*`.
   268  //
   269  // It is the caller's responsibility to close the file with
   270  // [`file:close`](file.html#close). The caller should also remove the file if it
   271  // is intended to be temporary (with `rm $f[name]`).
   272  //
   273  // ```elvish-transcript
   274  // ~> f = path:temp-file
   275  // ~> put $f[name]
   276  // ▶ /tmp/elvish-RANDOMSTR
   277  // ~> echo hello > $f
   278  // ~> cat $f[name]
   279  // hello
   280  // ~> f = path:temp-file x-
   281  // ~> put $f[name]
   282  // ▶ /tmp/x-RANDOMSTR
   283  // ~> f = path:temp-file 'x-*.y'
   284  // ~> put $f[name]
   285  // ▶ /tmp/x-RANDOMSTR.y
   286  // ~> f = path:temp-file &dir=.
   287  // ~> put $f[name]
   288  // ▶ elvish-RANDOMSTR
   289  // ~> f = path:temp-file &dir=/some/dir
   290  // ~> put $f[name]
   291  // ▶ /some/dir/elvish-RANDOMSTR
   292  // ```
   293  
   294  func tempFile(opts mktempOpt, args ...string) (*os.File, error) {
   295  	var pattern string
   296  	switch len(args) {
   297  	case 0:
   298  		pattern = "elvish-*"
   299  	case 1:
   300  		pattern = args[0]
   301  	default:
   302  		return nil, errs.ArityMismatch{
   303  			What:     "arguments here",
   304  			ValidLow: 0, ValidHigh: 1, Actual: len(args)}
   305  	}
   306  
   307  	return ioutil.TempFile(opts.Dir, pattern)
   308  }