From d6e7b03d3557140e28553cd407a450ad64876b58 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Sat, 30 Jul 2022 17:31:05 +0300 Subject: [PATCH] add namespaces - extract domain logic - extract cli logic - cover domain logic with unit tests --- .env.example | 22 +++- .gitignore | 3 +- Cargo.toml | 2 + README.md | 15 ++- src/cli.rs | 4 +- src/cli/switch.rs | 21 ++++ src/domain.rs | 26 +++++ src/domain/switch.rs | 136 +++++++++++++++++++++++ src/main.rs | 58 +--------- test_data/all_env | 36 ++++++ test_data/base_env | 36 ++++++ test_data/should_enable_local | 36 ++++++ test_data/should_enable_staging | 36 ++++++ test_data/should_use_debug_in_staging | 36 ++++++ test_data/should_use_staging_db_in_local | 36 ++++++ 15 files changed, 439 insertions(+), 64 deletions(-) create mode 100644 src/cli/switch.rs create mode 100644 src/domain.rs create mode 100644 src/domain/switch.rs create mode 100644 test_data/all_env create mode 100644 test_data/base_env create mode 100644 test_data/should_enable_local create mode 100644 test_data/should_enable_staging create mode 100644 test_data/should_use_debug_in_staging create mode 100644 test_data/should_use_staging_db_in_local diff --git a/.env.example b/.env.example index 0e1e19c..aa5e51b 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,17 @@ VITE_STRIPE_PK= VITE_SENTRY_ENABLED= VITE_SENTRY_DSN= -# staging / production +### local,staging VITE_SENTRY_ENV=staging +### prod +# VITE_SENTRY_ENV=production + +### db:local +DATABASE_URL='postgres://user:password@localhost:5432/core' +### db:staging +# DATABASE_URL='postgres://user:password@localhost:5555/core' +### db:prod +# DATABASE_URL='postgres://user:password@localhost:6666/core' CYPRESS_CRED_EMAIL='owner@cypress.test' CYPRESS_CRED_PASSWORD='12345678' @@ -10,13 +19,18 @@ CYPRESS_TEST_PRIVATE_TOKEN='12345678' ### local VITE_API_BASE_URL='http://localhost:1337/api' -VITE_API_BASE_URL='http://localhost:3000' +VITE_SITE_URL='http://localhost:3000' CYPRESS_API_BASE_URL='http://localhost:1337/api' # this variable will not switch -DEBUG=1 +GRAPHQL_PLAYGROUND=1 ### staging # VITE_API_BASE_URL='https://app.develop.staging.example.com/api' -# VITE_BMM_SITE_URL='https://www.develop.staging.example.com' +# VITE_SITE_URL='https://www.develop.staging.example.com' # CYPRESS_API_BASE_URL='https://app.develop.staging.example.com/api' + +### debug:on,local +# DEBUG=1 +### debug:off,staging +# DEBUG= diff --git a/.gitignore b/.gitignore index 83a00ee..cb094d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target -.env +.env* +!.env.example diff --git a/Cargo.toml b/Cargo.toml index a714bd2..6e71447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,5 @@ categories = ["command-line-interface", "config", "development-tools"] [dependencies] clap = { version = "3.2.15", features = ["derive"] } + +[features] diff --git a/README.md b/README.md index fe351b3..4e065f9 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,20 @@ You can see the [full example]. # Usage +Basic usage + ```sh cp .env.example .env -vnetod local # choose local state -vnetod staging # choose staging state -vnetod - # disable all states +vnetod local # enable local section +vnetod staging # enable staging section +vnetod local debug # enable local and debug sections +vnetod # disable all sections +``` + +Namespaces + +```sh +vnetod db:local debug:on ``` # License diff --git a/src/cli.rs b/src/cli.rs index 46d599f..e236cf0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -14,6 +14,8 @@ //! along with vnetod. If not, see . //! +pub mod switch; + use std::path::PathBuf; use clap::Parser; @@ -40,5 +42,5 @@ pub struct Args { pub output: Option, #[clap(value_parser)] - pub section_names: Vec, + pub sections: Vec, } diff --git a/src/cli/switch.rs b/src/cli/switch.rs new file mode 100644 index 0000000..e9829bd --- /dev/null +++ b/src/cli/switch.rs @@ -0,0 +1,21 @@ +use crate::{cli::Args, domain}; +use std::{cell::RefCell, fs::File}; + +#[derive(Debug)] +pub enum Error { + OpenFile, + Switch(domain::switch::Error), +} + +pub fn execute(args: &Args) -> Result<(), Error> { + let reader = File::open(&args.file).map_err(|_| Error::OpenFile)?; + let writer = + File::create(args.output.as_ref().unwrap_or(&args.file)).map_err(|_| Error::OpenFile)?; + + domain::switch::execute(domain::switch::Request { + reader: RefCell::new(reader), + writer, + sections: &args.sections, + }) + .map_err(Error::Switch) +} diff --git a/src/domain.rs b/src/domain.rs new file mode 100644 index 0000000..cfa1899 --- /dev/null +++ b/src/domain.rs @@ -0,0 +1,26 @@ +pub mod switch; + +#[derive(Debug, Clone, Eq)] +struct Section { + namespace: Option, + name: String, +} + +impl<'a> From<&'a str> for Section { + fn from(s: &'a str) -> Self { + let (ns, name) = s + .trim() + .split_once(':') + .map_or_else(|| (None, s), |(ns, name)| (Some(ns), name)); + Self { + namespace: ns.map(String::from), + name: String::from(name), + } + } +} + +impl PartialEq for Section { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && (self.namespace.is_none() || self.namespace == other.namespace) + } +} diff --git a/src/domain/switch.rs b/src/domain/switch.rs new file mode 100644 index 0000000..0f0089c --- /dev/null +++ b/src/domain/switch.rs @@ -0,0 +1,136 @@ +use std::cell::RefCell; +use std::io::{BufWriter, Read, Write}; + +use super::Section; + +#[derive(Debug)] +pub enum Error { + ReadData, + WriteData, +} + +pub struct Request<'args, R, W> +where + R: Read, + W: Write, +{ + pub reader: RefCell, + pub writer: W, + pub sections: &'args [String], +} + +pub fn execute(req: Request) -> Result<(), Error> +where + R: Read, + W: Write, +{ + let mut content = String::new(); + req.reader + .borrow_mut() + .read_to_string(&mut content) + .map_err(|_| Error::ReadData)?; + + let mut writer = BufWriter::new(req.writer); + + let choose_sections = req + .sections + .iter() + .map(|s| Section::from(s.as_str())) + .collect::>(); + + dbg!(&choose_sections); + + let choose_namespaces = choose_sections + .iter() + .map(|s| s.namespace.clone()) + .collect::>(); + + let mut current_sections: Option> = None; + + for line in content.split_inclusive('\n') { + let new_line = if is_section_end(line) { + current_sections = None; + line.to_string() + } else if let Some(section_info) = line.strip_prefix("### ") { + current_sections = section_info + .split_whitespace() + .next() + .map(|r| r.split(',').map(Section::from).collect()); + line.to_string() + } else if let Some(cur_sections) = current_sections.clone() { + let trimmed_line = line.trim_start_matches(['#', ' ']); + if cur_sections.iter().any(|s| choose_sections.contains(s)) { + String::from(trimmed_line) + } else { + format!("# {}", trimmed_line) + } + } else { + line.to_string() + }; + + dbg!(line, ¤t_sections, &new_line); + + writer + .write_all(new_line.as_bytes()) + .map_err(|_| Error::WriteData)?; + } + + writer.flush().map_err(|_| Error::WriteData) +} + +fn is_section_end(line: &str) -> bool { + line.trim().is_empty() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::cell::RefCell; + use std::io::Cursor; + + const BASE_ENV: &str = include_str!("../../test_data/base_env"); + + fn make_test(input: &str, expected_output: &str, sections: Vec<&str>) { + let reader = RefCell::new(Cursor::new(input)); + let mut output_data = vec![]; + let writer = Cursor::new(&mut output_data); + + match execute(Request { + reader, + writer, + sections: §ions.into_iter().map(String::from).collect::>(), + }) { + Ok(()) => { + let output = String::from_utf8(output_data).unwrap(); + assert_eq!( + output.lines().collect::>(), + expected_output.lines().collect::>() + ); + } + _ => unreachable!(), + } + } + + #[test] + fn should_disable_all_sections() { + make_test(include_str!("../../test_data/all_env"), BASE_ENV, vec![]); + } + + #[test] + fn should_enable_local_sections() { + make_test( + BASE_ENV, + include_str!("../../test_data/should_enable_local"), + vec!["local"], + ); + } + + #[test] + fn should_enable_staging_sections() { + make_test( + BASE_ENV, + include_str!("../../test_data/should_enable_staging"), + vec!["staging"], + ); + } +} diff --git a/src/main.rs b/src/main.rs index a9029f6..8c2fc7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,66 +16,14 @@ #![deny(clippy::all, clippy::pedantic)] mod cli; - -use std::{ - fs::File, - io::{BufWriter, Write}, -}; +mod domain; use clap::Parser; -fn main() -> Result<(), Error> { +fn main() -> Result<(), cli::switch::Error> { let cli = cli::Args::parse(); - change_env_layout(&cli)?; + cli::switch::execute(&cli)?; Ok(()) } - -fn change_env_layout(args: &cli::Args) -> Result<(), Error> { - let content = std::fs::read_to_string(&args.file).map_err(|_| Error::OpenFile)?; - - let mut writer = File::create(args.output.as_ref().unwrap_or(&args.file)) - .map_err(|_| Error::OpenFile) - .map(BufWriter::new)?; - - let mut current_section_name: Option> = None; - - for line in content.split_inclusive('\n') { - let new_line = if is_section_end(line) { - current_section_name = None; - line.to_string() - } else if let Some(section_info) = line.strip_prefix("### ") { - current_section_name = section_info - .split_whitespace() - .next() - .map(|r| r.split(',').map(str::trim).map(String::from).collect()); - line.to_string() - } else if let Some(cur_name) = current_section_name.clone() { - let trimmed_line = line.trim_start_matches(['#', ' ']); - if args.section_names.iter().any(|sn| cur_name.contains(sn)) { - String::from(trimmed_line) - } else { - format!("# {}", trimmed_line) - } - } else { - line.to_string() - }; - - writer - .write_all(new_line.as_bytes()) - .map_err(|_| Error::WriteData)?; - } - - writer.flush().map_err(|_| Error::WriteData) -} - -fn is_section_end(line: &str) -> bool { - line.trim().is_empty() -} - -#[derive(Debug)] -enum Error { - OpenFile, - WriteData, -} diff --git a/test_data/all_env b/test_data/all_env new file mode 100644 index 0000000..1583d32 --- /dev/null +++ b/test_data/all_env @@ -0,0 +1,36 @@ +VITE_STRIPE_PK= +VITE_SENTRY_ENABLED= +VITE_SENTRY_DSN= +### local,staging +VITE_SENTRY_ENV=staging +### prod +VITE_SENTRY_ENV=production + +### db:local +DATABASE_URL='postgres://user:password@localhost:5432/core' +### db:staging +DATABASE_URL='postgres://user:password@localhost:5555/core' +### db:prod +DATABASE_URL='postgres://user:password@localhost:6666/core' + +CYPRESS_CRED_EMAIL='owner@cypress.test' +CYPRESS_CRED_PASSWORD='12345678' +CYPRESS_TEST_PRIVATE_TOKEN='12345678' + +### local +VITE_API_BASE_URL='http://localhost:1337/api' +VITE_SITE_URL='http://localhost:3000' +CYPRESS_API_BASE_URL='http://localhost:1337/api' + +# this variable will not switch +GRAPHQL_PLAYGROUND=1 + +### staging +VITE_API_BASE_URL='https://app.develop.staging.example.com/api' +VITE_SITE_URL='https://www.develop.staging.example.com' +CYPRESS_API_BASE_URL='https://app.develop.staging.example.com/api' + +### debug:on,local +DEBUG=1 +### debug:off,staging +DEBUG= diff --git a/test_data/base_env b/test_data/base_env new file mode 100644 index 0000000..6888eb6 --- /dev/null +++ b/test_data/base_env @@ -0,0 +1,36 @@ +VITE_STRIPE_PK= +VITE_SENTRY_ENABLED= +VITE_SENTRY_DSN= +### local,staging +# VITE_SENTRY_ENV=staging +### prod +# VITE_SENTRY_ENV=production + +### db:local +# DATABASE_URL='postgres://user:password@localhost:5432/core' +### db:staging +# DATABASE_URL='postgres://user:password@localhost:5555/core' +### db:prod +# DATABASE_URL='postgres://user:password@localhost:6666/core' + +CYPRESS_CRED_EMAIL='owner@cypress.test' +CYPRESS_CRED_PASSWORD='12345678' +CYPRESS_TEST_PRIVATE_TOKEN='12345678' + +### local +# VITE_API_BASE_URL='http://localhost:1337/api' +# VITE_SITE_URL='http://localhost:3000' +# CYPRESS_API_BASE_URL='http://localhost:1337/api' + +# this variable will not switch +GRAPHQL_PLAYGROUND=1 + +### staging +# VITE_API_BASE_URL='https://app.develop.staging.example.com/api' +# VITE_SITE_URL='https://www.develop.staging.example.com' +# CYPRESS_API_BASE_URL='https://app.develop.staging.example.com/api' + +### debug:on,local +# DEBUG=1 +### debug:off,staging +# DEBUG= diff --git a/test_data/should_enable_local b/test_data/should_enable_local new file mode 100644 index 0000000..d6e4df7 --- /dev/null +++ b/test_data/should_enable_local @@ -0,0 +1,36 @@ +VITE_STRIPE_PK= +VITE_SENTRY_ENABLED= +VITE_SENTRY_DSN= +### local,staging +VITE_SENTRY_ENV=staging +### prod +# VITE_SENTRY_ENV=production + +### db:local +DATABASE_URL='postgres://user:password@localhost:5432/core' +### db:staging +# DATABASE_URL='postgres://user:password@localhost:5555/core' +### db:prod +# DATABASE_URL='postgres://user:password@localhost:6666/core' + +CYPRESS_CRED_EMAIL='owner@cypress.test' +CYPRESS_CRED_PASSWORD='12345678' +CYPRESS_TEST_PRIVATE_TOKEN='12345678' + +### local +VITE_API_BASE_URL='http://localhost:1337/api' +VITE_SITE_URL='http://localhost:3000' +CYPRESS_API_BASE_URL='http://localhost:1337/api' + +# this variable will not switch +GRAPHQL_PLAYGROUND=1 + +### staging +# VITE_API_BASE_URL='https://app.develop.staging.example.com/api' +# VITE_SITE_URL='https://www.develop.staging.example.com' +# CYPRESS_API_BASE_URL='https://app.develop.staging.example.com/api' + +### debug:on,local +DEBUG=1 +### debug:off,staging +# DEBUG= diff --git a/test_data/should_enable_staging b/test_data/should_enable_staging new file mode 100644 index 0000000..a6b48fc --- /dev/null +++ b/test_data/should_enable_staging @@ -0,0 +1,36 @@ +VITE_STRIPE_PK= +VITE_SENTRY_ENABLED= +VITE_SENTRY_DSN= +### local,staging +VITE_SENTRY_ENV=staging +### prod +# VITE_SENTRY_ENV=production + +### db:local +# DATABASE_URL='postgres://user:password@localhost:5432/core' +### db:staging +DATABASE_URL='postgres://user:password@localhost:5555/core' +### db:prod +# DATABASE_URL='postgres://user:password@localhost:6666/core' + +CYPRESS_CRED_EMAIL='owner@cypress.test' +CYPRESS_CRED_PASSWORD='12345678' +CYPRESS_TEST_PRIVATE_TOKEN='12345678' + +### local +# VITE_API_BASE_URL='http://localhost:1337/api' +# VITE_SITE_URL='http://localhost:3000' +# CYPRESS_API_BASE_URL='http://localhost:1337/api' + +# this variable will not switch +GRAPHQL_PLAYGROUND=1 + +### staging +VITE_API_BASE_URL='https://app.develop.staging.example.com/api' +VITE_SITE_URL='https://www.develop.staging.example.com' +CYPRESS_API_BASE_URL='https://app.develop.staging.example.com/api' + +### debug:on,local +# DEBUG=1 +### debug:off,staging +DEBUG= diff --git a/test_data/should_use_debug_in_staging b/test_data/should_use_debug_in_staging new file mode 100644 index 0000000..6681dda --- /dev/null +++ b/test_data/should_use_debug_in_staging @@ -0,0 +1,36 @@ +VITE_STRIPE_PK= +VITE_SENTRY_ENABLED= +VITE_SENTRY_DSN= +### local,staging +VITE_SENTRY_ENV=staging +### prod +# VITE_SENTRY_ENV=production + +### db:local +# DATABASE_URL='postgres://user:password@localhost:5432/core' +### db:staging +DATABASE_URL='postgres://user:password@localhost:5555/core' +### db:prod +# DATABASE_URL='postgres://user:password@localhost:6666/core' + +CYPRESS_CRED_EMAIL='owner@cypress.test' +CYPRESS_CRED_PASSWORD='12345678' +CYPRESS_TEST_PRIVATE_TOKEN='12345678' + +### local +# VITE_API_BASE_URL='http://localhost:1337/api' +# VITE_SITE_URL='http://localhost:3000' +# CYPRESS_API_BASE_URL='http://localhost:1337/api' + +# this variable will not switch +GRAPHQL_PLAYGROUND=1 + +### staging +VITE_API_BASE_URL='https://app.develop.staging.example.com/api' +VITE_SITE_URL='https://www.develop.staging.example.com' +CYPRESS_API_BASE_URL='https://app.develop.staging.example.com/api' + +### debug:on,local +DEBUG=1 +### debug:off,staging +# DEBUG= diff --git a/test_data/should_use_staging_db_in_local b/test_data/should_use_staging_db_in_local new file mode 100644 index 0000000..8cb508a --- /dev/null +++ b/test_data/should_use_staging_db_in_local @@ -0,0 +1,36 @@ +VITE_STRIPE_PK= +VITE_SENTRY_ENABLED= +VITE_SENTRY_DSN= +### local,staging +VITE_SENTRY_ENV=staging +### prod +# VITE_SENTRY_ENV=production + +### db:local +# DATABASE_URL='postgres://user:password@localhost:5432/core' +### db:staging +DATABASE_URL='postgres://user:password@localhost:5555/core' +### db:prod +# DATABASE_URL='postgres://user:password@localhost:6666/core' + +CYPRESS_CRED_EMAIL='owner@cypress.test' +CYPRESS_CRED_PASSWORD='12345678' +CYPRESS_TEST_PRIVATE_TOKEN='12345678' + +### local +VITE_API_BASE_URL='http://localhost:1337/api' +VITE_SITE_URL='http://localhost:3000' +CYPRESS_API_BASE_URL='http://localhost:1337/api' + +# this variable will not switch +GRAPHQL_PLAYGROUND=1 + +### staging +# VITE_API_BASE_URL='https://app.develop.staging.example.com/api' +# VITE_SITE_URL='https://www.develop.staging.example.com' +# CYPRESS_API_BASE_URL='https://app.develop.staging.example.com/api' + +### debug:on,local +DEBUG=1 +### debug:off,staging +# DEBUG=