diff --git a/command-signatures/json/docker.json b/command-signatures/json/docker.json index f1c4dcbe..2886b577 100644 --- a/command-signatures/json/docker.json +++ b/command-signatures/json/docker.json @@ -267,8 +267,9 @@ "description": "Create a new container", "args": [ { - "name": "container", - "generatorName": "all_local_images", + "name": "image", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, { @@ -1099,7 +1100,8 @@ "description": "Show the history of an image", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, "options": [ @@ -1632,7 +1634,8 @@ "description": "Remove one or more images", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false, "isVariadic": true }, @@ -2351,7 +2354,7 @@ { "name": "image", "description": "The Docker image to use", - "generatorName": "docker_image_with_tag_and_size", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, { @@ -2364,7 +2367,8 @@ "description": "Save one or more images to a tar archive (streamed to STDOUT by default)", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, "options": [ @@ -2423,7 +2427,8 @@ "description": "View the packaged-based Software Bill Of Materials (SBOM) for an image", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, "options": [ @@ -3111,8 +3116,9 @@ "description": "Create a new container", "args": [ { - "name": "container", - "generatorName": "all_local_images", + "name": "image", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, { @@ -4881,7 +4887,7 @@ { "name": "image", "description": "The Docker image to use", - "generatorName": "docker_image_with_tag_and_size" + "generatorName": "image_with_tags" }, { "name": "command" @@ -5663,7 +5669,8 @@ "description": "Show the history of an image", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, "options": [ @@ -5735,7 +5742,8 @@ "description": "Display detailed information on one or more images", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false, "isVariadic": true }, @@ -5916,7 +5924,8 @@ "description": "Remove one or more images", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false, "isVariadic": true }, @@ -5939,7 +5948,8 @@ "description": "Save one or more images to a tar archive (streamed to STDOUT by default)", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, "options": [ @@ -6779,7 +6789,8 @@ "args": [ { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, { @@ -8759,7 +8770,8 @@ "description": "Remove trust for an image", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, "options": [ @@ -8777,7 +8789,8 @@ "description": "Sign an image", "args": { "name": "image", - "generatorName": "all_local_images", + "description": "The Docker image to use", + "generatorName": "image_with_tags", "skipGeneratorValidation": false }, "options": [ diff --git a/command-signatures/src/generators/docker.rs b/command-signatures/src/generators/docker.rs index 56374087..e29e059e 100644 --- a/command-signatures/src/generators/docker.rs +++ b/command-signatures/src/generators/docker.rs @@ -5,6 +5,73 @@ use warp_completion_metadata::{ GeneratorResultsCollector, IconType, Suggestion, TemplateFilter, }; +/// Post-processes the output for the `image_with_tags` generator. +/// Handles two output modes: +/// - JSON mode (normal): lines are JSON objects from `docker images --format '{{ json . }}'` +/// - Tag mode: lines are space-separated `image:tag ID Size` from a filtered `docker image ls` +fn post_process_image_with_tags(output: &str) -> GeneratorResults { + if output.trim().is_empty() { + return GeneratorResults::default(); + } + + let first_char = output.trim_start().chars().next().unwrap_or(' '); + + if first_char == '{' { + // JSON mode (normal): suggest repository names with metadata + output + .lines() + .filter_map(|line| { + let docker_image_output: Result = serde_json::from_str(line); + if let Ok(docker_image_output) = docker_image_output { + if let (Some(repo), Some(size), Some(tag), Some(id)) = ( + docker_image_output.repository, + docker_image_output.size, + docker_image_output.tag, + docker_image_output.id, + ) { + Some( + Suggestion::with_description( + repo, + format!("{}@{} - {}", id, tag, size), + ) + .with_icon(IconType::DockerImage), + ) + } else { + None + } + } else { + log::info!( + "Unable to deserialize docker image output with err {:?}", + docker_image_output.err().unwrap() + ); + None + } + }) + .collect_unordered_results() + } else { + // Tag mode: lines like "nginx:latest abc123 52MB" + output + .lines() + .filter(|line| !line.is_empty() && !line.contains("")) + .filter_map(|line| { + let parts: Vec<&str> = line.splitn(3, ' ').collect(); + let image_tag = parts.first()?; + if parts.len() >= 3 { + Some( + Suggestion::with_description( + *image_tag, + format!("{} - {}", parts[1], parts[2]), + ) + .with_icon(IconType::DockerImage), + ) + } else { + Some(Suggestion::new(*image_tag).with_icon(IconType::DockerImage)) + } + }) + .collect_unordered_results() + } +} + #[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct DockerContainerOutput { @@ -384,72 +451,27 @@ pub fn generator() -> CommandSignatureGenerators { ), ) .add_generator( - "run_images", - Generator::script( - CommandBuilder::single_command("docker images --format '{{ json . }}'"), - |output| { - if output.trim().is_empty() { - return GeneratorResults::default(); - } + "image_with_tags", + Generator::command_from_tokens( + |tokens, has_trailing_whitespace, _| { + let last_token = if has_trailing_whitespace { + "" + } else { + tokens.last().copied().unwrap_or("") + }; - output - .lines() - .filter_map(|image| { - let docker_image_output: Result = - serde_json::from_str(image); - if let Ok(docker_image_output) = docker_image_output { - if let (Some(repo), Some(size), Some(tag), Some(id)) = ( - docker_image_output.repository, - docker_image_output.size, - docker_image_output.tag, - docker_image_output.id, - ) { - Some( - Suggestion::with_description( - repo, - format!("{}@{} -{}", id, tag, size), - ) - .with_icon(IconType::DockerImage), - ) - } else { - None - } - } else { - log::info!( - "Unable to deserialize docker image output with err {:?}", - docker_image_output.err().unwrap() - ); - None - } - }) - .collect_unordered_results() - }, - ), - ) - .add_generator( - "docker_image_with_tag_and_size", - Generator::script( - CommandBuilder::single_command( - "docker images --format '{{.Repository}} {{.Size}} {{.Tag}} {{.ID}}'", - ), - |output| { - output - .split('\n') - .filter_map(|line| { - let words: Vec<&str> = line.split(' ').collect(); - (words.len() >= 4).then(|| { - let id = words[1]; - let tag = words[2]; - let size = words[3]; - Suggestion::with_description( - words[0], - format!("{}@{} - {}", id, tag, size), - ) - .with_icon(IconType::DockerImage) - }) - }) - .collect_unordered_results() + if let Some(colon_pos) = last_token.rfind(':') { + let image = &last_token[..colon_pos]; + if !image.is_empty() { + return CommandBuilder::single_command(format!( + "docker image ls '{}' --format '{{{{.Repository}}}}:{{{{.Tag}}}} {{{{.ID}}}} {{{{.Size}}}}'" , + image + )); + } + } + CommandBuilder::single_command("docker images --format '{{ json . }}'") }, + post_process_image_with_tags, ), ) .add_filter(