diff --git a/.deepsource.toml b/.deepsource.toml index 81145d5..e1aa2aa 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -4,5 +4,5 @@ version = 1 name = "rust" enabled = true - [analyzers.meta] - msrv = "stable" \ No newline at end of file +[analyzers.meta] +msrv = "stable" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..03e7092 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +# Copyright © 2023 LibMake. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.rs] +max_line_length = 80 + +[*.md] +# double whitespace at end of line +# denotes a line break in Markdown +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/Cargo.toml b/Cargo.toml index 1fc741e..b5ed214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["Sebastien Rousseau }"] +authors = ["Sebastien Rousseau "] categories = ['development-tools', 'command-line-utilities', 'template-engine'] description = "A code generator to reduce repetitive tasks and build high-quality Rust libraries." edition = "2021" @@ -13,8 +13,8 @@ keywords = [ license = "MIT OR Apache-2.0" name = "libmake" repository = "https://github.com/sebastienrousseau/libmake.git" -rust-version = "1.69.0" -version = "0.1.9" +rust-version = "1.71.1" +version = "0.2.0" include = [ "/CONTRIBUTING.md", "/LICENSE-APACHE", @@ -41,16 +41,19 @@ path = "benches/criterion.rs" debug = true [dependencies] +anyhow = "1.0.75" assert_cmd = "2.0.12" -clap = "4.4.6" +clap = "4.4.7" csv = "1.3.0" +dtt = "0.0.4" figlet-rs = "0.1.5" -openssl = { version = "0.10.57", features = ["vendored"] } +# openssl = { version = "0.10.59", features = ["vendored"] } reqwest = { version = "0.11.22", features = ["blocking"] } -serde = { version = "1.0.188", features = ["derive"] } -serde_json = "1.0.107" -serde_yaml = "0.9.25" -toml = "0.8.2" +serde = { version = "1.0.192", features = ["derive"] } +serde_json = "1.0.108" +serde_yaml = "0.9.27" +toml = "0.8.8" +vrd = "0.0.4" [dev-dependencies] criterion = "0.5.1" diff --git a/README.md b/README.md index 0e253a1..c3d19a8 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,24 @@ -LibMake logo # LibMake -A code generator to reduce repetitive tasks and build high-quality Rust -libraries. +A code generator to reduce repetitive tasks and build high-quality Rust libraries. -*Part of the [Mini Functions][0] family of libraries.* +*Part of the [Mini Functions][00] family of libraries.*
-![Libmake Banner][banner] +![Banner of Libmake][banner] -[![Made With Rust][made-with-rust-badge]][13] -[![Crates.io][crates-badge]][8] [![Lib.rs][libs-badge]][10] -[![Docs.rs][docs-badge]][9] [![License][license-badge]][3] -[![Codecov][codecov-badge]][14] +[![Made With Rust][made-with-rust-badge]][13] [![Crates.io][crates-badge]][08] [![Lib.rs][libs-badge]][10] [![Docs.rs][docs-badge]][09] [![License][license-badge]][03] [![Codecov][codecov-badge]][14] -• [Website][1] • [Documentation][9] • [Report Bug][4] -• [Request Feature][4] • [Contributing Guidelines][5] +• [Website][01] • [Documentation][09] • [Report Bug][04] • [Request Feature][04] • [Contributing Guidelines][05]
@@ -31,49 +26,64 @@ libraries. -## Overview 📖 +![divider][divider] -`LibMake` is a tool designed to quickly help creating high-quality Rust -libraries by generating a set of pre-filled and pre-defined templated -files. This opinionated boilerplate scaffolding tool aims to greatly -reduces development time and minimizes repetitive tasks, allowing you to -focus on your business logic while enforcing standards, best practices, -consistency, and providing style guides for your library. +## Overview -With `LibMake`, you can easily generate a new Rust library code base -structure with all the necessary files, layouts, build configurations, -code, tests, benchmarks, documentation, and much more in a matter of -seconds. +`LibMake` is a tool designed to quickly help creating high-quality Rust libraries by generating a set of pre-filled and pre-defined templated files. This opinionated boilerplate scaffolding tool aims to greatly reduce development time and minimizes repetitive tasks, allowing you to focus on your business logic while enforcing standards, best practices, consistency, and providing style guides for your library. -The library is designed to be used as a command-line tool. It is -available on [Crates.io][7] and [Lib.rs][8]. +With `LibMake`, you can easily generate a new Rust library code base structure with all the necessary files, layouts, build configurations, code, tests, benchmarks, documentation, and much more in a matter of seconds. -## Features ✨ +The library is designed to be used as a command-line tool. It is available on [Crates.io][07] and [Lib.rs][08]. + +## Table of Contents + +- [LibMake](#libmake) + - [Overview](#overview) + - [Table of Contents](#table-of-contents) + - [Features](#features) + - [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Installation](#installation) + - [Requirements](#requirements) + - [Tier 1 platforms](#tier-1-platforms) + - [Tier 2 platforms](#tier-2-platforms) + - [Documentation](#documentation) + - [Usage](#usage) + - [Command-line interface](#command-line-interface) + - [Generate a new library using a CSV file](#generate-a-new-library-using-a-csv-file) + - [Generate a new library using a JSON file](#generate-a-new-library-using-a-json-file) + - [Generate a new library using a TOML file](#generate-a-new-library-using-a-toml-file) + - [Generate a new library using a YAML file](#generate-a-new-library-using-a-yaml-file) + - [Generate a new library using the command-line interface (CLI) directly](#generate-a-new-library-using-the-command-line-interface-cli-directly) + - [Examples](#examples) + - [Semantic Versioning Policy](#semantic-versioning-policy) + - [License](#license) + - [Contribution](#contribution) + - [Acknowledgements](#acknowledgements) + +## Features `LibMake` offers the following features and benefits: -- Create your Rust library with ease using the command line interface or - by providing a configuration file in CSV, JSON, TOML, or YAML format. -- Rapidly generate new library projects with a pre-defined structure and - boilerplate code that you can customize with your own template. -- Generate a library pre-defined GitHub Actions workflow to help you - automate your library development and testing. -- Automatically generate basic functions, methods, and macros to get you - started with your Rust library. -- Enforce best practices and standards with starter documentation, test - suites, and benchmark suites that are designed to help you get up and - running quickly. +- **Simplicity**: Create Rust libraries effortlessly via CLI or configuration files in CSV, JSON, TOML, or YAML. +- **Speed**: Instantly scaffold new libraries with a standard structure and essential boilerplate. +- **Automation**: Generate predefined GitHub Actions workflows for streamlined development and testing. +- **Foundation**: Jumpstart your project with automatically generated functions, methods, and macros. +- **Standards**: Embrace best practices from the start with starter documentation, tests, and benchmarks. + +## Getting Started -## Getting Started 🚀 +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. + +### Prerequisites + +You will need Rust and Cargo installed on your system. If you don't have them installed, you can install them from the official Rust website. It takes just a few seconds to get up and running with `LibMake`. ### Installation -To install `LibMake`, you need to have the Rust toolchain installed on -your machine. You can install the Rust toolchain by following the -instructions on the [Rust website][12]. - Once you have the Rust toolchain installed, you can install `LibMake` using the following command: @@ -89,8 +99,7 @@ libmake --help ### Requirements -The minimum supported Rust toolchain version is currently Rust `1.69.0` -or later (stable). +The minimum supported Rust toolchain version is currently Rust `1.71.1` or later (stable). `LibMake` is supported and has been tested on the following platforms: @@ -126,22 +135,19 @@ The [GitHub Actions][11] shows the platforms in which the `LibMake` library tests are run. Should you encounter any issues with the library on any of the above -platforms, please [report a bug][4]. We will do our best to resolve the +platforms, please [report a bug][04]. We will do our best to resolve the issue as soon as possible. If you would like to contribute to help us to support additional platforms, please submit a pull request. ### Documentation -> ℹ️ **Info:** Do check out our [website][1] for more information. You -can find our documentation on [docs.rs][9], [lib.rs][10] and -[crates.io][8]. +**Info:** Do check out our [website][01] for more information. You can find our documentation on [docs.rs][09], [lib.rs][10] and [crates.io][08]. -## Usage 📖 +## Usage ### Command-line interface -`LibMake` provides a command-line interface to generate a new library -project. There are a few options available to help you get started. +`LibMake` provides a command-line interface to generate a new library project. There are a few options available to help you get started. #### Generate a new library using a CSV file @@ -232,7 +238,7 @@ libmake \ --output "my_library" \ --readme "README.md" \ --repository "https://github.com/example/my_library" \ - --rustversion "1.69.0" \ + --rustversion "1.71.1" \ --version "0.1.0" \ --website "https://example.com/john-smith" ``` @@ -254,18 +260,16 @@ cargo run -- --author "John Smith" \ --output "my_library" \ --readme "README.md" \ --repository "https://github.com/example/my_library" \ - --rustversion "1.69.0" \ + --rustversion "1.71.1" \ --version "0.1.0" \ --website "https://example.com/john-smith" ``` ### Examples -To get started with `LibMake`, you can use the examples provided in the -`examples` directory of the project. +To get started with `LibMake`, you can use the examples provided in the `examples` directory of the project. -To run the examples, clone the repository and run the following command -in your terminal from the project root directory. +To run the examples, clone the repository and run the following command in your terminal from the project root directory. | Example | Description | Command | |---------|-------------|---------| @@ -276,23 +280,23 @@ in your terminal from the project root directory. | `generate_from_toml` | Generates a library template from a TOML file. | `cargo run --example generate_from_toml` | | `generate_from_yaml` | Generates a library template from a YAML file. | `cargo run --example generate_from_yaml` | -## Semantic Versioning Policy 🚥 +## Semantic Versioning Policy For transparency into our release cycle and in striving to maintain -backward compatibility, `libmake` follows [semantic versioning][7]. +backward compatibility, `libmake` follows [semantic versioning][07]. -## License 📝 +## License The project is licensed under the terms of both the MIT license and the Apache License (Version 2.0). -- [Apache License, Version 2.0][2] -- [MIT license][3] +- [Apache License, Version 2.0][02] +- [MIT license][03] -## Contribution 🤝 +## Contribution We welcome all people who want to contribute. Please see the -[contributing instructions][5] for more information. +[contributing instructions][05] for more information. Contributions in any form (issues, pull requests, etc.) to this project must adhere to the [Rust's Code of Conduct][12]. @@ -302,23 +306,22 @@ submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -## Acknowledgements 💙 - -A big thank you to all the awesome contributors of [libmake][6] for -their help and support. A special thank you goes to the -[Rust Reddit][15] community for providing a lot of useful suggestions on how to improve -this project. - -[0]: https://minifunctions.com/libmake "Mini Functions" -[1]: https://libmake.com "LibMake" -[2]: https://opensource.org/license/apache-2-0/ "Apache License, Version 2.0" -[3]: http://opensource.org/licenses/MIT "MIT license" -[4]: https://github.com/sebastienrousseau/libmake/issues "Issues" -[5]: https://github.com/sebastienrousseau/libmake/blob/main/CONTRIBUTING.md "Contributing" -[6]: https://github.com/sebastienrousseau/libmake/graphs/contributors "Contributors" -[7]: http://semver.org/ "Semantic Versioning" -[8]: https://crates.io/crates/libmake "LibMake on crates.io" -[9]: https://docs.rs/libmake "LibMake on docs.rs" +## Acknowledgements + +A big thank you to all the awesome contributors of [libmake][06] for their help +and support. A special thank you goes to the [Rust Reddit][15] community for +providing a lot of useful suggestions on how to improve this project. + +[00]: https://minifunctions.com/libmake "Mini Functions" +[01]: https://libmake.com "LibMake" +[02]: https://opensource.org/license/apache-2-0/ "Apache License, Version 2.0" +[03]: http://opensource.org/licenses/MIT "MIT license" +[04]: https://github.com/sebastienrousseau/libmake/issues "Issues" +[05]: https://github.com/sebastienrousseau/libmake/blob/main/CONTRIBUTING.md "Contributing" +[06]: https://github.com/sebastienrousseau/libmake/graphs/contributors "Contributors" +[07]: http://semver.org/ "Semantic Versioning" +[08]: https://crates.io/crates/libmake "LibMake on crates.io" +[09]: https://docs.rs/libmake "LibMake on docs.rs" [10]: https://lib.rs/crates/libmake "LibMake on lib.rs" [11]: https://github.com/sebastienrousseau/libmake/actions "GitHub Actions" [12]: https://www.rust-lang.org/policies/code-of-conduct "Rust's Code of Conduct" @@ -327,9 +330,10 @@ this project. [15]: https://www.reddit.com/r/rust/ "Rust Reddit" [banner]: https://kura.pro/libmake/images/titles/title-libmake.svg "LibMake Banner" -[codecov-badge]: https://img.shields.io/codecov/c/github/sebastienrousseau/libmake?style=for-the-badge&token=Q9KJ6XXL67 'Codecov' -[crates-badge]: https://img.shields.io/crates/v/libmake.svg?style=for-the-badge 'Crates.io Badge' -[docs-badge]: https://img.shields.io/docsrs/libmake.svg?style=for-the-badge 'Docs.rs Badge' -[libs-badge]: https://img.shields.io/badge/lib.rs-v0.1.9-orange.svg?style=for-the-badge 'Lib.rs Badge' -[license-badge]: https://img.shields.io/crates/l/libmake.svg?style=for-the-badge 'License Badge' -[made-with-rust-badge]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust 'Made With Rust Badge' +[codecov-badge]: https://img.shields.io/codecov/c/github/sebastienrousseau/libmake?style=for-the-badge&token=Q9KJ6XXL67 "Codecov Badge" +[crates-badge]: https://img.shields.io/crates/v/libmake.svg?style=for-the-badge "Crates.io Badge" +[divider]: https://kura.pro/common/images/elements/divider.svg "divider" +[docs-badge]: https://img.shields.io/docsrs/libmake.svg?style=for-the-badge "Docs.rs Badge" +[libs-badge]: https://img.shields.io/badge/lib.rs-v0.2.0-orange.svg?style=for-the-badge "Lib.rs Badge" +[license-badge]: https://img.shields.io/crates/l/libmake.svg?style=for-the-badge "License Badge" +[made-with-rust-badge]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust "Made With Rust Badge" diff --git a/TEMPLATE.md b/TEMPLATE.md index 50eb0b5..0507c05 100644 --- a/TEMPLATE.md +++ b/TEMPLATE.md @@ -4,11 +4,11 @@ alt="LibMake logo" width="261" align="right" /> -# LibMake v0.1.9 🦀 +# LibMake v0.2.0 🦀 A code generator to reduce repetitive tasks and build high-quality Rust libraries. -*Part of the [Mini Functions][0] family of libraries.* +*Part of the [Mini Functions][00] family of libraries.*
@@ -16,10 +16,10 @@ A code generator to reduce repetitive tasks and build high-quality Rust librarie ![Libmake Banner][banner] -[![Made With Rust][made-with-rust-badge]][13] [![Crates.io][crates-badge]][8] [![Lib.rs][libs-badge]][10] [![Docs.rs][docs-badge]][9] -[![License][license-badge]][3] [![Codecov][codecov-badge]][14] +[![Made With Rust][made-with-rust-badge]][09] [![Crates.io][crates-badge]][06] [![Lib.rs][libs-badge]][08] [![Docs.rs][docs-badge]][07] +[![License][license-badge]][02] [![Codecov][codecov-badge]][10] -• [Website][1] • [Documentation][9] • [Report Bug][4] • [Request Feature][4] • [Contributing Guidelines][5] +• [Website][01] • [Documentation][07] • [Report Bug][03] • [Request Feature][03] • [Contributing Guidelines][04]
@@ -33,7 +33,7 @@ A code generator to reduce repetitive tasks and build high-quality Rust librarie With `LibMake`, you can easily generate a new Rust library code base structure with all the necessary files, layouts, build configurations, code, tests, benchmarks, documentation, and much more in a matter of seconds. -The library is designed to be used as a command-line tool. It is available on [Crates.io][7] and [Lib.rs][8]. +The library is designed to be used as a command-line tool. It is available on [Crates.io][05] and [Lib.rs][06]. ## Features ✨ @@ -47,22 +47,22 @@ The library is designed to be used as a command-line tool. It is available on [C ## Changelog 📚 -[0]: https://minifunctions.com/libmake "Mini Functions" -[1]: https://libmake.com "LibMake" -[3]: http://opensource.org/licenses/MIT "MIT license" -[4]: https://github.com/sebastienrousseau/libmake/issues "Issues" -[5]: https://github.com/sebastienrousseau/libmake/blob/main/CONTRIBUTING.md "Contributing" -[7]: http://semver.org/ "Semantic Versioning" -[8]: https://crates.io/crates/libmake "LibMake on crates.io" -[9]: https://docs.rs/libmake "LibMake on docs.rs" -[10]: https://lib.rs/crates/libmake "LibMake on lib.rs" -[13]: https://www.rust-lang.org "The Rust Programming Language" -[14]: https://codecov.io/gh/sebastienrousseau/libmake "Codecov" +[00]: https://minifunctions.com/libmake "Mini Functions" +[01]: https://libmake.com "LibMake" +[02]: http://opensource.org/licenses/MIT "MIT license" +[03]: https://github.com/sebastienrousseau/libmake/issues "Issues" +[04]: https://github.com/sebastienrousseau/libmake/blob/main/CONTRIBUTING.md "Contributing" +[05]: http://semver.org/ "Semantic Versioning" +[06]: https://crates.io/crates/libmake "LibMake on Crates.io" +[07]: https://docs.rs/libmake "LibMake on docs.rs" +[08]: https://lib.rs/crates/libmake "LibMake on lib.rs" +[09]: https://www.rust-lang.org "The Rust Programming Language" +[10]: https://codecov.io/gh/sebastienrousseau/libmake "Codecov" [banner]: https://kura.pro/libmake/images/titles/title-libmake.svg "LibMake Banner" -[codecov-badge]: https://img.shields.io/codecov/c/github/sebastienrousseau/libmake?style=for-the-badge&token=Q9KJ6XXL67 'Codecov' -[crates-badge]: https://img.shields.io/crates/v/libmake.svg?style=for-the-badge 'Crates.io Badge' -[docs-badge]: https://img.shields.io/docsrs/libmake.svg?style=for-the-badge 'Docs.rs Badge' -[libs-badge]: https://img.shields.io/badge/lib.rs-v0.1.9-orange.svg?style=for-the-badge 'Lib.rs Badge' -[license-badge]: https://img.shields.io/crates/l/libmake.svg?style=for-the-badge 'License Badge' -[made-with-rust-badge]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust 'Made With Rust Badge' +[codecov-badge]: https://img.shields.io/codecov/c/github/sebastienrousseau/libmake?style=for-the-badge&token=Q9KJ6XXL67 "Codecov Badge" +[crates-badge]: https://img.shields.io/crates/v/libmake.svg?style=for-the-badge "Crates.io Badge" +[docs-badge]: https://img.shields.io/docsrs/libmake.svg?style=for-the-badge "Docs.rs Badge" +[libs-badge]: https://img.shields.io/badge/lib.rs-v0.2.0-orange.svg?style=for-the-badge "Lib.rs Badge" +[license-badge]: https://img.shields.io/crates/l/libmake.svg?style=for-the-badge "License Badge" +[made-with-rust-badge]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust "Made With Rust Badge" diff --git a/build.rs b/build.rs index ca595bf..0bb867a 100644 --- a/build.rs +++ b/build.rs @@ -7,4 +7,4 @@ fn main() { // Avoid unnecessary re-building. println!("cargo:rerun-if-changed=build.rs"); -} \ No newline at end of file +} diff --git a/src/args.rs b/src/args.rs index 876dd33..8f99bfd 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,15 +1,15 @@ + // Copyright © 2023 LibMake. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR MIT use clap::ArgMatches; - use crate::generator::{ generate_from_json, generate_from_toml, generate_from_yaml, }; - use super::generator::{ generate_files, generate_from_csv, FileGenerationParams, }; +use std::error::Error; /// Processes the command line arguments provided to the program. /// @@ -18,72 +18,63 @@ use super::generator::{ /// * `matches` - An instance of `clap::ArgMatches` containing the /// parsed command line arguments. /// -pub fn process_arguments(matches: &ArgMatches) { +pub fn process_arguments(matches: &ArgMatches) -> Result<(), Box> { // Extracting optional argument values from the parsed matches. - let author = matches.get_one::("author"); - let build = matches.get_one::("build"); - let categories = matches.get_one::("categories"); - let description = matches.get_one::("description"); - let documentation = matches.get_one::("documentation"); - let edition = matches.get_one::("edition"); - let email = matches.get_one::("email"); - let homepage = matches.get_one::("homepage"); - let keywords = matches.get_one::("keywords"); - let license = matches.get_one::("license"); - let name = matches.get_one::("name"); - let output = matches.get_one::("output"); - let readme = matches.get_one::("readme"); - let repository = matches.get_one::("repository"); - let rustversion = matches.get_one::("rustversion"); - let version = matches.get_one::("version"); - let website = matches.get_one::("website"); + let author = matches.get_one::("author").cloned(); + let build = matches.get_one::("build").cloned(); + let categories = matches.get_one::("categories").cloned(); + let description = matches.get_one::("description").cloned(); + let documentation = matches.get_one::("documentation").cloned(); + let edition = matches.get_one::("edition").cloned(); + let email = matches.get_one::("email").cloned(); + let homepage = matches.get_one::("homepage").cloned(); + let keywords = matches.get_one::("keywords").cloned(); + let license = matches.get_one::("license").cloned(); + let name = matches.get_one::("name").cloned(); + let output = matches.get_one::("output").cloned(); + let readme = matches.get_one::("readme").cloned(); + let repository = matches.get_one::("repository").cloned(); + let rustversion = matches.get_one::("rustversion").cloned(); + let version = matches.get_one::("version").cloned(); + let website = matches.get_one::("website").cloned(); // Check which subcommand was used and perform the corresponding action. if matches.contains_id("csv") { let csv_file_path = matches.get_one::("csv").unwrap(); - generate_from_csv(csv_file_path) - .expect("Failed to generate the template files"); - } else if let Some(yaml_file_path) = - matches.get_one::("yml") - { - generate_from_yaml(yaml_file_path) - .expect("Failed to generate the template files"); - } else if let Some(json_file_path) = - matches.get_one::("json") - { - generate_from_json(json_file_path) - .expect("Failed to generate the template files"); - } else if let Some(toml_file_path) = - matches.get_one::("toml") - { - generate_from_toml(toml_file_path) - .expect("Failed to generate the template files"); + generate_from_csv(csv_file_path)?; + } else if let Some(yaml_file_path) = matches.get_one::("yml") { + generate_from_yaml(yaml_file_path)?; + } else if let Some(json_file_path) = matches.get_one::("json") { + generate_from_json(json_file_path)?; + } else if let Some(toml_file_path) = matches.get_one::("toml") { + generate_from_toml(toml_file_path)?; } else if !matches.args_present() { // If no subcommand is used and there are additional arguments, // create a parameter struct and generate files. let params = FileGenerationParams { - author: author.cloned(), - build: build.cloned(), - categories: categories.cloned(), - description: description.cloned(), - documentation: documentation.cloned(), - edition: edition.cloned(), - email: email.cloned(), - homepage: homepage.cloned(), - keywords: keywords.cloned(), - license: license.cloned(), - name: name.cloned(), - output: output.cloned(), - readme: readme.cloned(), - repository: repository.cloned(), - rustversion: rustversion.cloned(), - version: version.cloned(), - website: website.cloned(), + author, + build, + categories, + description, + documentation, + edition, + email, + homepage, + keywords, + license, + name, + output, + readme, + repository, + rustversion, + version, + website, }; - generate_files(params) - .expect("Failed to generate the template files"); + generate_files(params)?; println!("\n\nTemplate files generated successfully!"); } else { println!("❌ No arguments provided. Please provide the required arguments to generate the template files."); } + + Ok(()) } diff --git a/src/ascii.rs b/src/ascii.rs index 4f35da9..1c7b57f 100644 --- a/src/ascii.rs +++ b/src/ascii.rs @@ -2,24 +2,42 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use figlet_rs::FIGfont; +use std::fmt; +use std::error::Error; -/// Generates ASCII art from the given text using the specified FIGfont -/// file. +/// Error type for ASCII art generation failures. +#[derive(Debug)] +pub enum AsciiArtError { + /// Represents a failure to load the FIGfont. + FontLoadError, + /// Represents a failure to convert text to ASCII art. + ConversionError, +} + +impl fmt::Display for AsciiArtError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + AsciiArtError::FontLoadError => write!(f, "Failed to load FIGfont"), + AsciiArtError::ConversionError => write!(f, "Failed to convert text to ASCII art"), + } + } +} + +impl Error for AsciiArtError {} + +/// Generates ASCII art from the given text using the standard FIGfont. /// /// # Arguments /// -/// - `text` - The text to convert to ASCII art in the form of a string. -/// - `font_file` - The path to the FIGfont file to use for the -/// conversion in the form of a string. +/// * `text` - The text to convert to ASCII art. /// -/// # Panics +/// # Returns /// -/// This function panics if the FIGfont file cannot be loaded or if the -/// conversion from text to ASCII art fails. +/// This function returns a `Result` with the ASCII art as `String` or +/// an `AsciiArtError` if the operation fails. /// -pub fn generate_ascii_art(text: &str) { - let standard_font = FIGfont::standard().unwrap(); - let figure = standard_font.convert(text); - assert!(figure.is_some()); - println!("{}", figure.unwrap()); +pub fn generate_ascii_art(text: &str) -> Result { + let standard_font = FIGfont::standard().map_err(|_| AsciiArtError::FontLoadError)?; + let figure = standard_font.convert(text).ok_or(AsciiArtError::ConversionError)?; + Ok(figure.to_string()) } diff --git a/src/cli.rs b/src/cli.rs index 30c1bc8..5d42c9a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3,24 +3,15 @@ use clap::{Arg, ArgMatches, Command, Error}; -/// Builds and returns a set of command-line arguments using the Clap -/// library. +/// Constructs the CLI for the application using Clap, including all necessary arguments. /// -/// # Arguments -/// -/// None -/// -/// # Returns -/// -/// * `Result` - A struct containing the parsed -/// command-line arguments and their values, or an error if the -/// arguments could not be parsed. +/// Returns a `Result` containing the `ArgMatches` if successful, or an `Error` if parsing fails. /// /// # Examples /// /// ``` /// use libmake::cli; -/// let matches = cli::build_cli().unwrap(); +/// let matches = cli::build_cli().expect("CLI parsing failed"); /// ``` pub fn build_cli() -> Result { let matches = Command::new("My Library") @@ -142,7 +133,7 @@ pub fn build_cli() -> Result { ) .arg( Arg::new("rustversion") - .default_value("1.69.0") + .default_value("1.71.1") .help("Sets the Rust version of the library") .long("rustversion") .short('r') @@ -150,7 +141,7 @@ pub fn build_cli() -> Result { ) .arg( Arg::new("version") - .default_value("0.1.9") + .default_value("0.2.0") .help("Sets the version of the library") .long("version") .short('v') diff --git a/src/generator.rs b/src/generator.rs index c1cb19d..7e89671 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,9 +1,7 @@ // Copyright © 2023 LibMake. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::interface::replace_placeholders; -use reqwest::blocking::get; -// use assert_cmd::Command; +use super::interface::replace_placeholders; use serde::{Deserialize, Serialize}; use serde_json; use serde_yaml; @@ -16,13 +14,11 @@ use std::{ /// /// # Description /// -/// * The parameters are optional, but the output directory is required. -/// * The output directory is the directory where the project files will be created. +/// * The `output` directory is the directory where the project files will be created. /// * The other parameters are optional and will be used to replace the placeholders in the template files. /// * The template files are located in the template directory of the project. /// * The template files are copied to the output directory and the placeholders are replaced with the values of the parameters. /// -/// #[derive( Clone, Debug, @@ -58,7 +54,7 @@ pub struct FileGenerationParams { pub license: Option, /// The name of the project (optional). pub name: Option, - /// The output directory where the project files will be created (required). + /// The output directory where the project files will be created (optional). pub output: Option, /// The name of the readme file (optional). pub readme: Option, @@ -73,7 +69,8 @@ pub struct FileGenerationParams { } impl FileGenerationParams { - /// Creates a default instance with default values. + /// Creates a default instance with default values for all fields. + /// Fields that are truly optional without a default are initialized as `None`. pub fn default_params() -> Self { Self { author: Some("John Smith".to_string()), @@ -101,7 +98,7 @@ impl FileGenerationParams { repository: Some( "https://github.com/example/my_library".to_string(), ), - rustversion: Some("1.69.0".to_string()), + rustversion: Some("1.71.1".to_string()), version: Some("0.1.0".to_string()), website: Some("https://example.com/john-smith".to_string()), } @@ -211,34 +208,42 @@ pub fn create_template_folder() -> io::Result<()> { "example.tpl", "gitignore.tpl", "lib.tpl", + "loggers.tpl", "macros.tpl", "main.tpl", "README.tpl", "rustfmt.tpl", "TEMPLATE.tpl", "test.tpl", + "test_loggers.tpl" ]; for file in &files { - let file_url = format!("{}{}", url, file); let file_path = template_dir_path.join(file); - let response = get(&file_url).map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("Failed to download template file: {}", e), - ) - })?; - let file_contents = response.text().map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("Failed to read response body: {}", e), - ) - })?; - std::fs::write( - &file_path, - file_contents - .trim_start_matches('\n') - .trim_end_matches('\n'), - )?; + // Check if the file already exists + if !file_path.exists() { + let file_url = format!("{}{}", url, file); + let response = reqwest::blocking::get(&file_url).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("Failed to download template file: {}", e), + ) + })?; + + let file_contents = response.text().map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("Failed to read response body: {}", e), + ) + })?; + + // Write the file contents, trimming any leading or trailing newline characters + std::fs::write( + &file_path, + file_contents + .trim_start_matches('\n') + .trim_end_matches('\n'), + )?; + } } Ok(()) } @@ -323,151 +328,53 @@ pub fn generate_files(params: FileGenerationParams) -> io::Result<()> { // Creating the template directory create_template_folder()?; - // Creating the src directory - let src_directory = project_directory.join("src"); - create_directory(&src_directory)?; - - // Creating the benches directory - let benches_directory = project_directory.join("benches"); - create_directory(&benches_directory)?; - - // Creating the examples directory - let examples_directory = project_directory.join("examples"); - create_directory(&examples_directory)?; - - // Creating the tests directory - let tests_directory = project_directory.join("tests"); - create_directory(&tests_directory)?; - - // Creating the .github directory - let github_directory = project_directory.join(".github"); - create_directory(&github_directory)?; + // Define the subdirectories to be created within the project directory + let subdirectories = [ + "src", + "benches", + "examples", + "tests", + ".github/", + ".github/workflows", + ]; - // Creating the .github/workflows directory - let workflows_directory = github_directory.join("workflows"); - create_directory(&workflows_directory)?; + // Iterate over the subdirectories and create them + for subdir in &subdirectories { + let dir_path = project_directory.join(subdir); + create_directory(&dir_path)?; + } // Copying the template files to the new library directory + let templates = [ + ("AUTHORS.tpl", "AUTHORS.md"), + ("build.tpl", "build.rs"), + ("Cargo.tpl", "Cargo.toml"), + ("ci.tpl", ".github/workflows/ci.yml"), + ("CONTRIBUTING.tpl", "CONTRIBUTING.md"), + ("criterion.tpl", "benches/criterion.rs"), + ("deepsource.tpl", ".deepsource.toml"), + ("deny.tpl", "deny.toml"), + ("example.tpl", "examples/example.rs"), + ("gitignore.tpl", ".gitignore"), + ("lib.tpl", "src/lib.rs"), + ("loggers.tpl", "src/loggers.rs"), + ("macros.tpl", "src/macros.rs"), + ("main.tpl", "src/main.rs"), + ("README.tpl", "README.md"), + ("rustfmt.tpl", "rustfmt.toml"), + ("TEMPLATE.tpl", "TEMPLATE.md"), + ("test_loggers.tpl", "tests/test_loggers.rs"), + ("test.tpl", "tests/test.rs"), + ]; - // Copying the `AUTHORS.tpl` file to the new library directory - copy_and_replace_template( - "AUTHORS.tpl", - "AUTHORS.md", - &project_directory, - ¶ms, - )?; - // Copying the `build.tpl` file to the new library directory - copy_and_replace_template( - "build.tpl", - "build.rs", - &project_directory, - ¶ms, - )?; - // Copying the `Cargo.tpl` file to the new library directory - copy_and_replace_template( - "Cargo.tpl", - "Cargo.toml", - &project_directory, - ¶ms, - )?; - // Copying the `ci.tpl` file to the new library directory - copy_and_replace_template( - "ci.tpl", - ".github/workflows/ci.yml", - &project_directory, - ¶ms, - )?; - // Copying the `CONTRIBUTING.tpl` file to the new library directory - copy_and_replace_template( - "CONTRIBUTING.tpl", - "CONTRIBUTING.md", - &project_directory, - ¶ms, - )?; - // Copying the `criterion.tpl` file to the new library directory - copy_and_replace_template( - "criterion.tpl", - "benches/criterion.rs", - &project_directory, - ¶ms, - )?; - // Copying the `deepsource.tpl` file to the new library directory - copy_and_replace_template( - "deepsource.tpl", - ".deepsource.toml", - &project_directory, - ¶ms, - )?; - // Copying the `deny.tpl` file to the new library directory - copy_and_replace_template( - "deny.tpl", - "deny.toml", - &project_directory, - ¶ms, - )?; - // Copying the `example.tpl` file to the new library directory - copy_and_replace_template( - "example.tpl", - "examples/example.rs", - &project_directory, - ¶ms, - )?; - // Copying the `gitignore.tpl` file to the new library directory - copy_and_replace_template( - "gitignore.tpl", - ".gitignore", - &project_directory, - ¶ms, - )?; - // Copying the `lib.tpl` file to the new library directory - copy_and_replace_template( - "lib.tpl", - "src/lib.rs", - &project_directory, - ¶ms, - )?; - // Copying the `macros.tpl` file to the new library directory - copy_and_replace_template( - "macros.tpl", - "src/macros.rs", - &project_directory, - ¶ms, - )?; - // Copying the `main.tpl` file to the new library directory - copy_and_replace_template( - "main.tpl", - "src/main.rs", - &project_directory, - ¶ms, - )?; - // Copying the `README.tpl` file to the new library directory - copy_and_replace_template( - "README.tpl", - "README.md", - &project_directory, - ¶ms, - )?; - // Copying the `rustfmt.tpl` file to the new library directory - copy_and_replace_template( - "rustfmt.tpl", - "rustfmt.toml", - &project_directory, - ¶ms, - )?; - // Copying the `TEMPLATE.tpl` file to the new library directory - copy_and_replace_template( - "TEMPLATE.tpl", - "TEMPLATE.md", - &project_directory, - ¶ms, - )?; - // Copying the `test.tpl` file to the new library directory - copy_and_replace_template( - "test.tpl", - "tests/test.rs", - &project_directory, - ¶ms, - )?; + for (template, target) in templates { + copy_and_replace_template( + template, + target, + &project_directory, + ¶ms, + )?; + } // Displaying the argument and value pairs println!("{:<15}Value", "Argument"); diff --git a/src/lib.rs b/src/lib.rs index 3e0c020..1efc2a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ -// Copyright © 2023 LibMake. All rights reserved. +// Copyright © 2023 xtasks. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR MIT +//! # LibMake //! -//! A code generator to reduce repetitive tasks and build high-quality Rust -//! libraries. +//! A code generator to reduce repetitive tasks and build high-quality Rust libraries. //! //! *Part of the [Mini Functions][0] family of libraries.* //! @@ -13,7 +13,7 @@ //! //! [![Rust](https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust)](https://www.rust-lang.org) //! [![Crates.io](https://img.shields.io/crates/v/libmake.svg?style=for-the-badge&color=success&labelColor=27A006)](https://crates.io/crates/libmake) -//! [![Lib.rs](https://img.shields.io/badge/lib.rs-v0.1.9-success.svg?style=for-the-badge&color=8A48FF&labelColor=6F36E4)](https://lib.rs/crates/libmake) +//! [![Lib.rs](https://img.shields.io/badge/lib.rs-v0.2.0-success.svg?style=for-the-badge&color=8A48FF&labelColor=6F36E4)](https://lib.rs/crates/libmake) //! [![GitHub](https://img.shields.io/badge/github-555555?style=for-the-badge&labelColor=000000&logo=github)](https://github.com/sebastienrousseau/libmake) //! [![License](https://img.shields.io/crates/l/libmake.svg?style=for-the-badge&color=007EC6&labelColor=03589B)](http://opensource.org/licenses/MIT) //! @@ -60,18 +60,19 @@ //! [`serde`]: https://github.com/serde-rs/serde //! [0]: https://minifunctions.com/libmake "Mini Functions" //! +#![allow(clippy::must_use_candidate)] #![cfg_attr(feature = "bench", feature(test))] #![deny(dead_code)] #![deny(rustc::existing_doc_keyword)] -#![forbid(missing_debug_implementations)] -#![forbid(missing_docs)] -#![forbid(unreachable_pub)] -#![forbid(unsafe_code)] #![doc( html_favicon_url = "https://kura.pro/libmake/images/favicon.ico", html_logo_url = "https://kura.pro/libmake/images/logos/libmake.svg", html_root_url = "https://docs.rs/libmake" )] +#![forbid(missing_debug_implementations)] +#![forbid(missing_docs)] +#![forbid(unreachable_pub)] +#![forbid(unsafe_code)] #![crate_name = "libmake"] #![crate_type = "lib"] @@ -89,36 +90,58 @@ pub mod generator; /// The `interface` module contains functions for displaying the /// interface. pub mod interface; +/// The `loggers` module contains the loggers for the library. +pub mod loggers; /// The `macros` module contains functions for generating macros. pub mod macros; /// The `utils` module contains a function for reading a CSV file at the /// given file path and returns the value of the given field. pub mod utils; -/// Initializes the logger with a file logger and a terminal logger. +use std::error::Error; +use crate::ascii::generate_ascii_art; + +/// Initializes the logger with a file logger and a terminal logger and processes +/// command-line arguments to generate the new library. /// /// # Examples /// /// ``` /// use libmake::run; -/// run(); +/// +/// if let Err(e) = run() { +/// eprintln!("Application error: {}", e); +/// } /// ``` -pub fn run() -> Result<(), Box> { - // Process the ascii art - ascii::generate_ascii_art("LibMake"); - - // Process the command-line arguments - // let args: Vec = env::args().collect(); - // println!("{:?}", args); +pub fn run() -> Result<(), Box> { + // Generate ASCII art for the tool's CLI + macro_log_info!( + LogLevel::INFO, + "deps", + "Starting generating ASCII art for the tool's CLI...", + LogFormat::CLF + ); + match generate_ascii_art("LibMake") { + Ok(ascii_art) => println!("{}", ascii_art), + Err(e) => eprintln!("Error generating ASCII art: {}", e), + } + macro_log_info!( + LogLevel::INFO, + "deps", + "Finished generating ASCII art for the tool's CLI.", + LogFormat::CLF + ); + // Build the command-line interface and process the arguments let matches = cli::build_cli()?; - args::process_arguments(&matches); + args::process_arguments(&matches)?; - // Print the welcome message if no arguments were passed + // Check the number of arguments, provide a welcome message if no arguments were passed if std::env::args().len() == 1 { eprintln!( "\n\nWelcome to LibMake! 👋\n\nLet's get started! Please, run `libmake --help` for more information.\n" ); } + Ok(()) } diff --git a/src/loggers.rs b/src/loggers.rs new file mode 100644 index 0000000..9163eb4 --- /dev/null +++ b/src/loggers.rs @@ -0,0 +1,243 @@ +// Copyright © 2023 LibMake. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// Standard library imports for formatting and I/O operations. +use std::{ + fmt, + fs::OpenOptions, + io::{self, Write as IoWrite}, +}; + +/// Enum representing the different log formats that can be used. +/// +/// This enum allows the developer to specify the format in which log messages should be displayed. +/// +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd)] +pub enum LogFormat { + /// The log format is set to the Common Log Format (CLF) + CLF, + /// The log format is set to the JSON format + JSON, + /// The log format is set to the Common Event Format (CEF) + CEF, + /// The log format is set to the Extended Log Format (ELF) + ELF, + /// The log format is set to the W3C Extended Log File Format + W3C, + /// The log format is set to the Graylog Extended Log Format (GELF) + GELF, +} + +/// Implements Display trait for `LogFormat` enum. +/// +/// This allows easy conversion of the log format enums to strings. +impl fmt::Display for LogFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{self:?}") + } +} + +/// An enumeration of the different levels that a log message can have. +/// Each variant of the enumeration represents a different level of +/// importance. +/// +/// # Arguments +/// +/// * `ALL` - The log level is set to all. +/// * `DEBUG` - The log level is set to debug. +/// * `DISABLED` - The log level is set to disabled. +/// * `ERROR` - The log level is set to error. +/// * `FATAL` - The log level is set to fatal. +/// * `INFO` - The log level is set to info. +/// * `NONE` - The log level is set to none. +/// * `TRACE` - The log level is set to trace. +/// * `VERBOSE` - The log level is set to verbose. +/// * `WARNING` - The log level is set to warning. +/// +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd)] +pub enum LogLevel { + /// The log level is set to all. + ALL, + /// The log level is set to debug. + DEBUG, + /// The log level is set to disabled. + DISABLED, + /// The log level is set to error. + ERROR, + /// The log level is set to fatal. + FATAL, + /// The log level is set to info. + INFO, + /// The log level is set to none. + NONE, + /// The log level is set to trace. + TRACE, + /// The log level is set to verbose. + VERBOSE, + /// The log level is set to warning. + WARNING, +} +/// Display trait implementation for `LogLevel`. +/// +/// This converts the enum to a string representation. +impl fmt::Display for LogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +/// Struct representing a log message. +/// +/// Contains all the elements that make up a complete log message. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct Log { + /// A string that holds a session ID. The session ID is a unique + /// identifier for the current session. A random GUID (Globally + /// Unique Identifier) is generated by default. + pub session_id: String, + /// A string that holds the timestamp in ISO 8601 format. + pub time: String, + /// A string that holds the level (INFO, WARN, ERROR, etc.). + pub level: LogLevel, + /// A string that holds the component name. + pub component: String, + /// A string that holds the description of the log message. + pub description: String, + /// A string that holds the log format. + pub format: LogFormat, +} + +impl Log { + /// Logs a message to the console using a pre-allocated buffer to + /// reduce memory allocation and flush the output buffer to ensure + /// that the message is written immediately. + /// + /// # Errors + /// + /// This function will panic if an error occurs when writing to the + /// pre-allocated buffer or flushing the output buffer. + pub fn log(&self) -> io::Result<()> { + // Open the file in append mode. If the file does not exist, create it. + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open("libmake.log")?; + match self.format { + LogFormat::CLF => { + writeln!( + file, + "SessionID={}\tTimestamp={}\tDescription={}\tLevel={}\tComponent={}\tFormat={}", + self.session_id, + self.time, + self.description, + self.level, + self.component, + self.format + ) + } + LogFormat::JSON => { + writeln!( + file, + r#"{{"session_id": "{}", "timestamp": "{}", "description": "{}", "level": "{}", "component": "{}", "format": "{}"}}"#, + self.session_id, + self.time, + self.description, + self.level, + self.component, + self.format + ) + } + LogFormat::CEF => { + writeln!( + file, + r#"[CEF] + + 1 + LibMake + Application + {} + Log + {} + {} + {} + localhost + localhost + - + - + - + - + - + - + + "#, + self.time, + self.level, + self.description, + self.session_id + ) + } + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Unsupported log format", + )), + }?; + file.flush()?; + Ok(()) + } + + /// Creates a new `Log` instance. + /// + /// Initializes a new `Log` struct with the provided details. + /// + /// # Returns + /// + /// Returns a new instance of the `Log` struct. + pub fn new( + session_id: &str, + time: &str, + level: LogLevel, + component: &str, + description: &str, + format: LogFormat, + ) -> Self { + Self { + session_id: session_id.to_string(), + time: time.to_string(), + level, + component: component.to_string(), + description: description.to_string(), + format, + } + } +} + +/// Provides default values for `Log`. +/// +/// This implementation provides a quick way to generate a `Log` instance with default values. +impl Default for Log { + fn default() -> Self { + Self { + session_id: String::default(), + time: String::default(), + level: LogLevel::INFO, // Default log level + component: String::default(), + description: String::default(), + format: LogFormat::CLF, // Default log format + } + } +} +#[cfg(test)] +/// Tests for the `log_info!` macro. +mod tests { + use crate::macro_log_info; + + #[test] + fn test_log_info() { + macro_log_info!( + LogLevel::INFO, + "component", + "description", + LogFormat::CLF + ); + } +} diff --git a/src/macros.rs b/src/macros.rs index 7eb9a52..1039de2 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -3,58 +3,209 @@ //! Macros for the `libmake` crate. //! -//! This module bundles all macros used across the `libmake` crate. -//! These include macros for asserting the creation of directories, -//! generating templates from JSON, YAML, and CSV files. -//! +//! This module provides macros for asserting the creation of directories, +//! generating templates from JSON, YAML, and CSV files, and custom logging functionality. +// Macro for creating a new directory at the specified path. #[macro_export] -/// A macro for creating a new directory. +/// Asserts that a directory is created at the given path. +/// +/// # Arguments +/// +/// * `$path` - A string literal specifying the path where the directory will be created. /// -/// This will create a new directory at the given path. If the directory -/// already exists, it will return an error. +/// # Panics /// +/// Panics if the directory cannot be created. macro_rules! assert_create_directory { ($path:expr) => { - assert!(create_directory(Path::new($path)).is_ok()); + use std::path::Path; + use std::fs::{self, create_dir_all}; + assert!(create_dir_all(Path::new($path)).is_ok(), "Failed to create directory at: {}", $path); }; } + +// Macro for generating file templates using a command-line interface. #[macro_export] -/// A macro for generating a new set of file templates using the command -/// line interface. +/// Asserts that file templates are generated from the given parameters. +/// +/// # Arguments +/// +/// * `$params` - Parameters for file generation. +/// +/// # Panics +/// +/// Panics if the files cannot be generated. macro_rules! assert_generate_files { ($params:expr) => { - assert!(generate_files($params).is_ok()); + assert!(generate_files($params).is_ok(), "Failed to generate files with parameters: {:?}", $params); }; } + +// Macro for generating file templates from a CSV file. #[macro_export] -/// A macro for generating a new set of file templates using a CSV file. +/// Asserts that file templates are generated from a CSV file. +/// +/// # Arguments +/// +/// * `$csv_path` - The path to the CSV file. +/// +/// # Panics +/// +/// Panics if the files cannot be generated from the CSV. macro_rules! assert_generate_from_csv { ($csv_path:expr) => { - assert!(generate_from_csv($csv_path).is_ok()); + assert!(generate_from_csv($csv_path).is_ok(), "Failed to generate files from CSV at: {}", $csv_path); }; } + +// Macro for generating file templates from a JSON file. #[macro_export] -/// A macro for generating a new set of file templates using a JSON file. +/// Asserts that file templates are generated from a JSON file. +/// +/// # Arguments +/// +/// * `$json_path` - The path to the JSON file. +/// +/// # Panics +/// +/// Panics if the files cannot be generated from the JSON. macro_rules! assert_generate_from_json { - ($path:expr) => { - assert!(generate_from_json($path).is_ok()); + ($json_path:expr) => { + assert!(generate_from_json($json_path).is_ok(), "Failed to generate files from JSON at: {}", $json_path); }; } + +// Macro for generating file templates from a YAML file. #[macro_export] -/// A macro for generating a new set of file templates using a YAML file. +/// Asserts that file templates are generated from a YAML file. +/// +/// # Arguments +/// +/// * `$yaml_path` - The path to the YAML file. +/// +/// # Panics +/// +/// Panics if the files cannot be generated from the YAML. macro_rules! assert_generate_from_yaml { - ($path:expr) => { - assert!(generate_from_yaml($path).is_ok()); + ($yaml_path:expr) => { + assert!(generate_from_yaml($yaml_path).is_ok(), "Failed to generate files from YAML at: {}", $yaml_path); }; } + +// Macro for generating file templates from a configuration file. #[macro_export] -/// A macro for generating a new set of file templates using a -/// configuration file. This will require a path to the configuration -/// file and the file type. The file type can be either `json`, `yaml`, -/// `yml` or `csv`. +/// Asserts that file templates are generated from a configuration file. +/// +/// # Arguments +/// +/// * `$path` - The path to the configuration file. +/// * `$file_type` - The type of the configuration file: `json`, `yaml`, `yml`, or `csv`. +/// +/// # Panics +/// +/// Panics if the files cannot be generated from the configuration file. macro_rules! assert_generate_from_config { ($path:expr, $file_type:expr) => { - assert!(generate_from_config($path, $file_type).is_ok()); + assert!(generate_from_config($path, $file_type).is_ok(), "Failed to generate files from {} configuration at: {}", $file_type, $path); }; } +// Macro for logging information with various log levels and formats. +#[macro_export] +/// Logs information with the specified level, component, and format. +/// +/// # Parameters +/// +/// * `$level` - The log level for the message. +/// * `$component` - The component where the log message originates. +/// * `$description` - A description for the log message. +/// * `$format` - The format for the log message. +/// +/// # Returns +/// +/// This macro returns the created `Log` instance. +macro_rules! macro_log_info { + ($level:expr, $component:expr, $description:expr, $format:expr) => {{ + use dtt::DateTime; + use vrd::Random; + use $crate::loggers::{Log, LogFormat, LogLevel}; + + // Get the current date and time in ISO 8601 format. + let date = DateTime::new(); + let iso = date.iso_8601; + + // Create a new random number generator + let mut rng = Random::default(); + let session_id = rng.rand().to_string(); + + let log = Log::new( + &session_id, + &iso, + $level, + $component, + $description, + $format, + ); + let _ = log.log(); + log // Return the Log instance + }}; +} +// Macro for executing a shell command and logging the operation. +#[macro_export] +/// Executes a shell command and logs the start, completion, and any errors. +/// +/// # Parameters +/// +/// * `$command` - The shell command to execute. +/// * `$package` - The name of the package being operated on. +/// * `$operation` - A description of the operation. +/// * `$start_message` - The message to log at the start of the operation. +/// * `$complete_message` - The message to log upon successful completion. +/// * `$error_message` - The message to log in case of an error. +/// +/// # Returns +/// +/// Returns a `Result<(), anyhow::Error>` to indicate the success or failure of the command execution. +macro_rules! macro_execute_and_log { + ($command:expr, $package:expr, $operation:expr, $start_message:expr, $complete_message:expr, $error_message:expr) => {{ + use anyhow::{Context, Result as AnyResult}; + use $crate::loggers::{LogFormat, LogLevel}; + use $crate::macro_log_info; + + macro_log_info!( + LogLevel::INFO, + $operation, + $start_message, + LogFormat::CLF + ); + + $command + .run() + .map(|_| ()) + .map_err(|err| { + macro_log_info!( + LogLevel::ERROR, + $operation, + $error_message, + LogFormat::CLF + ); + err + }) + .with_context(|| { + format!( + "Failed to execute '{}' for {} on package '{}'", + stringify!($command), + $operation, + $package + ) + })?; + + macro_log_info!( + LogLevel::INFO, + $operation, + $complete_message, + LogFormat::CLF + ); + Ok(()) + }}; +} diff --git a/template/Cargo.tpl b/template/Cargo.tpl index fc0a811..4cca6f2 100644 --- a/template/Cargo.tpl +++ b/template/Cargo.tpl @@ -1,95 +1,100 @@ -[package] -authors = ["{author} <{email}>"] # Add your name and email here. -build = "{build}" # Add your build command here (e.g. build.rs). -categories = {categories} # Add your categories here. See https://doc.rust-lang.org/cargo/reference/manifest.html?highlight=keywords#the-categories-field for more information. -description = "{description}" # Add your description here. -documentation = "{documentation}" # Add your documentation link here. -edition = "{edition}" # Add your edition here (e.g. 2018). -exclude = [ - "/.git/*", - "/.github/*", - "/.gitignore", - "/.vscode/*" - ] # Add files to exclude here -homepage = "{homepage}" # Add your homepage here -keywords = ["{keywords}"] # Add your keywords here. See https://doc.rust-lang.org/cargo/reference/manifest.html?highlight=keywords#the-keywords-field for more information. -license = "{license}" # Add your license here. -name = "{name}" # Add your library name here. -readme = "{readme}" # Add your readme here. -repository = "{repository}" # Add your repository here. -rust-version = "{rustversion}" # Add your rust version here. -version = "{version}" # Add your version here. -include = [ - "/benches/**", - "/build.rs", - "/Cargo.toml", - "/CONTRIBUTING.md", - "/examples/**", - "/LICENSE-APACHE", - "/LICENSE-MIT", - "/README.md", - "/src/**", - "/tests/**", - "/xtask/**", -] # Add files to include here. - -[[bench]] -name = "benchmark" -harness = false -path = "benches/criterion.rs" - -[profile.bench] -debug = true - -[dependencies] -serde = { version = "1.0.162", features = ["derive"] } -serde_json = "1.0.96" - -[dev-dependencies] -criterion = "0.4.0" - -[lib] -crate-type = ["lib"] -name = "{name}" -path = "src/lib.rs" - -[features] -default = [] - -[package.metadata.docs.rs] -all-features = true - -[profile.dev] -codegen-units = 256 # Number of parallel codegen units -debug = true # Enable debug symbols -debug-assertions = true # Enable debug assertions -incremental = true # Enable incremental compilation -lto = false # Disable link-time optimization -opt-level = 0 # Optimize for speed -overflow-checks = true # Enable overflow checks -panic = 'unwind' # Use unwinding panic handling -rpath = false # Disable rpath -strip = false # Disable symbol stripping - -[profile.release] -codegen-units = 1 # Number of parallel codegen units -debug = false # Disable debug symbols -debug-assertions = false # Disable debug assertions -incremental = false # Disable incremental compilation -lto = true # Enable link-time optimization -opt-level = "s" # Optimize for size -overflow-checks = false # Disable overflow checks -panic = "abort" # Use aborting panic handling -rpath = false # Disable rpath -strip = "symbols" # Strip symbols - -[profile.test] -codegen-units = 256 # Number of parallel codegen units -debug = true # Enable debug symbols -debug-assertions = true # Enable debug assertions -incremental = true # Enable incremental compilation -lto = false # Disable link-time optimization -opt-level = 0 # Optimize for speed -overflow-checks = true # Enable overflow checks -rpath = false # Disable rpath -strip = false # Disable symbol stripping \ No newline at end of file +[package] +authors = ["{author} <{email}>"] +build = "{build}" +categories = ["{categories}"] +description = "{description}" +documentation = "{documentation}" +edition = "{edition}" +exclude = [ + "/.git/*", + "/.github/*", + "/.gitignore", + "/.vscode/*" + ] +homepage = "{homepage}" +keywords = ["{keywords}"] +license = "{license}" +name = "{name}" +readme = "{readme}" +repository = "{repository}" +rust-version = "{rustversion}" +version = "{version}" +include = [ + "/benches/**", + "/build.rs", + "/Cargo.toml", + "/CONTRIBUTING.md", + "/examples/**", + "/LICENSE-APACHE", + "/LICENSE-MIT", + "/README.md", + "/src/**", + "/tests/**", + "/xtask/**", +] + +[[bench]] +name = "benchmark" +harness = false +path = "benches/criterion.rs" + +[profile.bench] +debug = true + +[dependencies] +anyhow = "1.0.75" +dtt = "0.0.4" +serde = { version = "1.0.192", features = ["derive"] } +serde_json = "1.0.108" +serde_yaml = "0.9.27" +toml = "0.8.8" +vrd = "0.0.4" + +[dev-dependencies] +criterion = "0.5.1" + +[lib] +crate-type = ["lib"] +name = "{name}" +path = "src/lib.rs" + +[features] +default = [] + +[package.metadata.docs.rs] +all-features = true + +[profile.dev] +codegen-units = 256 +debug = true +debug-assertions = true +incremental = true +lto = false +opt-level = 0 +overflow-checks = true +panic = 'unwind' +rpath = false +strip = false + +[profile.release] +codegen-units = 1 +debug = false +debug-assertions = false +incremental = false +lto = true +opt-level = "s" +overflow-checks = false +panic = "abort" +rpath = false +strip = "symbols" + +[profile.test] +codegen-units = 256 +debug = true +debug-assertions = true +incremental = true +lto = false +opt-level = 0 +overflow-checks = true +rpath = false +strip = false diff --git a/template/README.tpl b/template/README.tpl index 96357f3..acb539e 100644 --- a/template/README.tpl +++ b/template/README.tpl @@ -1,198 +1,197 @@ - - - - - - -# {name} - -{description} - - -
- - -[![Made With Rust][made-with-rust-badge]][5] -[![Crates.io][crates-badge]][7] -[![Lib.rs][libs-badge]][9] -[![Docs.rs][docs-badge]][8] -[![License][license-badge]][2] - -• [Website][0] -• [Documentation][8] -• [Report Bug][3] -• [Request Feature][3] -• [Contributing Guidelines][4] - - -
- - -![divider][divider] - -## Overview 📖 - -{description} - -## Features ✨ - -- Feature 1 -- Feature 2 -- Feature 3 - -## Getting Started 🚀 - -It takes just a few minutes to get up and running with `{name}`. - -### Installation - -To install `{name}`, you need to have the Rust toolchain installed on -your machine. You can install the Rust toolchain by following the -instructions on the [Rust website][13]. - -Once you have the Rust toolchain installed, you can install `{name}` -using the following command: - -```shell -cargo install {name} -``` - -You can then run the help command to see the available options: - -```shell -{name} --help -``` - -### Requirements - -The minimum supported Rust toolchain version is currently Rust -**{rustversion}** or later (stable). - -### Platform support - -`{name}` is supported and tested on the following platforms: - -### Tier 1 platforms 🏆 - -| | Operating System | Target | Description | -| --- | --- | --- | --- | -| ✅ | Linux | aarch64-unknown-linux-gnu | 64-bit Linux systems on ARM architecture | -| ✅ | Linux | i686-unknown-linux-gnu | 32-bit Linux (kernel 3.2+, glibc 2.17+) | -| ✅ | Linux | x86_64-unknown-linux-gnu | 64-bit Linux (kernel 2.6.32+, glibc 2.11+) | -| ✅ | macOS | x86_64-apple-darwin | 64-bit macOS (10.7 Lion or later) | -| ✅ | Windows | i686-pc-windows-gnu | 32-bit Windows (7 or later) | -| ✅ | Windows | i686-pc-windows-msvc | 32-bit Windows (7 or later) | -| ✅ | Windows | x86_64-pc-windows-gnu | 64-bit Windows (7 or later) | -| ✅ | Windows | x86_64-pc-windows-msvc | 64-bit Windows (7 or later) | - -### Tier 2 platforms 🥈 - -| | Operating System | Target | Description | -| --- | --- | --- | --- | -| ✅ | Linux | aarch64-unknown-linux-musl | 64-bit Linux systems on ARM architecture | -| ✅ | Linux | arm-unknown-linux-gnueabi | ARMv6 Linux (kernel 3.2, glibc 2.17) | -| ✅ | Linux | arm-unknown-linux-gnueabihf | ARMv7 Linux, hardfloat (kernel 3.2, glibc 2.17) | -| ✅ | Linux | armv7-unknown-linux-gnueabihf | ARMv7 Linux, hardfloat (kernel 3.2, glibc 2.17) | -| ✅ | Linux | mips-unknown-linux-gnu | MIPS Linux (kernel 2.6.32+, glibc 2.11+) | -| ✅ | Linux | mips64-unknown-linux-gnuabi64 | MIPS64 Linux (kernel 2.6.32+, glibc 2.11+) | -| ✅ | Linux | mips64el-unknown-linux-gnuabi64 | MIPS64 Linux (kernel 2.6.32+, glibc 2.11+) | -| ✅ | Linux | mipsel-unknown-linux-gnu | MIPS Linux (kernel 2.6.32+, glibc 2.11+) | -| ✅ | macOS | aarch64-apple-darwin | 64-bit macOS (10.7 Lion or later) | -| ✅ | Windows | aarch64-pc-windows-msvc | 64-bit Windows (7 or later) | - -The [GitHub Actions][10] shows the platforms in which the `{name}` -library tests are run. - -### Documentation - -> ℹ️ **Info:** Please check out our [website][0] for more information. -You can find our documentation on [docs.rs][8], [lib.rs][9] and -[crates.io][7]. - -## Usage 📖 - -To use the `{name}` library in your project, add the following to your -`Cargo.toml` file: - -```toml -[dependencies] -{name} = "{version}" -``` - -Add the following to your `main.rs` file: - -```rust -extern crate {name}; -use {name}::*; -``` - -then you can use the functions in your application code. - -### Examples - -To get started with `{name}`, you can use the examples provided in the -`examples` directory of the project. - -To run the examples, clone the repository and run the following command -in your terminal from the project root directory. - -```shell -cargo run --example {name} -``` - -## Semantic Versioning Policy 🚥 - -For transparency into our release cycle and in striving to maintain -backward compatibility, `{name}` follows [semantic versioning][6]. - -## License 📝 - -The project is licensed under the terms of {license}. - -## Contribution 🤝 - -We welcome all people who want to contribute. Please see the -[contributing instructions][4] for more information. - -Contributions in any form (issues, pull requests, etc.) to this project -must adhere to the [Rust's Code of Conduct][11]. - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the -Apache-2.0 license, shall be dual licensed as above, without any -additional terms or conditions. - -## Acknowledgements 💙 - -A big thank you to all the awesome contributors of [{name}][5] for their -help and support. - -A special thank you goes to the [Rust Reddit][12] community for -providing a lot of useful suggestions on how to improve this project. - -[0]: {website} -[2]: http://opensource.org/licenses/MIT -[3]: {repository}/{name}/issues -[4]: {repository}/{name}/blob/main/CONTRIBUTING.md -[5]: {repository}/{name}/graphs/contributors -[6]: http://semver.org/ -[7]: https://crates.io/crates/{name} -[8]: https://docs.rs/{name} -[9]: https://lib.rs/crates/{name} -[10]: {repository}/{name}/actions -[11]: https://www.rust-lang.org/policies/code-of-conduct -[12]: https://www.reddit.com/r/rust/ -[13]: https://www.rust-lang.org/learn/get-started - -[crates-badge]: https://img.shields.io/crates/v/{name}.svg?style=for-the-badge 'Crates.io badge' -[divider]: https://kura.pro/common/images/elements/divider.svg "divider" -[docs-badge]: https://img.shields.io/docsrs/{name}.svg?style=for-the-badge 'Docs.rs badge' -[libs-badge]: https://img.shields.io/badge/lib.rs-v{version}-orange.svg?style=for-the-badge 'Lib.rs badge' -[license-badge]: https://img.shields.io/crates/l/{name}.svg?style=for-the-badge 'License badge' -[made-with-rust-badge]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust 'Made With Rust badge' \ No newline at end of file + + + + + + +# {name} + +{description} + + +
+ + +[![Made With Rust][made-with-rust-badge]][5] +[![Crates.io][crates-badge]][7] +[![Lib.rs][libs-badge]][9] +[![Docs.rs][docs-badge]][8] +[![License][license-badge]][2] + +• [Website][0] +• [Documentation][8] +• [Report Bug][3] +• [Request Feature][3] +• [Contributing Guidelines][4] + + +
+ + +![divider][divider] + +## Overview 📖 + +{description} + +## Features ✨ + +- Feature 1 +- Feature 2 +- Feature 3 + +## Getting Started 🚀 + +It takes just a few minutes to get up and running with `{name}`. + +### Installation + +To install `{name}`, you need to have the Rust toolchain installed on +your machine. You can install the Rust toolchain by following the +instructions on the [Rust website][13]. + +Once you have the Rust toolchain installed, you can install `{name}` +using the following command: + +```shell +cargo install {name} +``` + +You can then run the help command to see the available options: + +```shell +{name} --help +``` + +### Requirements + +The minimum supported Rust toolchain version is currently Rust +**{rustversion}** or later (stable). + +### Platform support + +`{name}` is supported and tested on the following platforms: + +### Tier 1 platforms 🏆 + +| | Operating System | Target | Description | +| --- | --- | --- | --- | +| ✅ | Linux | aarch64-unknown-linux-gnu | 64-bit Linux systems on ARM architecture | +| ✅ | Linux | i686-unknown-linux-gnu | 32-bit Linux (kernel 3.2+, glibc 2.17+) | +| ✅ | Linux | x86_64-unknown-linux-gnu | 64-bit Linux (kernel 2.6.32+, glibc 2.11+) | +| ✅ | macOS | x86_64-apple-darwin | 64-bit macOS (10.7 Lion or later) | +| ✅ | Windows | i686-pc-windows-gnu | 32-bit Windows (7 or later) | +| ✅ | Windows | i686-pc-windows-msvc | 32-bit Windows (7 or later) | +| ✅ | Windows | x86_64-pc-windows-gnu | 64-bit Windows (7 or later) | +| ✅ | Windows | x86_64-pc-windows-msvc | 64-bit Windows (7 or later) | + +### Tier 2 platforms 🥈 + +| | Operating System | Target | Description | +| --- | --- | --- | --- | +| ✅ | Linux | aarch64-unknown-linux-musl | 64-bit Linux systems on ARM architecture | +| ✅ | Linux | arm-unknown-linux-gnueabi | ARMv6 Linux (kernel 3.2, glibc 2.17) | +| ✅ | Linux | arm-unknown-linux-gnueabihf | ARMv7 Linux, hardfloat (kernel 3.2, glibc 2.17) | +| ✅ | Linux | armv7-unknown-linux-gnueabihf | ARMv7 Linux, hardfloat (kernel 3.2, glibc 2.17) | +| ✅ | Linux | mips-unknown-linux-gnu | MIPS Linux (kernel 2.6.32+, glibc 2.11+) | +| ✅ | Linux | mips64-unknown-linux-gnuabi64 | MIPS64 Linux (kernel 2.6.32+, glibc 2.11+) | +| ✅ | Linux | mips64el-unknown-linux-gnuabi64 | MIPS64 Linux (kernel 2.6.32+, glibc 2.11+) | +| ✅ | Linux | mipsel-unknown-linux-gnu | MIPS Linux (kernel 2.6.32+, glibc 2.11+) | +| ✅ | macOS | aarch64-apple-darwin | 64-bit macOS (10.7 Lion or later) | +| ✅ | Windows | aarch64-pc-windows-msvc | 64-bit Windows (7 or later) | + +The [GitHub Actions][10] shows the platforms in which the `{name}` +library tests are run. + +### Documentation + +**Info:** Please check out our [website][0] for more information. You can find our documentation on [docs.rs][8], [lib.rs][9] and +[crates.io][7]. + +## Usage 📖 + +To use the `{name}` library in your project, add the following to your +`Cargo.toml` file: + +```toml +[dependencies] +{name} = "{version}" +``` + +Add the following to your `main.rs` file: + +```rust +extern crate {name}; +use {name}::*; +``` + +then you can use the functions in your application code. + +### Examples + +To get started with `{name}`, you can use the examples provided in the +`examples` directory of the project. + +To run the examples, clone the repository and run the following command +in your terminal from the project root directory. + +```shell +cargo run --example {name} +``` + +## Semantic Versioning Policy 🚥 + +For transparency into our release cycle and in striving to maintain +backward compatibility, `{name}` follows [semantic versioning][6]. + +## License 📝 + +The project is licensed under the terms of {license}. + +## Contribution 🤝 + +We welcome all people who want to contribute. Please see the +[contributing instructions][4] for more information. + +Contributions in any form (issues, pull requests, etc.) to this project +must adhere to the [Rust's Code of Conduct][11]. + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the +Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. + +## Acknowledgements 💙 + +A big thank you to all the awesome contributors of [{name}][5] for their +help and support. + +A special thank you goes to the [Rust Reddit][12] community for +providing a lot of useful suggestions on how to improve this project. + +[0]: {website} +[2]: http://opensource.org/licenses/MIT +[3]: {repository}/{name}/issues +[4]: {repository}/{name}/blob/main/CONTRIBUTING.md +[5]: {repository}/{name}/graphs/contributors +[6]: http://semver.org/ +[7]: https://crates.io/crates/{name} +[8]: https://docs.rs/{name} +[9]: https://lib.rs/crates/{name} +[10]: {repository}/{name}/actions +[11]: https://www.rust-lang.org/policies/code-of-conduct +[12]: https://www.reddit.com/r/rust/ +[13]: https://www.rust-lang.org/learn/get-started + +[crates-badge]: https://img.shields.io/crates/v/{name}.svg?style=for-the-badge 'Crates.io badge' +[divider]: https://kura.pro/common/images/elements/divider.svg "divider" +[docs-badge]: https://img.shields.io/docsrs/{name}.svg?style=for-the-badge 'Docs.rs badge' +[libs-badge]: https://img.shields.io/badge/lib.rs-v{version}-orange.svg?style=for-the-badge 'Lib.rs badge' +[license-badge]: https://img.shields.io/crates/l/{name}.svg?style=for-the-badge 'License badge' +[made-with-rust-badge]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust 'Made With Rust badge' diff --git a/template/lib.tpl b/template/lib.tpl index 9620e29..5d8911b 100644 --- a/template/lib.tpl +++ b/template/lib.tpl @@ -63,7 +63,10 @@ #![crate_name = "{name}"] #![crate_type = "lib"] -/// The `macros` module. +/// The `loggers` module contains the loggers for the library. +pub mod loggers; + +/// The `macros` module contains functions for generating macros. pub mod macros; use serde::{Deserialize, Serialize}; diff --git a/template/loggers.tpl b/template/loggers.tpl new file mode 100644 index 0000000..c2c84ab --- /dev/null +++ b/template/loggers.tpl @@ -0,0 +1,243 @@ +// Copyright © 2023 LibMake. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// Standard library imports for formatting and I/O operations. +use std::{ + fmt, + fs::OpenOptions, + io::{self, Write as IoWrite}, +}; + +/// Enum representing the different log formats that can be used. +/// +/// This enum allows the developer to specify the format in which log messages should be displayed. +/// +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd)] +pub enum LogFormat { + /// The log format is set to the Common Log Format (CLF) + CLF, + /// The log format is set to the JSON format + JSON, + /// The log format is set to the Common Event Format (CEF) + CEF, + /// The log format is set to the Extended Log Format (ELF) + ELF, + /// The log format is set to the W3C Extended Log File Format + W3C, + /// The log format is set to the Graylog Extended Log Format (GELF) + GELF, +} + +/// Implements Display trait for `LogFormat` enum. +/// +/// This allows easy conversion of the log format enums to strings. +impl fmt::Display for LogFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{self:?}") + } +} + +/// An enumeration of the different levels that a log message can have. +/// Each variant of the enumeration represents a different level of +/// importance. +/// +/// # Arguments +/// +/// * `ALL` - The log level is set to all. +/// * `DEBUG` - The log level is set to debug. +/// * `DISABLED` - The log level is set to disabled. +/// * `ERROR` - The log level is set to error. +/// * `FATAL` - The log level is set to fatal. +/// * `INFO` - The log level is set to info. +/// * `NONE` - The log level is set to none. +/// * `TRACE` - The log level is set to trace. +/// * `VERBOSE` - The log level is set to verbose. +/// * `WARNING` - The log level is set to warning. +/// +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd)] +pub enum LogLevel { + /// The log level is set to all. + ALL, + /// The log level is set to debug. + DEBUG, + /// The log level is set to disabled. + DISABLED, + /// The log level is set to error. + ERROR, + /// The log level is set to fatal. + FATAL, + /// The log level is set to info. + INFO, + /// The log level is set to none. + NONE, + /// The log level is set to trace. + TRACE, + /// The log level is set to verbose. + VERBOSE, + /// The log level is set to warning. + WARNING, +} +/// Display trait implementation for `LogLevel`. +/// +/// This converts the enum to a string representation. +impl fmt::Display for LogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +/// Struct representing a log message. +/// +/// Contains all the elements that make up a complete log message. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct Log { + /// A string that holds a session ID. The session ID is a unique + /// identifier for the current session. A random GUID (Globally + /// Unique Identifier) is generated by default. + pub session_id: String, + /// A string that holds the timestamp in ISO 8601 format. + pub time: String, + /// A string that holds the level (INFO, WARN, ERROR, etc.). + pub level: LogLevel, + /// A string that holds the component name. + pub component: String, + /// A string that holds the description of the log message. + pub description: String, + /// A string that holds the log format. + pub format: LogFormat, +} + +impl Log { + /// Logs a message to the console using a pre-allocated buffer to + /// reduce memory allocation and flush the output buffer to ensure + /// that the message is written immediately. + /// + /// # Errors + /// + /// This function will panic if an error occurs when writing to the + /// pre-allocated buffer or flushing the output buffer. + pub fn log(&self) -> io::Result<()> { + // Open the file in append mode. If the file does not exist, create it. + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open("{name}.log")?; + match self.format { + LogFormat::CLF => { + writeln!( + file, + "SessionID={}\tTimestamp={}\tDescription={}\tLevel={}\tComponent={}\tFormat={}", + self.session_id, + self.time, + self.description, + self.level, + self.component, + self.format + ) + } + LogFormat::JSON => { + writeln!( + file, + r#"{{"session_id": "{}", "timestamp": "{}", "description": "{}", "level": "{}", "component": "{}", "format": "{}"}}"#, + self.session_id, + self.time, + self.description, + self.level, + self.component, + self.format + ) + } + LogFormat::CEF => { + writeln!( + file, + r#"[CEF] + + 1 + {name} + Application + {} + Log + {} + {} + {} + localhost + localhost + - + - + - + - + - + - + + "#, + self.time, + self.level, + self.description, + self.session_id + ) + } + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Unsupported log format", + )), + }?; + file.flush()?; + Ok(()) + } + + /// Creates a new `Log` instance. + /// + /// Initializes a new `Log` struct with the provided details. + /// + /// # Returns + /// + /// Returns a new instance of the `Log` struct. + pub fn new( + session_id: &str, + time: &str, + level: LogLevel, + component: &str, + description: &str, + format: LogFormat, + ) -> Self { + Self { + session_id: session_id.to_string(), + time: time.to_string(), + level, + component: component.to_string(), + description: description.to_string(), + format, + } + } +} + +/// Provides default values for `Log`. +/// +/// This implementation provides a quick way to generate a `Log` instance with default values. +impl Default for Log { + fn default() -> Self { + Self { + session_id: String::default(), + time: String::default(), + level: LogLevel::INFO, // Default log level + component: String::default(), + description: String::default(), + format: LogFormat::CLF, // Default log format + } + } +} +#[cfg(test)] +/// Tests for the `log_info!` macro. +mod tests { + use crate::{name}_log_info; + + #[test] + fn test_log_info() { + {name}_log_info!( + LogLevel::INFO, + "component", + "description", + LogFormat::CLF + ); + } +} diff --git a/template/macros.tpl b/template/macros.tpl index 2b16ec9..ea39e7f 100644 --- a/template/macros.tpl +++ b/template/macros.tpl @@ -12,6 +12,8 @@ //! - `{name}_split`: Splits a string into a vector of words. //! - `{name}_join`: Joins a vector of strings into a single string. //! - `{name}_print_vec`: Prints a vector of elements to the console. +//! - `{name}_log_info`: Logs information with the specified level, component, and format. +//! - `{name}_execute_and_log`: Executes a shell command and logs the start, completion, and any errors. //! /// This macro takes any number of arguments and parses them into a @@ -117,4 +119,104 @@ macro_rules! {name}_print_vec { println!("{}", v); } }}; +} + +// Macro for logging information with various log levels and formats. +#[macro_export] +/// Logs information with the specified level, component, and format. +/// +/// # Parameters +/// +/// * `$level` - The log level for the message. +/// * `$component` - The component where the log message originates. +/// * `$description` - A description for the log message. +/// * `$format` - The format for the log message. +/// +/// # Returns +/// +/// This macro returns the created `Log` instance. +macro_rules! {name}_log_info { + ($level:expr, $component:expr, $description:expr, $format:expr) => {{ + use dtt::DateTime; + use vrd::Random; + use $crate::loggers::{Log, LogFormat, LogLevel}; + + // Get the current date and time in ISO 8601 format. + let date = DateTime::new(); + let iso = date.iso_8601; + + // Create a new random number generator + let mut rng = Random::default(); + let session_id = rng.rand().to_string(); + + let log = Log::new( + &session_id, + &iso, + $level, + $component, + $description, + $format, + ); + let _ = log.log(); + log // Return the Log instance + }}; +} +// Macro for executing a shell command and logging the operation. +#[macro_export] +/// Executes a shell command and logs the start, completion, and any errors. +/// +/// # Parameters +/// +/// * `$command` - The shell command to execute. +/// * `$package` - The name of the package being operated on. +/// * `$operation` - A description of the operation. +/// * `$start_message` - The message to log at the start of the operation. +/// * `$complete_message` - The message to log upon successful completion. +/// * `$error_message` - The message to log in case of an error. +/// +/// # Returns +/// +/// Returns a `Result<(), anyhow::Error>` to indicate the success or failure of the command execution. +macro_rules! {name}_execute_and_log { + ($command:expr, $package:expr, $operation:expr, $start_message:expr, $complete_message:expr, $error_message:expr) => {{ + use anyhow::{Context, Result as AnyResult}; + use $crate::loggers::{LogFormat, LogLevel}; + use $crate::{name}_log_info; + + {name}_log_info!( + LogLevel::INFO, + $operation, + $start_message, + LogFormat::CLF + ); + + $command + .run() + .map(|_| ()) + .map_err(|err| { + {name}_log_info!( + LogLevel::ERROR, + $operation, + $error_message, + LogFormat::CLF + ); + err + }) + .with_context(|| { + format!( + "Failed to execute '{}' for {} on package '{}'", + stringify!($command), + $operation, + $package + ) + })?; + + {name}_log_info!( + LogLevel::INFO, + $operation, + $complete_message, + LogFormat::CLF + ); + Ok(()) + }}; } \ No newline at end of file diff --git a/template/test_loggers.tpl b/template/test_loggers.tpl new file mode 100644 index 0000000..b4b85ca --- /dev/null +++ b/template/test_loggers.tpl @@ -0,0 +1,132 @@ +// Copyright © 2023 {name}. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[cfg(test)] +mod tests { + + fn {name}_log_info( + level: LogLevel, + component: &str, + description: &str, + format: LogFormat, + ) { + let log = + {name}_log_info!(level, component, description, format); + assert_eq!(log.level, level); + assert_eq!(log.component, component); + assert_eq!(log.description, description); + assert_eq!(log.format, format); + } + + #[test] + fn test_macros() { + {name}_log_info( + LogLevel::ALL, + "component", + "description", + LogFormat::CLF, + ); + {name}_log_info( + LogLevel::DEBUG, + "component", + "description", + LogFormat::CLF, + ); + {name}_log_info( + LogLevel::DISABLED, + "component", + "description", + LogFormat::CLF, + ); + {name}_log_info( + LogLevel::ERROR, + "component", + "description", + LogFormat::CLF, + ); + {name}_log_info( + LogLevel::FATAL, + "component", + "description", + LogFormat::CLF, + ); + {name}_log_info( + LogLevel::INFO, + "component", + "description", + LogFormat::CLF, + ); + {name}_log_info( + LogLevel::NONE, + "component", + "description", + LogFormat::CLF, + ); + {name}_log_info( + LogLevel::TRACE, + "component", + "description", + LogFormat::CLF, + ); + {name}_log_info( + LogLevel::VERBOSE, + "component", + "description", + LogFormat::CLF, + ); + {name}_log_info( + LogLevel::WARNING, + "component", + "description", + LogFormat::CLF, + ); + } + + use {name}::{ + loggers::{Log, LogFormat, LogLevel}, + {name}_log_info, + }; + + #[test] + fn test_log_level_display() { + let level = LogLevel::INFO; + assert_eq!(format!("{level}"), "INFO"); + } + + #[test] + fn test_log_format_display() { + let format = LogFormat::JSON; + assert_eq!(format!("{format}"), "JSON\n"); + } + + #[test] + fn test_log_new() { + let log = Log::new( + "session123", + "2023-02-28T12:34:56Z", + LogLevel::WARNING, + "auth", + "Invalid credentials", + LogFormat::CLF, + ); + + assert_eq!(log.session_id, "session123"); + assert_eq!(log.time, "2023-02-28T12:34:56Z"); + assert_eq!(log.level, LogLevel::WARNING); + assert_eq!(log.component, "auth"); + assert_eq!(log.description, "Invalid credentials"); + assert_eq!(log.format, LogFormat::CLF); + } + + #[test] + fn test_log_default() { + let log = Log::default(); + + assert!(log.session_id.is_empty()); + assert!(log.time.is_empty()); + assert_eq!(log.level, LogLevel::INFO); + assert!(log.component.is_empty()); + assert!(log.description.is_empty()); + assert_eq!(log.format, LogFormat::CLF); + } +} \ No newline at end of file diff --git a/tests/data/mylibrary.csv b/tests/data/mylibrary.csv index a5d6b66..db6a155 100644 --- a/tests/data/mylibrary.csv +++ b/tests/data/mylibrary.csv @@ -1,2 +1,2 @@ -author,build,categories,description,documentation,edition,email,homepage,keywords,license,name,output,readme,repository,rustversion,version,website -Me,build.rs,"['category 1', 'category 2']",A library for doing things,https://lib.rs/crates/my_library,2021,test@test.com,https://test.com,"['keyword1', 'keyword2']",MIT OR Apache-2.0,my_library,my_library,README.md,https://github.com/test/test,1.69.0,0.1.9,https://test.com \ No newline at end of file +author,build,categories,description,documentation,edition,email,homepage,keywords,license,name,output,readme,repository,rustversion,version,website +Me,build.rs,"['category 1', 'category 2']",A library for doing things,https://lib.rs/crates/my_library,2021,test@test.com,https://test.com,"['keyword1', 'keyword2']",MIT OR Apache-2.0,my_library,my_library,README.md,https://github.com/test/test,1.71.1,0.2.0,https://test.com diff --git a/tests/data/mylibrary.json b/tests/data/mylibrary.json index b4e8c7e..edb86d6 100644 --- a/tests/data/mylibrary.json +++ b/tests/data/mylibrary.json @@ -13,7 +13,7 @@ "output": "my_library", "readme": "README.md", "repository": "https://github.com/test/test", - "rustversion": "1.69.0", - "version": "0.1.9", + "rustversion": "1.71.1", + "version": "0.2.0", "website": "https://test.com" -} \ No newline at end of file +} diff --git a/tests/data/mylibrary.toml b/tests/data/mylibrary.toml index c46d450..0e5709c 100644 --- a/tests/data/mylibrary.toml +++ b/tests/data/mylibrary.toml @@ -12,6 +12,6 @@ name = "my_library" output = "my_library" readme = "README.md" repository = "https://github.com/test/test" -rustversion = "1.69.0" -version = "0.1.9" +rustversion = "1.71.1" +version = "0.2.0" website = "https://test.com" diff --git a/tests/data/mylibrary.yaml b/tests/data/mylibrary.yaml index e8cf66c..e9bf4e7 100644 --- a/tests/data/mylibrary.yaml +++ b/tests/data/mylibrary.yaml @@ -12,6 +12,6 @@ name: my_library output: my_library readme: README.md repository: https://github.com/test/test -rustversion: '1.69.0' -version: '0.1.9' +rustversion: '1.71.1' +version: '0.2.0' website: https://test.com diff --git a/tests/test_args.rs b/tests/test_args.rs index dfbd532..33716f2 100644 --- a/tests/test_args.rs +++ b/tests/test_args.rs @@ -105,7 +105,7 @@ mod tests { Some(&file_path.to_string()) ); - process_arguments(&matches); + process_arguments(&matches).unwrap(); // Check that the files were generated let expected_files = vec![ "Cargo.toml", diff --git a/tests/test_ascii.rs b/tests/test_ascii.rs index d2dd6f8..75fc088 100644 --- a/tests/test_ascii.rs +++ b/tests/test_ascii.rs @@ -11,7 +11,7 @@ mod tests { // Call the generate_ascii_art function with the test string and assert that no panic occurs assert!(std::panic::catch_unwind(|| { - generate_ascii_art(text); + generate_ascii_art(text).unwrap(); }) .is_ok()); } diff --git a/tests/test_cli.rs b/tests/test_cli.rs index 17c7695..3585532 100644 --- a/tests/test_cli.rs +++ b/tests/test_cli.rs @@ -21,8 +21,8 @@ mod tests { ("output", "my_library"), ("readme", "README.md"), ("repository", "https://github.com/test/test"), - ("rustversion", "1.69.0"), - ("version", "0.1.9"), + ("rustversion", "1.71.1"), + ("version", "0.2.0"), ("website", "https://test.com"), ]; diff --git a/tests/test_generator.rs b/tests/test_generator.rs index 9f67f4c..70b5704 100644 --- a/tests/test_generator.rs +++ b/tests/test_generator.rs @@ -150,7 +150,7 @@ fn test_assert_generate_files() { params.output = Some(temp_dir.as_path().to_str().unwrap().to_owned()); - assert_generate_files!(params); + assert_generate_files!(params.clone()); assert!(temp_dir.exists()); std::fs::remove_dir_all(temp_dir).unwrap(); } diff --git a/tests/test_loggers.rs b/tests/test_loggers.rs new file mode 100644 index 0000000..3fc2ae8 --- /dev/null +++ b/tests/test_loggers.rs @@ -0,0 +1,132 @@ +// Copyright © 2023 LibMake. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[cfg(test)] +mod tests { + + fn test_macro_log( + level: LogLevel, + component: &str, + description: &str, + format: LogFormat, + ) { + let log = + macro_log_info!(level, component, description, format); + assert_eq!(log.level, level); + assert_eq!(log.component, component); + assert_eq!(log.description, description); + assert_eq!(log.format, format); + } + + #[test] + fn test_macros() { + test_macro_log( + LogLevel::ALL, + "component", + "description", + LogFormat::CLF, + ); + test_macro_log( + LogLevel::DEBUG, + "component", + "description", + LogFormat::CLF, + ); + test_macro_log( + LogLevel::DISABLED, + "component", + "description", + LogFormat::CLF, + ); + test_macro_log( + LogLevel::ERROR, + "component", + "description", + LogFormat::CLF, + ); + test_macro_log( + LogLevel::FATAL, + "component", + "description", + LogFormat::CLF, + ); + test_macro_log( + LogLevel::INFO, + "component", + "description", + LogFormat::CLF, + ); + test_macro_log( + LogLevel::NONE, + "component", + "description", + LogFormat::CLF, + ); + test_macro_log( + LogLevel::TRACE, + "component", + "description", + LogFormat::CLF, + ); + test_macro_log( + LogLevel::VERBOSE, + "component", + "description", + LogFormat::CLF, + ); + test_macro_log( + LogLevel::WARNING, + "component", + "description", + LogFormat::CLF, + ); + } + + use libmake::{ + loggers::{Log, LogFormat, LogLevel}, + macro_log_info, + }; + + #[test] + fn test_log_level_display() { + let level = LogLevel::INFO; + assert_eq!(format!("{level}"), "INFO"); + } + + #[test] + fn test_log_format_display() { + let format = LogFormat::JSON; + assert_eq!(format!("{format}"), "JSON\n"); + } + + #[test] + fn test_log_new() { + let log = Log::new( + "session123", + "2023-02-28T12:34:56Z", + LogLevel::WARNING, + "auth", + "Invalid credentials", + LogFormat::CLF, + ); + + assert_eq!(log.session_id, "session123"); + assert_eq!(log.time, "2023-02-28T12:34:56Z"); + assert_eq!(log.level, LogLevel::WARNING); + assert_eq!(log.component, "auth"); + assert_eq!(log.description, "Invalid credentials"); + assert_eq!(log.format, LogFormat::CLF); + } + + #[test] + fn test_log_default() { + let log = Log::default(); + + assert!(log.session_id.is_empty()); + assert!(log.time.is_empty()); + assert_eq!(log.level, LogLevel::INFO); + assert!(log.component.is_empty()); + assert!(log.description.is_empty()); + assert_eq!(log.format, LogFormat::CLF); + } +} \ No newline at end of file diff --git a/tests/test_macros.rs b/tests/test_macros.rs index 7223958..8cade2f 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -3,7 +3,7 @@ mod tests { extern crate libmake; use libmake::generator::{ - create_directory, generate_files, generate_from_csv, + generate_files, generate_from_csv, generate_from_json, generate_from_yaml, }; use libmake::generator::{ @@ -27,7 +27,7 @@ mod tests { fn test_generate_files() { let mut params = FileGenerationParams::new(); params.output = Some("my_library".into()); - assert_generate_files!(params); + assert_generate_files!(params.clone()); } // Unit test for the `generate_from_csv()` function. #[test] diff --git a/tests/test_utils.rs b/tests/test_utils.rs index fbf7cb6..a921faa 100644 --- a/tests/test_utils.rs +++ b/tests/test_utils.rs @@ -101,11 +101,11 @@ mod tests { ); assert_eq!( get_csv_field(Some(file_path), 14), - Some(vec!["1.69.0".to_string()]) + Some(vec!["1.71.1".to_string()]) ); assert_eq!( get_csv_field(Some(file_path), 15), - Some(vec!["0.1.9".to_string()]) + Some(vec!["0.2.0".to_string()]) ); assert_eq!( get_csv_field(Some(file_path), 16),