github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/mods/path/path.go (about)

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