add namespaces
- extract domain logic - extract cli logic - cover domain logic with unit tests
This commit is contained in:
parent
4cd7087f13
commit
d6e7b03d35
15 changed files with 439 additions and 64 deletions
22
.env.example
22
.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=
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
/target
|
||||
|
||||
.env
|
||||
.env*
|
||||
!.env.example
|
||||
|
|
|
@ -12,3 +12,5 @@ categories = ["command-line-interface", "config", "development-tools"]
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "3.2.15", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
|
|
15
README.md
15
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
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
//! along with vnetod. If not, see <https://www.gnu.org/licenses/>.
|
||||
//!
|
||||
|
||||
pub mod switch;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
@ -40,5 +42,5 @@ pub struct Args {
|
|||
pub output: Option<PathBuf>,
|
||||
|
||||
#[clap(value_parser)]
|
||||
pub section_names: Vec<String>,
|
||||
pub sections: Vec<String>,
|
||||
}
|
||||
|
|
21
src/cli/switch.rs
Normal file
21
src/cli/switch.rs
Normal file
|
@ -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)
|
||||
}
|
26
src/domain.rs
Normal file
26
src/domain.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
pub mod switch;
|
||||
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
struct Section {
|
||||
namespace: Option<String>,
|
||||
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)
|
||||
}
|
||||
}
|
136
src/domain/switch.rs
Normal file
136
src/domain/switch.rs
Normal file
|
@ -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<R>,
|
||||
pub writer: W,
|
||||
pub sections: &'args [String],
|
||||
}
|
||||
|
||||
pub fn execute<R, W>(req: Request<R, W>) -> 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::<Vec<_>>();
|
||||
|
||||
dbg!(&choose_sections);
|
||||
|
||||
let choose_namespaces = choose_sections
|
||||
.iter()
|
||||
.map(|s| s.namespace.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut current_sections: Option<Vec<Section>> = 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::<Vec<_>>(),
|
||||
}) {
|
||||
Ok(()) => {
|
||||
let output = String::from_utf8(output_data).unwrap();
|
||||
assert_eq!(
|
||||
output.lines().collect::<Vec<_>>(),
|
||||
expected_output.lines().collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
_ => 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"],
|
||||
);
|
||||
}
|
||||
}
|
58
src/main.rs
58
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<Vec<String>> = 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,
|
||||
}
|
||||
|
|
36
test_data/all_env
Normal file
36
test_data/all_env
Normal file
|
@ -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=
|
36
test_data/base_env
Normal file
36
test_data/base_env
Normal file
|
@ -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=
|
36
test_data/should_enable_local
Normal file
36
test_data/should_enable_local
Normal file
|
@ -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=
|
36
test_data/should_enable_staging
Normal file
36
test_data/should_enable_staging
Normal file
|
@ -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=
|
36
test_data/should_use_debug_in_staging
Normal file
36
test_data/should_use_debug_in_staging
Normal file
|
@ -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=
|
36
test_data/should_use_staging_db_in_local
Normal file
36
test_data/should_use_staging_db_in_local
Normal file
|
@ -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=
|
Loading…
Reference in a new issue