1 Commits

Author SHA1 Message Date
cuddle-please
7f5c716c7a chore(release): 0.0.6
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-07-26 20:37:13 +00:00
6 changed files with 7 additions and 146 deletions

View File

@@ -6,10 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.1.0] - 2025-08-03 ## [0.0.6] - 2025-07-26
### Added ### Added
- add file opener with chooser
- update demo - update demo
- add help text for preview - add help text for preview

View File

@@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "0.1.0" version = "0.0.6"
[workspace.dependencies] [workspace.dependencies]
noil = { path = "crates/noil" } noil = { path = "crates/noil" }

View File

@@ -1,9 +1,7 @@
use std::path::PathBuf;
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
use crate::{ use crate::{
cli::edit::{ApplyOptions, apply}, cli::edit::apply,
commit::{Action, print_changes}, commit::{Action, print_changes},
}; };
@@ -11,9 +9,6 @@ use crate::{
pub struct ApplyCommand { pub struct ApplyCommand {
#[arg(long = "commit")] #[arg(long = "commit")]
commit: bool, commit: bool,
#[arg(long = "chooser-file", env = "NOIL_CHOOSER_FILE")]
chooser_file: Option<PathBuf>,
} }
impl ApplyCommand { impl ApplyCommand {
@@ -29,15 +24,7 @@ impl ApplyCommand {
let action = print_changes(&input, !self.commit).await?; let action = print_changes(&input, !self.commit).await?;
let res = match action { let res = match action {
Action::Quit => Ok(()), Action::Quit => Ok(()),
Action::Apply { original } => { Action::Apply { original } => apply(&original).await,
apply(
&original,
ApplyOptions {
chooser_file: self.chooser_file.clone(),
},
)
.await
}
Action::Edit => todo!(), Action::Edit => todo!(),
}; };
@@ -45,13 +32,7 @@ impl ApplyCommand {
res res
} else { } else {
apply( apply(&input).await
&input,
ApplyOptions {
chooser_file: self.chooser_file.clone(),
},
)
.await
} }
} }
} }

View File

