Skip to content

Commit

Permalink
feat!: remove --extra-configs, add --configs
Browse files Browse the repository at this point in the history
  • Loading branch information
DenisGorbachev committed Jul 23, 2024
1 parent 9b01520 commit b95465f
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 61 deletions.
19 changes: 5 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "create-rust-github-repo"
description = "`create-rust-github-repo` is a CLI program that creates a new repository on GitHub, clones it locally, initializes a Rust project, copies the configs from a pre-existing directory."
version = "0.3.4"
version = "0.4.0"
edition = "2021"
license = "Apache-2.0 OR MIT"
homepage = "https://github.com/DenisGorbachev/create-rust-github-repo"
Expand All @@ -19,5 +19,5 @@ announcement = ""
[dependencies]
anyhow = "1.0.86"
clap = { version = "4.3.24", features = ["derive"] }
fs-err = "2.11.0"
derive_setters = "0.1.6"
fs_extra = "1.3.0"
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
create-rust-github-repo --name my-new-project

# Copy configs from existing project
create-rust-github-repo --name my-new-project --copy-configs-from ~/workspace/my-existing-project
create-rust-github-repo --name my-new-project --copy-configs-from ~/workspace/my-existing-project --configs .github,rustfmt.toml,clippy.toml

# Clone to a specific directory
create-rust-github-repo --name my-new-project --dir ~/workspace/my-new-project
Expand Down Expand Up @@ -60,9 +60,9 @@ Options:
--shell-cmd <SHELL_CMD>
Shell to use for executing commands [default: /bin/sh]
-c, --copy-configs-from <COPY_CONFIGS_FROM>
Source directory for configuration files
--extra-configs <EXTRA_CONFIGS>
Extra config file paths (relative to resolved `dir`), separated by comma
Source directory for config paths
--configs <CONFIGS>
Config paths separated by comma (relative to `copy_configs_from`) (only applies if `copy_configs_from` is specified) (supports files and directories)
--repo-exists-cmd <REPO_EXISTS_CMD>
Shell command to check if repo exists (supports substitutions - see help below) [default: "gh repo view --json nameWithOwner {{name}} 2>/dev/null"]
--repo-create-cmd <REPO_CREATE_CMD>
Expand All @@ -76,7 +76,7 @@ Options:
--repo-add-args <REPO_ADD_ARGS>
Shell command to add new files (supports substitutions - see help below) [default: "git add ."]
--repo-commit-args <REPO_COMMIT_ARGS>
Shell command to make a commit (supports substitutions - see help below) [default: "git commit -m \"Add configs\""]
Shell command to make a commit (supports substitutions - see help below) [default: "git commit -m \"Setup project\""]
--repo-push-args <REPO_PUSH_ARGS>
Shell command to push the commit (supports substitutions - see help below) [default: "git push"]
-h, --help
Expand Down
2 changes: 2 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ pre-commit:
run: cargo test --doc
lint-commit:
run: commitlint --last
lint-deps:
run: cargo machete --with-metadata
75 changes: 35 additions & 40 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! create-rust-github-repo --name my-new-project
//!
//! # Copy configs from existing project
//! create-rust-github-repo --name my-new-project --copy-configs-from ~/workspace/my-existing-project
//! create-rust-github-repo --name my-new-project --copy-configs-from ~/workspace/my-existing-project --configs .github,rustfmt.toml,clippy.toml
//!
//! # Clone to a specific directory
//! create-rust-github-repo --name my-new-project --dir ~/workspace/my-new-project
Expand Down Expand Up @@ -38,6 +38,8 @@ use std::process::{Command, ExitStatus};
use anyhow::Context;
use clap::{value_parser, Parser, ValueEnum};
use derive_setters::Setters;
use fs_extra::copy_items;
use fs_extra::dir::CopyOptions;

