Skip to main content

usage/spec/
mount.rs

1use std::fmt::Display;
2
3use kdl::{KdlEntry, KdlNode};
4use serde::Serialize;
5
6use crate::error::Result;
7use crate::spec::context::ParsingContext;
8use crate::spec::helpers::{string_entry, NodeHelper};
9
10#[derive(Debug, Default, Clone, Serialize)]
11#[non_exhaustive]
12pub struct SpecMount {
13    pub run: String,
14    /// Whether a discovered command may take precedence over
15    /// [`Spec::default_subcommand`](crate::Spec::default_subcommand).
16    ///
17    /// Off by default, because resolving a mount runs a process: with a default
18    /// subcommand declared, every word that is not a known command would otherwise
19    /// pay for discovery before falling back — for a task runner, that is a
20    /// subprocess per task invocation. Turn it on when a discovered command should
21    /// win, and accept the cost.
22    pub overrides_default: bool,
23}
24
25impl SpecMount {
26    /// A mount that runs `run` to produce a spec for the subcommands here.
27    pub fn new(run: impl Into<String>) -> Self {
28        Self {
29            run: run.into(),
30            overrides_default: false,
31        }
32    }
33
34    /// The same, but a discovered command outranks the default subcommand.
35    pub fn overriding_default(run: impl Into<String>) -> Self {
36        Self {
37            run: run.into(),
38            overrides_default: true,
39        }
40    }
41}
42
43impl SpecMount {
44    pub(crate) fn parse(ctx: &ParsingContext, node: &NodeHelper) -> Result<Self> {
45        let mut mount = SpecMount::default();
46        for (k, v) in node.props() {
47            match k {
48                "run" => mount.run = v.ensure_string()?,
49                "overrides_default" => mount.overrides_default = v.ensure_bool()?,
50                k => bail_parse!(ctx, v.entry.span(), "unsupported mount key {k}"),
51            }
52        }
53        for child in node.children() {
54            match child.name() {
55                "run" => mount.run = child.arg(0)?.ensure_string()?,
56                "overrides_default" => mount.overrides_default = child.arg(0)?.ensure_bool()?,
57                k => bail_parse!(
58                    ctx,
59                    child.node.name().span(),
60                    "unsupported mount value key {k}"
61                ),
62            }
63        }
64        if mount.run.is_empty() {
65            bail_parse!(ctx, node.span(), "mount run is required")
66        }
67        Ok(mount)
68    }
69    pub fn usage(&self) -> String {
70        format!("mount:{}", self.run)
71    }
72}
73
74impl From<&SpecMount> for KdlNode {
75    fn from(mount: &SpecMount) -> KdlNode {
76        let mut node = KdlNode::new("mount");
77        node.push(string_entry(Some("run"), &mount.run));
78        if mount.overrides_default {
79            node.push(KdlEntry::new_prop("overrides_default", true));
80        }
81        node
82    }
83}
84
85impl Display for SpecMount {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        write!(f, "{}", self.usage())
88    }
89}