diff --git a/Cargo.lock b/Cargo.lock index 8ac796f5..1104c993 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,8 @@ dependencies = [ "pallas-crypto", "pallas-primitives", "pallas-traverse", + "regex", + "thiserror", "uplc", ] @@ -233,9 +235,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.22" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", @@ -245,7 +247,7 @@ dependencies = [ "once_cell", "strsim", "termcolor", - "textwrap", + "textwrap 0.16.0", ] [[package]] @@ -591,7 +593,7 @@ dependencies = [ "supports-hyperlinks", "supports-unicode", "terminal_size", - "textwrap", + "textwrap 0.15.0", "thiserror", "unicode-width", ] @@ -1214,15 +1216,21 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" dependencies = [ "smawk", "unicode-linebreak", "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.37" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 1c6a77f4..708c2c00 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -20,6 +20,8 @@ pallas-codec = "0.14.0" pallas-crypto = "0.14.0" pallas-primitives = "0.14.0" pallas-traverse = "0.14.0" +regex = "1.5.4" +thiserror = "1.0.31" aiken-lang = { path = "../lang", version = "0.0.24" } aiken-lsp = { path = "../lsp", version = "0.0.0" } diff --git a/crates/cli/src/cmd/error.rs b/crates/cli/src/cmd/error.rs new file mode 100644 index 00000000..29cce983 --- /dev/null +++ b/crates/cli/src/cmd/error.rs @@ -0,0 +1,36 @@ +use std::fmt; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("{} is not a valid project name. {}", name, reason.to_string())] + InvalidProjectName { + name: String, + reason: InvalidProjectNameReason, + }, +} + +#[derive(Debug, Clone, Copy)] +pub enum InvalidProjectNameReason { + AikenPrefix, + AikenReservedModule, + Format, +} + +impl fmt::Display for InvalidProjectNameReason { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InvalidProjectNameReason::AikenPrefix => write!(f, "It is a reserved word in Aiken."), + InvalidProjectNameReason::AikenReservedModule => { + write!(f, "It is a reserved module name in Aiken.") + } + InvalidProjectNameReason::Format => write!( + f, + "It does not have the correct format. Project names \ + must start with a lowercase letter and may only contain lowercase letters, \ + numbers and underscores." + ), + } + } +} diff --git a/crates/cli/src/cmd/mod.rs b/crates/cli/src/cmd/mod.rs index 3c79d162..75b5fe74 100644 --- a/crates/cli/src/cmd/mod.rs +++ b/crates/cli/src/cmd/mod.rs @@ -1,5 +1,6 @@ pub mod build; pub mod check; +pub mod error; pub mod fmt; pub mod lsp; pub mod new; diff --git a/crates/cli/src/cmd/new.rs b/crates/cli/src/cmd/new.rs index 4e98690d..23172c47 100644 --- a/crates/cli/src/cmd/new.rs +++ b/crates/cli/src/cmd/new.rs @@ -4,6 +4,8 @@ use std::fs; use std::io::Write; use std::path::PathBuf; +use super::error::{Error, InvalidProjectNameReason}; + #[derive(clap::Args)] /// Create a new Aiken project pub struct Args { @@ -20,11 +22,11 @@ pub struct Creator { } impl Creator { - fn new(args: Args) -> Self { + fn new(args: Args, project_name: String) -> Self { let root = args.name; let src = root.join("src"); let scripts = src.join("scripts"); - let project_name = root.clone().into_os_string().into_string().unwrap(); + let project_name = project_name; let project = src.join(&project_name); Self { root, @@ -111,9 +113,37 @@ fn write(path: PathBuf, contents: &str) -> miette::Result<()> { Ok(()) } +fn validate_name(name: &str) -> Result<(), Error> { + if name.starts_with("aiken_") { + Err(Error::InvalidProjectName { + name: name.to_string(), + reason: InvalidProjectNameReason::AikenPrefix, + }) + } else if name == "aiken" { + Err(Error::InvalidProjectName { + name: name.to_string(), + reason: InvalidProjectNameReason::AikenReservedModule, + }) + } else if !regex::Regex::new("^[a-z][a-z0-9_]*$") + .expect("new name regex could not be compiled") + .is_match(name) + { + Err(Error::InvalidProjectName { + name: name.to_string(), + reason: InvalidProjectNameReason::Format, + }) + } else { + Ok(()) + } +} + pub fn exec(args: Args) -> miette::Result<()> { if !args.name.exists() { - let creator = Creator::new(args); + let project_name = args.name.clone().into_os_string().into_string().unwrap(); + + validate_name(&project_name).into_diagnostic()?; + + let creator = Creator::new(args, project_name); creator.run()?; }