0.2.0 release

This commit is contained in:
2025-08-17 00:44:45 +01:00
parent 039bfcc201
commit e4780cf729
9 changed files with 902 additions and 455 deletions

146
src/cfg.rs Normal file
View 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
View 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
View 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
View 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),
}
}

View File

@@ -1,138 +1,52 @@
use std::{
fs::{File, OpenOptions},
io::{self, BufReader, BufWriter, Read, Write},
path::PathBuf,
process::ExitCode,
};
use std::io::{BufReader, BufWriter, Read, Write};
use std::{env, fs::OpenOptions, process::ExitCode};
use clap::{ArgAction, Parser};
use thiserror::Error;
#[cfg(windows)]
const LINE_ENDING: &'static str = "\r\n";
#[cfg(not(windows))]
const LINE_ENDING: &'static str = "\n";
use crate::cfg::Config;
#[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>,
}
mod cfg;
mod cli;
mod constants;
mod generate;
fn main() -> ExitCode {
let cli_args = CliArgs::parse();
let args: Vec<String> = env::args().collect();
if !cli_args.input_path.exists() {
eprintln!(
"file path \"{}\" does not exist",
cli_args.input_path.to_string_lossy()
);
if args.len() < 2 {
eprintln!("Not enough arguments, see the --help option");
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;
if args.contains(&String::from("--help")) || args.contains(&String::from("-h")) {
println!("{}", constants::BIN2HPP_HELP_TEXT);
return ExitCode::SUCCESS;
}
// 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() {
Ok(p) => p,
Err(_) => {
eprintln!("environment's current working directory is unavailable");
let args = match cli::parse_cli_args(&args) {
Ok(args) => args,
Err(e) => {
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;
}
};
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)
.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()) {
match write_header(&cfg) {
Ok(_) => (),
Err(error) => {
eprintln!("failed to write to output file: {}", error);
Err(e) => {
eprintln!("{}", e);
return ExitCode::FAILURE;
}
};
@@ -140,112 +54,48 @@ fn main() -> ExitCode {
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);
#[derive(Error, Debug)]
pub enum HeaderGenerationError {
#[error("I/O error: \"{0}\"")]
IOError(String),
#[error("input file doesn't contain valid UTF-8: \"{0}\"")]
InvalidUtf8String(String),
}
/// 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 ','
}
fn write_header(config: &Config) -> Result<(), HeaderGenerationError> {
let in_file = OpenOptions::new()
.read(true)
.write(false)
.open(config.input_file_path.clone())
.expect("failed to open input file");
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)
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 => (),
let mut reader = BufReader::new(in_file);
let mut in_data: Vec<u8> = Vec::new();
let n_bytes_read = match reader.read_to_end(&mut in_data) {
Ok(n) => n,
Err(e) => return Err(HeaderGenerationError::IOError(e.to_string())),
};
// Array declaration
out_string.push_str(
format!(
"constexpr std::array<std::uint8_t,{}> {}{{{}}};",
array_len, symbol_name, array_contents
)
.as_str(),
);
generate::with_header(config, &in_data[..n_bytes_read], &mut buf)?;
// Close namespace (if need be)
match ns_name {
Some(_) => out_string.push_str("}"),
None => (),
let out_file = OpenOptions::new()
.write(true)
.truncate(config.overwrite)
.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
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;
Ok(())
}