runner 0.7.0

Utility for running Rust snippets
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# Running Little Rust Snippets

## Leaving the Comfort (and Restrictions) of Cargo

Cargo is a good, reliable way to build programs and libraries in Rust with versioned dependencies.
Those of us who have worked with the Wild West practices of C++ development find this particularly soothing,
and it's one of the core strengths of the Rust ecosystem.

However, it's not intended to make running little test programs straightforward - you have to
create a project with all the dependencies you wish to play with, and then edit `src/main.rs` and
do `cargo run`. A useful tip is to create a `src/bin` directory containing your little programs
and then use `cargo run --bin NAME` to run them. But there is a better way; if you have such
a project (say called 'cache') then the following invocation will compile and link
a program against those dependencies (`rustc` is an unusually intelligent compiler)

```
$ rustc -L /path/to/cache/target/debug/deps mytest.rs
```

Of course, you need to manually run `cargo build` on your `cache` project whenever new dependencies
are added, or when the compiler is updated.

The `runner` tool helps to automate this pattern. It also supports _snippets_, which
are little 'scripts' formatted like Rust documentation examples.

```
$ cat print.rs
println!("Hello, World!");

$ runner print.rs
Hello, World!
```

The snippet file will be found on `RUNNER_PATH` if defined, e.g. `.:/home/me/myscripts`.

This follows basically the same rules as the doc-test snippets you find in Rust
documentation, so `runner` allows you to copy those snippets into an editor
and directly run them.

You can use `?` in snippets instead of the ubiquitous and awful `unwrap`, since the boilerplate
encloses code in a function that returns `Result<(),Box<Error+Sync+Send>>` which is compatible with
any error return.

A special variable `args` is available containing any arguments passed to the program:

```
$ cat hello.rs
println!("hello {}",args[1]);
$ runner hello.rs dolly
hello dolly
```

You can even - on Unix platforms - add a 'shebang' line to invoke runner (but will still need an .rs extension).

```
$ cat hello.rs
#!/usr/bin/env runner
println!("Hello, World!");
$ chmod a+x hello.rs
$ ./hello.rs
Hello, World!
```

> You can choose for these scripts not to be re-compiled each time with the `-R` flag. Then the
> shebang line becomes `#!/usr/bin/env -S runner -R` and `runner` will compare the script timestamp with
> the executable.

`runner` adds the necessary boilerplate and creates a proper Rust program in `~/.cargo/.runner/bin`,
prefixed with a prelude, which is initially:

```rust
#![allow(unused_imports)]
#![allow(unused_variables)]
#![allow(dead_code)]
#![allow(unused_macros)]
use std::{fs, io, env};
use std::fs::File;
use std::io::prelude::*;
use std::path::{PathBuf, Path};
use std::collections::HashMap;
use std::time::Duration;
use std::thread;

macro_rules! debug {
    ($x:expr) => {
        println!("{} = {:?}",stringify!($x),$x);
    }
}
```

After first invocation of `runner`, this is found in `~/.cargo/.runner/prelude`;
you can edit it later with `runner --edit-prelude`.

If a file `env.rs` exists on `RUNNER_PATH` then its contents will be appended to the program prelude.
This is useful to share constants and small functions among all your scripts.

`debug!` saves typing: `debug!(my_var)` is equivalent to `println!("my_var = {:?}",my_var)`.

`runner` will also do some massaging of `rustc` errors.
They are usually very good, but involve fully qualified type names.
It reduces `std::` references to something simpler.

This is a snippet which a Java programmer would find easy to write - declare that type explicitly,
and assume that the important verb is "set":

```
$ cat testm.rs
let mut map: HashMap<String,String> = HashMap::new();
map.set("hello","dolly");
$  runner testm.rs
error[E0599]: no method named `set` found for type `HashMap<String, String>` in the current scope
  --> /home/steve/.cargo/.runner/bin/testm.rs:24:9
   |
24 |     map.set("hello","dolly");
   |         ^^^
   |
   = help: did you mean `get`?
```

Since we are being very _informal_ with Rust here, it's appropriate that we don't wish the type spelled
out in full glory (as you can see by running with `-S`):
`std::collections::HashMap<std::string::String, std::string::String>`.

## Adding External Crates

As you can see, `runner` is very much about playing with small code snippets. By
default it links the snippet _dynamically_ which is significantly faster.

The static option is much more convenient. You can easily create a static
cache with some common crates:

```
$ runner --add "time json regex"
```

You can add as many crates if you like - number of available dependencies doesn't
slow down the linker. Thereafter, you may refer to these crates in snippets. Note that
by default, `runner` uses 2024 edition since 0.7.0. If you want a particular version, use 'foo=1.0'
notation, and 'foo=1.0/f1,f2' to specify a version plus features.

```rust
// json.rs
use json;

let parsed = json::parse(r#"

{
    "code": 200,
    "success": true,
    "payload": {
        "features": [
            "awesome",
            "easyAPI",
            "lowLearningCurve"
        ]
    }
}

"#) ?;

println!("{}", parsed);
```

And then build statically and run (any extra arguments are passed to the program.)

