Skip to main content

flake_edit/app/commands/
add.rs

1//! `flake-edit add`: append a new input to the flake.
2//!
3//! Three branches: scripted (id + uri), interactive TUI (with
4//! optional prefill), and infer-id (uri only, ID derived from the
5//! parsed [`FlakeRef`]).
6
7use nix_uri::FlakeRef;
8
9use crate::change::{Change, ChangeId};
10use crate::edit::FlakeEdit;
11use crate::tui;
12
13use super::super::editor::Editor;
14use super::super::state::AppState;
15use super::uri::{BuildKind, UriOptions, apply_uri_options, build_uri_change, transform_uri};
16use super::{Error, Result, apply_change};
17
18pub fn add(
19    editor: &Editor,
20    flake_edit: &mut FlakeEdit,
21    state: &AppState,
22    id: Option<String>,
23    uri: Option<String>,
24    no_flake: bool,
25    opts: UriOptions<'_>,
26) -> Result<()> {
27    let change = match (id, uri, state.interactive) {
28        // Both ID and URI provided: non-interactive add.
29        (Some(id_val), Some(uri_str), _) => {
30            build_uri_change(BuildKind::Add { no_flake }, id_val, uri_str, &opts)?
31        }
32        // Interactive: show TUI (with or without prefill).
33        (id, None, true) | (None, id, true) => {
34            add_interactive(editor, state, id.as_deref(), no_flake, &opts)?
35        }
36        // Non-interactive with only one positional arg: infer ID from URI.
37        (Some(uri), None, false) | (None, Some(uri), false) => add_infer_id(uri, no_flake, &opts)?,
38        (None, None, false) => {
39            return Err(Error::NoUri);
40        }
41    };
42
43    apply_change(editor, flake_edit, state, change)
44}
45
46fn add_interactive(
47    editor: &Editor,
48    state: &AppState,
49    prefill_uri: Option<&str>,
50    no_flake: bool,
51    opts: &UriOptions<'_>,
52) -> Result<Change> {
53    let tui_app = tui::App::add("Add", editor.text(), prefill_uri, state.cache_config());
54    let Some(tui::AppResult::Change(tui_change)) = tui::run(tui_app)? else {
55        // User cancelled.
56        return Ok(Change::None);
57    };
58
59    // CLI options override the TUI result.
60    if let Change::Add { id, uri, flake } = tui_change {
61        let final_uri = uri
62            .map(|u| transform_uri(u, opts.ref_or_rev, opts.shallow))
63            .transpose()?;
64        Ok(Change::Add {
65            id,
66            uri: final_uri,
67            flake: flake && !no_flake,
68        })
69    } else {
70        Ok(tui_change)
71    }
72}
73
74/// Builds a `Change::Add` when only the URI is supplied, inferring
75/// the ID from the parsed flake reference.
76fn add_infer_id(uri: String, no_flake: bool, opts: &UriOptions<'_>) -> Result<Change> {
77    let (inferred_id, final_uri) = match uri.parse::<FlakeRef>() {
78        Ok(flake_ref) => {
79            let flake_ref = apply_uri_options(flake_ref, opts.ref_or_rev, opts.shallow);
80            let id = flake_ref.id().map(str::to_owned);
81            (id, flake_ref.into_uri())
82        }
83        Err(_) => (None, uri.clone()),
84    };
85
86    let final_id = inferred_id.ok_or_else(|| Error::CouldNotInferId { uri: uri.clone() })?;
87    let final_id = ChangeId::parse(&final_id).map_err(|source| Error::InvalidInputId {
88        id: final_id,
89        source,
90    })?;
91
92    Ok(Change::Add {
93        id: Some(final_id),
94        uri: Some(final_uri),
95        flake: !no_flake,
96    })
97}