mirror of
https://gitea.windcorp.ru/Wind-Corporation/Progressia.git
synced 2025-08-28 04:46:50 +03:00
Initial commit
This commit is contained in:
298
tools/embed/embed.py
Executable file
298
tools/embed/embed.py
Executable file
@@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
usage = \
|
||||
'''Usage: embed.py --cpp OUT_CPP --header OUT_H [--] [INPUT as PATH]...
|
||||
Generate C++ source code that includes binary contents of INPUT files.
|
||||
|
||||
Each file in INPUT is stored as a resource: a static array of unsigned char.
|
||||
It is identified by a PATH. If PATH is "auto", resource path is the path of
|
||||
the file relative to this script's working directory with forward slash '/'
|
||||
as separator.
|
||||
|
||||
Use -- to make sure the following one INPUT is not interpreted as an option.
|
||||
|
||||
This script generates two files:
|
||||
|
||||
OUT_CPP is a C++ implementation file that includes the contents of INPUT.
|
||||
OUT_H is a C++ header file that declares several methods of access to the data
|
||||
in OUT_CPP. It should be located in the same directory as OUT_H at compile
|
||||
time.
|
||||
|
||||
OUT_H declares the following symbols:
|
||||
|
||||
namespace __embedded_resources {
|
||||
struct EmbeddedResource {
|
||||
const unsigned char *data;
|
||||
std::size_t length;
|
||||
};
|
||||
EmbeddedResource getEmbeddedResource(const char *path);
|
||||
}
|
||||
|
||||
getEmbeddedResource(const char *path) returns an EmbeddedResource structure that
|
||||
contains the pointer to the beginning of the requested resource and its
|
||||
length, or {nullptr, 0} if the resource does not exist.'''
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from types import SimpleNamespace
|
||||
from json import dumps as json_dumps
|
||||
|
||||
def fail(*args):
|
||||
my_name = os.path.basename(sys.argv[0])
|
||||
print(my_name + ':', *args, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
# Parse arguments
|
||||
|
||||
out_cpp_path = None
|
||||
out_h_path = None
|
||||
inputs = []
|
||||
|
||||
argi = 1
|
||||
considerOptions = True
|
||||
while argi < len(sys.argv):
|
||||
arg = sys.argv[argi]
|
||||
|
||||
if considerOptions and arg.startswith('--cpp'):
|
||||
if arg == '--cpp':
|
||||
argi += 1
|
||||
if argi == len(sys.argv):
|
||||
fail('Missing argument for --cpp')
|
||||
out_cpp_path = sys.argv[argi]
|
||||
elif arg.startswith('--impl='):
|
||||
out_cpp_path = arg.removeprefix('--cpp=')
|
||||
else:
|
||||
fail(f"Unknown option '{arg}'")
|
||||
|
||||
elif considerOptions and arg.startswith('--header'):
|
||||
if arg == '--header':
|
||||
argi += 1
|
||||
if argi == len(sys.argv):
|
||||
fail('Missing argument for --header')
|
||||
out_h_path = sys.argv[argi]
|
||||
elif arg.startswith('--header='):
|
||||
out_h_path = arg.removeprefix('--header=')
|
||||
else:
|
||||
fail(f"Unknown option '{arg}'")
|
||||
|
||||
elif considerOptions and (arg == '-h' or arg == '--help'):
|
||||
sys.exit(0)
|
||||
|
||||
elif considerOptions and arg == '--':
|
||||
considerOptions = False
|
||||
|
||||
elif considerOptions and arg.startswith('-'):
|
||||
fail(f"Unknown option '{arg}'")
|
||||
|
||||
else:
|
||||
if argi + 2 >= len(sys.argv):
|
||||
fail(f'Invalid input declaration {sys.argv[argi:]}: '
|
||||
'expected "INPUT as PATH"')
|
||||
if sys.argv[argi + 1] != 'as':
|
||||
fail(f'Invalid input declaration {sys.argv[argi:argi+3]}: '
|
||||
'expected "INPUT as PATH"')
|
||||
|
||||
the_input = arg
|
||||
argi += 2
|
||||
name = sys.argv[argi]
|
||||
|
||||
if name == 'auto':
|
||||
name = os.path.relpath(the_input).replace(os.sep, '/')
|
||||
|
||||
inputs.append((the_input, name))
|
||||
|
||||
argi += 1
|
||||
|
||||
if out_cpp_path == None:
|
||||
fail('--impl not set')
|
||||
|
||||
if out_h_path == None:
|
||||
fail('--header not set')
|
||||
|
||||
if len(inputs) == 0:
|
||||
fail('No inputs')
|
||||
|
||||
generate_impl(out_cpp_path, out_h_path, inputs)
|
||||
generate_header(out_h_path)
|
||||
|
||||
|
||||
def generate_impl(out_cpp_path, out_h_path, inputs):
|
||||
|
||||
try:
|
||||
with open(out_cpp_path, 'w', encoding="utf-8") as output:
|
||||
|
||||
output.write(impl.start %
|
||||
{'header_name': os.path.basename(out_h_path)})
|
||||
|
||||
variables = {}
|
||||
|
||||
# Open each input
|
||||
for number, (input_path, resource_path) in enumerate(inputs):
|
||||
|
||||
variable_name = make_variable_name(resource_path, number)
|
||||
|
||||
if resource_path in variables:
|
||||
fail('Inputs resolve to duplicate resource paths: ' +
|
||||
resource_path)
|
||||
variables[resource_path] = variable_name
|
||||
|
||||
try:
|
||||
with open(input_path, 'rb') as input_file:
|
||||
write_bytes(output, input_file, variable_name)
|
||||
|
||||
if number == len(inputs) - 1:
|
||||
output.write(";\n")
|
||||
else:
|
||||
output.write(",\n")
|
||||
|
||||
except FileNotFoundError as e:
|
||||
fail(f"Input file '{input_path}' does not exist")
|
||||
except (PermissionError, OSError) as e:
|
||||
fail(f"Could not read input '{input_path}': {e}")
|
||||
|
||||
output.write(impl.mid)
|
||||
|
||||
# Add EmbeddedResources to lookup table
|
||||
|
||||
for number, (resource, variable) in enumerate(variables.items()):
|
||||
output.write(impl.mapping % {
|
||||
'resource_path_quoted': json_dumps(resource),
|
||||
'variable_name': variable})
|
||||
|
||||
if number == len(variables) - 1:
|
||||
output.write("\n")
|
||||
else:
|
||||
output.write(",\n")
|
||||
|
||||
output.write(impl.end)
|
||||
|
||||
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||
fail(f"Could not write to '{out_cpp_path}': {e}")
|
||||
|
||||
|
||||
def make_variable_name(resource_path, number):
|
||||
max_variable_name_length = 255 # very conservative
|
||||
max_path_length = max_variable_name_length - \
|
||||
len(impl.variable_name.format(number, ''))
|
||||
|
||||
return impl.variable_name % (number,
|
||||
re.sub(r'\W', '_', resource_path[-max_path_length:]).upper())
|
||||
|
||||
|
||||
def write_bytes(out_file, in_file, variable_name):
|
||||
|
||||
out_file.write(impl.declar_start % variable_name)
|
||||
|
||||
max_line_length = 79
|
||||
line = impl.declar_mid_prefix
|
||||
|
||||
# Process contents in chunks
|
||||
while True:
|
||||
chunk = in_file.read1(-1)
|
||||
if len(chunk) == 0:
|
||||
break
|
||||
|
||||
for byte in chunk:
|
||||
|
||||
byte_str = str(byte)
|
||||
if len(line) + 1 + len(byte_str) > max_line_length:
|
||||
out_file.write(line + '\n')
|
||||
line = impl.declar_mid_prefix
|
||||
|
||||
line += byte_str + ','
|
||||
|
||||
out_file.write(line[:-1] + '\n')
|
||||
out_file.write(impl.declar_end)
|
||||
|
||||
|
||||
def generate_header(out_h_path):
|
||||
try:
|
||||
with open(out_h_path, 'w', encoding="utf-8") as output:
|
||||
output.write(header)
|
||||
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||
fail(f"Could not write to '{out_h_path}': {e}")
|
||||
|
||||
|
||||
# Templates
|
||||
|
||||
impl = SimpleNamespace(
|
||||
|
||||
start=\
|
||||
'''/*
|
||||
* This file is autogenerated by tools/embed/embed.py. Do not edit directly.
|
||||
* Add this file as a compilation unit.
|
||||
*/
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
#include "%(header_name)s"
|
||||
|
||||
namespace {
|
||||
const unsigned char
|
||||
''',
|
||||
|
||||
mid=\
|
||||
'''
|
||||
std::unordered_map<std::string,
|
||||
__embedded_resources::EmbeddedResource>
|
||||
EMBEDDED_RESOURCES =
|
||||
{
|
||||
''',
|
||||
|
||||
end=\
|
||||
''' };
|
||||
}
|
||||
|
||||
namespace __embedded_resources {
|
||||
|
||||
EmbeddedResource getEmbeddedResource(const std::string &path) {
|
||||
auto result = EMBEDDED_RESOURCES.find(path);
|
||||
if (result == EMBEDDED_RESOURCES.end()) {
|
||||
return EmbeddedResource{nullptr, 0};
|
||||
}
|
||||
return result->second;
|
||||
}
|
||||
|
||||
}
|
||||
''',
|
||||
|
||||
mapping=\
|
||||
''' {%(resource_path_quoted)s, {
|
||||
%(variable_name)s,
|
||||
sizeof(%(variable_name)s)
|
||||
}}''',
|
||||
|
||||
declar_start= " %s[] = {\n",
|
||||
declar_mid_prefix= ' ',
|
||||
declar_end= ' }',
|
||||
|
||||
variable_name='EMBED_%s_%s'
|
||||
)
|
||||
|
||||
header = '''/*
|
||||
* This file is autogenerated by tools/embed/embed.py. Do not edit directly.
|
||||
* Include this header as necessary.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace __embedded_resources {
|
||||
|
||||
struct EmbeddedResource {
|
||||
const unsigned char *data;
|
||||
std::size_t length;
|
||||
};
|
||||
|
||||
EmbeddedResource getEmbeddedResource(const std::string &path);
|
||||
|
||||
}
|
||||
'''
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Reference in New Issue
Block a user