```json
$ runner -s json.rs
{
  "code": 200,
  "success": true,
  "payload": {
    "features": [
      "awesome",
      "easyAPI",
      "lowLearningCurve"
    ]
  }
}
```

A convenient feature is "argument lines" - if the first line of `json.rs` was

```
//: -s
```

then any `runner` arguments specified after "//:" will be merged in with the command-line arguments.
It is now possible to simply invoke using `runner json.rs`. It's better to keep any special build
instructions in the file itself, and it means that an editor run action bound to `runner FILE` can be
made to work in all cases.

`runner` provides various utilities for managing the static cache.
You can say `runner --edit` to edit the static cache `Cargo.toml`, and `runner --build` to
rebuild the cache afterwards. `runner --update` will update all the dependencies in the
cache, and `runner --update package` will update a _particular_ package - follow this
with `build` as before. (for `--edit` to work on Posix then define `VISUAL` or `EDITOR`
in environment.)

The cache is built in release mode, since the compilation time is not much affected.
Documentation is also built
for the cache, and `runner --doc` will open that documentation in the browser. (It's
always nice to have local docs, especially in bandwidth-starved situations. If you
are in WSL2 install the `wslu` package so the docs will open on the host.)

If you want docs for a specific crate `NAME`, then `runner --doc NAME` will work.
Remember that the Rust documentation generated has a fast offline searchable
index!

The `--crates` command also has an optional argument; without arguments it lists all
he crates known to `runner`, with their versions. With a name, it uses an exact match:

```
$ runner --crates yansi
yansi = "0.3.4"
```

You may provide a number of crate names here; if `--verbose` (`-v`) is specified
then the dependencies of these crates are also listed.

The `-c` flag only compiles the program or snippet, and copies it to `~/.cargo/bin`.
`-r` only runs the program, which must have previously been compiled, either
explicitly with `-c` or implicitly with default operation. `-R` will rerun if changed,
which is more convenient.

Plain Rust source files (which already have `fn main`) are of course supported, but you
will need explicit `extern crate <crate>` statements to bring in any crates from the static cache.

A useful trick - if you want to look at the `Cargo.toml` of a crate in the static cache,
to find out dependencies and features, then this command will open it for you:

```
favorite-editor $(runner -P some-crate)/Cargo.toml
```

## Rust on the Command-line

There are a few Perl-inspired features. The `-e` flag compiles and evaluates an
_expression_. You can use it as an unusually strict desktop calculator:

```
$ runner -e "10 + 20*4.5"
error[E0277]: the trait bound `{integer}: Mul<{float}>` is not satisfied
  --> temp/tmp.rs:20:22
   |
20 |     let res = 10 + 20*4.5;
   |                      ^ no implementation for `{integer} * {float}`
```

Likewise, you have to say `1.2f64.sin()` because `1.2` has ambiguous type.

(Note that the trait `std::ops::Mul` is presented in _simplified form_ by default)

`--expression` is very useful if you quickly want to find out how Rust
will evaluate an expression - we do a debug print for maximum flexibility.

```
$ runner -e 'PathBuf::from("bonzo.dog").extension()'
Some("dog")
```

(This works because we have a `use std::path::PathBuf` in the runner prelude.)

