@@ -5,6 +5,73 @@ use warp_completion_metadata::{
55 GeneratorResultsCollector , IconType , Suggestion , TemplateFilter ,
66} ;
77
8+ /// Post-processes the output for the `image_with_tags` generator.
9+ /// Handles two output modes:
10+ /// - JSON mode (normal): lines are JSON objects from `docker images --format '{{ json . }}'`
11+ /// - Tag mode: lines are space-separated `image:tag ID Size` from a filtered `docker image ls`
12+ fn post_process_image_with_tags ( output : & str ) -> GeneratorResults {
13+ if output. trim ( ) . is_empty ( ) {
14+ return GeneratorResults :: default ( ) ;
15+ }
16+
17+ let first_char = output. trim_start ( ) . chars ( ) . next ( ) . unwrap_or ( ' ' ) ;
18+
19+ if first_char == '{' {
20+ // JSON mode (normal): suggest repository names with metadata
21+ output
22+ . lines ( )
23+ . filter_map ( |line| {
24+ let docker_image_output: Result < DockerImageOutput > = serde_json:: from_str ( line) ;
25+ if let Ok ( docker_image_output) = docker_image_output {
26+ if let ( Some ( repo) , Some ( size) , Some ( tag) , Some ( id) ) = (
27+ docker_image_output. repository ,
28+ docker_image_output. size ,
29+ docker_image_output. tag ,
30+ docker_image_output. id ,
31+ ) {
32+ Some (
33+ Suggestion :: with_description (
34+ repo,
35+ format ! ( "{}@{} - {}" , id, tag, size) ,
36+ )
37+ . with_icon ( IconType :: DockerImage ) ,
38+ )
39+ } else {
40+ None
41+ }
42+ } else {
43+ log:: info!(
44+ "Unable to deserialize docker image output with err {:?}" ,
45+ docker_image_output. err( ) . unwrap( )
46+ ) ;
47+ None
48+ }
49+ } )
50+ . collect_unordered_results ( )
51+ } else {
52+ // Tag mode: lines like "nginx:latest abc123 52MB"
53+ output
54+ . lines ( )
55+ . filter ( |line| !line. is_empty ( ) && !line. contains ( "<none>" ) )
56+ . filter_map ( |line| {
57+ let parts: Vec < & str > = line. splitn ( 3 , ' ' ) . collect ( ) ;
58+ let image_tag = parts. first ( ) ?;
59+ if parts. len ( ) >= 3 {
60+ Some (
61+ Suggestion :: with_description (
62+ * image_tag,
63+ format ! ( "{} - {}" , parts[ 1 ] , parts[ 2 ] ) ,
64+ )
65+ . with_icon ( IconType :: DockerImage ) ,
66+ )
67+ } else {
68+ Some ( Suggestion :: new ( * image_tag) . with_icon ( IconType :: DockerImage ) )
69+ }
70+ } )
71+ . collect_unordered_results ( )
72+ }
73+ }
74+
875#[ derive( Debug , serde:: Deserialize ) ]
976#[ serde( rename_all = "PascalCase" ) ]
1077struct DockerContainerOutput {
@@ -384,72 +451,27 @@ pub fn generator() -> CommandSignatureGenerators {
384451 ) ,
385452 )
386453 . add_generator (
387- "run_images" ,
388- Generator :: script (
389- CommandBuilder :: single_command ( "docker images --format '{{ json . }}'" ) ,
390- |output| {
391- if output. trim ( ) . is_empty ( ) {
392- return GeneratorResults :: default ( ) ;
393- }
454+ "image_with_tags" ,
455+ Generator :: command_from_tokens (
456+ |tokens, has_trailing_whitespace, _| {
457+ let last_token = if has_trailing_whitespace {
458+ ""
459+ } else {
460+ tokens. last ( ) . copied ( ) . unwrap_or ( "" )
461+ } ;
394462
395- output
396- . lines ( )
397- . filter_map ( |image| {
398- let docker_image_output: Result < DockerImageOutput > =
399- serde_json:: from_str ( image) ;
400- if let Ok ( docker_image_output) = docker_image_output {
401- if let ( Some ( repo) , Some ( size) , Some ( tag) , Some ( id) ) = (
402- docker_image_output. repository ,
403- docker_image_output. size ,
404- docker_image_output. tag ,
405- docker_image_output. id ,
406- ) {
407- Some (
408- Suggestion :: with_description (
409- repo,
410- format ! ( "{}@{} -{}" , id, tag, size) ,
411- )
412- . with_icon ( IconType :: DockerImage ) ,
413- )
414- } else {
415- None
416- }
417- } else {
418- log:: info!(
419- "Unable to deserialize docker image output with err {:?}" ,
420- docker_image_output. err( ) . unwrap( )
421- ) ;
422- None
423- }
424- } )
425- . collect_unordered_results ( )
426- } ,
427- ) ,
428- )
429- . add_generator (
430- "docker_image_with_tag_and_size" ,
431- Generator :: script (
432- CommandBuilder :: single_command (
433- "docker images --format '{{.Repository}} {{.Size}} {{.Tag}} {{.ID}}'" ,
434- ) ,
435- |output| {
436- output
437- . split ( '\n' )
438- . filter_map ( |line| {
439- let words: Vec < & str > = line. split ( ' ' ) . collect ( ) ;
440- ( words. len ( ) >= 4 ) . then ( || {
441- let id = words[ 1 ] ;
442- let tag = words[ 2 ] ;
443- let size = words[ 3 ] ;
444- Suggestion :: with_description (
445- words[ 0 ] ,
446- format ! ( "{}@{} - {}" , id, tag, size) ,
447- )
448- . with_icon ( IconType :: DockerImage )
449- } )
450- } )
451- . collect_unordered_results ( )
463+ if let Some ( colon_pos) = last_token. rfind ( ':' ) {
464+ let image = & last_token[ ..colon_pos] ;
465+ if !image. is_empty ( ) {
466+ return CommandBuilder :: single_command ( format ! (
467+ "docker image ls '{}' --format '{{{{.Repository}}}}:{{{{.Tag}}}} {{{{.ID}}}} {{{{.Size}}}}'" ,
468+ image
469+ ) ) ;
470+ }
471+ }
472+ CommandBuilder :: single_command ( "docker images --format '{{ json . }}'" )
452473 } ,
474+ post_process_image_with_tags,
453475 ) ,
454476 )
455477 . add_filter (
0 commit comments