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 }