Skip to content

Commit

Permalink
Merge pull request #1 from refcell/refcell/cli-impl
Browse files Browse the repository at this point in the history
feat: v0.1.2
  • Loading branch information
refcell authored Oct 18, 2023
2 parents 9f00211 + 69c362e commit 76d0bec
Show file tree
Hide file tree
Showing 13 changed files with 399 additions and 6 deletions.
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "amble"
description = "First class, scalable rust project generator with batteries included."
version = "0.1.1"
version = "0.1.2"
edition = "2021"
license = "MIT"
authors = ["refcell"]
Expand All @@ -11,6 +11,8 @@ categories = ["command-line-utilities"]
homepage = "https://github.com/refcell/amble"
repository = "https://github.com/refcell/amble"
exclude = ["benches/", "tests/"]
inclue = ["templates/"]
build = "build.rs"

[[bin]]
bench = false
Expand All @@ -19,11 +21,15 @@ name = "amble"

[dependencies]
eyre = "0.6.8"
ptree = "0.4"
inquire = "0.6.2"
tracing = "0.1.37"
tracing-subscriber = "0.3.17"
clap = { version = "4.4.3", features = ["derive"] }

[dev-dependencies]
tempfile = "3.8.0"

[profile.dev]
opt-level = 1
overflow-checks = false
Expand Down
28 changes: 28 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::{fs, io};

fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();

fs::create_dir_all(format!("{}/templates/bin/", out_dir))
.expect("unable to create templates bin directory");
fs::create_dir_all(format!("{}/templates/lib/", out_dir))
.expect("unable to create templates lib directory");

copy_content(&out_dir, "templates/bin/Cargo.toml");
copy_content(&out_dir, "templates/bin/main.rs");
copy_content(&out_dir, "templates/lib/Cargo.toml");
copy_content(&out_dir, "templates/lib/lib.rs");
copy_content(&out_dir, "templates/Cargo.toml");
}

fn copy_content(out: &str, source: &str) {
let out_path = format!("{}/{}", out, source);
let mut out_file = fs::OpenOptions::new()
.append(true)
.create(true)
.open(out_path)
.expect("unable to open/create data file");
if let Ok(mut source_file) = fs::File::open(source) {
io::copy(&mut source_file, &mut out_file).expect("failed to copy data after opening");
}
}
118 changes: 118 additions & 0 deletions src/bins.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use std::io::Write;
use std::path::Path;

use eyre::Result;
use ptree::TreeBuilder;
use tracing::instrument;

