Skip to content

Commit

Permalink
Add seagull init command
Browse files Browse the repository at this point in the history
  • Loading branch information
Wahuh committed May 4, 2020
1 parent 1abc7d1 commit 70fd7da
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Generated by Cargo
# will have compiled files and executables
/target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk
15 changes: 15 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "cargo run",
"type": "shell",
"command": "cargo",
"args": ["run"],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
26 changes: 26 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "seagull"
version = "0.1.0"
authors = ["Thanh Doan <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
structopt = "0.3.13"
failure = "0.1.7"
exitfailure = "0.5.1"
chrono = "0.4"
termcolor = "1.1"
colored = "1.9"

toml = "0.5.6"
serde = { version = "1.0", features = ["derive"] }

postgres = "0.17.2"

[dev-dependencies]
tempfile = "3"
rusty-hook = "^0.11.1"
assert_cmd = "1.0.1"
predicates = "1"
2 changes: 2 additions & 0 deletions rusty-hook.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[hooks]
pre-commit = "cargo fmt -- --check"
147 changes: 147 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use crate::error::CliError;
use serde::{Deserialize, Serialize};
use std::{
fs,
io::{self, Write},
path::Path,
};

#[derive(Deserialize, Serialize)]
pub struct Database {
#[serde(default)]
pub host: String,

#[serde(default)]
pub password: String,

#[serde(default)]
pub port: i32,

#[serde(default)]
pub username: String,
}

#[derive(Deserialize, Serialize)]
pub struct Migrations {
#[serde(default)]
pub dir_path: String,
}

#[derive(Deserialize, Serialize)]
pub struct Config {
#[serde(default)]
pub migrations: Migrations,

#[serde(default)]
pub database: Database,
}

impl Config {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config, CliError> {
let contents = fs::read_to_string(path)?;
let config: Config = toml::from_str(&contents)?;
Ok(config)
}

pub fn from_input() -> Result<Config, CliError> {
print!("What's your database host? ");
io::stdout().flush()?;
let mut host = String::new();
io::stdin().read_line(&mut host)?;

print!("What's your database username? ");
io::stdout().flush()?;
let mut username = String::new();
io::stdin().read_line(&mut username)?;

print!("What's your database password? ");
io::stdout().flush()?;
let mut password = String::new();
io::stdin().read_line(&mut password)?;

print!("What's your database port? ");
io::stdout().flush()?;
let mut port = String::new();
io::stdin().read_line(&mut port)?;
let port: i32 = port
.trim()
.parse()
.expect("Please enter a positive port number");

let config = Config {
database: Database {
host: host.trim().to_string(),
username: username.trim().to_string(),
port,
password: password.trim().to_string(),
},
migrations: Migrations::default(),
};
Ok(config)
}
}

impl Default for Config {
fn default() -> Self {
Config {
migrations: Migrations {
dir_path: String::from("migrations"),
},
database: Database {
host: String::from("postgres"),
username: String::from("postgres"),
password: String::from("postgres"),
port: 5432,
},
}
}
}

impl Default for Database {
fn default() -> Self {
Database {
host: String::from("postgres"),
username: String::from("postgres"),
password: String::from("postgres"),
port: 5432,
}
}
}

impl Default for Migrations {
fn default() -> Self {
Migrations {
dir_path: String::from("migrations"),
}
}
}

#[cfg(test)]
mod tests {
use super::Config;
use crate::error::CliError;
use io::Write;
use std::io;
use tempfile::NamedTempFile;

#[test]
fn it_reads_from_toml_config_file() -> Result<(), CliError> {
let config = b"
[database] \n
host = \"postgres_host\" \n
port = 6969 \n
username = \"Chuck\" \n
password = \"Norris\" \n
";
let mut file = NamedTempFile::new()?;
file.write_all(config)?;

let config = Config::from_file(file.path())?;
assert_eq!("postgres_host", config.database.host);
assert_eq!(6969, config.database.port);
assert_eq!("Chuck", config.database.username);
assert_eq!("Norris", config.database.password);
assert_eq!("migrations", config.migrations.dir_path);
Ok(())
}
}
46 changes: 46 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::{error, fmt, io};

#[derive(Debug)]
pub enum CliError {
Io(io::Error),
TomlSer(toml::ser::Error),
TomlDer(toml::de::Error),
}

impl fmt::Display for CliError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
CliError::Io(ref err) => write!(f, "IO error: {}", err),
CliError::TomlSer(ref err) => write!(f, "TOML Serialize error: {}", err),
CliError::TomlDer(ref err) => write!(f, "TOML Deserialize error: {}", err),
}
}
}

impl error::Error for CliError {
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
CliError::Io(ref err) => Some(err),
CliError::TomlDer(ref err) => Some(err),
CliError::TomlSer(ref err) => Some(err),
}
}
}

impl From<io::Error> for CliError {
fn from(err: io::Error) -> CliError {
CliError::Io(err)
}
}

impl From<toml::de::Error> for CliError {
fn from(err: toml::de::Error) -> CliError {
CliError::TomlDer(err)
}
}

impl From<toml::ser::Error> for CliError {
fn from(err: toml::ser::Error) -> CliError {
CliError::TomlSer(err)
}
}
10 changes: 10 additions & 0 deletions src/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use crate::{config::Config, error::CliError};
use std::{fs::File, io::Write};

pub fn scaffold_config_file() -> Result<(), CliError> {
let config = Config::from_input()?;
let toml = toml::to_string(&config)?;
let mut file = File::create("seagull.toml").unwrap();
file.write_all(toml.as_bytes())?;
Ok(())
}
32 changes: 32 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
mod config;
mod error;
mod init;
mod poop;

use error::CliError;
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(name = "seagull", about = "PostgreSQL migration tool")]
pub enum Seagull {
#[structopt(name = "poop")]
Poop {
description: String,
},
Migrate {},
Init {},
}

fn main() -> Result<(), CliError> {
let args = Seagull::from_args();

match args {
Seagull::Poop { description } => {}
Seagull::Migrate {} => {}
Seagull::Init {} => {
init::scaffold_config_file()?;
}
}

Ok(())
}

0 comments on commit 70fd7da

Please sign in to comment.