github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/builtin_fn_misc.go (about) 1 package eval 2 3 // Misc builtin functions. 4 5 import ( 6 "errors" 7 "fmt" 8 "math/rand" 9 "net" 10 "os" 11 "sync" 12 "time" 13 "unicode/utf8" 14 15 "github.com/markusbkk/elvish/pkg/diag" 16 "github.com/markusbkk/elvish/pkg/eval/errs" 17 "github.com/markusbkk/elvish/pkg/eval/vals" 18 "github.com/markusbkk/elvish/pkg/parse" 19 ) 20 21 var ( 22 ErrNegativeSleepDuration = errors.New("sleep duration must be >= zero") 23 ErrInvalidSleepDuration = errors.New("invalid sleep duration") 24 ) 25 26 // Builtins that have not been put into their own groups go here. 27 28 func init() { 29 addBuiltinFns(map[string]interface{}{ 30 "nop": nop, 31 "kind-of": kindOf, 32 "constantly": constantly, 33 34 // Introspection 35 "call": call, 36 "resolve": resolve, 37 "eval": eval, 38 "use-mod": useMod, 39 40 "deprecate": deprecate, 41 42 // Time 43 "sleep": sleep, 44 "time": timeCmd, 45 46 "-ifaddrs": _ifaddrs, 47 }) 48 49 // For rand and randint. 50 rand.Seed(time.Now().UTC().UnixNano()) 51 } 52 53 //elvdoc:fn nop 54 // 55 // ```elvish 56 // nop &any-opt= $value... 57 // ``` 58 // 59 // Accepts arbitrary arguments and options and does exactly nothing. 60 // 61 // Examples: 62 // 63 // ```elvish-transcript 64 // ~> nop 65 // ~> nop a b c 66 // ~> nop &k=v 67 // ``` 68 // 69 // Etymology: Various languages, in particular NOP in 70 // [assembly languages](https://en.wikipedia.org/wiki/NOP). 71 72 func nop(opts RawOptions, args ...interface{}) { 73 // Do nothing 74 } 75 76 //elvdoc:fn kind-of 77 // 78 // ```elvish 79 // kind-of $value... 80 // ``` 81 // 82 // Output the kinds of `$value`s. Example: 83 // 84 // ```elvish-transcript 85 // ~> kind-of lorem [] [&] 86 // ▶ string 87 // ▶ list 88 // ▶ map 89 // ``` 90 // 91 // The terminology and definition of "kind" is subject to change. 92 93 func kindOf(fm *Frame, args ...interface{}) error { 94 out := fm.ValueOutput() 95 for _, a := range args { 96 err := out.Put(vals.Kind(a)) 97 if err != nil { 98 return err 99 } 100 } 101 return nil 102 } 103 104 //elvdoc:fn constantly 105 // 106 // ```elvish 107 // constantly $value... 108 // ``` 109 // 110 // Output a function that takes no arguments and outputs `$value`s when called. 111 // Examples: 112 // 113 // ```elvish-transcript 114 // ~> var f = (constantly lorem ipsum) 115 // ~> $f 116 // ▶ lorem 117 // ▶ ipsum 118 // ``` 119 // 120 // The above example is equivalent to simply `var f = { put lorem ipsum }`; 121 // it is most useful when the argument is **not** a literal value, e.g. 122 // 123 // ```elvish-transcript 124 // ~> var f = (constantly (uname)) 125 // ~> $f 126 // ▶ Darwin 127 // ~> $f 128 // ▶ Darwin 129 // ``` 130 // 131 // The above code only calls `uname` once when defining `$f`. In contrast, if 132 // `$f` is defined as `var f = { put (uname) }`, every time you invoke `$f`, 133 // `uname` will be called. 134 // 135 // Etymology: [Clojure](https://clojuredocs.org/clojure.core/constantly). 136 137 func constantly(args ...interface{}) Callable { 138 // TODO(xiaq): Repr of this function is not right. 139 return NewGoFn( 140 "created by constantly", 141 func(fm *Frame) error { 142 out := fm.ValueOutput() 143 for _, v := range args { 144 err := out.Put(v) 145 if err != nil { 146 return err 147 } 148 } 149 return nil 150 }, 151 ) 152 } 153 154 //elvdoc:fn call 155 // 156 // ```elvish 157 // call $fn $args $opts 158 // ``` 159 // 160 // Calls `$fn` with `$args` as the arguments, and `$opts` as the option. Useful 161 // for calling a function with dynamic option keys. 162 // 163 // Example: 164 // 165 // ```elvish-transcript 166 // ~> var f = {|a &k1=v1 &k2=v2| put $a $k1 $k2 } 167 // ~> call $f [foo] [&k1=bar] 168 // ▶ foo 169 // ▶ bar 170 // ▶ v2 171 // ``` 172 173 func call(fm *Frame, fn Callable, argsVal vals.List, optsVal vals.Map) error { 174 args := make([]interface{}, 0, argsVal.Len()) 175 for it := argsVal.Iterator(); it.HasElem(); it.Next() { 176 args = append(args, it.Elem()) 177 } 178 opts := make(map[string]interface{}, optsVal.Len()) 179 for it := optsVal.Iterator(); it.HasElem(); it.Next() { 180 k, v := it.Elem() 181 ks, ok := k.(string) 182 if !ok { 183 return errs.BadValue{What: "option key", 184 Valid: "string", Actual: vals.Kind(k)} 185 } 186 opts[ks] = v 187 } 188 return fn.Call(fm.Fork("-call"), args, opts) 189 } 190 191 //elvdoc:fn resolve 192 // 193 // ```elvish 194 // resolve $command 195 // ``` 196 // 197 // Output what `$command` resolves to in symbolic form. Command resolution is 198 // described in the [language reference](language.html#ordinary-command). 199 // 200 // Example: 201 // 202 // ```elvish-transcript 203 // ~> resolve echo 204 // ▶ <builtin echo> 205 // ~> fn f { } 206 // ~> resolve f 207 // ▶ <closure 0xc4201c24d0> 208 // ~> resolve cat 209 // ▶ <external cat> 210 // ``` 211 212 func resolve(fm *Frame, head string) string { 213 special, fnRef := resolveCmdHeadInternally(fm, head, nil) 214 switch { 215 case special != nil: 216 return "special" 217 case fnRef != nil: 218 return "$" + head + FnSuffix 219 default: 220 return "(external " + parse.Quote(head) + ")" 221 } 222 } 223 224 //elvdoc:fn eval 225 // 226 // ```elvish 227 // eval $code &ns=$nil &on-end=$nil 228 // ``` 229 // 230 // Evaluates `$code`, which should be a string. The evaluation happens in a 231 // new, restricted namespace, whose initial set of variables can be specified by 232 // the `&ns` option. After evaluation completes, the new namespace is passed to 233 // the callback specified by `&on-end` if it is not nil. 234 // 235 // The namespace specified by `&ns` is never modified; it will not be affected 236 // by the creation or deletion of variables by `$code`. However, the values of 237 // the variables may be mutated by `$code`. 238 // 239 // If the `&ns` option is `$nil` (the default), a temporary namespace built by 240 // amalgamating the local and upvalue scopes of the caller is used. 241 // 242 // If `$code` fails to parse or compile, the parse error or compilation error is 243 // raised as an exception. 244 // 245 // Basic examples that do not modify the namespace or any variable: 246 // 247 // ```elvish-transcript 248 // ~> eval 'put x' 249 // ▶ x 250 // ~> var x = foo 251 // ~> eval 'put $x' 252 // ▶ foo 253 // ~> var ns = (ns [&x=bar]) 254 // ~> eval &ns=$ns 'put $x' 255 // ▶ bar 256 // ``` 257 // 258 // Examples that modify existing variables: 259 // 260 // ```elvish-transcript 261 // ~> var y = foo 262 // ~> eval 'set y = bar' 263 // ~> put $y 264 // ▶ bar 265 // ``` 266 // 267 // Examples that creates new variables and uses the callback to access it: 268 // 269 // ```elvish-transcript 270 // ~> eval 'var z = lorem' 271 // ~> put $z 272 // compilation error: variable $z not found 273 // [ttz 2], line 1: put $z 274 // ~> var saved-ns = $nil 275 // ~> eval &on-end={|ns| set saved-ns = $ns } 'var z = lorem' 276 // ~> put $saved-ns[z] 277 // ▶ lorem 278 // ``` 279 // 280 // Note that when using variables from an outer scope, only those 281 // that have been referenced are captured as upvalues (see [closure 282 // semantics](language.html#closure-semantics)) and thus accessible to `eval`: 283 // 284 // ```elvish-transcript 285 // ~> var a b 286 // ~> fn f {|code| nop $a; eval $code } 287 // ~> f 'echo $a' 288 // $nil 289 // ~> f 'echo $b' 290 // Exception: compilation error: variable $b not found 291 // [eval 2], line 1: echo $b 292 // Traceback: [... omitted ...] 293 // ``` 294 295 type evalOpts struct { 296 Ns *Ns 297 OnEnd Callable 298 } 299 300 func (*evalOpts) SetDefaultOptions() {} 301 302 func eval(fm *Frame, opts evalOpts, code string) error { 303 src := parse.Source{Name: fmt.Sprintf("[eval %d]", nextEvalCount()), Code: code} 304 ns := opts.Ns 305 if ns == nil { 306 ns = CombineNs(fm.up, fm.local) 307 } 308 // The stacktrace already contains the line that calls "eval", so we pass 309 // nil as the second argument. 310 newNs, exc := fm.Eval(src, nil, ns) 311 if opts.OnEnd != nil { 312 newFm := fm.Fork("on-end callback of eval") 313 errCb := opts.OnEnd.Call(newFm, []interface{}{newNs}, NoOpts) 314 if exc == nil { 315 return errCb 316 } 317 } 318 return exc 319 } 320 321 // Used to generate unique names for each source passed to eval. 322 var ( 323 evalCount int 324 evalCountMutex sync.Mutex 325 ) 326 327 func nextEvalCount() int { 328 evalCountMutex.Lock() 329 defer evalCountMutex.Unlock() 330 evalCount++ 331 return evalCount 332 } 333 334 //elvdoc:fn use-mod 335 // 336 // ```elvish 337 // use-mod $use-spec 338 // ``` 339 // 340 // Imports a module, and outputs the namespace for the module. 341 // 342 // Most code should use the [use](language.html#importing-modules-with-use) 343 // special command instead. 344 // 345 // Examples: 346 // 347 // ```elvish-transcript 348 // ~> echo 'var x = value' > a.elv 349 // ~> put (use-mod ./a)[x] 350 // ▶ value 351 // ``` 352 353 func useMod(fm *Frame, spec string) (*Ns, error) { 354 return use(fm, spec, nil) 355 } 356 357 func readFileUTF8(fname string) (string, error) { 358 bytes, err := os.ReadFile(fname) 359 if err != nil { 360 return "", err 361 } 362 if !utf8.Valid(bytes) { 363 return "", fmt.Errorf("%s: source is not valid UTF-8", fname) 364 } 365 return string(bytes), nil 366 } 367 368 //elvdoc:fn deprecate 369 // 370 // ```elvish 371 // deprecate $msg 372 // ``` 373 // 374 // Shows the given deprecation message to stderr. If called from a function 375 // or module, also shows the call site of the function or import site of the 376 // module. Does nothing if the combination of the call site and the message has 377 // been shown before. 378 // 379 // ```elvish-transcript 380 // ~> deprecate msg 381 // deprecation: msg 382 // ~> fn f { deprecate msg } 383 // ~> f 384 // deprecation: msg 385 // [tty 19], line 1: f 386 // ~> exec 387 // ~> deprecate msg 388 // deprecation: msg 389 // ~> fn f { deprecate msg } 390 // ~> f 391 // deprecation: msg 392 // [tty 3], line 1: f 393 // ~> f # a different call site; shows deprecate message 394 // deprecation: msg 395 // [tty 4], line 1: f 396 // ~> fn g { f } 397 // ~> g 398 // deprecation: msg 399 // [tty 5], line 1: fn g { f } 400 // ~> g # same call site, no more deprecation message 401 // ``` 402 403 func deprecate(fm *Frame, msg string) { 404 var ctx *diag.Context 405 if fm.traceback.Next != nil { 406 ctx = fm.traceback.Next.Head 407 } 408 fm.Deprecate(msg, ctx, 0) 409 } 410 411 // TimeAfter is used by the sleep command to obtain a channel that is delivered 412 // a value after the specified time. 413 // 414 // It is a variable to allow for unit tests to efficiently test the behavior of 415 // the `sleep` command, both by eliminating an actual sleep and verifying the 416 // duration was properly parsed. 417 var TimeAfter = func(fm *Frame, d time.Duration) <-chan time.Time { 418 return time.After(d) 419 } 420 421 //elvdoc:fn sleep 422 // 423 // ```elvish 424 // sleep $duration 425 // ``` 426 // 427 // Pauses for at least the specified duration. The actual pause duration depends 428 // on the system. 429 // 430 // This only affects the current Elvish context. It does not affect any other 431 // contexts that might be executing in parallel as a consequence of a command 432 // such as [`peach`](#peach). 433 // 434 // A duration can be a simple [number](language.html#number) (with optional 435 // fractional value) without an explicit unit suffix, with an implicit unit of 436 // seconds. 437 // 438 // A duration can also be a string written as a sequence of decimal numbers, 439 // each with optional fraction, plus a unit suffix. For example, "300ms", 440 // "1.5h" or "1h45m7s". Valid time units are "ns", "us" (or "µs"), "ms", "s", 441 // "m", "h". 442 // 443 // Passing a negative duration causes an exception; this is different from the 444 // typical BSD or GNU `sleep` command that silently exits with a success status 445 // without pausing when given a negative duration. 446 // 447 // See the [Go documentation](https://golang.org/pkg/time/#ParseDuration) for 448 // more information about how durations are parsed. 449 // 450 // Examples: 451 // 452 // ```elvish-transcript 453 // ~> sleep 0.1 # sleeps 0.1 seconds 454 // ~> sleep 100ms # sleeps 0.1 seconds 455 // ~> sleep 1.5m # sleeps 1.5 minutes 456 // ~> sleep 1m30s # sleeps 1.5 minutes 457 // ~> sleep -1 458 // Exception: sleep duration must be >= zero 459 // [tty 8], line 1: sleep -1 460 // ``` 461 462 func sleep(fm *Frame, duration interface{}) error { 463 var f float64 464 var d time.Duration 465 466 if err := vals.ScanToGo(duration, &f); err == nil { 467 d = time.Duration(f * float64(time.Second)) 468 } else { 469 // See if it is a duration string rather than a simple number. 470 switch duration := duration.(type) { 471 case string: 472 d, err = time.ParseDuration(duration) 473 if err != nil { 474 return ErrInvalidSleepDuration 475 } 476 default: 477 return ErrInvalidSleepDuration 478 } 479 } 480 481 if d < 0 { 482 return ErrNegativeSleepDuration 483 } 484 485 select { 486 case <-fm.Interrupts(): 487 return ErrInterrupted 488 case <-TimeAfter(fm, d): 489 return nil 490 } 491 } 492 493 //elvdoc:fn time 494 // 495 // ```elvish 496 // time &on-end=$nil $callable 497 // ``` 498 // 499 // Runs the callable, and call `$on-end` with the duration it took, as a 500 // number in seconds. If `$on-end` is `$nil` (the default), prints the 501 // duration in human-readable form. 502 // 503 // If `$callable` throws an exception, the exception is propagated after the 504 // on-end or default printing is done. 505 // 506 // If `$on-end` throws an exception, it is propagated, unless `$callable` has 507 // already thrown an exception. 508 // 509 // Example: 510 // 511 // ```elvish-transcript 512 // ~> time { sleep 1 } 513 // 1.006060647s 514 // ~> time { sleep 0.01 } 515 // 1.288977ms 516 // ~> var t = '' 517 // ~> time &on-end={|x| set t = $x } { sleep 1 } 518 // ~> put $t 519 // ▶ (float64 1.000925004) 520 // ~> time &on-end={|x| set t = $x } { sleep 0.01 } 521 // ~> put $t 522 // ▶ (float64 0.011030208) 523 // ``` 524 525 type timeOpt struct{ OnEnd Callable } 526 527 func (o *timeOpt) SetDefaultOptions() {} 528 529 func timeCmd(fm *Frame, opts timeOpt, f Callable) error { 530 t0 := time.Now() 531 err := f.Call(fm, NoArgs, NoOpts) 532 t1 := time.Now() 533 534 dt := t1.Sub(t0) 535 if opts.OnEnd != nil { 536 newFm := fm.Fork("on-end callback of time") 537 errCb := opts.OnEnd.Call(newFm, []interface{}{dt.Seconds()}, NoOpts) 538 if err == nil { 539 err = errCb 540 } 541 } else { 542 _, errWrite := fmt.Fprintln(fm.ByteOutput(), dt) 543 if err == nil { 544 err = errWrite 545 } 546 } 547 548 return err 549 } 550 551 //elvdoc:fn -ifaddrs 552 // 553 // ```elvish 554 // -ifaddrs 555 // ``` 556 // 557 // Output all IP addresses of the current host. 558 // 559 // This should be part of a networking module instead of the builtin module. 560 561 func _ifaddrs(fm *Frame) error { 562 addrs, err := net.InterfaceAddrs() 563 if err != nil { 564 return err 565 } 566 out := fm.ValueOutput() 567 for _, addr := range addrs { 568 err := out.Put(addr.String()) 569 if err != nil { 570 return err 571 } 572 } 573 return nil 574 }