initial commit

This commit is contained in:
Adam Macdonald 2025-04-08 19:00:24 +01:00
commit f4fc652ed3
5 changed files with 526 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Cargo
target/

244
Cargo.lock generated Normal file
View File

@ -0,0 +1,244 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
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]]
name = "bin2hpp"
version = "0.1.0"
dependencies = [
"clap",
]
[[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]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
[[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"

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "bin2hpp"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.35", features = ["derive"] }
[profile.optimised]
inherits = "release"
opt-level = 3
strip = true
lto = true
codegen-units = 1

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# bin2hpp
> _One day we'll get #embed and std::embed, but today is not that day._
CLI tool for converting files into header files which one can use to directly embed data in their C++ projects.
## Building
1. `cargo build`
## Future improvements
- Not panicking if the source data is not UTF-8 encoded text when operating in text mode
- C support
- Choices between std::array, C-style arrays, std::string_view & C strings
- Customisable data types & data widths (unsigned vs. signed, uint8_t vs uint16_t, etc.)

250
src/main.rs Normal file
View File

@ -0,0 +1,250 @@
use std::{
fs::{File, OpenOptions},
io::{self, BufReader, BufWriter, Read, Write},
path::PathBuf,
process::ExitCode,
};
use clap::{ArgAction, Parser};
#[cfg(windows)]
const LINE_ENDING: &'static str = "\r\n";
#[cfg(not(windows))]
const LINE_ENDING: &'static str = "\n";
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct CliArgs {
/// Input file path
#[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 {
let cli_args = CliArgs::parse();
if !cli_args.input_path.exists() {
eprintln!(
"file path \"{}\" does not exist",
cli_args.input_path.to_string_lossy()
);
return ExitCode::FAILURE;
}
if !cli_args.input_path.is_file() {
eprintln!(
"file path \"{}\" is not a file",
cli_args.input_path.to_string_lossy()
);
return ExitCode::FAILURE;
}
// Derive output path from cwd & original filename if not provided in CLI
let cwd = match std::env::current_dir() {
Ok(p) => p,
Err(_) => {
eprintln!("environment's current working directory is unavailable");
return ExitCode::FAILURE;
}
};
let input_filename = match cli_args.input_path.file_name() {
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)
.create_new(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(_) => (),
Err(error) => {
eprintln!("failed to write to output file: {}", error);
return ExitCode::FAILURE;
}
};
return ExitCode::SUCCESS;
}
fn read_file(f: &File) -> io::Result<Vec<u8>> {
let buf_size: u64 = match f.metadata() {
Ok(metadata) => metadata.len(),
Err(_) => 0x1000, // just preallocate 4 KiB otherwise
};
let mut buf: Vec<u8> = Vec::with_capacity(buf_size as usize);
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 format_as_binary(data: &[u8]) -> String {
let mut formatted = data
.iter()
.map(|b| format!("{:#x},", b))
.collect::<String>();
if !formatted.is_empty() {
formatted.pop().unwrap(); // remove trailing ','
}
return formatted;
}
/// Format a slice of bytes into a string literal (without quotes)
fn format_as_text(data: &[u8]) -> String {
// FIXME: this will currently panic if the input file was not UTF-8 encoded!
return String::from_utf8(data.to_vec())
.unwrap()
.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
out_string.push_str(
format!(
"constexpr std::array<std::uint8_t,{}> {}{{{}}};",
array_len, symbol_name, array_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;
}
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;
}