diff options
Diffstat (limited to 'src/commands')
| -rw-r--r-- | src/commands/build/macros.rs | 74 | ||||
| -rw-r--r-- | src/commands/build/mod.rs | 132 | ||||
| -rw-r--r-- | src/commands/format.rs | 79 | ||||
| -rw-r--r-- | src/commands/init_new.rs | 100 | ||||
| -rw-r--r-- | src/commands/mod.rs | 25 |
5 files changed, 410 insertions, 0 deletions
diff --git a/src/commands/build/macros.rs b/src/commands/build/macros.rs new file mode 100644 index 0000000..4281dff --- /dev/null +++ b/src/commands/build/macros.rs @@ -0,0 +1,74 @@ +// This file is part of Chorus <https://github.com/Fuwn/chorus>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +/// "None if empty string, or with preformat" +/// +/// By default, this macro evaluates whether the input `$string` is empty by +/// calling `is_empty`. However, by specifying the type `i32` at the end of the +/// macro invocation, you can tell this macro to evaluate based on `== 0` +/// instead of `is_empty`. +#[macro_export] +macro_rules! niesowp { + ($preformat:literal, $string:expr) => {{ + if $string.is_empty() { + "".to_string() + } else { + format!("{}{}", $preformat, $string) + } + }}; + ($preformat:literal, $number:expr,i32) => {{ + if $number == 0 { + "".to_string() + } else { + format!("{}{}", $preformat, $number) + } + }}; +} + +#[macro_export] +macro_rules! compiler_get_or { + ($compiler:ident, $key:ident, $default:literal) => { + $crate::config::CONFIG.build.$compiler.as_ref().map_or_else( + || $default.into(), + |compiler| { + compiler + .$key + .as_ref() + .map_or_else(|| $default.into(), Clone::clone) + }, + ) + }; +} + +/// A helper to assist with the sanity checks +#[macro_export] +macro_rules! entry_exists { + ($entries:ident, $path:ident, $entry:literal, $context:literal) => { + let entry = $entry.replace("\\", "/"); + + if !$entries.contains(&PathBuf::from_slash( + format!("{}/{}", $path.as_path().display(), entry).to_string(), + )) { + return Err(anyhow::anyhow!( + "{} \"{}\" does not exist", + $context, + format!("{}/{}", $path.as_path().display(), entry) + )); + } + }; +} diff --git a/src/commands/build/mod.rs b/src/commands/build/mod.rs new file mode 100644 index 0000000..4f22303 --- /dev/null +++ b/src/commands/build/mod.rs @@ -0,0 +1,132 @@ +// This file is part of Chorus <https://github.com/Fuwn/chorus>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +mod macros; + +use std::path::PathBuf; + +use anyhow::{anyhow, Context}; +use path_slash::PathBufExt; +use walkdir::{DirEntry, WalkDir}; + +use crate::{ + compiler_get_or, + compilers::gnucobol, + config::CONFIG, + create_directory, + entry_exists, + niesowp, +}; + +/// Build an existing Chorus package +pub fn build( + path: &Option<PathBuf>, + output: &Option<PathBuf>, +) -> anyhow::Result<()> { + let our_path: PathBuf = path.clone().unwrap_or_else(|| PathBuf::from(".")); + let entries = WalkDir::new(&our_path) + .into_iter() + .filter_map(Result::ok) + .into_iter() + .map(|e: DirEntry| e.path().to_owned()) + .collect::<Vec<PathBuf>>(); + + // Sanity checks to make sure the target directory is a Chorus package + entry_exists!(entries, our_path, "src", "directory"); + entry_exists!(entries, our_path, "src/main.cbl", "file"); + entry_exists!(entries, our_path, "package.toml", "directory"); + + std::env::set_current_dir(&our_path)?; + + let our_output = output.clone().unwrap_or_else(|| { + PathBuf::from(format!("out/{}", CONFIG.package.name).as_str()) + }); + + if !PathBuf::from("out").exists() { + create_directory!("out"); + } + + if !our_output + .parent() + .ok_or_else(|| { + anyhow!( + "parent directory of output directory \"{}\" does not exist", + our_output.display() + ) + })? + .exists() + { + return Err(anyhow!( + "output directory \"{}\" does not exist", + our_output.display() + )); + } + + dispatch_build( + &compiler_get_or!(gnucobol, format, "free"), + &niesowp!("-", compiler_get_or!(gnucobol, optimization, "")), + &niesowp!("-std=", compiler_get_or!(gnucobol, std, "")), + &our_output, + )?; + + Ok(()) +} + +fn dispatch_build( + format: &str, + optimization: &str, + standard: &str, + our_output: &std::path::Path, +) -> anyhow::Result<()> { + if let Some(mode) = &CONFIG.build.mode { + if mode.eq_ignore_ascii_case("incremental") { + if let Err(why) = gnucobol::compile_executable_object( + "cobc", + &format!("-{} {} {}", format, optimization, standard), + "src/main.cbl", + &format!("{}.o", &our_output.display().to_string()), + ) { + return Err(anyhow!(why.to_string())); + } + if let Err(why) = gnucobol::compile_executable_single( + "cobc", + &format!("-{} {} {}", format, optimization, standard), + &format!("{}.o", &our_output.display().to_string()), + &our_output.display().to_string(), + ) { + return Err(anyhow!(why.to_string())); + } + } else if let Err(why) = gnucobol::compile_executable_single( + "cobc", + &format!("-{} {} {}", format, optimization, standard), + "src/main.cbl", + &our_output.display().to_string(), + ) { + return Err(anyhow!(why.to_string())); + } + } else if let Err(why) = gnucobol::compile_executable_single( + "cobc", + &format!("-{} {} {}", format, optimization, standard), + "src/main.cbl", + &our_output.display().to_string(), + ) { + return Err(anyhow!(why.to_string())); + } + + Ok(()) +} diff --git a/src/commands/format.rs b/src/commands/format.rs new file mode 100644 index 0000000..a0aabc4 --- /dev/null +++ b/src/commands/format.rs @@ -0,0 +1,79 @@ +// This file is part of Chorus <https://github.com/Fuwn/chorus>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use std::path::PathBuf; + +use walkdir::{DirEntry, WalkDir}; + +/// Format an existing Chorus package +#[allow(clippy::unnecessary_wraps)] +pub fn format(path: &Option<PathBuf>) -> anyhow::Result<()> { + let our_path = path.clone().unwrap_or_else(|| PathBuf::from(".")); + + // Only try to format if the target Chorus package directory exists + if our_path.exists() { + let mut entries: Vec<DirEntry> = vec![]; + + std::env::set_current_dir(&our_path)?; + + // Iterate over all paths in the target Chorus package + for entry in WalkDir::new(our_path).into_iter().filter_map(Result::ok) { + // Only track COBOL files to format if the file has a valid COBOL extension + if entry + .file_name() + .to_str() + .unwrap_or("") + .rsplit('.') + .next() + .map(|extension: &str| { + #[allow(clippy::blocks_in_if_conditions)] + { + let mut valid_extension = false; + + // If any user defined COBOL extensions exist, check against them; + // otherwise, check against the "cbl" file extension. + if let Some(valid_extensions) = + &crate::config::CONFIG.build.extensions + { + for an_extension in valid_extensions { + if valid_extension { + break; + } + + valid_extension = extension.eq_ignore_ascii_case(an_extension); + } + } else { + valid_extension = extension.eq_ignore_ascii_case("cbl"); + } + + valid_extension + } + }) + == Some(true) + { + entries.push(entry); + } + } + + for entry in entries { + println!("entry: {:?}", entry); + } + } + + Ok(()) +} diff --git a/src/commands/init_new.rs b/src/commands/init_new.rs new file mode 100644 index 0000000..5889b7f --- /dev/null +++ b/src/commands/init_new.rs @@ -0,0 +1,100 @@ +// This file is part of Chorus <https://github.com/Fuwn/chorus>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use std::{io::Write, path::PathBuf}; + +use anyhow::Context; + +use crate::file_generator; + +/// A wrapper of `std::fs::File::create` and `write!` which creates, writes, and +/// error checks a file given a base path and file name. +macro_rules! write_new_file { + ($path:ident, $filepath:expr, $content:expr) => {{ + // Save the file path for later use + let file = + format!("{}{}", format!("{}/", $path.as_path().display()), $filepath); + + // Create and write to the new file with error handling + write!( + &mut std::fs::File::create(&file) + .with_context(|| format!("could not create file: \"{}\"", file))?, + "{}", + $content, + ) + .with_context(|| format!("could not write to file: \"{}\"", file))?; + }}; +} + +#[macro_export] +/// A wrapper of `std::fs::create_dir_all` which creates and error checks a +/// directory given a base path and directory name. +macro_rules! create_directory { + ($path:ident, $directory_path:expr) => {{ + // Save the directory path for later use + let directory = format!( + "{}{}", + format!("{}/", $path.as_path().display()), + $directory_path + ); + + // Create the new directory with error handling + std::fs::create_dir(&directory).with_context(|| { + format!("could not create directory: \"{}\"", directory) + })?; + }}; + ($directory_path:expr) => {{ + // Create the new directory with error handling + std::fs::create_dir(&$directory_path).with_context(|| { + format!("could not create directory: \"{}\"", $directory_path) + })?; + }}; +} + +/// Initialise or create a new Chorus package +pub fn init_new(path: &Option<PathBuf>, init: bool) -> anyhow::Result<()> { + let our_path = path.clone().unwrap_or_else(|| PathBuf::from(".")); + + crate::utilities::is_unicode_xid(&our_path.display().to_string())?; + + // Make sure that we are not initialing a new Chorus project in a non-empty + // directory. + if init && our_path.exists() && our_path.read_dir()?.next().is_some() { + return Err(anyhow::anyhow!( + "target directory \"{}\" is not empty", + our_path.as_path().display() + )); + } + + // Set up the new Chorus package with the default file structure of a Chorus + // package. + create_directory!(our_path, ""); + create_directory!(our_path, "src"); + write_new_file!( + our_path, + "src/main.cbl", + file_generator::main_file(&our_path)? + ); + write_new_file!( + our_path, + "package.toml", + file_generator::package_manifest(&our_path)? + ); + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..c92082c --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,25 @@ +// This file is part of Chorus <https://github.com/Fuwn/chorus>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +pub mod build; +pub mod format; +pub mod init_new; + +pub use build::build; +pub use format::format; +pub use init_new::init_new; |