You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.0 KiB

+++ title = "Wire --filter flag into list, ready, and migrate commands" priority = 8 status = "done" ticket_type = "feature" dependencies = ["c2a024"] +++

Summary

Add --filter KEY=VALUE (repeatable) to the list, ready, and migrate CLI commands. Parse filter arguments into a TicketFilter and apply it in each command handler.

Depends on: TicketFilter module (ticket c2a024).

CLI changes (src/main.rs)

Add to Commands::List, Commands::Ready, and Commands::Migrate variants:

/// Filter tickets: key=value pairs (repeatable).
/// Keys: priority, type, status, title.
/// Different keys are ANDed; same key with multiple values is ORed.
/// Values support glob wildcards: status=* matches all statuses.
#[arg(long = "filter", value_name = "KEY=VALUE")]
filter: Vec<String>,

Update the dispatch function to pass filter args through to each handler.

Handler changes

cmd_list(filter_args, json)

let filter = filter::parse_filters(&filter_args)?;
let tickets: Vec<Ticket> = list_tickets(&root).await?
    .into_iter()
    .filter(|t| filter.matches(t))
    .collect();

Note: the default done-exclusion behaviour (ticket for that is separate) will also live here, layered on top of this filter application.

cmd_ready(filter_args, json)

Apply the user filter AFTER the ready check. The ready check (not done + all deps done) is always applied first; the user's filter narrows further within ready tickets.

let filter = filter::parse_filters(&filter_args)?;
// ... build done_ids as before ...
let ready: Vec<Ticket> = all
    .into_iter()
    .filter(|t| {
        t.status \!= Status::Done
            && t.dependencies.iter().all(|dep| done_ids.contains(dep.as_str()))
            && filter.matches(t)
    })
    .collect();

cmd_migrate(filter_args, dry_run, json)

For migrate, the filter selects which tickets are candidates for migration. Tickets not matching the filter are skipped (counted separately, not treated as errors).

Add a skipped field to MigrateReport in store.rs:

pub struct MigrateReport {
    pub updated: usize,
    pub already_current: usize,
    pub skipped: usize,          // NEW: tickets excluded by filter
    pub errors: Vec<(String, String)>,
}

Update migrate_tickets signature in store.rs:

pub async fn migrate_tickets(
    root: &Path,
    dry_run: bool,
    filter: &TicketFilter,
) -> Result<MigrateReport>

Inside the per-file loop, after deserialising the ticket, check filter.matches(&ticket). If false: increment report.skipped and continue to next file.

Update cmd_migrate to parse the filter and pass it to migrate_tickets.

display.rs changes

Update format_migrate_report and format_migrate_report_json to include the skipped count:

Human format:

Migrated  3 tickets.
Current   5 tickets (already up to date).
Skipped   2 tickets (did not match filter).
Errors    1 ticket could not be migrated:
  bad_ticket.json: trailing comma at line 4

Only print the "Skipped" line when skipped > 0.

JSON format: add "skipped": N key to the existing object.

files touched

  • src/main.rsfilter fields on List/Ready/Migrate variants, updated dispatch, updated cmd_list/cmd_ready/cmd_migrate handlers
  • src/store.rsMigrateReport::skipped, migrate_tickets gains filter param
  • src/display.rs — updated format_migrate_report and format_migrate_report_json
  • src/tests.rs — unit tests for updated migrate report formatting
  • tests/integration.rs — integration tests

Integration tests to add (tests/integration.rs)

list filtering:

  • Create tickets: 1 bug/todo, 1 task/in_progress, 1 bug/done. nbd list --filter type=bug shows only the bug tickets (done-exclusion is separate, but this test can use non-done bugs).
  • nbd list --filter status=in_progress shows only in_progress tickets.
  • nbd list --filter status=todo --filter status=in_progress shows both todo and in_progress (OR within same key).
  • nbd list --filter type=bug --filter status=todo shows only bug+todo tickets (AND across keys).
  • nbd list --filter title=*login* shows only tickets whose title contains "login".
  • nbd list --filter status=* matches all statuses (wildcard).
  • nbd list --filter type=unknown exits non-zero with an error (unknown key passes through as a value, but "unknown" does not match any type → empty results, or error? Error on unknown key is preferable).
  • nbd list --filter badformat (no =) exits non-zero with an error.

ready filtering:

  • nbd ready --filter type=bug returns only ready bug tickets.
  • nbd ready --filter priority=8 returns only ready tickets with priority 8.

migrate filtering:

  • Create two tickets. Run nbd migrate --filter status=todo --dry-run. Verify skipped count in JSON output matches tickets not matching the filter.
  • nbd migrate --filter status=todo --json includes skipped key.

error cases:

  • --filter with unknown key exits non-zero.
  • --filter with no = exits non-zero.