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