nate_derive/lib.rs
1// Copyright (c) 2021-2022 René Kijewski <crates.io@k6i.de>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// --- LLVM Exceptions to the Apache 2.0 License ----
16//
17// As an exception, if, as a result of your compiling your source code, portions
18// of this Software are embedded into an Object form of such source code, you
19// may redistribute such embedded portions in such Object form without complying
20// with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
21//
22// In addition, if you combine or link compiled forms of this Software with
23// software that is licensed under the GPLv2 ("Combined Software") and if a
24// court of competent jurisdiction determines that the patent provision (Section
25// 3), the indemnity provision (Section 9) or other Section of the License
26// conflicts with the conditions of the GPLv2, you may retroactively and
27// prospectively choose to deem waived or otherwise exclude such Section(s) of
28// the License, but only in their entirety and only with respect to the Combined
29// Software.
30
31#![forbid(unsafe_code)]
32#![allow(unused_attributes)]
33#![warn(absolute_paths_not_starting_with_crate)]
34#![warn(elided_lifetimes_in_paths)]
35#![warn(explicit_outlives_requirements)]
36#![warn(meta_variable_misuse)]
37#![warn(missing_copy_implementations)]
38#![warn(missing_debug_implementations)]
39#![warn(missing_docs)]
40#![warn(non_ascii_idents)]
41#![warn(noop_method_call)]
42#![warn(single_use_lifetimes)]
43#![warn(trivial_casts)]
44#![warn(unreachable_pub)]
45#![warn(unused_crate_dependencies)]
46#![warn(unused_extern_crates)]
47#![warn(unused_lifetimes)]
48#![warn(unused_results)]
49#![allow(clippy::many_single_char_names)]
50
51//! ## `NaTE` — Not a Template Engine
52//!
53//! [](https://github.com/Kijewski/nate/actions/workflows/ci.yml)
54//! [](https://crates.io/crates/nate)
55//! 
56//! [](https://github.com/Kijewski/nate/blob/v0.2.2/LICENSE "Apache-2.0 WITH LLVM-exception")
57//!
58//! Proc-macros for [`NaTE`](https://crates.io/crates/nate).
59//!
60//! This libary implements the `#![derive(Nate)]` annotation.
61//!
62
63mod compile_error;
64mod generate;
65mod nate_span;
66mod parse;
67mod strip;
68
69use std::convert::TryInto;
70use std::fs::OpenOptions;
71use std::io::Read;
72use std::path::Path;
73
74use blake2::{Blake2s256, Digest};
75use compile_error::IoOp;
76use darling::FromDeriveInput;
77use proc_macro::TokenStream;
78use quote::quote;
79
80use crate::compile_error::CompileError;
81use crate::generate::generate;
82use crate::strip::Strip;
83
84/// Implement [`fmt::Display`](core::fmt::Display) for a struct or enum
85///
86/// Usage:
87///
88/// ```ignore
89/// #[derive(Nate)]
90/// #[template(
91/// path = "…",
92/// generated = "…",
93/// )]
94/// struct Template { /* … */ }
95/// ```
96///
97/// The path is relative to the cargo manifest dir (where you find Cargo.toml) of the calling
98/// project.
99///
100/// The optional debug output path `generated` is relative to the cargo manifest dir.
101/// If supplied the generated code will be written into this file.
102/// An existing file fill be replaced!
103#[proc_macro_derive(Nate, attributes(template))]
104pub fn derive_nate(input: TokenStream) -> TokenStream {
105 let err = match generate(input) {
106 Ok(ts) => return ts,
107 Err(err) => err,
108 };
109
110 let err = format!("{}", err);
111 Into::into(quote!(
112 const _: () = {
113 ::nate::details::core::compile_error!(#err);
114 };
115 ))
116}
117
118#[derive(Debug, Default, FromDeriveInput)]
119#[darling(attributes(template))]
120struct Settings {
121 path: String,
122 #[darling(default)]
123 generated: Option<String>,
124 #[darling(default)]
125 #[allow(unused)] // TODO
126 strip: Strip,
127}
128
129#[derive(Debug, Default)]
130struct Context {
131 settings: Settings,
132 strings_hash: Blake2s256,
133}
134
135impl Context {
136 fn load_file(&mut self, path: &Path) -> Result<String, CompileError> {
137 let mut f = OpenOptions::new()
138 .read(true)
139 .open(path)
140 .map_err(|err| CompileError::IoError(IoOp::Open, path.to_owned(), err))?;
141 let len = f
142 .metadata()
143 .map_err(|err| CompileError::IoError(IoOp::Metadata, path.to_owned(), err))?
144 .len();
145 let mut s = String::with_capacity(len.try_into().unwrap_or_default());
146 let _ = f
147 .read_to_string(&mut s)
148 .map_err(|err| CompileError::IoError(IoOp::Read, path.to_owned(), err))?;
149 self.strings_hash.update((s.len() as u128).to_be_bytes());
150 self.strings_hash.update(s.as_bytes());
151 self.strings_hash.update([0xff_u8]);
152 Ok(s)
153 }
154}
155
156#[doc(hidden)]
157#[proc_macro_attribute]
158pub fn addr(_attr: TokenStream, item: TokenStream) -> TokenStream {
159 item
160}