0.2.0 release
This commit is contained in:
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::{
|
||||
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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user