#![ cfg_attr( not( feature = "enabled" ), allow( unused ) ) ]
#[ cfg( feature = "enabled" ) ]
use macro_tools ::
{
quote ::quote,
syn,
};
#[ cfg( feature = "enabled" ) ]
use proc_macro::TokenStream;
#[ cfg( feature = "enabled" ) ]
use std ::
{
env,
fs,
path ::Path,
};
#[ cfg( feature = "enabled" ) ]
fn heading_level( line : &str ) -> Option< usize >
{
if !line.starts_with( '#' )
{
return None;
}
let count = line.chars().take_while( | c | *c == '#' ).count();
let after = &line[ count.. ];
if after.is_empty() || after.starts_with( ' ' )
{
Some( count )
}
else
{
None
}
}
#[ cfg( feature = "enabled" ) ]
fn extract_section( content : &str, heading : &str ) -> Option< String >
{
let target_level = heading_level( heading )?;
let mut result = String ::new();
let mut in_section = false;
let mut in_code_block = false;
for line in content.lines()
{
if in_section
{
if line.starts_with( "```" ) || line.starts_with( "~~~" )
{
in_code_block = !in_code_block;
}
if !in_code_block
{
if let Some( level ) = heading_level( line )
{
if level <= target_level
{
break;
}
}
}
result.push_str( line );
result.push( '\n' );
}
else if line == heading
{
in_section = true;
result.push_str( line );
result.push( '\n' );
}
}
if in_section { Some( result ) } else { None }
}
#[ cfg( feature = "enabled" ) ]
#[ proc_macro ]
pub fn include_md( input : TokenStream ) -> TokenStream
{
let path = match syn ::parse ::< syn ::LitStr >( input )
{
Ok( lit ) => lit,
Err( e ) => return e.to_compile_error().into(),
};
let path_str = path.value();
quote!
{{
const _ : () = assert!
(
:: core :: mem :: size_of_val( include_bytes!( #path_str ) ) <= 10_000_000_usize,
"include_md: file exceeds 10 MB limit"
);
include_str!( #path_str )
}}
.into()
}
#[ cfg( feature = "enabled" ) ]
struct SectionArgs
{
path : syn ::LitStr,
heading : syn ::LitStr,
}
#[ cfg( feature = "enabled" ) ]
impl syn ::parse ::Parse for SectionArgs
{
fn parse( input : syn ::parse ::ParseStream< '_ > ) -> syn ::Result< Self >
{
let path = input.parse ::< syn ::LitStr >()?;
input.parse ::< syn ::Token![ , ] >()?;
let heading = input.parse ::< syn ::LitStr >()?;
Ok( SectionArgs { path, heading } )
}
}
#[ cfg( feature = "enabled" ) ]
#[ proc_macro ]
pub fn include_md_section( input : TokenStream ) -> TokenStream
{
let args = match syn ::parse ::< SectionArgs >( input )
{
Ok( a ) => a,
Err( e ) => return e.to_compile_error().into(),
};
let path_lit = args.path;
let heading_lit = args.heading;
let path_str = path_lit.value();
let heading_str = heading_lit.value();
let Ok( manifest_dir ) = env ::var( "CARGO_MANIFEST_DIR" ) else
{
return syn ::Error ::new( path_lit.span(), "include_md_section: CARGO_MANIFEST_DIR not set" )
.to_compile_error()
.into();
};
let abs_path = Path ::new( &manifest_dir ).join( &path_str );
let content = match fs ::read_to_string( &abs_path )
{
Ok( c ) => c,
Err( e ) =>
{
return syn ::Error ::new
(
path_lit.span(),
format!( "include_md_section: cannot read `{}`: {}", abs_path.display(), e ),
)
.to_compile_error()
.into();
}
};
if content.len() > 10_000_000
{
return syn ::Error ::new( path_lit.span(), "include_md_section: file exceeds 10 MB limit" )
.to_compile_error()
.into();
}
let Some( section ) = extract_section( &content, &heading_str ) else
{
return syn ::Error ::new
(
heading_lit.span(),
format!
(
"include_md_section: heading `{}` not found in `{}`",
heading_str,
abs_path.display(),
),
)
.to_compile_error()
.into();
};
let lit = syn ::LitStr ::new( §ion, heading_lit.span() );
quote!( #lit ).into()
}