99//! create-rust-github-repo --name my-new-project
1010//!
1111//! # Copy configs from existing project
12- //! create-rust-github-repo --name my-new-project --copy-configs-from ~/workspace/my-existing-project
12+ //! create-rust-github-repo --name my-new-project --copy-configs-from ~/workspace/my-existing-project --configs .github,rustfmt.toml,clippy.toml
1313//!
1414//! # Clone to a specific directory
1515//! create-rust-github-repo --name my-new-project --dir ~/workspace/my-new-project
@@ -38,6 +38,8 @@ use std::process::{Command, ExitStatus};
3838use anyhow:: Context ;
3939use clap:: { value_parser, Parser , ValueEnum } ;
4040use derive_setters:: Setters ;
41+ use fs_extra:: copy_items;
42+ use fs_extra:: dir:: CopyOptions ;
4143
4244#[ derive( ValueEnum , Default , Eq , PartialEq , Hash , Clone , Copy , Debug ) ]
4345pub enum RepoVisibility {
@@ -73,11 +75,12 @@ pub struct CreateRustGithubRepo {
7375 #[ arg( long, help = "Shell to use for executing commands" , default_value = "/bin/sh" ) ]
7476 shell_cmd : String ,
7577
76- #[ arg( long, short, help = "Source directory for configuration files " , value_parser = value_parser!( PathBuf ) ) ]
78+ #[ arg( long, short, help = "Source directory for config paths " , value_parser = value_parser!( PathBuf ) ) ]
7779 copy_configs_from : Option < PathBuf > ,
7880
79- #[ arg( long, help = "Extra config file paths (relative to resolved `dir`), separated by comma" , value_delimiter = ',' ) ]
80- extra_configs : Vec < String > ,
81+ /// Config paths separated by comma (relative to `copy_configs_from`) (only applies if `copy_configs_from` is specified) (supports files and directories)
82+ #[ arg( long, value_delimiter = ',' ) ]
83+ configs : Vec < String > ,
8184
8285 #[ arg( long, help = "Shell command to check if repo exists (supports substitutions - see help below)" , default_value = "gh repo view --json nameWithOwner {{name}} 2>/dev/null" ) ]
8386 repo_exists_cmd : String ,
@@ -97,7 +100,7 @@ pub struct CreateRustGithubRepo {
97100 #[ arg( long, help = "Shell command to add new files (supports substitutions - see help below)" , default_value = "git add ." ) ]
98101 repo_add_args : String ,
99102
100- #[ arg( long, help = "Shell command to make a commit (supports substitutions - see help below)" , default_value = "git commit -m \" Add configs \" " ) ]
103+ #[ arg( long, help = "Shell command to make a commit (supports substitutions - see help below)" , default_value = "git commit -m \" Setup project \" " ) ]
101104 repo_commit_args : String ,
102105
103106 #[ arg( long, help = "Shell command to push the commit (supports substitutions - see help below)" , default_value = "git push" ) ]
@@ -142,11 +145,16 @@ impl CreateRustGithubRepo {
142145 }
143146
144147 if let Some ( copy_configs_from) = self . copy_configs_from {
145- let mut configs: Vec < String > = vec ! [ ] ;
146- configs. extend ( CONFIGS . iter ( ) . copied ( ) . map ( ToOwned :: to_owned) ) ;
147- configs. extend ( self . extra_configs ) ;
148- // Copy config files
149- copy_configs_if_not_exists ( & copy_configs_from, & dir, configs) . context ( "Failed to copy configuration files" ) ?;
148+ let paths: Vec < PathBuf > = self
149+ . configs
150+ . iter ( )
151+ . map ( |config| copy_configs_from. join ( config) )
152+ . collect ( ) ;
153+ let options = CopyOptions :: new ( )
154+ . skip_exist ( true )
155+ . copy_inside ( true )
156+ . buffer_size ( MEGABYTE ) ;
157+ copy_items ( & paths, & dir, & options) . context ( "Failed to copy configuration files" ) ?;
150158 }
151159
152160 // test
@@ -178,27 +186,28 @@ pub fn replace_all(mut input: String, substitutions: &HashMap<&str, &str>) -> St
178186 input
179187}
180188
181- pub fn exec ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > , extra_args : impl IntoIterator < Item = String > , current_dir : impl AsRef < Path > , substitutions : & HashMap < & str , & str > ) -> io:: Result < ExitStatus > {
189+ pub fn exec ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > + Clone , extra_args : impl IntoIterator < Item = String > , current_dir : impl AsRef < Path > , substitutions : & HashMap < & str , & str > ) -> io:: Result < ExitStatus > {
182190 let replacements = replace_args ( extra_args, substitutions) ;
183191 let extra_args = replacements. iter ( ) . map ( AsRef :: < OsStr > :: as_ref) ;
184192 exec_raw ( cmd, args, extra_args, current_dir)
185193}
186194
187- pub fn success ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > , extra_args : impl IntoIterator < Item = String > , current_dir : impl AsRef < Path > , substitutions : & HashMap < & str , & str > ) -> io:: Result < bool > {
195+ pub fn success ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > + Clone , extra_args : impl IntoIterator < Item = String > , current_dir : impl AsRef < Path > , substitutions : & HashMap < & str , & str > ) -> io:: Result < bool > {
188196 let replacements = replace_args ( extra_args, substitutions) ;
189197 let extra_args = replacements. iter ( ) . map ( AsRef :: < OsStr > :: as_ref) ;
190198 success_raw ( cmd, args, extra_args, current_dir)
191199}
192200
193- pub fn exec_raw ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > , extra_args : impl IntoIterator < Item = impl AsRef < OsStr > > , current_dir : impl AsRef < Path > ) -> io:: Result < ExitStatus > {
201+ pub fn exec_raw ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > + Clone , extra_args : impl IntoIterator < Item = impl AsRef < OsStr > > , current_dir : impl AsRef < Path > ) -> io:: Result < ExitStatus > {
194202 get_status_raw ( cmd, args, extra_args, current_dir) . and_then ( check_status)
195203}
196204
197- pub fn success_raw ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > , extra_args : impl IntoIterator < Item = impl AsRef < OsStr > > , current_dir : impl AsRef < Path > ) -> io:: Result < bool > {
205+ pub fn success_raw ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > + Clone , extra_args : impl IntoIterator < Item = impl AsRef < OsStr > > , current_dir : impl AsRef < Path > ) -> io:: Result < bool > {
198206 get_status_raw ( cmd, args, extra_args, current_dir) . map ( |status| status. success ( ) )
199207}
200208
201- pub fn get_status_raw ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > , extra_args : impl IntoIterator < Item = impl AsRef < OsStr > > , current_dir : impl AsRef < Path > ) -> io:: Result < ExitStatus > {
209+ pub fn get_status_raw ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > + Clone , extra_args : impl IntoIterator < Item = impl AsRef < OsStr > > , current_dir : impl AsRef < Path > ) -> io:: Result < ExitStatus > {
210+ eprintln ! ( "$ {}" , cmd_to_string( cmd. as_ref( ) , args. clone( ) ) ) ;
202211 Command :: new ( cmd)
203212 . args ( args)
204213 . args ( extra_args)
@@ -207,6 +216,15 @@ pub fn get_status_raw(cmd: impl AsRef<OsStr>, args: impl IntoIterator<Item = imp
207216 . wait ( )
208217}
209218
219+ fn cmd_to_string ( cmd : impl AsRef < OsStr > , args : impl IntoIterator < Item = impl AsRef < OsStr > > ) -> String {
220+ let mut cmd_str = cmd. as_ref ( ) . to_string_lossy ( ) . to_string ( ) ;
221+ for arg in args {
222+ cmd_str. push ( ' ' ) ;
223+ cmd_str. push_str ( arg. as_ref ( ) . to_string_lossy ( ) . as_ref ( ) ) ;
224+ }
225+ cmd_str
226+ }
227+
210228pub fn check_status ( status : ExitStatus ) -> io:: Result < ExitStatus > {
211229 if status. success ( ) {
212230 Ok ( status)
@@ -215,33 +233,10 @@ pub fn check_status(status: ExitStatus) -> io::Result<ExitStatus> {
215233 }
216234}
217235
218- pub fn copy_configs_if_not_exists < P : Clone + AsRef < Path > > ( source : & Path , target : & Path , configs : impl IntoIterator < Item = P > ) -> io:: Result < ( ) > {
219- for config in configs {
220- let source_path = source. join ( config. clone ( ) ) ;
221- let target_path = target. join ( config) ;
222- if source_path. exists ( ) && !target_path. exists ( ) {
223- fs_err:: copy ( & source_path, & target_path) ?;
224- }
225- }
226- Ok ( ( ) )
227- }
228-
229- pub const CONFIGS : & [ & str ] = & [
230- "clippy.toml" ,
231- "rustfmt.toml" ,
232- "Justfile" ,
233- "lefthook.yml" ,
234- ".lefthook.yml" ,
235- "lefthook.yaml" ,
236- ".lefthook.yaml" ,
237- "lefthook.toml" ,
238- ".lefthook.toml" ,
239- "lefthook.json" ,
240- ".lefthook.json" ,
241- ] ;
242-
243236#[ test]
244237fn verify_cli ( ) {
245238 use clap:: CommandFactory ;
246239 CreateRustGithubRepo :: command ( ) . debug_assert ( ) ;
247240}
241+
242+ const MEGABYTE : usize = 1048576 ;
0 commit comments