0.2.0 release
This commit is contained in:
parent
039bfcc201
commit
e4780cf729
239
Cargo.lock
generated
239
Cargo.lock
generated
@ -2,243 +2,64 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "0.6.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"is_terminal_polyfill",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle"
|
|
||||||
version = "1.0.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "0.2.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "3.0.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"once_cell",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bin2hpp"
|
name = "bin2hpp"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "4.5.35"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
|
|
||||||
dependencies = [
|
|
||||||
"clap_builder",
|
|
||||||
"clap_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_builder"
|
|
||||||
version = "4.5.35"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"clap_lex",
|
|
||||||
"strsim",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_derive"
|
|
||||||
version = "4.5.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
|
||||||
dependencies = [
|
|
||||||
"heck",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_lex"
|
|
||||||
version = "0.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heck"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_terminal_polyfill"
|
|
||||||
version = "1.70.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.21.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.93"
|
version = "1.0.97"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.38"
|
version = "1.0.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.98"
|
version = "2.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.17"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.59.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_gnullvm",
|
|
||||||
"windows_aarch64_msvc",
|
|
||||||
"windows_i686_gnu",
|
|
||||||
"windows_i686_gnullvm",
|
|
||||||
"windows_i686_msvc",
|
|
||||||
"windows_x86_64_gnu",
|
|
||||||
"windows_x86_64_gnullvm",
|
|
||||||
"windows_x86_64_msvc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bin2hpp"
|
name = "bin2hpp"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
authors = ["Adam Macdonald"]
|
authors = ["Adam Macdonald"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "Unlicense"
|
license = "GPL-3.0-only"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.35", features = ["derive"] }
|
thiserror = "2.0.14"
|
||||||
|
|
||||||
[profile.optimised]
|
[profile.optimised]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
33
LICENSE
33
LICENSE
@ -1,24 +1,15 @@
|
|||||||
This is free and unencumbered software released into the public domain.
|
bin2hpp
|
||||||
|
Copyright (C) 2025 <Adam Macdonald>
|
||||||
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
This program is free software: you can redistribute it and/or modify
|
||||||
distribute this software, either in source code form or as a compiled
|
it under the terms of the GNU General Public License as published by
|
||||||
binary, for any purpose, commercial or non-commercial, and by any
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
means.
|
(at your option) any later version.
|
||||||
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors
|
This program is distributed in the hope that it will be useful,
|
||||||
of this software dedicate any and all copyright interest in the
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
software to the public domain. We make this dedication for the benefit
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
of the public at large and to the detriment of our heirs and
|
GNU General Public License for more details.
|
||||||
successors. We intend this dedication to be an overt act of
|
|
||||||
relinquishment in perpetuity of all present and future rights to this
|
|
||||||
software under copyright law.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
You should have received a copy of the GNU General Public License
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
||||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
For more information, please refer to <https://unlicense.org/>
|
|
||||||
|
31
README.md
31
README.md
@ -8,9 +8,30 @@ CLI tool for converting files into header files which one can use to directly em
|
|||||||
|
|
||||||
1. `cargo build`
|
1. `cargo build`
|
||||||
|
|
||||||
## Future improvements
|
## Usage
|
||||||
|
|
||||||
- Not panicking if the source data is not UTF-8 encoded text when operating in text mode
|
### Basic usage
|
||||||
- C support
|
|
||||||
- Choices between std::array, C-style arrays, std::string_view & C strings
|
`$ bin2hpp -i ~/my_image.bmp` will generate an `my_image_bmp.h` file in your current working directory containing something similar to the following:
|
||||||
- Customisable data types & data widths (unsigned vs. signed, uint8_t vs uint16_t, etc.)
|
|
||||||
|
```c
|
||||||
|
// Generated by bin2hpp 0.2.0
|
||||||
|
#ifndef MY_IMAGE_BMP_H
|
||||||
|
#define MY_IMAGE_BMP_H
|
||||||
|
#include <stddef.h>
|
||||||
|
const size_t MYIMAGE_BMP_LEN = 36000054;
|
||||||
|
const unsigned char MYIMAGE_BMP[MYIMAGE_BMP_LEN] = {0x42,0x4d,0x36,0x51,0x25, ...}
|
||||||
|
#else
|
||||||
|
extern const size_t MYIMAGE_BMP_LEN;
|
||||||
|
extern const unsigned char MYIMAGE_BMP[MYIMAGE_BMP_LEN];
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
### Note about CLI arguments
|
||||||
|
|
||||||
|
Command line arguments are not positional. The input file path argument is the
|
||||||
|
only required command line argument. The command line argument parser will
|
||||||
|
choose the first instance of any provided argument. For example, if you provide
|
||||||
|
the `-i` argument twice; only the first `-i ./file/path` will be used. This behaviour
|
||||||
|
should not be relied upon as the implementation of the command line argument
|
||||||
|
parser may change at any time.
|
||||||
|
146
src/cfg.rs
Normal file
146
src/cfg.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::cli::{CliArgs, Constness, ContainerType, Language, LineEnding};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ProgramConfigError {
|
||||||
|
#[error("invalid path provided: \"{0}\"")]
|
||||||
|
InvalidPath(String),
|
||||||
|
#[error("invalid symbol name provided: \"{0}\"")]
|
||||||
|
InvalidSymbolName(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub(crate) input_file_path: PathBuf,
|
||||||
|
pub(crate) output_file_path: PathBuf,
|
||||||
|
pub(crate) overwrite: bool,
|
||||||
|
pub(crate) line_ending: String,
|
||||||
|
pub(crate) language: Language,
|
||||||
|
pub(crate) constness: Constness,
|
||||||
|
pub(crate) symbol_name: String,
|
||||||
|
pub(crate) namespace: Option<String>,
|
||||||
|
pub(crate) guard_name: String,
|
||||||
|
pub(crate) container_type: ContainerType,
|
||||||
|
}
|
||||||
|
|
||||||
|
const CRLF_LINE_ENDING: &'static str = "\r\n";
|
||||||
|
const LF_LINE_ENDING: &'static str = "\n";
|
||||||
|
|
||||||
|
pub fn parse_config_from_args(args: &CliArgs) -> Result<Config, ProgramConfigError> {
|
||||||
|
let output_path = if let Some(path) = &args.output_file_path {
|
||||||
|
path.clone()
|
||||||
|
} else {
|
||||||
|
if let Some(stem) = args.input_file_path.file_stem() {
|
||||||
|
let mut out_path = PathBuf::new();
|
||||||
|
|
||||||
|
let mut filename = String::new();
|
||||||
|
filename.push_str(&stem.to_string_lossy());
|
||||||
|
if let Some(ext) = args.input_file_path.extension() {
|
||||||
|
filename.push_str(&format!("_{}", ext.to_string_lossy()));
|
||||||
|
}
|
||||||
|
if args.language == Language::Cpp {
|
||||||
|
filename.push_str(".hpp");
|
||||||
|
} else {
|
||||||
|
filename.push_str(".h");
|
||||||
|
}
|
||||||
|
out_path.push(filename);
|
||||||
|
|
||||||
|
out_path
|
||||||
|
} else {
|
||||||
|
return Err(ProgramConfigError::InvalidPath(
|
||||||
|
args.input_file_path.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
input_file_path: args.input_file_path.clone(),
|
||||||
|
output_file_path: output_path.clone(),
|
||||||
|
overwrite: args.overwrite,
|
||||||
|
line_ending: if args.line_ending == LineEnding::Unix {
|
||||||
|
LF_LINE_ENDING.into()
|
||||||
|
} else {
|
||||||
|
CRLF_LINE_ENDING.into()
|
||||||
|
},
|
||||||
|
language: args.language,
|
||||||
|
constness: args.constness,
|
||||||
|
symbol_name: match &args.symbol_name {
|
||||||
|
Some(s) => {
|
||||||
|
if s.chars().all(|c| c.is_ascii_alphanumeric()) {
|
||||||
|
s.to_string()
|
||||||
|
} else {
|
||||||
|
return Err(ProgramConfigError::InvalidSymbolName(s.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut stem: String = match args.input_file_path.file_stem() {
|
||||||
|
Some(s) => s.to_string_lossy().to_string(),
|
||||||
|
None => {
|
||||||
|
return Err(ProgramConfigError::InvalidPath(
|
||||||
|
args.input_file_path.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stem.retain(|c| c.is_ascii_alphanumeric());
|
||||||
|
|
||||||
|
let mut ext: String = match args.input_file_path.extension() {
|
||||||
|
Some(s) => s.to_string_lossy().to_string(),
|
||||||
|
None => {
|
||||||
|
return Err(ProgramConfigError::InvalidPath(
|
||||||
|
args.input_file_path.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ext.retain(|c| c.is_ascii_alphanumeric());
|
||||||
|
|
||||||
|
format!("{}_{}", stem.to_ascii_uppercase(), ext.to_ascii_uppercase())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
namespace: match &args.namespace {
|
||||||
|
Some(ns) => {
|
||||||
|
if ns.chars().all(|c| c.is_ascii_alphanumeric()) {
|
||||||
|
Some(ns.to_string())
|
||||||
|
} else {
|
||||||
|
return Err(ProgramConfigError::InvalidSymbolName(ns.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
guard_name: match &args.guard_name {
|
||||||
|
Some(guard) => {
|
||||||
|
if guard.chars().all(|c| c.is_ascii_alphanumeric()) {
|
||||||
|
guard.clone()
|
||||||
|
} else {
|
||||||
|
return Err(ProgramConfigError::InvalidSymbolName(guard.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut stem: String = match output_path.file_stem() {
|
||||||
|
Some(s) => s.to_string_lossy().to_ascii_uppercase(),
|
||||||
|
None => {
|
||||||
|
return Err(ProgramConfigError::InvalidPath(
|
||||||
|
args.input_file_path.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stem.retain(|c| c.is_ascii_alphanumeric() || c == '_');
|
||||||
|
|
||||||
|
let mut ext: String = match output_path.extension() {
|
||||||
|
Some(s) => s.to_string_lossy().to_ascii_uppercase(),
|
||||||
|
None => {
|
||||||
|
return Err(ProgramConfigError::InvalidPath(
|
||||||
|
args.input_file_path.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ext.retain(|c| c.is_ascii_alphanumeric());
|
||||||
|
|
||||||
|
format!("{}_{}", stem, ext)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
container_type: args.container_type,
|
||||||
|
})
|
||||||
|
}
|
223
src/cli.rs
Normal file
223
src/cli.rs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum CliParseError {
|
||||||
|
#[error("missing command line argument: \"{0}\"")]
|
||||||
|
MissingArgument(&'static str),
|
||||||
|
#[error("missing command line argument operand, \"{0}\" requires parameters")]
|
||||||
|
MissingOperand(&'static str),
|
||||||
|
#[error("conflicting option: \"{0}\" (check C vs. C++ mode)")]
|
||||||
|
ConflictingOption(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum ByteType {
|
||||||
|
UnsignedChar,
|
||||||
|
Char,
|
||||||
|
U8,
|
||||||
|
I8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum ContainerType {
|
||||||
|
CArray(ByteType),
|
||||||
|
StdArray(ByteType),
|
||||||
|
CString,
|
||||||
|
StdString,
|
||||||
|
StdStringView,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Default, Clone, Copy)]
|
||||||
|
pub enum Language {
|
||||||
|
#[default]
|
||||||
|
C,
|
||||||
|
Cpp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Default, Clone, Copy)]
|
||||||
|
pub enum LineEnding {
|
||||||
|
#[default]
|
||||||
|
Unix,
|
||||||
|
Windows,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Default, Clone, Copy)]
|
||||||
|
pub enum Constness {
|
||||||
|
#[default]
|
||||||
|
Const,
|
||||||
|
Constexpr,
|
||||||
|
NonConst,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CliArgs {
|
||||||
|
pub(crate) input_file_path: PathBuf,
|
||||||
|
pub(crate) output_file_path: Option<PathBuf>,
|
||||||
|
pub(crate) overwrite: bool,
|
||||||
|
pub(crate) line_ending: LineEnding,
|
||||||
|
pub(crate) language: Language,
|
||||||
|
pub(crate) constness: Constness,
|
||||||
|
pub(crate) symbol_name: Option<String>,
|
||||||
|
pub(crate) namespace: Option<String>,
|
||||||
|
pub(crate) guard_name: Option<String>,
|
||||||
|
pub(crate) container_type: ContainerType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_cli_args(args: &[String]) -> Result<CliArgs, CliParseError> {
|
||||||
|
let input_path = match args.iter().position(|s| *s == "-i") {
|
||||||
|
Some(idx) => match args.get(idx + 1) {
|
||||||
|
Some(p) => Path::new(p),
|
||||||
|
None => return Err(CliParseError::MissingOperand("-i")),
|
||||||
|
},
|
||||||
|
None => return Err(CliParseError::MissingArgument("-i")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let output_path = match args.iter().position(|s| *s == "-o") {
|
||||||
|
Some(idx) => match args.get(idx + 1) {
|
||||||
|
Some(p) => Some(Path::new(p)),
|
||||||
|
None => return Err(CliParseError::MissingOperand("-o")),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let overwrite = match args.iter().find(|s| *s == "--overwrite") {
|
||||||
|
Some(_) => true,
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let symbol_name = match args.iter().position(|s| *s == "--symname") {
|
||||||
|
Some(idx) => match args.get(idx + 1) {
|
||||||
|
Some(s) => Some(s),
|
||||||
|
None => return Err(CliParseError::MissingOperand("--symname")),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let namespace = match args.iter().position(|s| *s == "--namespace") {
|
||||||
|
Some(idx) => match args.get(idx + 1) {
|
||||||
|
Some(s) => Some(s),
|
||||||
|
None => return Err(CliParseError::MissingOperand("--namespace")),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let guard_name = match args.iter().position(|s| *s == "--guard") {
|
||||||
|
Some(idx) => match args.get(idx + 1) {
|
||||||
|
Some(s) => Some(s),
|
||||||
|
None => return Err(CliParseError::MissingOperand("--guard")),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let line_ending = match args.iter().find(|s| *s == "--crlf") {
|
||||||
|
Some(_) => LineEnding::Windows,
|
||||||
|
_ => LineEnding::Unix,
|
||||||
|
};
|
||||||
|
|
||||||
|
let constness: Constness = match args
|
||||||
|
.iter()
|
||||||
|
.find(|s| *s == "--mutable" || *s == "--const" || *s == "--constexpr")
|
||||||
|
{
|
||||||
|
Some(a) => {
|
||||||
|
if a == "--const" {
|
||||||
|
Constness::Const
|
||||||
|
} else if a == "--constexpr" {
|
||||||
|
Constness::Constexpr
|
||||||
|
} else {
|
||||||
|
Constness::NonConst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Constness::Const,
|
||||||
|
};
|
||||||
|
|
||||||
|
let language = match args.iter().find(|s| *s == "--cpp") {
|
||||||
|
Some(_) => Language::Cpp,
|
||||||
|
_ => Language::C,
|
||||||
|
};
|
||||||
|
|
||||||
|
let byte_type = match args
|
||||||
|
.iter()
|
||||||
|
.find(|s| *s == "--char" || *s == "--uchar" || *s == "--u8" || *s == "--i8")
|
||||||
|
{
|
||||||
|
Some(a) => {
|
||||||
|
if a == "--char" {
|
||||||
|
ByteType::Char
|
||||||
|
} else if a == "--uchar" {
|
||||||
|
ByteType::UnsignedChar
|
||||||
|
} else if a == "--u8" {
|
||||||
|
ByteType::U8
|
||||||
|
} else if a == "--i8" {
|
||||||
|
ByteType::I8
|
||||||
|
} else {
|
||||||
|
ByteType::UnsignedChar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => ByteType::UnsignedChar,
|
||||||
|
};
|
||||||
|
|
||||||
|
let container_type = match args.iter().find(|s| {
|
||||||
|
*s == "--carray"
|
||||||
|
|| *s == "--cstring"
|
||||||
|
|| *s == "--stdarray"
|
||||||
|
|| *s == "--stdstring"
|
||||||
|
|| *s == "--stdstringview"
|
||||||
|
}) {
|
||||||
|
Some(a) => {
|
||||||
|
if a == "--carray" {
|
||||||
|
ContainerType::CArray(byte_type)
|
||||||
|
} else if a == "--cstring" {
|
||||||
|
ContainerType::CString
|
||||||
|
} else if a == "--stdarray" {
|
||||||
|
ContainerType::StdArray(byte_type)
|
||||||
|
} else if a == "--stdstring" {
|
||||||
|
ContainerType::StdString
|
||||||
|
} else if a == "--stdstringview" {
|
||||||
|
ContainerType::StdStringView
|
||||||
|
} else {
|
||||||
|
ContainerType::CArray(byte_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => ContainerType::CArray(byte_type),
|
||||||
|
};
|
||||||
|
|
||||||
|
if language == Language::C {
|
||||||
|
match container_type {
|
||||||
|
ContainerType::StdArray(_) => {
|
||||||
|
return Err(CliParseError::ConflictingOption("--stdarray"));
|
||||||
|
}
|
||||||
|
ContainerType::StdString => {
|
||||||
|
return Err(CliParseError::ConflictingOption("--stdstring"));
|
||||||
|
}
|
||||||
|
ContainerType::StdStringView => {
|
||||||
|
return Err(CliParseError::ConflictingOption("--stdstringview"));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
match constness {
|
||||||
|
Constness::Constexpr => {
|
||||||
|
return Err(CliParseError::ConflictingOption("--constexpr"));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(CliArgs {
|
||||||
|
input_file_path: input_path.to_path_buf(),
|
||||||
|
output_file_path: if let Some(path) = output_path {
|
||||||
|
Some(path.to_path_buf())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
overwrite: overwrite,
|
||||||
|
line_ending: line_ending,
|
||||||
|
language: language,
|
||||||
|
constness: constness,
|
||||||
|
symbol_name: symbol_name.cloned(),
|
||||||
|
namespace: namespace.cloned(),
|
||||||
|
guard_name: guard_name.cloned(),
|
||||||
|
container_type: container_type,
|
||||||
|
});
|
||||||
|
}
|
50
src/constants.rs
Normal file
50
src/constants.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
pub const BIN2HPP_HELP_TEXT: &'static str =
|
||||||
|
"Usage:\tbin2hpp -i INPUT_FILE [-o OUTPUT_FILE] [OPTIONS]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
|
||||||
|
-i INPUT_FILE Path to input file to encode
|
||||||
|
(example: -i ../assets/shoot.ogg)
|
||||||
|
|
||||||
|
-o OUTPUT_FILE Optional output file path. Default behaviour is to create
|
||||||
|
the header file in the input file's directory.
|
||||||
|
(example: -o ./my_header.hpp)
|
||||||
|
|
||||||
|
--overwrite Overwrite the output file if it already exists
|
||||||
|
(default: don't overwrite)
|
||||||
|
|
||||||
|
LANGUAGE:
|
||||||
|
|
||||||
|
--cpp Set language features to C++ (runs in C mode by default)
|
||||||
|
|
||||||
|
OUTPUT OPTIONS:
|
||||||
|
|
||||||
|
--lf Use UNIX-style LF line endings (default)
|
||||||
|
--crlf Use Windows-style CRLF line endings
|
||||||
|
--mutable Whether the symbol should not be marked as const
|
||||||
|
--const Whether the symbol should be marked as const (default)
|
||||||
|
--constexpr Whether the symbol should be marked as constexpr (C++ mode)
|
||||||
|
--symname NAME Symbol name
|
||||||
|
--namespace NAME Namespace in which the symbol will exist
|
||||||
|
(uses namespace example {} in C++ mode and prepends
|
||||||
|
EXAMPLE_ to symbol names in C mode)
|
||||||
|
--guard NAME Preprocessor macro to use for the header guard (#ifndef ...)
|
||||||
|
(default: derive from output filename)
|
||||||
|
|
||||||
|
CONTAINER TYPES:
|
||||||
|
|
||||||
|
--carray C-style array (default)
|
||||||
|
--cstring C string, NUL-terminated
|
||||||
|
--stdarray C++ std::array
|
||||||
|
--stdstring C++ std::string
|
||||||
|
--stdstringview C++ std::string_view
|
||||||
|
|
||||||
|
BYTES TYPES FOR ARRAYS:
|
||||||
|
|
||||||
|
--char C's char type
|
||||||
|
--uchar C's unsigned char type (default)
|
||||||
|
--u8 C/C++'s (std::)uint8_t type
|
||||||
|
--i8 C/C++'s (std::)int8_t type
|
||||||
|
|
||||||
|
-h | --help Show this help text
|
||||||
|
-v | --version Print the program's build & version information";
|
345
src/generate.rs
Normal file
345
src/generate.rs
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
HeaderGenerationError,
|
||||||
|
cfg::Config,
|
||||||
|
cli::{ByteType, Constness, ContainerType, Language},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn with_header(
|
||||||
|
config: &Config,
|
||||||
|
in_data: &[u8],
|
||||||
|
buf: &mut String,
|
||||||
|
) -> Result<(), HeaderGenerationError> {
|
||||||
|
// Program banner at the top of the header file
|
||||||
|
write!(
|
||||||
|
buf,
|
||||||
|
"// Generated by {} {}{}",
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_VERSION"),
|
||||||
|
config.line_ending
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Header guard
|
||||||
|
if config.language == Language::Cpp {
|
||||||
|
write!(
|
||||||
|
buf,
|
||||||
|
"#if !defined({}){}",
|
||||||
|
config.guard_name, config.line_ending
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
write!(buf, "#ifndef {}{}", config.guard_name, config.line_ending).unwrap();
|
||||||
|
}
|
||||||
|
write!(buf, "#define {}{}", config.guard_name, config.line_ending).unwrap();
|
||||||
|
|
||||||
|
with_includes(config, buf);
|
||||||
|
with_contents(config, in_data, buf)?;
|
||||||
|
|
||||||
|
write!(buf, "#endif{}", config.line_ending).unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_contents(
|
||||||
|
config: &Config,
|
||||||
|
in_data: &[u8],
|
||||||
|
buf: &mut String,
|
||||||
|
) -> Result<(), HeaderGenerationError> {
|
||||||
|
if config.language == Language::Cpp {
|
||||||
|
// C++ mode
|
||||||
|
|
||||||
|
if let Some(ns) = &config.namespace {
|
||||||
|
write!(buf, "namespace {}{{{}", ns, config.line_ending).unwrap();
|
||||||
|
with_definition(config, in_data, buf)?;
|
||||||
|
write!(buf, "}}{}", config.line_ending).unwrap();
|
||||||
|
} else {
|
||||||
|
with_definition(config, in_data, buf)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// C mode
|
||||||
|
|
||||||
|
with_definition(config, in_data, buf)?;
|
||||||
|
write!(buf, "#else{}", config.line_ending).unwrap();
|
||||||
|
with_declaration(config, in_data, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_includes(config: &Config, buf: &mut String) {
|
||||||
|
// Includes
|
||||||
|
|
||||||
|
let stdint_h = if config.language == Language::Cpp {
|
||||||
|
"#include <cstdint>"
|
||||||
|
} else {
|
||||||
|
"#include <stdint.h>"
|
||||||
|
};
|
||||||
|
|
||||||
|
let stddef_h = if config.language == Language::Cpp {
|
||||||
|
"#include <cstddef>"
|
||||||
|
} else {
|
||||||
|
"#include <stddef.h>"
|
||||||
|
};
|
||||||
|
|
||||||
|
match config.container_type {
|
||||||
|
ContainerType::CArray(byte_type) => {
|
||||||
|
write!(buf, "{}{}", stddef_h, config.line_ending).unwrap();
|
||||||
|
match byte_type {
|
||||||
|
ByteType::U8 => write!(buf, "{}{}", stdint_h, config.line_ending).unwrap(),
|
||||||
|
ByteType::I8 => write!(buf, "{}{}", stdint_h, config.line_ending).unwrap(),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ContainerType::StdArray(byte_type) => {
|
||||||
|
match byte_type {
|
||||||
|
ByteType::U8 => write!(buf, "{}{}", stdint_h, config.line_ending).unwrap(),
|
||||||
|
ByteType::I8 => write!(buf, "{}{}", stdint_h, config.line_ending).unwrap(),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(buf, "#include <array>{}", config.line_ending).unwrap();
|
||||||
|
}
|
||||||
|
ContainerType::CString => {
|
||||||
|
write!(buf, "{}{}", stddef_h, config.line_ending).unwrap();
|
||||||
|
}
|
||||||
|
ContainerType::StdString => {
|
||||||
|
write!(buf, "#include <string>{}", config.line_ending).unwrap();
|
||||||
|
}
|
||||||
|
ContainerType::StdStringView => {
|
||||||
|
write!(buf, "#include <string_view>{}", config.line_ending).unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn definition_modifiers(config: &Config) -> String {
|
||||||
|
let mut out_str: String = String::with_capacity(20);
|
||||||
|
|
||||||
|
if config.language == Language::Cpp {
|
||||||
|
write!(out_str, "inline ").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
match config.constness {
|
||||||
|
Constness::Const => write!(out_str, "{} ", "const").unwrap(),
|
||||||
|
Constness::Constexpr => write!(out_str, "{} ", "constexpr").unwrap(),
|
||||||
|
Constness::NonConst => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
out_str
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn declaration_modifiers(config: &Config) -> String {
|
||||||
|
let mut out_str: String = String::with_capacity(20);
|
||||||
|
|
||||||
|
if config.language == Language::C {
|
||||||
|
write!(out_str, "extern ").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
match config.constness {
|
||||||
|
Constness::Const => write!(out_str, "{} ", "const").unwrap(),
|
||||||
|
Constness::Constexpr => write!(out_str, "{} ", "constexpr").unwrap(),
|
||||||
|
Constness::NonConst => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
out_str
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_definition(
|
||||||
|
config: &Config,
|
||||||
|
in_data: &[u8],
|
||||||
|
buf: &mut String,
|
||||||
|
) -> Result<(), HeaderGenerationError> {
|
||||||
|
let length: usize = match config.container_type {
|
||||||
|
ContainerType::CString => in_data.len() + 1,
|
||||||
|
_ => in_data.len(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let modifiers = definition_modifiers(config);
|
||||||
|
|
||||||
|
match config.container_type {
|
||||||
|
ContainerType::CArray(_) => {
|
||||||
|
write!(
|
||||||
|
buf,
|
||||||
|
"{}{} = {};{}",
|
||||||
|
modifiers,
|
||||||
|
length_data_type_symbol(config),
|
||||||
|
length,
|
||||||
|
config.line_ending
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ContainerType::CString => {
|
||||||
|
write!(
|
||||||
|
buf,
|
||||||
|
"{}{} = {};{}",
|
||||||
|
modifiers,
|
||||||
|
length_data_type_symbol(config),
|
||||||
|
length,
|
||||||
|
config.line_ending
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(
|
||||||
|
buf,
|
||||||
|
"{}{} = {};{}",
|
||||||
|
modifiers,
|
||||||
|
data_type_and_symbol(config, in_data),
|
||||||
|
match config.container_type {
|
||||||
|
ContainerType::CArray(_) => with_array_initialiser(in_data),
|
||||||
|
ContainerType::StdArray(_) => with_array_initialiser(in_data),
|
||||||
|
_ => with_string_initialiser(config, in_data)?,
|
||||||
|
},
|
||||||
|
config.line_ending
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_declaration(config: &Config, in_data: &[u8], buf: &mut String) {
|
||||||
|
let modifiers = declaration_modifiers(config);
|
||||||
|
|
||||||
|
match config.container_type {
|
||||||
|
ContainerType::CArray(_) => {
|
||||||
|
write!(
|
||||||
|
buf,
|
||||||
|
"{}{};{}",
|
||||||
|
modifiers,
|
||||||
|
length_data_type_symbol(config),
|
||||||
|
config.line_ending
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ContainerType::CString => {
|
||||||
|
write!(
|
||||||
|
buf,
|
||||||
|
"{}{};{}",
|
||||||
|
modifiers,
|
||||||
|
length_data_type_symbol(config),
|
||||||
|
config.line_ending
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(
|
||||||
|
buf,
|
||||||
|
"{}{};{}",
|
||||||
|
modifiers,
|
||||||
|
data_type_and_symbol(config, in_data),
|
||||||
|
config.line_ending
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_array_initialiser(in_data: &[u8]) -> String {
|
||||||
|
let mut out_str: String = String::with_capacity(in_data.len() * 6 + 16);
|
||||||
|
|
||||||
|
write!(out_str, "{{").unwrap();
|
||||||
|
for b in in_data {
|
||||||
|
write!(out_str, "{:#x},", b).unwrap();
|
||||||
|
}
|
||||||
|
let _ = out_str.pop();
|
||||||
|
write!(out_str, "}}").unwrap();
|
||||||
|
|
||||||
|
out_str
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_string_initialiser(
|
||||||
|
config: &Config,
|
||||||
|
in_data: &[u8],
|
||||||
|
) -> Result<String, HeaderGenerationError> {
|
||||||
|
let mut out_str: String = String::with_capacity(in_data.len() + 4);
|
||||||
|
|
||||||
|
let string_repr: String = match String::from_utf8(in_data.into()) {
|
||||||
|
Ok(s) => s.escape_default().to_string(),
|
||||||
|
Err(_) => {
|
||||||
|
return Err(HeaderGenerationError::InvalidUtf8String(
|
||||||
|
config.input_file_path.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(out_str, "\"{}\"", string_repr).unwrap();
|
||||||
|
|
||||||
|
Ok(out_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length_data_type_symbol(config: &Config) -> String {
|
||||||
|
let mut out_str = String::with_capacity(36 + config.symbol_name.len());
|
||||||
|
|
||||||
|
let size_type = match config.language {
|
||||||
|
Language::C => "size_t",
|
||||||
|
Language::Cpp => "std::size_t",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(out_str, "{} {}", size_type, length_symbol(config)).unwrap();
|
||||||
|
|
||||||
|
out_str
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length_symbol(config: &Config) -> String {
|
||||||
|
let mut out_str = String::with_capacity(config.symbol_name.len() + 5);
|
||||||
|
|
||||||
|
write!(
|
||||||
|
out_str,
|
||||||
|
"{}{}",
|
||||||
|
config.symbol_name,
|
||||||
|
if config
|
||||||
|
.symbol_name
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_uppercase() || c == '_')
|
||||||
|
{
|
||||||
|
"_LEN"
|
||||||
|
} else {
|
||||||
|
"_len"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
out_str
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data_type_and_symbol(config: &Config, in_data: &[u8]) -> String {
|
||||||
|
let u8_type = match config.language {
|
||||||
|
Language::C => "uint8_t",
|
||||||
|
Language::Cpp => "std::uint8_t",
|
||||||
|
};
|
||||||
|
let i8_type = match config.language {
|
||||||
|
Language::C => "int8_t",
|
||||||
|
Language::Cpp => "std::int8_t",
|
||||||
|
};
|
||||||
|
|
||||||
|
let length_sym = length_symbol(config);
|
||||||
|
|
||||||
|
match config.container_type {
|
||||||
|
ContainerType::CArray(byte_type) => match byte_type {
|
||||||
|
ByteType::UnsignedChar => {
|
||||||
|
format!("unsigned char {}[{}]", config.symbol_name, length_sym)
|
||||||
|
}
|
||||||
|
ByteType::Char => format!("char {}[{}]", config.symbol_name, length_sym),
|
||||||
|
_ => format!("{} {}[{}]", u8_type, config.symbol_name, length_sym),
|
||||||
|
},
|
||||||
|
ContainerType::StdArray(byte_type) => {
|
||||||
|
format!(
|
||||||
|
"std::array<{},{}> {}",
|
||||||
|
match byte_type {
|
||||||
|
ByteType::UnsignedChar => "unsigned char",
|
||||||
|
ByteType::Char => "char",
|
||||||
|
ByteType::U8 => u8_type,
|
||||||
|
ByteType::I8 => i8_type,
|
||||||
|
},
|
||||||
|
in_data.len(),
|
||||||
|
config.symbol_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ContainerType::CString => format!("char {}[{}]", config.symbol_name, length_sym),
|
||||||
|
ContainerType::StdString => format!("std::string {}", config.symbol_name),
|
||||||
|
ContainerType::StdStringView => format!("std::string_view {}", config.symbol_name),
|
||||||
|
}
|
||||||
|
}
|
284
src/main.rs
284
src/main.rs
@ -1,138 +1,52 @@
|
|||||||
use std::{
|
use std::io::{BufReader, BufWriter, Read, Write};
|
||||||
fs::{File, OpenOptions},
|
use std::{env, fs::OpenOptions, process::ExitCode};
|
||||||
io::{self, BufReader, BufWriter, Read, Write},
|
|
||||||
path::PathBuf,
|
|
||||||
process::ExitCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clap::{ArgAction, Parser};
|
use thiserror::Error;
|
||||||
|
|
||||||
#[cfg(windows)]
|
use crate::cfg::Config;
|
||||||
const LINE_ENDING: &'static str = "\r\n";
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
const LINE_ENDING: &'static str = "\n";
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
mod cfg;
|
||||||
#[command(version, about, long_about = None)]
|
mod cli;
|
||||||
struct CliArgs {
|
mod constants;
|
||||||
/// Input file path
|
mod generate;
|
||||||
#[arg(short, long)]
|
|
||||||
input_path: PathBuf,
|
|
||||||
/// Output file path
|
|
||||||
#[arg(short, long)]
|
|
||||||
output_path: Option<PathBuf>,
|
|
||||||
/// Name of the C++ symbol
|
|
||||||
#[arg(short, long)]
|
|
||||||
symbol_name: Option<String>,
|
|
||||||
/// Namespace in which to put the symbol
|
|
||||||
#[arg(short, long)]
|
|
||||||
namespace: Option<String>,
|
|
||||||
/// Whether to operate in binary mode as opposed to text mode (default: text mode)
|
|
||||||
#[arg(short, long, action = ArgAction::SetTrue)]
|
|
||||||
binary: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
let cli_args = CliArgs::parse();
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
if !cli_args.input_path.exists() {
|
if args.len() < 2 {
|
||||||
eprintln!(
|
eprintln!("Not enough arguments, see the --help option");
|
||||||
"file path \"{}\" does not exist",
|
|
||||||
cli_args.input_path.to_string_lossy()
|
|
||||||
);
|
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cli_args.input_path.is_file() {
|
if args.contains(&String::from("--help")) || args.contains(&String::from("-h")) {
|
||||||
eprintln!(
|
println!("{}", constants::BIN2HPP_HELP_TEXT);
|
||||||
"file path \"{}\" is not a file",
|
return ExitCode::SUCCESS;
|
||||||
cli_args.input_path.to_string_lossy()
|
|
||||||
);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive output path from cwd & original filename if not provided in CLI
|
if args.contains(&String::from("--version")) || args.contains(&String::from("-v")) {
|
||||||
|
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
|
||||||
|
return ExitCode::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
let cwd = match std::env::current_dir() {
|
let args = match cli::parse_cli_args(&args) {
|
||||||
Ok(p) => p,
|
Ok(args) => args,
|
||||||
Err(_) => {
|
Err(e) => {
|
||||||
eprintln!("environment's current working directory is unavailable");
|
eprintln!("Failed to parse command line arguments: {}", e);
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let cfg = match cfg::parse_config_from_args(&args) {
|
||||||
|
Ok(cfg) => cfg,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Invalid configuration specified: {}", e);
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let input_filename = match cli_args.input_path.file_name() {
|
match write_header(&cfg) {
|
||||||
Some(f) => f,
|
|
||||||
None => {
|
|
||||||
eprintln!(
|
|
||||||
"input file path \"{}\" does not contain a valid filename",
|
|
||||||
cli_args.input_path.to_string_lossy()
|
|
||||||
);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let output_path = match cli_args.output_path {
|
|
||||||
Some(p) => p,
|
|
||||||
None => cwd.join(input_filename).with_extension("hpp"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let symbol_name = match cli_args.symbol_name {
|
|
||||||
Some(s) => s,
|
|
||||||
None => input_filename
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
.chars()
|
|
||||||
.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let input_file = match OpenOptions::new().read(true).open(cli_args.input_path) {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(error) => {
|
|
||||||
eprintln!("failed to open input file for reading: {}", error);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let buf = match read_file(&input_file) {
|
|
||||||
Ok(data) => data,
|
|
||||||
Err(error) => {
|
|
||||||
eprintln!("failed read input file: {}", error);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let formatted = match cli_args.binary {
|
|
||||||
Some(true) => format_as_binary(&buf),
|
|
||||||
_ => format_as_text(&buf),
|
|
||||||
};
|
|
||||||
|
|
||||||
let out_src = match cli_args.binary {
|
|
||||||
Some(true) => {
|
|
||||||
generate_src_for_array(&formatted, buf.len(), &symbol_name, cli_args.namespace)
|
|
||||||
}
|
|
||||||
_ => generate_src_for_string(&formatted, &symbol_name, cli_args.namespace),
|
|
||||||
};
|
|
||||||
|
|
||||||
let output_file = match OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.truncate(true)
|
|
||||||
.create(true)
|
|
||||||
.open(output_path)
|
|
||||||
{
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(error) => {
|
|
||||||
eprintln!("failed to open output file for writing: {}", error);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut writer = BufWriter::new(output_file);
|
|
||||||
match writer.write_all(out_src.as_bytes()) {
|
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(error) => {
|
Err(e) => {
|
||||||
eprintln!("failed to write to output file: {}", error);
|
eprintln!("{}", e);
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -140,112 +54,48 @@ fn main() -> ExitCode {
|
|||||||
return ExitCode::SUCCESS;
|
return ExitCode::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_file(f: &File) -> io::Result<Vec<u8>> {
|
#[derive(Error, Debug)]
|
||||||
let buf_size: u64 = match f.metadata() {
|
pub enum HeaderGenerationError {
|
||||||
Ok(metadata) => metadata.len(),
|
#[error("I/O error: \"{0}\"")]
|
||||||
Err(_) => 0x1000, // just preallocate 4 KiB otherwise
|
IOError(String),
|
||||||
};
|
#[error("input file doesn't contain valid UTF-8: \"{0}\"")]
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(buf_size as usize);
|
InvalidUtf8String(String),
|
||||||
|
|
||||||
let mut reader = BufReader::new(f);
|
|
||||||
reader.read_to_end(&mut buf)?;
|
|
||||||
|
|
||||||
return Ok(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format a slice of bytes into an array-of-bytes initialiser list
|
fn write_header(config: &Config) -> Result<(), HeaderGenerationError> {
|
||||||
fn format_as_binary(data: &[u8]) -> String {
|
let in_file = OpenOptions::new()
|
||||||
let mut formatted = data
|
.read(true)
|
||||||
.iter()
|
.write(false)
|
||||||
.map(|b| format!("{:#x},", b))
|
.open(config.input_file_path.clone())
|
||||||
.collect::<String>();
|
.expect("failed to open input file");
|
||||||
if !formatted.is_empty() {
|
|
||||||
formatted.pop().unwrap(); // remove trailing ','
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatted;
|
let mut buf = String::with_capacity(if let Ok(meta) = in_file.metadata() {
|
||||||
}
|
meta.len() as usize
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
});
|
||||||
|
|
||||||
/// Format a slice of bytes into a string literal (without quotes)
|
let mut reader = BufReader::new(in_file);
|
||||||
fn format_as_text(data: &[u8]) -> String {
|
let mut in_data: Vec<u8> = Vec::new();
|
||||||
// FIXME: this will currently panic if the input file was not UTF-8 encoded!
|
let n_bytes_read = match reader.read_to_end(&mut in_data) {
|
||||||
return String::from_utf8(data.to_vec())
|
Ok(n) => n,
|
||||||
.unwrap()
|
Err(e) => return Err(HeaderGenerationError::IOError(e.to_string())),
|
||||||
.escape_default()
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_src_for_array(
|
|
||||||
array_contents: &str,
|
|
||||||
array_len: usize,
|
|
||||||
symbol_name: &str,
|
|
||||||
ns_name: Option<String>,
|
|
||||||
) -> String {
|
|
||||||
// Includes
|
|
||||||
let mut out_string: String = String::with_capacity(array_contents.len() + 0x100);
|
|
||||||
out_string.push_str("#include <array>");
|
|
||||||
out_string.push_str(LINE_ENDING);
|
|
||||||
out_string.push_str("#include <cstdint>");
|
|
||||||
out_string.push_str(LINE_ENDING);
|
|
||||||
|
|
||||||
// Namespace
|
|
||||||
match ns_name {
|
|
||||||
Some(ref namespace) => out_string.push_str(format!("namespace {}{{", namespace).as_str()),
|
|
||||||
None => (),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Array declaration
|
generate::with_header(config, &in_data[..n_bytes_read], &mut buf)?;
|
||||||
out_string.push_str(
|
|
||||||
format!(
|
|
||||||
"constexpr std::array<std::uint8_t,{}> {}{{{}}};",
|
|
||||||
array_len, symbol_name, array_contents
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Close namespace (if need be)
|
let out_file = OpenOptions::new()
|
||||||
match ns_name {
|
.write(true)
|
||||||
Some(_) => out_string.push_str("}"),
|
.truncate(config.overwrite)
|
||||||
None => (),
|
.create_new(!config.overwrite)
|
||||||
|
.open(config.output_file_path.clone())
|
||||||
|
.expect("failed to open input file");
|
||||||
|
let mut writer = BufWriter::new(out_file);
|
||||||
|
|
||||||
|
match writer.write_all(buf.as_bytes()) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => return Err(HeaderGenerationError::IOError(e.to_string())),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Trailing newline
|
Ok(())
|
||||||
out_string.push_str(LINE_ENDING);
|
|
||||||
|
|
||||||
return out_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_src_for_string(
|
|
||||||
string_contents: &str,
|
|
||||||
symbol_name: &str,
|
|
||||||
ns_name: Option<String>,
|
|
||||||
) -> String {
|
|
||||||
// Includes
|
|
||||||
let mut out_string: String = String::with_capacity(string_contents.len() + 0x100);
|
|
||||||
|
|
||||||
// Namespace
|
|
||||||
match ns_name {
|
|
||||||
Some(ref namespace) => out_string.push_str(format!("namespace {}{{", namespace).as_str()),
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
// String initialisation
|
|
||||||
out_string.push_str(
|
|
||||||
format!(
|
|
||||||
"constexpr const char* {} = \"{}\";",
|
|
||||||
symbol_name, string_contents
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Close namespace (if need be)
|
|
||||||
match ns_name {
|
|
||||||
Some(_) => out_string.push_str("}"),
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Trailing newline
|
|
||||||
out_string.push_str(LINE_ENDING);
|
|
||||||
|
|
||||||
return out_string;
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user