initial commit
This commit is contained in:
commit
f4fc652ed3
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Cargo
|
||||
target/
|
244
Cargo.lock
generated
Normal file
244
Cargo.lock
generated
Normal 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
14
Cargo.toml
Normal 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
16
README.md
Normal 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
250
src/main.rs
Normal 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user