Now, this will not work on Windows
since [quoting](https://stackoverflow.com/questions/7760545/escape-double-quotes-in-parameter)
is seriously baroque. So `runner` re-uses an old trick that some Windows versions of `AWK` used. We can
only use double-quotes for an argument that may contain spaces, but single-quotes within this will
be converted to double-quotes.

```
c:> runner -e "PathBuf::from('bonzo.dog').extension()"
Some("dog")
```

So, in these examples where you need to quote strings in the Rust expression,
remember that it works the other way in Windows.

`-i` (or `--iterator`) evaluates iterator expressions and does a debug
dump of the results:

```
$ runner -i '(0..5).map(|i| (10*i,100*i))'
(0, 0)
(10, 100)
(20, 200)
(30, 300)
(40, 400)
```

Any extra command-line arguments are available for these commands, so:

```
$ runner -i 'env::args().enumerate()' one 'two 2' 3
(0, "/home/steve/.cargo/.runner/bin/tmp")
(1, "one")
(2, "two 2")
(3, "3")
```

And finally `-n` (or `--lines`) evaluates the expression for each line in
standard input (`line` is a special variable):

```
$ echo "hello there" | runner -n 'line.to_uppercase()'
"HELLO THERE"
```

In this case, you would probably would like to display the strings, not use Debug. There
is a `-d` flag to set this. Note that then you have to remember to use an explicit error return
in cases like `runner -de '"20".parse::<i32>()?'`

The `-x` flag (`--extern`) allows you to insert an `extern crate` into your
snippet. This is particularly useful for these one-line shortcuts. For
example, my `easy-shortcuts` crate has a couple of helper functions. Before
running these examples, first `runner --add easy-shortcuts` to load it into the
static crate, and then `runner -C easy-shortcuts` to dynamically compile it.

```
$ runner -xeasy_shortcuts -e 'easy_shortcuts::argn_err(1,"gimme an arg!")' 'an arg'
"an arg"
$ runner -xeasy_shortcuts -e 'easy_shortcuts::argn_err(1,"gimme an arg!")'
/home/steve/.cargo/.runner/bin/tmp error: no argument 1: gimme an arg!
```

This also applies to `--iterator`:

```
$ runner -xeasy_shortcuts -i 'easy_shortcuts::files(".")'
"json.rs"
"print.rs"
```

With long crate names like this, you can define _aliases_:

```
$ runner --alias es=easy_shortcuts
$ runner -xes -e 'es::argn_err(1,"gimme an arg!")'
...
```

By default, `runner -e` does a dynamic link, and there are known limitations.
By also using `--static`, you can evaluate expressions against crates
compiled as static libraries. So, assuming that we have
`time` in the static cache (`runner --add time` will do that for you):

```
$ runner -s -xtime -e "time::now()"
Tm { tm_sec: 34, tm_min: 4, tm_hour: 9, tm_mday: 28, tm_mon: 6, tm_year: 117,
tm_wday: 5, tm_yday: 208, tm_isdst: 0, tm_utcoff: 7200, tm_nsec: 302755857 }
```

'-X' (`--wild`) is like `-x` except it brings all the crate's symbols into scope.
Not something you would overdo in regular code, but it makes for shorter
command lines - the last example becomes (note how short flags can be combined):

```
$ runner -seX time 'Timestamp::now()'
...
```

`-M` (`--macro`) is also like `-x` except it prepends the 'extern crate' with
`#[macro_use]`. Consider the very cool [r4](https://docs.rs/r4) crate which
provides list comprehensions. First load in the static cache with `runner --add r4`,
and then we can say:

```
$ runner -s --macro r4 -i 'iterate![for x in 0..4; yield x]'
0
1
2
3
```

Small snippets like these are faster if the crates can be linked dynamically, so
after `runner -C r4` to build a shared library in the dynamic cast, you can run this
without the `-s`.

```
$ runner -Xto_vec -Mr4 -e 'iterate![for i in 0..2; for j in 0..2; yield (i,j)].to_vec()'
[(0, 0), (0, 1), (1, 0), (1, 1)]
```

(At this point, the command-line is getting sufficiently complicated that you would
be better off with a little snippet that you can edit in a proper editor.)

With `-e`,`-n` or `-i`, you can specify. some initial code with `--prepend`:

```
$  runner -p 'let nums=0..5' -i 'nums.clone().zip(nums.skip(1))'
(0, 1)
(1, 2)
(2, 3)
(3, 4)
```

If you can get away with dynamic linking, then `runner` can make it
easy to test a module interactively. In this way you get much of the
benefit of a fully interactive interpreter (a REPL):

```
$ cat universe.rs
pub fn answer() -> i32 {
    42
}
$ runner -C universe.rs
building crate 'universe' at universe.rs
$ runner -xuniverse -e "universe::answer()"
42
```

This provides a way to get to play with big predefined strings:

```
$ cat > text.rs
pub const TEXT: &str = "possibly very long string";
$ runner -C text.rs
building crate 'text' at text.rs
$ runner -Xtext -e 'TEXT.find("long")'
Some(14)
```

If there is an `env.rs` on the path, then it is a convenient way to provide global properties to
scxripts and expressions:

```
cat env.rs
//: -Xeasy_shortcuts
```

## Compiling Rust Doc Examples

Consider the example for the [filetime](https://docs.rs/filetime) crate:

```rust
use std::fs;
use filetime::FileTime;

let metadata = fs::metadata("runner.rs").unwrap();

let mtime = FileTime::from_last_modification_time( & metadata);
println!("{}", mtime);

let atime = FileTime::from_last_access_time( & metadata);
assert!(mtime < atime);

// Inspect values that can be interpreted across platforms
println!("{}", mtime.seconds_relative_to_1970());
println!("{}", mtime.nanoseconds());

// Print the platform-specific value of seconds
println!("{}", mtime.seconds());
```

After `runner --add filetime`, this crate is in your static cache. And `runner --doc filetime`
will give you its local documentation.

However, it can't be compiled directly, for the reason that `use std::fs` is already in the runner prelude.

So we need to say:

```
$ runner -s --no-prelude filetime.rs
1506778536.945440909s
1506778536
945440909
1506778536
```

Or if you're in a hurry: `runner -sN filetime.rs`.

As always, can always put these arguments in a first comment like so "//: -sN".

## Dymamic Compilation of Crates

It would be good to provide such an experience for the dynamic-link case, since
it is faster. There is in fact a dynamic cache as well but support for linking
against external crates dynamically is very basic. It works fine for crates that
don't have any external depdendencies, e.g. this creates a `libjson.so` in the
dynamic cache:

```
$ runner -C json
```

And then you can run the `json.rs` example without `-s`.

The `--compile` action takes three kinds of arguments:

- a crate name that is already loaded and known to Cargo
- a Cargo directory
- a Rust source file - the crate name is the file name without extension.

Dynamic linking is not a priority for
Rust tooling at the moment. So although it is possible to write a wrapper crate that exposes e.g. `regex`
as a shared library, it is still rather unstable.