github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/builtin_fn_stream.go (about) 1 package eval 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/markusbkk/elvish/pkg/eval/errs" 8 "github.com/markusbkk/elvish/pkg/eval/vals" 9 ) 10 11 // Stream manipulation. 12 13 func init() { 14 addBuiltinFns(map[string]interface{}{ 15 "all": all, 16 "one": one, 17 18 "take": take, 19 "drop": drop, 20 21 "count": count, 22 23 "order": order, 24 }) 25 } 26 27 //elvdoc:fn all 28 // 29 // ```elvish 30 // all $inputs? 31 // ``` 32 // 33 // Takes [value inputs](#value-inputs), and outputs those values unchanged. 34 // 35 // This is an [identity 36 // function](https://en.wikipedia.org/wiki/Identity_function) for the value 37 // channel; in other words, `a | all` is equivalent to just `a` if `a` only has 38 // value output. 39 // 40 // This command can be used inside output capture (i.e. `(all)`) to turn value 41 // inputs into arguments. For example: 42 // 43 // ```elvish-transcript 44 // ~> echo '["foo","bar"] ["lorem","ipsum"]' | from-json 45 // ▶ [foo bar] 46 // ▶ [lorem ipsum] 47 // ~> echo '["foo","bar"] ["lorem","ipsum"]' | from-json | put (all)[0] 48 // ▶ foo 49 // ▶ lorem 50 // ``` 51 // 52 // The latter pipeline is equivalent to the following: 53 // 54 // ```elvish-transcript 55 // ~> put (echo '["foo","bar"] ["lorem","ipsum"]' | from-json)[0] 56 // ▶ foo 57 // ▶ lorem 58 // ``` 59 // 60 // In general, when `(all)` appears in the last command of a pipeline, it is 61 // equivalent to just moving the previous commands of the pipeline into `()`. 62 // The choice is a stylistic one; the `(all)` variant is longer overall, but can 63 // be more readable since it allows you to avoid putting an excessively long 64 // pipeline inside an output capture, and keeps the data flow within the 65 // pipeline. 66 // 67 // Putting the value capture inside `[]` (i.e. `[(all)]`) is useful for storing 68 // all value inputs in a list for further processing: 69 // 70 // ```elvish-transcript 71 // ~> fn f { var inputs = [(all)]; put $inputs[1] } 72 // ~> put foo bar baz | f 73 // ▶ bar 74 // ``` 75 // 76 // The `all` command can also take "inputs" from an iterable argument. This can 77 // be used to flatten lists or strings (although not recursively): 78 // 79 // ```elvish-transcript 80 // ~> all [foo [lorem ipsum]] 81 // ▶ foo 82 // ▶ [lorem ipsum] 83 // ~> all foo 84 // ▶ f 85 // ▶ o 86 // ▶ o 87 // ``` 88 // 89 // This can be used together with `(one)` to turn a single iterable value in the 90 // pipeline into its elements: 91 // 92 // ```elvish-transcript 93 // ~> echo '["foo","bar","lorem"]' | from-json 94 // ▶ [foo bar lorem] 95 // ~> echo '["foo","bar","lorem"]' | from-json | all (one) 96 // ▶ foo 97 // ▶ bar 98 // ▶ lorem 99 // ``` 100 // 101 // When given byte inputs, the `all` command currently functions like 102 // [`from-lines`](#from-lines), although this behavior is subject to change: 103 // 104 // ```elvish-transcript 105 // ~> print "foo\nbar\n" | all 106 // ▶ foo 107 // ▶ bar 108 // ``` 109 // 110 // @cf one 111 112 func all(fm *Frame, inputs Inputs) error { 113 out := fm.ValueOutput() 114 var errOut error 115 inputs(func(v interface{}) { 116 if errOut != nil { 117 return 118 } 119 errOut = out.Put(v) 120 }) 121 return errOut 122 } 123 124 //elvdoc:fn one 125 // 126 // ```elvish 127 // one $inputs? 128 // ``` 129 // 130 // Takes exactly one [value input](#value-inputs) and outputs it. If there are 131 // more than one value inputs, raises an exception. 132 // 133 // This function can be used in a similar way to [`all`](#all), but is a better 134 // choice when you expect that there is exactly one output. 135 // 136 // @cf all 137 138 func one(fm *Frame, inputs Inputs) error { 139 var val interface{} 140 n := 0 141 inputs(func(v interface{}) { 142 if n == 0 { 143 val = v 144 } 145 n++ 146 }) 147 if n == 1 { 148 return fm.ValueOutput().Put(val) 149 } 150 return errs.ArityMismatch{What: "values", ValidLow: 1, ValidHigh: 1, Actual: n} 151 } 152 153 //elvdoc:fn take 154 // 155 // ```elvish 156 // take $n $inputs? 157 // ``` 158 // 159 // Outputs the first `$n` [value inputs](#value-inputs). If `$n` is larger than 160 // the number of value inputs, outputs everything. 161 // 162 // Examples: 163 // 164 // ```elvish-transcript 165 // ~> range 2 | take 10 166 // ▶ 0 167 // ▶ 1 168 // ~> take 3 [a b c d e] 169 // ▶ a 170 // ▶ b 171 // ▶ c 172 // ~> use str 173 // ~> str:split ' ' 'how are you?' | take 1 174 // ▶ how 175 // ``` 176 // 177 // Etymology: Haskell. 178 // 179 // @cf drop 180 181 func take(fm *Frame, n int, inputs Inputs) error { 182 out := fm.ValueOutput() 183 var errOut error 184 i := 0 185 inputs(func(v interface{}) { 186 if errOut != nil { 187 return 188 } 189 if i < n { 190 errOut = out.Put(v) 191 } 192 i++ 193 }) 194 return errOut 195 } 196 197 //elvdoc:fn drop 198 // 199 // ```elvish 200 // drop $n $inputs? 201 // ``` 202 // 203 // Ignores the first `$n` [value inputs](#value-inputs) and outputs the rest. 204 // If `$n` is larger than the number of value inputs, outputs nothing. 205 // 206 // Example: 207 // 208 // ```elvish-transcript 209 // ~> range 10 | drop 8 210 // ▶ (num 8) 211 // ▶ (num 9) 212 // ~> range 2 | drop 10 213 // ~> drop 2 [a b c d e] 214 // ▶ c 215 // ▶ d 216 // ▶ e 217 // ~> use str 218 // ~> str:split ' ' 'how are you?' | drop 1 219 // ▶ are 220 // ▶ 'you?' 221 // ``` 222 // 223 // Etymology: Haskell. 224 // 225 // @cf take 226 227 func drop(fm *Frame, n int, inputs Inputs) error { 228 out := fm.ValueOutput() 229 var errOut error 230 i := 0 231 inputs(func(v interface{}) { 232 if errOut != nil { 233 return 234 } 235 if i >= n { 236 errOut = out.Put(v) 237 } 238 i++ 239 }) 240 return errOut 241 } 242 243 //elvdoc:fn count 244 // 245 // ```elvish 246 // count $input-list? 247 // ``` 248 // 249 // Count the number of inputs. 250 // 251 // Examples: 252 // 253 // ```elvish-transcript 254 // ~> count lorem # count bytes in a string 255 // ▶ 5 256 // ~> count [lorem ipsum] 257 // ▶ 2 258 // ~> range 100 | count 259 // ▶ 100 260 // ~> seq 100 | count 261 // ▶ 100 262 // ``` 263 264 // The count implementation uses a custom varargs based implementation rather 265 // than the more common `Inputs` API (see pkg/eval/go_fn.go) because this 266 // allows the implementation to be O(1) for the common cases rather than O(n). 267 func count(fm *Frame, args ...interface{}) (int, error) { 268 var n int 269 switch nargs := len(args); nargs { 270 case 0: 271 // Count inputs. 272 fm.IterateInputs(func(interface{}) { 273 n++ 274 }) 275 case 1: 276 // Get length of argument. 277 v := args[0] 278 if len := vals.Len(v); len >= 0 { 279 n = len 280 } else { 281 err := vals.Iterate(v, func(interface{}) bool { 282 n++ 283 return true 284 }) 285 if err != nil { 286 return 0, fmt.Errorf("cannot get length of a %s", vals.Kind(v)) 287 } 288 } 289 default: 290 // The error matches what would be returned if the `Inputs` API was 291 // used. See GoFn.Call(). 292 return 0, errs.ArityMismatch{What: "arguments", ValidLow: 0, ValidHigh: 1, Actual: nargs} 293 } 294 return n, nil 295 } 296 297 //elvdoc:fn order 298 // 299 // ```elvish 300 // order &reverse=$false $less-than=$nil $inputs? 301 // ``` 302 // 303 // Outputs the [value inputs](#value-inputs) sorted 304 // in ascending order. The sorting process is guaranteed to be 305 // [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability). 306 // 307 // The `&reverse` option, if true, reverses the order of output. 308 // 309 // The `&less-than` option, if given, establishes the ordering of the elements. 310 // Its value should be a function that takes two arguments and outputs a single 311 // boolean indicating whether the first argument is less than the second 312 // argument. If the function throws an exception, `order` rethrows the exception 313 // without outputting any value. 314 // 315 // If `&less-than` has value `$nil` (the default if not set), it is equivalent 316 // to `{|a b| eq -1 (compare $a $b) }`. 317 // 318 // Examples: 319 // 320 // ```elvish-transcript 321 // ~> put foo bar ipsum | order 322 // ▶ bar 323 // ▶ foo 324 // ▶ ipsum 325 // ~> order [(float64 10) (float64 1) (float64 5)] 326 // ▶ (float64 1) 327 // ▶ (float64 5) 328 // ▶ (float64 10) 329 // ~> order [[a b] [a] [b b] [a c]] 330 // ▶ [a] 331 // ▶ [a b] 332 // ▶ [a c] 333 // ▶ [b b] 334 // ~> order &reverse [a c b] 335 // ▶ c 336 // ▶ b 337 // ▶ a 338 // ~> order &less-than={|a b| eq $a x } [l x o r x e x m] 339 // ▶ x 340 // ▶ x 341 // ▶ x 342 // ▶ l 343 // ▶ o 344 // ▶ r 345 // ▶ e 346 // ▶ m 347 // ``` 348 // 349 // Beware that strings that look like numbers are treated as strings, not 350 // numbers. To sort strings as numbers, use an explicit `&less-than` option: 351 // 352 // ```elvish-transcript 353 // ~> order [5 1 10] 354 // ▶ 1 355 // ▶ 10 356 // ▶ 5 357 // ~> order &less-than={|a b| < $a $b } [5 1 10] 358 // ▶ 1 359 // ▶ 5 360 // ▶ 10 361 // ``` 362 // 363 // @cf compare 364 365 type orderOptions struct { 366 Reverse bool 367 LessThan Callable 368 } 369 370 func (opt *orderOptions) SetDefaultOptions() {} 371 372 func order(fm *Frame, opts orderOptions, inputs Inputs) error { 373 var values []interface{} 374 inputs(func(v interface{}) { values = append(values, v) }) 375 376 var errSort error 377 var lessFn func(i, j int) bool 378 if opts.LessThan != nil { 379 lessFn = func(i, j int) bool { 380 if errSort != nil { 381 return true 382 } 383 var args []interface{} 384 if opts.Reverse { 385 args = []interface{}{values[j], values[i]} 386 } else { 387 args = []interface{}{values[i], values[j]} 388 } 389 outputs, err := fm.CaptureOutput(func(fm *Frame) error { 390 return opts.LessThan.Call(fm, args, NoOpts) 391 }) 392 if err != nil { 393 errSort = err 394 return true 395 } 396 if len(outputs) != 1 { 397 errSort = errs.BadValue{ 398 What: "output of the &less-than callback", 399 Valid: "a single boolean", 400 Actual: fmt.Sprintf("%d values", len(outputs))} 401 return true 402 } 403 if b, ok := outputs[0].(bool); ok { 404 return b 405 } 406 errSort = errs.BadValue{ 407 What: "output of the &less-than callback", 408 Valid: "boolean", Actual: vals.Kind(outputs[0])} 409 return true 410 } 411 } else { 412 // Use default comparison implemented by cmp. 413 lessFn = func(i, j int) bool { 414 if errSort != nil { 415 return true 416 } 417 o := cmp(values[i], values[j]) 418 if o == uncomparable { 419 errSort = ErrUncomparable 420 return true 421 } 422 if opts.Reverse { 423 return o == more 424 } 425 return o == less 426 } 427 } 428 429 sort.SliceStable(values, lessFn) 430 431 if errSort != nil { 432 return errSort 433 } 434 out := fm.ValueOutput() 435 for _, v := range values { 436 err := out.Put(v) 437 if err != nil { 438 return err 439 } 440 } 441 return nil 442 }