diff --git a/src/main.rs b/src/main.rs index e531917..faacb8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,7 +150,7 @@ enum Commands { /// Show recent changes to the database History { #[arg(long, value_name = "CONTEXT", action = ArgAction::Set, - help = "Number of lines of redo / undo context", + help = "Number of lines of redo / undo context (0 = infinite)", default_value_t = 5)] context: usize, }, @@ -426,8 +426,8 @@ async fn main() -> Result<()> { } // Given a Valve instance, a table name, a row number, a column name, and an input value, - // fetch the row from the given table with the given row number, such that the value of the - // given column has been replaced with the given input_value. + // fetches the row from the given table with the given row number, such that the value of the + // given column is replaced with the given input_value. async fn fetch_row_with_input_value( valve: &Valve, table: &str, @@ -833,12 +833,13 @@ async fn main() -> Result<()> { _ => undo_history[0].history_id, }; undo_history.reverse(); + let id_width = next_undo.to_string().len(); for undo in &undo_history { if undo.history_id == next_undo { - let line = format!("▲ {} {}", undo.history_id, undo.message); + let line = format!("▲ {:>id_width$} {}", undo.history_id, undo.message); println!("{}", Style::new().bold().paint(line)); } else { - println!(" {} {}", undo.history_id, undo.message); + println!(" {:>id_width$} {}", undo.history_id, undo.message); } } @@ -857,9 +858,9 @@ async fn main() -> Result<()> { // which indicates that nothing can be redone even though there are entries in the // redo stack. if redo.history_id == next_redo && redo.history_id > next_undo { - println!("▼ {} {}", redo.history_id, redo.message); + println!("▼ {:>id_width$} {}", redo.history_id, redo.message); } else { - let line = format!(" {} {}", redo.history_id, redo.message); + let line = format!(" {:>id_width$} {}", redo.history_id, redo.message); // If the history_id under consideration is lower than the next undo, or if // there is a redo operation appearing before this one in the returned results // that has a greater history_id, then this is an orphaned operation that cannot diff --git a/src/toolkit.rs b/src/toolkit.rs index 2f318eb..001b227 100644 --- a/src/toolkit.rs +++ b/src/toolkit.rs @@ -2772,10 +2772,10 @@ pub async fn get_text_row_from_db_tx( valve_row.contents_to_rich_json() } -/// Given a configuration struct, the database kind, a table name, and a [SerdeMap] representing -/// a row in the table, such that all of the column values are represented as strings, use the -/// configuration to find the actual SQL types of each column in the row, and then convert the -/// values of each column from TEXT to the appropriate type, before returning the modified map. +/// Given a Valve configuration, the database kind, a table name, and a [SerdeMap] representing +/// a row in the table, such that all of the column values are represented as strings, uses the +/// configuration to find the actual SQL types of each column in the row, and then converts the +/// value of each column from a string to the appropriate type, before returning the modified map. pub fn correct_row_datatypes( config: &ValveConfig, db_kind: &DbKind, @@ -3884,9 +3884,10 @@ pub async fn get_next_undo_id(pool: &AnyPool) -> Result { } } -/// Given a table name, an [AnyRow] representing the last change to the database, a history id, -/// a row number, a database transaction, and a flag indicating whether the given move should be -/// undone (true) or redone (false), undo or redo the move. +/// Given a table name, an [AnyRow] representing the last change to the database (which is +/// expected to be a move operation), a history id, a row number, a database transaction, and a +/// flag indicating whether the given move should be undone (true) or redone (false), undoes or +/// redoes the move. pub async fn undo_or_redo_move_tx( table: &str, last_change: &AnyRow, diff --git a/src/valve.rs b/src/valve.rs index 3406af0..f769b1c 100644 --- a/src/valve.rs +++ b/src/valve.rs @@ -414,20 +414,20 @@ pub struct ValveRowChange { } impl ValveRowChange { - /// Given a database record representing either an undo or a redo from the history table, - /// return a [ValveRowChange] struct. - pub fn from_undo_or_redo_record(record: &AnyRow) -> Result { - let table: &str = record.get("table"); - let row_number: i64 = record.get("row"); + /// Given a database row representing either an undo or a redo record from the history table, + /// returns a [ValveRowChange] struct containing the same information. + pub fn from_undo_or_redo_record(db_rec: &AnyRow) -> Result { + let table: &str = db_rec.try_get("table")?; + let row_number: i64 = db_rec.try_get("row")?; let row_number = row_number as u32; - let history_id: i32 = record.get("history_id"); + let history_id: i32 = db_rec.try_get("history_id")?; let history_id = history_id as u16; - let from = get_json_object_from_column(&record, "from"); - let to = get_json_object_from_column(&record, "to"); + let from = get_json_object_from_column(&db_rec, "from"); + let to = get_json_object_from_column(&db_rec, "to"); let summary = { - let summary = record.try_get_raw("summary")?; + let summary = db_rec.try_get_raw("summary")?; if !summary.is_null() { - let summary: &str = record.get("summary"); + let summary: &str = db_rec.try_get("summary")?; match serde_json::from_str::(summary) { Ok(SerdeValue::Array(v)) => Some(v), _ => { @@ -448,37 +448,75 @@ impl ValveRowChange { for entry in summary { let column = entry .get("column") + .and_then(|s| s.as_str()) .and_then(|s| Some(s.to_string())) .ok_or(ValveError::InputError("No 'column' found".to_string()))?; let level = entry .get("level") + .and_then(|s| s.as_str()) .and_then(|s| Some(s.to_string())) .ok_or(ValveError::InputError("No 'level' found".to_string()))?; let old_value = entry .get("old_value") + .and_then(|s| s.as_str()) .and_then(|s| Some(s.to_string())) .ok_or(ValveError::InputError("No 'old_value' found".to_string()))?; let value = entry .get("value") + .and_then(|s| s.as_str()) .and_then(|s| Some(s.to_string())) .ok_or(ValveError::InputError("No 'value' found".to_string()))?; let message = entry .get("message") + .and_then(|s| s.as_str()) .and_then(|s| Some(s.to_string())) .ok_or(ValveError::InputError("No 'message' found".to_string()))?; column_changes.push(ValveChange { - column: column.to_string(), - level: level.to_string(), - old_value: old_value.to_string(), - value: value.to_string(), - message: message.to_string(), + column: column, + level: level, + old_value: old_value, + value: value, + message: message, }); } + let message = { + let moves = column_changes + .iter() + .filter(|cc| cc.level.to_lowercase() == "move") + .collect::>(); + if moves.len() == 0 { + format!("Update row {row_number} of '{table}'") + } else if moves.len() == 1 { + format!( + "Move row {row_number} of '{table}' from {before} to {after}", + before = { + if moves[0].old_value == "0" { + "first".to_string() + } else { + format!("after row {}", moves[0].old_value) + } + }, + after = { + if moves[0].value == "0" { + "first".to_string() + } else { + format!("after row {}", moves[0].value) + } + }, + ) + } else { + return Err(ValveError::InputError( + "Summary for move (history ID: {history_id}) contains too many records" + .to_string(), + ) + .into()); + } + }; return Ok(ValveRowChange { history_id: history_id, table: table.to_string(), row: row_number, - message: format!("Update row {row_number} of '{table}'"), + message: message, changes: column_changes, }); }