/// Creates a new bin crate.
#[instrument(name = "bin", skip(dir, name, dry, tree))]
pub(crate) fn create(
dir: &Path,
name: impl AsRef<str>,
dry: bool,
mut tree: Option<&mut TreeBuilder>,
) -> Result<()> {
tracing::info!("Creating binary crate");

let project_path_buf = dir.join(name.as_ref());
let cargo_toml_path_buf = project_path_buf.join("Cargo.toml");
let src_path_buf = project_path_buf.join("src");
let main_rs_path_buf = project_path_buf.join("src").join("main.rs");

if !dry {
tracing::debug!("Creating bin directory at {:?}", dir);
std::fs::create_dir_all(dir)?;
}
tree.as_deref_mut()
.map(|t| t.begin_child("bin".to_string()));

if !dry {
tracing::debug!("Creating crate directory at {:?}", project_path_buf);
std::fs::create_dir_all(&project_path_buf)?;
}
tree.as_deref_mut()
.map(|t| t.begin_child(name.as_ref().to_string()));

if !dry {
tracing::debug!(
"Creating crate Cargo.toml file as {:?}",
cargo_toml_path_buf
);
let mut cargo_toml = std::fs::File::create(&cargo_toml_path_buf)?;
cargo_toml.write_all(include_bytes!(concat!(
env!("OUT_DIR"),
"/templates/bin/Cargo.toml"
)))?;
}
tree.as_deref_mut()
.map(|t| t.add_empty_child("Cargo.toml".to_string()));

if !dry {
tracing::debug!("Creating crate src directory at {:?}", src_path_buf);
std::fs::create_dir_all(&src_path_buf)?;
}
tree.as_deref_mut()
.map(|t| t.begin_child("src".to_string()));

if !dry {
tracing::debug!("Creating main.rs file as {:?}", main_rs_path_buf);
let mut main_rs = std::fs::File::create(&main_rs_path_buf)?;
main_rs.write_all(include_bytes!(concat!(
env!("OUT_DIR"),
"/templates/bin/main.rs"
)))?;
}
tree.as_deref_mut()
.map(|t| t.add_empty_child("main.rs".to_string()));

tree.as_deref_mut().map(|t| t.end_child()); // <- src/
tree.as_deref_mut().map(|t| t.end_child()); // <- <name>/
tree.map(|t| t.end_child()); // <- bin/

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Read;
use tempfile::tempdir;

#[test]
fn test_create() {
let dir = tempdir().unwrap();
let dir_path_buf = dir.path().to_path_buf();
let bin_path_buf = dir_path_buf.join("bin");
let project_name = "example";
let project_path = bin_path_buf.join(project_name);
create(&bin_path_buf, project_name, false, None).unwrap();

assert!(project_path.exists());
assert!(project_path.join("src").exists());
assert!(project_path.join("src").join("main.rs").exists());
assert!(project_path.join("Cargo.toml").exists());

let mut main_rs = File::open(project_path.join("src").join("main.rs")).unwrap();
let mut main_rs_contents = String::new();
main_rs.read_to_string(&mut main_rs_contents).unwrap();
let expected_contents = "fn main() {\n println!(\"Hello World!\");\n}\n";
assert_eq!(main_rs_contents, expected_contents);
}

#[test]
fn test_create_dry_run() {
let dir = tempdir().unwrap();
let dir_path_buf = dir.path().to_path_buf();
let bin_path_buf = dir_path_buf.join("bin");
let project_name = "example";
let project_path = bin_path_buf.join(project_name);
create(&bin_path_buf, project_name, true, None).unwrap();

assert!(!project_path.exists());
assert!(!project_path.join("src").exists());
assert!(!project_path.join("src").join("main.rs").exists());
assert!(!project_path.join("Cargo.toml").exists());
}
}
41 changes: 36 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use clap::{ArgAction, Parser};
use eyre::Result;
use ptree::TreeBuilder;

/// Command line arguments
#[derive(Parser, Debug)]
Expand All @@ -12,27 +13,57 @@ pub struct Args {
/// Dry run mode.
/// If this flag is provided, the cli will no execute commands,
/// printing the directories and files that would be created instead.
#[arg(long, short)]
#[arg(long)]
dry_run: bool,

/// The project name.
/// This will be used for the binary application name.
#[arg(long, short, default_value = "example")]
project_name: String,
name: String,

/// The path to the project directory.
/// By default, the current working directory is used.
/// If any rust artifacts are detected in the specified
/// or unspecified directory, an error will be thrown.
#[arg(long, short, default_value = ".")]
dir: String,
}

