Skip to content

Commit

Permalink
Adds init command
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon committed Oct 21, 2023
1 parent 25bd13b commit 2772af7
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 25 deletions.
168 changes: 164 additions & 4 deletions stage0/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use crate::ast::ParseError;
use crate::ffi::llvm_init;
use crate::pkg::{DependencyResolver, Package, PrimitiveTarget, Target, TargetResolver};
use crate::pkg::{
DependencyResolver, Package, PackageName, PrimitiveTarget, Target, TargetResolver,
};
use crate::project::{Project, ProjectBuildError, ProjectLoadError};
use clap::{command, value_parser, Arg, ArgMatches, Command};
use clap::{command, value_parser, Arg, ArgAction, ArgMatches, Command};
use std::borrow::Cow;
use std::error::Error;
use std::fmt::Write;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

Expand All @@ -25,6 +28,22 @@ fn main() -> ExitCode {
.value_parser(value_parser!(PathBuf));
let args = command!()
.subcommand_required(true)
.subcommand(
Command::new("init")
.about("Create a Nitro project in an existing directory")
.arg(
Arg::new("lib")
.help("Create a library project (default is executable project)")
.long("lib")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("directory")
.help("The directory to create the project (default to current directory)")
.value_name("DIRECTORY")
.value_parser(value_parser!(PathBuf)),
),
)
.subcommand(
Command::new("build")
.about("Build a Nitro project")
Expand Down Expand Up @@ -102,6 +121,10 @@ fn main() -> ExitCode {
};

match args.subcommand().unwrap() {
("init", args) => match init(args) {
Ok(_) => ExitCode::SUCCESS,
Err(v) => v,
},
("build", args) => match build(args, &cx) {
Ok(_) => ExitCode::SUCCESS,
Err(v) => v,
Expand All @@ -112,13 +135,149 @@ fn main() -> ExitCode {
}
}

fn init(args: &ArgMatches) -> Result<(), ExitCode> {
// Get destination directory.
let prefix = match args.get_one::<PathBuf>("directory") {
Some(v) => v.canonicalize().unwrap(),
None => std::env::current_dir().unwrap(),
};

// Get project name.
let name: PackageName = match prefix.file_name() {
Some(v) => match v.to_str().unwrap().parse() {
Ok(v) => v,
Err(e) => {
eprintln!(
"The name of project directory is not a valid project name: {}.",
join_nested(&e)
);
return Err(ExitCode::FAILURE);
}
},
None => {
eprintln!("The location th create the project cannot be a root of filesystem.");
return Err(ExitCode::FAILURE);
}
};

// Create Nitro.yml.
let lib = args.get_flag("lib");
let proj = prefix.join("Nitro.yml");
let mut proj = match OpenOptions::new().create_new(true).write(true).open(&proj) {
Ok(v) => v,
Err(e) => {
eprintln!("Cannot create {}: {}.", proj.display(), join_nested(&e));
return Err(ExitCode::FAILURE);
}
};

writeln!(proj, "package:").unwrap();
writeln!(proj, " name: {name}").unwrap();
writeln!(proj, " version: 1.0.0").unwrap();

if lib {
writeln!(proj, "library:").unwrap();
writeln!(proj, " sources: lib").unwrap();
init_lib(prefix.join("lib"))?;
} else {
writeln!(proj, "executable:").unwrap();
writeln!(proj, " sources: exe").unwrap();
init_exe(prefix.join("exe"))?;
}

// Create .gitignore.
let ignore = prefix.join(".gitignore");
let mut ignore = match OpenOptions::new()
.create_new(true)
.write(true)
.open(&ignore)
{
Ok(v) => v,
Err(e) => {
eprintln!("Cannot create {}: {}.", ignore.display(), join_nested(&e));
return Err(ExitCode::FAILURE);
}
};

writeln!(ignore, ".build/").unwrap();

Ok(())
}

fn init_exe(src: PathBuf) -> Result<(), ExitCode> {
// Create source directory.
if let Err(e) = std::fs::create_dir(&src) {
if e.kind() != std::io::ErrorKind::AlreadyExists {
eprintln!("Cannot create {}: {}.", src.display(), join_nested(&e));
return Err(ExitCode::FAILURE);
}
}

// Create App.nt.
let path = src.join("App.nt");
let mut app = match OpenOptions::new().create_new(true).write(true).open(&path) {
Ok(v) => v,
Err(e) => {
eprintln!("Cannot create {}: {}.", path.display(), join_nested(&e));
return Err(ExitCode::FAILURE);
}
};

writeln!(app, "use nitro.Int32;").unwrap();
writeln!(app).unwrap();
writeln!(app, "class App;").unwrap();
writeln!(app).unwrap();
writeln!(app, "impl App {{").unwrap();
writeln!(app, " @entry").unwrap();
writeln!(app, " fn Main(): Int32 {{").unwrap();
writeln!(app, " 0").unwrap();
writeln!(app, " }}").unwrap();
writeln!(app, "}}").unwrap();

Ok(())
}

fn init_lib(src: PathBuf) -> Result<(), ExitCode> {
// Create source directory.
if let Err(e) = std::fs::create_dir(&src) {
if e.kind() != std::io::ErrorKind::AlreadyExists {
eprintln!("Cannot create {}: {}.", src.display(), join_nested(&e));
return Err(ExitCode::FAILURE);
}
}

// Create SampleClass.nt.
let path = src.join("SampleClass.nt");
let mut class = match OpenOptions::new().create_new(true).write(true).open(&path) {
Ok(v) => v,
Err(e) => {
eprintln!("Cannot create {}: {}.", path.display(), join_nested(&e));
return Err(ExitCode::FAILURE);
}
};

writeln!(class, "use nitro.Int32;").unwrap();
writeln!(class).unwrap();
writeln!(class, "@pub").unwrap();
writeln!(class, "class SampleClass;").unwrap();
writeln!(class).unwrap();
writeln!(class, "impl SampleClass {{").unwrap();
writeln!(class, " @pub").unwrap();
writeln!(class, " fn SampleMethod(arg: Int32): Int32 {{").unwrap();
writeln!(class, " 0").unwrap();
writeln!(class, " }}").unwrap();
writeln!(class, "}}").unwrap();

Ok(())
}

fn build(args: &ArgMatches, cx: &Context) -> Result<Package, ExitCode> {
// Initialize LLVM.
unsafe { llvm_init() };

// Get path to the project.
let path = match args.get_one::<PathBuf>("project") {
Some(v) => Cow::Borrowed(v),
Some(v) => Cow::Borrowed(v.as_path()),
None => Cow::Owned(std::env::current_dir().unwrap()),
};

Expand Down Expand Up @@ -222,6 +381,7 @@ fn join_nested(mut e: &dyn Error) -> String {
let mut m = e.to_string();

while let Some(v) = e.source() {
use std::fmt::Write;
write!(m, " -> {v}").unwrap();
e = v;
}
Expand Down
64 changes: 43 additions & 21 deletions stage0/src/pkg/meta.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use serde::de::{Error, Unexpected, Visitor};
use serde::{Deserialize, Deserializer};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use thiserror::Error;

/// Metadata for a Nitro package.
pub struct PackageMeta {
Expand Down Expand Up @@ -41,19 +43,34 @@ impl PackageName {
bin[..src.len()].copy_from_slice(src);
bin
}
}

fn is_valid(v: &str) -> bool {
impl<'a> Deserialize<'a> for PackageName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
deserializer.deserialize_any(PackageNameVisitor)
}
}

impl FromStr for PackageName {
type Err = PackageNameError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
// Check length.
if v.is_empty() || v.len() > 32 {
return false;
if s.is_empty() {
return Err(PackageNameError::EmptyName);
} else if s.len() > 32 {
return Err(PackageNameError::NameTooLong);
}

// Check first char.
let mut i = v.as_bytes().into_iter();
let mut i = s.as_bytes().into_iter();
let b = i.next().unwrap();

if !b.is_ascii_lowercase() {
return false;
return Err(PackageNameError::NotStartWithLowerCase);
}

// Check remaining chars.
Expand All @@ -62,19 +79,10 @@ impl PackageName {
continue;
}

return false;
return Err(PackageNameError::NotDigitOrLowerCase);
}

true
}
}

impl<'a> Deserialize<'a> for PackageName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
deserializer.deserialize_any(PackageNameVisitor)
Ok(Self(s.to_owned()))
}
}

Expand Down Expand Up @@ -131,11 +139,9 @@ impl<'a> Visitor<'a> for PackageNameVisitor {
where
E: Error,
{
if PackageName::is_valid(value) {
Ok(PackageName(value.to_owned()))
} else {
Err(Error::invalid_value(Unexpected::Str(value), &self))
}
value
.parse()
.map_err(|_| Error::invalid_value(Unexpected::Str(value), &self))
}
}

Expand Down Expand Up @@ -176,3 +182,19 @@ impl<'a> Visitor<'a> for PackageVersionVisitor {
})
}
}

/// Represents an error when [`PackageName`] is failed to construct.
#[derive(Debug, Error)]
pub enum PackageNameError {
#[error("name cannot be empty")]
EmptyName,

#[error("name cannot exceed 32 bytes")]
NameTooLong,

#[error("name must start with a lower-case ASCII")]
NotStartWithLowerCase,

#[error("name cannot contains other alphabet except digits or lowe-case ASCIIs")]
NotDigitOrLowerCase,
}

0 comments on commit 2772af7

Please sign in to comment.