~matthiasbeyer/imag

ref: 6bc08b60e6e3a99cc9a263713db33061d63dc964 imag/lib/core/libimagrt/src/runtime.rs -rw-r--r-- 24.3 KiB
6bc08b60Matthias Beyer Merge branch 'update-deps' into master 5 months ago
                                                                                                    
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
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2020 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

use std::path::PathBuf;
use std::process::Command;
use std::env;
use std::process::exit;
use std::io::Stdin;
use std::io::StdoutLock;
use std::borrow::Borrow;

pub use clap::App;
use clap::AppSettings;
use toml::Value;
use toml_query::read::TomlValueReadExt;

use clap::{Arg, ArgMatches};
use anyhow::Context;
use anyhow::Result;
use anyhow::Error;


use crate::configuration::{fetch_config, override_config, InternalConfiguration};
use crate::logger::ImagLogger;
use crate::io::OutputProxy;

use libimagerror::trace::*;
use libimagstore::store::Store;
use libimagstore::storeid::StoreId;
use crate::spec::CliSpec;
use atty;

/// The Runtime object
///
/// This object contains the complete runtime environment of the imag application running.
#[derive(Debug)]
pub struct Runtime<'a> {
    rtp: PathBuf,
    configuration: Option<Value>,
    cli_matches: ArgMatches<'a>,
    store: Store,

    has_output_pipe: bool,
    has_input_pipe: bool,

    input_data: bool,
    output_data: bool,
    blackhole_stdout: bool,
}

impl<'a> Runtime<'a> {

    /// Gets the CLI spec for the program and retreives the config file path (or uses the default on
    /// in $HOME/.imag/config, $XDG_CONFIG_DIR/imag/config or from env("$IMAG_CONFIG")
    /// and builds the Runtime object with it.
    ///
    /// The cli_app object should be initially build with the ::get_default_cli_builder() function.
    pub fn new<C>(cli_app: C) -> Result<Runtime<'a>>
        where C: Clone + CliSpec<'a> + InternalConfiguration
    {
        let matches = cli_app.clone().matches();

        let rtp = get_rtp_match(&matches)?;

        let configpath = matches.value_of("config")
                                .map_or_else(|| rtp.clone(), PathBuf::from);

        debug!("Config path = {:?}", configpath);

        let config = match fetch_config(&configpath)? {
            None => {
                return Err(anyhow!("No configuration file found"))
                    .context(anyhow!("Maybe try to use 'imag-init' to initialize imag?"))
                    .context(anyhow!("Continuing without configuration file"))
                    .context(anyhow!("Cannot instantiate runtime"))
                    .map_err(Error::from);
            },
            Some(mut config) => {
                if let Err(e) = override_config(&mut config, get_override_specs(&matches)) {
                    error!("Could not apply config overrides");
                    trace_error(&e);
                }

                Some(config)
            }
        };

        Runtime::_new(cli_app, matches, config)
    }

    /// Builds the Runtime object using the given `config`.
    pub fn with_configuration<C>(cli_app: C, config: Option<Value>) -> Result<Runtime<'a>>
        where C: Clone + CliSpec<'a> + InternalConfiguration
    {
        let matches = cli_app.clone().matches();
        Runtime::_new(cli_app, matches, config)
    }

