Compare commits

...

7 Commits

Author SHA1 Message Date
987152f14f 0.3.0: fix linkage in C mode 2025-08-17 20:01:04 +01:00
5657325d97 an header file 2025-08-17 18:33:25 +01:00
8054e10c7b 0.2.5: fix signed literals 2025-08-17 17:52:15 +01:00
077861f13c update README with more examples 2025-08-17 16:20:06 +01:00
0b418a728f std::array uses length #define now 2025-08-17 16:10:21 +01:00
3ba2eadf3e ignores 2025-08-17 15:58:09 +01:00
184fb9eda3 0.2.4: fix length defines 2025-08-17 15:52:13 +01:00
9 changed files with 280 additions and 240 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
# Cargo # Cargo
target/ target/
# Slop
.vscode/

2
Cargo.lock generated
View File

@@ -4,7 +4,7 @@ version = 4
[[package]] [[package]]
name = "bin2hpp" name = "bin2hpp"
version = "0.2.3" version = "0.3.0"
dependencies = [ dependencies = [
"thiserror", "thiserror",
] ]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bin2hpp" name = "bin2hpp"
version = "0.2.3" version = "0.3.0"
authors = ["Adam Macdonald"] authors = ["Adam Macdonald"]
edition = "2024" edition = "2024"
license = "GPL-3.0-only" license = "GPL-3.0-only"

View File

