use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, FieldsNamed, Meta, Path};
#[proc_macro_derive(BarExt, attributes(bar))]
pub fn bar_ext(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut bar_field = None;
if let Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed { named, .. }),
..
}) = &input.data
{
for field in named {
for attr in &field.attrs {
if let Meta::Path(Path { segments, .. }) = &attr.meta {
for segment in segments {
if &segment.ident.to_string() == "bar" {
bar_field = Some(field.ident.clone());
}
}
}
}
}
} else {
unimplemented!("BarExt derive macro is only derivable on structs.")
}
if bar_field.is_none() {
panic!("One struct field needs to use #[bar] attribute.")
}
let crate_name = if std::env::var("CARGO_CRATE_NAME")
.expect("CARGO_CRATE_NAME env variable not set by cargo.")
== "kdam"
{
"crate"
} else {
"kdam"
};
let crate_name = format_ident!("{}", crate_name);
let bar_field = bar_field
.flatten()
.expect("#[bar] attribute struct field has not a valid identifier.");
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics #crate_name::BarExt for #name #ty_generics #where_clause {
fn clear(&mut self) -> ::std::io::Result<()> {
self.#bar_field.clear()
}
fn input<T: Into<String>>(&mut self, text: T) -> ::std::io::Result<String> {
self.clear()?;
self.#bar_field.writer.print(text.into().as_bytes())?;
let mut buf = String::new();
::std::io::stdin().read_line(&mut buf)?;
if self.#bar_field.leave {
self.refresh()?;
}
Ok(buf)
}
fn refresh(&mut self) -> ::std::io::Result<()> {
self.#bar_field.elapsed_time();
if self.#bar_field.completed() {
if !self.#bar_field.leave && self.#bar_field.position > 0 {
return self.clear();
}
self.#bar_field.total = self.#bar_field.counter;
}
let text = self.render();
let bar_length = #crate_name::term::Colorizer::len_ansi(text.as_str()) as u16;
if bar_length > self.#bar_field.bar_length {
self.clear()?;
self.#bar_field.bar_length = bar_length;
}
self.#bar_field.writer.print_at(self.#bar_field.position, text.as_bytes())?;
Ok(())
}
fn render(&mut self) -> String {
Self::render(self)
}
fn reset(&mut self, total: Option<usize>) {
self.#bar_field.reset(total);
}
fn update(&mut self, n: usize) -> ::std::io::Result<bool> {
self.#bar_field.counter += n;
let should_refresh = self.#bar_field.should_refresh();
if should_refresh {
self.refresh()?;
}
Ok(should_refresh)
}
fn update_to(&mut self, n: usize) -> ::std::io::Result<bool> {
self.#bar_field.counter = n;
self.update(0)
}
fn write<T: Into<String>>(&mut self, text: T) -> ::std::io::Result<()> {
self.#bar_field.clear()?;
self.#bar_field.writer.print(format!("\r{}\n", text.into()).as_bytes())?;
if self.#bar_field.leave {
self.refresh()?;
}
Ok(())
}
fn write_to<T: ::std::io::Write>(&mut self, writer: &mut T, n: Option<usize>) -> ::std::io::Result<bool> {
let text;
if let Some(n) = n {
self.#bar_field.counter += n;
if self.#bar_field.should_refresh() {
text = #crate_name::term::Colorizer::trim_ansi(self.render().as_str());
} else {
return Ok(false);
}
} else {
text = #crate_name::term::Colorizer::trim_ansi(self.render().as_str());
}
self.#bar_field.bar_length = #crate_name::term::Colorizer::len_ansi(text.as_str()) as u16;
writer.write_all((text + "\n").as_bytes())?;
writer.flush()?;
Ok(true)
}
}
};
TokenStream::from(expanded)
}