rune/cli/
mod.rs

1//! Helper to build customized commandline interfaces using custom rune
2//! contexts.
3//!
4//! This can be used to:
5//! * Generate documentation using types only available in your context.
6//! * Build a language server, which is aware of things only available in your
7//!   context.
8
9mod ace;
10mod benches;
11mod check;
12mod doc;
13mod format;
14mod languageserver;
15mod loader;
16mod naming;
17mod out;
18mod run;
19mod tests;
20mod visitor;
21
22use std::fmt;
23use std::io::{self, IsTerminal, Write};
24use std::path::{Path, PathBuf};
25
26use rust_alloc::string::String;
27use rust_alloc::vec::Vec;
28
29use crate as rune;
30use crate::alloc;
31use crate::alloc::prelude::*;
32use crate::workspace::{self, WorkspaceFilter};
33
34use anyhow::{bail, Context as _, Error, Result};
35use clap::{Parser, Subcommand, ValueEnum};
36use tracing_subscriber::filter::EnvFilter;
37
38use crate::compile::ParseOptionError;
39use crate::modules::capture_io::CaptureIo;
40use crate::termcolor::{ColorChoice, StandardStream};
41use crate::{Context, ContextError, Hash, ItemBuf, Options};
42
43use self::out::{Color, Io, Stream};
44
45/// Default about splash.
46const DEFAULT_ABOUT: &str = "The Rune Language Interpreter";
47
48/// Options for building context.
49#[non_exhaustive]
50pub struct ContextOptions<'a> {
51    /// If we need to capture I/O this is set to the capture instance you should
52    /// be using to do so.
53    pub capture: Option<&'a CaptureIo>,
54    /// If we're running in a test context.
55    pub test: bool,
56}
57
58/// Type used to build a context.
59pub type ContextBuilder = dyn FnMut(ContextOptions<'_>) -> Result<Context, ContextError>;
60
61/// A rune-based entrypoint used for custom applications.
62///
63/// This can be used to construct your own rune-based environment, with a custom
64/// configuration such as your own modules.
65#[derive(Default)]
66pub struct Entry<'a> {
67    about: Option<alloc::String>,
68    context: Option<&'a mut ContextBuilder>,
69}
70
71impl<'a> Entry<'a> {
72    /// Entry point.
73    pub fn new() -> Self {
74        Self::default()
75    }
76
77    /// Set about string used in cli output.
78    ///
79    /// For example, this is the first row outputted when the command prints its
80    /// help text.
81    ///
82    /// # Examples
83    ///
84    /// ```no_run
85    /// rune::cli::Entry::new()
86    ///     .about("My own interpreter")
87    ///     .run();
88    ///```
89    pub fn about(mut self, about: impl fmt::Display) -> Self {
90        self.about = Some(
91            about
92                .try_to_string()
93                .expect("Failed to format about string"),
94        );
95        self
96    }
97
98    /// Configure context to use using a builder.
99    ///
100    /// # Examples
101    ///
102    /// ```no_run
103    /// use rune::{Context, ContextError, Module};
104    ///
105    /// fn my_module() -> Result<Module, ContextError> {
106    ///     let module = Module::default();
107    ///     /* install things into module */
108    ///     Ok(module)
109    /// }
110    ///
111    /// rune::cli::Entry::new()
112    ///     .about("My own interpreter")
113    ///     .context(&mut |opts| {
114    ///         let mut c = Context::with_config(opts.capture.is_none())?;
115    ///         c.install(my_module()?);
116    ///         Ok(c)
117    ///     })
118    ///     .run();
119    ///```
120    pub fn context(mut self, context: &'a mut ContextBuilder) -> Self {
121        self.context = Some(context);
122        self
123    }
124
125    /// Run the configured application.
126    ///
127    /// This will take over stdout and stdin.
128    pub fn run(self) -> ! {
129        let runtime = tokio::runtime::Builder::new_current_thread()
130            .enable_all()
131            .build()
132            .expect("Failed to build runtime");
133
134        match runtime.block_on(self.inner()) {
135            Ok(exit_code) => {
136                std::process::exit(exit_code as i32);
137            }
138            Err(error) => {
139                let o = std::io::stderr();
140                // ignore error because stdout / stderr might've been closed.
141                let _ = format_errors(&mut o.lock(), &error);
142                std::process::exit(ExitCode::Failure as i32);
143            }
144        }
145    }
146
147    /// Run the configured application without starting a new tokio runtime.
148    ///
149    /// This will take over stdout and stdin.
150    pub async fn run_async(self) -> ! {
151        match self.inner().await {
152            Ok(exit_code) => {
153                std::process::exit(exit_code as i32);
154            }
155            Err(error) => {
156                let o = std::io::stderr();
157                // ignore error because stdout / stderr might've been closed.
158                let _ = format_errors(&mut o.lock(), &error);
159                std::process::exit(ExitCode::Failure as i32);
160            }
161        }
162    }
163
164    async fn inner(mut self) -> Result<ExitCode> {
165        let args = match Args::try_parse() {
166            Ok(args) => args,
167            Err(e) => {
168                let about = self.about.as_deref().unwrap_or(DEFAULT_ABOUT);
169
170                let code = if e.use_stderr() {
171                    let o = std::io::stderr();
172                    let mut o = o.lock();
173                    o.write_all(about.as_bytes())?;
174                    writeln!(o)?;
175                    writeln!(o)?;
176                    writeln!(o, "{}", e)?;
177                    o.flush()?;
178                    ExitCode::Failure
179                } else {
180                    let o = std::io::stdout();
181                    let mut o = o.lock();
182                    o.write_all(about.as_bytes())?;
183                    writeln!(o)?;
184                    writeln!(o)?;
185                    writeln!(o, "{}", e)?;
186                    o.flush()?;
187                    ExitCode::Success
188                };
189
190                return Ok(code);
191            }
192        };
193
194        if args.version {
195            let o = std::io::stdout();
196            let mut o = o.lock();
197            let about = self.about.as_deref().unwrap_or(DEFAULT_ABOUT);
198            o.write_all(about.as_bytes())?;
199            o.flush()?;
200            return Ok(ExitCode::Success);
201        }
202
203        let choice = match args.color {
204            ColorArgument::Always => ColorChoice::Always,
205            ColorArgument::Ansi => ColorChoice::AlwaysAnsi,
206            ColorArgument::Auto => {
207                if std::io::stdin().is_terminal() {
208                    ColorChoice::Auto
209                } else {
210                    ColorChoice::Never
211                }
212            }
213            ColorArgument::Never => ColorChoice::Never,
214        };
215
216        let mut stdout = StandardStream::stdout(choice);
217        let mut stderr = StandardStream::stderr(choice);
218
219        let mut io = Io::new(&mut stdout, &mut stderr);
220
221        tracing_subscriber::fmt()
222            .with_env_filter(EnvFilter::from_default_env())
223            .init();
224
225        match main_with_out(&mut io, &mut self, args).await {
226            Ok(code) => Ok(code),
227            Err(error) => {
228                let o = io.with_color(Stream::Stdout, Color::Error)?;
229                format_errors(o, &error)?;
230                o.close()?;
231                Ok(ExitCode::Failure)
232            }
233        }
234    }
235}
236
237/// A single entrypoint that can be built or processed.
238#[derive(TryClone)]
239pub(crate) enum EntryPoint<'a> {
240    /// A plain path entrypoint.
241    Path(PathBuf, bool),
242    /// A package entrypoint.
243    Package(workspace::FoundPackage<'a>),
244}
245
246impl EntryPoint<'_> {
247    /// Path to entrypoint.
248    pub(crate) fn path(&self) -> &Path {
249        match self {
250            EntryPoint::Path(path, _) => path,
251            EntryPoint::Package(p) => &p.found.path,
252        }
253    }
254
255    /// If a path is an additional argument.
256    pub(crate) fn is_argument(&self) -> bool {
257        match self {
258            EntryPoint::Path(_, explicit) => *explicit,
259            EntryPoint::Package(..) => false,
260        }
261    }
262}
263
264impl fmt::Display for EntryPoint<'_> {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        match self {
267            EntryPoint::Path(path, false) => {
268                write!(f, "path in {}", path.display())
269            }
270            EntryPoint::Path(path, true) => {
271                write!(f, "path in {} (argument)", path.display())
272            }
273            EntryPoint::Package(package) => {
274                write!(
275                    f,
276                    "package `{}` in {} ({})",
277                    package.package.name,
278                    package.found.path.display(),
279                    package.found.kind
280                )
281            }
282        }
283    }
284}
285
286#[derive(Parser, Debug, Clone)]
287#[command(rename_all = "kebab-case")]
288struct CommandShared<T>
289where
290    T: clap::Args,
291{
292    #[command(flatten)]
293    shared: SharedFlags,
294    #[command(flatten)]
295    command: T,
296}
297
298impl<T> CommandShared<T>
299where
300    T: CommandBase + clap::Args,
301{
302    /// Construct compiler options from arguments.
303    fn options(&self) -> Result<Options, ParseOptionError> {
304        let mut options = Options::from_default_env()?;
305
306        // Command-specific override defaults.
307        if self.command.is_debug() {
308            options.debug_info(true);
309            options.test(true);
310            options.bytecode(false);
311        }
312
313        for option in &self.shared.compiler_option {
314            options.parse_option(option)?;
315        }
316
317        Ok(options)
318    }
319}
320
321#[derive(Clone, Copy)]
322struct CommandSharedRef<'a> {
323    shared: &'a SharedFlags,
324    command: &'a dyn CommandBase,
325}
326
327impl<'a> CommandSharedRef<'a> {
328    fn find(
329        &self,
330        all_targets: bool,
331        kind: AssetKind,
332        name: Option<&'a str>,
333    ) -> Option<WorkspaceFilter<'a>> {
334        if !all_targets && !self.command.is_workspace(kind) {
335            return None;
336        }
337
338        if let Some(name) = name {
339            return Some(WorkspaceFilter::Name(name));
340        }
341
342        self.shared.is_unfiltered().then_some(WorkspaceFilter::All)
343    }
344
345    #[inline]
346    fn find_bins(&self, all_targets: bool) -> Option<WorkspaceFilter<'a>> {
347        self.find(all_targets, AssetKind::Bin, self.shared.bin.as_deref())
348    }
349
350    #[inline]
351    fn find_tests(&self, all_targets: bool) -> Option<WorkspaceFilter<'a>> {
352        self.find(all_targets, AssetKind::Test, self.shared.test.as_deref())
353    }
354
355    #[inline]
356    fn find_examples(&self, all_targets: bool) -> Option<WorkspaceFilter<'a>> {
357        self.find(all_targets, AssetKind::Bin, self.shared.example.as_deref())
358    }
359
360    #[inline]
361    fn find_benches(&self, all_targets: bool) -> Option<WorkspaceFilter<'a>> {
362        self.find(all_targets, AssetKind::Bench, self.shared.bench.as_deref())
363    }
364}
365
366#[derive(Parser, Debug)]
367#[command(rename_all = "kebab-case")]
368struct HashFlags {
369    /// Generate a random hash.
370    #[arg(long)]
371    random: bool,
372    /// When generating a random hash, generate the given number of hashes.
373    #[arg(long)]
374    count: Option<usize>,
375    /// Items to generate hashes for.
376    item: Vec<String>,
377}
378
379enum AssetKind {
380    Bin,
381    Test,
382    Bench,
383}
384
385trait CommandBase {
386    /// Test if the command should perform a debug build by default.
387    #[inline]
388    fn is_debug(&self) -> bool {
389        false
390    }
391
392    /// Test if the command should acquire workspace assets for the given asset kind.
393    #[inline]
394    fn is_workspace(&self, _: AssetKind) -> bool {
395        false
396    }
397
398    /// Describe the current command.
399    #[inline]
400    fn describe(&self) -> &str {
401        "Running"
402    }
403
404    /// Propagate related flags from command and config.
405    #[inline]
406    fn propagate(&mut self, _: &mut Config, _: &mut SharedFlags) {}
407
408    /// Extra paths to run.
409    #[inline]
410    fn paths(&self) -> &[PathBuf] {
411        &[]
412    }
413}
414
415#[derive(Subcommand, Debug)]
416enum Command {
417    /// Run checks but do not execute
418    Check(CommandShared<check::Flags>),
419    /// Build documentation.
420    Doc(CommandShared<doc::Flags>),
421    /// Build ace autocompletion.
422    Ace(CommandShared<ace::Flags>),
423    /// Run all tests but do not execute
424    Test(CommandShared<tests::Flags>),
425    /// Run the given program as a benchmark
426    Bench(CommandShared<benches::Flags>),
427    /// Run the designated script
428    Run(CommandShared<run::Flags>),
429    /// Format the provided file
430    Fmt(CommandShared<format::Flags>),
431    /// Run a language server.
432    LanguageServer(SharedFlags),
433    /// Helper command to generate type hashes.
434    Hash(HashFlags),
435}
436
437impl Command {
438    const ALL: [&'static str; 9] = [
439        "check",
440        "doc",
441        "ace",
442        "test",
443        "bench",
444        "run",
445        "fmt",
446        "languageserver",
447        "hash",
448    ];
449
450    fn as_command_base_mut(&mut self) -> Option<(&mut SharedFlags, &mut dyn CommandBase)> {
451        let (shared, command): (_, &mut dyn CommandBase) = match self {
452            Command::Check(shared) => (&mut shared.shared, &mut shared.command),
453            Command::Doc(shared) => (&mut shared.shared, &mut shared.command),
454            Command::Ace(shared) => (&mut shared.shared, &mut shared.command),
455            Command::Test(shared) => (&mut shared.shared, &mut shared.command),
456            Command::Bench(shared) => (&mut shared.shared, &mut shared.command),
457            Command::Run(shared) => (&mut shared.shared, &mut shared.command),
458            Command::Fmt(shared) => (&mut shared.shared, &mut shared.command),
459            Command::LanguageServer(..) => return None,
460            Command::Hash(..) => return None,
461        };
462
463        Some((shared, command))
464    }
465
466    fn as_command_shared_ref(&self) -> Option<CommandSharedRef<'_>> {
467        let (shared, command): (_, &dyn CommandBase) = match self {
468            Command::Check(shared) => (&shared.shared, &shared.command),
469            Command::Doc(shared) => (&shared.shared, &shared.command),
470            Command::Ace(shared) => (&shared.shared, &shared.command),
471            Command::Test(shared) => (&shared.shared, &shared.command),
472            Command::Bench(shared) => (&shared.shared, &shared.command),
473            Command::Run(shared) => (&shared.shared, &shared.command),
474            Command::Fmt(shared) => (&shared.shared, &shared.command),
475            Command::LanguageServer(..) => return None,
476            Command::Hash(..) => return None,
477        };
478
479        Some(CommandSharedRef { shared, command })
480    }
481}
482
483enum BuildPath<'a> {
484    /// A plain path entry.
485    Path(&'a Path, bool),
486    /// An entry from the specified package.
487    Package(workspace::FoundPackage<'a>),
488}
489
490#[derive(Default)]
491struct Config {
492    /// Whether the touput has been filtered at all.
493    filtered: bool,
494    /// Whether or not the test module should be included.
495    test: bool,
496    /// Whether or not to use verbose output.
497    verbose: bool,
498    /// Always include all targets.
499    all_targets: bool,
500    /// Manifest root directory.
501    manifest_root: Option<PathBuf>,
502}
503
504#[derive(Default)]
505struct Inputs {
506    /// Loaded build manifest.
507    manifest: workspace::Manifest,
508    /// Immediate found paths.
509    found_paths: alloc::Vec<(PathBuf, bool)>,
510}
511
512impl Inputs {
513    /// Construct build paths from configuration.
514    fn build_paths<'m>(
515        &'m self,
516        cmd: CommandSharedRef<'_>,
517        c: &mut Config,
518    ) -> Result<alloc::Vec<BuildPath<'m>>> {
519        let mut build_paths = alloc::Vec::new();
520
521        if !self.found_paths.is_empty() {
522            build_paths.try_extend(self.found_paths.iter().map(|(p, e)| BuildPath::Path(p, *e)))?;
523
524            if !cmd.shared.workspace {
525                return Ok(build_paths);
526            }
527        }
528
529        if let Some(filter) = cmd.find_bins(c.all_targets) {
530            c.filtered |= !matches!(filter, WorkspaceFilter::All);
531
532            for p in self.manifest.find_bins(filter)? {
533                build_paths.try_push(BuildPath::Package(p))?;
534            }
535        }
536
537        if let Some(filter) = cmd.find_tests(c.all_targets) {
538            c.filtered |= !matches!(filter, WorkspaceFilter::All);
539
540            for p in self.manifest.find_tests(filter)? {
541                build_paths.try_push(BuildPath::Package(p))?;
542            }
543        }
544
545        if let Some(filter) = cmd.find_examples(c.all_targets) {
546            c.filtered |= !matches!(filter, WorkspaceFilter::All);
547
548            for p in self.manifest.find_examples(filter)? {
549                build_paths.try_push(BuildPath::Package(p))?;
550            }
551        }
552
553        if let Some(filter) = cmd.find_benches(c.all_targets) {
554            c.filtered |= !matches!(filter, WorkspaceFilter::All);
555
556            for p in self.manifest.find_benches(filter)? {
557                build_paths.try_push(BuildPath::Package(p))?;
558            }
559        }
560
561        Ok(build_paths)
562    }
563}
564
565impl SharedFlags {
566    /// Setup build context.
567    fn context(
568        &self,
569        entry: &mut Entry<'_>,
570        c: &Config,
571        capture: Option<&CaptureIo>,
572    ) -> Result<Context> {
573        let opts = ContextOptions {
574            capture,
575            test: c.test,
576        };
577
578        let mut context =
579            entry
580                .context
581                .as_mut()
582                .context("Context builder not configured with Entry::context")?(opts)?;
583
584        if let Some(capture) = capture {
585            context.install(crate::modules::capture_io::module(capture)?)?;
586        }
587
588        Ok(context)
589    }
590}
591
592#[derive(Default, Debug, Clone, Copy, ValueEnum)]
593enum ColorArgument {
594    #[default]
595    /// Automatically enable coloring if the output is a terminal.
596    Auto,
597    /// Force ANSI coloring.
598    Ansi,
599    /// Always color using the platform-specific coloring implementation.
600    Always,
601    /// Never color output.
602    Never,
603}
604
605#[derive(Parser, Debug)]
606#[command(name = "rune", about = None)]
607struct Args {
608    /// Print the version of the command.
609    #[arg(long)]
610    version: bool,
611
612    /// Control if output is colored or not.
613    #[arg(short = 'C', long, default_value = "auto")]
614    color: ColorArgument,
615
616    /// The command to execute
617    #[command(subcommand)]
618    cmd: Option<Command>,
619}
620
621#[derive(Parser, Debug, Clone)]
622#[command(rename_all = "kebab-case")]
623struct SharedFlags {
624    /// Recursively load all files if a specified build `<path>` is a directory.
625    #[arg(long, short = 'R')]
626    recursive: bool,
627
628    /// Display warnings.
629    #[arg(long)]
630    warnings: bool,
631
632    /// Display verbose output.
633    #[arg(long)]
634    verbose: bool,
635
636    /// Collect sources to operate over from the workspace.
637    ///
638    /// This is what happens by default, but is disabled in case any `<paths>`
639    /// are specified.
640    #[arg(long)]
641    workspace: bool,
642
643    /// Set the given compiler option (see `--list-options` for available options).
644    #[arg(short = 'O', num_args = 1)]
645    compiler_option: Vec<String>,
646
647    /// List available compiler options.
648    #[arg(long)]
649    list_options: bool,
650
651    /// Run with the following binary from a loaded manifest. This requires a
652    /// `Rune.toml` manifest.
653    #[arg(long)]
654    bin: Option<String>,
655
656    /// Run with the following test from a loaded manifest. This requires a
657    /// `Rune.toml` manifest.
658    #[arg(long)]
659    test: Option<String>,
660
661    /// Run with the following example from a loaded manifest. This requires a
662    /// `Rune.toml` manifest.
663    #[arg(long)]
664    example: Option<String>,
665
666    /// Run with the following benchmark by name from a loaded manifest. This
667    /// requires a `Rune.toml` manifest.
668    #[arg(long)]
669    bench: Option<String>,
670
671    /// Include all targets, and not just the ones which are default for the
672    /// current command.
673    #[arg(long)]
674    all_targets: bool,
675
676    /// Build paths to include in the command.
677    ///
678    /// By default, the tool searches for:
679    ///
680    /// * A `Rune.toml` file in a parent directory, in which case this treated
681    ///   as a workspace.
682    ///
683    /// * In order: `main.rn`, `lib.rn`, `src/main.rn`, `src/lib.rn`,
684    ///   `script/main.rn`, and `script/lib.rn`.
685    #[arg(long)]
686    path: Vec<PathBuf>,
687}
688
689impl SharedFlags {
690    fn is_unfiltered(&self) -> bool {
691        self.bin.is_none()
692            && self.test.is_none()
693            && self.example.is_none()
694            && self.bench.is_none()
695            && self.path.is_empty()
696    }
697}
698
699const SPECIAL_FILES: &[&str] = &[
700    "main.rn",
701    "lib.rn",
702    "src/main.rn",
703    "src/lib.rn",
704    "script/main.rn",
705    "script/lib.rn",
706];
707
708// Our own private ExitCode since std::process::ExitCode is nightly only. Note
709// that these numbers are actually meaningful on Windows, but we don't care.
710#[repr(i32)]
711enum ExitCode {
712    Success = 0,
713    Failure = 1,
714    VmError = 2,
715}
716
717/// Format the given error.
718fn format_errors<O>(o: &mut O, error: &Error) -> io::Result<()>
719where
720    O: ?Sized + io::Write,
721{
722    writeln!(o, "Error: {}", error)?;
723
724    for error in error.chain().skip(1) {
725        writeln!(o, "Caused by: {}", error)?;
726    }
727
728    Ok(())
729}
730
731fn find_manifest() -> Option<(PathBuf, PathBuf)> {
732    let mut path = PathBuf::new();
733
734    loop {
735        let manifest_path = path.join(workspace::MANIFEST_FILE);
736
737        if manifest_path.is_file() {
738            return Some((path, manifest_path));
739        }
740
741        path.push("..");
742
743        if !path.is_dir() {
744            return None;
745        }
746    }
747}
748
749fn populate_config(
750    io: &mut Io<'_>,
751    c: &mut Config,
752    inputs: &mut Inputs,
753    cmd: CommandSharedRef<'_>,
754) -> Result<()> {
755    c.all_targets = cmd.shared.all_targets;
756
757    inputs
758        .found_paths
759        .try_extend(cmd.shared.path.iter().map(|p| (p.clone(), false)))?;
760
761    inputs
762        .found_paths
763        .try_extend(cmd.command.paths().iter().map(|p| (p.clone(), true)))?;
764
765    if !inputs.found_paths.is_empty() && !cmd.shared.workspace {
766        return Ok(());
767    }
768
769    let Some((manifest_root, manifest_path)) = find_manifest() else {
770        for file in SPECIAL_FILES {
771            let path = Path::new(file);
772
773            if path.is_file() {
774                inputs.found_paths.try_push((path.try_to_owned()?, false))?;
775                return Ok(());
776            }
777        }
778
779        let special = SPECIAL_FILES.join(", ");
780
781        bail!(
782            "Could not find `{}` in this or parent directories nor any of the special files: {special}",
783            workspace::MANIFEST_FILE
784        )
785    };
786
787    // When building or running a workspace we need to be more verbose so that
788    // users understand what exactly happens.
789    c.verbose = true;
790    c.manifest_root = Some(manifest_root);
791
792    let mut sources = crate::Sources::new();
793    sources.insert(crate::Source::from_path(manifest_path)?)?;
794
795    let mut diagnostics = workspace::Diagnostics::new();
796
797    let result = workspace::prepare(&mut sources)
798        .with_diagnostics(&mut diagnostics)
799        .build();
800
801    diagnostics.emit(io.stdout, &sources)?;
802    inputs.manifest = result?;
803    Ok(())
804}
805
806async fn main_with_out(io: &mut Io<'_>, entry: &mut Entry<'_>, mut args: Args) -> Result<ExitCode> {
807    let mut c = Config::default();
808    let mut inputs = Inputs::default();
809
810    if let Some((shared, base)) = args.cmd.as_mut().and_then(|c| c.as_command_base_mut()) {
811        base.propagate(&mut c, shared);
812    }
813
814    let Some(cmd) = &args.cmd else {
815        let commands: alloc::String = Command::ALL.into_iter().try_join(", ")?;
816        writeln!(io.stdout, "Expected a subcommand: {commands}")?;
817        return Ok(ExitCode::Failure);
818    };
819
820    let mut entries = alloc::Vec::new();
821
822    if let Some(cmd) = cmd.as_command_shared_ref() {
823        if cmd.shared.list_options {
824            writeln!(
825                io.stdout,
826                "Available compiler options (set with -O <option>=<value>):"
827            )?;
828            writeln!(io.stdout)?;
829
830            for (i, option) in Options::available().iter().enumerate() {
831                if i > 0 {
832                    writeln!(io.stdout)?;
833                }
834
835                io.write(
836                    format_args!("{}", option.key),
837                    Stream::Stdout,
838                    Color::Highlight,
839                )?;
840
841                write!(io.stdout, "={}", option.default)?;
842
843                if option.unstable {
844                    io.write(" (unstable)", Stream::Stdout, Color::Error)?;
845                }
846
847                writeln!(io.stdout, ":")?;
848                writeln!(io.stdout, "    Options: {}", option.options)?;
849                writeln!(io.stdout)?;
850
851                for &line in option.doc {
852                    let line = line.strip_prefix(' ').unwrap_or(line);
853                    writeln!(io.stdout, "    {line}")?;
854                }
855            }
856
857            return Ok(ExitCode::Success);
858        }
859
860        populate_config(io, &mut c, &mut inputs, cmd)?;
861
862        let build_paths = inputs.build_paths(cmd, &mut c)?;
863
864        let what = cmd.command.describe();
865        let verbose = c.verbose;
866        let recursive = cmd.shared.recursive;
867
868        for build_path in build_paths {
869            match build_path {
870                BuildPath::Path(path, explicit) => {
871                    for path in loader::recurse_paths(recursive, path.try_to_owned()?) {
872                        entries.try_push(EntryPoint::Path(path?, explicit))?;
873                    }
874                }
875                BuildPath::Package(p) => {
876                    if verbose {
877                        let mut section = io.section(what, Stream::Stderr, Color::Highlight)?;
878
879                        section.append(format_args!(
880                            " {} `{}` (from {})",
881                            p.found.kind,
882                            p.found.path.display(),
883                            p.package.name
884                        ))?;
885
886                        section.close()?;
887                    }
888
889                    entries.try_push(EntryPoint::Package(p))?;
890                }
891            }
892        }
893    }
894
895    match run_path(io, &c, cmd, entry, entries).await? {
896        ExitCode::Success => (),
897        other => {
898            return Ok(other);
899        }
900    }
901
902    Ok(ExitCode::Success)
903}
904
905/// Run a single path.
906async fn run_path<'p, I>(
907    io: &mut Io<'_>,
908    c: &Config,
909    cmd: &Command,
910    entry: &mut Entry<'_>,
911    entries: I,
912) -> Result<ExitCode>
913where
914    I: IntoIterator<Item = EntryPoint<'p>>,
915{
916    match cmd {
917        Command::Check(f) => {
918            let options = f.options()?;
919
920            for e in entries {
921                let mut options = options.clone();
922
923                if e.is_argument() {
924                    options.function_body = true;
925                }
926
927                match check::run(io, entry, c, &f.command, &f.shared, &options, e.path())? {
928                    ExitCode::Success => (),
929                    other => return Ok(other),
930                }
931            }
932        }
933        Command::Doc(f) => {
934            let options = f.options()?;
935            return doc::run(io, entry, c, &f.command, &f.shared, &options, entries);
936        }
937        Command::Ace(f) => {
938            let options = f.options()?;
939            return ace::run(io, entry, c, &f.command, &f.shared, &options, entries);
940        }
941        Command::Fmt(f) => {
942            let options = f.options()?;
943            return format::run(io, entry, c, entries, &f.command, &f.shared, &options);
944        }
945        Command::Test(f) => {
946            let options = f.options()?;
947
948            match tests::run(io, c, &f.command, &f.shared, &options, entry, entries).await? {
949                ExitCode::Success => (),
950                other => return Ok(other),
951            }
952        }
953        Command::Bench(f) => {
954            let options = f.options()?;
955
956            for e in entries {
957                let mut options = options.clone();
958
959                if e.is_argument() {
960                    options.function_body = true;
961                }
962
963                let capture_io = crate::modules::capture_io::CaptureIo::new();
964                let context = f.shared.context(entry, c, Some(&capture_io))?;
965
966                let load = loader::load(
967                    io,
968                    &context,
969                    &f.shared,
970                    &options,
971                    e.path(),
972                    visitor::Attribute::Bench,
973                )?;
974
975                match benches::run(
976                    io,
977                    &f.command,
978                    &context,
979                    Some(&capture_io),
980                    load.unit,
981                    &load.sources,
982                    &load.functions,
983                )
984                .await?
985                {
986                    ExitCode::Success => (),
987                    other => return Ok(other),
988                }
989            }
990        }
991        Command::Run(f) => {
992            let options = f.options()?;
993            let context = f.shared.context(entry, c, None)?;
994
995            for e in entries {
996                let mut options = options.clone();
997
998                if e.is_argument() {
999                    options.function_body = true;
1000                }
1001
1002                let load = loader::load(
1003                    io,
1004                    &context,
1005                    &f.shared,
1006                    &options,
1007                    e.path(),
1008                    visitor::Attribute::None,
1009                )?;
1010
1011                let entry = if e.is_argument() {
1012                    Hash::EMPTY
1013                } else {
1014                    Hash::type_hash(["main"])
1015                };
1016
1017                match run::run(io, c, &f.command, &context, load.unit, &load.sources, entry).await?
1018                {
1019                    ExitCode::Success => (),
1020                    other => return Ok(other),
1021                }
1022            }
1023        }
1024        Command::LanguageServer(shared) => {
1025            let context = shared.context(entry, c, None)?;
1026            languageserver::run(context).await?;
1027        }
1028        Command::Hash(args) => {
1029            use rand::prelude::*;
1030
1031            if args.random {
1032                for _ in 0..args.count.unwrap_or(1) {
1033                    let mut rand = rand::thread_rng();
1034                    writeln!(io.stdout, "{}", Hash::new(rand.gen::<u64>()))?;
1035                }
1036            }
1037
1038            for item in &args.item {
1039                let item: ItemBuf = item.parse()?;
1040                let hash = Hash::type_hash(&item);
1041                writeln!(io.stdout, "{item} => {hash}")?;
1042            }
1043        }
1044    }
1045
1046    Ok(ExitCode::Success)
1047}