    fn _new<C>(cli_app: C, matches: ArgMatches<'a>, config: Option<Value>) -> Result<Runtime<'a>>
    where C: Clone + CliSpec<'a> + InternalConfiguration
    {
        if cli_app.enable_logging() {
            Runtime::init_logger(&matches, config.as_ref())
        }

        let rtp = get_rtp_match(&matches)?;

        let storepath = matches.value_of("storepath")
                                .map_or_else(|| {
                                    let mut spath = rtp.clone();
                                    spath.push("store");
                                    spath
                                }, PathBuf::from);

        debug!("RTP path    = {:?}", rtp);
        debug!("Store path  = {:?}", storepath);
        debug!("CLI         = {:?}", matches);
        trace!("Config      = {:#?}", config);

        let store_result = if cli_app.use_inmemory_fs() {
            Store::new_inmemory(storepath, &config)
        } else {
            Store::new(storepath, &config)
        };

        let has_output_pipe  = !atty::is(atty::Stream::Stdout);
        let has_input_pipe   = !atty::is(atty::Stream::Stdin);
        let input_data       = matches.is_present("input-pipe-data");
        let output_data      = matches.is_present("output-pipe-data");
        let blackhole_stdout = matches.is_present("blackhole-stdout");

        debug!("has output pipe  = {}", has_output_pipe);
        debug!("has input pipe   = {}", has_input_pipe);
        debug!("input pipe data  = {}", input_data);
        debug!("output pipe data = {}", output_data);
        debug!("blackhole output = {}", blackhole_stdout);

        store_result.map(|store| Runtime {
            cli_matches: matches,
            configuration: config,
            rtp,
            store,

            has_output_pipe,
            has_input_pipe,
            input_data,
            output_data,
            blackhole_stdout,
        })
        .context(anyhow!("Cannot instantiate runtime"))
        .map_err(Error::from)
    }

    ///
    /// Get a commandline-interface builder object from `clap`
    ///
    /// This commandline interface builder object already contains some predefined interface flags:
    ///   * -v | --verbose for verbosity
    ///   * --debug for debugging
    ///   * -c <file> | --config <file> for alternative configuration file
    ///   * -r <path> | --rtp <path> for alternative runtimepath
    ///   * --store <path> for alternative store path
    /// Each has the appropriate help text included.
    ///
    /// The `appname` shall be "imag-<command>".
    ///
    pub fn get_default_cli_builder(appname: &'a str,
                                   version: &'a str,
                                   about: &'a str)
        -> App<'a, 'a>
    {
        App::new(appname)
            .version(version)
            .author("Matthias Beyer <mail@beyermatthias.de>")
            .about(about)
            .settings(&[AppSettings::AllowExternalSubcommands])
            .arg(Arg::with_name("verbosity")
                .short("v")
                .long("verbose")
                .help("Set log level")
                .required(false)
                .takes_value(true)
                .possible_values(&["trace", "debug", "info", "warn", "error"])
                .value_name("LOGLEVEL"))

            .arg(Arg::with_name("debugging")
                .long("debug")
                .help("Enables debugging output. Shortcut for '--verbose debug'")
                .required(false)
                .takes_value(false))

            .arg(Arg::with_name("no-color-output")
                .long("no-color")
                .help("Disable color output")
                .required(false)
                .takes_value(false))

            .arg(Arg::with_name("config")
                .long("config")
                .help("Path to alternative config file")
                .required(false)
                .validator(::libimagutil::cli_validators::is_existing_path)
                .takes_value(true))

            .arg(Arg::with_name("config-override")
                 .long("override-config")
                 .help("Override a configuration settings. Use 'key=value' pairs, where the key is a path in the TOML configuration. The value must be present in the configuration and be convertible to the type of the configuration setting. If the argument does not contain a '=', it gets ignored. Setting Arrays and Tables is not yet supported.")
                 .required(false)
                 .multiple(true)
                 .takes_value(true))

            .arg(Arg::with_name("runtimepath")
                .long("rtp")
                .help("Alternative runtimepath")
                .required(false)
                .validator(::libimagutil::cli_validators::is_directory)
                .takes_value(true))

            .arg(Arg::with_name("storepath")
                .long("store")
                .help("Alternative storepath. Must be specified as full path, can be outside of the RTP")
                .required(false)
                .validator(::libimagutil::cli_validators::is_directory)
                .takes_value(true))

            .arg(Arg::with_name("editor")
                .long("editor")
                .help("Set editor")
                .required(false)
                .takes_value(true))

            .arg(Arg::with_name("input-pipe-data")
                 .long("pipe-input")
                 .short("I")
                 .required(false)
                 .takes_value(false)
                 .multiple(false)
                 .help("Do not expect imag ids on stdin if stdin is a pipe.")
                 )

            .arg(Arg::with_name("output-pipe-data")
                 .long("pipe-output")
                 .short("O")
                 .required(false)
                 .takes_value(false)
                 .multiple(false)
                 .help("Do not print imag ids to stdout if stdout is a pipe.")
            )

            .arg(Arg::with_name("blackhole-stdout")
                 .long("blackhole")
                 .short("B")
                 .required(false)
                 .takes_value(false)
                 .multiple(false)
                 .help("Do not print normal output")
                 .long_help(indoc!(r#"
                 This flag can be used to ignore output.
                 For example, if imag is used in a piping context, which pipes the Store IDs, the
                 normal output is automatically redirected to stderr. To ignore the normal output
                 instead, this flag can be passed.
                 "#))
             )

    }

    /// Extract the Store object from the Runtime object, destroying the Runtime object
    ///
    /// # Warning
    ///
    /// This function is for testing _only_! It can be used to re-build a Runtime object with an
    /// alternative Store.
    #[cfg(feature = "testing")]
    pub fn extract_store(self) -> Store {
        self.store
    }

    /// Re-set the Store object within
    ///
    /// # Warning
    ///
    /// This function is for testing _only_! It can be used to re-build a Runtime object with an
    /// alternative Store.
    #[cfg(feature = "testing")]
    pub fn with_store(mut self, s: Store) -> Self {
        self.store = s;
        self
    }

    #[cfg(feature = "pub_logging_initialization")]
    pub fn init_logger(matches: &ArgMatches, config: Option<&Value>) {
        Self::_init_logger(matches, config)
    }
    #[cfg(not(feature = "pub_logging_initialization"))]
    fn init_logger(matches: &ArgMatches, config: Option<&Value>) {
        Self::_init_logger(matches, config)
    }

    /// Initialize the internal logger
    ///
    /// If the environment variable "IMAG_LOG_ENV" is set, this simply
    /// initializes a env-logger instance. Errors are ignored in this case.
    /// If the environment variable is not set, this initializes the internal imag logger. On
    /// error, this exits (as there is nothing we can do about that)
    fn _init_logger(matches: &ArgMatches, config: Option<&Value>) {
        use log::set_max_level;
        use log::set_boxed_logger;
        use std::env::var as env_var;

        if env_var("IMAG_LOG_ENV").is_ok() {
            let _ = env_logger::try_init();
        } else {
            let logger = ImagLogger::new(matches, config)
                .map_err_trace()
                .unwrap_or_else(|_| exit(1));

            set_max_level(logger.global_loglevel().to_level_filter());

            // safe debug output for later, after the instance itself
            // is moved away
            #[allow(unused)]
            let logger_expl = format!("{:?}", logger);

            set_boxed_logger(Box::new(logger))
                .map_err(|e| panic!("Could not setup logger: {:?}", e))
                .ok();

            trace!("Imag Logger = {}", logger_expl);
        }
    }

    /// Get the verbosity flag value
    pub fn is_verbose(&self) -> bool {
        self.cli_matches.is_present("verbosity")
    }

    /// Get the debugging flag value
    pub fn is_debugging(&self) -> bool {
        self.cli_matches.is_present("debugging")
    }

    /// Get the runtimepath
    pub fn rtp(&self) -> &PathBuf {
        &self.rtp
    }

    /// Get the commandline interface matches
    pub fn cli(&self) -> &ArgMatches {
        &self.cli_matches
    }

    pub fn ids<T: IdPathProvider>(&self) -> Result<Option<Vec<StoreId>>> {
        use std::io::Read;

        if self.has_input_pipe {
            trace!("Getting IDs from stdin...");
            let stdin    = ::std::io::stdin();
            let mut lock = stdin.lock();

            let mut buf = String::new();
            lock.read_to_string(&mut buf)
                .context("Failed to read stdin to buffer")
                .map_err(Error::from)
                .and_then(|_| {
                    trace!("Got IDs = {}", buf);
                    buf.lines()
                        .map(PathBuf::from)
                        .map(|id| StoreId::new(id).map_err(Error::from))
                        .collect()
                })
                .map(Some)
        } else {
            Ok(T::get_ids(self.cli())?)
        }
    }

    /// Get the configuration object
    pub fn config(&self) -> Option<&Value> {
        self.configuration.as_ref()
    }

    /// Get the store object
    pub fn store(&self) -> &Store {
        &self.store
    }

    /// Get a editor command object which can be called to open the $EDITOR
    pub fn editor(&self) -> Result<Option<Command>> {
        self.cli()
            .value_of("editor")
            .map(String::from)
            .ok_or_else(|| {
                self.config()
                    .ok_or_else(|| anyhow!("No Configuration!"))
                    .and_then(|v| match v.read("rt.editor")? {
                        Some(&Value::String(ref s)) => Ok(Some(s.clone())),
                        Some(_) => Err(anyhow!("Type error at 'rt.editor', expected 'String'")),
                        None    => Ok(None),
                    })
            })
            .or_else(|_| env::var("EDITOR"))
            .map_err(Error::from)
            .and_then(|s| {
                debug!("Editing with '{}'", s);
                let mut split = s.split_whitespace();
                let command   = split.next();
                if command.is_none() {
                    return Ok(None)
                }
                let mut c = Command::new(command.unwrap()); // secured above
                c.args(split);
                c.stdin(::std::fs::File::open("/dev/tty")?);
                c.stderr(::std::process::Stdio::inherit());
                Ok(Some(c))
            })
    }

    pub fn output_is_pipe(&self) -> bool {
        self.has_output_pipe
    }

    pub fn input_is_pipe(&self) -> bool {
        self.has_input_pipe
    }

    pub fn output_is_blackholed(&self) -> bool {
        self.blackhole_stdout
    }

    /// Check whether the runtime expects imag ids from stdin
    pub fn ids_from_stdin(&self) -> bool {
        self.input_is_pipe() && !self.input_data
    }

    /// Check whether the runtime allows data to be piped into the program
    pub fn input_data_pipe(&self) -> bool {
        self.input_is_pipe() && self.input_data
    }

    /// Check whether the runtime allows data to be piped into the program
    pub fn output_data_pipe(&self) -> bool {
        self.output_is_pipe() && self.output_data
    }

    pub fn stdout(&self) -> OutputProxy {
        if self.output_is_pipe() && self.output_data {
            OutputProxy::Out(::std::io::stdout())
        } else if self.output_is_blackholed() {
            OutputProxy::Sink
        } else {
            OutputProxy::Err(::std::io::stderr())
        }
    }

    pub fn stderr(&self) -> OutputProxy {
        OutputProxy::Err(::std::io::stderr())
    }

    pub fn stdin(&self) -> Option<Stdin> {
        if self.input_is_pipe() && self.input_data {
            Some(::std::io::stdin())
        } else {
            None
        }
    }

    /// Helper for handling subcommands which are not available.
    ///
    /// # Example
    ///
    /// For example someone calls `imag foo bar`. If `imag-foo` is in the $PATH, but it has no
    /// subcommand `bar`, the `imag-foo` binary is able to automatically forward the invokation to a
    /// `imag-foo-bar` binary which might be in $PATH.
    ///
    /// It needs to call `Runtime::handle_unknown_subcommand` with the following parameters:
    ///
    /// 1. The "command" which was issued. In the example this would be `"imag-foo"`
    /// 2. The "subcommand" which is missing: `"bar"` in the example
    /// 3. The `ArgMatches` object from the call, so that this routine can forward all flags passed
    ///    to the `bar` subcommand.
    ///
    /// # Warning
    ///
    /// If, and only if, the subcommand does not exist (as in `::std::io::ErrorKind::NotFound`),
    /// this function exits with 1 as exit status.
    ///
    /// # Return value
    ///
    /// On success, the exit status object of the `Command` invocation is returned.
    ///
    /// # Details
    ///
    /// The `IMAG_RTP` variable is set for the child process. It is set to the current runtime path.
    ///
    /// Stdin, stdout and stderr are inherited to the child process.
    ///
    /// This function **blocks** until the child returns.
    ///
    pub fn handle_unknown_subcommand<S: AsRef<str>>(&self,
                                                    command: S,
                                                    subcommand: S,
                                                    args: &ArgMatches)
        -> Result<::std::process::ExitStatus>
    {
        use std::io::Write;
        use std::io::ErrorKind;

        let rtp_str = self.rtp()
            .to_str()
            .map(String::from)
            .ok_or_else(|| anyhow!("UTF8 Error: Runtimepath is not valid UTF8"))?;

        let command = format!("{}-{}", command.as_ref(), subcommand.as_ref());

        let subcommand_args = args.values_of("")
            .map(|sx| sx.map(String::from).collect())
            .unwrap_or_else(|| vec![]);

        Command::new(&command)
            .stdin(::std::process::Stdio::inherit())
            .stdout(::std::process::Stdio::inherit())
            .stderr(::std::process::Stdio::inherit())
            .args(&subcommand_args[..])
            .env("IMAG_RTP", rtp_str)
            .spawn()
            .and_then(|mut c| c.wait())
            .map_err(|e| match e.kind() {
                ErrorKind::NotFound => {
                    let mut out = self.stdout();

                    if let Err(e) = writeln!(out, "No such command: '{}'", command) {
                        return e;
                    }
                    if let Err(e) = writeln!(out, "See 'imag --help' for available subcommands") {
                        return e;
                    }

                    ::std::process::exit(1)
                },
                _ => e,
            })
            .map_err(Error::from)
    }

    pub fn report_touched(&self, id: &StoreId) -> Result<()> {
        let out      = ::std::io::stdout();
        let mut lock = out.lock();

        self.report_touched_id(id, &mut lock).map(|_| ())
    }

    pub fn report_all_touched<ID, I>(&self, ids: I) -> Result<()>
        where ID: Borrow<StoreId> + Sized,
              I: Iterator<Item = ID>
    {
        let out      = ::std::io::stdout();
        let mut lock = out.lock();

        for id in ids {
            if !self.report_touched_id(id.borrow(), &mut lock)? {
                break
            }
        }

        Ok(())
    }

    #[inline]
    fn report_touched_id(&self, id: &StoreId, output: &mut StdoutLock) -> Result<bool> {
        use std::io::Write;

        if self.output_is_pipe() && !self.output_data {
            trace!("Reporting: {} to {:?}", id, output);
            if let Err(e) = writeln!(output, "{}", id) {
                return if e.kind() == std::io::ErrorKind::BrokenPipe {
                    Ok(false)
                } else {
                    Err(Error::from(e))
                }
            }
        }

        Ok(true)
    }
}

pub trait IntoTouchIterator<E, F>
    where Self: Iterator<Item = E> + Sized,
          E: Sized,
          F: Fn(&E) -> Option<StoreId>
{
    fn report_entries_touched<'a>(self, rt: &'a Runtime<'a>, func: F) -> TouchIterator<'a, Self, E, F> {
        TouchIterator {
            inner: self,
            rt,
            func
        }
    }
}

impl<I, E, F> IntoTouchIterator<E, F> for I
    where I: Iterator<Item = E>,
          E: Sized,
          F: Fn(&E) -> Option<StoreId>
{
    // default implementation
}

pub struct TouchIterator<'a, I, E, F>
    where I: Iterator<Item = E>,
          E: Sized,
          F: Fn(&E) -> Option<StoreId>
{
    inner: I,
    rt: &'a Runtime<'a>,
    func: F
}

impl<'a, I, E, F> Iterator for TouchIterator<'a, I, E, F>
    where I: Iterator<Item = E>,
          E: Sized,
          F: Fn(&E) -> Option<StoreId>
{
    type Item = Result<E>;

    fn next(&mut self) -> Option<Self::Item> {
        while let Some(next) = self.inner.next() {
            match (self.func)(&next) {
                Some(id) => if let Err(e) = self.rt.report_touched(&id) {
                    return Some(Err(e))
                } else {
                    return Some(Ok(next))
                },

                None => continue,
            }
        }

        None
    }
}

/// A trait for providing ids from clap argument matches
///
/// This trait can be implement on a type so that it can provide IDs when given a ArgMatches
/// object.
/// It can be used with Runtime::ids(), and libimagrt handles "stdin-provides-ids" cases
/// automatically:
///
/// ```ignore
/// runtime.ids::<PathProvider>()?.iter().for_each(|id| /* ... */)
/// ```
///
/// libimagrt does not call the PathProvider if the ids are provided by piping to stdin.
///
///
/// # Passed arguments
///
/// The arguments which are passed into the IdPathProvider::get_ids() function are the _top level
/// ArgMatches_. Traversing might be required in the implementation of the ::get_ids() function.
///
///
/// # Returns
///
/// In case no store ids could be matched, the function should return `Ok(None)` instance.
/// On success, the StoreId objects to operate on are returned from the ArgMatches.
/// Otherwise, an appropriate error may be returned.
///
pub trait IdPathProvider {
    fn get_ids(matches: &ArgMatches) -> Result<Option<Vec<StoreId>>>;
}

/// Exported for the `imag` command, you probably do not want to use that.
pub fn get_rtp_match<'a>(matches: &ArgMatches<'a>) -> Result<PathBuf> {
    if let Some(p) = matches
        .value_of("runtimepath")
        .map(PathBuf::from)
    {
        return Ok(p)
    }

    match env::var("IMAG_RTP").map(PathBuf::from) {
        Ok(p) => return Ok(p),
        Err(env::VarError::NotUnicode(_)) => {
            return Err(anyhow!("Environment variable 'IMAG_RTP' does not contain valid Unicode"))
        },
        Err(env::VarError::NotPresent) => { /* passthrough */ }
    }

    env::var("HOME")
        .map(PathBuf::from)
        .map(|mut p| { p.push(".imag"); p })
        .map_err(|e| match e {
            env::VarError::NotUnicode(_) => {
                anyhow!("Environment variable 'HOME' does not contain valid Unicode")
            },
            env::VarError::NotPresent => {
                anyhow!("You seem to be $HOME-less. Please get a $HOME before using this \
                    software. We are sorry for you and hope you have some \
                    accommodation anyways.")
            }
        })
}

fn get_override_specs(matches: &ArgMatches) -> Vec<String> {
    matches
        .values_of("config-override")
        .map(|values| {
             values
             .filter(|s| {
                 let b = s.contains('=');
                 if !b { warn!("override '{}' does not contain '=' - will be ignored!", s); }
                 b
             })
             .map(String::from)
             .collect()
        })
        .unwrap_or_else(|| vec![])
}