Skip to content

Commit

Permalink
Merge pull request #560 from magicwenli/dev-filter-command
Browse files Browse the repository at this point in the history
feat: support command filter on `pueue status`
  • Loading branch information
Nukesor authored Jul 31, 2024
2 parents d6fa9a6 + 91d25d3 commit 3e12a75
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ TLDR: The new task state representation is more verbose but significantly cleane
- Add `pueue reset --groups [group_names]` to allow resetting individual groups. [#482](https://github.com/Nukesor/pueue/issues/482) \
This also refactors the way resets are done internally, resulting in a cleaner code architecture.
- Ability to set the Unix socket permissions through the new `unix_socket_permissions` configuration option. [#544](https://github.com/Nukesor/pueue/pull/544)
- Add `command` filter to `pueue status`. [#524](https://github.com/Nukesor/pueue/issues/524) [#560](https://github.com/Nukesor/pueue/pull/560)
- Allow `pueue status` to order tasks by `enqueue_at`. [#554](https://github.com/Nukesor/pueue/issues/554)

## \[3.4.1\] - 2024-06-04
Expand Down
10 changes: 9 additions & 1 deletion pueue/src/client/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ where:
- column := `id | status | command | label | path | enqueue_at | dependencies | start | end`
- filter := `[filter_column] [filter_op] [filter_value]`
(note: not all columns support all operators, see \"Filter columns\" below.)
- filter_column := `start | end | enqueue_at | status | label`
- filter_column := `status | command | label | start | end | enqueue_at`
- filter_op := `= | != | < | > | %=`
(`%=` means 'contains', as in the test value is a substring of the column value)
- order_by := `order_by [column] [order_direction]`
Expand All @@ -325,6 +325,12 @@ where:
- limit_count := a positive integer
Filter columns:
- `status` supports the operators `=`, `!=`
against test values that are:
- strings like `queued`, `stashed`, `paused`, `running`, `success`, `failed`
- `command`, `label` support the operators `=`, `!=`, `%=`
against test values that are:
- strings like `some text`
- `start`, `end`, `enqueue_at` contain a datetime
which support the operators `=`, `!=`, `<`, `>`
against test values that are:
Expand All @@ -335,6 +341,8 @@ Filter columns:
Examples:
- `status=running`
- `command%=echo`
- `label=mytask`
- `columns=id,status,command status=running start > 2023-05-2112:03:17 order_by command first 5`
The formal syntax is defined here:
Expand Down
31 changes: 31 additions & 0 deletions pueue/src/client/query/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,37 @@ pub fn label(section: Pair<'_, Rule>, query_result: &mut QueryResult) -> Result<
Ok(())
}

/// Parse a filter for the command field.
///
/// This filter syntax is exactly the same as the [label] filter.
/// Only the keyword changed from `label` to `command`.
pub fn command(section: Pair<'_, Rule>, query_result: &mut QueryResult) -> Result<()> {
let mut filter = section.into_inner();
// The first word should be the `command` keyword.
let _command = filter.next().unwrap();

// Get the operator that should be applied in this filter.
// Can be either of [Rule::eq | Rule::neq].
let operator = filter.next().unwrap().as_rule();

// Get the name of the command we should filter for.
let operand = filter.next().unwrap().as_str().to_string();

// Build the command filter function.
let filter_function = Box::new(move |task: &Task| -> bool {
let command = task.command.as_str();
match operator {
Rule::eq => command == operand,
Rule::neq => command != operand,
Rule::contains => command.contains(&operand),
_ => false,
}
});
query_result.filters.push(filter_function);

Ok(())
}

/// Parse a filter for the status field.
///
/// This filter syntax looks like this:
Expand Down
1 change: 1 addition & 0 deletions pueue/src/client/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ pub fn apply_query(query: &str, group: &Option<String>) -> Result<QueryResult> {
Rule::column_selection => column_selection::apply(section, &mut query_result)?,
Rule::datetime_filter => filters::datetime(section, &mut query_result)?,
Rule::label_filter => filters::label(section, &mut query_result)?,
Rule::command_filter => filters::command(section, &mut query_result)?,
Rule::status_filter => filters::status(section, &mut query_result)?,
Rule::order_by_condition => order_by::order_by(section, &mut query_result)?,
Rule::limit_condition => limit::limit(section, &mut query_result)?,
Expand Down
6 changes: 5 additions & 1 deletion pueue/src/client/query/syntax.pest
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ status_filter = { column_status ~ (eq | neq) ~ (status_queued | status_stashed |
label = { ANY* }
label_filter = { column_label ~ ( eq | neq | contains ) ~ label }

// Command filter
command = { ANY* }
command_filter = { column_command ~ ( eq | neq | contains ) ~ command }

// Time related filters
datetime = { ASCII_DIGIT{4} ~ "-" ~ ASCII_DIGIT{2} ~ "-" ~ ASCII_DIGIT{2} ~ ASCII_DIGIT{2} ~ ":" ~ ASCII_DIGIT{2} ~ (":" ~ ASCII_DIGIT{2})? }
date = { ASCII_DIGIT{4} ~ "-" ~ ASCII_DIGIT{2} ~ "-" ~ ASCII_DIGIT{2} }
Expand All @@ -67,4 +71,4 @@ limit_count = { ASCII_DIGIT* }
limit_condition = { (first | last) ~ limit_count }

// ----- The final query syntax -----
query = { SOI ~ column_selection? ~ ( datetime_filter | status_filter | label_filter )*? ~ order_by_condition? ~ limit_condition? ~ EOI }
query = { SOI ~ column_selection? ~ ( datetime_filter | status_filter | label_filter | command_filter )*? ~ order_by_condition? ~ limit_condition? ~ EOI }
60 changes: 57 additions & 3 deletions pueue/tests/client/unit/status_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ use pueue::client::query::{apply_query, Rule};
use pueue_lib::state::PUEUE_DEFAULT_GROUP;
use pueue_lib::task::{Task, TaskResult, TaskStatus};

const TEST_COMMAND_SLEEP: &str = "sleep 60";
const TEST_COMMAND_HELLO: &str = "echo Hello Pueue";

/// A small helper function to reduce a bit of boilerplate.
pub fn build_task() -> Task {
Task::new(
"sleep 60".to_owned(),
TEST_COMMAND_SLEEP.to_owned(),
PathBuf::from("/tmp"),
HashMap::new(),
PUEUE_DEFAULT_GROUP.to_owned(),
Expand Down Expand Up @@ -79,9 +82,10 @@ pub fn test_tasks() -> Vec<Task> {
running.id = 4;
tasks.insert(running.id, running);

// Add two queued tasks
// Add two queued tasks with different command
let mut queued = build_task();
queued.id = 5;
queued.command = TEST_COMMAND_HELLO.to_string();
tasks.insert(queued.id, queued.clone());

// Task 6 depends on task 5
Expand Down Expand Up @@ -321,7 +325,7 @@ async fn order_by_enqueue_at() -> Result<()> {
Ok(())
}

/// Filter tasks by label with the "contains" `%=` filter.
/// Filter tasks by label with the "eq" `=` "ne" `!=` and "contains" `%=`filter.
#[rstest]
#[case("%=", "label", 3)]
#[case("%=", "label-10", 3)]
Expand Down Expand Up @@ -373,3 +377,53 @@ async fn filter_label(

Ok(())
}

/// Filter tasks by command with the "eq" `=` "ne" `!=` and "contains" `%=`filter.
#[rstest]
#[case("=", TEST_COMMAND_SLEEP, 5)]
#[case("!=", TEST_COMMAND_SLEEP, 2)]
#[case("%=", &TEST_COMMAND_SLEEP[..4], 5)]
#[case("=", TEST_COMMAND_HELLO, 2)]
#[case("!=", TEST_COMMAND_HELLO, 5)]
#[case("%=", &TEST_COMMAND_HELLO[..4], 2)]
#[case("!=", "nonexist", 7)]
#[case("%=", "nonexist", 0)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn filter_command(
#[case] operator: &'static str,
#[case] command_filter: &'static str,
#[case] match_count: usize,
) -> Result<()> {
let tasks = test_tasks_with_query(&format!("command{operator}{command_filter}"), &None)?;

for task in tasks.iter() {
let command = task.command.as_str();
if operator == "!=" {
// Make sure the task's command doesn't match the filter.
assert_ne!(
command, command_filter,
"Command '{command}' matched exact filter '{command_filter}'"
);
} else if operator == "%=" {
// Make sure the command contained our filter.
assert!(
command.contains(command_filter),
"Command '{command}' didn't contain filter '{command_filter}'"
);
} else if operator == "=" {
// Make sure the command exactly matches the filter.
assert_eq!(
command, command_filter,
"Command '{command}' didn't match exact filter '{command_filter}'"
);
}
}

assert_eq!(
tasks.len(),
match_count,
"Got a different amount of tasks than expected for the command filter: {command_filter}."
);

Ok(())
}

0 comments on commit 3e12a75

Please sign in to comment.