From 8cc9fd81f9fd598935410ae16fdb1d5436229a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 6 Mar 2024 10:20:05 -0500 Subject: [PATCH 01/10] Create a rust binary to generate a new rails application --- .gitignore | 1 + Cargo.lock | 7 +++++++ Cargo.toml | 8 ++++++++ src/main.rs | 18 ++++++++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c654eda --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rails-new" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..10ece63 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rails-new" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9651d91 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,18 @@ +// Write a CLI program that call the bash file rails-new inside the bin folder. + +use std::process::Command; + +fn main() { + // Read the application name from the command arguments + let args: Vec = std::env::args().collect(); + let app_name = &args[1]; + + // Execute the bash file rails-new + let status = Command::new("bash") + .arg("bin/rails-new") + .arg(app_name) + .status() + .expect("Failed to execute process"); + + assert!(status.success()); +} From 017510d8f9d4945860586d639b9f15598cfa8060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 6 Mar 2024 11:40:19 -0500 Subject: [PATCH 02/10] Use clap to parse the command line arguments --- Cargo.lock | 401 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 + src/main.rs | 11 +- src/rails_new.rs | 13 ++ tests/cli.rs | 15 ++ 5 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 src/rails_new.rs create mode 100644 tests/cli.rs diff --git a/Cargo.lock b/Cargo.lock index c654eda..43e5649 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,407 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "assert_cmd" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "clap" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rails-new" version = "0.1.0" +dependencies = [ + "assert_cmd", + "clap", + "predicates", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/Cargo.toml b/Cargo.toml index 10ece63..ea8ae4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,14 @@ [package] name = "rails-new" version = "0.1.0" +description = "A CLI tool to generate a new Rails project" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "4.5.1", features = ["derive"] } + +[dev-dependencies] +assert_cmd = "2.0.14" +predicates = "3.1.0" diff --git a/src/main.rs b/src/main.rs index 9651d91..bb099d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,19 @@ // Write a CLI program that call the bash file rails-new inside the bin folder. +// use std::process::Command; +mod rails_new; +use rails_new::Cli; use std::process::Command; +use clap::Parser; + fn main() { - // Read the application name from the command arguments - let args: Vec = std::env::args().collect(); - let app_name = &args[1]; + let cli = Cli::parse(); // Execute the bash file rails-new let status = Command::new("bash") .arg("bin/rails-new") - .arg(app_name) + .arg(cli.name) .status() .expect("Failed to execute process"); diff --git a/src/rails_new.rs b/src/rails_new.rs new file mode 100644 index 0000000..5e2ebd7 --- /dev/null +++ b/src/rails_new.rs @@ -0,0 +1,13 @@ +use clap::Parser; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +pub struct Cli { + pub name: String, +} + +#[test] +fn verify_cli() { + use clap::CommandFactory; + Cli::command().debug_assert() +} diff --git a/tests/cli.rs b/tests/cli.rs new file mode 100644 index 0000000..93e1ce0 --- /dev/null +++ b/tests/cli.rs @@ -0,0 +1,15 @@ +use assert_cmd::prelude::*; +use predicates::prelude::*; +use std::process::Command; + +#[test] +fn requires_a_name() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("rails-new")?; + + cmd.assert() + .failure() + .stderr(predicate::str::contains("the following required arguments were not provided:")) + .stderr(predicate::str::contains("")); + + Ok(()) +} From 7e09c15b0add426186dc20e0c162078b77c7267f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 6 Mar 2024 15:29:29 -0500 Subject: [PATCH 03/10] Remove bash script and push logic to Rust binary --- README.md | 13 +++++-------- bin/rails-new | 20 -------------------- src/main.rs | 49 +++++++++++++++++++++++++++++++++++++++++++----- src/rails_new.rs | 20 +++++++++++++++++++- tests/cli.rs | 6 ++++-- 5 files changed, 72 insertions(+), 36 deletions(-) delete mode 100755 bin/rails-new diff --git a/README.md b/README.md index 535e2bb..8b07c31 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,19 @@ Rails versions for you, so you don't have to worry about it. You need to have Docker installed on your machine. You can find instructions on how to install Docker on your machine [here](https://docs.docker.com/engine/install/). -## Usage +## Installation -First, clone the repository: -```bash -git clone https://github.com/rails/rails-new.git -cd rails-new -``` + +## Usage To generate a new Rails application, you can run the following command: ```bash -bin/rails-new myapp +rails-new myapp ``` Or with options: ```bash -bin/rails-new myapp --main +rails-new myapp --main ``` diff --git a/bin/rails-new b/bin/rails-new deleted file mode 100755 index 7a2d0a4..0000000 --- a/bin/rails-new +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env sh - -# This script is to generate a new Rails application using the Dockerfile in this repository. - -# The first argument is the name of the new Rails application. - -# Ruby version variable -RUBY_VERSION=3.2.3 - -# Rails version variable -RAILS_VERSION=7.1.3 - -# Build the image -docker build \ - --build-arg RUBY_VERSION=$RUBY_VERSION \ - --build-arg RAILS_VERSION=$RAILS_VERSION \ - -t rails-new-$RUBY_VERSION-$RAILS_VERSION . - -# Run the image -docker run -v $(pwd):/$(pwd) -w $(pwd) rails-new-$RUBY_VERSION-$RAILS_VERSION rails new $@ diff --git a/src/main.rs b/src/main.rs index bb099d0..aff5a5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,17 +3,56 @@ // use std::process::Command; mod rails_new; use rails_new::Cli; -use std::process::Command; +use std::io::Write; +use std::process::{Command, Stdio}; use clap::Parser; fn main() { let cli = Cli::parse(); - // Execute the bash file rails-new - let status = Command::new("bash") - .arg("bin/rails-new") - .arg(cli.name) + // read the content of the DOCKERFILE and store it in a variable + let dockerfile = include_str!("../Dockerfile"); + + let ruby_version = cli.ruby_version.unwrap(); + let rails_version = cli.rails_version.unwrap(); + + // Run docker build --build-arg RUBY_VERSION=$RUBY_VERSION --build-arg RAILS_VERSION=$RAILS_VERSION -t rails-new-$RUBY_VERSION-$RAILS_VERSION + // passing the content of DOCKERFILE to the command stdin + let mut child = Command::new("docker") + .arg("build") + .arg("--build-arg") + .arg(format!("RUBY_VERSION={}", ruby_version)) + .arg("--build-arg") + .arg(format!("RAILS_VERSION={}", rails_version)) + .arg("-t") + .arg(format!("rails-new-{}-{}", ruby_version, rails_version)) + .arg("-") + .stdin(Stdio::piped()) + .spawn() + .expect("Failed to execute process"); + + let stdin = child.stdin.as_mut().expect("Failed to open stdin"); + stdin.write_all(dockerfile.as_bytes()).unwrap(); + + let status = child.wait().expect("failed to wait on child"); + + assert!(status.success()); + + // Run the image with docker run -v $(pwd):/$(pwd) -w $(pwd) rails-new-$RUBY_VERSION-$RAILS_VERSION rails new $@ + let binding = std::env::current_dir().unwrap(); + let current_dir = binding.to_str().unwrap(); + + let status = Command::new("docker") + .arg("run") + .arg("-v") + .arg(format!("{}:{}", current_dir, current_dir)) + .arg("-w") + .arg(current_dir) + .arg(format!("rails-new-{}-{}", ruby_version, rails_version)) + .arg("rails") + .arg("new") + .args(cli.args) .status() .expect("Failed to execute process"); diff --git a/src/rails_new.rs b/src/rails_new.rs index 5e2ebd7..d61a5b4 100644 --- a/src/rails_new.rs +++ b/src/rails_new.rs @@ -3,7 +3,12 @@ use clap::Parser; #[derive(Parser)] #[command(version, about, long_about = None)] pub struct Cli { - pub name: String, + #[clap(trailing_var_arg = true, required = true)] + pub args: Vec, + #[clap(long, short = 'u', default_value = "3.2.3")] + pub ruby_version: Option, + #[clap(long, short = 'r', default_value = "7.1.3")] + pub rails_version: Option, } #[test] @@ -11,3 +16,16 @@ fn verify_cli() { use clap::CommandFactory; Cli::command().debug_assert() } + +#[test] +fn arguments_are_directed_to_rails_new() -> Result<(), Box> { + use clap::CommandFactory; + + let m = Cli::command().get_matches_from(vec!["rails-new", "my_app", "--main"]); + + let trail: Vec<_> = m.get_many::("args").unwrap().collect(); + + assert_eq!(trail, &["my_app", "--main"]); + + Ok(()) +} diff --git a/tests/cli.rs b/tests/cli.rs index 93e1ce0..4339e52 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -8,8 +8,10 @@ fn requires_a_name() -> Result<(), Box> { cmd.assert() .failure() - .stderr(predicate::str::contains("the following required arguments were not provided:")) - .stderr(predicate::str::contains("")); + .stderr(predicate::str::contains( + "the following required arguments were not provided:", + )) + .stderr(predicate::str::contains("...")); Ok(()) } From 6b9db6b7eafd11049772039f751a3d76d8943238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 6 Mar 2024 15:58:21 -0500 Subject: [PATCH 04/10] Extract DockerClient abstraction This will make easier for us to change the implmentation later. --- src/docker_client.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 29 +++++------------------------ 2 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 src/docker_client.rs diff --git a/src/docker_client.rs b/src/docker_client.rs new file mode 100644 index 0000000..74c0aa2 --- /dev/null +++ b/src/docker_client.rs @@ -0,0 +1,42 @@ +use std::process::{Command, Stdio}; + +pub struct DockerClient {} + +impl DockerClient { + pub fn build_image(ruby_version: &str, rails_version: &str) -> Command { + let mut command = Command::new("docker"); + + command + .arg("build") + .arg("--build-arg") + .arg(format!("RUBY_VERSION={}", ruby_version)) + .arg("--build-arg") + .arg(format!("RAILS_VERSION={}", rails_version)) + .arg("-t") + .arg(format!("rails-new-{}-{}", ruby_version, rails_version)) + .arg("-") + .stdin(Stdio::piped()); + + command + } + + pub fn run_image(ruby_version: &str, rails_version: &str, args: Vec) -> Command { + let binding = std::env::current_dir().unwrap(); + let current_dir = binding.to_str().unwrap(); + + let mut command = Command::new("docker"); + + command + .arg("run") + .arg("-v") + .arg(format!("{}:{}", current_dir, current_dir)) + .arg("-w") + .arg(current_dir) + .arg(format!("rails-new-{}-{}", ruby_version, rails_version)) + .arg("rails") + .arg("new") + .args(args); + + command + } +} diff --git a/src/main.rs b/src/main.rs index aff5a5f..1043ad2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ // Write a CLI program that call the bash file rails-new inside the bin folder. // use std::process::Command; +mod docker_client; mod rails_new; use rails_new::Cli; use std::io::Write; -use std::process::{Command, Stdio}; use clap::Parser; +use crate::docker_client::DockerClient; + fn main() { let cli = Cli::parse(); @@ -19,16 +21,7 @@ fn main() { // Run docker build --build-arg RUBY_VERSION=$RUBY_VERSION --build-arg RAILS_VERSION=$RAILS_VERSION -t rails-new-$RUBY_VERSION-$RAILS_VERSION // passing the content of DOCKERFILE to the command stdin - let mut child = Command::new("docker") - .arg("build") - .arg("--build-arg") - .arg(format!("RUBY_VERSION={}", ruby_version)) - .arg("--build-arg") - .arg(format!("RAILS_VERSION={}", rails_version)) - .arg("-t") - .arg(format!("rails-new-{}-{}", ruby_version, rails_version)) - .arg("-") - .stdin(Stdio::piped()) + let mut child = DockerClient::build_image(&ruby_version, &rails_version) .spawn() .expect("Failed to execute process"); @@ -40,19 +33,7 @@ fn main() { assert!(status.success()); // Run the image with docker run -v $(pwd):/$(pwd) -w $(pwd) rails-new-$RUBY_VERSION-$RAILS_VERSION rails new $@ - let binding = std::env::current_dir().unwrap(); - let current_dir = binding.to_str().unwrap(); - - let status = Command::new("docker") - .arg("run") - .arg("-v") - .arg(format!("{}:{}", current_dir, current_dir)) - .arg("-w") - .arg(current_dir) - .arg(format!("rails-new-{}-{}", ruby_version, rails_version)) - .arg("rails") - .arg("new") - .args(cli.args) + let status = DockerClient::run_image(&ruby_version, &rails_version, cli.args) .status() .expect("Failed to execute process"); From a24d2d08bf60442587ea16b2852c4e7542a15c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 6 Mar 2024 16:20:38 -0500 Subject: [PATCH 05/10] Setup CI --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5830a3c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: Rust + +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose From f688ed2162680bc8d92af2212b360b7410827099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 6 Mar 2024 16:57:05 -0500 Subject: [PATCH 06/10] Remove container after command is done --- src/docker_client.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/docker_client.rs b/src/docker_client.rs index 74c0aa2..84188e9 100644 --- a/src/docker_client.rs +++ b/src/docker_client.rs @@ -28,6 +28,7 @@ impl DockerClient { command .arg("run") + .arg("--rm") .arg("-v") .arg(format!("{}:{}", current_dir, current_dir)) .arg("-w") From bca3e0e93973c6dc792b8d2c9372100a244f18fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 19 Mar 2024 16:09:26 -0400 Subject: [PATCH 07/10] Add description to the args option --- src/rails_new.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rails_new.rs b/src/rails_new.rs index d61a5b4..626dba4 100644 --- a/src/rails_new.rs +++ b/src/rails_new.rs @@ -4,6 +4,7 @@ use clap::Parser; #[command(version, about, long_about = None)] pub struct Cli { #[clap(trailing_var_arg = true, required = true)] + /// arguments passed to `rails new` pub args: Vec, #[clap(long, short = 'u', default_value = "3.2.3")] pub ruby_version: Option, From 4826c31a7fb1abc62f72d3e9c8a0516b0d177fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 19 Mar 2024 16:09:47 -0400 Subject: [PATCH 08/10] Load as binary instead of text We only need the binary form. --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1043ad2..3e8d207 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ fn main() { let cli = Cli::parse(); // read the content of the DOCKERFILE and store it in a variable - let dockerfile = include_str!("../Dockerfile"); + let dockerfile = include_bytes!("../Dockerfile"); let ruby_version = cli.ruby_version.unwrap(); let rails_version = cli.rails_version.unwrap(); @@ -26,7 +26,7 @@ fn main() { .expect("Failed to execute process"); let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - stdin.write_all(dockerfile.as_bytes()).unwrap(); + stdin.write_all(dockerfile).unwrap(); let status = child.wait().expect("failed to wait on child"); From 2717f49b72e63d0d15cb0a4e967bea0ee5e0bac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 19 Mar 2024 16:10:47 -0400 Subject: [PATCH 09/10] Make the ruby_version and rails_version fields of the Cli struct non-optional. This is because the default values are already set in the struct definition. --- src/main.rs | 4 ++-- src/rails_new.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3e8d207..f518277 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,8 +16,8 @@ fn main() { // read the content of the DOCKERFILE and store it in a variable let dockerfile = include_bytes!("../Dockerfile"); - let ruby_version = cli.ruby_version.unwrap(); - let rails_version = cli.rails_version.unwrap(); + let ruby_version = cli.ruby_version; + let rails_version = cli.rails_version; // Run docker build --build-arg RUBY_VERSION=$RUBY_VERSION --build-arg RAILS_VERSION=$RAILS_VERSION -t rails-new-$RUBY_VERSION-$RAILS_VERSION // passing the content of DOCKERFILE to the command stdin diff --git a/src/rails_new.rs b/src/rails_new.rs index 626dba4..b0ca823 100644 --- a/src/rails_new.rs +++ b/src/rails_new.rs @@ -7,9 +7,9 @@ pub struct Cli { /// arguments passed to `rails new` pub args: Vec, #[clap(long, short = 'u', default_value = "3.2.3")] - pub ruby_version: Option, + pub ruby_version: String, #[clap(long, short = 'r', default_value = "7.1.3")] - pub rails_version: Option, + pub rails_version: String, } #[test] From d69e0d079da4ff0818598f5f3f481e0c5cc66f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 19 Mar 2024 16:14:49 -0400 Subject: [PATCH 10/10] Write to stdin in a separate thread Avoid deadlock when the child process writes to stdout and fills the buffer. See https://github.com/rust-lang/rust/commit/ce2d95cd75adb5f75921536191b4dcfa41be1eff --- src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index f518277..91084e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,8 +25,10 @@ fn main() { .spawn() .expect("Failed to execute process"); - let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - stdin.write_all(dockerfile).unwrap(); + let mut stdin = child.stdin.take().expect("Failed to open stdin"); + std::thread::spawn(move || { + stdin.write_all(dockerfile).unwrap(); + }); let status = child.wait().expect("failed to wait on child");