@@ -23,9 +23,6 @@ const PREVIEW: bool = false;
pub struct EditCommand { pub struct EditCommand {
#[arg()] #[arg()]
path: PathBuf, path: PathBuf,
#[arg(long = "chooser-file", env = "NOIL_CHOOSER_FILE")]
chooser_file: Option<PathBuf>,
} }
impl EditCommand { impl EditCommand {
@@ -92,13 +89,7 @@ impl EditCommand {
match action { match action {
Action::Quit => return Ok(()), Action::Quit => return Ok(()),
Action::Apply { original } => { Action::Apply { original } => {
return apply( return apply(&original).await;
&original,
ApplyOptions {
chooser_file: self.chooser_file.clone(),
},
)
.await;
} }
Action::Edit => continue, Action::Edit => continue,
} }
@@ -116,11 +107,6 @@ async fn wait_user() -> Result<(), anyhow::Error> {
Ok(()) Ok(())
} }
#[derive(Default, Clone, Debug)]
pub struct ApplyOptions {
pub chooser_file: Option<PathBuf>,
}
/// the philosphy behind apply is that we try unlike normal file system operations to be idempotent. /// the philosphy behind apply is that we try unlike normal file system operations to be idempotent.
/// This is mainly for 2 reasons. /// This is mainly for 2 reasons.
/// ///
@@ -128,27 +114,17 @@ pub struct ApplyOptions {
/// 2. A .noil recipe can be rerun, having small issues disrupt the work would be counterproductive, as the .noil language is not powerful enough to handle the flexibility required for file checking /// 2. A .noil recipe can be rerun, having small issues disrupt the work would be counterproductive, as the .noil language is not powerful enough to handle the flexibility required for file checking
/// ///
/// All in all apply is mostly idempotent, and won't override files, it tries to be as non destructive as possible. For example move will only throw a warning if the source file doesn't exists, but the destination does /// All in all apply is mostly idempotent, and won't override files, it tries to be as non destructive as possible. For example move will only throw a warning if the source file doesn't exists, but the destination does
pub async fn apply(input: &str, options: ApplyOptions) -> anyhow::Result<()> { pub async fn apply(input: &str) -> anyhow::Result<()> {
eprintln!("applying changes"); eprintln!("applying changes");
let noil_index = parse::parse_input(input).context("parse input")?; let noil_index = parse::parse_input(input).context("parse input")?;
let mut open_files = Vec::new();
for file in &noil_index.files { for file in &noil_index.files {
let path = &file.path; let path = &file.path;
match &file.entry.operation { match &file.entry.operation {
Operation::Existing { .. } => { Operation::Existing { .. } => {
// Noop // Noop
} }
Operation::Open { .. } => {
if path.to_string_lossy().ends_with("/") {
// We can't open directories, so they're skipped
continue;
}
open_files.push(path);
}
Operation::Add => { Operation::Add => {
tracing::debug!("creating file"); tracing::debug!("creating file");
@@ -254,31 +230,6 @@ pub async fn apply(input: &str, options: ApplyOptions) -> anyhow::Result<()> {
} }
} }
if let Some(chooser_file) = &options.chooser_file {
tracing::debug!("creating chooser file");
if let Some(parent) = chooser_file.parent()
&& !chooser_file.exists()
{
tokio::fs::create_dir_all(parent)
.await
.context("parent dir for chooser file")?;
}
let mut file = tokio::fs::File::create(chooser_file)
.await
.context("create new chooser file")?;
let open_files = open_files
.iter()
.map(|i| i.display().to_string())
.collect::<Vec<_>>();
file.write_all(open_files.join("\n").as_bytes())
.await
.context("write chosen files")?;
file.flush().await.context("flush chosen file")?;
}
Ok(()) Ok(())
} }

View File

@@ -23,9 +23,6 @@ pub(crate) fn format(input: &str) -> anyhow::Result<String> {
| models::Operation::Delete { index } | models::Operation::Delete { index }
| models::Operation::Move { index } | models::Operation::Move { index }
| models::Operation::Existing { index } => index.len(), | models::Operation::Existing { index } => index.len(),
models::Operation::Open { index } => {
index.as_ref().map(|i| i.len()).unwrap_or_default()
}
models::Operation::Add => 0, models::Operation::Add => 0,
}) })
.max() .max()
@@ -56,7 +53,6 @@ pub(crate) fn format(input: &str) -> anyhow::Result<String> {
| models::Operation::Delete { index } | models::Operation::Delete { index }
| models::Operation::Move { index } | models::Operation::Move { index }
| models::Operation::Existing { index } => Some(index), | models::Operation::Existing { index } => Some(index),
models::Operation::Open { index } => index,
models::Operation::Add => None, models::Operation::Add => None,
}; };

View File

@@ -35,7 +35,6 @@ pub enum Operation {
Copy { index: String }, Copy { index: String },
Delete { index: String }, Delete { index: String },
Move { index: String }, Move { index: String },
Open { index: Option<String> },
} }
impl Display for Operation { impl Display for Operation {
@@ -46,7 +45,6 @@ impl Display for Operation {
Operation::Copy { .. } => "COPY", Operation::Copy { .. } => "COPY",
Operation::Delete { .. } => "DELETE", Operation::Delete { .. } => "DELETE",
Operation::Move { .. } => "MOVE", Operation::Move { .. } => "MOVE",
Operation::Open { .. } => "OPEN",
}; };
f.write_str(op) f.write_str(op)
@@ -88,16 +86,6 @@ impl FileEntry {
"D" | "DEL" | "DELETE" if first != last => Operation::Delete { index }, "D" | "DEL" | "DELETE" if first != last => Operation::Delete { index },
// MOVE: // MOVE:
"M" | "MV" | "MOVE" | "RENAME" if first != last => Operation::Move { index }, "M" | "MV" | "MOVE" | "RENAME" if first != last => Operation::Move { index },
"O" | "OPEN" => Operation::Open {
index: {
// if LAST == is the Operation, we set the index to empty, if the index is missing we set it to None
if index.is_empty() || index.chars().any(|c| c.is_uppercase()) {
None
} else {
Some(index)
}
},
},
o => { o => {
anyhow::bail!("operation: {} is not supported", o); anyhow::bail!("operation: {} is not supported", o);
} }
@@ -206,60 +194,6 @@ ADD : /var/my/path/new-long-path
Ok(()) Ok(())
} }
#[test]
fn can_parse_item_open_operation() -> anyhow::Result<()> {
let input = r#"
O abc : /var/my
OPEN ecd : /var/my/path
O : /var/my/path/new-path
OPEN : /var/my/path/new-long-path
"#;
let output = parse::parse_input(input)?;
pretty_assertions::assert_eq!(
Buffer {
files: vec![
File {
path: "/var/my".into(),
entry: FileEntry {
raw_op: Some("O".into()),
operation: Operation::Open {
index: Some("abc".into()),
}
},
},
File {
path: "/var/my/path".into(),
entry: FileEntry {
raw_op: Some("OPEN".into()),
operation: Operation::Open {
index: Some("ecd".into())
}
},
},
File {
path: "/var/my/path/new-path".into(),
entry: FileEntry {
raw_op: Some("O".into()),
operation: Operation::Open { index: None },
},
},
File {
path: "/var/my/path/new-long-path".into(),
entry: FileEntry {
raw_op: Some("OPEN".into()),
operation: Operation::Open { index: None },
}
}
]
},
output
);
Ok(())
}
#[test] #[test]
fn can_parse_item_copy_operation() -> anyhow::Result<()> { fn can_parse_item_copy_operation() -> anyhow::Result<()> {
let input = r#" let input = r#"