feat: add init command

This commit is contained in:
DataHearth 2022-12-07 00:18:33 +01:00
parent 5ec04478fe
commit 60fe830b58
No known key found for this signature in database
GPG Key ID: E88FD356ACC5F3C4
5 changed files with 406 additions and 3 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
pyproject.toml

187
Cargo.lock generated
View File

@ -2,6 +2,21 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -14,6 +29,12 @@ version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.0.29"
@ -51,6 +72,37 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "console"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"terminal_size",
"unicode-width",
"winapi",
]
[[package]]
name = "dialoguer"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92e7e37ecef6857fdc0c0c5d42fd5b0938e46590c2183cc92dd310a6d078eb1"
dependencies = [
"console",
"tempfile",
"zeroize",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "errno"
version = "0.2.8"
@ -72,6 +124,15 @@ dependencies = [
"libc",
]
[[package]]
name = "fastrand"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [
"instant",
]
[[package]]
name = "heck"
version = "0.4.0"
@ -87,6 +148,15 @@ dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.3"
@ -109,6 +179,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.137"
@ -121,6 +197,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "once_cell"
version = "1.16.0"
@ -170,7 +252,12 @@ dependencies = [
name = "pynit"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"dialoguer",
"regex",
"serde",
"toml",
]
[[package]]
@ -182,6 +269,41 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "rustix"
version = "0.36.4"
@ -196,6 +318,26 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "serde"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -213,6 +355,20 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"
@ -222,12 +378,37 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "version_check"
version = "0.9.4"
@ -321,3 +502,9 @@ name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "zeroize"
version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"

View File

@ -4,9 +4,14 @@ version = "0.1.0"
edition = "2021"
authors = ["Antoine Langlois <antoine.l@antoine-langlois.net>"]
license = "GPL-3.0-or-later"
description = "small CLI tool to initialize a python project"
description = "Small CLI tool to initialize a python project"
homepage = "https://gitea.antoine-langlois.net/DataHeart/pynit"
repository = "https://gitea.antoine-langlois.net/DataHeart/pynit"
[dependencies]
anyhow = "1.0"
clap = { version = "4.0", features = ["derive"] }
dialoguer = "0.10"
regex = "1.7"
serde = { version = "1.0", features = ["derive"] }
toml = "0.5"

171
src/init.rs Normal file
View File

@ -0,0 +1,171 @@
use std::{collections::HashMap, env};
use anyhow::Result;
use dialoguer::{theme::ColorfulTheme, Input};
use regex::Regex;
use serde::Serialize;
use toml::Value;
#[derive(Debug, Serialize)]
#[serde(rename_all(serialize = "kebab-case"))]
pub struct Pyproject {
pub build_system: BuildSystem,
pub project: Project,
}
#[derive(Default, Debug, Serialize)]
#[serde(rename_all(serialize = "kebab-case"))]
pub struct BuildSystem {
pub requires: Vec<String>,
pub build_backend: String,
}
#[derive(Debug, Serialize)]
pub enum Contributor {
Flat(String),
Complex { name: String, email: String },
}
#[derive(Default, Debug, Serialize)]
#[serde(rename_all(serialize = "kebab-case"))]
pub struct Project {
pub name: String,
pub version: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub description: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub readme: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub requires_python: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub license: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub authors: Vec<Contributor>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub maintainers: Vec<Contributor>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub keywords: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub classifiers: Vec<String>,
#[serde(flatten)]
pub additional_fields: HashMap<String, Value>,
}
pub fn ask_user_inputs(minimum: bool) -> Result<Pyproject> {
let mut project = Project::default();
let mut build_system = BuildSystem::default();
let theme = ColorfulTheme::default();
build_system.requires = Input::<String>::with_theme(&theme)
.with_prompt("build dependencies (comma separated)")
.default("setuptools,wheel".to_string())
.interact_text()?
.split(',')
.filter(|v| !v.is_empty())
.map(|v| v.to_string())
.collect();
build_system.build_backend = Input::with_theme(&theme)
.with_prompt("build back-end")
.default("setuptools.build_meta".to_string())
.interact_text()?;
project.name = Input::with_theme(&theme)
.with_prompt("project name")
.default(
env::current_dir()?
.file_name()
.expect("invalid current directory")
.to_str()
.expect("invalid UTF-8 cwd name")
.to_string(),
)
.interact_text()?;
project.version = Input::with_theme(&theme)
.with_prompt("version")
.default("0.1.0".to_string())
.interact_text()?;
if !minimum {
project.description = Input::with_theme(&theme)
.with_prompt("description")
.allow_empty(true)
.interact_text()?;
project.readme = Input::with_theme(&theme)
.with_prompt("readme")
.allow_empty(true)
.interact_text()?;
project.requires_python = Input::with_theme(&theme)
.with_prompt("minimum python version")
.allow_empty(true)
.interact_text()?;
project.license = Input::with_theme(&theme)
.with_prompt("license")
.allow_empty(true)
.interact_text()?;
project.authors = Input::<String>::with_theme(&theme)
.with_prompt(
r#"authors (e.g: "Antoine Langlois";"name="Antoine L",email="email@domain.net"")"#,
)
.allow_empty(true)
.interact_text()?
.split(';')
.filter(|v| !v.is_empty())
.map(|v| parse_contributor(v))
.collect();
project.maintainers = Input::<String>::with_theme(&theme)
.with_prompt(
r#"maintainers (e.g: "Antoine Langlois";"name="Antoine L",email="email@domain.net"")"#,
)
.allow_empty(true)
.interact_text()?
.split(';')
.filter(|v| !v.is_empty())
.map(|v| parse_contributor(v))
.collect();
project.keywords = Input::<String>::with_theme(&theme)
.with_prompt("keywords (e.g: KEYW1;KEYW2)")
.allow_empty(true)
.interact_text()?
.split(';')
.filter(|v| !v.is_empty())
.map(|v| v.to_string())
.collect();
project.classifiers = Input::<String>::with_theme(&theme)
.with_prompt("classifiers (e.g: CLASS1;CLASS2)")
.allow_empty(true)
.interact_text()?
.split(';')
.filter(|v| !v.is_empty())
.map(|v| v.to_string())
.collect();
}
Ok(Pyproject {
build_system,
project,
})
}
fn parse_contributor(contributor: &str) -> Contributor {
let name_regex = Regex::new(r#"name="([\w\s\-\.]*)""#).expect("invalid name regex expression");
let email_regex =
Regex::new(r#"email="([\w\s\-\.@]*)""#).expect("invalid email regex expression");
let name = name_regex.captures(contributor);
let email = email_regex.captures(contributor);
if name.is_some() && email.is_some() {
Contributor::Complex {
name: name.unwrap().get(1).unwrap().as_str().to_string(),
email: email.unwrap().get(1).unwrap().as_str().to_string(),
}
} else if let Some(name_match) = name {
Contributor::Flat(name_match.get(1).unwrap().as_str().to_string())
} else if let Some(email_match) = email {
Contributor::Flat(email_match.get(1).unwrap().as_str().to_string())
} else {
Contributor::Flat(contributor.to_string())
}
}

View File

@ -1,3 +1,42 @@
fn main() {
println!("Hello, world!");
use std::{fs::write, path::PathBuf};
use anyhow::Result;
use clap::Parser;
use init::ask_user_inputs;
use toml::toml;
mod init;
#[derive(clap::Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long, default_value_t = false)]
verbose: bool,
/// Initialize a new python directory with minimal fields for "pyproject.toml"
#[arg(short, long, default_value_t = true)]
minimum: bool,
#[command(subcommand)]
subcommands: Subcommands,
}
#[derive(clap::Subcommand)]
enum Subcommands {
New { folder: PathBuf },
Init {},
}
fn main() -> Result<()> {
let args = Args::parse();
match args.subcommands {
Subcommands::New { folder } => todo!(),
Subcommands::Init {} => {
let pyproject = ask_user_inputs(args.minimum)?;
write("pyproject.toml", toml::to_vec(&pyproject)?)?;
}
};
Ok(())
}