@@ -6,11 +6,13 @@ use std::fs::File;
66use std:: io:: Read ;
77use std:: io:: Seek ;
88use std:: io:: SeekFrom ;
9+ use std:: path:: Path ;
910use std:: path:: PathBuf ;
1011use std:: sync:: Arc ;
1112
1213use flatbuffers:: root;
1314use itertools:: Itertools ;
15+ use serde:: Serialize ;
1416use vortex:: buffer:: Alignment ;
1517use vortex:: buffer:: ByteBuffer ;
1618use vortex:: error:: VortexExpect ;
@@ -36,6 +38,10 @@ pub struct InspectArgs {
3638
3739 /// Path to the Vortex file to inspect
3840 pub file : PathBuf ,
41+
42+ /// Output as JSON
43+ #[ arg( long, global = true ) ]
44+ pub json : bool ,
3945}
4046
4147#[ derive( Debug , clap:: Subcommand ) ]
@@ -50,15 +56,189 @@ pub enum InspectMode {
5056 Footer ,
5157}
5258
59+ #[ derive( Serialize ) ]
60+ pub struct InspectOutput {
61+ pub file_path : String ,
62+ pub file_size : u64 ,
63+ pub eof : EofInfoJson ,
64+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
65+ pub postscript : Option < PostscriptInfoJson > ,
66+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
67+ pub footer : Option < FooterInfoJson > ,
68+ }
69+
70+ #[ derive( Serialize ) ]
71+ pub struct EofInfoJson {
72+ pub version : u16 ,
73+ pub current_version : u16 ,
74+ pub postscript_size : u16 ,
75+ pub magic_bytes : String ,
76+ pub valid_magic : bool ,
77+ }
78+
79+ #[ derive( Serialize ) ]
80+ pub struct SegmentInfoJson {
81+ pub offset : u64 ,
82+ pub length : u32 ,
83+ pub alignment : usize ,
84+ }
85+
86+ #[ derive( Serialize ) ]
87+ pub struct PostscriptInfoJson {
88+ pub dtype : Option < SegmentInfoJson > ,
89+ pub layout : SegmentInfoJson ,
90+ pub statistics : Option < SegmentInfoJson > ,
91+ pub footer : SegmentInfoJson ,
92+ }
93+
94+ #[ derive( Serialize ) ]
95+ pub struct FooterInfoJson {
96+ pub total_segments : usize ,
97+ pub total_data_size : u64 ,
98+ pub segments : Vec < FooterSegmentJson > ,
99+ }
100+
101+ #[ derive( Serialize ) ]
102+ pub struct FooterSegmentJson {
103+ pub index : usize ,
104+ pub offset : u64 ,
105+ pub end_offset : u64 ,
106+ pub length : u32 ,
107+ pub alignment : usize ,
108+ pub path : Option < String > ,
109+ }
110+
53111pub async fn exec_inspect ( args : InspectArgs ) -> anyhow:: Result < ( ) > {
54112 let mut inspector = VortexInspector :: new ( args. file . clone ( ) ) ?;
55113
56- println ! ( "File: {}" , args. file. display( ) ) ;
114+ let mode = args. mode . unwrap_or ( InspectMode :: Footer ) ;
115+
116+ if args. json {
117+ exec_inspect_json ( & mut inspector, & args. file , mode) . await
118+ } else {
119+ exec_inspect_text ( & mut inspector, & args. file , mode) . await
120+ }
121+ }
122+
123+ async fn exec_inspect_json (
124+ inspector : & mut VortexInspector ,
125+ file_path : & Path ,
126+ mode : InspectMode ,
127+ ) -> anyhow:: Result < ( ) > {
128+ let eof = inspector. read_eof ( ) ?;
129+ let eof_json = EofInfoJson {
130+ version : eof. version ,
131+ current_version : VERSION ,
132+ postscript_size : eof. postscript_size ,
133+ magic_bytes : std:: str:: from_utf8 ( & eof. magic_bytes )
134+ . unwrap_or ( "<invalid utf8>" )
135+ . to_string ( ) ,
136+ valid_magic : eof. valid_magic ,
137+ } ;
138+
139+ let postscript_json =
140+ if matches ! ( mode, InspectMode :: Postscript | InspectMode :: Footer ) && eof. valid_magic {
141+ inspector
142+ . read_postscript ( eof. postscript_size )
143+ . ok ( )
144+ . map ( |ps| PostscriptInfoJson {
145+ dtype : ps. dtype . map ( |s| SegmentInfoJson {
146+ offset : s. offset ,
147+ length : s. length ,
148+ alignment : * s. alignment ,
149+ } ) ,
150+ layout : SegmentInfoJson {
151+ offset : ps. layout . offset ,
152+ length : ps. layout . length ,
153+ alignment : * ps. layout . alignment ,
154+ } ,
155+ statistics : ps. statistics . map ( |s| SegmentInfoJson {
156+ offset : s. offset ,
157+ length : s. length ,
158+ alignment : * s. alignment ,
159+ } ) ,
160+ footer : SegmentInfoJson {
161+ offset : ps. footer . offset ,
162+ length : ps. footer . length ,
163+ alignment : * ps. footer . alignment ,
164+ } ,
165+ } )
166+ } else {
167+ None
168+ } ;
169+
170+ let footer_json =
171+ if matches ! ( mode, InspectMode :: Footer ) && eof. valid_magic && postscript_json. is_some ( ) {
172+ inspector. read_footer ( ) . await . ok ( ) . map ( |footer| {
173+ let segment_map = footer. segment_map ( ) . clone ( ) ;
174+ let root_layout = footer. layout ( ) . clone ( ) ;
175+
176+ let mut segment_paths: Vec < Option < Vec < Arc < str > > > > = vec ! [ None ; segment_map. len( ) ] ;
177+ let mut queue =
178+ VecDeque :: < ( Vec < Arc < str > > , LayoutRef ) > :: from_iter ( [ ( Vec :: new ( ) , root_layout) ] ) ;
179+ while !queue. is_empty ( ) {
180+ let ( path, layout) = queue. pop_front ( ) . vortex_expect ( "queue is not empty" ) ;
181+ for segment in layout. segment_ids ( ) {
182+ segment_paths[ * segment as usize ] = Some ( path. clone ( ) ) ;
183+ }
184+ if let Ok ( children) = layout. children ( ) {
185+ for ( child_layout, child_name) in
186+ children. into_iter ( ) . zip ( layout. child_names ( ) )
187+ {
188+ let child_path = path. iter ( ) . cloned ( ) . chain ( [ child_name] ) . collect ( ) ;
189+ queue. push_back ( ( child_path, child_layout) ) ;
190+ }
191+ }
192+ }
193+
194+ let segments: Vec < FooterSegmentJson > = segment_map
195+ . iter ( )
196+ . enumerate ( )
197+ . map ( |( i, segment) | FooterSegmentJson {
198+ index : i,
199+ offset : segment. offset ,
200+ end_offset : segment. offset + segment. length as u64 ,
201+ length : segment. length ,
202+ alignment : * segment. alignment ,
203+ path : segment_paths[ i]
204+ . as_ref ( )
205+ . map ( |p| p. iter ( ) . map ( |s| s. as_ref ( ) ) . collect :: < Vec < _ > > ( ) . join ( "." ) ) ,
206+ } )
207+ . collect ( ) ;
208+
209+ FooterInfoJson {
210+ total_segments : segment_map. len ( ) ,
211+ total_data_size : segment_map. iter ( ) . map ( |s| s. length as u64 ) . sum ( ) ,
212+ segments,
213+ }
214+ } )
215+ } else {
216+ None
217+ } ;
218+
219+ let output = InspectOutput {
220+ file_path : file_path. display ( ) . to_string ( ) ,
221+ file_size : inspector. file_size ,
222+ eof : eof_json,
223+ postscript : postscript_json,
224+ footer : footer_json,
225+ } ;
226+
227+ let json_output = serde_json:: to_string_pretty ( & output) ?;
228+ println ! ( "{json_output}" ) ;
229+
230+ Ok ( ( ) )
231+ }
232+
233+ async fn exec_inspect_text (
234+ inspector : & mut VortexInspector ,
235+ file_path : & Path ,
236+ mode : InspectMode ,
237+ ) -> anyhow:: Result < ( ) > {
238+ println ! ( "File: {}" , file_path. display( ) ) ;
57239 println ! ( "Size: {} bytes" , inspector. file_size) ;
58240 println ! ( ) ;
59241
60- let mode = args. mode . unwrap_or ( InspectMode :: Footer ) ;
61-
62242 match mode {
63243 InspectMode :: Eof => {
64244 let eof = inspector. read_eof ( ) ?;
0 commit comments