11//! Code related to the example models and the CLI commands for interacting with them.
22use super :: { RunOpts , handle_run_command} ;
3+ use crate :: example:: patches:: { get_patch_names, get_patches} ;
4+ use crate :: example:: { Example , get_example_names} ;
5+ use crate :: patch:: ModelPatch ;
36use crate :: settings:: Settings ;
4- use anyhow:: { Context , Result , ensure } ;
7+ use anyhow:: { Context , Result } ;
58use clap:: Subcommand ;
6- use include_dir:: { Dir , DirEntry , include_dir} ;
79use std:: fs;
810use std:: path:: { Path , PathBuf } ;
911use tempfile:: TempDir ;
1012
11- /// The directory containing the example models.
12- const EXAMPLES_DIR : Dir = include_dir ! ( "examples" ) ;
13-
1413/// The available subcommands for managing example models.
1514#[ derive( Subcommand ) ]
1615pub enum ExampleSubcommands {
1716 /// List available examples.
18- List ,
17+ List {
18+ /// Whether to list patched models.
19+ #[ arg( long, hide = true ) ]
20+ patch : bool ,
21+ } ,
1922 /// Provide information about the specified example.
2023 Info {
2124 /// The name of the example.
@@ -27,11 +30,17 @@ pub enum ExampleSubcommands {
2730 name : String ,
2831 /// The destination folder for the example.
2932 new_path : Option < PathBuf > ,
33+ /// Whether the model to extract is a patched model.
34+ #[ arg( long, hide = true ) ]
35+ patch : bool ,
3036 } ,
3137 /// Run an example.
3238 Run {
3339 /// The name of the example to run.
3440 name : String ,
41+ /// Whether the model to run is a patched model.
42+ #[ arg( long, hide = true ) ]
43+ patch : bool ,
3544 /// Other run options
3645 #[ command( flatten) ]
3746 opts : RunOpts ,
@@ -42,81 +51,113 @@ impl ExampleSubcommands {
4251 /// Execute the supplied example subcommand
4352 pub fn execute ( self ) -> Result < ( ) > {
4453 match self {
45- Self :: List => handle_example_list_command ( ) ,
54+ Self :: List { patch } => handle_example_list_command ( patch ) ,
4655 Self :: Info { name } => handle_example_info_command ( & name) ?,
4756 Self :: Extract {
4857 name,
49- new_path : dest,
50- } => handle_example_extract_command ( & name, dest. as_deref ( ) ) ?,
51- Self :: Run { name, opts } => handle_example_run_command ( & name, & opts, None ) ?,
58+ patch,
59+ new_path,
60+ } => handle_example_extract_command ( & name, new_path. as_deref ( ) , patch) ?,
61+ Self :: Run { name, patch, opts } => {
62+ handle_example_run_command ( & name, patch, & opts, None ) ?;
63+ }
5264 }
5365
5466 Ok ( ( ) )
5567 }
5668}
5769
5870/// Handle the `example list` command.
59- fn handle_example_list_command ( ) {
60- for entry in EXAMPLES_DIR . dirs ( ) {
61- println ! ( "{}" , entry. path( ) . display( ) ) ;
71+ fn handle_example_list_command ( patch : bool ) {
72+ if patch {
73+ for name in get_patch_names ( ) {
74+ println ! ( "{name}" ) ;
75+ }
76+ } else {
77+ for name in get_example_names ( ) {
78+ println ! ( "{name}" ) ;
79+ }
6280 }
6381}
6482
6583/// Handle the `example info` command.
6684fn handle_example_info_command ( name : & str ) -> Result < ( ) > {
67- let path: PathBuf = [ name, "README.txt" ] . iter ( ) . collect ( ) ;
68- let readme = EXAMPLES_DIR
69- . get_file ( path)
70- . context ( "Example not found." ) ?
71- . contents_utf8 ( )
72- . expect ( "README.txt is not UTF-8 encoded" ) ;
73-
74- print ! ( "{readme}" ) ;
85+ // If we can't load it, it's a bug, hence why we panic
86+ let info = Example :: from_name ( name) ?
87+ . get_readme ( )
88+ . unwrap_or_else ( |_| panic ! ( "Could not load README.txt for '{name}' example" ) ) ;
89+ print ! ( "{info}" ) ;
7590
7691 Ok ( ( ) )
7792}
7893
7994/// Handle the `example extract` command
80- fn handle_example_extract_command ( name : & str , dest : Option < & Path > ) -> Result < ( ) > {
81- let dest = dest. unwrap_or ( Path :: new ( name) ) ;
82- extract_example ( name, dest)
95+ fn handle_example_extract_command ( name : & str , dest : Option < & Path > , patch : bool ) -> Result < ( ) > {
96+ extract_example ( name, patch, dest. unwrap_or ( Path :: new ( name) ) )
8397}
8498
85- /// Extract the specified example to a new directory
86- fn extract_example ( name : & str , new_path : & Path ) -> Result < ( ) > {
87- // Find the subdirectory in EXAMPLES_DIR whose name matches `name`.
88- let sub_dir = EXAMPLES_DIR . get_dir ( name) . context ( "Example not found." ) ?;
99+ /// Extract the specified example to a new directory.
100+ ///
101+ /// If `patch` is `true`, then the corresponding patched example will be extracted.
102+ fn extract_example ( name : & str , patch : bool , dest : & Path ) -> Result < ( ) > {
103+ if patch {
104+ let patches = get_patches ( name) ?;
89105
90- ensure ! (
91- !new_path. exists( ) ,
92- "Destination directory {} already exists" ,
93- new_path. display( )
94- ) ;
106+ // NB: All patched models are based on `simple`, for now
107+ let example = Example :: from_name ( "simple" ) . unwrap ( ) ;
95108
96- // Copy the contents of the subdirectory to the destination
97- fs:: create_dir ( new_path) ?;
98- for entry in sub_dir. entries ( ) {
99- match entry {
100- DirEntry :: Dir ( _) => panic ! ( "Subdirectories in examples not supported" ) ,
101- DirEntry :: File ( f) => {
102- let file_name = f. path ( ) . file_name ( ) . unwrap ( ) ;
103- let file_path = new_path. join ( file_name) ;
104- fs:: write ( & file_path, f. contents ( ) ) ?;
105- }
106- }
107- }
109+ // First extract the example to a temp dir
110+ let example_tmp = TempDir :: new ( ) . context ( "Failed to create temporary directory" ) ?;
111+ let example_path = example_tmp. path ( ) . join ( name) ;
112+ example
113+ . extract ( & example_path)
114+ . context ( "Could not extract example" ) ?;
108115
109- Ok ( ( ) )
116+ // Patch example and put contents in dest
117+ fs:: create_dir ( dest) . context ( "Could not create output directory" ) ?;
118+ ModelPatch :: new ( example_path)
119+ . with_file_patches ( patches. to_owned ( ) )
120+ . build ( dest)
121+ . context ( "Failed to patch example" )
122+ } else {
123+ // Otherwise it's just a regular example
124+ let example = Example :: from_name ( name) ?;
125+ example. extract ( dest)
126+ }
110127}
111128
112129/// Handle the `example run` command.
113130pub fn handle_example_run_command (
114131 name : & str ,
132+ patch : bool ,
115133 opts : & RunOpts ,
116134 settings : Option < Settings > ,
117135) -> Result < ( ) > {
118- let temp_dir = TempDir :: new ( ) . context ( "Failed to create temporary directory. " ) ?;
136+ let temp_dir = TempDir :: new ( ) . context ( "Failed to create temporary directory" ) ?;
119137 let model_path = temp_dir. path ( ) . join ( name) ;
120- extract_example ( name, & model_path) ?;
138+ extract_example ( name, patch , & model_path) ?;
121139 handle_run_command ( & model_path, opts, settings)
122140}
141+
142+ #[ cfg( test) ]
143+ mod tests {
144+ use super :: * ;
145+ use rstest:: rstest;
146+
147+ fn assert_dir_non_empty ( path : & Path ) {
148+ assert ! (
149+ path. read_dir( ) . unwrap( ) . next( ) . is_some( ) ,
150+ "Directory is empty"
151+ ) ;
152+ }
153+
154+ #[ rstest]
155+ #[ case( "muse1_default" , false ) ]
156+ #[ case( "simple_divisible" , true ) ]
157+ fn check_extract_example ( #[ case] name : & str , #[ case] patch : bool ) {
158+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
159+ let dest = tmp. path ( ) . join ( "out" ) ;
160+ extract_example ( name, patch, & dest) . unwrap ( ) ;
161+ assert_dir_non_empty ( & dest) ;
162+ }
163+ }
0 commit comments