@@ -12,8 +12,6 @@ use secrecy::ExposeSecret as _;
1212use sha2:: { Digest as _, Sha256 } ;
1313use walkdir:: WalkDir ;
1414
15- use serde:: Deserialize ;
16-
1715use crate :: api:: { Api , CreateSnapshotResponse , ImageMetadata , SnapshotsManifest } ;
1816use crate :: config:: { Auth , Config } ;
1917use crate :: utils:: args:: ArgExt as _;
@@ -97,8 +95,8 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
9795 style( images. len( ) ) . yellow( ) ,
9896 if images. len( ) == 1 { "file" } else { "files" }
9997 ) ;
100- let display_names = collect_display_names ( dir_path ) ;
101- let manifest_entries = upload_images ( images, & display_names , & org, & project) ?;
98+
99+ let manifest_entries = upload_images ( images, & org, & project) ?;
102100
103101 // Build manifest from discovered images
104102 let manifest = SnapshotsManifest {
@@ -191,9 +189,42 @@ fn is_image_file(path: &Path) -> bool {
191189 . unwrap_or ( false )
192190}
193191
192+ /// Reads the companion JSON sidecar for an image, if it exists.
193+ ///
194+ /// For an image at `path/to/button.png`, looks for `path/to/button.json`.
195+ /// Returns a map of all key-value pairs from the JSON file.
196+ fn read_sidecar_metadata ( image_path : & Path ) -> HashMap < String , serde_json:: Value > {
197+ let sidecar_path = image_path. with_extension ( "json" ) ;
198+ if !sidecar_path. is_file ( ) {
199+ return HashMap :: new ( ) ;
200+ }
201+
202+ debug ! ( "Reading sidecar metadata: {}" , sidecar_path. display( ) ) ;
203+ let contents = match fs:: read_to_string ( & sidecar_path) {
204+ Ok ( c) => c,
205+ Err ( err) => {
206+ warn ! (
207+ "Failed to read sidecar file {}: {err}" ,
208+ sidecar_path. display( )
209+ ) ;
210+ return HashMap :: new ( ) ;
211+ }
212+ } ;
213+
214+ match serde_json:: from_str ( & contents) {
215+ Ok ( map) => map,
216+ Err ( err) => {
217+ warn ! (
218+ "Failed to parse sidecar file {}: {err}" ,
219+ sidecar_path. display( )
220+ ) ;
221+ HashMap :: new ( )
222+ }
223+ }
224+ }
225+
194226fn upload_images (
195227 images : Vec < ImageInfo > ,
196- display_names : & HashMap < String , String > ,
197228 org : & str ,
198229 project : & str ,
199230) -> Result < HashMap < String , ImageMetadata > > {
@@ -251,14 +282,16 @@ fn upload_images(
251282 . unwrap_or_default ( )
252283 . to_string_lossy ( )
253284 . into_owned ( ) ;
254- let display_name = display_names. get ( & image_file_name) . cloned ( ) ;
285+
286+ let extra = read_sidecar_metadata ( & image. path ) ;
287+
255288 manifest_entries. insert (
256289 hash,
257290 ImageMetadata {
291+ extra,
258292 image_file_name,
259293 width : image. width ,
260294 height : image. height ,
261- display_name,
262295 } ,
263296 ) ;
264297 }
@@ -286,62 +319,3 @@ fn upload_images(
286319 }
287320 }
288321}
289-
290- /// Input format for user-provided JSON manifest files.
291- #[ derive( Deserialize ) ]
292- struct ManifestFile {
293- images : HashMap < String , ManifestFileEntry > ,
294- }
295-
296- #[ derive( Deserialize ) ]
297- struct ManifestFileEntry {
298- image_file_name : String ,
299- display_name : Option < String > ,
300- }
301-
302- /// Collects `image_file_name -> display_name` mappings from JSON manifest files in a directory.
303- fn collect_display_names ( dir : & Path ) -> HashMap < String , String > {
304- let mut display_names = HashMap :: new ( ) ;
305- let entries = WalkDir :: new ( dir)
306- . follow_links ( true )
307- . into_iter ( )
308- . filter_entry ( |e| !is_hidden ( dir, e. path ( ) ) ) ;
309-
310- for entry in entries. flatten ( ) {
311- if !entry. file_type ( ) . is_file ( ) {
312- continue ;
313- }
314- let path = entry. path ( ) ;
315- let is_json = path
316- . extension ( )
317- . and_then ( |ext| ext. to_str ( ) )
318- . map ( |ext| ext. eq_ignore_ascii_case ( "json" ) )
319- . unwrap_or ( false ) ;
320- if !is_json {
321- continue ;
322- }
323-
324- debug ! ( "Reading manifest file: {}" , path. display( ) ) ;
325- let contents = match fs:: read_to_string ( path) {
326- Ok ( c) => c,
327- Err ( err) => {
328- warn ! ( "Failed to read manifest file {}: {err}" , path. display( ) ) ;
329- continue ;
330- }
331- } ;
332- let manifest: ManifestFile = match serde_json:: from_str ( & contents) {
333- Ok ( m) => m,
334- Err ( err) => {
335- warn ! ( "Failed to parse manifest file {}: {err}" , path. display( ) ) ;
336- continue ;
337- }
338- } ;
339-
340- for entry in manifest. images . into_values ( ) {
341- if let Some ( display_name) = entry. display_name {
342- display_names. insert ( entry. image_file_name , display_name) ;
343- }
344- }
345- }
346- display_names
347- }
0 commit comments