use std::path::Path;
use crate::containment::PathGuard;
use crate::ops;
use crate::plan::Operation;
use super::{ApplyMode, EditResult};
#[cfg(any(feature = "cli", feature = "files"))]
fn cwd_from_path(path: &Path) -> &Path {
path.parent().unwrap_or_else(|| Path::new("."))
}
#[cfg(any(feature = "cli", feature = "files"))]
fn md_write(
mut op: Operation,
path: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
action: &'static str,
) -> anyhow::Result<EditResult> {
let abs = super::absolute_for_engine(path).map_err(|e| {
crate::fallback::EditError::new(
crate::fallback::EditErrorKind::OperationFailed,
format!("failed to resolve path {}: {e}", path.display()),
)
})?;
rewrite_md_op_path(&mut op, abs.to_string_lossy().as_ref());
let display = path.to_string_lossy();
super::execute_as_edit_result_with_path(
op,
mode,
cwd_from_path(&abs),
guard,
action,
None,
Some(display.as_ref()),
)
}
#[cfg(any(feature = "cli", feature = "files"))]
fn rewrite_md_op_path(op: &mut Operation, abs: &str) {
match op {
Operation::MdReplaceSection { path, .. }
| Operation::MdInsertAfterHeading { path, .. }
| Operation::MdInsertAfterSection { path, .. }
| Operation::MdInsertBeforeHeading { path, .. }
| Operation::MdUpsertBullet { path, .. }
| Operation::MdTableAppend { path, .. }
| Operation::MdMoveSection { path, .. }
| Operation::MdDedupeHeadings { path, .. }
| Operation::MdLintAgents { path, .. } => {
*path = abs.into();
}
_ => {}
}
}
#[cfg(not(any(feature = "cli", feature = "files")))]
fn md_write(
_op: Operation,
path: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
action: &'static str,
) -> anyhow::Result<EditResult> {
use crate::write::WritePolicy;
let path_str = path.to_string_lossy();
let original = crate::files::load_text_strict(path, &path_str)?;
let new_content = match _op {
Operation::MdReplaceSection {
heading, content, ..
} => ops::md::replace_section_in(&original, &heading, &content)
.map_err(|e| e.into_anyhow(&heading))?,
Operation::MdInsertAfterHeading {
heading, content, ..
} => ops::md::insert_after_heading_in(&original, &heading, &content)
.map_err(|e| e.into_anyhow(&heading))?,
Operation::MdInsertAfterSection {
heading, content, ..
} => ops::md::insert_after_section_in(&original, &heading, &content)
.map_err(|e| e.into_anyhow(&heading))?,
Operation::MdInsertBeforeHeading {
heading, content, ..
} => ops::md::insert_before_heading_in(&original, &heading, &content)
.map_err(|e| e.into_anyhow(&heading))?,
Operation::MdUpsertBullet {
heading, bullet, ..
} => ops::md::upsert_bullet_in(&original, &heading, &bullet)
.map_err(|e| e.into_anyhow(&heading))?,
Operation::MdTableAppend { heading, row, .. } => {
let (body_start, body_end) =
ops::md::find_section(&original, &heading).map_err(|e| e.into_anyhow(&heading))?;
return match ops::md::table_append_in(&original, body_start, body_end, &row) {
Ok(new_content) => {
let policy = WritePolicy::default();
let (applied, backup_session) =
super::write_if_apply(path, &new_content, mode, &policy, guard)?;
let mut edit = super::build_edit_result(
&path_str,
original,
new_content,
applied,
action,
None,
);
edit.backup_session = backup_session;
Ok(edit)
}
Err(e) => Err(anyhow::Error::new(crate::exit::InvalidInputError {
msg: format!("{e} under heading {heading:?} in {path_str}"),
})),
};
}
_ => {
return Err(anyhow::Error::new(crate::exit::InvalidInputError {
msg: format!("unsupported md operation in no-cli path for {path_str}"),
}));
}
};
let policy = WritePolicy::default();
let (applied, backup_session) =
super::write_if_apply(path, &new_content, mode, &policy, guard)?;
let mut edit =
super::build_edit_result(&path_str, original, new_content, applied, action, None);
edit.backup_session = backup_session;
Ok(edit)
}
pub fn md_replace_section(
path: &Path,
heading: &str,
content: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::MdReplaceSection {
path: path.to_string_lossy().into(),
heading: heading.into(),
content: content.into(),
};
md_write(op, path, mode, guard, "md.replace_section")
}
pub fn md_upsert_bullet(
path: &Path,
heading: &str,
bullet: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::MdUpsertBullet {
path: path.to_string_lossy().into(),
heading: heading.into(),
bullet: bullet.into(),
};
md_write(op, path, mode, guard, "md.upsert_bullet")
}
pub fn md_table_append(
path: &Path,
heading: &str,
row: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::MdTableAppend {
path: path.to_string_lossy().into(),
heading: heading.into(),
row: row.into(),
};
md_write(op, path, mode, guard, "md.table_append")
}
pub fn md_insert_after_heading(
path: &Path,
heading: &str,
insertion: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::MdInsertAfterHeading {
path: path.to_string_lossy().into(),
heading: heading.into(),
content: insertion.into(),
};
md_write(op, path, mode, guard, "md.insert_after_heading")
}
pub fn md_insert_after_section(
path: &Path,
heading: &str,
insertion: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::MdInsertAfterSection {
path: path.to_string_lossy().into(),
heading: heading.into(),
content: insertion.into(),
};
md_write(op, path, mode, guard, "md.insert_after_section")
}
pub fn md_move_section(
path: &Path,
heading: &str,
position: (&str, &str),
to: Option<&Path>,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let path_str = path.to_string_lossy();
let original = crate::files::load_text_strict(path, &path_str)?;
let dest_content = match to {
Some(dest_path) => {
let d = dest_path.to_string_lossy();
crate::files::load_text_strict(dest_path, &d)?
}
None => original.clone(),
};
let (new_source, new_dest) =
ops::md::move_section_in(&original, heading, &dest_content, position, to.is_none())
.map_err(|e| e.into_anyhow(heading, position.1))?;
let policy = crate::write::WritePolicy::default();
let dest = to.map(|p| p.to_string_lossy().to_string());
let (applied, backup_session) = if let Some(dest_path) = to {
let backup_root = path.parent().unwrap_or_else(|| Path::new("."));
let files: [(&Path, &str); 2] =
[(dest_path, new_dest.as_str()), (path, new_source.as_str())];
super::write_if_apply_many(&files, mode, &policy, guard, backup_root)?
} else {
super::write_if_apply(path, &new_source, mode, &policy, guard)?
};
let mut edit =
super::build_edit_result(&path_str, original, new_source, applied, "md.move", dest);
edit.backup_session = backup_session;
Ok(edit)
}
pub fn md_dedupe_headings(
path: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<(EditResult, Vec<String>)> {
let path_str = path.to_string_lossy();
let original = crate::files::load_text_strict(path, &path_str)?;
let (new_content, removed) = ops::md::dedupe_headings_in(&original);
let policy = crate::write::WritePolicy::default();
let (applied, backup_session) =
super::write_if_apply(path, &new_content, mode, &policy, guard)?;
let mut edit =
super::build_edit_result(&path_str, original, new_content, applied, "md.dedupe", None);
edit.backup_session = backup_session;
Ok((edit, removed))
}
pub use crate::ops::md::LintIssue;
pub fn md_lint_agents(path: &Path) -> anyhow::Result<Vec<LintIssue>> {
let display = path.to_string_lossy();
let content = crate::files::load_text_strict(path, &display)?;
Ok(crate::ops::md::lint_agents_content(&content))
}
pub fn md_insert_before_heading(
path: &Path,
heading: &str,
insertion: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::MdInsertBeforeHeading {
path: path.to_string_lossy().into(),
heading: heading.into(),
content: insertion.into(),
};
md_write(op, path, mode, guard, "md.insert_before_heading")
}