1#![doc = include_str!("../README.md")]
5
6mod markdown;
7#[cfg(test)]
8mod tests;
9
10use proc_macro2::{Span, TokenStream};
11use std::{
12 fmt, fs,
13 io::{self, BufRead},
14 path::PathBuf,
15};
16use syn::{
17 parse::{Parse, ParseStream},
18 parse2, LitStr, Token,
19};
20
21#[proc_macro]
59pub fn include_markdown(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
60 markdown::include_markdown(item.into())
61 .unwrap_or_else(syn::Error::into_compile_error)
62 .into()
63}
64
65struct MarkdownArgs {
66 path: LitStr,
67 name: LitStr,
68}
69
70impl fmt::Debug for MarkdownArgs {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 f.debug_struct("MarkdownArgs")
73 .field("path", &self.path.value())
74 .field("name", &self.name.value())
75 .finish()
76 }
77}
78
79impl Parse for MarkdownArgs {
80 fn parse(input: ParseStream) -> syn::Result<Self> {
81 let path = input.parse()?;
82 input.parse::<Token![,]>()?;
83 let name = input.parse()?;
84 Ok(Self { path, name })
85 }
86}
87
88fn include_file<F>(item: TokenStream, f: F) -> syn::Result<TokenStream>
89where
90 F: FnOnce(&str, io::Lines<io::BufReader<fs::File>>) -> io::Result<Vec<String>>,
91{
92 let args: MarkdownArgs = parse2(item).map_err(|_| {
93 syn::Error::new(
94 Span::call_site(),
95 "expected (path, name) literal string arguments",
96 )
97 })?;
98 let file = open(&args.path.value()).map_err(|err| syn::Error::new(args.path.span(), err))?;
99 let content = extract(file, &args.name.value(), f)
100 .map_err(|err| syn::Error::new(args.name.span(), err))?;
101
102 Ok(content.parse()?)
103}
104
105fn open(path: &str) -> io::Result<fs::File> {
106 let file_path = PathBuf::from(file!());
107 let path = file_path
108 .parent()
109 .ok_or_else(|| {
110 io::Error::new(
111 io::ErrorKind::NotFound,
112 "could not get parent of current source file",
113 )
114 })?
115 .join(path);
116 fs::File::open(path)
117}
118
119fn extract<R, F>(buffer: R, name: &str, f: F) -> io::Result<String>
120where
121 R: io::Read,
122 F: FnOnce(&str, io::Lines<io::BufReader<R>>) -> io::Result<Vec<String>>,
123{
124 let reader = io::BufReader::new(buffer);
125 let lines = f(name, reader.lines())?;
126 if lines.is_empty() {
127 return Err(io::Error::new(
128 io::ErrorKind::NotFound,
129 format!("code fence '{}' not found", name),
130 ));
131 }
132
133 Ok(lines.join("\n"))
134}