Archived
1
0
Fork 0

refac: move commands to separate folder

This commit is contained in:
Dmitriy Pleshevskiy 2021-02-13 00:39:39 +03:00
parent 20a520b662
commit 40c0a43dab
10 changed files with 242 additions and 181 deletions

View file

@ -0,0 +1,28 @@
use crate::config::Config;
use crate::database;
use crate::opts::ApplyCommandOpt;
use crate::path::PathBuilder;
use crate::StdResult;
pub(crate) fn apply_sql(config: Config, opts: ApplyCommandOpt) -> StdResult<()> {
let database_connection_string = &config.database_connection_string()?;
let mut client = database::connect(database_connection_string)?;
let file_path = PathBuilder::from(config.directory_path())
.append(opts.file_name)
.default_extension("sql")
.build();
let content = std::fs::read_to_string(file_path)?;
match database::apply_sql(&mut client, &content) {
Ok(_) => {
println!("File was applied successfully");
}
Err(err) => {
println!("{}", err);
}
}
Ok(())
}

View file

@ -0,0 +1,23 @@
use crate::config::Config;
use crate::database;
use crate::StdResult;
pub(crate) fn downgrade_applied_migrations(config: Config) -> StdResult<()> {
let database_connection_string = &config.database_connection_string()?;
let mut client = database::connect(database_connection_string)?;
let applied_migrations = database::applied_migrations(&mut client)?;
let migrations = config.migrations()?;
if let Some(first_applied_migration) = applied_migrations.first() {
if let Some(migration) = migrations
.iter()
.find(|m| m.name() == first_applied_migration)
{
println!("downgrade {}...", migration.name());
migration.downgrade(&mut client)?;
}
}
Ok(())
}

View file

@ -0,0 +1,34 @@
use crate::config::Config;
use crate::config::MIGRA_TOML_FILENAME;
use crate::StdResult;
use std::path::PathBuf;
pub(crate) fn initialize_migra_manifest(config_path: Option<PathBuf>) -> StdResult<()> {
let config_path = config_path
.map(|mut config_path| {
let ext = config_path.extension();
if config_path.is_dir() || ext.is_none() {
config_path.push(MIGRA_TOML_FILENAME);
}
config_path
})
.unwrap_or_else(|| PathBuf::from(MIGRA_TOML_FILENAME));
if config_path.exists() {
println!("{} already exists", config_path.to_str().unwrap());
return Ok(());
}
if let Some(dirs) = config_path.parent() {
std::fs::create_dir_all(dirs)?;
}
let config = Config::default();
let content = toml::to_string(&config)?;
std::fs::write(&config_path, content)?;
println!("Created {}", config_path.to_str().unwrap());
Ok(())
}

View file

@ -0,0 +1,51 @@
use crate::config::Config;
use crate::database;
use crate::error::{ErrorKind, StdResult};
const EM_DASH: char = '—';
pub(crate) fn print_migration_lists(config: Config) -> StdResult<()> {
let applied_migrations = match config.database_connection_string() {
Ok(ref database_connection_string) => {
let mut client = database::connect(database_connection_string)?;
let applied_migrations = database::applied_migrations(&mut client)?;
println!("Applied migrations:");
if applied_migrations.is_empty() {
println!("{}", EM_DASH);
} else {
applied_migrations
.iter()
.rev()
.for_each(|name| println!("{}", name));
}
applied_migrations
}
Err(e) if *e.kind() == ErrorKind::MissedEnvVar(String::new()) => {
println!("{}", e.kind());
println!("No connection to database");
Vec::new()
}
Err(e) => panic!(e),
};
println!();
let pending_migrations = config
.migrations()?
.into_iter()
.filter(|m| !applied_migrations.contains(m.name()))
.collect::<Vec<_>>();
println!("Pending migrations:");
if pending_migrations.is_empty() {
println!("{}", EM_DASH);
} else {
pending_migrations.iter().for_each(|m| {
println!("{}", m.name());
});
}
Ok(())
}

View file

@ -0,0 +1,45 @@
use crate::opts::MakeCommandOpt;
use crate::path::PathBuilder;
use crate::Config;
use crate::StdResult;
use chrono::Local;
pub(crate) fn make_migration(config: Config, opts: MakeCommandOpt) -> StdResult<()> {
let now = Local::now().format("%y%m%d%H%M%S");
let migration_name: String = opts
.migration_name
.to_lowercase()
.chars()
.map(|c| match c {
'0'..='9' | 'a'..='z' => c,
_ => '_',
})
.collect();
let migration_dir_path = PathBuilder::from(config.migration_dir_path())
.append(format!("{}_{}", now, migration_name))
.build();
if !migration_dir_path.exists() {
std::fs::create_dir_all(&migration_dir_path)?;
}
let upgrade_migration_path = PathBuilder::from(&migration_dir_path)
.append("up.sql")
.build();
if !upgrade_migration_path.exists() {
std::fs::write(upgrade_migration_path, "-- Your SQL goes here\n\n")?;
}
let downgrade_migration_path = PathBuilder::from(&migration_dir_path)
.append("down.sql")
.build();
if !downgrade_migration_path.exists() {
std::fs::write(
downgrade_migration_path,
"-- This file should undo anything in `up.sql`\n\n",
)?;
}
Ok(())
}