/// CLI Entrypoint.
pub fn run() -> Result<()> {
let Args {
v,
dry_run,
project_name,
name,
dir,
} = Args::parse();

crate::telemetry::init_tracing_subscriber(v)?;

tracing::info!("running amble with project name: {}", project_name);
tracing::debug!("dry run mode: {}", dry_run);
let mut builder = TreeBuilder::new(dir.clone());
let project_dir_path = std::path::Path::new(&dir);
std::fs::create_dir_all(project_dir_path)?;

crate::utils::check_artifacts(project_dir_path, dry_run)?;

crate::root::create(project_dir_path, &name, dry_run, Some(&mut builder))?;
crate::bins::create(
&project_dir_path.join("bin"),
&name,
dry_run,
Some(&mut builder),
)?;
crate::libs::create(
&project_dir_path.join("crates"),
"common",
dry_run,
Some(&mut builder),
)?;

if dry_run {
let tree = builder.build();
ptree::print_tree(&tree).expect("Error printing tree");
}

Ok(())
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@
/// The CLI Module
pub mod cli;

pub(crate) mod bins;
pub(crate) mod libs;
pub(crate) mod root;
pub(crate) mod telemetry;
pub(crate) mod utils;
117 changes: 117 additions & 0 deletions src/libs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::io::Write;
use std::path::Path;

use eyre::Result;
use ptree::TreeBuilder;
use tracing::instrument;

/// Creates a new lib crate.
#[instrument(name = "lib", skip(dir, name, dry, tree))]
pub(crate) fn create(
dir: &Path,
name: impl AsRef<str>,
dry: bool,
mut tree: Option<&mut TreeBuilder>,
) -> Result<()> {
tracing::info!("Creating lib crate");

let lib_path_buf = dir.join(name.as_ref());
let src_path_buf = lib_path_buf.join("src");
let cargo_toml_path_buf = lib_path_buf.join("Cargo.toml");
let lib_rs_path_buf = lib_path_buf.join("src").join("lib.rs");

if !dry {
tracing::debug!("Creating crates directory at {:?}", dir);
std::fs::create_dir_all(dir)?;
}
tree.as_deref_mut()
.map(|t| t.begin_child("crates".to_string()));

if !dry {
tracing::debug!("Creating crate directory at {:?}", lib_path_buf);
std::fs::create_dir_all(&lib_path_buf)?;
}
tree.as_deref_mut()
.map(|t| t.begin_child(name.as_ref().to_string()));

if !dry {
tracing::debug!(
"Creating crate Cargo.toml file as {:?}",
cargo_toml_path_buf
);
let mut cargo_toml = std::fs::File::create(&cargo_toml_path_buf)?;
cargo_toml.write_all(include_bytes!(concat!(
env!("OUT_DIR"),
"/templates/lib/Cargo.toml"
)))?;
}
tree.as_deref_mut()
.map(|t| t.add_empty_child("Cargo.toml".to_string()));

if !dry {
tracing::debug!("Creating crate src directory at {:?}", src_path_buf);
std::fs::create_dir_all(&src_path_buf)?;
}
tree.as_deref_mut()
.map(|t| t.begin_child("src".to_string()));

if !dry {
tracing::debug!("Creating lib.rs file as {:?}", lib_rs_path_buf);
let mut lib_rs = std::fs::File::create(&lib_rs_path_buf)?;
lib_rs.write_all(include_bytes!(concat!(
env!("OUT_DIR"),
"/templates/lib/lib.rs"
)))?;
}
tree.as_deref_mut()
.map(|t| t.add_empty_child("main.rs".to_string()));

tree.as_deref_mut().map(|t| t.end_child()); // <- src/
tree.as_deref_mut().map(|t| t.end_child()); // <- <name>/
tree.map(|t| t.end_child()); // <- crates

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Read;
use tempfile::tempdir;

#[test]
fn test_create() {
let dir = tempdir().unwrap();
let dir_path_buf = dir.path().to_path_buf();
let crates_path_buf = dir_path_buf.join("crates");
let project_name = "example";
let project_path = crates_path_buf.join(project_name);
create(&crates_path_buf, project_name, false, None).unwrap();

assert!(project_path.exists());
assert!(project_path.join("src").exists());
assert!(project_path.join("src").join("lib.rs").exists());
assert!(project_path.join("Cargo.toml").exists());

let mut lib_rs = File::open(project_path.join("src").join("lib.rs")).unwrap();
let mut lib_rs_contents = String::new();
lib_rs.read_to_string(&mut lib_rs_contents).unwrap();
assert!(lib_rs_contents.len() > 0);
}

#[test]
fn test_create_dry_run() {
let dir = tempdir().unwrap();
let dir_path_buf = dir.path().to_path_buf();
let crates_path_buf = dir_path_buf.join("crates");
let project_name = "example";
let project_path = crates_path_buf.join(project_name);
create(&crates_path_buf, project_name, true, None).unwrap();

assert!(!project_path.exists());
assert!(!project_path.join("src").exists());
assert!(!project_path.join("src").join("lib.rs").exists());
assert!(!project_path.join("Cargo.toml").exists());
}
}
Loading

0 comments on commit 76d0bec

Please sign in to comment.