Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 30 additions & 17 deletions command-signatures/json/docker.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
{
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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
},
Expand Down Expand Up @@ -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
},
{
Expand All @@ -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": [
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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
},
{
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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
},
Expand Down Expand Up @@ -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
},
Expand All @@ -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": [
Expand Down Expand Up @@ -6779,7 +6789,8 @@
"args": [
{
"name": "image",
"generatorName": "all_local_images",
"description": "The Docker image to use",
"generatorName": "image_with_tags",
"skipGeneratorValidation": false
},
{
Expand Down Expand Up @@ -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": [
Expand All @@ -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": [
Expand Down
150 changes: 86 additions & 64 deletions command-signatures/src/generators/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DockerImageOutput> = 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("<none>"))
.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 {
Expand Down Expand Up @@ -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<DockerImageOutput> =
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(
Expand Down
Loading