@@ -6,24 +6,52 @@ CLI tool for converting files into header files which one can use to directly em
## Building ## Building
1. `cargo build` `cargo build`, or `cargo [build | run | install] --profile optimised` to build an optimised binary.
The performance (but potentially not the executable size) can be further optimised by telling the compiler to use a more modern instruction set: `RUSTFLAGS="-C target-cpu=native"`.
## Usage ## Usage
### Basic usage ### Basic usage
`$ bin2hpp -i ~/my_image.bmp` will generate an `my_image_bmp.h` file in your current working directory containing something similar to the following: `$ bin2hpp -i my_library.dll -o c_header.h` will generate a `c_header.h` file in your current working directory containing something similar to the following:
```c ```c
// Generated by bin2hpp 0.2.0 // Generated by bin2hpp 0.3.0
#ifndef MY_IMAGE_BMP_H #ifdef MY_LIBRARY_DLL_INIT
#define MY_IMAGE_BMP_H #define MY_LIBRARY_DLL_LEN 9728
#include <stddef.h> const unsigned char MY_LIBRARY_DLL[MY_LIBRARY_DLL_LEN] = {77,90,144,0,3,0,0,0,4, ...};
const size_t MYIMAGE_BMP_LEN = 36000054;
const unsigned char MYIMAGE_BMP[MYIMAGE_BMP_LEN] = {0x42,0x4d,0x36,0x51,0x25, ...}
#else #else
extern const size_t MYIMAGE_BMP_LEN; #define MY_LIBRARY_DLL_LEN 9728
extern const unsigned char MYIMAGE_BMP[MYIMAGE_BMP_LEN]; extern const unsigned char MY_LIBRARY_DLL[MY_LIBRARY_DLL_LEN];
#endif
```
Note how in **C mode** one must `#define MY_LIBRARY_DLL_INIT`, or whatever the corresponding implementation macro is for your generated header file, in exactly _one_ C source file (`*.c`, not in a `*.h` file).
Example:
`main.c`
```c
// In any other C source files other than the first: do not redefine
#define MY_LIBRARY_DLL_INIT
#include "c_header.h"
int main() {
return MY_LIBRARY_DLL_LEN;
}
```
Alternatively, in **C++ mode**: `bin2hpp -i my_library.dll -o cpp_header.hpp --cpp --constexpr --stdarray --i8`
```cpp
// Generated by bin2hpp 0.3.0
#if !defined(CPP_HEADER_HPP)
#define CPP_HEADER_HPP
#include <cstdint>
#include <array>
#define MY_LIBRARY_DLL_LEN 9728
inline constexpr std::array<std::int8_t,MY_LIBRARY_DLL_LEN> MY_LIBRARY_DLL = {77,90,-112,0,3,0,0,0,4, ...};
#endif #endif
``` ```

View File

@@ -22,7 +22,8 @@ pub struct Config {
pub(crate) constness: Constness, pub(crate) constness: Constness,
pub(crate) symbol_name: String, pub(crate) symbol_name: String,
pub(crate) namespace: Option<String>, pub(crate) namespace: Option<String>,
pub(crate) guard_name: String, pub(crate) impl_macro: String,
pub(crate) header_guard: String,
pub(crate) container_type: ContainerType, pub(crate) container_type: ContainerType,
} }
@@ -56,6 +57,54 @@ pub fn parse_config_from_args(args: &CliArgs) -> Result<Config, ProgramConfigErr
} }
}; };
let symbol_name = match &args.symbol_name {
Some(s) => {
if s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
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() || c == '_');
let mut ext: String = match args.input_file_path.extension() {
Some(s) => format!("_{}", s.to_string_lossy()),
None => "".to_string(),
};
ext.retain(|c| c.is_ascii_alphanumeric() || c == '_');
format!("{}{}", stem.to_ascii_uppercase(), ext.to_ascii_uppercase())
}
};
let header_guard = match output_path.file_stem() {
Some(stem) => {
format!(
"{}_{}",
stem.to_string_lossy().to_uppercase(),
if let Some(ext) = output_path.extension() {
ext.to_string_lossy().to_uppercase()
} else {
"".into()
}
)
}
None => {
return Err(ProgramConfigError::InvalidPath(
output_path.to_string_lossy().to_string(),
));
}
};
Ok(Config { Ok(Config {
input_file_path: args.input_file_path.clone(), input_file_path: args.input_file_path.clone(),
output_file_path: output_path.clone(), output_file_path: output_path.clone(),
@@ -67,34 +116,7 @@ pub fn parse_config_from_args(args: &CliArgs) -> Result<Config, ProgramConfigErr
}, },
language: args.language, language: args.language,
constness: args.constness, constness: args.constness,
symbol_name: match &args.symbol_name { symbol_name: symbol_name.clone(),
Some(s) => {
if s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
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) => format!("_{}", s.to_string_lossy()),
None => "".to_string(),
};
ext.retain(|c| c.is_ascii_alphanumeric());
format!("{}{}", stem.to_ascii_uppercase(), ext.to_ascii_uppercase())
}
},
namespace: match &args.namespace { namespace: match &args.namespace {
Some(ns) => { Some(ns) => {
if ns.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') { if ns.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
@@ -105,7 +127,7 @@ pub fn parse_config_from_args(args: &CliArgs) -> Result<Config, ProgramConfigErr
} }
_ => None, _ => None,
}, },
guard_name: match &args.guard_name { impl_macro: match &args.impl_macro_name {
Some(guard) => { Some(guard) => {
if guard.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') { if guard.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
guard.clone() guard.clone()
@@ -114,29 +136,10 @@ pub fn parse_config_from_args(args: &CliArgs) -> Result<Config, ProgramConfigErr
} }
} }
_ => { _ => {
let mut stem: String = match output_path.file_stem() { format!("{}_INIT", symbol_name.to_uppercase())
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)
} }
}, },
header_guard: header_guard,
container_type: args.container_type, container_type: args.container_type,
}) })
} }

View File

@@ -20,6 +20,26 @@ pub enum ByteType {
I8, I8,
} }
impl ByteType {
pub fn to_c_type(&self) -> &'static str {
match self {
ByteType::UnsignedChar => "unsigned char",
ByteType::Char => "char",
ByteType::U8 => "uint8_t",
ByteType::I8 => "int8_t",
}
}
pub fn to_cpp_type(&self) -> &'static str {
match self {
ByteType::UnsignedChar => self.to_c_type(),
ByteType::Char => self.to_c_type(),
ByteType::U8 => "std::uint8_t",
ByteType::I8 => "std::int8_t",
}
}
}
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
pub enum ContainerType { pub enum ContainerType {
CArray(ByteType), CArray(ByteType),
@@ -61,7 +81,7 @@ pub struct CliArgs {
pub(crate) constness: Constness, pub(crate) constness: Constness,
pub(crate) symbol_name: Option<String>, pub(crate) symbol_name: Option<String>,
pub(crate) namespace: Option<String>, pub(crate) namespace: Option<String>,
pub(crate) guard_name: Option<String>, pub(crate) impl_macro_name: Option<String>,
pub(crate) container_type: ContainerType, pub(crate) container_type: ContainerType,
} }
@@ -103,10 +123,10 @@ pub fn parse_cli_args(args: &[String]) -> Result<CliArgs, CliParseError> {
None => None, None => None,
}; };
let guard_name = match args.iter().position(|s| *s == "--guard") { let impl_macro_name = match args.iter().position(|s| *s == "--impl") {
Some(idx) => match args.get(idx + 1) { Some(idx) => match args.get(idx + 1) {
Some(s) => Some(s), Some(s) => Some(s),
None => return Err(CliParseError::MissingOperand("--guard")), None => return Err(CliParseError::MissingOperand("--impl")),
}, },
None => None, None => None,
}; };
@@ -217,7 +237,7 @@ pub fn parse_cli_args(args: &[String]) -> Result<CliArgs, CliParseError> {
constness: constness, constness: constness,
symbol_name: symbol_name.cloned(), symbol_name: symbol_name.cloned(),
namespace: namespace.cloned(), namespace: namespace.cloned(),
guard_name: guard_name.cloned(), impl_macro_name: impl_macro_name.cloned(),
container_type: container_type, container_type: container_type,
}); });
} }

View File

@@ -28,8 +28,8 @@ OUTPUT OPTIONS:
--symname NAME Symbol name --symname NAME Symbol name
--namespace NAME Namespace in which the symbol will exist (C++ mode) --namespace NAME Namespace in which the symbol will exist (C++ mode)
--guard NAME Preprocessor macro to use for the header guard (#ifndef ...) --impl NAME Preprocessor macro to use for the implementation guard
(default: derive from output filename) in C mode (default: derive from symbol name)
CONTAINER TYPES: CONTAINER TYPES:

View File

@@ -1,16 +1,21 @@
use std::fmt::Write; use std::fmt::Write;
use thiserror::Error;
use crate::{ use crate::{
HeaderGenerationError,
cfg::Config, cfg::Config,
cli::{ByteType, Constness, ContainerType, Language}, cli::{ByteType, Constness, ContainerType, Language},
}; };
pub fn with_header( #[derive(Error, Debug)]
config: &Config, pub enum HeaderGenerationError {
in_data: &[u8], #[error("input file doesn't contain valid UTF-8: \"{0}\"")]
buf: &mut String, InvalidUtf8String(String),
) -> Result<(), HeaderGenerationError> { }
pub fn header(config: &Config, in_data: &[u8]) -> Result<String, HeaderGenerationError> {
let mut buf = String::with_capacity(in_data.len() * 6);
// Program banner at the top of the header file // Program banner at the top of the header file
write!( write!(
buf, buf,
@@ -22,24 +27,30 @@ pub fn with_header(
.unwrap(); .unwrap();
// Header guard // Header guard
if config.language == Language::Cpp { if config.language == Language::Cpp {
// C++ style
write!( write!(
buf, buf,
"#if !defined({}){}", "#if !defined({}){}",
config.guard_name, config.line_ending config.header_guard, config.line_ending
) )
.unwrap(); .unwrap();
write!(buf, "#define {}{}", config.header_guard, config.line_ending).unwrap();
} else { } else {
write!(buf, "#ifndef {}{}", config.guard_name, config.line_ending).unwrap(); // C style
write!(buf, "#ifdef {}{}", config.impl_macro, config.line_ending).unwrap();
} }
write!(buf, "#define {}{}", config.guard_name, config.line_ending).unwrap();
with_includes(config, buf); // Includes & main content
with_contents(config, in_data, buf)?;
with_contents(config, in_data, &mut buf)?;
// End header guard
write!(buf, "#endif{}", config.line_ending).unwrap(); write!(buf, "#endif{}", config.line_ending).unwrap();
Ok(()) Ok(buf)
} }
pub fn with_contents( pub fn with_contents(
@@ -47,6 +58,21 @@ pub fn with_contents(
in_data: &[u8], in_data: &[u8],
buf: &mut String, buf: &mut String,
) -> Result<(), HeaderGenerationError> { ) -> Result<(), HeaderGenerationError> {
with_includes(config, buf);
let length: usize = match config.container_type {
ContainerType::CString => in_data.len() + 1, // +1 for NUL terminator
_ => in_data.len(),
};
write!(
buf,
"{}{}",
length_define(config, length),
config.line_ending
)
.unwrap();
if config.language == Language::Cpp { if config.language == Language::Cpp {
// C++ mode // C++ mode
@@ -61,9 +87,18 @@ pub fn with_contents(
// C mode // C mode
with_definition(config, in_data, buf)?; with_definition(config, in_data, buf)?;
write!(buf, "#else{}", config.line_ending).unwrap(); write!(buf, "#else{}", config.line_ending).unwrap();
with_includes(config, buf); with_includes(config, buf);
with_declaration(config, in_data, buf); write!(
buf,
"{}{}",
length_define(config, length),
config.line_ending
)
.unwrap();
with_declaration(config, buf);
} }
Ok(()) Ok(())
@@ -78,15 +113,8 @@ pub fn with_includes(config: &Config, buf: &mut String) {
"#include <stdint.h>" "#include <stdint.h>"
}; };
let stddef_h = if config.language == Language::Cpp {
"#include <cstddef>"
} else {
"#include <stddef.h>"
};
match config.container_type { match config.container_type {
ContainerType::CArray(byte_type) => { ContainerType::CArray(byte_type) => {
write!(buf, "{}{}", stddef_h, config.line_ending).unwrap();
match byte_type { match byte_type {
ByteType::U8 => write!(buf, "{}{}", stdint_h, config.line_ending).unwrap(), ByteType::U8 => write!(buf, "{}{}", stdint_h, config.line_ending).unwrap(),
ByteType::I8 => write!(buf, "{}{}", stdint_h, config.line_ending).unwrap(), ByteType::I8 => write!(buf, "{}{}", stdint_h, config.line_ending).unwrap(),
@@ -102,15 +130,13 @@ pub fn with_includes(config: &Config, buf: &mut String) {
write!(buf, "#include <array>{}", config.line_ending).unwrap(); write!(buf, "#include <array>{}", config.line_ending).unwrap();
} }
ContainerType::CString => {
write!(buf, "{}{}", stddef_h, config.line_ending).unwrap();
}
ContainerType::StdString => { ContainerType::StdString => {
write!(buf, "#include <string>{}", config.line_ending).unwrap(); write!(buf, "#include <string>{}", config.line_ending).unwrap();
} }
ContainerType::StdStringView => { ContainerType::StdStringView => {
write!(buf, "#include <string_view>{}", config.line_ending).unwrap(); write!(buf, "#include <string_view>{}", config.line_ending).unwrap();
} }
_ => (),
}; };
} }
@@ -151,47 +177,16 @@ pub fn with_definition(
in_data: &[u8], in_data: &[u8],
buf: &mut String, buf: &mut String,
) -> Result<(), HeaderGenerationError> { ) -> Result<(), HeaderGenerationError> {
let length: usize = match config.container_type {
ContainerType::CString => in_data.len() + 1,
_ => in_data.len(),
};
let modifiers = definition_modifiers(config); 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!( write!(
buf, buf,
"{}{} = {};{}", "{}{} = {};{}",
modifiers, modifiers,
data_type_and_symbol(config, in_data), data_type_and_symbol(config),
match config.container_type { match config.container_type {
ContainerType::CArray(_) => with_array_initialiser(in_data), ContainerType::CArray(_) => with_array_initialiser(config, in_data),
ContainerType::StdArray(_) => with_array_initialiser(in_data), ContainerType::StdArray(_) => with_array_initialiser(config, in_data),
_ => with_string_initialiser(config, in_data)?, _ => with_string_initialiser(config, in_data)?,
}, },
config.line_ending config.line_ending
@@ -201,42 +196,47 @@ pub fn with_definition(
Ok(()) Ok(())
} }
pub fn with_declaration(config: &Config, in_data: &[u8], buf: &mut String) { pub fn with_declaration(config: &Config, buf: &mut String) {
let modifiers = declaration_modifiers(config); let modifiers = declaration_modifiers(config);
match config.container_type {
ContainerType::CArray(_) => {
write!(buf, "{}{};{}", modifiers, in_data.len(), config.line_ending).unwrap();
}
ContainerType::CString => {
write!(
buf,
"{}{};{}",
modifiers,
in_data.len() + 1,
config.line_ending
)
.unwrap();
}
_ => (),
};
write!( write!(
buf, buf,
"{}{};{}", "{}{};{}",
modifiers, modifiers,
data_type_and_symbol(config, in_data), data_type_and_symbol(config),
config.line_ending config.line_ending
) )
.unwrap(); .unwrap();
} }
pub fn with_array_initialiser(in_data: &[u8]) -> String { pub fn with_array_initialiser(config: &Config, in_data: &[u8]) -> String {
let mut out_str: String = String::with_capacity(in_data.len() * 6 + 16); let mut out_str: String = String::with_capacity(in_data.len() * 6 + 16);
let signed_literals = match config.container_type {
ContainerType::CArray(byte_type) => match byte_type {
ByteType::Char => true,
ByteType::I8 => true,
_ => false,
},
ContainerType::StdArray(byte_type) => match byte_type {
ByteType::Char => true,
ByteType::I8 => true,
_ => false,
},
_ => false,
};
write!(out_str, "{{").unwrap(); write!(out_str, "{{").unwrap();
for b in in_data {
write!(out_str, "{:#x},", b).unwrap(); if signed_literals {
for b in in_data {
let b = i8::from_ne_bytes([*b; 1]);
write!(out_str, "{},", b).unwrap();
}
} else {
for b in in_data {
write!(out_str, "{},", b).unwrap();
}
} }
let _ = out_str.pop(); let _ = out_str.pop();
write!(out_str, "}}").unwrap(); write!(out_str, "}}").unwrap();
@@ -264,15 +264,10 @@ pub fn with_string_initialiser(
Ok(out_str) Ok(out_str)
} }
pub fn length_data_type_symbol(config: &Config) -> String { pub fn length_define(config: &Config, length: usize) -> String {
let mut out_str = String::with_capacity(36 + config.symbol_name.len()); let mut out_str = String::with_capacity(36 + config.symbol_name.len());
let size_type = match config.language { write!(out_str, "#define {} {}", length_symbol(config), length).unwrap();
Language::C => "size_t",
Language::Cpp => "std::size_t",
};
write!(out_str, "{} {}", size_type, length_symbol(config)).unwrap();
out_str out_str
} }
@@ -280,60 +275,54 @@ pub fn length_data_type_symbol(config: &Config) -> String {
pub fn length_symbol(config: &Config) -> String { pub fn length_symbol(config: &Config) -> String {
let mut out_str = String::with_capacity(config.symbol_name.len() + 5); let mut out_str = String::with_capacity(config.symbol_name.len() + 5);
write!( write!(out_str, "{}_LEN", config.symbol_name.to_uppercase(),).unwrap();
out_str,
"{}{}",
config.symbol_name,
if config
.symbol_name
.chars()
.all(|c| c.is_uppercase() || c == '_')
{
"_LEN"
} else {
"_len"
},
)
.unwrap();
out_str out_str
} }
pub fn data_type_and_symbol(config: &Config, in_data: &[u8]) -> String { pub fn data_type_and_symbol(config: &Config) -> String {
let u8_type = match config.language { let mut out_str = String::with_capacity(config.symbol_name.len() + 20);
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); let length_sym = length_symbol(config);
match config.container_type { match config.container_type {
ContainerType::CArray(byte_type) => match byte_type { ContainerType::CArray(byte_type) => write!(
ByteType::UnsignedChar => { out_str,
format!("unsigned char {}[{}]", config.symbol_name, length_sym) "{} {}[{}]",
} if config.language == Language::Cpp {
ByteType::Char => format!("char {}[{}]", config.symbol_name, length_sym), byte_type.to_cpp_type()
_ => format!("{} {}[{}]", u8_type, config.symbol_name, length_sym), } else {
}, byte_type.to_c_type()
ContainerType::StdArray(byte_type) => { },
format!( config.symbol_name,
"std::array<{},{}> {}", length_sym
match byte_type { )
ByteType::UnsignedChar => "unsigned char", .unwrap(),
ByteType::Char => "char", ContainerType::StdArray(byte_type) => write!(
ByteType::U8 => u8_type, out_str,
ByteType::I8 => i8_type, "std::array<{},{}> {}",
}, if config.language == Language::Cpp {
in_data.len(), byte_type.to_cpp_type()
config.symbol_name } else {
) byte_type.to_c_type()
},
length_sym,
config.symbol_name
)
.unwrap(),
ContainerType::CString => write!(
out_str,
"{} {}[{}]",
ByteType::Char.to_c_type(),
config.symbol_name,
length_sym
)
.unwrap(),
ContainerType::StdString => write!(out_str, "std::string {}", config.symbol_name).unwrap(),
ContainerType::StdStringView => {
write!(out_str, "std::string_view {}", config.symbol_name).unwrap()
} }
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), out_str
}
} }

View File

@@ -1,10 +1,7 @@
use std::io::{BufReader, BufWriter, Read, Write}; use std::io::{self, BufReader, BufWriter, Read, Write};
use std::path::Path;
use std::{env, fs::OpenOptions, process::ExitCode}; use std::{env, fs::OpenOptions, process::ExitCode};
use thiserror::Error;
use crate::cfg::Config;
mod cfg; mod cfg;
mod cli; mod cli;
mod constants; mod constants;
@@ -43,10 +40,25 @@ fn main() -> ExitCode {
} }
}; };
match write_header(&cfg) { let in_data = match read_input_file(&cfg.input_file_path) {
Ok(v) => v,
Err(e) => {
eprintln!("Failed to read input file: {}", e);
return ExitCode::FAILURE;
}
};
let header_src = match generate::header(&cfg, &in_data) {
Ok(s) => s,
Err(e) => {
eprintln!("Failed to generate header source file: {}", e);
return ExitCode::FAILURE;
}
};
match write_output_file(header_src.as_bytes(), cfg.overwrite, &cfg.output_file_path) {
Ok(_) => (), Ok(_) => (),
Err(e) => { Err(e) => {
eprintln!("{}", e); eprintln!("Failed to write output file: {}", e);
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
}; };
@@ -54,48 +66,33 @@ fn main() -> ExitCode {
return ExitCode::SUCCESS; return ExitCode::SUCCESS;
} }
#[derive(Error, Debug)] fn read_input_file(input_path: &Path) -> Result<Vec<u8>, io::Error> {
pub enum HeaderGenerationError {
#[error("I/O error: \"{0}\"")]
IOError(String),
#[error("input file doesn't contain valid UTF-8: \"{0}\"")]
InvalidUtf8String(String),
}
fn write_header(config: &Config) -> Result<(), HeaderGenerationError> {
let in_file = OpenOptions::new() let in_file = OpenOptions::new()
.read(true) .read(true)
.write(false) .write(false)
.open(config.input_file_path.clone()) .open(input_path)?;
.expect("failed to open input file");
let mut buf = String::with_capacity(if let Ok(meta) = in_file.metadata() {
meta.len() as usize
} else {
0
});
let file_size = in_file.metadata()?.len();
let mut in_data: Vec<u8> = Vec::with_capacity(file_size as usize);
let mut reader = BufReader::new(in_file); 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())),
};
generate::with_header(config, &in_data[..n_bytes_read], &mut buf)?; let _ = reader.read_to_end(&mut in_data)?;
Ok(in_data)
}
fn write_output_file(buf: &[u8], overwrite: bool, output_path: &Path) -> Result<(), io::Error> {
if overwrite {
let _ = std::fs::remove_file(output_path);
}
let out_file = OpenOptions::new() let out_file = OpenOptions::new()
.write(true) .write(true)
.truncate(config.overwrite) .create_new(true)
.create(true) .open(output_path)?;
.open(config.output_file_path.clone())
.expect("failed to open input file");
let mut writer = BufWriter::new(out_file); let mut writer = BufWriter::new(out_file);
match writer.write_all(buf.as_bytes()) { writer.write_all(buf)?;
Ok(_) => (),
Err(e) => return Err(HeaderGenerationError::IOError(e.to_string())),
};
Ok(()) Ok(())
} }