use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::path::PathBuf;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::{braced, parse_macro_input, Ident, LitStr, Token, Visibility};
struct TargetFunc {
vis: Visibility,
ident: Ident,
}
impl Parse for TargetFunc {
fn parse(input: ParseStream) -> Result<Self> {
let vis = input.parse()?;
let ident = input.parse()?;
Ok(TargetFunc { vis, ident })
}
}
#[derive(Debug)]
struct Resource {
base_dir: Option<String>,
prefix: String,
files: Vec<File>,
}
impl Resource {
pub fn files<'a>(&'a self) -> impl Iterator<Item = File> + 'a {
self.files.iter().map(move |f| self.resolve(f))
}
fn resolve(&self, file: &File) -> File {
match self.base_dir.as_ref() {
Some(base_dir) => {
let f = File {
path: format!("./{}/{}", base_dir, file.path.clone()),
alias: Some(file.resolved_alias().to_owned()),
};
f
}
None => file.clone(),
}
}
}
impl Parse for Resource {
fn parse(input: ParseStream) -> Result<Self> {
let mut base_dir = None;
let mut prefix = input.parse::<LitStr>()?.value();
if let Some(_) = input.parse::<Option<Token![as]>>()? {
base_dir = Some(prefix);
prefix = input.parse::<LitStr>()?.value();
}
let files = {
let content;
braced!(content in input);
content.parse_terminated::<File, Token![,]>(File::parse)?.into_iter().collect()
};
Ok(Resource { base_dir, prefix, files })
}
}
#[derive(Clone, Debug)]
struct File {
path: String,
alias: Option<String>,
}
impl Parse for File {
fn parse(input: ParseStream) -> Result<Self> {
let file = input.parse::<LitStr>()?.value();
let mut alias = None;
if let Some(_) = input.parse::<Option<Token![as]>>()? {
alias = Some(input.parse::<LitStr>()?.value());
}
Ok(File { path: file, alias })
}
}
impl File {
pub fn resolved_alias(&self) -> &str {
&*self.alias.as_ref().unwrap_or(&self.path)
}
}
struct QrcMacro {
func: TargetFunc,
data: Vec<Resource>,
}
impl Parse for QrcMacro {
fn parse(input: ParseStream) -> Result<Self> {
let func = input.parse()?;
input.parse::<Option<Token![,]>>()?;
let data =
input.parse_terminated::<Resource, Token![,]>(Resource::parse)?.into_iter().collect();
Ok(QrcMacro { func, data })
}
}
fn qt_hash(key: &str) -> u32 {
let mut h = 0u32;
for p in key.chars() {
assert_eq!(p.len_utf16(), 1, "Surrogate pair not supported by the hash function");
h = (h << 4) + p as u32;
h ^= (h & 0xf0000000) >> 23;
h &= 0x0fffffff;
}
h
}
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone)]
struct HashedString {
hash: u32,
string: String,
}
impl HashedString {
fn new(string: String) -> HashedString {
let hash = qt_hash(&string);
HashedString { hash, string }
}
}
#[derive(Debug)]
enum TreeNode {
File(String),
Directory { contents: BTreeMap<HashedString, TreeNode>, offset: u32 },
}
impl TreeNode {
fn new_dir() -> TreeNode {
TreeNode::Directory { contents: Default::default(), offset: 0 }
}
fn new_file(file: String) -> TreeNode {
TreeNode::File(file)
}
fn insert_node(&mut self, virtual_rel_path: &str, node: TreeNode) {
let contents = match self {
TreeNode::Directory { contents, .. } => contents,
_ => panic!("root not a dir?"),
};
if virtual_rel_path == "" {
contents.extend(match node {
TreeNode::Directory { contents, .. } => contents,
_ => panic!("merge file and directory?"),
});
return;
}
match virtual_rel_path.find('/') {
Some(idx) => {
let (name, rest) = virtual_rel_path.split_at(idx);
let hashed = HashedString::new(name.into());
contents
.entry(hashed)
.or_insert_with(TreeNode::new_dir)
.insert_node(&rest[1..], node);
}
None => {
let hashed = HashedString::new(virtual_rel_path.into());
contents
.insert(hashed, node)
.and_then(|_| -> Option<()> { panic!("Several time the same file?") });
}
};
}
fn compute_offsets(&mut self, mut offset: u32) -> u32 {
if let TreeNode::Directory { contents, offset: o } = self {
*o = offset;
offset += contents.len() as u32;
for node in contents.values_mut() {
offset = node.compute_offsets(offset);
}
}
offset
}
}
fn simplify_prefix(mut s: String) -> String {
let mut last_slash = true; s.retain(|x| {
let r = last_slash && x == '/';
last_slash = x == '/';
!r
});
if last_slash {
s.pop();
}
s
}
#[test]
fn simplify_prefix_test() {
assert_eq!(simplify_prefix("/".into()), "");
assert_eq!(simplify_prefix("///".into()), "");
assert_eq!(simplify_prefix("/foo//bar/d".into()), "foo/bar/d");
assert_eq!(simplify_prefix("hello/".into()), "hello");
}
fn build_tree(resources: Vec<Resource>) -> TreeNode {
let mut root = TreeNode::new_dir();
for r in resources {
let mut node = TreeNode::new_dir();
for f in r.files() {
let local_file = TreeNode::new_file(f.path.clone());
let virt_path = f.resolved_alias().to_owned();
node.insert_node(&*virt_path, local_file);
}
root.insert_node(&simplify_prefix(r.prefix), node);
}
root
}
fn push_u32_be(v: &mut Vec<u8>, val: u32) {
v.extend_from_slice(&[
((val >> 24) & 0xff) as u8,
((val >> 16) & 0xff) as u8,
((val >> 8) & 0xff) as u8,
(val & 0xff) as u8,
]);
}
fn push_u16_be(v: &mut Vec<u8>, val: u16) {
v.extend_from_slice(&[((val >> 8) & 0xff) as u8, (val & 0xff) as u8]);
}
#[derive(Default, Debug)]
struct Data {
payload: Vec<u8>,
names: Vec<u8>,
tree_data: Vec<u8>,
files: Vec<String>,
}
impl Data {
fn insert_file(&mut self, filename: &str) {
let mut filepath = PathBuf::new();
if let Ok(cargo_manifest) = env::var("CARGO_MANIFEST_DIR") {
filepath.push(cargo_manifest);
}
filepath.push(filename);
let mut data = fs::read(&filepath)
.unwrap_or_else(|_| panic!("Cannot open file {}", filepath.display()));
push_u32_be(&mut self.payload, data.len() as u32);
self.payload.append(&mut data);
self.files.push(filepath.to_str().expect("File path contains invalid Unicode").into());
}
fn insert_directory(&mut self, contents: &BTreeMap<HashedString, TreeNode>) {
for (ref name, ref val) in contents {
let name_off = self.insert_name(name);
push_u32_be(&mut self.tree_data, name_off);
match val {
TreeNode::File(ref filename) => {
push_u16_be(&mut self.tree_data, 0); push_u16_be(&mut self.tree_data, 0); push_u16_be(&mut self.tree_data, 1); let offset = self.payload.len();
push_u32_be(&mut self.tree_data, offset as u32);
self.insert_file(filename);
}
TreeNode::Directory { ref contents, offset } => {
push_u16_be(&mut self.tree_data, 2); push_u32_be(&mut self.tree_data, contents.len() as u32);
push_u32_be(&mut self.tree_data, *offset);
}
}
push_u32_be(&mut self.tree_data, 0);
push_u32_be(&mut self.tree_data, 0);
}
for val in contents.values() {
if let TreeNode::Directory { ref contents, .. } = val {
self.insert_directory(contents)
}
}
}
fn insert_name(&mut self, name: &HashedString) -> u32 {
let offset = self.names.len();
push_u16_be(&mut self.names, name.string.len() as u16);
push_u32_be(&mut self.names, name.hash);
for p in name.string.chars() {
assert_eq!(p.len_utf16(), 1, "Surrogate pair not supported");
push_u16_be(&mut self.names, p as u16);
}
offset as u32
}
}
fn generate_data(root: &TreeNode) -> Data {
let mut d = Data::default();
let contents = match root {
TreeNode::Directory { ref contents, .. } => contents,
_ => panic!("root not a dir?"),
};
push_u32_be(&mut d.tree_data, 0); push_u16_be(&mut d.tree_data, 2); push_u32_be(&mut d.tree_data, contents.len() as u32);
push_u32_be(&mut d.tree_data, 1);
push_u32_be(&mut d.tree_data, 0);
push_u32_be(&mut d.tree_data, 0);
d.insert_directory(contents);
d
}
fn expand_macro(func: TargetFunc, data: Data) -> TokenStream {
let TargetFunc { vis, ident } = func;
let Data { payload, names, tree_data, files } = data;
let payload =
::proc_macro2::TokenTree::Literal(::proc_macro2::Literal::byte_string(payload.as_slice()));
let q = quote! {
#vis fn #ident() {
use ::std::sync::Once;
static INIT_RESOURCES: Once = Once::new();
INIT_RESOURCES.call_once(|| {
static PAYLOAD : &'static [u8] = #payload;
static NAMES : &'static [u8] = & [ #(#names),* ];
static TREE_DATA : &'static [u8] = & [ #(#tree_data),* ];
unsafe { ::qmetaobject::qrc::register_resource_data(2, TREE_DATA, NAMES, PAYLOAD) };
#({ const _X: &'static [ u8 ] = include_bytes!(#files); })*
});
}
};
q.into()
}
pub fn process_qrc(source: TokenStream) -> TokenStream {
let parsed = parse_macro_input!(source as QrcMacro);
let mut tree = build_tree(parsed.data);
tree.compute_offsets(1);
let d = generate_data(&tree);
expand_macro(parsed.func, d)
}