diff --git a/Cargo.lock b/Cargo.lock index 4f628e3..4cea302 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,9 +111,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" @@ -198,6 +198,7 @@ version = "0.2.0" dependencies = [ "anstyle", "brack-common", + "brack-internal-plugin", "brack-project", "clap", "codespan-reporting", @@ -236,6 +237,10 @@ dependencies = [ "brack-plugin", ] +[[package]] +name = "brack-internal-plugin" +version = "0.1.0" + [[package]] name = "brack-language-server" version = "0.2.0" @@ -255,6 +260,15 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "brack-lower" +version = "0.2.0" +dependencies = [ + "brack-common", + "serde", + "serde_json", +] + [[package]] name = "brack-parser" version = "0.2.0" @@ -267,6 +281,7 @@ dependencies = [ [[package]] name = "brack-plugin" version = "0.2.0" +source = "git+https://github.com/brack-lang/brack#aa9fbfaa00e27009f224cff32590fdebbc8f73fe" dependencies = [ "anyhow", "brack-common", @@ -332,6 +347,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "brack-wasm-plugin" +version = "0.1.0" +dependencies = [ + "brack-common", + "extism", + "serde", + "serde_json", +] + [[package]] name = "bumpalo" version = "3.17.0" diff --git a/Cargo.toml b/Cargo.toml index 975d2d5..6cee2f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ description = "A bracket-based lightweight markup language that extends commands [dependencies] brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } brack-project = { git = "https://github.com/brack-lang/brack", package = "brack-project" } +brack-internal-plugin = { git = "https://github.com/brack-lang/brack", package = "brack-internal-plugin" } tokio = { version = "1", features = ["full"] } anstyle = "1.0.10" colored = "3.0.0" @@ -30,13 +31,15 @@ members = [ "crates/brack-codegen", "crates/brack-common", "crates/brack-expander", + "crates/brack-internal-plugin", "crates/brack-language-server", + "crates/brack-lower", "crates/brack-parser", - "crates/brack-plugin", "crates/brack-project", "crates/brack-release", "crates/brack-tokenizer", "crates/brack-transformer", + "crates/brack-wasm-plugin", ] resolver = "2" @@ -45,9 +48,12 @@ resolver = "2" brack-codegen = { path = "crates/brack-codegen" } brack-common = { path = "crates/brack-common" } brack-expander = { path = "crates/brack-expander" } +brack-internal-plugin = { path = "crates/brack-internal-plugin" } brack-language-server = { path = "crates/brack-language-server" } +brack-lower = { path = "crates/brack-lower" } brack-parser = { path = "crates/brack-parser" } -brack-plugin = { path = "crates/brack-plugin" } brack-project = { path = "crates/brack-project" } +brack-release = { path = "crates/brack-release" } brack-tokenizer = { path = "crates/brack-tokenizer" } brack-transformer = { path = "crates/brack-transformer" } +brack-wasm-plugin = { path = "crates/brack-wasm-plugin" } \ No newline at end of file diff --git a/crates/brack-common/src/errors.rs b/crates/brack-common/src/errors.rs new file mode 100644 index 0000000..ba6c84d --- /dev/null +++ b/crates/brack-common/src/errors.rs @@ -0,0 +1,467 @@ +use std::{ffi::OsString, io, path::PathBuf}; + +use crate::{ + location::Location, + logger::Logger, + plugins::{CommandType, Signature}, +}; + +pub enum Error { + TransformingError(TransformingError), + LoweringError(LoweringError), + CodegenError(CodegenError), + ProjectError(ProjectError), + ReleaseError(ReleaseError), + PluginError(PluginError), + InternalError { panic_message: String }, +} + +impl Error { + pub fn code(&self) -> String { + match self { + Self::TransformingError(err) => err.code(), + Self::LoweringError(err) => err.code(), + Self::CodegenError(err) => err.code(), + Self::ProjectError(err) => err.code(), + Self::ReleaseError(err) => err.code(), + Self::PluginError(err) => err.code(), + Self::InternalError { .. } => String::from("Fatal"), + } + } +} + +pub enum TransformingError { + AngleNotOpened(Location), + AngleNotClosed(Location), + CurlyNotOpened(Location), + CurlyNotClosed(Location), + SquareNotOpened(Location), + SquareNotClosed(Location), + MismatchedBracket(Location), + ModuleNotFound(Location), + IdentifierNotFound(Location), + DotNotFound(Location), + CommaNotFound(Location), + UnexpectedDot(Location), + UnexpectedComma(Location), + InvalidBackslash(Location), +} + +impl TransformingError { + pub fn code(&self) -> String { + match self { + Self::AngleNotOpened(_) => String::from("ET001"), + Self::AngleNotClosed(_) => String::from("ET002"), + Self::CurlyNotOpened(_) => String::from("ET003"), + Self::CurlyNotClosed(_) => String::from("ET004"), + Self::SquareNotOpened(_) => String::from("ET005"), + Self::SquareNotClosed(_) => String::from("ET006"), + Self::MismatchedBracket(_) => String::from("ET007"), + Self::ModuleNotFound(_) => String::from("ET008"), + Self::IdentifierNotFound(_) => String::from("ET009"), + Self::DotNotFound(_) => String::from("ET010"), + Self::CommaNotFound(_) => String::from("ET011"), + Self::UnexpectedDot(_) => String::from("ET012"), + Self::UnexpectedComma(_) => String::from("ET013"), + Self::InvalidBackslash(_) => String::from("ET014"), + } + } +} + +pub enum LoweringError {} + +impl LoweringError { + pub fn code(&self) -> String { + match self { + _ => String::from("EL001"), + } + } +} + +pub enum CodegenError {} + +impl CodegenError { + pub fn code(&self) -> String { + match self { + _ => String::from("EC001"), + } + } +} + +pub enum ProjectError { + /// This error is used when the following function returns None: + /// file_name(self: &std::path::Path) -> Option<&OsStr> + FailedToGetFileNameFromPath { path: PathBuf }, + /// This error is used when the following function returns None: + /// to_str(self: &OsStr) -> Option<&str> + FailedToConvertOsStrToStr { os_str: OsString }, + /// This error is used when the project is initialized but the project is already created. + ProjectAlreadyExists { path: PathBuf }, + /// This error is used when the target directory already exists. + DirectoryAlreadyExists { path: PathBuf }, + /// This error is used when creating a directory fails. + /// create_dir_all(self: &Path) -> Result<(), std::io::Error> + FailedToCreateDirectory { path: PathBuf, source: io::Error }, + /// This error is used when writing a file fails. + /// write(self: &Path, content: &str) -> Result<(), std::io::Error> + FailedToWriteFile { path: PathBuf, source: io::Error }, + /// This error is used when the project is not initialized. + ProjectNotInitialized, + /// This error is used when the serialization of manifest fails. + /// to_string(self: &toml::Value) -> Result + FailedToSerializeManifest { source: toml::ser::Error }, + /// This error is used when the manifest file is not found. + ManifestNotFound { path: PathBuf }, + /// This error is used when reading a file fails. + /// read_to_string(self: &Path) -> Result + FailedToReadFile { path: PathBuf, source: io::Error }, + /// This error is used when the deserialization of manifest fails. + /// from_str(self: &str) -> Result + FailedToDeserializeManifest { source: toml::de::Error }, + /// This error is used when reading file size fails. + /// metadata(self: &Path) -> Result + FailedToReadFileSize { path: PathBuf, source: io::Error }, + /// This error is used when removing a file fails. + /// remove_file(self: &Path) -> Result<(), std::io::Error> + FailedToRemoveFile { path: PathBuf, source: io::Error }, + /// This error is used when removing directories fails. + /// remove_dir_all(self: &Path) -> Result<(), std::io::Error> + FailedToRemoveDirectory { path: PathBuf, source: io::Error }, + /// This error is used when fetching a channel fails. + /// get(url: T) -> Result + FailedToFetchChannel { url: String, source: reqwest::Error }, + /// This error is used when the HTTP response status is not success. + /// status(self: &reqwest::Response) -> reqwest::StatusCode + ReceivedHttpNonSuccessStatus { + url: String, + status: u16, + reason: String, + }, + /// This error is used when converting Response to bytes fails. + /// bytes(self: &reqwest::Response) -> Result + FailedToConvertResponseToBytes { url: String, source: reqwest::Error }, + /// This error is used when converting bytes to str fails. + /// from_utf8(self: &[u8]) -> Result<&str, std::str::Utf8Error> + FailedToConvertBytesToStr { source: std::str::Utf8Error }, + /// This error is used when parsing a channel fails. + /// toml::from_str(self: &str) -> Result + FailedToDeserializeToChannel { source: toml::de::Error }, + /// This error is used when the channel name is invalid. + InvalidChannelName { name: String }, + /// This error is used when the channel URL is invalid. + InvalidChannelUrl { url: String }, + /// This error is used when the channel already exists. + ChannelAlreadyExists { name: String }, + /// This error is used when the channel is not found. + ChannelNotFound { name: String }, +} + +impl ProjectError { + pub fn code(&self) -> String { + match self { + ProjectError::FailedToGetFileNameFromPath { .. } => String::from("E0001"), + ProjectError::FailedToConvertOsStrToStr { .. } => String::from("E0002"), + ProjectError::ProjectAlreadyExists { .. } => String::from("E0003"), + ProjectError::DirectoryAlreadyExists { .. } => String::from("E0004"), + ProjectError::FailedToCreateDirectory { .. } => String::from("E0005"), + ProjectError::FailedToWriteFile { .. } => String::from("E0006"), + ProjectError::ProjectNotInitialized => String::from("E0007"), + ProjectError::FailedToSerializeManifest { .. } => String::from("E0008"), + ProjectError::ManifestNotFound { .. } => String::from("E0009"), + ProjectError::FailedToReadFile { .. } => String::from("E0010"), + ProjectError::FailedToDeserializeManifest { .. } => String::from("E0011"), + ProjectError::FailedToReadFileSize { .. } => String::from("E0012"), + ProjectError::FailedToRemoveFile { .. } => String::from("E0013"), + ProjectError::FailedToRemoveDirectory { .. } => String::from("E0014"), + ProjectError::FailedToFetchChannel { .. } => String::from("E0015"), + ProjectError::ReceivedHttpNonSuccessStatus { .. } => String::from("E0016"), + ProjectError::FailedToConvertResponseToBytes { .. } => String::from("E0017"), + ProjectError::FailedToConvertBytesToStr { .. } => String::from("E0018"), + ProjectError::FailedToDeserializeToChannel { .. } => String::from("E0019"), + ProjectError::InvalidChannelName { .. } => String::from("E0020"), + ProjectError::InvalidChannelUrl { .. } => String::from("E0021"), + ProjectError::ChannelAlreadyExists { .. } => String::from("E0022"), + ProjectError::ChannelNotFound { .. } => String::from("E0023"), + } + } +} + +impl From for Error { + fn from(err: ProjectError) -> Self { + Error::ProjectError(err) + } +} + +pub trait ProjectResultExt { + fn map_project_err(self, logger: &L) -> Result; +} + +impl ProjectResultExt for Result +where + E: Into, +{ + fn map_project_err(self, logger: &L) -> Result { + self.map_err(|err| { + let err = Error::ProjectError(err.into()); + logger.error(&err); + err + }) + } +} + +pub enum ReleaseError {} + +impl ReleaseError { + pub fn code(&self) -> String { + match self { + _ => String::from("ER001"), + } + } +} + +pub enum PluginError { + PluginCallError { + name: String, + signature: Signature, + args: String, + }, + PluginReadError { + name: String, + source: String, + }, + PluginCreateError { + name: String, + source: String, + }, + PluginNotFoundError { + name: String, + command_name: String, + command_type: CommandType, + }, + InvalidArgumentError { + name: String, + command_name: String, + command_type: CommandType, + expected: String, + found: Signature, + }, +} + +impl PluginError { + pub fn code(&self) -> String { + match self { + PluginError::PluginCallError { .. } => String::from("EG001"), + PluginError::PluginReadError { .. } => String::from("EG002"), + PluginError::PluginCreateError { .. } => String::from("EG003"), + PluginError::PluginNotFoundError { .. } => String::from("EG004"), + PluginError::InvalidArgumentError { .. } => String::from("EG005"), + } + } +} + +pub enum Warning { + TransformingWarning(TransformingWarning), + LoweringWarning(LoweringWarning), + CodegenWarning(CodegenWarning), + ProjectWarning(ProjectWarning), + ReleaseWarning(ReleaseWarning), +} + +impl Warning { + pub fn code(&self) -> String { + match self { + Self::TransformingWarning(warn) => warn.code(), + Self::LoweringWarning(warn) => warn.code(), + Self::CodegenWarning(warn) => warn.code(), + Self::ProjectWarning(warn) => warn.code(), + Self::ReleaseWarning(warn) => warn.code(), + } + } +} + +pub enum TransformingWarning {} + +impl TransformingWarning { + pub fn code(&self) -> String { + match self { + _ => String::from("WT001"), + } + } +} + +pub enum LoweringWarning {} + +impl LoweringWarning { + pub fn code(&self) -> String { + match self { + _ => String::from("WL001"), + } + } +} + +pub enum CodegenWarning {} + +impl CodegenWarning { + pub fn code(&self) -> String { + match self { + _ => String::from("WC001"), + } + } +} + +pub enum ProjectWarning { + NoFilesRemovedDueToDryRun, + NoChannelsFound, +} + +impl ProjectWarning { + pub fn code(&self) -> String { + match self { + ProjectWarning::NoFilesRemovedDueToDryRun => String::from("WP001"), + ProjectWarning::NoChannelsFound => String::from("WP002"), + } + } +} + +impl From for Warning { + fn from(warning: ProjectWarning) -> Self { + Warning::ProjectWarning(warning) + } +} + +pub enum ReleaseWarning {} + +impl ReleaseWarning { + pub fn code(&self) -> String { + match self { + _ => String::from("WR001"), + } + } +} + +pub enum Info { + TransformingInfo(TransformingInfo), + LoweringInfo(LoweringInfo), + CodegenInfo(CodegenInfo), + ProjectInfo(ProjectInfo), + ReleaseInfo(ReleaseInfo), +} + +pub enum TransformingInfo {} + +pub enum LoweringInfo {} + +pub enum CodegenInfo {} + +pub enum ProjectInfo { + CreatingProject { + name: String, + }, + FinishedCreatingProject { + name: String, + }, + CleaningProject { + name: String, + }, + FinishedCleaningProject { + name: String, + num_files: i32, + file_size: u64, + }, + AddingChannel { + name: String, + }, + FinishedAddingChannel { + name: String, + }, + FetchingChannel { + name: String, + url: String, + }, + FinishedFetchingChannel { + name: String, + }, + RemovingChannel { + name: String, + }, + FinishedRemovingChannel { + name: String, + }, + UpdatingChannel { + name: Option, + }, + FinishedUpdatingChannel { + name: Option, + }, + ListingChannels, + FinishedListingChannels, + ChannelInfo { + name: String, + url: String, + health: bool, + }, +} + +impl From for Info { + fn from(info: ProjectInfo) -> Self { + Info::ProjectInfo(info) + } +} + +pub enum ReleaseInfo {} + +pub enum Debug { + TransformingDebug(TransformingDebug), + LoweringDebug(LoweringDebug), + CodegenDebug(CodegenDebug), + ProjectDebug(ProjectDebug), + ReleaseDebug(ReleaseDebug), +} + +pub enum TransformingDebug {} + +pub enum LoweringDebug {} + +pub enum CodegenDebug {} + +pub enum ProjectDebug { + DirectoryAlreadyExists { path: PathBuf }, + CreatingDirectory { path: PathBuf }, + WritingFile { path: PathBuf, content: String }, + WritingToml { content: String }, + ReadingFile { path: PathBuf, content: String }, + ReadingToml { content: String }, + RemovingFile { path: PathBuf }, + RemovingDirectory { path: PathBuf }, + ReceivedResponse { url: String }, + ConvertingResponseToBytes { url: String }, + ConvertingBytesToStr { url: String }, + DeserializingToChannel, +} + +impl ProjectDebug { + pub fn code(&self) -> String { + match self { + ProjectDebug::DirectoryAlreadyExists { .. } => String::from("DP0001"), + ProjectDebug::CreatingDirectory { .. } => String::from("DP0002"), + ProjectDebug::WritingFile { .. } => String::from("DP0003"), + ProjectDebug::WritingToml { .. } => String::from("DP0004"), + ProjectDebug::ReadingFile { .. } => String::from("DP0005"), + ProjectDebug::ReadingToml { .. } => String::from("DP0006"), + ProjectDebug::RemovingFile { .. } => String::from("DP0007"), + ProjectDebug::RemovingDirectory { .. } => String::from("DP0008"), + ProjectDebug::ReceivedResponse { .. } => String::from("DP0009"), + ProjectDebug::ConvertingResponseToBytes { .. } => String::from("DP0010"), + ProjectDebug::ConvertingBytesToStr { .. } => String::from("DP0011"), + ProjectDebug::DeserializingToChannel { .. } => String::from("DP0012"), + } + } +} + +impl From for Debug { + fn from(debug: ProjectDebug) -> Self { + Debug::ProjectDebug(debug) + } +} + +pub enum ReleaseDebug {} diff --git a/crates/brack-common/src/html.rs b/crates/brack-common/src/html.rs new file mode 100644 index 0000000..b1bf4bf --- /dev/null +++ b/crates/brack-common/src/html.rs @@ -0,0 +1,49 @@ +use core::fmt; +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Html { + pub tag: HtmlTag, + pub attributes: Vec>, + pub children: Vec, +} + +impl fmt::Display for Html { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "<{} ", self.tag)?; + for attr in self.attributes.clone() { + for (key, value) in attr { + write!(f, "{}=\"{}\" ", key, value)?; + } + } + write!(f, ">")?; + for child in &self.children { + write!(f, "{}", child)?; + } + write!(f, "", self.tag) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum HtmlTag { + Fragment(String), + Div, + Span, + P, + B, +} + +impl fmt::Display for HtmlTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + HtmlTag::Fragment(_) => write!(f, ""), + HtmlTag::Div => write!(f, "div"), + HtmlTag::Span => write!(f, "span"), + HtmlTag::P => write!(f, "p"), + HtmlTag::B => write!(f, "b"), + } + } +} + diff --git a/crates/brack-common/src/ir.rs b/crates/brack-common/src/ir.rs new file mode 100644 index 0000000..8f809cf --- /dev/null +++ b/crates/brack-common/src/ir.rs @@ -0,0 +1,23 @@ +use core::fmt; + +use serde::{Deserialize, Serialize}; + +use crate::html::Html; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum IR { + Html(Html), +} + +impl fmt::Display for IR { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + IR::Html(html) => write!(f, "{}", html), + } + } +} + +#[derive(Clone)] +pub enum IRKind { + Html, +} diff --git a/crates/brack-common/src/lib.rs b/crates/brack-common/src/lib.rs index a50fb7b..1613e21 100644 --- a/crates/brack-common/src/lib.rs +++ b/crates/brack-common/src/lib.rs @@ -1,7 +1,9 @@ pub mod ast; pub mod cst; +pub mod errors; +pub mod html; +pub mod ir; pub mod location; pub mod logger; -pub mod project_errors; +pub mod plugins; pub mod tokens; -pub mod transformer_errors; diff --git a/crates/brack-common/src/logger.rs b/crates/brack-common/src/logger.rs index ff6ecb6..1f73ec7 100644 --- a/crates/brack-common/src/logger.rs +++ b/crates/brack-common/src/logger.rs @@ -1,11 +1,11 @@ -use crate::project_errors::{ProjectDebug, ProjectError, ProjectInfo, ProjectWarning}; +use crate::errors::{Debug, Error, Info, Warning}; use std::path::PathBuf; pub trait Logger { - fn error(&self, error: &ProjectError); - fn warn(&self, warning: &ProjectWarning); - fn info(&self, info: &ProjectInfo); - fn debug(&self, debug: &ProjectDebug); + fn error(&self, error: &Error); + fn warn(&self, warning: &Warning); + fn info(&self, info: &Info); + fn debug(&self, debug: &Debug); fn set_path(&mut self, path: PathBuf); fn get_path(&self) -> PathBuf; } diff --git a/crates/brack-common/src/plugins.rs b/crates/brack-common/src/plugins.rs new file mode 100644 index 0000000..6d3c79f --- /dev/null +++ b/crates/brack-common/src/plugins.rs @@ -0,0 +1,261 @@ +use core::fmt; +use std::{collections::HashMap, ops::Index}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + errors::PluginError, + ir::{IRKind, IR}, +}; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Signature { + pub callee: String, + pub command_name: String, + pub return_type: Type, + pub args: Vec<(String, Type)>, + pub command_type: CommandType, +} + +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = String::new(); + for (i, (k, v)) in self.args.iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push_str(&format!("{}: {}", k, v)); + } + write!( + f, + "{} {}({}) -> {}", + self.command_type, self.command_name, s, self.return_type + ) + } +} + +#[derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, Clone)] +pub enum CommandType { + InlineCommand, + BlockCommand, + Macro, +} + +impl fmt::Display for CommandType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CommandType::InlineCommand => write!(f, "inline"), + CommandType::BlockCommand => write!(f, "block"), + CommandType::Macro => write!(f, "macro"), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum Type { + String, + Integer, + Float, + Boolean, + List(Box), + Tuple(Vec), + Option(Box), + Record(HashMap), + Varargs(Box), + IR, + Invalid, +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::String => write!(f, "string"), + Type::Integer => write!(f, "integer"), + Type::Float => write!(f, "float"), + Type::Boolean => write!(f, "boolean"), + Type::List(t) => write!(f, "[{}]", t), + Type::Tuple(t) => { + let mut s = String::new(); + for (i, v) in t.iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push_str(&v.to_string()); + } + write!(f, "!({})", s) + } + Type::Option(t) => write!(f, "Option<{}>", t), + Type::Record(r) => { + let mut s = String::new(); + for (k, v) in r.iter() { + if !s.is_empty() { + s.push_str(", "); + } + s.push_str(&format!("{}: {}", k, v)); + } + write!(f, "{{{}}}", s) + } + Type::Varargs(t) => write!(f, "...[{}]", t), + Type::IR => write!(f, "IR"), + Type::Invalid => write!(f, "Invalid"), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum Value { + String(String), + Integer(i64), + Float(f64), + Boolean(bool), + List(Vec), + Tuple(Vec), + Option(Option>), + Record(HashMap), + Varargs(Vec), + IR(IR), +} + +impl From for Type { + fn from(v: Value) -> Self { + match v { + Value::String(_) => Type::String, + Value::Integer(_) => Type::Integer, + Value::Float(_) => Type::Float, + Value::Boolean(_) => Type::Boolean, + Value::List(l) => Type::List(Box::new(l[0].clone().into())), + Value::Tuple(t) => Type::Tuple(t.iter().map(|v| v.clone().into()).collect()), + Value::Option(o) => Type::Option(Box::new((*o.unwrap()).into())), + Value::Record(r) => { + let mut map = HashMap::new(); + for (k, v) in r.iter() { + map.insert(k.clone(), v.clone().into()); + } + Type::Record(map) + } + Value::Varargs(v) => Type::Varargs(Box::new(v[0].clone().into())), + Value::IR(_) => Type::IR, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ValueVec(pub Vec); + +impl fmt::Display for ValueVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = String::new(); + for (i, v) in self.0.iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push_str(&v.to_string()); + } + write!(f, "[{}]", s) + } +} + +impl ValueVec { + pub fn new() -> Self { + ValueVec(Vec::new()) + } + + pub fn len(&self) -> usize { + self.0.len() + } +} + +impl Index for ValueVec { + type Output = Value; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Value::String(s) => write!(f, "{}", s), + Value::Integer(i) => write!(f, "{}", i), + Value::Float(fl) => write!(f, "{}", fl), + Value::Boolean(b) => write!(f, "{}", b), + Value::List(l) => { + let mut s = String::new(); + for (i, v) in l.iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push_str(&v.to_string()); + } + write!(f, "#({})", s) + } + Value::Tuple(t) => { + let mut s = String::new(); + for (i, v) in t.iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push_str(&v.to_string()); + } + write!(f, "!({})", s) + } + Value::Option(o) => { + if let Some(v) = o { + write!(f, "{}", v) + } else { + write!(f, "None") + } + } + Value::Record(r) => { + let mut s = String::new(); + for (k, v) in r.iter() { + if !s.is_empty() { + s.push_str(", "); + } + s.push_str(&format!("{}: {}", k, v)); + } + write!(f, "({})", s) + } + Value::Varargs(v) => { + let mut s = String::new(); + for (i, v) in v.iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push_str(&v.to_string()); + } + write!(f, "...({})", s) + } + Value::IR(ir) => write!(f, "{}", ir), + } + } +} + +pub type SignatureTable = HashMap<(String, CommandType), Vec>; + +pub trait Plugin { + fn call( + &mut self, + command_name: &str, + command_type: &CommandType, + args: &ValueVec, + ) -> Result; + + fn get_signatures( + &self, + command_name: &str, + command_type: &CommandType, + ) -> Option>; + + fn match_signature( + &self, + command_name: &str, + command_type: &CommandType, + args: &ValueVec, + ) -> Option; + + fn ir_kind(&self) -> IRKind; +} + +pub type Plugins = HashMap>; diff --git a/crates/brack-common/src/project_errors.rs b/crates/brack-common/src/project_errors.rs deleted file mode 100644 index 86f8604..0000000 --- a/crates/brack-common/src/project_errors.rs +++ /dev/null @@ -1,287 +0,0 @@ -use std::io; -use std::path::PathBuf; -use std::str; - -use crate::location::Location; -use crate::transformer_errors::TransformError; - -#[derive(Debug)] -pub enum ProjectError { - /// If the path terminates in `..` or is `/`. - FailedToGetFileNameFromPath { - path: PathBuf, - }, - /// If the path is not valid UTF-8. - FailedToConvertPathToStr { - path: PathBuf, - }, - /// If the project already exists. - ProjectAlreadyExists { - path: PathBuf, - }, - /// If the directory already exists. - DirectoryAlreadyExists { - path: PathBuf, - }, - /// If `std::io::write` fails. - FailedToWriteFile { - path: PathBuf, - source: io::Error, - }, - /// If `std::fs::create_dir` fails. - FailedToCreateDirectory { - path: PathBuf, - source: io::Error, - }, - /// If `toml::to_string` fails. - FailedToSerializeManifest { - source: toml::ser::Error, - }, - FailedToDeserializeManifest { - source: toml::de::Error, - }, - UninitializedProject, - ChannelAlreadyExists { - name: String, - }, - FailedToFetchResource { - url: String, - source: reqwest::Error, - }, - ReceivedHttpNonSuccessStatus { - url: String, - status: reqwest::StatusCode, - err_msg: String, - }, - /// If `std::str::from_utf8` fails. - FailedToConvertBytesToStr { - source: str::Utf8Error, - }, - FailedToParseChannel { - source: toml::de::Error, - }, - ChannelNameCannotBeEmpty, - InvalidChannelName { - name: String, - }, - ChannelUrlCannotBeEmpty, - InvalidChannelUrl { - url: String, - }, - FailedToReadFile { - path: PathBuf, - source: io::Error, - }, - ManifestNotFound { - path: PathBuf, - }, - FailedToRemoveFile { - path: PathBuf, - source: io::Error, - }, - FailedToRemoveDir { - path: PathBuf, - source: io::Error, - }, - FailedToReadFileSize { - path: PathBuf, - source: io::Error, - }, - ChannelNotFound { - name: String, - }, - AngleNotOpened { - location: Location, - }, - AngleNotClosed { - location: Location, - }, - CurlyNotOpened { - location: Location, - }, - CurlyNotClosed { - location: Location, - }, - SquareNotOpened { - location: Location, - }, - SquareNotClosed { - location: Location, - }, - MismatchedBracket { - location: Location, - }, - ModuleNotFound { - location: Location, - }, - IdentifierNotFound { - location: Location, - }, - DotNotFound { - location: Location, - }, - CommaNotFound { - location: Location, - }, - UnexpectedDot { - location: Location, - }, - UnexpectedComma { - location: Location, - }, - InvalidBackslash { - location: Location, - }, - TransformError, - FailedToCreatePlugin, - ExpandError, - CodegenError, - DocumentSettingsNotFound, -} - -impl ProjectError { - pub fn codespan_code(&self) -> String { - match self { - ProjectError::FailedToGetFileNameFromPath { .. } => String::from("E0001"), - ProjectError::FailedToConvertPathToStr { .. } => String::from("E0002"), - ProjectError::ProjectAlreadyExists { .. } => String::from("E0003"), - ProjectError::DirectoryAlreadyExists { .. } => String::from("E0004"), - ProjectError::FailedToWriteFile { .. } => String::from("E0005"), - ProjectError::FailedToCreateDirectory { .. } => String::from("E0006"), - ProjectError::FailedToSerializeManifest { .. } => String::from("E0007"), - ProjectError::FailedToDeserializeManifest { .. } => String::from("E0008"), - ProjectError::UninitializedProject => String::from("E0009"), - ProjectError::ChannelAlreadyExists { .. } => String::from("E0010"), - ProjectError::FailedToFetchResource { .. } => String::from("E0011"), - ProjectError::ReceivedHttpNonSuccessStatus { .. } => String::from("E0012"), - ProjectError::FailedToConvertBytesToStr { .. } => String::from("E0013"), - ProjectError::FailedToParseChannel { .. } => String::from("E0014"), - ProjectError::ChannelNameCannotBeEmpty => String::from("E0015"), - ProjectError::InvalidChannelName { .. } => String::from("E0016"), - ProjectError::ChannelUrlCannotBeEmpty => String::from("E0017"), - ProjectError::InvalidChannelUrl { .. } => String::from("E0018"), - ProjectError::FailedToReadFile { .. } => String::from("E0019"), - ProjectError::ManifestNotFound { .. } => String::from("E0020"), - ProjectError::FailedToRemoveFile { .. } => String::from("E0021"), - ProjectError::FailedToRemoveDir { .. } => String::from("E0022"), - ProjectError::FailedToReadFileSize { .. } => String::from("E0023"), - ProjectError::ChannelNotFound { .. } => String::from("E0024"), - ProjectError::AngleNotOpened { .. } => String::from("E0025"), - ProjectError::AngleNotClosed { .. } => String::from("E0026"), - ProjectError::CurlyNotOpened { .. } => String::from("E0027"), - ProjectError::CurlyNotClosed { .. } => String::from("E0028"), - ProjectError::SquareNotOpened { .. } => String::from("E0029"), - ProjectError::SquareNotClosed { .. } => String::from("E0030"), - ProjectError::MismatchedBracket { .. } => String::from("E0031"), - ProjectError::ModuleNotFound { .. } => String::from("E0032"), - ProjectError::IdentifierNotFound { .. } => String::from("E0033"), - ProjectError::DotNotFound { .. } => String::from("E0034"), - ProjectError::CommaNotFound { .. } => String::from("E0035"), - ProjectError::UnexpectedDot { .. } => String::from("E0036"), - ProjectError::UnexpectedComma { .. } => String::from("E0037"), - ProjectError::InvalidBackslash { .. } => String::from("E0038"), - ProjectError::TransformError => String::from("E0039"), - ProjectError::FailedToCreatePlugin => String::from("E0040"), - ProjectError::ExpandError => String::from("E0041"), - ProjectError::CodegenError => String::from("E0042"), - ProjectError::DocumentSettingsNotFound => String::from("E0043"), - } - } -} - -#[derive(Debug, Clone)] -pub enum ProjectWarning { - NoFilesRemovedDueToDryRun, - NoChannelsFound, -} - -impl ProjectWarning { - pub fn codespan_code(&self) -> String { - match self { - ProjectWarning::NoFilesRemovedDueToDryRun => String::from("W0001"), - ProjectWarning::NoChannelsFound => String::from("W0002"), - } - } -} - -#[derive(Debug, Clone)] -pub enum ProjectInfo { - CreatingProject { - name: String, - }, - FinishedCreatingProject { - name: String, - }, - CleaningProject, - FinishedCleaningProject { - num_files: u64, - file_size: u64, - }, - AddingChannel { - name: String, - }, - FinishedAddingChannel { - name: String, - }, - ListingChannels, - FinishedListingChannels, - ChannelInfo { - name: String, - url: String, - health: bool, - }, - RemovingChannel { - name: String, - }, - FinishedRemovingChannel { - name: String, - }, - UpdatingChannel { - name: Option, - }, - FinishedUpdatingChannel { - name: Option, - }, - BuildingProject { - name: String, - }, - FinishedBuildingProject { - name: String, - }, -} - -#[derive(Debug)] -pub enum ProjectDebug { - WritingFile { path: PathBuf, content: String }, - CreatingDirectory { path: PathBuf }, - RemoveFile { path: PathBuf }, - RemoveDir { path: PathBuf }, - BuildingFile { path: PathBuf, file_name: String }, -} - -impl From for ProjectError { - fn from(val: TransformError) -> Self { - match val { - TransformError::AngleNotOpened(location) => ProjectError::AngleNotOpened { location }, - TransformError::AngleNotClosed(location) => ProjectError::AngleNotClosed { location }, - TransformError::CurlyNotOpened(location) => ProjectError::CurlyNotOpened { location }, - TransformError::CurlyNotClosed(location) => ProjectError::CurlyNotClosed { location }, - TransformError::SquareNotOpened(location) => ProjectError::SquareNotOpened { location }, - TransformError::SquareNotClosed(location) => ProjectError::SquareNotClosed { location }, - TransformError::MismatchedBracket(location) => { - ProjectError::MismatchedBracket { location } - } - TransformError::ModuleNotFound(location) => ProjectError::ModuleNotFound { location }, - TransformError::IdentifierNotFound(location) => { - ProjectError::IdentifierNotFound { location } - } - TransformError::DotNotFound(location) => ProjectError::DotNotFound { location }, - TransformError::CommaNotFound(location) => ProjectError::CommaNotFound { location }, - TransformError::UnexpectedDot(location) => ProjectError::UnexpectedDot { location }, - TransformError::UnexpectedComma(location) => ProjectError::UnexpectedComma { location }, - TransformError::InvalidBackslash(location) => { - ProjectError::InvalidBackslash { location } - } - } - } -} diff --git a/crates/brack-common/src/transformer_errors.rs b/crates/brack-common/src/transformer_errors.rs deleted file mode 100644 index 471b975..0000000 --- a/crates/brack-common/src/transformer_errors.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::location::Location; -use std::fmt::{self, Display, Formatter}; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum TransformError { - AngleNotOpened(Location), - AngleNotClosed(Location), - CurlyNotOpened(Location), - CurlyNotClosed(Location), - SquareNotOpened(Location), - SquareNotClosed(Location), - MismatchedBracket(Location), - ModuleNotFound(Location), - IdentifierNotFound(Location), - DotNotFound(Location), - CommaNotFound(Location), - UnexpectedDot(Location), - UnexpectedComma(Location), - InvalidBackslash(Location), -} - -impl TransformError { - pub fn get_location(&self) -> Location { - match self { - Self::AngleNotOpened(location) => location.clone(), - Self::AngleNotClosed(location) => location.clone(), - Self::CurlyNotOpened(location) => location.clone(), - Self::CurlyNotClosed(location) => location.clone(), - Self::SquareNotOpened(location) => location.clone(), - Self::SquareNotClosed(location) => location.clone(), - Self::MismatchedBracket(location) => location.clone(), - Self::ModuleNotFound(location) => location.clone(), - Self::IdentifierNotFound(location) => location.clone(), - Self::DotNotFound(location) => location.clone(), - Self::CommaNotFound(location) => location.clone(), - Self::UnexpectedDot(location) => location.clone(), - Self::UnexpectedComma(location) => location.clone(), - Self::InvalidBackslash(location) => location.clone(), - } - } - - pub fn get_message(&self) -> String { - match self { - Self::AngleNotOpened(_) => "Angle bracket not opened".to_string(), - Self::AngleNotClosed(_) => "Angle bracket not closed".to_string(), - Self::CurlyNotOpened(_) => "Curly bracket not opened".to_string(), - Self::CurlyNotClosed(_) => "Curly bracket not closed".to_string(), - Self::SquareNotOpened(_) => "Square bracket not opened".to_string(), - Self::SquareNotClosed(_) => "Square bracket not closed".to_string(), - Self::MismatchedBracket(_) => "Mismatched bracket".to_string(), - Self::ModuleNotFound(_) => "Need module".to_string(), - Self::IdentifierNotFound(_) => "Need identifier after module".to_string(), - Self::DotNotFound(_) => "Need dot after module".to_string(), - Self::CommaNotFound(_) => "Need comma after module".to_string(), - Self::UnexpectedDot(_) => "Unexpected dot".to_string(), - Self::UnexpectedComma(_) => "Unexpected comma".to_string(), - Self::InvalidBackslash(_) => { - "Backslash must be followed by dot, comma, backslash, or bracket".to_string() - } - } - } -} - -impl Display for TransformError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let location = self.get_location(); - let message = self.get_message(); - write!( - f, - "Error at line {}, column {} to line {}, column {}: {}", - location.start.line, - location.start.character, - location.end.line, - location.end.character, - message - ) - } -} diff --git a/crates/brack-internal-plugin/Cargo.toml b/crates/brack-internal-plugin/Cargo.toml new file mode 100644 index 0000000..a34158c --- /dev/null +++ b/crates/brack-internal-plugin/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "brack-internal-plugin" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/crates/brack-internal-plugin/src/lib.rs b/crates/brack-internal-plugin/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/crates/brack-internal-plugin/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/crates/brack-language-server/src/logger.rs b/crates/brack-language-server/src/logger.rs index 26180ad..59922f1 100644 --- a/crates/brack-language-server/src/logger.rs +++ b/crates/brack-language-server/src/logger.rs @@ -1,16 +1,17 @@ -use brack_common::project_errors::{ProjectDebug, ProjectError, ProjectInfo, ProjectWarning}; use std::path::PathBuf; +use brack_common::errors::{Debug, Error, Info, Warning}; + pub struct Logger {} impl brack_common::logger::Logger for Logger { - fn error(&self, _error: &ProjectError) {} + fn error(&self, _error: &Error) {} - fn warn(&self, _warning: &ProjectWarning) {} + fn warn(&self, _warning: &Warning) {} - fn info(&self, _info: &ProjectInfo) {} + fn info(&self, _info: &Info) {} - fn debug(&self, _debug: &ProjectDebug) {} + fn debug(&self, _debug: &Debug) {} fn set_path(&mut self, _path: PathBuf) {} diff --git a/crates/brack-language-server/src/notification/did_open.rs b/crates/brack-language-server/src/notification/did_open.rs index f4f3fd1..9133f9b 100644 --- a/crates/brack-language-server/src/notification/did_open.rs +++ b/crates/brack-language-server/src/notification/did_open.rs @@ -1,6 +1,6 @@ use crate::{logger::Logger, server::Server}; use anyhow::Result; -use brack_project::project::Project; +use brack_project::projects::Project; use lsp_types::DidOpenTextDocumentParams; use std::path::Path; @@ -20,8 +20,8 @@ impl Server { .ok_or_else(|| anyhow::anyhow!("Invalid file path"))?; let logger = Logger {}; - let project = Project::new_with_manifest(&logger, root) - .map_err(|e| anyhow::anyhow!("Failed to create project: {:?}", e))?; + let project = Project::new_with_manifest(&logger, &root) + .map_err(|_| anyhow::anyhow!("Failed to create project"))?; self.project = Some(project); Ok(()) diff --git a/crates/brack-language-server/src/notification/did_save.rs b/crates/brack-language-server/src/notification/did_save.rs index 18ffbe0..7ae3173 100644 --- a/crates/brack-language-server/src/notification/did_save.rs +++ b/crates/brack-language-server/src/notification/did_save.rs @@ -29,25 +29,25 @@ impl Server { return self.send_publish_diagnostics(path_str, &diagnostics).await; } - let mut diagnostics = vec![]; - for error in errors { - let location = error.get_location(); - let message = error.get_message(); - let diagnostic = Diagnostic { - range: lsp_types::Range { - start: lsp_types::Position { - line: location.start.line as u32, - character: location.start.character as u32, - }, - end: lsp_types::Position { - line: location.end.line as u32, - character: location.end.character as u32, - }, - }, - message, - ..Default::default() - }; - diagnostics.push(diagnostic); + let diagnostics = vec![]; + for _error in errors { + // let location = error.get_location(); + // let message = error.get_message(); + // let diagnostic = Diagnostic { + // range: lsp_types::Range { + // start: lsp_types::Position { + // line: location.start.line as u32, + // character: location.start.character as u32, + // }, + // end: lsp_types::Position { + // line: location.end.line as u32, + // character: location.end.character as u32, + // }, + // }, + // message, + // ..Default::default() + // }; + // diagnostics.push(diagnostic); } self.send_publish_diagnostics(path_str, &diagnostics).await } diff --git a/crates/brack-language-server/src/server.rs b/crates/brack-language-server/src/server.rs index 083591a..63cf51a 100644 --- a/crates/brack-language-server/src/server.rs +++ b/crates/brack-language-server/src/server.rs @@ -1,7 +1,7 @@ use std::str::from_utf8; use anyhow::Result; -use brack_project::project::Project; +use brack_project::projects::Project; use lsp_types::{ClientCapabilities, Diagnostic}; use serde::Serialize; use serde_json::{from_str, json, Value}; diff --git a/crates/brack-lower/Cargo.toml b/crates/brack-lower/Cargo.toml new file mode 100644 index 0000000..6bdaed3 --- /dev/null +++ b/crates/brack-lower/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "brack-lower" +version = "0.2.0" +edition = "2021" + +[dependencies] +brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } +serde = "1.0.219" +serde_json = "1.0.140" diff --git a/crates/brack-lower/README_JA.md b/crates/brack-lower/README_JA.md new file mode 100644 index 0000000..e39b662 --- /dev/null +++ b/crates/brack-lower/README_JA.md @@ -0,0 +1,17 @@ +# brack-lower +Brackの抽象構文木(AST)を各ターゲットの中間表現(IR)に変換するクレート。 +以下の手順で適切なASTであるかどうかを検査します。 + +1. マクロの型検査 + 1. ASTに含まれるマクロに対して型検査を行います +1. マクロの展開 + 1. ASTにマクロが含まれなくなるまで展開を行います + 1. 上限(10000回程度)に達するまで展開してもなおマクロが含まれる場合にはエラーを返して展開を中断します + 1. マクロは、コマンドやマクロを含むBrackの文書に変換する置換型のマクロと、文書全体のASTと呼び出されたマクロのIDを渡されて文書全体のASTを返す手続き型のマクロが存在します +1. コマンドの型検査 + 1. 展開後のASTに含まれるコマンドに対して型検査を行います +1. コマンドの実行 + 1. コマンドを実行し、各ターゲットのIRに変換します + +多くの場合には各ターゲットの成果物に変換する処理がボトルネックになります。 + diff --git a/crates/brack-lower/src/expand.rs b/crates/brack-lower/src/expand.rs new file mode 100644 index 0000000..994e3f8 --- /dev/null +++ b/crates/brack-lower/src/expand.rs @@ -0,0 +1,67 @@ +use brack_common::{ast::{InnerNode, AST}, errors::Error, plugins::{CommandType, Plugins}}; + +fn apply_expanding_to_node(node: &InnerNode, plugins: &Plugins) -> InnerNode { + let mut children = vec![]; + for child in node.children.clone() { + children.push(expand_once(&child, plugins)); + } + return InnerNode { + id: node.id.clone(), + location: node.location.clone(), + children, + } +} + +fn expand_once(ast: &AST, plugins: &Plugins) -> Result { + match ast { + AST::Document(node) => AST::Document(apply_expanding_to_node(node, plugins)), + AST::Stmt(node) => AST::Stmt(apply_expanding_to_node(node, plugins)), + AST::Expr(node) => AST::Expr(apply_expanding_to_node(node, plugins)), + AST::Angle(node) => { + let children = node.children.clone(); + let module_name = if let AST::Module(node) = children.get(0).unwrap() { + node.value().unwrap() + } else { + // エラーを返す + }; + let ident_name = if let AST::Ident(node) = children.get(1).unwrap() { + node.value().unwrap() + } else { + // error + }; + let plugin = plugins.get_mut(module_name).unwrap(); + plugin.call(ident_name, &CommandType::Macro, args) + .map_err(|err| Error::PluginError(err)) + }, + otherwise => otherwise.clone(), + } +} + +fn remain_macro_for_inner_node(node: &InnerNode) -> bool { + let mut result = false; + for child in node.children.clone() { + result = result || remain_macro(&child); + } + result +} + +fn remain_macro(ast: &AST) -> bool { + match ast { + AST::Document(node) + | AST::Stmt(node) + | AST::Expr(node) + | AST::Curly(node) + | AST::Square(node) => remain_macro_for_inner_node(node), + AST::Angle(_) => true, + _ => false, + } +} + +pub(crate) fn expand(ast: &AST, plugins: &Plugins) -> AST { + let mut result = ast.clone(); + while !remain_macro(ast) { + result = expand_once(ast, plugins); + } + return result; +} + diff --git a/crates/brack-lower/src/html.rs b/crates/brack-lower/src/html.rs new file mode 100644 index 0000000..aed824d --- /dev/null +++ b/crates/brack-lower/src/html.rs @@ -0,0 +1,120 @@ +use std::collections::HashMap; + +use brack_common::{ + ast::{InnerNode, LeafNode, AST}, + html::{Html, HtmlTag}, + logger::Logger, + plugins::Plugins, +}; + +pub enum LowerError { + RemainingMacros, + DocumentNotFound, + MustNotBeTopLevelNode, +} + +pub fn lowering( + ast: AST, + plugins: &mut Plugins, + logger: &L, +) -> (Option, Vec) { + let (ast, mut errors) = expand(ast, plugins, logger); + match ast { + AST::Document(_) => (), + _ => { + let err = LowerError::DocumentNotFound; + errors.push(err); + } + } + let (html, new_errors) = match ast { + AST::Stmt(stmt) => lowering_stmt(stmt, plugins, logger), + AST::Expr(expr) => lowering_expr(expr, plugins, logger), + AST::Angle(_) => { + let err = LowerError::RemainingMacros; + (None, vec![err]) + } + AST::Curly(curly) => lowering_curly(curly, plugins, logger), + AST::Square(square) => lowering_square(square, plugins, logger), + AST::Text(text) => lowering_text(text, plugins, logger), + _ => { + let err = LowerError::MustNotBeTopLevelNode; + (None, vec![err]) + } + }; + errors.extend(new_errors); + (html, errors) +} + +fn expand( + ast: AST, + _plugins: &mut Plugins, + _logger: &L, +) -> (AST, Vec) { + return (ast, vec![]); +} + +fn lowering_stmt( + stmt: InnerNode, + plugins: &mut Plugins, + logger: &L, +) -> (Option, Vec) { + let mut html = Html { + tag: HtmlTag::Div, + attributes: HashMap::new(), + children: vec![], + }; + let mut errors = vec![]; + for child in stmt.children { + let (child_html, child_errors) = lowering(child, plugins, logger); + if let Some(child_html) = child_html { + html.children.push(child_html); + } + errors.extend(child_errors); + } + (Some(html), errors) +} + +fn lowering_expr( + expr: InnerNode, + plugins: &mut Plugins, + logger: &L, +) -> (Option, Vec) { + let mut html = Html { + tag: HtmlTag::Span, + attributes: HashMap::new(), + children: vec![], + }; + let mut errors = vec![]; + for child in expr.children { + let (child_html, child_errors) = lowering(child, plugins, logger); + if let Some(child_html) = child_html { + html.children.push(child_html); + } + errors.extend(child_errors); + } + (Some(html), errors) +} + +fn lowering_curly( + curly: InnerNode, + plugins: &mut Plugins, + logger: &L, +) -> (Option, Vec) { + lowering(curly.children[0].clone(), plugins, logger) +} + +fn lowering_square( + square: InnerNode, + plugins: &mut Plugins, + logger: &L, +) -> (Option, Vec) { + lowering(square.children[0].clone(), plugins, logger) +} + +fn lowering_text( + text: LeafNode, + plugins: &mut Plugins, + logger: &L, +) -> (Option, Vec) { + lowering(AST::Text(text), plugins, logger) +} diff --git a/crates/brack-lower/src/lib.rs b/crates/brack-lower/src/lib.rs new file mode 100644 index 0000000..8ff7c59 --- /dev/null +++ b/crates/brack-lower/src/lib.rs @@ -0,0 +1,3 @@ +pub mod expand; +pub mod html; +pub mod lowering; diff --git a/crates/brack-lower/src/lowering.rs b/crates/brack-lower/src/lowering.rs new file mode 100644 index 0000000..8dec6ec --- /dev/null +++ b/crates/brack-lower/src/lowering.rs @@ -0,0 +1,7 @@ +pub trait Lower { + type Output; + + fn tycheck_macro() -> AST; + fn expand() -> AST; + fn tycheck() -> AST; +} diff --git a/crates/brack-lower/src/tycheck.rs b/crates/brack-lower/src/tycheck.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/brack-lower/src/tycheck_macro.rs b/crates/brack-lower/src/tycheck_macro.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/brack-plugin/Cargo.toml b/crates/brack-plugin/Cargo.toml deleted file mode 100644 index 681ffac..0000000 --- a/crates/brack-plugin/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "brack-plugin" -version = "0.2.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1.0.97" -brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } -extism = "1.9.1" -serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.134" -extism-convert = { version = "1.9.1" } - diff --git a/crates/brack-plugin/src/feature_flag.rs b/crates/brack-plugin/src/feature_flag.rs deleted file mode 100644 index 9a4d362..0000000 --- a/crates/brack-plugin/src/feature_flag.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -pub struct FeatureFlag { - pub document_hook: bool, - pub stmt_hook: bool, - pub expr_hook: bool, - pub text_hook: bool, -} diff --git a/crates/brack-plugin/src/lib.rs b/crates/brack-plugin/src/lib.rs deleted file mode 100644 index 73d8bb4..0000000 --- a/crates/brack-plugin/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod feature_flag; -pub mod metadata; -pub mod plugin; -pub mod plugins; -pub mod types; -pub mod value; diff --git a/crates/brack-plugin/src/metadata.rs b/crates/brack-plugin/src/metadata.rs deleted file mode 100644 index 306daae..0000000 --- a/crates/brack-plugin/src/metadata.rs +++ /dev/null @@ -1,11 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::Type; - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Metadata { - pub command_name: String, - pub call_name: String, - pub argument_types: Vec<(String, Type)>, - pub return_type: Type, -} diff --git a/crates/brack-plugin/src/plugin.rs b/crates/brack-plugin/src/plugin.rs deleted file mode 100644 index 4ea4f31..0000000 --- a/crates/brack-plugin/src/plugin.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::{ - collections::HashMap, - fs::{self}, - path::Path, -}; - -use crate::{feature_flag::FeatureFlag, metadata::Metadata, types::Type}; -use anyhow::Result; -use extism::{FromBytes, Plugin as ExtismPlugin, ToBytes}; -use extism_convert::Json; - -#[derive(Debug)] -pub struct Plugin { - pub name: String, - pub(crate) extism_plugin: ExtismPlugin, - pub signature_to_metadata: HashMap<(String, Type), Metadata>, - pub(crate) feature_flag: FeatureFlag, -} - -impl Plugin { - pub fn new>( - name: &str, - wasm_bin_path: P, - feature_flag: FeatureFlag, - ) -> Result { - let wasm_bin = fs::read(wasm_bin_path)?; - let mut extism_plugin = ExtismPlugin::new(wasm_bin, [], true)?; - let Json(metadatas) = extism_plugin.call::<(), Json>>("get_metadata", ())?; - let mut signature_to_metadata = HashMap::new(); - - let mut exists_document_hook = false; - let mut exists_stmt_hook = false; - let mut exists_expr_hook = false; - let mut exists_text_hook = false; - - for metadata in metadatas { - let command_name = metadata.command_name.clone(); - let return_type = metadata.return_type.clone(); - if command_name == "document" && feature_flag.document_hook { - if return_type != Type::TBlock { - return Err(anyhow::anyhow!("document hook must return TBlock")); - } - exists_document_hook = true; - } - if command_name == "stmt" && feature_flag.stmt_hook { - if return_type != Type::TBlock { - return Err(anyhow::anyhow!("stmt hook must return TBlock")); - } - exists_stmt_hook = true; - } - if command_name == "expr" && feature_flag.expr_hook { - if return_type != Type::TInline { - return Err(anyhow::anyhow!("expr hook must return TInline")); - } - exists_expr_hook = true; - } - if command_name == "text" && feature_flag.text_hook { - if return_type != Type::TInline { - return Err(anyhow::anyhow!("text hook must return TInline")); - } - exists_text_hook = true; - } - signature_to_metadata.insert((command_name, return_type), metadata); - } - - if feature_flag.document_hook && !exists_document_hook { - return Err(anyhow::anyhow!("document hook not found")); - } - if feature_flag.stmt_hook && !exists_stmt_hook { - return Err(anyhow::anyhow!("stmt hook not found")); - } - if feature_flag.expr_hook && !exists_expr_hook { - return Err(anyhow::anyhow!("expr hook not found")); - } - if feature_flag.text_hook && !exists_text_hook { - return Err(anyhow::anyhow!("text hook not found")); - } - - Ok(Self { - name: name.to_string(), - extism_plugin, - signature_to_metadata, - feature_flag, - }) - } - - pub(crate) fn call ToBytes<'a>, U: for<'a> FromBytes<'a>>( - &mut self, - command_name: &str, - return_type: Type, - args: T, - ) -> Result { - let metadata = self - .signature_to_metadata - .get(&(command_name.to_string(), return_type)) - .ok_or_else(|| anyhow::anyhow!("metadata not found: {}", command_name))?; - let result = self - .extism_plugin - .call::(metadata.call_name.clone(), args)?; - Ok(result) - } -} diff --git a/crates/brack-plugin/src/plugins.rs b/crates/brack-plugin/src/plugins.rs deleted file mode 100644 index b6aa177..0000000 --- a/crates/brack-plugin/src/plugins.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::collections::HashMap; - -use anyhow::Result; -use brack_common::ast::AST; -use extism::{FromBytes, ToBytes}; -use extism_convert::Json; - -use crate::{plugin::Plugin, types::Type, value::Value}; - -pub struct Plugins { - pub name_to_plugin: HashMap, - document_hook_plugin_name: Option, - stmt_hook_plugin_name: Option, - expr_hook_plugin_name: Option, - text_hook_plugin_name: Option, -} - -impl Plugins { - pub fn new(plugins: Vec) -> Result { - let mut name_to_plugin = HashMap::new(); - let mut document_hook_plugin_name = None; - let mut stmt_hook_plugin_name = None; - let mut expr_hook_plugin_name = None; - let mut text_hook_plugin_name = None; - - for plugin in plugins { - let name = plugin.name.clone(); - if plugin.feature_flag.document_hook { - if document_hook_plugin_name.is_some() { - return Err(anyhow::anyhow!("only one document hook is allowed")); - } - document_hook_plugin_name = Some(name.clone()); - } - if plugin.feature_flag.stmt_hook { - if stmt_hook_plugin_name.is_some() { - return Err(anyhow::anyhow!("only one stmt hook is allowed")); - } - stmt_hook_plugin_name = Some(name.clone()); - } - if plugin.feature_flag.expr_hook { - if expr_hook_plugin_name.is_some() { - return Err(anyhow::anyhow!("only one expr hook is allowed")); - } - expr_hook_plugin_name = Some(name.clone()); - } - if plugin.feature_flag.text_hook { - if text_hook_plugin_name.is_some() { - return Err(anyhow::anyhow!("only one text hook is allowed")); - } - text_hook_plugin_name = Some(name.clone()); - } - name_to_plugin.insert(name, plugin); - } - - Ok(Self { - name_to_plugin, - document_hook_plugin_name, - stmt_hook_plugin_name, - expr_hook_plugin_name, - text_hook_plugin_name, - }) - } - - pub fn argument_types( - &self, - module_name: &str, - command_name: &str, - typ: Type, - ) -> Result> { - let plugin = self - .name_to_plugin - .get(module_name) - .ok_or_else(|| anyhow::anyhow!("plugin not found: {}", module_name))?; - let metadata = plugin - .signature_to_metadata - .get(&(command_name.to_string(), typ)) - .ok_or_else(|| anyhow::anyhow!("command not found: {}", command_name))?; - Ok(metadata.argument_types.clone()) - } - - fn call ToBytes<'a>, U: for<'a> FromBytes<'a>>( - &mut self, - plugin_name: &str, - command_name: &str, - return_type: Type, - args: T, - ) -> Result { - let plugin = self - .name_to_plugin - .get_mut(plugin_name) - .ok_or_else(|| anyhow::anyhow!("plugin not found: {}", plugin_name))?; - let result = plugin.call(command_name, return_type, args)?; - Ok(result) - } - - pub fn call_inline_command( - &mut self, - plugin_name: &str, - command_name: &str, - args: Vec, - ) -> Result { - let result = self.call::>, String>( - plugin_name, - command_name, - Type::TInline, - Json(args), - )?; - Ok(result) - } - - pub fn call_block_command( - &mut self, - plugin_name: &str, - command_name: &str, - args: Vec, - ) -> Result { - let result = self.call::>, String>( - plugin_name, - command_name, - Type::TBlock, - Json(args), - )?; - Ok(result) - } - - pub fn call_macro_command( - &mut self, - plugin_name: &str, - command_name: &str, - ast: AST, - id: String, - ) -> Result { - let result = self.call::, Json>( - plugin_name, - command_name, - Type::TAST, - Json((ast, id)), - )?; - let Json(ast) = result; - Ok(ast) - } - - pub fn call_document_hook(&mut self, args: Vec) -> Result> { - let document_hook_plugin_name = self.document_hook_plugin_name.clone(); - if let Some(plugin_name) = document_hook_plugin_name { - let result = self.call_block_command(&plugin_name, "document", args)?; - return Ok(Some(result)); - } - Ok(None) - } - - pub fn call_stmt_hook(&mut self, args: Vec) -> Result> { - let stmt_hook_plugin_name = self.stmt_hook_plugin_name.clone(); - if let Some(plugin_name) = stmt_hook_plugin_name { - let result = self.call_block_command(&plugin_name, "stmt", args)?; - return Ok(Some(result)); - } - Ok(None) - } - - pub fn call_expr_hook(&mut self, args: Vec) -> Result> { - let expr_hook_plugin_name = self.expr_hook_plugin_name.clone(); - if let Some(plugin_name) = expr_hook_plugin_name { - let result = self.call_inline_command(&plugin_name, "expr", args)?; - return Ok(Some(result)); - } - Ok(None) - } - - pub fn call_text_hook(&mut self, args: Vec) -> Result> { - let text_hook_plugin_name = self.text_hook_plugin_name.clone(); - if let Some(plugin_name) = text_hook_plugin_name { - let result = self.call_inline_command(&plugin_name, "text", args)?; - return Ok(Some(result)); - } - Ok(None) - } -} diff --git a/crates/brack-plugin/src/types.rs b/crates/brack-plugin/src/types.rs deleted file mode 100644 index e6b4a75..0000000 --- a/crates/brack-plugin/src/types.rs +++ /dev/null @@ -1,34 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] -pub enum Type { - TInline, - TOption(Box), - TBlock, - TArray(Box), - TInlineCmd(String), - TBlockCmd(String), - TAST, -} - -pub fn arg_counter(arg_types: &Vec) -> (usize, usize) { - let mut min = 0; - let mut max = 0; - for arg_type in arg_types { - match arg_type { - Type::TOption(_) => { - min += 0; - max += 1; - } - Type::TArray(_) => { - min += 0; - max = usize::MAX; - } - _ => { - min += 1; - max += 1; - } - } - } - (min, max) -} diff --git a/crates/brack-plugin/src/value.rs b/crates/brack-plugin/src/value.rs deleted file mode 100644 index 981e6e9..0000000 --- a/crates/brack-plugin/src/value.rs +++ /dev/null @@ -1,8 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub enum Value { - Text(String), - TextArray(Vec), - TextOption(Option), -} diff --git a/crates/brack-plugin/test.html.wasm b/crates/brack-plugin/test.html.wasm deleted file mode 100755 index 491ee65..0000000 Binary files a/crates/brack-plugin/test.html.wasm and /dev/null differ diff --git a/crates/brack-project/src/channels.rs b/crates/brack-project/src/channels.rs new file mode 100644 index 0000000..d01afab --- /dev/null +++ b/crates/brack-project/src/channels.rs @@ -0,0 +1,272 @@ +use std::{collections::HashMap, path::Path}; + +use brack_common::{ + errors::{ProjectDebug, ProjectError, ProjectInfo}, + logger::Logger, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + manifest::License, + utils::{create_dir, toml_to_string, write, DEFAULT_PROJECT_TARGET_PATH}, +}; + +pub struct ChannelProvider { + name: String, + url: String, +} + +pub struct Channels { + channels: HashMap, +} + +impl Channels { + pub fn new() -> Self { + Channels { + channels: HashMap::new(), + } + } + + pub fn ensure_channel_not_exists(&self, name: &str) -> Result<(), ProjectError> { + if self.channels.contains_key(name) { + return Err(ProjectError::ChannelAlreadyExists { + name: name.to_string(), + }); + } + Ok(()) + } +} + +pub type Channel = HashMap; + +#[derive(Serialize, Deserialize, Clone)] +pub struct PluginProvider { + name: String, + url: String, + method: PluginFetchMethod, + licenses: Vec, + targets: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +pub enum PluginFetchMethod { + Git, +} + +pub fn check_valid_channel_name(name: &str) -> Result<(), ProjectError> { + if name.is_empty() { + return Err(ProjectError::InvalidChannelName { + name: name.to_string(), + }); + } + if !name.chars().all(|c| c.is_alphanumeric() || c == '_') { + return Err(ProjectError::InvalidChannelName { + name: name.to_string(), + }); + } + Ok(()) +} + +pub fn check_valid_channel_url(url: &str) -> Result<(), ProjectError> { + if url.is_empty() { + return Err(ProjectError::InvalidChannelUrl { + url: url.to_string(), + }); + } + if !url.starts_with("http://") && !url.starts_with("https://") { + return Err(ProjectError::InvalidChannelUrl { + url: url.to_string(), + }); + } + Ok(()) +} + +impl ChannelProvider { + pub fn new(name: &str, url: &str) -> Result { + check_valid_channel_name(name)?; + check_valid_channel_url(url)?; + Ok(ChannelProvider { + name: name.to_string(), + url: url.to_string(), + }) + } + + pub async fn fetch(&self, logger: &L) -> Result { + logger.info( + &ProjectInfo::FetchingChannel { + name: self.name.clone(), + url: self.url.clone(), + } + .into(), + ); + let response = reqwest::get(self.url.clone()).await.map_err(|source| { + ProjectError::FailedToFetchChannel { + url: self.url.clone(), + source, + } + })?; + logger.debug( + &ProjectDebug::ReceivedResponse { + url: self.url.clone(), + } + .into(), + ); + if !response.status().is_success() { + let status = response.status(); + let reason = status.canonical_reason().unwrap_or("Unknown reqwest error"); + return Err(ProjectError::ReceivedHttpNonSuccessStatus { + url: self.url.clone(), + status: status.as_u16(), + reason: reason.to_string(), + }); + } + let downloaded_data = response.bytes().await.map_err(|source| { + ProjectError::FailedToConvertResponseToBytes { + url: self.url.clone(), + source, + } + })?; + logger.debug( + &ProjectDebug::ConvertingResponseToBytes { + url: self.url.clone(), + } + .into(), + ); + let downloaded_data_str = std::str::from_utf8(&downloaded_data) + .map_err(|source| ProjectError::FailedToConvertBytesToStr { source })?; + logger.debug( + &ProjectDebug::ConvertingBytesToStr { + url: self.url.clone(), + } + .into(), + ); + let channel: Channel = toml::from_str(downloaded_data_str) + .map_err(|source| ProjectError::FailedToDeserializeToChannel { source })?; + logger.debug(&ProjectDebug::DeserializingToChannel.into()); + logger.info( + &ProjectInfo::FinishedFetchingChannel { + name: self.name.clone(), + } + .into(), + ); + Ok(channel) + } + + pub fn write, L: Logger>( + &self, + project_root: &P, + channel: &Channel, + logger: &L, + ) -> Result<(), ProjectError> { + let path = project_root + .as_ref() + .join(DEFAULT_PROJECT_TARGET_PATH) + .join(".channels") + .join(&self.name); + create_dir(&path, logger)?; + let path = path.join("Channel.toml"); + if path.exists() { + return Err(ProjectError::ChannelAlreadyExists { + name: self.name.clone(), + }); + } + let content = toml_to_string(channel, logger)?; + write(&path, &content, logger)?; + Ok(()) + } +} + +// fn get_brack_progress_bar() -> ProgressBar { +// let pb = ProgressBar::new(0); +// pb.set_style( +// ProgressStyle::default_bar() +// .template("[{elapsed_precise}] [{bar:40.cyan/blue}] {pos:>3}/{len:3} {msg}") +// .unwrap() +// .progress_chars("=>-"), +// ); +// pb +// } + +// async fn download_plugin_from_channel(channel: Channel, version: String) -> Result<()> { +// Ok(()) +// } + +// async fn download_plugin_from_local( +// path: String, +// feature_flags: Option>, +// ) -> Result<()> { +// Ok(()) +// } + +// async fn download_plugin_from_github(owner: String, name: String, tag: String) -> Result<()> { +// let url = format!( +// "https://github.com/{}/{}/releases/download/{}/{}.tar.gz", +// owner, name, tag, name +// ); +// let pb = get_brack_progress_bar(); +// pb.set_message(format!("Downloading plugin {}", name)); +// let response = reqwest::get(&url).await?; +// if !response.status().is_success() { +// pb.finish_and_clear(); +// anyhow::bail!( +// "Failed to download plugin from {}.\nStatus: {} - {}", +// url, +// response.status().as_str(), +// response +// .status() +// .canonical_reason() +// .unwrap_or("Unknown error") +// ); +// } +// if let Some(size) = response.content_length() { +// pb.set_length(size); +// } +// let mut stream = response.bytes_stream(); +// let mut downloaded_data = Vec::new(); +// while let Some(chunk) = stream.next().await { +// let chunk = chunk?; +// pb.inc(chunk.len() as u64); +// downloaded_data.extend_from_slice(&chunk); +// } +// pb.finish_with_message(format!("Downloaded plugin {}", name)); +// let mut tar = tar::Archive::new(std::io::Cursor::new(downloaded_data)); +// tar.unpack("plugins")?; +// Ok(()) +// } + +// async fn download_plugins(manifest: &Manifest, channels: &Channels) -> Result { +// let mut dependencies = vec![]; +// if let Some(deps) = manifest.dependencies.clone() { +// for (name, dep) in deps { +// match dep { +// Dependency::Version(version) => {} +// Dependency::Channel { +// channel, +// version, +// feature_flags, +// } => { +// let channel_name = match channel { +// Some(channel) => channel, +// None => String::from("default"), +// }; +// let channel = channels +// .get(&channel_name) +// .ok_or_else(|| anyhow::anyhow!("Channel {} not found", channel_name))?; +// let channel = channel.clone(); +// download_plugin_from_channel(channel, version).await? +// } +// Dependency::GitHub { +// owner, +// name, +// tag, +// feature_flags, +// } => download_plugin_from_github(owner, name, tag).await?, +// Dependency::Local { +// path, +// feature_flags, +// } => download_plugin_from_local(path, feature_flags).await?, +// } +// } +// } +// Plugins::new(dependencies) +// } diff --git a/crates/brack-project/src/lib.rs b/crates/brack-project/src/lib.rs index 2849007..a02a46f 100644 --- a/crates/brack-project/src/lib.rs +++ b/crates/brack-project/src/lib.rs @@ -1,2 +1,4 @@ +pub mod channels; pub mod manifest; -pub mod project; +pub mod projects; +mod utils; diff --git a/crates/brack-project/src/project.rs b/crates/brack-project/src/project.rs deleted file mode 100644 index 74307cf..0000000 --- a/crates/brack-project/src/project.rs +++ /dev/null @@ -1,766 +0,0 @@ -use crate::manifest::{Author, DocumentSettings, License, Manifest}; -use anyhow::Result; -use brack_common::{ - logger::Logger, - project_errors::{ProjectDebug, ProjectError, ProjectInfo, ProjectWarning}, -}; -use brack_plugin::feature_flag::FeatureFlag; -// use brack_plugin::plugins::Plugins; -// use futures_util::StreamExt; -// use indicatif::{ProgressBar, ProgressStyle}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fs::{create_dir_all, read_to_string, remove_dir_all, remove_file, write}; -use std::path::{Path, PathBuf}; -use walkdir::WalkDir; - -pub struct Project { - pub manifest: Manifest, - pub project_root: Option, - pub channels: Option, - // todo: delete this field - pub plugins_metadata: HashMap, -} - -const BRACK_TOML: &str = "Brack.toml"; - -pub type Channels = HashMap; - -pub type Channel = HashMap; - -#[derive(Serialize, Deserialize, Clone)] -pub enum PluginInChannelMethod { - Git, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct PluginInChannel { - pub name: String, - pub method: PluginInChannelMethod, - pub url: String, - pub licenses: Vec, - pub support_backends: Vec, -} - -// fn get_brack_progress_bar() -> ProgressBar { -// let pb = ProgressBar::new(0); -// pb.set_style( -// ProgressStyle::default_bar() -// .template("[{elapsed_precise}] [{bar:40.cyan/blue}] {pos:>3}/{len:3} {msg}") -// .unwrap() -// .progress_chars("=>-"), -// ); -// pb -// } - -// async fn download_plugin_from_channel(channel: Channel, version: String) -> Result<()> { -// Ok(()) -// } - -// async fn download_plugin_from_local( -// path: String, -// feature_flags: Option>, -// ) -> Result<()> { -// Ok(()) -// } - -// async fn download_plugin_from_github(owner: String, name: String, tag: String) -> Result<()> { -// let url = format!( -// "https://github.com/{}/{}/releases/download/{}/{}.tar.gz", -// owner, name, tag, name -// ); -// let pb = get_brack_progress_bar(); -// pb.set_message(format!("Downloading plugin {}", name)); -// let response = reqwest::get(&url).await?; -// if !response.status().is_success() { -// pb.finish_and_clear(); -// anyhow::bail!( -// "Failed to download plugin from {}.\nStatus: {} - {}", -// url, -// response.status().as_str(), -// response -// .status() -// .canonical_reason() -// .unwrap_or("Unknown error") -// ); -// } -// if let Some(size) = response.content_length() { -// pb.set_length(size); -// } -// let mut stream = response.bytes_stream(); -// let mut downloaded_data = Vec::new(); -// while let Some(chunk) = stream.next().await { -// let chunk = chunk?; -// pb.inc(chunk.len() as u64); -// downloaded_data.extend_from_slice(&chunk); -// } -// pb.finish_with_message(format!("Downloaded plugin {}", name)); -// let mut tar = tar::Archive::new(std::io::Cursor::new(downloaded_data)); -// tar.unpack("plugins")?; -// Ok(()) -// } - -// async fn download_plugins(manifest: &Manifest, channels: &Channels) -> Result { -// let mut dependencies = vec![]; -// if let Some(deps) = manifest.dependencies.clone() { -// for (name, dep) in deps { -// match dep { -// Dependency::Version(version) => {} -// Dependency::Channel { -// channel, -// version, -// feature_flags, -// } => { -// let channel_name = match channel { -// Some(channel) => channel, -// None => String::from("default"), -// }; -// let channel = channels -// .get(&channel_name) -// .ok_or_else(|| anyhow::anyhow!("Channel {} not found", channel_name))?; -// let channel = channel.clone(); -// download_plugin_from_channel(channel, version).await? -// } -// Dependency::GitHub { -// owner, -// name, -// tag, -// feature_flags, -// } => download_plugin_from_github(owner, name, tag).await?, -// Dependency::Local { -// path, -// feature_flags, -// } => download_plugin_from_local(path, feature_flags).await?, -// } -// } -// } -// Plugins::new(dependencies) -// } - -// async fn download_channels(manifest: &Manifest) -> Result { -// let mut result: Channels = HashMap::new(); -// if let Some(channels) = manifest.channels.clone() { -// for (name, url) in channels { -// let pb = get_brack_progress_bar(); -// pb.set_message(format!("Downloading channel {}", name)); -// let response = reqwest::get(&url).await?; -// if !response.status().is_success() { -// pb.finish_and_clear(); -// anyhow::bail!( -// "Failed to download channel from {}.\nStatus: {} - {}", -// url, -// response.status().as_str(), -// response -// .status() -// .canonical_reason() -// .unwrap_or("Unknown error") -// ); -// } -// if let Some(size) = response.content_length() { -// pb.set_length(size); -// } -// let mut stream = response.bytes_stream(); -// let mut downloaded_data = Vec::new(); -// while let Some(chunk) = stream.next().await { -// let chunk = chunk?; -// pb.inc(chunk.len() as u64); -// downloaded_data.extend_from_slice(&chunk); -// } -// pb.finish_with_message(format!("Downloaded channel {}", name)); -// let downloaded_data_str = std::str::from_utf8(&downloaded_data)?; -// if let Ok(channel) = toml::from_str::(downloaded_data_str) { -// result.insert(name, channel); -// } else { -// anyhow::bail!("Failed to parse channel from {}", name); -// } -// } -// } -// Ok(result) -// } - -const DEFAULT_PROJECT_SRC_PATH: &str = "docs"; -const DEFAULT_PROJECT_TARGET_PATH: &str = "target"; - -fn try_create_dir, L: Logger>(path: &P, logger: &L) -> Result<(), ProjectError> { - let path = path.as_ref().to_path_buf(); - if path.exists() { - // logger.debug(&ProjectDebug::DirectoryAlreadyExists { path }); - return Ok(()); - } - match create_dir_all(&path) { - Ok(_) => { - logger.debug(&ProjectDebug::CreatingDirectory { path }); - Ok(()) - } - Err(err) => { - let err = ProjectError::FailedToCreateDirectory { path, source: err }; - logger.error(&err); - Err(err) - } - } -} - -fn try_write, L: Logger>( - path: &P, - content: &str, - logger: &L, -) -> Result<(), ProjectError> { - let path = path.as_ref().to_path_buf(); - match write(&path, content) { - Ok(_) => { - logger.debug(&ProjectDebug::WritingFile { - path, - content: content.to_string(), - }); - Ok(()) - } - Err(err) => { - let err = ProjectError::FailedToWriteFile { path, source: err }; - logger.error(&err); - Err(err) - } - } -} - -fn try_read_to_string, L: Logger>( - path: &P, - logger: &L, -) -> Result { - let path = path.as_ref().to_path_buf(); - match read_to_string(&path) { - Ok(content) => { - // logger.log(ProjectDebug::ReadingFile(path_str, content.clone()).into()); - Ok(content) - } - Err(source) => { - let err = ProjectError::FailedToReadFile { path, source }; - logger.error(&err); - Err(err) - } - } -} - -impl Default for Project { - fn default() -> Self { - Self::new() - } -} - -impl Project { - pub fn new() -> Self { - Self { - manifest: Manifest::default(), - project_root: None, - plugins_metadata: HashMap::default(), - channels: None, - } - } - - /// If the Brack.toml file exists, load it instead of `new` - pub fn new_with_manifest, L: Logger>( - logger: &L, - project_root: P, - ) -> Result { - let path = project_root.as_ref().join(BRACK_TOML); - if !path.exists() { - let err = ProjectError::ManifestNotFound { path }; - logger.error(&err); - return Err(err); - } - let file = try_read_to_string(&path, logger)?; - let manifest = toml::from_str(&file).map_err(|source| { - let err = ProjectError::FailedToDeserializeManifest { source }; - logger.error(&err); - err - })?; - Ok(Self { - manifest, - project_root: Some(project_root.as_ref().to_path_buf()), - plugins_metadata: HashMap::default(), - // fix: manifest.channels to Channels - channels: None, - }) - } - - /// Corresponds to `brack create` command - pub fn create_document, L: Logger>( - &mut self, - logger: &L, - path: P, - ) -> Result<(), ProjectError> { - let path = path.as_ref(); - let name = path - .file_name() - .ok_or_else(|| { - let err = ProjectError::FailedToGetFileNameFromPath { - path: path.to_path_buf(), - }; - logger.error(&err); - err - })? - .to_str() - .ok_or_else(|| { - let err = ProjectError::FailedToConvertPathToStr { - path: path.to_path_buf(), - }; - logger.error(&err); - err - })? - .to_string(); - - logger.info(&ProjectInfo::CreatingProject { name: name.clone() }); - - if let Some(project_root) = &self.project_root { - let err = ProjectError::ProjectAlreadyExists { - path: project_root.to_path_buf(), - }; - logger.error(&err); - return Err(err); - } - - if path.exists() { - let err = ProjectError::DirectoryAlreadyExists { - path: path.to_path_buf(), - }; - logger.error(&err); - return Err(err); - } - - try_create_dir(&path, logger)?; - try_create_dir(&path.join(DEFAULT_PROJECT_SRC_PATH), logger)?; - try_write( - &path.join(format!("{}/main.[]", DEFAULT_PROJECT_SRC_PATH)), - "Hello, Brack!", - logger, - )?; - - self.project_root = Some(path.to_path_buf()); - self.manifest.name = name.clone(); - self.manifest.version = String::from("0.1.0"); - self.manifest.authors = Some(vec![Author { - name: String::from("your name"), - email: String::from("you@example.com"), - }]); - self.manifest.dependencies = Some(HashMap::default()); - self.manifest.document = Some(DocumentSettings { - target: String::from("html"), - output_level: None, - output_format: None, - src: String::from(DEFAULT_PROJECT_SRC_PATH), - }); - self.write_manifest(logger)?; - logger.info(&ProjectInfo::FinishedCreatingProject { name }); - Ok(()) - } - - pub fn write_manifest(&self, logger: &L) -> Result<(), ProjectError> { - match &self.project_root { - Some(project_root) => { - let manifest_path = project_root.join(BRACK_TOML); - let file = match toml::to_string(&self.manifest) { - Ok(file) => file, - Err(err) => { - let err = ProjectError::FailedToSerializeManifest { source: err }; - logger.error(&err); - return Err(err); - } - }; - try_write(&manifest_path, &file, logger)?; - } - _ => { - let err = ProjectError::UninitializedProject; - logger.error(&err); - return Err(err); - } - }; - Ok(()) - } - - pub async fn build(&self, logger: &mut L) -> Result<(), ProjectError> { - // let channels = download_channels(&self.manifest).await?; - // let _plugins = download_plugins(&self.manifest, &channels).await?; - let mut plugins = match brack_plugin::plugins::Plugins::new(vec![]) { - Ok(plugins) => plugins, - Err(_) => { - let err = ProjectError::FailedToCreatePlugin; - logger.error(&err); - return Err(err); - } - }; - let name = self.manifest.name.clone(); - logger.info(&ProjectInfo::BuildingProject { name: name.clone() }); - if self.project_root.is_none() { - let err = ProjectError::UninitializedProject; - logger.error(&err); - return Err(err); - } - let project_root = self.project_root.as_ref().unwrap(); - let docs_path = project_root.join(DEFAULT_PROJECT_SRC_PATH); - let target = match &self.manifest.document { - Some(document) => &document.target, - _ => { - let err = ProjectError::DocumentSettingsNotFound; - logger.error(&err); - return Err(err); - } - }; - let target_path = project_root.join(DEFAULT_PROJECT_TARGET_PATH).join(target); - try_create_dir(&project_root.join(DEFAULT_PROJECT_TARGET_PATH), logger)?; - try_create_dir(&target_path, logger)?; - let mut has_transform_error = false; - for entry in WalkDir::new(&docs_path).into_iter().filter_map(|e| e.ok()) { - let path = entry.path(); - if !path.is_file() { - continue; - } - logger.set_path(path.to_path_buf()); - let file_name = path.file_name().unwrap().to_str().unwrap(); - logger.debug(&ProjectDebug::BuildingFile { - path: path.to_path_buf(), - file_name: file_name.to_string(), - }); - if !file_name.ends_with(".[]") { - continue; - } - let file = match read_to_string(path) { - Ok(file) => file, - Err(source) => { - let err = ProjectError::FailedToReadFile { - path: path.to_path_buf(), - source, - }; - logger.error(&err); - return Err(err); - } - }; - let tokens = brack_tokenizer::tokenize::tokenize(&file); - let cst = brack_parser::parse::parse(&tokens); - let (ast, errors) = brack_transformer::transform::transform(&cst); - if !errors.is_empty() { - for error in errors { - logger.error(&error.into()); - } - has_transform_error = true; - } - let east = match brack_expander::expand::expander(&ast, &mut plugins) { - Ok(east) => east, - Err(_) => { - let err = ProjectError::ExpandError; - logger.error(&err); - return Err(err); - } - }; - let result = match brack_codegen::generate::generate(&east, &mut plugins) { - Ok(result) => result, - Err(_) => { - let err = ProjectError::CodegenError; - logger.error(&err); - return Err(err); - } - }; - let out_dir = - target_path.join(path.strip_prefix(&docs_path).unwrap().parent().unwrap()); - let out_path = target_path.join( - path.strip_prefix(&docs_path) - .unwrap() - .with_extension(target), - ); - try_create_dir(&out_dir, logger)?; - try_write(&out_path, &result, logger)?; - } - if has_transform_error { - return Err(ProjectError::TransformError); - } - logger.info(&ProjectInfo::FinishedBuildingProject { name: name.clone() }); - Ok(()) - } - - /// Corresponds to `brack clean` command - pub fn clean(&self, logger: &L, dry_run: bool) -> Result<(), ProjectError> { - logger.info(&ProjectInfo::CleaningProject); - if self.project_root.is_none() { - let err = ProjectError::UninitializedProject; - logger.error(&err); - return Err(err); - } - let project_root = self.project_root.as_ref().unwrap(); - let target_path = project_root.join(DEFAULT_PROJECT_TARGET_PATH); - let mut num_files = 0; - let mut file_size = 0; - for entry in WalkDir::new(&target_path) - .into_iter() - .filter_map(|e| e.ok()) - { - let path = entry.path(); - if !path.is_file() { - continue; - } - num_files += 1; - match path.metadata() { - Ok(meta) => { - file_size += meta.len(); - } - Err(source) => { - let err = ProjectError::FailedToReadFileSize { - path: path.to_path_buf(), - source, - }; - logger.error(&err); - return Err(err); - } - } - logger.debug(&ProjectDebug::RemoveFile { - path: path.to_path_buf(), - }); - if !dry_run { - remove_file(path).map_err(|source| { - let err = ProjectError::FailedToRemoveFile { - path: path.to_path_buf(), - source, - }; - logger.error(&err); - err - })?; - } - } - logger.debug(&ProjectDebug::RemoveDir { - path: target_path.clone(), - }); - if !dry_run && target_path.exists() { - remove_dir_all(&target_path).map_err(|source| { - let err = ProjectError::FailedToRemoveDir { - path: target_path, - source, - }; - logger.error(&err); - err - })?; - } - logger.info(&ProjectInfo::FinishedCleaningProject { - num_files, - file_size, - }); - if dry_run { - logger.warn(&ProjectWarning::NoFilesRemovedDueToDryRun); - } - Ok(()) - } - - pub async fn add_channel( - &mut self, - logger: &L, - name: &str, - url: &str, - ) -> Result<(), ProjectError> { - logger.info(&ProjectInfo::AddingChannel { - name: name.to_string(), - }); - if self.project_root.is_none() { - let err = ProjectError::UninitializedProject; - logger.error(&err); - return Err(err); - } - let mut channels = self.manifest.channels.clone().unwrap_or_default(); - if channels.contains_key(name) { - let err = ProjectError::ChannelAlreadyExists { - name: name.to_string(), - }; - logger.error(&err); - return Err(err); - } - if name.is_empty() { - let err = ProjectError::ChannelNameCannotBeEmpty; - logger.error(&err); - return Err(err); - } - if !name.chars().all(|c| c.is_alphanumeric() || c == '_') { - let err = ProjectError::InvalidChannelName { - name: name.to_string(), - }; - logger.error(&err); - return Err(err); - } - if url.is_empty() { - let err = ProjectError::ChannelUrlCannotBeEmpty; - logger.error(&err); - return Err(err); - } - if url.starts_with("http") { - let _channel = fetch_channel(logger, url).await?; - } else { - let err = ProjectError::InvalidChannelUrl { - url: url.to_string(), - }; - logger.error(&err); - return Err(err); - } - channels.insert(name.to_string(), url.to_string()); - self.manifest.channels = Some(channels); - self.write_manifest(logger)?; - logger.info(&ProjectInfo::FinishedAddingChannel { - name: name.to_string(), - }); - Ok(()) - } - - pub fn remove_channel( - &mut self, - logger: &L, - name: &str, - ) -> Result<(), ProjectError> { - logger.info(&ProjectInfo::RemovingChannel { - name: name.to_string(), - }); - if self.project_root.is_none() { - let err = ProjectError::UninitializedProject; - logger.error(&err); - return Err(err); - } - let mut channels = self.manifest.channels.clone().unwrap_or_default(); - if name.is_empty() { - let err = ProjectError::ChannelNameCannotBeEmpty; - logger.error(&err); - return Err(err); - } - if !name.chars().all(|c| c.is_alphanumeric() || c == '_') { - let err = ProjectError::InvalidChannelName { - name: name.to_string(), - }; - logger.error(&err); - return Err(err); - } - if !channels.contains_key(name) { - let err = ProjectError::ChannelNotFound { - name: name.to_string(), - }; - logger.error(&err); - return Err(err); - } - channels.remove(name); - self.manifest.channels = Some(channels); - self.write_manifest(logger)?; - logger.info(&ProjectInfo::FinishedRemovingChannel { - name: name.to_string(), - }); - Ok(()) - } - - pub async fn update_channel( - &self, - logger: &L, - name: Option, - ) -> Result<(), ProjectError> { - logger.info(&ProjectInfo::UpdatingChannel { name: name.clone() }); - if self.project_root.is_none() { - let err = ProjectError::UninitializedProject; - logger.error(&err); - return Err(err); - } - let channels = self.manifest.channels.clone().unwrap_or_default(); - match name { - Some(ref name) => { - if !channels.contains_key(name) { - let err = ProjectError::ChannelNotFound { name: name.clone() }; - logger.error(&err); - return Err(err); - } - let url = channels.get(name).unwrap(); - let _channel = fetch_channel(logger, url).await?; - } - _ => { - for (_name, url) in channels { - let _channel = fetch_channel(logger, &url).await?; - } - } - } - logger.info(&ProjectInfo::FinishedUpdatingChannel { name: name.clone() }); - Ok(()) - } - - pub async fn list_channels(&self, logger: &L) -> Result<(), ProjectError> { - logger.info(&ProjectInfo::ListingChannels); - if self.project_root.is_none() { - let err = ProjectError::UninitializedProject; - logger.error(&err); - return Err(err); - } - let channels = self.manifest.channels.clone().unwrap_or_default(); - if channels.is_empty() { - logger.warn(&ProjectWarning::NoChannelsFound); - } else { - for (name, url) in channels { - let health = channel_health_check(&url).await; - logger.info(&ProjectInfo::ChannelInfo { name, url, health }); - } - } - logger.info(&ProjectInfo::FinishedListingChannels); - Ok(()) - } -} - -async fn channel_health_check(url: &str) -> bool { - let response = match reqwest::get(url).await { - Ok(response) => response, - Err(_) => return false, - }; - if !response.status().is_success() { - return false; - } - let downloaded_data = match response.bytes().await { - Ok(data) => data, - Err(_) => return false, - }; - let downloaded_data_str = match std::str::from_utf8(&downloaded_data) { - Ok(data) => data, - Err(_) => return false, - }; - let channel: Channel = match toml::from_str(downloaded_data_str) { - Ok(channel) => channel, - Err(_) => return false, - }; - if channel.is_empty() { - return false; - } - true -} - -async fn fetch_channel(logger: &L, url: &str) -> Result { - let response = reqwest::get(url).await.map_err(|source| { - let err = ProjectError::FailedToFetchResource { - url: url.to_string(), - source, - }; - logger.error(&err); - err - })?; - if !response.status().is_success() { - let status = response.status(); - let reason = status.canonical_reason().unwrap_or("Unknown reqwest error"); - let err = ProjectError::ReceivedHttpNonSuccessStatus { - url: url.to_string(), - status, - err_msg: reason.to_string(), - }; - logger.error(&err); - return Err(err); - } - let downloaded_data = response.bytes().await.map_err(|source| { - let err = ProjectError::FailedToFetchResource { - url: url.to_string(), - source, - }; - logger.error(&err); - err - })?; - let downloaded_data_str = std::str::from_utf8(&downloaded_data).map_err(|source| { - let err = ProjectError::FailedToConvertBytesToStr { source }; - logger.error(&err); - err - })?; - let channel: Channel = toml::from_str(downloaded_data_str).map_err(|source| { - let err = ProjectError::FailedToParseChannel { source }; - logger.error(&err); - err - })?; - Ok(channel) -} diff --git a/crates/brack-project/src/projects.rs b/crates/brack-project/src/projects.rs new file mode 100644 index 0000000..57c05b6 --- /dev/null +++ b/crates/brack-project/src/projects.rs @@ -0,0 +1,364 @@ +use crate::channels::{check_valid_channel_name, ChannelProvider, Channels}; +use crate::manifest::{Author, DocumentSettings, Manifest}; +use crate::utils::{ + create_dir, ensure_directory_not_exists, ensure_manifest_exists, ensure_project_exists, + ensure_project_not_exists, get_file_name, get_file_size, get_manifest, get_project_root, + remove_dir, remove_file, write, write_manifest, +}; +use anyhow::Result; +use brack_common::errors::{Error, ProjectError, ProjectInfo, ProjectResultExt, ProjectWarning}; +use brack_common::logger::Logger; +use brack_plugin::feature_flag::FeatureFlag; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; + +pub struct Project { + pub manifest: Manifest, + pub project_root: Option, + pub channels: Option, + // todo: delete this field + pub plugins_metadata: HashMap, +} + +const DEFAULT_PROJECT_SRC_PATH: &str = "docs"; +const DEFAULT_PROJECT_TARGET_PATH: &str = "target"; + +impl Default for Project { + fn default() -> Self { + Self::new() + } +} + +impl Project { + pub fn new() -> Self { + Self { + manifest: Manifest::default(), + project_root: None, + plugins_metadata: HashMap::default(), + channels: None, + } + } + + /// If the Brack.toml file exists, load it instead of `new` + pub fn new_with_manifest, L: Logger>( + logger: &L, + project_root: &P, + ) -> Result { + ensure_manifest_exists(project_root).map_project_err(logger)?; + Ok(Self { + manifest: get_manifest(project_root, logger).map_project_err(logger)?, + project_root: Some(project_root.as_ref().to_path_buf()), + plugins_metadata: HashMap::default(), + // fix: manifest.channels to Channels + channels: None, + }) + } + + /// Corresponds to `brack create` command + pub fn create_document, L: Logger>( + &mut self, + logger: &L, + path: P, + ) -> Result<(), Error> { + let path = path.as_ref(); + let name = get_file_name(&path).map_project_err(logger)?; + + logger.info(&ProjectInfo::CreatingProject { name: name.clone() }.into()); + + ensure_project_not_exists(self).map_project_err(logger)?; + ensure_directory_not_exists(&path).map_project_err(logger)?; + + create_dir(&path.join(DEFAULT_PROJECT_SRC_PATH), logger).map_project_err(logger)?; + write( + &path.join(DEFAULT_PROJECT_SRC_PATH).join("main.[]"), + "Hello, Brack!", + logger, + ) + .map_project_err(logger)?; + + self.project_root = Some(path.to_path_buf()); + self.manifest.name = name.clone(); + self.manifest.version = String::from("0.1.0"); + self.manifest.authors = Some(vec![Author { + name: String::from("your name"), + email: String::from("you@example.com"), + }]); + self.manifest.dependencies = Some(HashMap::default()); + self.manifest.document = Some(DocumentSettings { + target: String::from("html"), + output_level: None, + output_format: None, + src: String::from(DEFAULT_PROJECT_SRC_PATH), + }); + write_manifest(self, logger).map_project_err(logger)?; + + logger.info(&ProjectInfo::FinishedCreatingProject { name }.into()); + Ok(()) + } + + pub async fn build(&self, _logger: &mut L) -> Result<(), ProjectError> { + // let channels = download_channels(&self.manifest).await?; + // let _plugins = download_plugins(&self.manifest, &channels).await?; + // let mut plugins = match brack_plugin::plugins::Plugins::new(vec![]) { + // Ok(plugins) => plugins, + // Err(_) => { + // let err = ProjectError::FailedToCreatePlugin; + // logger.error(&err); + // return Err(err); + // } + // }; + // let name = self.manifest.name.clone(); + // logger.info(&ProjectInfo::BuildingProject { name: name.clone() }); + // if self.project_root.is_none() { + // let err = ProjectError::UninitializedProject; + // logger.error(&err); + // return Err(err); + // } + // let project_root = self.project_root.as_ref().unwrap(); + // let docs_path = project_root.join(DEFAULT_PROJECT_SRC_PATH); + // let target = match &self.manifest.document { + // Some(document) => &document.target, + // _ => { + // let err = ProjectError::DocumentSettingsNotFound; + // logger.error(&err); + // return Err(err); + // } + // }; + // let target_path = project_root.join(DEFAULT_PROJECT_TARGET_PATH).join(target); + // try_create_dir(&project_root.join(DEFAULT_PROJECT_TARGET_PATH), logger)?; + // try_create_dir(&target_path, logger)?; + // let mut has_transform_error = false; + // for entry in WalkDir::new(&docs_path).into_iter().filter_map(|e| e.ok()) { + // let path = entry.path(); + // if !path.is_file() { + // continue; + // } + // logger.set_path(path.to_path_buf()); + // let file_name = path.file_name().unwrap().to_str().unwrap(); + // logger.debug(&ProjectDebug::BuildingFile { + // path: path.to_path_buf(), + // file_name: file_name.to_string(), + // }); + // if !file_name.ends_with(".[]") { + // continue; + // } + // let file = match read_to_string(path) { + // Ok(file) => file, + // Err(source) => { + // let err = ProjectError::FailedToReadFile { + // path: path.to_path_buf(), + // source, + // }; + // logger.error(&err); + // return Err(err); + // } + // }; + // let tokens = brack_tokenizer::tokenize::tokenize(&file); + // let cst = brack_parser::parse::parse(&tokens); + // let (ast, errors) = brack_transformer::transform::transform(&cst); + // if !errors.is_empty() { + // for error in errors { + // logger.error(&error.into()); + // } + // has_transform_error = true; + // } + // let east = match brack_expander::expand::expander(&ast, &mut plugins) { + // Ok(east) => east, + // Err(_) => { + // let err = ProjectError::ExpandError; + // logger.error(&err); + // return Err(err); + // } + // }; + // let result = match brack_codegen::generate::generate(&east, &mut plugins) { + // Ok(result) => result, + // Err(_) => { + // let err = ProjectError::CodegenError; + // logger.error(&err); + // return Err(err); + // } + // }; + // let out_dir = + // target_path.join(path.strip_prefix(&docs_path).unwrap().parent().unwrap()); + // let out_path = target_path.join( + // path.strip_prefix(&docs_path) + // .unwrap() + // .with_extension(target), + // ); + // try_create_dir(&out_dir, logger)?; + // try_write(&out_path, &result, logger)?; + // } + // if has_transform_error { + // return Err(ProjectError::TransformError); + // } + // logger.info(&ProjectInfo::FinishedBuildingProject { name: name.clone() }); + Ok(()) + } + + /// Corresponds to `brack clean` command + pub fn clean(&self, logger: &L, dry_run: bool) -> Result<(), Error> { + ensure_project_exists(self).map_project_err(logger)?; + logger.info( + &ProjectInfo::CleaningProject { + name: self.manifest.name.clone(), + } + .into(), + ); + let project_root = self.project_root.as_ref().unwrap(); + let target_path = project_root.join(DEFAULT_PROJECT_TARGET_PATH); + let mut num_files = 0; + let mut file_size = 0; + let files = WalkDir::new(&target_path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()); + for file in files { + let path = file.path(); + num_files += 1; + file_size += get_file_size(&path).map_project_err(logger)?; + if !dry_run { + remove_file(&path, logger).map_project_err(logger)?; + } + } + if !dry_run && target_path.exists() { + remove_dir(&target_path, logger).map_project_err(logger)?; + } + logger.info( + &ProjectInfo::FinishedCleaningProject { + name: self.manifest.name.clone(), + num_files, + file_size, + } + .into(), + ); + if dry_run { + logger.warn(&ProjectWarning::NoFilesRemovedDueToDryRun.into()); + } + Ok(()) + } + + pub async fn add_channel( + &mut self, + logger: &L, + name: &str, + url: &str, + ) -> Result<(), Error> { + logger.info( + &ProjectInfo::AddingChannel { + name: name.to_string(), + } + .into(), + ); + + ensure_project_exists(self).map_project_err(logger)?; + let mut channels = self.manifest.channels.clone().unwrap_or_default(); + if channels.contains_key(name) { + return Err(ProjectError::ChannelAlreadyExists { + name: name.to_string(), + } + .into()); + } + let channel_provider = ChannelProvider::new(name, url).map_project_err(logger)?; + let channel = channel_provider + .fetch(logger) + .await + .map_project_err(logger)?; + let project_root = get_project_root(self)?; + channel_provider.write(&project_root, &channel, logger)?; + channels.insert(name.to_string(), url.to_string()); + self.manifest.channels = Some(channels); + write_manifest(self, logger).map_project_err(logger)?; + + logger.info( + &ProjectInfo::FinishedAddingChannel { + name: name.to_string(), + } + .into(), + ); + Ok(()) + } + + pub fn remove_channel(&mut self, logger: &L, name: &str) -> Result<(), Error> { + logger.info( + &ProjectInfo::RemovingChannel { + name: name.to_string(), + } + .into(), + ); + + ensure_project_exists(self).map_project_err(logger)?; + let mut channels = self.manifest.channels.clone().unwrap_or_default(); + check_valid_channel_name(name).map_project_err(logger)?; + if !channels.contains_key(name) { + return Err(ProjectError::ChannelNotFound { + name: name.to_string(), + } + .into()); + } + channels.remove(name); + self.manifest.channels = Some(channels); + write_manifest(self, logger).map_project_err(logger)?; + + logger.info( + &ProjectInfo::FinishedRemovingChannel { + name: name.to_string(), + } + .into(), + ); + Ok(()) + } + + pub async fn update_channel( + &self, + logger: &L, + name: Option, + ) -> Result<(), Error> { + logger.info(&ProjectInfo::UpdatingChannel { name: name.clone() }.into()); + + ensure_project_exists(self).map_project_err(logger)?; + let channels = self.manifest.channels.clone().unwrap_or_default(); + let channels = match name { + Some(ref name) => { + if !channels.contains_key(name) { + return Err(ProjectError::ChannelNotFound { name: name.clone() }.into()); + } + let url = channels.get(name).unwrap(); + HashMap::from([(name.clone(), url.clone())]) + } + _ => channels.clone(), + }; + for (name, url) in channels { + let channel_provider = ChannelProvider::new(&name, &url).map_project_err(logger)?; + let channel = channel_provider + .fetch(logger) + .await + .map_project_err(logger)?; + let project_root = get_project_root(self)?; + channel_provider.write(&project_root, &channel, logger)?; + } + + logger.info(&ProjectInfo::FinishedUpdatingChannel { name: name.clone() }.into()); + Ok(()) + } + + pub async fn list_channels(&self, logger: &L) -> Result<(), Error> { + logger.info(&ProjectInfo::ListingChannels.into()); + + ensure_project_exists(self).map_project_err(logger)?; + let channels = self.manifest.channels.clone().unwrap_or_default(); + if channels.is_empty() { + logger.warn(&ProjectWarning::NoChannelsFound.into()); + } else { + for (name, url) in channels { + let channels_provider = + ChannelProvider::new(&name, &url).map_project_err(logger)?; + let health = channels_provider.fetch(logger).await.is_ok(); + logger.info(&ProjectInfo::ChannelInfo { name, url, health }.into()); + } + } + + logger.info(&ProjectInfo::FinishedListingChannels.into()); + Ok(()) + } +} diff --git a/crates/brack-project/src/utils.rs b/crates/brack-project/src/utils.rs new file mode 100644 index 0000000..8204a94 --- /dev/null +++ b/crates/brack-project/src/utils.rs @@ -0,0 +1,215 @@ +use std::path::{Path, PathBuf}; + +use brack_common::{ + errors::{Debug, ProjectDebug, ProjectError}, + logger::Logger, +}; + +use crate::{manifest::Manifest, projects::Project}; + +const BRACK_TOML: &str = "Brack.toml"; +pub const DEFAULT_PROJECT_TARGET_PATH: &str = "target"; + +pub fn get_file_name>(path: &P) -> Result { + let path = path.as_ref(); + let os_str = path + .file_name() + .ok_or_else(|| ProjectError::FailedToGetFileNameFromPath { + path: path.to_path_buf(), + })?; + let result = os_str + .to_str() + .ok_or_else(|| ProjectError::FailedToConvertOsStrToStr { + os_str: os_str.to_os_string(), + })? + .to_string(); + Ok(result) +} + +pub fn ensure_project_not_exists(project: &Project) -> Result<(), ProjectError> { + if let Some(project_root) = &project.project_root { + return Err(ProjectError::ProjectAlreadyExists { + path: project_root.clone(), + }); + } + Ok(()) +} + +pub fn ensure_project_exists(project: &Project) -> Result<(), ProjectError> { + if project.project_root.is_none() { + return Err(ProjectError::ProjectNotInitialized); + } + Ok(()) +} + +pub fn get_project_root(project: &Project) -> Result { + if let Some(project_root) = &project.project_root { + return Ok(project_root.clone()); + } + Err(ProjectError::ProjectNotInitialized) +} + +pub fn ensure_directory_not_exists>(path: &P) -> Result<(), ProjectError> { + let path = path.as_ref(); + if path.exists() { + return Err(ProjectError::DirectoryAlreadyExists { + path: path.to_path_buf(), + }); + } + Ok(()) +} + +pub fn create_dir, L: Logger>(path: &P, logger: &L) -> Result<(), ProjectError> { + let path = path.as_ref().to_path_buf(); + if path.exists() { + logger.debug(&Debug::ProjectDebug(ProjectDebug::DirectoryAlreadyExists { + path: path.to_path_buf(), + })); + return Ok(()); + } + match std::fs::create_dir_all(&path) { + Ok(_) => { + logger.debug(&Debug::ProjectDebug(ProjectDebug::CreatingDirectory { + path: path.to_path_buf(), + })); + Ok(()) + } + Err(source) => Err(ProjectError::FailedToCreateDirectory { + path: path.to_path_buf(), + source, + }), + } +} + +pub fn write, L: Logger>( + path: &P, + content: &str, + logger: &L, +) -> Result<(), ProjectError> { + let path = path.as_ref().to_path_buf(); + match std::fs::write(&path, content) { + Ok(_) => { + logger.debug(&Debug::ProjectDebug(ProjectDebug::WritingFile { + path, + content: content.to_string(), + })); + Ok(()) + } + Err(source) => Err(ProjectError::FailedToWriteFile { path, source }), + } +} + +pub fn toml_to_string(value: &T, logger: &L) -> Result +where + T: serde::Serialize + ?Sized, + L: Logger, +{ + match toml::to_string(value) { + Ok(toml_string) => { + logger.debug( + &ProjectDebug::WritingToml { + content: toml_string.clone(), + } + .into(), + ); + Ok(toml_string) + } + Err(source) => Err(ProjectError::FailedToSerializeManifest { source }), + } +} + +pub fn write_manifest(project: &Project, logger: &L) -> Result<(), ProjectError> { + let project_root = get_project_root(project)?; + let path = project_root.join(BRACK_TOML); + let content = toml_to_string(&project.manifest, logger)?; + write(&path, &content, logger)?; + Ok(()) +} + +pub fn ensure_manifest_exists>(project_root: &P) -> Result<(), ProjectError> { + let path = project_root.as_ref().join(BRACK_TOML); + if !path.exists() { + return Err(ProjectError::ManifestNotFound { path }); + } + Ok(()) +} + +pub fn read_to_string, L: Logger>( + path: &P, + logger: &L, +) -> Result { + let path = path.as_ref().to_path_buf(); + match std::fs::read_to_string(&path) { + Ok(content) => { + logger.debug( + &ProjectDebug::ReadingFile { + path, + content: content.clone(), + } + .into(), + ); + Ok(content) + } + Err(source) => Err(ProjectError::FailedToReadFile { path, source }), + } +} + +pub fn toml_from_str(content: &str, logger: &L) -> Result +where + T: serde::de::DeserializeOwned, +{ + match toml::from_str(content) { + Ok(value) => { + logger.debug( + &ProjectDebug::ReadingToml { + content: content.to_string(), + } + .into(), + ); + Ok(value) + } + Err(source) => Err(ProjectError::FailedToDeserializeManifest { source }), + } +} + +pub fn get_manifest, L: Logger>( + project_root: &P, + logger: &L, +) -> Result { + let manifest_file_path = project_root.as_ref().join(BRACK_TOML); + let manifest_file = read_to_string(&manifest_file_path, logger)?; + toml_from_str::(&manifest_file, logger) +} + +pub fn get_file_size>(path: &P) -> Result { + let path = path.as_ref(); + match std::fs::metadata(&path) { + Ok(metadata) => Ok(metadata.len()), + Err(source) => Err(ProjectError::FailedToReadFileSize { + path: path.to_path_buf(), + source, + }), + } +} + +pub fn remove_file, L: Logger>(path: &P, logger: &L) -> Result<(), ProjectError> { + let path = path.as_ref().to_path_buf(); + match std::fs::remove_file(&path) { + Ok(()) => { + logger.debug(&ProjectDebug::RemovingFile { path }.into()); + Ok(()) + } + Err(source) => Err(ProjectError::FailedToRemoveFile { path, source }), + } +} + +pub fn remove_dir, L: Logger>(path: &P, logger: &L) -> Result<(), ProjectError> { + let path = path.as_ref().to_path_buf(); + match std::fs::remove_dir_all(&path) { + Ok(()) => { + logger.debug(&ProjectDebug::RemovingDirectory { path }.into()); + Ok(()) + } + Err(source) => Err(ProjectError::FailedToRemoveDirectory { path, source }), + } +} diff --git a/crates/brack-transformer/src/angle.rs b/crates/brack-transformer/src/angle.rs index 7a904b8..39ea660 100644 --- a/crates/brack-transformer/src/angle.rs +++ b/crates/brack-transformer/src/angle.rs @@ -1,6 +1,6 @@ use brack_common::cst::{InnerNode, CST}; +use brack_common::errors::TransformingError; use brack_common::location::merge_location; -use brack_common::transformer_errors::TransformError; use crate::{ simplify, @@ -11,15 +11,15 @@ use crate::{ }, }; -fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec { +fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec { let mut errors = vec![]; match (csts[0].clone(), csts[csts.len() - 1].clone()) { (CST::AngleBracketOpen(_), CST::AngleBracketClose(_)) => (), (CST::AngleBracketOpen(left), CST::CurlyBracketClose(right)) | (CST::AngleBracketOpen(left), CST::SquareBracketClose(right)) => errors.push( - TransformError::MismatchedBracket(merge_location(&left.location, &right.location)), + TransformingError::MismatchedBracket(merge_location(&left.location, &right.location)), ), - (CST::AngleBracketOpen(left), right) => errors.push(TransformError::AngleNotClosed( + (CST::AngleBracketOpen(left), right) => errors.push(TransformingError::AngleNotClosed( merge_location(&left.location, &right.location()), )), _ => panic!( @@ -29,7 +29,7 @@ fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec (CST, Vec) { +pub fn simplify(cst: &CST) -> (CST, Vec) { let node = match cst { CST::Angle(node) => node, _ => panic!("Cannot pass non-angle-bracket node to angle::simplify"), diff --git a/crates/brack-transformer/src/backslash.rs b/crates/brack-transformer/src/backslash.rs index 790b83d..1ed7b21 100644 --- a/crates/brack-transformer/src/backslash.rs +++ b/crates/brack-transformer/src/backslash.rs @@ -1,7 +1,7 @@ use brack_common::cst::{new_invalid, CST}; -use brack_common::transformer_errors::TransformError; +use brack_common::errors::TransformingError; -pub fn simplify(cst: &CST) -> (CST, Vec) { +pub fn simplify(cst: &CST) -> (CST, Vec) { let node = match cst { CST::BackSlash(node) => node, _ => panic!("Cannot pass non-back-slash node to backslash::simplify"), @@ -9,7 +9,7 @@ pub fn simplify(cst: &CST) -> (CST, Vec) { let mut errors = vec![]; if node.children.is_empty() { - errors.push(TransformError::InvalidBackslash(node.location.clone())); + errors.push(TransformingError::InvalidBackslash(node.location.clone())); return (new_invalid(node.location.clone()), errors); } diff --git a/crates/brack-transformer/src/curly.rs b/crates/brack-transformer/src/curly.rs index d94d93b..e0b5e48 100644 --- a/crates/brack-transformer/src/curly.rs +++ b/crates/brack-transformer/src/curly.rs @@ -1,6 +1,6 @@ use brack_common::cst::{InnerNode, CST}; +use brack_common::errors::TransformingError; use brack_common::location::merge_location; -use brack_common::transformer_errors::TransformError; use crate::{ simplify, @@ -11,15 +11,15 @@ use crate::{ }, }; -fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec { +fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec { let mut errors = vec![]; match (csts[0].clone(), csts[csts.len() - 1].clone()) { (CST::CurlyBracketOpen(_), CST::CurlyBracketClose(_)) => (), (CST::CurlyBracketOpen(left), CST::AngleBracketClose(right)) | (CST::CurlyBracketOpen(left), CST::SquareBracketClose(right)) => errors.push( - TransformError::MismatchedBracket(merge_location(&left.location, &right.location)), + TransformingError::MismatchedBracket(merge_location(&left.location, &right.location)), ), - (CST::CurlyBracketOpen(left), right) => errors.push(TransformError::CurlyNotClosed( + (CST::CurlyBracketOpen(left), right) => errors.push(TransformingError::CurlyNotClosed( merge_location(&left.location, &right.location()), )), _ => panic!( @@ -29,7 +29,7 @@ fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec (CST, Vec) { +pub fn simplify(cst: &CST) -> (CST, Vec) { let node = match cst { CST::Curly(node) => node, _ => panic!("Cannot pass non-curly-bracket node to curly::simplify"), diff --git a/crates/brack-transformer/src/document.rs b/crates/brack-transformer/src/document.rs index e555635..41aee2a 100644 --- a/crates/brack-transformer/src/document.rs +++ b/crates/brack-transformer/src/document.rs @@ -1,9 +1,9 @@ use brack_common::cst::{new_document, CST}; -use brack_common::transformer_errors::TransformError; +use brack_common::errors::TransformingError; use crate::{simplify, utils::remove_elements_not_included_ast}; -pub fn simplify(cst: &CST) -> (CST, Vec) { +pub fn simplify(cst: &CST) -> (CST, Vec) { let node = match cst { CST::Document(node) => node, _ => panic!("Cannot pass non-document node to document::simplify"), diff --git a/crates/brack-transformer/src/expr.rs b/crates/brack-transformer/src/expr.rs index 514c5e5..4cdc9ad 100644 --- a/crates/brack-transformer/src/expr.rs +++ b/crates/brack-transformer/src/expr.rs @@ -1,9 +1,9 @@ use brack_common::cst::{new_expr, CST}; -use brack_common::transformer_errors::TransformError; +use brack_common::errors::TransformingError; use crate::simplify; -pub fn simplify(cst: &CST) -> (CST, Vec) { +pub fn simplify(cst: &CST) -> (CST, Vec) { let node = match cst { CST::Expr(node) => node, _ => panic!("Cannot pass non-expr node to expr::simplify"), diff --git a/crates/brack-transformer/src/simplify.rs b/crates/brack-transformer/src/simplify.rs index d0a8101..a7e1a38 100644 --- a/crates/brack-transformer/src/simplify.rs +++ b/crates/brack-transformer/src/simplify.rs @@ -1,9 +1,9 @@ use brack_common::cst::CST; -use brack_common::transformer_errors::TransformError; +use brack_common::errors::TransformingError; use crate::{angle, backslash, curly, document, expr, square, stmt}; -pub fn simplify(cst: &CST) -> (CST, Vec) { +pub fn simplify(cst: &CST) -> (CST, Vec) { match cst { CST::Document(_) => document::simplify(cst), CST::Stmt(_) => stmt::simplify(cst), diff --git a/crates/brack-transformer/src/square.rs b/crates/brack-transformer/src/square.rs index 0262339..e37f0ae 100644 --- a/crates/brack-transformer/src/square.rs +++ b/crates/brack-transformer/src/square.rs @@ -1,6 +1,6 @@ use brack_common::cst::{InnerNode, CST}; +use brack_common::errors::TransformingError; use brack_common::location::merge_location; -use brack_common::transformer_errors::TransformError; use crate::{ simplify, @@ -11,15 +11,15 @@ use crate::{ }, }; -fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec { +fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec { let mut errors = vec![]; match (csts[0].clone(), csts[csts.len() - 1].clone()) { (CST::SquareBracketOpen(_), CST::SquareBracketClose(_)) => (), (CST::SquareBracketOpen(left), CST::AngleBracketClose(right)) | (CST::SquareBracketOpen(left), CST::CurlyBracketClose(right)) => errors.push( - TransformError::MismatchedBracket(merge_location(&left.location, &right.location)), + TransformingError::MismatchedBracket(merge_location(&left.location, &right.location)), ), - (CST::SquareBracketOpen(left), right) => errors.push(TransformError::SquareNotClosed( + (CST::SquareBracketOpen(left), right) => errors.push(TransformingError::SquareNotClosed( merge_location(&left.location, &right.location()), )), _ => panic!( @@ -29,7 +29,7 @@ fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec (CST, Vec) { +pub fn simplify(cst: &CST) -> (CST, Vec) { let node = match cst { CST::Square(node) => node, _ => panic!("Cannot pass non-square-bracket node to curly::simplify"), diff --git a/crates/brack-transformer/src/stmt.rs b/crates/brack-transformer/src/stmt.rs index dd23396..bb3c9d5 100644 --- a/crates/brack-transformer/src/stmt.rs +++ b/crates/brack-transformer/src/stmt.rs @@ -1,9 +1,9 @@ use brack_common::cst::{new_stmt, CST}; -use brack_common::transformer_errors::TransformError; +use brack_common::errors::TransformingError; use crate::{simplify, utils::remove_elements_not_included_ast}; -pub fn simplify(cst: &CST) -> (CST, Vec) { +pub fn simplify(cst: &CST) -> (CST, Vec) { let node = match cst { CST::Stmt(node) => node, _ => panic!("Cannot pass non-stmt node to stmt::simplify"), diff --git a/crates/brack-transformer/src/transform.rs b/crates/brack-transformer/src/transform.rs index 3364ec7..12ae4d6 100644 --- a/crates/brack-transformer/src/transform.rs +++ b/crates/brack-transformer/src/transform.rs @@ -3,11 +3,11 @@ use brack_common::ast::{ new_stmt, new_text, AST, }; use brack_common::cst::CST; -use brack_common::transformer_errors::TransformError; +use brack_common::errors::TransformingError; use crate::simplify; -pub fn transform(cst: &CST) -> (AST, Vec) { +pub fn transform(cst: &CST) -> (AST, Vec) { let (cst, errors) = simplify::simplify(cst); fn aux(cst: &CST) -> AST { diff --git a/crates/brack-transformer/src/utils.rs b/crates/brack-transformer/src/utils.rs index a17948a..8f2ac5f 100644 --- a/crates/brack-transformer/src/utils.rs +++ b/crates/brack-transformer/src/utils.rs @@ -1,7 +1,7 @@ use brack_common::cst::{new_expr, CST}; -use brack_common::transformer_errors::TransformError; +use brack_common::errors::TransformingError; -pub fn check_if_module_or_angle_bracket(csts: &[CST]) -> Vec { +pub fn check_if_module_or_angle_bracket(csts: &[CST]) -> Vec { if csts.len() < 2 { return vec![]; } @@ -9,22 +9,22 @@ pub fn check_if_module_or_angle_bracket(csts: &[CST]) -> Vec { match cst { CST::Module(_) => vec![], CST::Angle(_) => vec![], - _ => vec![TransformError::ModuleNotFound(cst.location())], + _ => vec![TransformingError::ModuleNotFound(cst.location())], } } -pub fn check_if_dot(csts: &[CST]) -> Vec { +pub fn check_if_dot(csts: &[CST]) -> Vec { if csts.len() < 3 { return vec![]; } let cst = csts[2].clone(); match cst { CST::Dot(_) => vec![], - _ => vec![TransformError::DotNotFound(cst.location())], + _ => vec![TransformingError::DotNotFound(cst.location())], } } -pub fn check_if_ident_or_angle_bracket(csts: &[CST]) -> Vec { +pub fn check_if_ident_or_angle_bracket(csts: &[CST]) -> Vec { if csts.len() < 4 { return vec![]; } @@ -32,7 +32,7 @@ pub fn check_if_ident_or_angle_bracket(csts: &[CST]) -> Vec { match cst { CST::Ident(_) => vec![], CST::Angle(_) => vec![], - _ => vec![TransformError::IdentifierNotFound(cst.location())], + _ => vec![TransformingError::IdentifierNotFound(cst.location())], } } @@ -57,7 +57,7 @@ pub fn remove_elements_not_included_ast(csts: &[CST]) -> Vec { new_csts } -pub fn check_valid_arguments(csts: &[CST]) -> (Vec, Vec) { +pub fn check_valid_arguments(csts: &[CST]) -> (Vec, Vec) { if csts.len() < 4 { return (csts.to_vec(), vec![]); } @@ -69,7 +69,7 @@ pub fn check_valid_arguments(csts: &[CST]) -> (Vec, Vec) { match csts[i].clone() { CST::Comma(_) => { if expr.children().is_empty() { - errors.push(TransformError::UnexpectedComma(csts[i].location())); + errors.push(TransformingError::UnexpectedComma(csts[i].location())); continue; } new_csts.push(expr); @@ -80,14 +80,14 @@ pub fn check_valid_arguments(csts: &[CST]) -> (Vec, Vec) { if !expr.children().is_empty() { new_csts.push(expr.clone()); } else if previous_comma { - errors.push(TransformError::UnexpectedComma(csts[i - 1].location())); + errors.push(TransformingError::UnexpectedComma(csts[i - 1].location())); } expr = new_expr(); new_csts.push(csts[i].clone()); break; } CST::Dot(_) => { - errors.push(TransformError::UnexpectedDot(csts[i].location())); + errors.push(TransformingError::UnexpectedDot(csts[i].location())); continue; } _ => { @@ -102,11 +102,11 @@ pub fn check_valid_arguments(csts: &[CST]) -> (Vec, Vec) { (new_csts, errors) } -pub fn check_unexpected_dot(csts: &[CST]) -> Vec { +pub fn check_unexpected_dot(csts: &[CST]) -> Vec { let mut errors = vec![]; for cst in csts.iter().skip(3) { if let CST::Dot(_) = cst { - errors.push(TransformError::UnexpectedDot(cst.location())); + errors.push(TransformingError::UnexpectedDot(cst.location())); } } errors diff --git a/crates/brack-wasm-plugin/Cargo.toml b/crates/brack-wasm-plugin/Cargo.toml new file mode 100644 index 0000000..0a0bd07 --- /dev/null +++ b/crates/brack-wasm-plugin/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "brack-wasm-plugin" +version = "0.1.0" +edition = "2021" + +[dependencies] +brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } +extism = "1.10.0" +serde = "1.0.219" +serde_json = "1.0.140" diff --git a/crates/brack-wasm-plugin/src/lib.rs b/crates/brack-wasm-plugin/src/lib.rs new file mode 100644 index 0000000..4f564de --- /dev/null +++ b/crates/brack-wasm-plugin/src/lib.rs @@ -0,0 +1 @@ +pub mod plugin_v1; diff --git a/crates/brack-wasm-plugin/src/plugin_v1.rs b/crates/brack-wasm-plugin/src/plugin_v1.rs new file mode 100644 index 0000000..8410944 --- /dev/null +++ b/crates/brack-wasm-plugin/src/plugin_v1.rs @@ -0,0 +1,136 @@ +use core::fmt; +use std::{collections::HashMap, fs, path::Path}; + +use brack_common::{ + errors::PluginError, + plugins::{CommandType, Plugin, Signature, Type, Value, ValueVec}, +}; +use extism::convert::Json; +use serde::{Deserialize, Serialize}; + +pub struct WasmPluginV1 Deserialize<'de> + fmt::Display + Clone> { + name: String, + extism_plugin: extism::Plugin, + signature_table: HashMap<(String, CommandType), Vec>, + _marker: std::marker::PhantomData, +} + +impl Deserialize<'de> + fmt::Display + Clone> Plugin for WasmPluginV1 { + type IR = T; + + fn call(&mut self, signature: &Signature, args: &ValueVec) -> Result, PluginError> { + let signatures = self.get_signature(&signature.command_name, &signature.command_type)?; + let signature = self.match_signature(&signatures, &signature.command_name, args)?; + let result = self + .extism_plugin + .call::>, Json>>(&signature.callee, Json(args.clone())) + .map_err(|_| PluginError::PluginCallError { + name: self.name.clone(), + signature: signature.clone(), + args: args.to_string(), + })?; + let Json(result) = result; + Ok(result) + } +} + +impl Deserialize<'de> + fmt::Display + Clone> WasmPluginV1 { + pub fn new>(name: &str, path: &P) -> Result { + let file = match fs::read(path) { + Ok(file) => file, + Err(source) => { + return Err(PluginError::PluginReadError { + name: name.to_string(), + source: source.to_string(), + }); + } + }; + let mut extism_plugin = match extism::Plugin::new(file, [], true) { + Ok(plugin) => plugin, + Err(source) => { + return Err(PluginError::PluginCreateError { + name: name.to_string(), + source: source.to_string(), + }); + } + }; + let Json(signatures_from_plugin) = extism_plugin + .call::<(), Json>>("get_signatures", ()) + .map_err(|_| PluginError::PluginCallError { + name: name.to_string(), + signature: Signature { + command_name: "get_signatures".to_string(), + args: vec![], + return_type: Type::Invalid, // `get_signatures` is special and doesn't have a return type + command_type: CommandType::InlineCommand, // dummy + callee: "get_signatures".to_string(), + }, + args: "()".to_string(), + })?; + let signature_table = + signatures_from_plugin + .iter() + .fold(HashMap::new(), |mut acc, signature| { + acc.entry(( + signature.command_name.clone(), + signature.command_type.clone(), + )) + .or_insert_with(Vec::new) + .push(signature.clone()); + acc + }); + Ok(Self { + name: name.to_string(), + extism_plugin, + signature_table, + _marker: std::marker::PhantomData, + }) + } + + fn get_signature( + &self, + command_name: &str, + command_type: &CommandType, + ) -> Result, PluginError> { + let signatures = self + .signature_table + .get(&(command_name.to_string(), command_type.clone())) + .ok_or_else(|| PluginError::SignatureNotFound { + name: self.name.clone(), + command_name: command_name.to_string(), + command_type: command_type.clone(), + })?; + if signatures.is_empty() { + return Err(PluginError::SignatureNotFound { + name: self.name.clone(), + command_name: command_name.to_string(), + command_type: command_type.clone(), + }); + } + Ok(signatures.clone()) + } + + fn match_signature( + &self, + signatures: &[Signature], + command_name: &str, + args: &ValueVec, + ) -> Result { + for signature in signatures { + let signature_args_type = signature + .args + .iter() + .map(|(_, typ)| typ.clone()) + .collect::>(); + let args_type = args.0.iter().map(|arg| arg.to_type()).collect::>(); + if signature_args_type == args_type { + return Ok(signature.clone()); + } + } + Err(PluginError::SignatureNotMatched { + name: self.name.clone(), + command_name: command_name.to_string(), + args: args.0.iter().map(|arg| arg.to_string()).collect(), + }) + } +} diff --git a/flake.nix b/flake.nix index 0333bfa..bc1089d 100644 --- a/flake.nix +++ b/flake.nix @@ -43,6 +43,8 @@ extensions = [ "rust-src" ]; }) rust-analyzer + openssl.dev + pkg-config ]; }; diff --git a/src/cli.rs b/src/cli.rs index 7417899..7a2b707 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,10 @@ use crate::logger::CliLogLevel; use crate::logger::Logger; use anstyle::{AnsiColor, Color, Style}; -use brack_project::project::Project; +use brack_common::ir::Html; +use brack_common::plugins::Plugin as PluginTrait; +use brack_internal_plugin::html::InternalHtmlPlugin; +use brack_project::projects::Project; use clap::{builder, ArgGroup, Parser, Subcommand}; use std::path::Path; use std::process::exit; @@ -126,7 +129,11 @@ impl Cli { cli_log_level: self.log_level.clone(), path: None, }; - let project = Project::new_with_manifest(&logger, Path::new(".")) + + let mut plugins: Vec> = vec![]; + plugins.push(Box::new(InternalHtmlPlugin::new())); + + let project = Project::new_with_manifest(&logger, &Path::new(".")) .map_err(|_| { exit(1); }) @@ -146,7 +153,7 @@ impl Cli { cli_log_level: self.log_level.clone(), path: None, }; - let project = Project::new_with_manifest(&logger, Path::new(".")) + let project = Project::new_with_manifest(&logger, &Path::new(".")) .map_err(|_| { exit(1); }) @@ -191,7 +198,7 @@ impl Cli { cli_log_level: self.log_level.clone(), path: None, }; - let mut project = Project::new_with_manifest(&logger, Path::new(".")) + let mut project = Project::new_with_manifest(&logger, &Path::new(".")) .map_err(|_| { exit(1); }) @@ -211,7 +218,7 @@ impl Cli { cli_log_level: self.log_level.clone(), path: None, }; - let mut project = Project::new_with_manifest(&logger, Path::new(".")) + let mut project = Project::new_with_manifest(&logger, &Path::new(".")) .map_err(|_| { exit(1); }) @@ -230,7 +237,7 @@ impl Cli { cli_log_level: self.log_level.clone(), path: None, }; - let project = Project::new_with_manifest(&logger, Path::new(".")) + let project = Project::new_with_manifest(&logger, &Path::new(".")) .map_err(|_| { exit(1); }) @@ -250,7 +257,7 @@ impl Cli { cli_log_level: self.log_level.clone(), path: None, }; - let project = Project::new_with_manifest(&logger, Path::new(".")) + let project = Project::new_with_manifest(&logger, &Path::new(".")) .map_err(|_| { exit(1); }) diff --git a/src/logger.rs b/src/logger.rs index ec50792..b6cc3f7 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,22 +1,42 @@ -use brack_common::{ - logger::Logger as BrackLogger, - project_errors::{ProjectDebug, ProjectError, ProjectInfo, ProjectWarning}, -}; +use std::path::PathBuf; + +use brack_common::errors::{Debug, Error, Info, Warning}; + +impl brack_common::logger::Logger for Logger { + fn error(&self, _error: &Error) {} + + fn warn(&self, _warning: &Warning) {} + + fn info(&self, _info: &Info) {} + + fn debug(&self, _debug: &Debug) {} + + fn set_path(&mut self, _path: PathBuf) {} + + fn get_path(&self) -> PathBuf { + PathBuf::new() + } +} + +// use brack_common::{ +// errors::{Debug, Error, Info, Warning}, +// logger::Logger as BrackLogger, +// }; use clap::ValueEnum; -use codespan_reporting::{ - diagnostic::{Diagnostic, Label, Severity}, - files::SimpleFile, - term::{ - emit, - termcolor::{ColorChoice, StandardStream}, - Config, - }, -}; -use colored::Colorize; -use file_size::fit_4; -use std::fs::read_to_string; -use std::path::{Path, PathBuf}; -use std::process::exit; +// use codespan_reporting::{ +// diagnostic::{Diagnostic, Label, Severity}, +// files::SimpleFile, +// term::{ +// emit, +// termcolor::{ColorChoice, StandardStream}, +// Config, +// }, +// }; +// use colored::Colorize; +// use file_size::fit_4; +// use std::fs::read_to_string; +// use std::path::{Path, PathBuf}; +// use std::process::exit; #[derive(Clone, ValueEnum, PartialOrd, PartialEq, Eq)] pub enum CliLogLevel { @@ -32,1073 +52,1073 @@ pub struct Logger { pub path: Option, } -impl BrackLogger for Logger { - fn error(&self, error: &ProjectError) { - emit_error_diagnostic(self, error); - } +// impl BrackLogger for Logger { +// fn error(&self, error: &Error) { +// emit_error_diagnostic(self, error); +// } - fn warn(&self, warning: &ProjectWarning) { - if self.cli_log_level > CliLogLevel::Warn { - return; - } - emit_warning_diagnostic(self, warning); - } +// fn warn(&self, warning: &Warning) { +// if self.cli_log_level > CliLogLevel::Warn { +// return; +// } +// emit_warning_diagnostic(self, warning); +// } - fn info(&self, info: &ProjectInfo) { - if self.cli_log_level > CliLogLevel::Info { - return; - } - emit_info_output(info); - } +// fn info(&self, info: &Info) { +// if self.cli_log_level > CliLogLevel::Info { +// return; +// } +// emit_info_output(info); +// } - fn debug(&self, debug: &ProjectDebug) { - if self.cli_log_level > CliLogLevel::Debug { - return; - } - emit_debug_output(debug); - } +// fn debug(&self, debug: &Debug) { +// if self.cli_log_level > CliLogLevel::Debug { +// return; +// } +// emit_debug_output(debug); +// } - /// Set the file path which is processing currently - fn set_path(&mut self, path: PathBuf) { - self.path = Some(path); - } +// /// Set the file path which is processing currently +// fn set_path(&mut self, path: PathBuf) { +// self.path = Some(path); +// } - /// Get the file path which is processing currently - /// If the path is not set, it will panic - fn get_path(&self) -> PathBuf { - self.path - .clone() - .unwrap_or_else(|| panic!("file path is not set")) - } -} +// /// Get the file path which is processing currently +// /// If the path is not set, it will panic +// fn get_path(&self) -> PathBuf { +// self.path +// .clone() +// .unwrap_or_else(|| panic!("file path is not set")) +// } +// } -fn emit_error_diagnostic(logger: &Logger, error: &ProjectError) { - let writer = StandardStream::stderr(ColorChoice::Always); - let config = Config::default(); - let file = get_file_from_error(logger, error); - let diagnostic_message = get_diagnostic_message_from_error(error); - let diagnostic_labels = get_diagnostic_labels_from_error(logger, error); - let diagnostic_notes = get_diagnostic_notes_from_error(error); - let diagnostic = Diagnostic::new(Severity::Error) - .with_message(diagnostic_message) - .with_code(error.codespan_code()) - .with_labels(diagnostic_labels) - .with_notes(diagnostic_notes); - let mut writer_lock = writer.lock(); - if let Err(err) = emit(&mut writer_lock, &config, &file, &diagnostic) { - eprintln!("Error emitting diagnostic: {}", err); - exit(1); - } -} +// fn emit_error_diagnostic(logger: &Logger, error: &Error) { +// let writer = StandardStream::stderr(ColorChoice::Always); +// let config = Config::default(); +// let file = get_file_from_error(logger, error); +// let diagnostic_message = get_diagnostic_message_from_error(error); +// let diagnostic_labels = get_diagnostic_labels_from_error(logger, error); +// let diagnostic_notes = get_diagnostic_notes_from_error(error); +// let diagnostic = Diagnostic::new(Severity::Error) +// .with_message(diagnostic_message) +// .with_code(error.code()) +// .with_labels(diagnostic_labels) +// .with_notes(diagnostic_notes); +// let mut writer_lock = writer.lock(); +// if let Err(err) = emit(&mut writer_lock, &config, &file, &diagnostic) { +// eprintln!("Error emitting diagnostic: {}", err); +// exit(1); +// } +// } -fn emit_warning_diagnostic(logger: &Logger, warning: &ProjectWarning) { - let writer = StandardStream::stderr(ColorChoice::Always); - let config = Config::default(); - let file = get_file_from_warning(logger, warning); - let diagnostic_message = get_diagnostic_message_from_warning(warning); - let diagnostic_labels = get_diagnostic_labels_from_warning(logger, warning); - let diagnostic_notes = get_diagnostic_notes_from_warning(warning); - let diagnostic = Diagnostic::new(Severity::Warning) - .with_message(diagnostic_message) - .with_code(warning.codespan_code()) - .with_labels(diagnostic_labels) - .with_notes(diagnostic_notes); - let mut writer_lock = writer.lock(); - if let Err(err) = emit(&mut writer_lock, &config, &file, &diagnostic) { - eprintln!("Error emitting diagnostic: {}", err); - exit(1); - } -} +// fn emit_warning_diagnostic(logger: &Logger, warning: &Warning) { +// let writer = StandardStream::stderr(ColorChoice::Always); +// let config = Config::default(); +// let file = get_file_from_warning(logger, warning); +// let diagnostic_message = get_diagnostic_message_from_warning(warning); +// let diagnostic_labels = get_diagnostic_labels_from_warning(logger, warning); +// let diagnostic_notes = get_diagnostic_notes_from_warning(warning); +// let diagnostic = Diagnostic::new(Severity::Warning) +// .with_message(diagnostic_message) +// .with_code(warning.code()) +// .with_labels(diagnostic_labels) +// .with_notes(diagnostic_notes); +// let mut writer_lock = writer.lock(); +// if let Err(err) = emit(&mut writer_lock, &config, &file, &diagnostic) { +// eprintln!("Error emitting diagnostic: {}", err); +// exit(1); +// } +// } -const TAG_WIDTH: usize = 12; +// const TAG_WIDTH: usize = 12; -fn emit_info_output(info: &ProjectInfo) { - let tag = get_info_tag(info); - let heading = get_info_heading(info); - let padding = " ".repeat(TAG_WIDTH - tag.len()); - let message = format!("{}{} {}", padding, tag.green().bold(), heading); - println!("{}", message); -} +// fn emit_info_output(info: &Info) { +// let tag = get_info_tag(info); +// let heading = get_info_heading(info); +// let padding = " ".repeat(TAG_WIDTH - tag.len()); +// let message = format!("{}{} {}", padding, tag.green().bold(), heading); +// println!("{}", message); +// } -fn emit_debug_output(debug: &ProjectDebug) { - let tag = "Debug"; - let heading = get_debug_heading(debug); - let padding = " ".repeat(TAG_WIDTH - tag.len()); - let message = format!("{}{} {}", padding, tag.black().bold(), heading); - println!("{}", message); -} +// fn emit_debug_output(debug: &Debug) { +// let tag = "Debug"; +// let heading = get_debug_heading(debug); +// let padding = " ".repeat(TAG_WIDTH - tag.len()); +// let message = format!("{}{} {}", padding, tag.black().bold(), heading); +// println!("{}", message); +// } -fn get_file_from_error(logger: &Logger, error: &ProjectError) -> SimpleFile { - let cli_command = SimpleFile::new(String::from("CLI Command"), logger.raw_command.clone()); - match error { - ProjectError::FailedToGetFileNameFromPath { .. } => cli_command, - ProjectError::FailedToConvertPathToStr { .. } => cli_command, - ProjectError::ProjectAlreadyExists { .. } => cli_command, - ProjectError::DirectoryAlreadyExists { .. } => cli_command, - ProjectError::FailedToWriteFile { .. } => cli_command, - ProjectError::FailedToCreateDirectory { .. } => cli_command, - ProjectError::FailedToSerializeManifest { .. } => cli_command, - ProjectError::FailedToDeserializeManifest { .. } => cli_command, - ProjectError::UninitializedProject => cli_command, - ProjectError::ChannelAlreadyExists { .. } => cli_command, - ProjectError::FailedToFetchResource { .. } => cli_command, - ProjectError::ReceivedHttpNonSuccessStatus { .. } => cli_command, - ProjectError::FailedToConvertBytesToStr { .. } => cli_command, - ProjectError::FailedToParseChannel { .. } => cli_command, - ProjectError::ChannelNameCannotBeEmpty => cli_command, - ProjectError::InvalidChannelName { .. } => cli_command, - ProjectError::ChannelUrlCannotBeEmpty => cli_command, - ProjectError::InvalidChannelUrl { .. } => cli_command, - ProjectError::FailedToReadFile { .. } => cli_command, - ProjectError::ManifestNotFound { .. } => cli_command, - ProjectError::FailedToRemoveFile { .. } => cli_command, - ProjectError::FailedToRemoveDir { .. } => cli_command, - ProjectError::FailedToReadFileSize { .. } => cli_command, - ProjectError::ChannelNotFound { .. } => cli_command, - ProjectError::AngleNotOpened { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::AngleNotClosed { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::CurlyNotOpened { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::CurlyNotClosed { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::SquareNotOpened { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::SquareNotClosed { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::MismatchedBracket { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::ModuleNotFound { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::IdentifierNotFound { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::DotNotFound { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::CommaNotFound { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::UnexpectedDot { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::UnexpectedComma { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::InvalidBackslash { .. } => { - let path = logger.get_path(); - let file = read_to_string(&path).unwrap_or_else(|err| { - eprintln!("Error reading file: {}", err); - exit(1); - }); - SimpleFile::new(path.display().to_string(), file) - } - ProjectError::TransformError => cli_command, - ProjectError::FailedToCreatePlugin => cli_command, - ProjectError::ExpandError => cli_command, - ProjectError::CodegenError => cli_command, - ProjectError::DocumentSettingsNotFound => cli_command, - } -} +// fn get_file_from_error(logger: &Logger, error: &Error) -> SimpleFile { +// let cli_command = SimpleFile::new(String::from("CLI Command"), logger.raw_command.clone()); +// match error { +// Error::FailedToGetFileNameFromPath { .. } => cli_command, +// ProjectError::FailedToConvertPathToStr { .. } => cli_command, +// ProjectError::ProjectAlreadyExists { .. } => cli_command, +// ProjectError::DirectoryAlreadyExists { .. } => cli_command, +// ProjectError::FailedToWriteFile { .. } => cli_command, +// ProjectError::FailedToCreateDirectory { .. } => cli_command, +// ProjectError::FailedToSerializeManifest { .. } => cli_command, +// ProjectError::FailedToDeserializeManifest { .. } => cli_command, +// ProjectError::UninitializedProject => cli_command, +// ProjectError::ChannelAlreadyExists { .. } => cli_command, +// ProjectError::FailedToFetchResource { .. } => cli_command, +// ProjectError::ReceivedHttpNonSuccessStatus { .. } => cli_command, +// ProjectError::FailedToConvertBytesToStr { .. } => cli_command, +// ProjectError::FailedToParseChannel { .. } => cli_command, +// ProjectError::ChannelNameCannotBeEmpty => cli_command, +// ProjectError::InvalidChannelName { .. } => cli_command, +// ProjectError::ChannelUrlCannotBeEmpty => cli_command, +// ProjectError::InvalidChannelUrl { .. } => cli_command, +// ProjectError::FailedToReadFile { .. } => cli_command, +// ProjectError::ManifestNotFound { .. } => cli_command, +// ProjectError::FailedToRemoveFile { .. } => cli_command, +// ProjectError::FailedToRemoveDir { .. } => cli_command, +// ProjectError::FailedToReadFileSize { .. } => cli_command, +// ProjectError::ChannelNotFound { .. } => cli_command, +// ProjectError::AngleNotOpened { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::AngleNotClosed { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::CurlyNotOpened { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::CurlyNotClosed { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::SquareNotOpened { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::SquareNotClosed { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::MismatchedBracket { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::ModuleNotFound { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::IdentifierNotFound { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::DotNotFound { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::CommaNotFound { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::UnexpectedDot { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::UnexpectedComma { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::InvalidBackslash { .. } => { +// let path = logger.get_path(); +// let file = read_to_string(&path).unwrap_or_else(|err| { +// eprintln!("Error reading file: {}", err); +// exit(1); +// }); +// SimpleFile::new(path.display().to_string(), file) +// } +// ProjectError::TransformError => cli_command, +// ProjectError::FailedToCreatePlugin => cli_command, +// ProjectError::ExpandError => cli_command, +// ProjectError::CodegenError => cli_command, +// ProjectError::DocumentSettingsNotFound => cli_command, +// } +// } -fn get_diagnostic_message_from_error(error: &ProjectError) -> String { - match error { - ProjectError::FailedToGetFileNameFromPath { .. } => { - String::from("failed to get file name from path") - } - ProjectError::FailedToConvertPathToStr { .. } => { - String::from("failed to convert path to string") - } - ProjectError::ProjectAlreadyExists { .. } => String::from("project already exists"), - ProjectError::DirectoryAlreadyExists { .. } => String::from("directory already exists"), - ProjectError::FailedToWriteFile { .. } => String::from("failed to write file"), - ProjectError::FailedToCreateDirectory { .. } => String::from("failed to create directory"), - ProjectError::FailedToSerializeManifest { .. } => { - String::from("failed to serialize manifest") - } - ProjectError::FailedToDeserializeManifest { .. } => { - String::from("failed to deserialize manifest") - } - ProjectError::UninitializedProject => String::from("uninitialized project"), - ProjectError::ChannelAlreadyExists { .. } => String::from("channel already exists"), - ProjectError::FailedToFetchResource { .. } => String::from("failed to fetch resource"), - ProjectError::ReceivedHttpNonSuccessStatus { .. } => { - String::from("received non-success status") - } - ProjectError::FailedToConvertBytesToStr { .. } => { - String::from("failed to convert bytes to string") - } - ProjectError::FailedToParseChannel { .. } => String::from("failed to parse channel"), - ProjectError::ChannelNameCannotBeEmpty => String::from("channel name cannot be empty"), - ProjectError::InvalidChannelName { .. } => String::from("invalid channel name"), - ProjectError::ChannelUrlCannotBeEmpty => String::from("channel url cannot be empty"), - ProjectError::InvalidChannelUrl { .. } => String::from("invalid channel url"), - ProjectError::FailedToReadFile { .. } => String::from("failed to read file"), - ProjectError::ManifestNotFound { .. } => String::from("manifest not found"), - ProjectError::FailedToRemoveFile { .. } => String::from("failed to remove file"), - ProjectError::FailedToRemoveDir { .. } => String::from("failed to remove directory"), - ProjectError::FailedToReadFileSize { .. } => String::from("failed to read file size"), - ProjectError::ChannelNotFound { .. } => String::from("channel not found"), - ProjectError::AngleNotOpened { .. } => String::from("angle not opened"), - ProjectError::AngleNotClosed { .. } => String::from("angle not closed"), - ProjectError::CurlyNotOpened { .. } => String::from("curly not opened"), - ProjectError::CurlyNotClosed { .. } => String::from("curly not closed"), - ProjectError::SquareNotOpened { .. } => String::from("square not opened"), - ProjectError::SquareNotClosed { .. } => String::from("square not closed"), - ProjectError::MismatchedBracket { .. } => String::from("mismatched bracket"), - ProjectError::ModuleNotFound { .. } => String::from("module not found"), - ProjectError::IdentifierNotFound { .. } => String::from("identifier not found"), - ProjectError::DotNotFound { .. } => String::from("dot not found"), - ProjectError::CommaNotFound { .. } => String::from("comma not found"), - ProjectError::UnexpectedDot { .. } => String::from("unexpected dot"), - ProjectError::UnexpectedComma { .. } => String::from("unexpected comma"), - ProjectError::InvalidBackslash { .. } => String::from("invalid backslash"), - ProjectError::TransformError => String::from("transform error"), - ProjectError::FailedToCreatePlugin => String::from("failed to create plugin"), - ProjectError::ExpandError => String::from("expand error"), - ProjectError::CodegenError => String::from("codegen error"), - ProjectError::DocumentSettingsNotFound => String::from("document settings not found"), - } -} +// fn get_diagnostic_message_from_error(error: &ProjectError) -> String { +// match error { +// ProjectError::FailedToGetFileNameFromPath { .. } => { +// String::from("failed to get file name from path") +// } +// ProjectError::FailedToConvertPathToStr { .. } => { +// String::from("failed to convert path to string") +// } +// ProjectError::ProjectAlreadyExists { .. } => String::from("project already exists"), +// ProjectError::DirectoryAlreadyExists { .. } => String::from("directory already exists"), +// ProjectError::FailedToWriteFile { .. } => String::from("failed to write file"), +// ProjectError::FailedToCreateDirectory { .. } => String::from("failed to create directory"), +// ProjectError::FailedToSerializeManifest { .. } => { +// String::from("failed to serialize manifest") +// } +// ProjectError::FailedToDeserializeManifest { .. } => { +// String::from("failed to deserialize manifest") +// } +// ProjectError::UninitializedProject => String::from("uninitialized project"), +// ProjectError::ChannelAlreadyExists { .. } => String::from("channel already exists"), +// ProjectError::FailedToFetchResource { .. } => String::from("failed to fetch resource"), +// ProjectError::ReceivedHttpNonSuccessStatus { .. } => { +// String::from("received non-success status") +// } +// ProjectError::FailedToConvertBytesToStr { .. } => { +// String::from("failed to convert bytes to string") +// } +// ProjectError::FailedToParseChannel { .. } => String::from("failed to parse channel"), +// ProjectError::ChannelNameCannotBeEmpty => String::from("channel name cannot be empty"), +// ProjectError::InvalidChannelName { .. } => String::from("invalid channel name"), +// ProjectError::ChannelUrlCannotBeEmpty => String::from("channel url cannot be empty"), +// ProjectError::InvalidChannelUrl { .. } => String::from("invalid channel url"), +// ProjectError::FailedToReadFile { .. } => String::from("failed to read file"), +// ProjectError::ManifestNotFound { .. } => String::from("manifest not found"), +// ProjectError::FailedToRemoveFile { .. } => String::from("failed to remove file"), +// ProjectError::FailedToRemoveDir { .. } => String::from("failed to remove directory"), +// ProjectError::FailedToReadFileSize { .. } => String::from("failed to read file size"), +// ProjectError::ChannelNotFound { .. } => String::from("channel not found"), +// ProjectError::AngleNotOpened { .. } => String::from("angle not opened"), +// ProjectError::AngleNotClosed { .. } => String::from("angle not closed"), +// ProjectError::CurlyNotOpened { .. } => String::from("curly not opened"), +// ProjectError::CurlyNotClosed { .. } => String::from("curly not closed"), +// ProjectError::SquareNotOpened { .. } => String::from("square not opened"), +// ProjectError::SquareNotClosed { .. } => String::from("square not closed"), +// ProjectError::MismatchedBracket { .. } => String::from("mismatched bracket"), +// ProjectError::ModuleNotFound { .. } => String::from("module not found"), +// ProjectError::IdentifierNotFound { .. } => String::from("identifier not found"), +// ProjectError::DotNotFound { .. } => String::from("dot not found"), +// ProjectError::CommaNotFound { .. } => String::from("comma not found"), +// ProjectError::UnexpectedDot { .. } => String::from("unexpected dot"), +// ProjectError::UnexpectedComma { .. } => String::from("unexpected comma"), +// ProjectError::InvalidBackslash { .. } => String::from("invalid backslash"), +// ProjectError::TransformError => String::from("transform error"), +// ProjectError::FailedToCreatePlugin => String::from("failed to create plugin"), +// ProjectError::ExpandError => String::from("expand error"), +// ProjectError::CodegenError => String::from("codegen error"), +// ProjectError::DocumentSettingsNotFound => String::from("document settings not found"), +// } +// } -fn get_label_message_from_error(error: &ProjectError) -> String { - match error { - ProjectError::FailedToGetFileNameFromPath { path } => { - let needle = path.display().to_string(); - if needle.ends_with("..") { - String::from("path cannot terminate with `..`") - } else if needle == "/" { - String::from("path cannot be `/`") - } else if needle.is_empty() { - String::from("path cannot be empty") - } else { - String::from("failed to get file name for unknown reason") - } - } - ProjectError::FailedToConvertPathToStr { path } => { - format!("path cannot be converted UTF-8: {}", path.display()) - } - ProjectError::ProjectAlreadyExists { path } => { - format!("project already exists: {}", path.display()) - } - ProjectError::DirectoryAlreadyExists { path } => { - format!("directory already exists: {}", path.display()) - } - ProjectError::FailedToWriteFile { path, .. } => { - format!("failed to write file: {}", path.display()) - } - ProjectError::FailedToCreateDirectory { path, .. } => { - format!("failed to create directory: {}", path.display()) - } - ProjectError::FailedToSerializeManifest { .. } => String::from(""), - ProjectError::FailedToDeserializeManifest { .. } => String::from(""), - ProjectError::UninitializedProject => String::from(""), - ProjectError::ChannelAlreadyExists { name } => { - format!("channel already exists: {}", name) - } - ProjectError::FailedToFetchResource { url, .. } => { - format!("failed to fetch resource: {}", url) - } - ProjectError::ReceivedHttpNonSuccessStatus { - url, - status, - err_msg, - } => { - format!( - "received non-success status: {} {}: {}", - url, status, err_msg - ) - } - ProjectError::FailedToConvertBytesToStr { source } => { - format!("failed to convert bytes to string: {}", source) - } - ProjectError::FailedToParseChannel { source } => { - format!("failed to parse channel: {}", source) - } - ProjectError::ChannelNameCannotBeEmpty => String::from("channel name cannot be empty"), - ProjectError::InvalidChannelName { name } => { - format!("invalid channel name: {}", name) - } - ProjectError::ChannelUrlCannotBeEmpty => String::from("channel url cannot be empty"), - ProjectError::InvalidChannelUrl { url } => { - format!("invalid channel url: {}", url) - } - ProjectError::FailedToReadFile { path, .. } => { - format!("failed to read file: {}", path.display()) - } - ProjectError::ManifestNotFound { path } => { - format!("manifest not found: {}", path.display()) - } - ProjectError::FailedToRemoveFile { path, .. } => { - format!("failed to remove file: {}", path.display()) - } - ProjectError::FailedToRemoveDir { path, .. } => { - format!("failed to remove directory: {}", path.display()) - } - ProjectError::FailedToReadFileSize { path, .. } => { - format!("failed to read file size: {}", path.display()) - } - ProjectError::ChannelNotFound { name } => { - format!("channel not found: {}", name) - } - ProjectError::AngleNotOpened { .. } => String::from("angle not opened"), - ProjectError::AngleNotClosed { .. } => String::from("angle not closed"), - ProjectError::CurlyNotOpened { .. } => String::from("curly not opened"), - ProjectError::CurlyNotClosed { .. } => String::from("curly not closed"), - ProjectError::SquareNotOpened { .. } => String::from("square not opened"), - ProjectError::SquareNotClosed { .. } => String::from("square not closed"), - ProjectError::MismatchedBracket { .. } => String::from("mismatched bracket"), - ProjectError::ModuleNotFound { .. } => String::from("module not found"), - ProjectError::IdentifierNotFound { .. } => String::from("identifier not found"), - ProjectError::DotNotFound { .. } => String::from("dot not found"), - ProjectError::CommaNotFound { .. } => String::from("comma not found"), - ProjectError::UnexpectedDot { .. } => String::from("unexpected dot"), - ProjectError::UnexpectedComma { .. } => String::from("unexpected comma"), - ProjectError::InvalidBackslash { .. } => String::from("invalid backslash"), - ProjectError::TransformError => String::from("transform error"), - ProjectError::FailedToCreatePlugin => String::from("failed to create plugin"), - ProjectError::ExpandError => String::from("expand error"), - ProjectError::CodegenError => String::from("codegen error"), - ProjectError::DocumentSettingsNotFound => String::from("document settings not found"), - } -} +// fn get_label_message_from_error(error: &ProjectError) -> String { +// match error { +// ProjectError::FailedToGetFileNameFromPath { path } => { +// let needle = path.display().to_string(); +// if needle.ends_with("..") { +// String::from("path cannot terminate with `..`") +// } else if needle == "/" { +// String::from("path cannot be `/`") +// } else if needle.is_empty() { +// String::from("path cannot be empty") +// } else { +// String::from("failed to get file name for unknown reason") +// } +// } +// ProjectError::FailedToConvertPathToStr { path } => { +// format!("path cannot be converted UTF-8: {}", path.display()) +// } +// ProjectError::ProjectAlreadyExists { path } => { +// format!("project already exists: {}", path.display()) +// } +// ProjectError::DirectoryAlreadyExists { path } => { +// format!("directory already exists: {}", path.display()) +// } +// ProjectError::FailedToWriteFile { path, .. } => { +// format!("failed to write file: {}", path.display()) +// } +// ProjectError::FailedToCreateDirectory { path, .. } => { +// format!("failed to create directory: {}", path.display()) +// } +// ProjectError::FailedToSerializeManifest { .. } => String::from(""), +// ProjectError::FailedToDeserializeManifest { .. } => String::from(""), +// ProjectError::UninitializedProject => String::from(""), +// ProjectError::ChannelAlreadyExists { name } => { +// format!("channel already exists: {}", name) +// } +// ProjectError::FailedToFetchResource { url, .. } => { +// format!("failed to fetch resource: {}", url) +// } +// ProjectError::ReceivedHttpNonSuccessStatus { +// url, +// status, +// err_msg, +// } => { +// format!( +// "received non-success status: {} {}: {}", +// url, status, err_msg +// ) +// } +// ProjectError::FailedToConvertBytesToStr { source } => { +// format!("failed to convert bytes to string: {}", source) +// } +// ProjectError::FailedToParseChannel { source } => { +// format!("failed to parse channel: {}", source) +// } +// ProjectError::ChannelNameCannotBeEmpty => String::from("channel name cannot be empty"), +// ProjectError::InvalidChannelName { name } => { +// format!("invalid channel name: {}", name) +// } +// ProjectError::ChannelUrlCannotBeEmpty => String::from("channel url cannot be empty"), +// ProjectError::InvalidChannelUrl { url } => { +// format!("invalid channel url: {}", url) +// } +// ProjectError::FailedToReadFile { path, .. } => { +// format!("failed to read file: {}", path.display()) +// } +// ProjectError::ManifestNotFound { path } => { +// format!("manifest not found: {}", path.display()) +// } +// ProjectError::FailedToRemoveFile { path, .. } => { +// format!("failed to remove file: {}", path.display()) +// } +// ProjectError::FailedToRemoveDir { path, .. } => { +// format!("failed to remove directory: {}", path.display()) +// } +// ProjectError::FailedToReadFileSize { path, .. } => { +// format!("failed to read file size: {}", path.display()) +// } +// ProjectError::ChannelNotFound { name } => { +// format!("channel not found: {}", name) +// } +// ProjectError::AngleNotOpened { .. } => String::from("angle not opened"), +// ProjectError::AngleNotClosed { .. } => String::from("angle not closed"), +// ProjectError::CurlyNotOpened { .. } => String::from("curly not opened"), +// ProjectError::CurlyNotClosed { .. } => String::from("curly not closed"), +// ProjectError::SquareNotOpened { .. } => String::from("square not opened"), +// ProjectError::SquareNotClosed { .. } => String::from("square not closed"), +// ProjectError::MismatchedBracket { .. } => String::from("mismatched bracket"), +// ProjectError::ModuleNotFound { .. } => String::from("module not found"), +// ProjectError::IdentifierNotFound { .. } => String::from("identifier not found"), +// ProjectError::DotNotFound { .. } => String::from("dot not found"), +// ProjectError::CommaNotFound { .. } => String::from("comma not found"), +// ProjectError::UnexpectedDot { .. } => String::from("unexpected dot"), +// ProjectError::UnexpectedComma { .. } => String::from("unexpected comma"), +// ProjectError::InvalidBackslash { .. } => String::from("invalid backslash"), +// ProjectError::TransformError => String::from("transform error"), +// ProjectError::FailedToCreatePlugin => String::from("failed to create plugin"), +// ProjectError::ExpandError => String::from("expand error"), +// ProjectError::CodegenError => String::from("codegen error"), +// ProjectError::DocumentSettingsNotFound => String::from("document settings not found"), +// } +// } -fn search_for_needle_in_heystack(needle: &str, heystack: &str) -> (usize, usize) { - let mut start = 0; - let mut end = 0; - for word in heystack.split_whitespace() { - if word == needle { - end = start + word.len(); - break; - } - start += word.len() + 1; - } - if end == 0 { - end = start + needle.len(); - } - (start, end) -} +// fn search_for_needle_in_heystack(needle: &str, heystack: &str) -> (usize, usize) { +// let mut start = 0; +// let mut end = 0; +// for word in heystack.split_whitespace() { +// if word == needle { +// end = start + word.len(); +// break; +// } +// start += word.len() + 1; +// } +// if end == 0 { +// end = start + needle.len(); +// } +// (start, end) +// } -fn common_label>(logger: &Logger, error: &ProjectError, path: &P) -> Label<()> { - let (start, end) = - search_for_needle_in_heystack(&path.as_ref().display().to_string(), &logger.raw_command); - Label::primary((), start..end).with_message(get_label_message_from_error(error)) -} +// fn common_label>(logger: &Logger, error: &ProjectError, path: &P) -> Label<()> { +// let (start, end) = +// search_for_needle_in_heystack(&path.as_ref().display().to_string(), &logger.raw_command); +// Label::primary((), start..end).with_message(get_label_message_from_error(error)) +// } -fn get_diagnostic_labels_from_error(logger: &Logger, error: &ProjectError) -> Vec> { - match error { - ProjectError::FailedToGetFileNameFromPath { path } => { - vec![common_label(logger, error, path)] - } - ProjectError::FailedToConvertPathToStr { path } => vec![common_label(logger, error, path)], - ProjectError::ProjectAlreadyExists { path } => vec![common_label(logger, error, path)], - ProjectError::DirectoryAlreadyExists { path } => vec![common_label(logger, error, path)], - ProjectError::FailedToWriteFile { path, .. } => vec![common_label(logger, error, path)], - ProjectError::FailedToCreateDirectory { path, .. } => { - vec![common_label(logger, error, path)] - } - ProjectError::FailedToSerializeManifest { .. } => vec![], - ProjectError::FailedToDeserializeManifest { .. } => vec![], - ProjectError::UninitializedProject => vec![], - ProjectError::ChannelAlreadyExists { name } => vec![common_label(logger, error, name)], - ProjectError::FailedToFetchResource { url, .. } => { - vec![common_label(logger, error, url)] - } - ProjectError::ReceivedHttpNonSuccessStatus { url, .. } => { - vec![common_label(logger, error, url)] - } - ProjectError::FailedToConvertBytesToStr { .. } => vec![], - ProjectError::FailedToParseChannel { .. } => vec![], - ProjectError::ChannelNameCannotBeEmpty => { - vec![common_label(logger, error, &logger.raw_command)] - } - ProjectError::InvalidChannelName { name } => vec![common_label(logger, error, name)], - ProjectError::ChannelUrlCannotBeEmpty => { - vec![common_label(logger, error, &logger.raw_command)] - } - ProjectError::InvalidChannelUrl { url } => vec![common_label(logger, error, url)], - ProjectError::FailedToReadFile { path, .. } => vec![common_label(logger, error, path)], - ProjectError::ManifestNotFound { path } => vec![common_label(logger, error, path)], - ProjectError::FailedToRemoveFile { path, .. } => vec![common_label(logger, error, path)], - ProjectError::FailedToRemoveDir { path, .. } => vec![common_label(logger, error, path)], - ProjectError::FailedToReadFileSize { path, .. } => vec![common_label(logger, error, path)], - ProjectError::ChannelNotFound { name } => { - vec![common_label(logger, error, name)] - } - ProjectError::AngleNotOpened { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("angle not opened")] - } - ProjectError::AngleNotClosed { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("angle not closed")] - } - ProjectError::CurlyNotOpened { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("curly not opened")] - } - ProjectError::CurlyNotClosed { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("curly not closed")] - } - ProjectError::SquareNotOpened { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("square not opened")] - } - ProjectError::SquareNotClosed { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("square not closed")] - } - ProjectError::MismatchedBracket { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("mismatched bracket")] - } - ProjectError::ModuleNotFound { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("module not found")] - } - ProjectError::IdentifierNotFound { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("identifier not found")] - } - ProjectError::DotNotFound { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("dot not found")] - } - ProjectError::CommaNotFound { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("comma not found")] - } - ProjectError::UnexpectedDot { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("unexpected dot")] - } - ProjectError::UnexpectedComma { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("unexpected comma")] - } - ProjectError::InvalidBackslash { location } => { - let path = logger.get_path(); - let file = match read_to_string(&path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error reading file: {}", err); - exit(1); - } - }; - vec![Label::primary( - (), - location.start.to_usize(&file)..location.end.to_usize(&file), - ) - .with_message("invalid backslash")] - } - ProjectError::TransformError => vec![], - ProjectError::FailedToCreatePlugin => vec![], - ProjectError::ExpandError => vec![], - ProjectError::CodegenError => vec![], - ProjectError::DocumentSettingsNotFound => vec![], - } -} +// fn get_diagnostic_labels_from_error(logger: &Logger, error: &ProjectError) -> Vec> { +// match error { +// ProjectError::FailedToGetFileNameFromPath { path } => { +// vec![common_label(logger, error, path)] +// } +// ProjectError::FailedToConvertPathToStr { path } => vec![common_label(logger, error, path)], +// ProjectError::ProjectAlreadyExists { path } => vec![common_label(logger, error, path)], +// ProjectError::DirectoryAlreadyExists { path } => vec![common_label(logger, error, path)], +// ProjectError::FailedToWriteFile { path, .. } => vec![common_label(logger, error, path)], +// ProjectError::FailedToCreateDirectory { path, .. } => { +// vec![common_label(logger, error, path)] +// } +// ProjectError::FailedToSerializeManifest { .. } => vec![], +// ProjectError::FailedToDeserializeManifest { .. } => vec![], +// ProjectError::UninitializedProject => vec![], +// ProjectError::ChannelAlreadyExists { name } => vec![common_label(logger, error, name)], +// ProjectError::FailedToFetchResource { url, .. } => { +// vec![common_label(logger, error, url)] +// } +// ProjectError::ReceivedHttpNonSuccessStatus { url, .. } => { +// vec![common_label(logger, error, url)] +// } +// ProjectError::FailedToConvertBytesToStr { .. } => vec![], +// ProjectError::FailedToParseChannel { .. } => vec![], +// ProjectError::ChannelNameCannotBeEmpty => { +// vec![common_label(logger, error, &logger.raw_command)] +// } +// ProjectError::InvalidChannelName { name } => vec![common_label(logger, error, name)], +// ProjectError::ChannelUrlCannotBeEmpty => { +// vec![common_label(logger, error, &logger.raw_command)] +// } +// ProjectError::InvalidChannelUrl { url } => vec![common_label(logger, error, url)], +// ProjectError::FailedToReadFile { path, .. } => vec![common_label(logger, error, path)], +// ProjectError::ManifestNotFound { path } => vec![common_label(logger, error, path)], +// ProjectError::FailedToRemoveFile { path, .. } => vec![common_label(logger, error, path)], +// ProjectError::FailedToRemoveDir { path, .. } => vec![common_label(logger, error, path)], +// ProjectError::FailedToReadFileSize { path, .. } => vec![common_label(logger, error, path)], +// ProjectError::ChannelNotFound { name } => { +// vec![common_label(logger, error, name)] +// } +// ProjectError::AngleNotOpened { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("angle not opened")] +// } +// ProjectError::AngleNotClosed { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("angle not closed")] +// } +// ProjectError::CurlyNotOpened { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("curly not opened")] +// } +// ProjectError::CurlyNotClosed { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("curly not closed")] +// } +// ProjectError::SquareNotOpened { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("square not opened")] +// } +// ProjectError::SquareNotClosed { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("square not closed")] +// } +// ProjectError::MismatchedBracket { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("mismatched bracket")] +// } +// ProjectError::ModuleNotFound { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("module not found")] +// } +// ProjectError::IdentifierNotFound { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("identifier not found")] +// } +// ProjectError::DotNotFound { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("dot not found")] +// } +// ProjectError::CommaNotFound { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("comma not found")] +// } +// ProjectError::UnexpectedDot { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("unexpected dot")] +// } +// ProjectError::UnexpectedComma { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("unexpected comma")] +// } +// ProjectError::InvalidBackslash { location } => { +// let path = logger.get_path(); +// let file = match read_to_string(&path) { +// Ok(file) => file, +// Err(err) => { +// eprintln!("Error reading file: {}", err); +// exit(1); +// } +// }; +// vec![Label::primary( +// (), +// location.start.to_usize(&file)..location.end.to_usize(&file), +// ) +// .with_message("invalid backslash")] +// } +// ProjectError::TransformError => vec![], +// ProjectError::FailedToCreatePlugin => vec![], +// ProjectError::ExpandError => vec![], +// ProjectError::CodegenError => vec![], +// ProjectError::DocumentSettingsNotFound => vec![], +// } +// } -fn get_diagnostic_notes_from_error(error: &ProjectError) -> Vec { - match error { - ProjectError::FailedToGetFileNameFromPath { .. } => { - vec![format!( - "{}: Check the file path unless it terminates with `..` or is `/`", - "hint".bold(), - )] - } - ProjectError::FailedToConvertPathToStr { .. } => { - vec![format!( - "{}: Check the file path unless it is not valid UTF-8", - "hint".bold(), - ), format!( - "{}: The file path in Unix contains u8 bytes sequence, so it sometimes cannot be converted to UTF-8", - "note".bold(), - ), format!( - "{}: The file path in Windows contains UTF-16 sequence, so it cannot be converted to UTF-8", - "note".bold(), - )] - } - ProjectError::ProjectAlreadyExists { path } => { - vec![ - format!( - "{}: Check the file path unless it already exists", - "hint".bold(), - ), - format!( - "{}: You can run `rm -rf {}` to remove the project", - "note".bold(), - path.display(), - ), - ] - } - ProjectError::DirectoryAlreadyExists { path } => { - vec![ - format!( - "{}: Check the directory path unless it already exists", - "hint".bold(), - ), - format!( - "{}: You can run `rm -rf {}` to remove the directory", - "note".bold(), - path.display(), - ), - ] - } - ProjectError::FailedToWriteFile { path, source } => { - vec![ - format!( - "{}: The file path is not writable: {}", - "hint".bold(), - source, - ), - format!( - "{}: You can run `chmod +w {}` to grant the write permission", - "note".bold(), - path.display(), - ), - ] - } - ProjectError::FailedToCreateDirectory { path, source } => { - vec![ - format!( - "{}: The directory path is not writable: {}", - "hint".bold(), - source, - ), - format!( - "{}: You can run `chmod +w {}` to grant the write permission", - "note".bold(), - path.display(), - ), - ] - } - ProjectError::FailedToSerializeManifest { source } => { - vec![format!( - "{}: Check the manifest file unless it is not valid TOML: {}", - "hint".bold(), - source, - )] - } - ProjectError::FailedToDeserializeManifest { source } => { - vec![format!( - "{}: Check the manifest file unless it is not valid TOML: {}", - "hint".bold(), - source, - )] - } - ProjectError::UninitializedProject => { - vec![ - format!( - "{}: Check the Brack.toml exists in the current directory", - "hint".bold(), - ), - format!( - "{}: You can run `brack create` to initialize the project", - "note".bold(), - ), - ] - } - ProjectError::ChannelAlreadyExists { name } => { - vec![ - format!( - "{}: Check the channel name unless it already exists", - "hint".bold(), - ), - format!( - "{}: You can run `brack channel remove {}` to remove the channel", - "note".bold(), - name, - ), - format!( - "{}: You can run `brack channel add --force {}` to force add the channel", - "note".bold(), - name, - ), - ] - } - ProjectError::FailedToFetchResource { url, source } => { - vec![ - format!( - "{}: Check the URL unless it is not reachable: {}", - "hint".bold(), - source, - ), - format!( - "{}: You can run `curl -v {}` to check the URL", - "note".bold(), - url, - ), - ] - } - ProjectError::ReceivedHttpNonSuccessStatus { - url, - status, - err_msg, - } => { - vec![ - format!( - "{}: Check the URL unless it is not reachable: {}", - "hint".bold(), - err_msg, - ), - format!( - "{}: You can run `curl -v {}` to check the URL", - "note".bold(), - url, - ), - format!("{}: The status code is {}", "note".bold(), status,), - ] - } - ProjectError::FailedToConvertBytesToStr { source } => { - vec![format!( - "{}: Check the bytes sequence unless it is not valid UTF-8: {}", - "hint".bold(), - source, - )] - } - ProjectError::FailedToParseChannel { source } => { - vec![format!( - "{}: Check the channel file unless it is not valid TOML: {}", - "hint".bold(), - source, - )] - } - ProjectError::ChannelNameCannotBeEmpty => { - vec![format!("{}: Channel name cannot be empty", "hint".bold(),)] - } - ProjectError::InvalidChannelName { .. } => { - vec![format!( - "{}: Channel name can only contain alphanumeric characters and underscores", - "hint".bold(), - )] - } - ProjectError::ChannelUrlCannotBeEmpty => { - vec![format!("{}: Channel URL cannot be empty", "hint".bold(),)] - } - ProjectError::InvalidChannelUrl { .. } => { - vec![format!( - "{}: Channel URL must start with `http://` or `https://`", - "hint".bold(), - )] - } - ProjectError::FailedToReadFile { path, source } => { - vec![format!( - "{}: Check the file path {} unless it is not readable: {}", - "hint".bold(), - path.display(), - source, - )] - } - ProjectError::ManifestNotFound { path } => { - vec![format!( - "{}: Check the file path unless it is not readable: {}", - "hint".bold(), - path.display(), - )] - } - ProjectError::FailedToRemoveFile { path, source } => { - vec![format!( - "{}: Check the file path {} unless it is not removable: {}", - "hint".bold(), - path.display(), - source, - )] - } - ProjectError::FailedToRemoveDir { path, source } => { - vec![format!( - "{}: Check the directory path {} unless it is not removable: {}", - "hint".bold(), - path.display(), - source, - )] - } - ProjectError::FailedToReadFileSize { path, source } => { - vec![format!( - "{}: Check the file path {} unless it is not readable: {}", - "hint".bold(), - path.display(), - source, - )] - } - ProjectError::ChannelNotFound { name } => { - vec![ - format!( - "{}: Check the channel name {} unless it is not found", - "hint".bold(), - name, - ), - format!( - "{}: You can run `brack channel list` to list all channels", - "note".bold(), - ), - ] - } - ProjectError::AngleNotOpened { .. } => vec![], - ProjectError::AngleNotClosed { .. } => vec![], - ProjectError::CurlyNotOpened { .. } => vec![], - ProjectError::CurlyNotClosed { .. } => vec![], - ProjectError::SquareNotOpened { .. } => vec![], - ProjectError::SquareNotClosed { .. } => vec![], - ProjectError::MismatchedBracket { .. } => vec![], - ProjectError::ModuleNotFound { .. } => vec![], - ProjectError::IdentifierNotFound { .. } => vec![], - ProjectError::DotNotFound { .. } => vec![], - ProjectError::CommaNotFound { .. } => vec![], - ProjectError::UnexpectedDot { .. } => vec![], - ProjectError::UnexpectedComma { .. } => vec![], - ProjectError::InvalidBackslash { .. } => vec![], - ProjectError::TransformError => vec![], - ProjectError::FailedToCreatePlugin => vec![], - ProjectError::ExpandError => vec![], - ProjectError::CodegenError => vec![], - ProjectError::DocumentSettingsNotFound => vec![], - } -} +// fn get_diagnostic_notes_from_error(error: &ProjectError) -> Vec { +// match error { +// ProjectError::FailedToGetFileNameFromPath { .. } => { +// vec![format!( +// "{}: Check the file path unless it terminates with `..` or is `/`", +// "hint".bold(), +// )] +// } +// ProjectError::FailedToConvertPathToStr { .. } => { +// vec![format!( +// "{}: Check the file path unless it is not valid UTF-8", +// "hint".bold(), +// ), format!( +// "{}: The file path in Unix contains u8 bytes sequence, so it sometimes cannot be converted to UTF-8", +// "note".bold(), +// ), format!( +// "{}: The file path in Windows contains UTF-16 sequence, so it cannot be converted to UTF-8", +// "note".bold(), +// )] +// } +// ProjectError::ProjectAlreadyExists { path } => { +// vec![ +// format!( +// "{}: Check the file path unless it already exists", +// "hint".bold(), +// ), +// format!( +// "{}: You can run `rm -rf {}` to remove the project", +// "note".bold(), +// path.display(), +// ), +// ] +// } +// ProjectError::DirectoryAlreadyExists { path } => { +// vec![ +// format!( +// "{}: Check the directory path unless it already exists", +// "hint".bold(), +// ), +// format!( +// "{}: You can run `rm -rf {}` to remove the directory", +// "note".bold(), +// path.display(), +// ), +// ] +// } +// ProjectError::FailedToWriteFile { path, source } => { +// vec![ +// format!( +// "{}: The file path is not writable: {}", +// "hint".bold(), +// source, +// ), +// format!( +// "{}: You can run `chmod +w {}` to grant the write permission", +// "note".bold(), +// path.display(), +// ), +// ] +// } +// ProjectError::FailedToCreateDirectory { path, source } => { +// vec![ +// format!( +// "{}: The directory path is not writable: {}", +// "hint".bold(), +// source, +// ), +// format!( +// "{}: You can run `chmod +w {}` to grant the write permission", +// "note".bold(), +// path.display(), +// ), +// ] +// } +// ProjectError::FailedToSerializeManifest { source } => { +// vec![format!( +// "{}: Check the manifest file unless it is not valid TOML: {}", +// "hint".bold(), +// source, +// )] +// } +// ProjectError::FailedToDeserializeManifest { source } => { +// vec![format!( +// "{}: Check the manifest file unless it is not valid TOML: {}", +// "hint".bold(), +// source, +// )] +// } +// ProjectError::UninitializedProject => { +// vec![ +// format!( +// "{}: Check the Brack.toml exists in the current directory", +// "hint".bold(), +// ), +// format!( +// "{}: You can run `brack create` to initialize the project", +// "note".bold(), +// ), +// ] +// } +// ProjectError::ChannelAlreadyExists { name } => { +// vec![ +// format!( +// "{}: Check the channel name unless it already exists", +// "hint".bold(), +// ), +// format!( +// "{}: You can run `brack channel remove {}` to remove the channel", +// "note".bold(), +// name, +// ), +// format!( +// "{}: You can run `brack channel add --force {}` to force add the channel", +// "note".bold(), +// name, +// ), +// ] +// } +// ProjectError::FailedToFetchResource { url, source } => { +// vec![ +// format!( +// "{}: Check the URL unless it is not reachable: {}", +// "hint".bold(), +// source, +// ), +// format!( +// "{}: You can run `curl -v {}` to check the URL", +// "note".bold(), +// url, +// ), +// ] +// } +// ProjectError::ReceivedHttpNonSuccessStatus { +// url, +// status, +// err_msg, +// } => { +// vec![ +// format!( +// "{}: Check the URL unless it is not reachable: {}", +// "hint".bold(), +// err_msg, +// ), +// format!( +// "{}: You can run `curl -v {}` to check the URL", +// "note".bold(), +// url, +// ), +// format!("{}: The status code is {}", "note".bold(), status,), +// ] +// } +// ProjectError::FailedToConvertBytesToStr { source } => { +// vec![format!( +// "{}: Check the bytes sequence unless it is not valid UTF-8: {}", +// "hint".bold(), +// source, +// )] +// } +// ProjectError::FailedToParseChannel { source } => { +// vec![format!( +// "{}: Check the channel file unless it is not valid TOML: {}", +// "hint".bold(), +// source, +// )] +// } +// ProjectError::ChannelNameCannotBeEmpty => { +// vec![format!("{}: Channel name cannot be empty", "hint".bold(),)] +// } +// ProjectError::InvalidChannelName { .. } => { +// vec![format!( +// "{}: Channel name can only contain alphanumeric characters and underscores", +// "hint".bold(), +// )] +// } +// ProjectError::ChannelUrlCannotBeEmpty => { +// vec![format!("{}: Channel URL cannot be empty", "hint".bold(),)] +// } +// ProjectError::InvalidChannelUrl { .. } => { +// vec![format!( +// "{}: Channel URL must start with `http://` or `https://`", +// "hint".bold(), +// )] +// } +// ProjectError::FailedToReadFile { path, source } => { +// vec![format!( +// "{}: Check the file path {} unless it is not readable: {}", +// "hint".bold(), +// path.display(), +// source, +// )] +// } +// ProjectError::ManifestNotFound { path } => { +// vec![format!( +// "{}: Check the file path unless it is not readable: {}", +// "hint".bold(), +// path.display(), +// )] +// } +// ProjectError::FailedToRemoveFile { path, source } => { +// vec![format!( +// "{}: Check the file path {} unless it is not removable: {}", +// "hint".bold(), +// path.display(), +// source, +// )] +// } +// ProjectError::FailedToRemoveDir { path, source } => { +// vec![format!( +// "{}: Check the directory path {} unless it is not removable: {}", +// "hint".bold(), +// path.display(), +// source, +// )] +// } +// ProjectError::FailedToReadFileSize { path, source } => { +// vec![format!( +// "{}: Check the file path {} unless it is not readable: {}", +// "hint".bold(), +// path.display(), +// source, +// )] +// } +// ProjectError::ChannelNotFound { name } => { +// vec![ +// format!( +// "{}: Check the channel name {} unless it is not found", +// "hint".bold(), +// name, +// ), +// format!( +// "{}: You can run `brack channel list` to list all channels", +// "note".bold(), +// ), +// ] +// } +// ProjectError::AngleNotOpened { .. } => vec![], +// ProjectError::AngleNotClosed { .. } => vec![], +// ProjectError::CurlyNotOpened { .. } => vec![], +// ProjectError::CurlyNotClosed { .. } => vec![], +// ProjectError::SquareNotOpened { .. } => vec![], +// ProjectError::SquareNotClosed { .. } => vec![], +// ProjectError::MismatchedBracket { .. } => vec![], +// ProjectError::ModuleNotFound { .. } => vec![], +// ProjectError::IdentifierNotFound { .. } => vec![], +// ProjectError::DotNotFound { .. } => vec![], +// ProjectError::CommaNotFound { .. } => vec![], +// ProjectError::UnexpectedDot { .. } => vec![], +// ProjectError::UnexpectedComma { .. } => vec![], +// ProjectError::InvalidBackslash { .. } => vec![], +// ProjectError::TransformError => vec![], +// ProjectError::FailedToCreatePlugin => vec![], +// ProjectError::ExpandError => vec![], +// ProjectError::CodegenError => vec![], +// ProjectError::DocumentSettingsNotFound => vec![], +// } +// } -fn get_file_from_warning(logger: &Logger, warning: &ProjectWarning) -> SimpleFile { - let cli_command = SimpleFile::new(String::from("CLI Command"), logger.raw_command.clone()); - match warning { - ProjectWarning::NoFilesRemovedDueToDryRun => cli_command, - ProjectWarning::NoChannelsFound => cli_command, - } -} +// fn get_file_from_warning(logger: &Logger, warning: &ProjectWarning) -> SimpleFile { +// let cli_command = SimpleFile::new(String::from("CLI Command"), logger.raw_command.clone()); +// match warning { +// ProjectWarning::NoFilesRemovedDueToDryRun => cli_command, +// ProjectWarning::NoChannelsFound => cli_command, +// } +// } -fn get_diagnostic_message_from_warning(warning: &ProjectWarning) -> String { - match warning { - ProjectWarning::NoFilesRemovedDueToDryRun => { - String::from("no files removed due to dry run") - } - ProjectWarning::NoChannelsFound => String::from("no channels found"), - } -} +// fn get_diagnostic_message_from_warning(warning: &ProjectWarning) -> String { +// match warning { +// ProjectWarning::NoFilesRemovedDueToDryRun => { +// String::from("no files removed due to dry run") +// } +// ProjectWarning::NoChannelsFound => String::from("no channels found"), +// } +// } -fn get_diagnostic_labels_from_warning( - _logger: &Logger, - warning: &ProjectWarning, -) -> Vec> { - match warning { - ProjectWarning::NoFilesRemovedDueToDryRun => vec![], - ProjectWarning::NoChannelsFound => vec![], - } -} +// fn get_diagnostic_labels_from_warning( +// _logger: &Logger, +// warning: &ProjectWarning, +// ) -> Vec> { +// match warning { +// ProjectWarning::NoFilesRemovedDueToDryRun => vec![], +// ProjectWarning::NoChannelsFound => vec![], +// } +// } -fn get_diagnostic_notes_from_warning(warning: &ProjectWarning) -> Vec { - match warning { - ProjectWarning::NoFilesRemovedDueToDryRun => vec![], - ProjectWarning::NoChannelsFound => vec![], - } -} +// fn get_diagnostic_notes_from_warning(warning: &ProjectWarning) -> Vec { +// match warning { +// ProjectWarning::NoFilesRemovedDueToDryRun => vec![], +// ProjectWarning::NoChannelsFound => vec![], +// } +// } -fn get_info_tag(info: &ProjectInfo) -> String { - match info { - ProjectInfo::CreatingProject { .. } => String::from("Creating"), - ProjectInfo::FinishedCreatingProject { .. } => String::from("Finished"), - ProjectInfo::AddingChannel { .. } => String::from("Adding"), - ProjectInfo::FinishedAddingChannel { .. } => String::from("Finished"), - ProjectInfo::CleaningProject => String::from("Cleaning"), - ProjectInfo::FinishedCleaningProject { .. } => String::from("Finished"), - ProjectInfo::ListingChannels => String::from("Listing"), - ProjectInfo::FinishedListingChannels => String::from("Finished"), - ProjectInfo::ChannelInfo { .. } => String::from("Channel"), - ProjectInfo::RemovingChannel { .. } => String::from("Removing"), - ProjectInfo::FinishedRemovingChannel { .. } => String::from("Finished"), - ProjectInfo::UpdatingChannel { .. } => String::from("Updating"), - ProjectInfo::FinishedUpdatingChannel { .. } => String::from("Finished"), - ProjectInfo::BuildingProject { .. } => String::from("Building"), - ProjectInfo::FinishedBuildingProject { .. } => String::from("Finished"), - } -} +// fn get_info_tag(info: &ProjectInfo) -> String { +// match info { +// ProjectInfo::CreatingProject { .. } => String::from("Creating"), +// ProjectInfo::FinishedCreatingProject { .. } => String::from("Finished"), +// ProjectInfo::AddingChannel { .. } => String::from("Adding"), +// ProjectInfo::FinishedAddingChannel { .. } => String::from("Finished"), +// ProjectInfo::CleaningProject => String::from("Cleaning"), +// ProjectInfo::FinishedCleaningProject { .. } => String::from("Finished"), +// ProjectInfo::ListingChannels => String::from("Listing"), +// ProjectInfo::FinishedListingChannels => String::from("Finished"), +// ProjectInfo::ChannelInfo { .. } => String::from("Channel"), +// ProjectInfo::RemovingChannel { .. } => String::from("Removing"), +// ProjectInfo::FinishedRemovingChannel { .. } => String::from("Finished"), +// ProjectInfo::UpdatingChannel { .. } => String::from("Updating"), +// ProjectInfo::FinishedUpdatingChannel { .. } => String::from("Finished"), +// ProjectInfo::BuildingProject { .. } => String::from("Building"), +// ProjectInfo::FinishedBuildingProject { .. } => String::from("Finished"), +// } +// } -fn get_info_heading(info: &ProjectInfo) -> String { - match info { - ProjectInfo::CreatingProject { name } => format!("project `{}`", name), - ProjectInfo::FinishedCreatingProject { name } => format!("creating project `{}`", name), - ProjectInfo::AddingChannel { name } => format!("channel `{}`", name), - ProjectInfo::FinishedAddingChannel { name } => format!("adding channel `{}`", name), - ProjectInfo::CleaningProject => String::from("cleaning project"), - ProjectInfo::FinishedCleaningProject { - num_files, - file_size, - } => { - format!( - "cleaning project {} files, {} bytes", - num_files, - fit_4(*file_size) - ) - } - ProjectInfo::ListingChannels => String::from("channels"), - ProjectInfo::FinishedListingChannels => String::from("listing channels"), - ProjectInfo::ChannelInfo { name, url, health } => { - let health = if *health { "healthy" } else { "unhealthy" }; - format!("channel `{}`: {} ({})", name, url, health) - } - ProjectInfo::RemovingChannel { name } => format!("channel `{}`", name), - ProjectInfo::FinishedRemovingChannel { name } => format!("removing channel `{}`", name), - ProjectInfo::UpdatingChannel { name } => { - let name = match name { - Some(name) => name, - _ => "all", - }; - format!("updating channel `{}`", name) - } - ProjectInfo::FinishedUpdatingChannel { name } => { - let name = match name { - Some(name) => name, - _ => "all", - }; - format!("updating channel `{}`", name) - } - ProjectInfo::BuildingProject { name } => format!("project `{}`", name), - ProjectInfo::FinishedBuildingProject { name } => format!("building project `{}`", name), - } -} +// fn get_info_heading(info: &ProjectInfo) -> String { +// match info { +// ProjectInfo::CreatingProject { name } => format!("project `{}`", name), +// ProjectInfo::FinishedCreatingProject { name } => format!("creating project `{}`", name), +// ProjectInfo::AddingChannel { name } => format!("channel `{}`", name), +// ProjectInfo::FinishedAddingChannel { name } => format!("adding channel `{}`", name), +// ProjectInfo::CleaningProject => String::from("cleaning project"), +// ProjectInfo::FinishedCleaningProject { +// num_files, +// file_size, +// } => { +// format!( +// "cleaning project {} files, {} bytes", +// num_files, +// fit_4(*file_size) +// ) +// } +// ProjectInfo::ListingChannels => String::from("channels"), +// ProjectInfo::FinishedListingChannels => String::from("listing channels"), +// ProjectInfo::ChannelInfo { name, url, health } => { +// let health = if *health { "healthy" } else { "unhealthy" }; +// format!("channel `{}`: {} ({})", name, url, health) +// } +// ProjectInfo::RemovingChannel { name } => format!("channel `{}`", name), +// ProjectInfo::FinishedRemovingChannel { name } => format!("removing channel `{}`", name), +// ProjectInfo::UpdatingChannel { name } => { +// let name = match name { +// Some(name) => name, +// _ => "all", +// }; +// format!("updating channel `{}`", name) +// } +// ProjectInfo::FinishedUpdatingChannel { name } => { +// let name = match name { +// Some(name) => name, +// _ => "all", +// }; +// format!("updating channel `{}`", name) +// } +// ProjectInfo::BuildingProject { name } => format!("project `{}`", name), +// ProjectInfo::FinishedBuildingProject { name } => format!("building project `{}`", name), +// } +// } -fn get_debug_heading(debug: &ProjectDebug) -> String { - match debug { - ProjectDebug::WritingFile { path, content } => { - let path = path.display().to_string(); - let content = content - .lines() - .map(|line| format!("{} {}", " ".repeat(TAG_WIDTH), line)) - .collect::>() - .join("\n") - .black(); - format!("writing file `{}`\n{}", path, content) - } - ProjectDebug::CreatingDirectory { path } => { - let path = path.display().to_string(); - format!("creating directory `{}`", path) - } - ProjectDebug::RemoveFile { path } => { - let path = path.display().to_string(); - format!("removing file `{}`", path) - } - ProjectDebug::RemoveDir { path } => { - let path = path.display().to_string(); - format!("removing directory `{}`", path) - } - ProjectDebug::BuildingFile { path, file_name } => { - let path = path.display().to_string(); - format!("building file `{}` in `{}`", file_name, path) - } - } -} +// fn get_debug_heading(debug: &ProjectDebug) -> String { +// match debug { +// ProjectDebug::WritingFile { path, content } => { +// let path = path.display().to_string(); +// let content = content +// .lines() +// .map(|line| format!("{} {}", " ".repeat(TAG_WIDTH), line)) +// .collect::>() +// .join("\n") +// .black(); +// format!("writing file `{}`\n{}", path, content) +// } +// ProjectDebug::CreatingDirectory { path } => { +// let path = path.display().to_string(); +// format!("creating directory `{}`", path) +// } +// ProjectDebug::RemoveFile { path } => { +// let path = path.display().to_string(); +// format!("removing file `{}`", path) +// } +// ProjectDebug::RemoveDir { path } => { +// let path = path.display().to_string(); +// format!("removing directory `{}`", path) +// } +// ProjectDebug::BuildingFile { path, file_name } => { +// let path = path.display().to_string(); +// format!("building file `{}` in `{}`", file_name, path) +// } +// } +// }