View file

@ -0,0 +1,13 @@
mod apply;
mod downgrade;
mod init;
mod list;
mod make;
mod upgrade;
pub(crate) use apply::*;
pub(crate) use downgrade::*;
pub(crate) use init::*;
pub(crate) use list::*;
pub(crate) use make::*;
pub(crate) use upgrade::*;

View file

@ -0,0 +1,26 @@
use crate::database;
use crate::Config;
use crate::StdResult;
pub(crate) fn upgrade_pending_migrations(config: Config) -> StdResult<()> {
let database_connection_string = &config.database_connection_string()?;
let mut client = database::connect(database_connection_string)?;
let applied_migrations = database::applied_migrations(&mut client)?;
let migrations = config.migrations()?;
if migrations.is_empty() || migrations.last().map(|m| m.name()) == applied_migrations.first() {
println!("Up to date");
} else {
for m in migrations
.iter()
.filter(|m| !applied_migrations.contains(m.name()))
{
println!("upgrade {}...", m.name());
m.upgrade(&mut client)?;
}
}
Ok(())
}

View file

@ -5,15 +5,15 @@ use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{env, fs, io}; use std::{env, fs, io};
const MIGRA_TOML_FILENAME: &str = "Migra.toml"; pub(crate) const MIGRA_TOML_FILENAME: &str = "Migra.toml";
const DEFAULT_DATABASE_CONNECTION_ENV: &str = "$DATABASE_URL"; pub(crate) const DEFAULT_DATABASE_CONNECTION_ENV: &str = "$DATABASE_URL";
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub(crate) struct Config { pub(crate) struct Config {
#[serde(skip)] #[serde(skip)]
root: PathBuf, manifest_root: PathBuf,
directory: PathBuf, root: PathBuf,
#[serde(default)] #[serde(default)]
database: DatabaseConfig, database: DatabaseConfig,
@ -27,8 +27,8 @@ pub(crate) struct DatabaseConfig {
impl Default for Config { impl Default for Config {
fn default() -> Config { fn default() -> Config {
Config { Config {
root: PathBuf::new(), manifest_root: PathBuf::new(),
directory: PathBuf::from("database"), root: PathBuf::from("database"),
database: DatabaseConfig { database: DatabaseConfig {
connection: Some(String::from(DEFAULT_DATABASE_CONNECTION_ENV)), connection: Some(String::from(DEFAULT_DATABASE_CONNECTION_ENV)),
}, },
@ -73,7 +73,7 @@ impl Config {
let content = fs::read_to_string(&config_path)?; let content = fs::read_to_string(&config_path)?;
let mut config: Config = toml::from_str(&content).expect("Cannot parse Migra.toml"); let mut config: Config = toml::from_str(&content).expect("Cannot parse Migra.toml");
config.root = config_path config.manifest_root = config_path
.parent() .parent()
.unwrap_or_else(|| Path::new("")) .unwrap_or_else(|| Path::new(""))
.to_path_buf(); .to_path_buf();
@ -82,40 +82,12 @@ impl Config {
} }
} }
} }
pub fn initialize(config_path: Option<PathBuf>) -> Result<(), Box<dyn std::error::Error>> {
let config_path = config_path
.map(|mut config_path| {
let ext = config_path.extension();
if config_path.is_dir() || ext.is_none() {
config_path.push(MIGRA_TOML_FILENAME);
}
config_path
})
.unwrap_or_else(|| PathBuf::from(MIGRA_TOML_FILENAME));
if config_path.exists() {
println!("{} already exists", config_path.to_str().unwrap());
return Ok(());
} else if let Some(dirs) = config_path.parent() {
fs::create_dir_all(dirs)?;
}
let config = Config::default();
let content = toml::to_string(&config)?;
fs::write(&config_path, content)?;
println!("Created {}", config_path.to_str().unwrap());
Ok(())
}
} }
impl Config { impl Config {
pub fn directory_path(&self) -> PathBuf { pub fn directory_path(&self) -> PathBuf {
PathBuilder::from(&self.root) PathBuilder::from(&self.manifest_root)
.append(&self.directory) .append(&self.root)
.build() .build()
} }

View file

@ -3,6 +3,7 @@ use std::fmt;
use std::mem; use std::mem;
use std::result; use std::result;
pub type StdResult<T> = result::Result<T, Box<dyn std::error::Error>>;
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)] #[derive(Debug)]

View file

@ -1,5 +1,6 @@
#![deny(clippy::all)] #![deny(clippy::all)]
mod commands;
mod config; mod config;
mod database; mod database;
mod error; mod error;
@ -7,169 +8,36 @@ mod migration;
mod opts; mod opts;
mod path; mod path;
use chrono::Local; use crate::error::StdResult;
use config::Config; use config::Config;
use error::ErrorKind; use opts::{AppOpt, Command, StructOpt};
use opts::{AppOpt, ApplyCommandOpt, Command, MakeCommandOpt, StructOpt};
use path::PathBuilder;
use std::fs;
const EM_DASH: char = '—'; fn main() -> StdResult<()> {
fn main() -> Result<(), Box<dyn std::error::Error>> {
let opt = AppOpt::from_args(); let opt = AppOpt::from_args();
match opt.command { match opt.command {
Command::Init => { Command::Init => {
Config::initialize(opt.config)?; commands::initialize_migra_manifest(opt.config)?;
} }
Command::Apply(ApplyCommandOpt { file_name }) => { Command::Apply(opts) => {
let config = Config::read(opt.config)?; let config = Config::read(opt.config)?;
commands::apply_sql(config, opts)?;
let database_connection_string = &config.database_connection_string()?;
let mut client = database::connect(database_connection_string)?;
let file_path = PathBuilder::from(config.directory_path())
.append(file_name)
.default_extension("sql")
.build();
let content = fs::read_to_string(file_path)?;
match database::apply_sql(&mut client, &content) {
Ok(_) => {
println!("File was applied successfully")
} }
Err(err) => { Command::Make(opts) => {
println!("{}", err)
}
}
}
Command::Make(MakeCommandOpt { migration_name }) => {
let config = Config::read(opt.config)?; let config = Config::read(opt.config)?;
commands::make_migration(config, opts)?;
let now = Local::now().format("%y%m%d%H%M%S");
let migration_name: String = migration_name
.to_lowercase()
.chars()
.map(|c| match c {
'0'..='9' | 'a'..='z' => c,
_ => '_',
})
.collect();
let migration_dir_path = PathBuilder::from(config.migration_dir_path())
.append(format!("{}_{}", now, migration_name))
.build();
if !migration_dir_path.exists() {
fs::create_dir_all(&migration_dir_path)?;
}
let upgrade_migration_path = PathBuilder::from(&migration_dir_path)
.append("up.sql")
.build();
if !upgrade_migration_path.exists() {
fs::write(upgrade_migration_path, "-- Your SQL goes here\n\n")?;
}
let downgrade_migration_path = PathBuilder::from(&migration_dir_path)
.append("down.sql")
.build();
if !downgrade_migration_path.exists() {
fs::write(
downgrade_migration_path,
"-- This file should undo anything in `up.sql`\n\n",
)?;
}
} }
Command::List => { Command::List => {
let config = Config::read(opt.config)?; let config = Config::read(opt.config)?;
commands::print_migration_lists(config)?;
let applied_migrations = match config.database_connection_string() {
Ok(ref database_connection_string) => {
let mut client = database::connect(database_connection_string)?;
let applied_migrations = database::applied_migrations(&mut client)?;
println!("Applied migrations:");
if applied_migrations.is_empty() {
println!("{}", EM_DASH);
} else {
applied_migrations
.iter()
.rev()
.for_each(|name| println!("{}", name));
}
applied_migrations
}
Err(e) if *e.kind() == ErrorKind::MissedEnvVar(String::new()) => {
println!("{}", e.kind());
println!("No connection to database");
Vec::new()
}
Err(e) => panic!(e),
};
println!();
let pending_migrations = config
.migrations()?
.into_iter()
.filter(|m| !applied_migrations.contains(m.name()))
.collect::<Vec<_>>();
println!("Pending migrations:");
if pending_migrations.is_empty() {
println!("{}", EM_DASH);
} else {
pending_migrations.iter().for_each(|m| {
println!("{}", m.name());
});
}
} }
Command::Upgrade => { Command::Upgrade => {
let config = Config::read(opt.config)?; let config = Config::read(opt.config)?;
commands::upgrade_pending_migrations(config)?;
let database_connection_string = &config.database_connection_string()?;
let mut client = database::connect(database_connection_string)?;
let applied_migrations = database::applied_migrations(&mut client)?;
let migrations = config.migrations()?;
if migrations.is_empty()
|| migrations.last().map(|m| m.name()) == applied_migrations.first()
{
println!("Up to date");
} else {
for m in migrations
.iter()
.filter(|m| !applied_migrations.contains(m.name()))
{
println!("upgrade {}...", m.name());
m.upgrade(&mut client)?;
}
}
} }
Command::Downgrade => { Command::Downgrade => {
let config = Config::read(opt.config)?; let config = Config::read(opt.config)?;
commands::downgrade_applied_migrations(config)?;
let database_connection_string = &config.database_connection_string()?;
let mut client = database::connect(database_connection_string)?;
let applied_migrations = database::applied_migrations(&mut client)?;
let migrations = config.migrations()?;
if let Some(first_applied_migration) = applied_migrations.first() {
if let Some(migration) = migrations
.iter()
.find(|m| m.name() == first_applied_migration)
{
println!("downgrade {}...", migration.name());
migration.downgrade(&mut client)?;
}
}
} }
} }