#[derive(ValueEnum, Default, Eq, PartialEq, Hash, Clone, Copy, Debug)]
pub enum RepoVisibility {
Expand Down Expand Up @@ -73,11 +75,12 @@ pub struct CreateRustGithubRepo {
#[arg(long, help = "Shell to use for executing commands", default_value = "/bin/sh")]
shell_cmd: String,

#[arg(long, short, help = "Source directory for configuration files", value_parser = value_parser!(PathBuf))]
#[arg(long, short, help = "Source directory for config paths", value_parser = value_parser!(PathBuf))]
copy_configs_from: Option<PathBuf>,

#[arg(long, help = "Extra config file paths (relative to resolved `dir`), separated by comma", value_delimiter = ',')]
extra_configs: Vec<String>,
/// Config paths separated by comma (relative to `copy_configs_from`) (only applies if `copy_configs_from` is specified) (supports files and directories)
#[arg(long, value_delimiter = ',')]
configs: Vec<String>,

#[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")]
repo_exists_cmd: String,
Expand All @@ -97,7 +100,7 @@ pub struct CreateRustGithubRepo {
#[arg(long, help = "Shell command to add new files (supports substitutions - see help below)", default_value = "git add .")]
repo_add_args: String,

#[arg(long, help = "Shell command to make a commit (supports substitutions - see help below)", default_value = "git commit -m \"Add configs\"")]
#[arg(long, help = "Shell command to make a commit (supports substitutions - see help below)", default_value = "git commit -m \"Setup project\"")]
repo_commit_args: String,

#[arg(long, help = "Shell command to push the commit (supports substitutions - see help below)", default_value = "git push")]
Expand Down Expand Up @@ -142,11 +145,16 @@ impl CreateRustGithubRepo {
}

if let Some(copy_configs_from) = self.copy_configs_from {
let mut configs: Vec<String> = vec![];
configs.extend(CONFIGS.iter().copied().map(ToOwned::to_owned));
configs.extend(self.extra_configs);
// Copy config files
copy_configs_if_not_exists(&copy_configs_from, &dir, configs).context("Failed to copy configuration files")?;
let paths: Vec<PathBuf> = self
.configs
.iter()
.map(|config| copy_configs_from.join(config))
.collect();
let options = CopyOptions::new()
.skip_exist(true)
.copy_inside(true)
.buffer_size(MEGABYTE);
copy_items(&paths, &dir, &options).context("Failed to copy configuration files")?;
}

// test
Expand Down Expand Up @@ -178,27 +186,28 @@ pub fn replace_all(mut input: String, substitutions: &HashMap<&str, &str>) -> St
input
}

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> {
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> {
let replacements = replace_args(extra_args, substitutions);
let extra_args = replacements.iter().map(AsRef::<OsStr>::as_ref);
exec_raw(cmd, args, extra_args, current_dir)
}

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> {
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> {
let replacements = replace_args(extra_args, substitutions);
let extra_args = replacements.iter().map(AsRef::<OsStr>::as_ref);
success_raw(cmd, args, extra_args, current_dir)
}

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> {
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> {
get_status_raw(cmd, args, extra_args, current_dir).and_then(check_status)
}

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> {
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> {
get_status_raw(cmd, args, extra_args, current_dir).map(|status| status.success())
}

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> {
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> {
eprintln!("$ {}", cmd_to_string(cmd.as_ref(), args.clone()));
Command::new(cmd)
.args(args)
.args(extra_args)
Expand All @@ -207,6 +216,15 @@ pub fn get_status_raw(cmd: impl AsRef<OsStr>, args: impl IntoIterator<Item = imp
.wait()
}

fn cmd_to_string(cmd: impl AsRef<OsStr>, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> String {
let mut cmd_str = cmd.as_ref().to_string_lossy().to_string();
for arg in args {
cmd_str.push(' ');
cmd_str.push_str(arg.as_ref().to_string_lossy().as_ref());
}
cmd_str
}

pub fn check_status(status: ExitStatus) -> io::Result<ExitStatus> {
if status.success() {
Ok(status)
Expand All @@ -215,33 +233,10 @@ pub fn check_status(status: ExitStatus) -> io::Result<ExitStatus> {
}
}

pub fn copy_configs_if_not_exists<P: Clone + AsRef<Path>>(source: &Path, target: &Path, configs: impl IntoIterator<Item = P>) -> io::Result<()> {
for config in configs {
let source_path = source.join(config.clone());
let target_path = target.join(config);
if source_path.exists() && !target_path.exists() {
fs_err::copy(&source_path, &target_path)?;
}
}
Ok(())
}

pub const CONFIGS: &[&str] = &[
"clippy.toml",
"rustfmt.toml",
"Justfile",
"lefthook.yml",
".lefthook.yml",
"lefthook.yaml",
".lefthook.yaml",
"lefthook.toml",
".lefthook.toml",
"lefthook.json",
".lefthook.json",
];

#[test]
fn verify_cli() {
use clap::CommandFactory;
CreateRustGithubRepo::command().debug_assert();
}

const MEGABYTE: usize = 1048576;

0 comments on commit b95465f

Please sign in to comment.