wchar_impl/
lib.rs

1#![cfg_attr(feature = "unstable", feature(proc_macro_span))]
2
3extern crate proc_macro;
4
5use std::fs;
6use std::path::PathBuf;
7
8use proc_macro2::{Span, TokenStream};
9use syn::{Error, Result};
10
11use crate::parse::{IncludeInput, LitStrOrChar, WchInput, WchzInput};
12
13mod encode;
14mod parse;
15
16// Utility function to handle expanding syn errors into a TokenStream.
17fn expand_macro<F: FnOnce() -> Result<TokenStream>>(f: F) -> proc_macro::TokenStream {
18    match f() {
19        Ok(expanded) => expanded.into(),
20        Err(err) => err.to_compile_error().into(),
21    }
22}
23
24#[proc_macro]
25pub fn wch(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
26    let WchInput { ty, literal, .. } = syn::parse_macro_input!(input);
27
28    expand_macro(|| match literal {
29        LitStrOrChar::Str(lit) => Ok(encode::expand_str(ty, &lit.value())),
30        LitStrOrChar::Char(lit) => encode::expand_char(ty, lit),
31    })
32}
33
34#[proc_macro]
35pub fn wchz(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
36    let WchzInput { ty, literal, .. } = syn::parse_macro_input!(input);
37
38    expand_macro(|| {
39        let text = literal.value();
40
41        if text.as_bytes().contains(&0) {
42            return Err(Error::new(
43                literal.span(),
44                "C-style string cannot contain nul characters",
45            ));
46        }
47
48        Ok(encode::expand_str_c(ty, &text))
49    })
50}
51
52#[proc_macro]
53pub fn include_wch(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
54    let IncludeInput { ty, file_path, .. } = syn::parse_macro_input!(input);
55
56    expand_macro(|| {
57        let text = read_file(&file_path)?;
58
59        Ok(encode::expand_str(ty, &text))
60    })
61}
62
63#[proc_macro]
64pub fn include_wchz(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
65    let IncludeInput { ty, file_path, .. } = syn::parse_macro_input!(input);
66
67    expand_macro(|| {
68        let text = read_file(&file_path)?;
69
70        if text.as_bytes().contains(&0) {
71            return Err(Error::new(
72                file_path.span(),
73                "C-style string cannot contain nul characters",
74            ));
75        }
76
77        Ok(encode::expand_str_c(ty, &text))
78    })
79}
80
81fn read_file(path: &syn::LitStr) -> Result<String> {
82    let span = path.span();
83    let mut path = PathBuf::from(path.value());
84
85    // If the path is relative, resolve it relative source file directory.
86    if path.is_relative() {
87        // Get the directory containing the call site source file.
88        let mut dir = call_site_dir(span)?;
89
90        // Resolve path relative to dir.
91        dir.push(path);
92        path = dir;
93    }
94
95    match fs::read_to_string(&path) {
96        Ok(text) => Ok(text),
97        Err(err) => Err(Error::new(
98            span,
99            format_args!("couldn't read {}: {}", path.display(), err),
100        )),
101    }
102}
103
104// `Span::source()` and `Span::source_file()` are currently unstable.
105#[cfg(feature = "unstable")]
106fn call_site_dir(_span: Span) -> Result<PathBuf> {
107    let call_site = Span::call_site().unwrap().source();
108    let source_file = call_site.source_file();
109
110    // The path to the source file.
111    let mut path = source_file.path();
112    // The path to the directory containing the source file.
113    path.pop();
114
115    Ok(path)
116}
117
118#[cfg(not(feature = "unstable"))]
119fn call_site_dir(span: Span) -> Result<PathBuf> {
120    Err(Error::new(
121        span,
122        "including files by relative path requires the `unstable` feature",
123    ))
124}