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 }