|  | use crate::error::{Error, Result}; | 
|  | use proc_macro::{token_stream, Delimiter, Ident, Span, TokenTree}; | 
|  | use std::iter::Peekable; | 
|  |  | 
|  | pub(crate) enum Segment { | 
|  | String(LitStr), | 
|  | Apostrophe(Span), | 
|  | Env(LitStr), | 
|  | Modifier(Colon, Ident), | 
|  | } | 
|  |  | 
|  | pub(crate) struct LitStr { | 
|  | pub value: String, | 
|  | pub span: Span, | 
|  | } | 
|  |  | 
|  | pub(crate) struct Colon { | 
|  | pub span: Span, | 
|  | } | 
|  |  | 
|  | pub(crate) fn parse(tokens: &mut Peekable<token_stream::IntoIter>) -> Result<Vec<Segment>> { | 
|  | let mut segments = Vec::new(); | 
|  | while match tokens.peek() { | 
|  | None => false, | 
|  | Some(TokenTree::Punct(punct)) => punct.as_char() != '>', | 
|  | Some(_) => true, | 
|  | } { | 
|  | match tokens.next().unwrap() { | 
|  | TokenTree::Ident(ident) => { | 
|  | let mut fragment = ident.to_string(); | 
|  | if fragment.starts_with("r#") { | 
|  | fragment = fragment.split_off(2); | 
|  | } | 
|  | if fragment == "env" | 
|  | && match tokens.peek() { | 
|  | Some(TokenTree::Punct(punct)) => punct.as_char() == '!', | 
|  | _ => false, | 
|  | } | 
|  | { | 
|  | let bang = tokens.next().unwrap(); // `!` | 
|  | let expect_group = tokens.next(); | 
|  | let parenthesized = match &expect_group { | 
|  | Some(TokenTree::Group(group)) | 
|  | if group.delimiter() == Delimiter::Parenthesis => | 
|  | { | 
|  | group | 
|  | } | 
|  | Some(wrong) => return Err(Error::new(wrong.span(), "expected `(`")), | 
|  | None => { | 
|  | return Err(Error::new2( | 
|  | ident.span(), | 
|  | bang.span(), | 
|  | "expected `(` after `env!`", | 
|  | )); | 
|  | } | 
|  | }; | 
|  | let mut inner = parenthesized.stream().into_iter(); | 
|  | let lit = match inner.next() { | 
|  | Some(TokenTree::Literal(lit)) => lit, | 
|  | Some(wrong) => { | 
|  | return Err(Error::new(wrong.span(), "expected string literal")) | 
|  | } | 
|  | None => { | 
|  | return Err(Error::new2( | 
|  | ident.span(), | 
|  | parenthesized.span(), | 
|  | "expected string literal as argument to env! macro", | 
|  | )) | 
|  | } | 
|  | }; | 
|  | let lit_string = lit.to_string(); | 
|  | if lit_string.starts_with('"') | 
|  | && lit_string.ends_with('"') | 
|  | && lit_string.len() >= 2 | 
|  | { | 
|  | // TODO: maybe handle escape sequences in the string if | 
|  | // someone has a use case. | 
|  | segments.push(Segment::Env(LitStr { | 
|  | value: lit_string[1..lit_string.len() - 1].to_owned(), | 
|  | span: lit.span(), | 
|  | })); | 
|  | } else { | 
|  | return Err(Error::new(lit.span(), "expected string literal")); | 
|  | } | 
|  | if let Some(unexpected) = inner.next() { | 
|  | return Err(Error::new( | 
|  | unexpected.span(), | 
|  | "unexpected token in env! macro", | 
|  | )); | 
|  | } | 
|  | } else { | 
|  | segments.push(Segment::String(LitStr { | 
|  | value: fragment, | 
|  | span: ident.span(), | 
|  | })); | 
|  | } | 
|  | } | 
|  | TokenTree::Literal(lit) => { | 
|  | segments.push(Segment::String(LitStr { | 
|  | value: lit.to_string(), | 
|  | span: lit.span(), | 
|  | })); | 
|  | } | 
|  | TokenTree::Punct(punct) => match punct.as_char() { | 
|  | '_' => segments.push(Segment::String(LitStr { | 
|  | value: "_".to_owned(), | 
|  | span: punct.span(), | 
|  | })), | 
|  | '\'' => segments.push(Segment::Apostrophe(punct.span())), | 
|  | ':' => { | 
|  | let colon_span = punct.span(); | 
|  | let colon = Colon { span: colon_span }; | 
|  | let ident = match tokens.next() { | 
|  | Some(TokenTree::Ident(ident)) => ident, | 
|  | wrong => { | 
|  | let span = wrong.as_ref().map_or(colon_span, TokenTree::span); | 
|  | return Err(Error::new(span, "expected identifier after `:`")); | 
|  | } | 
|  | }; | 
|  | segments.push(Segment::Modifier(colon, ident)); | 
|  | } | 
|  | _ => return Err(Error::new(punct.span(), "unexpected punct")), | 
|  | }, | 
|  | TokenTree::Group(group) => { | 
|  | if group.delimiter() == Delimiter::None { | 
|  | let mut inner = group.stream().into_iter().peekable(); | 
|  | let nested = parse(&mut inner)?; | 
|  | if let Some(unexpected) = inner.next() { | 
|  | return Err(Error::new(unexpected.span(), "unexpected token")); | 
|  | } | 
|  | segments.extend(nested); | 
|  | } else { | 
|  | return Err(Error::new(group.span(), "unexpected token")); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | Ok(segments) | 
|  | } | 
|  |  | 
|  | pub(crate) fn paste(segments: &[Segment]) -> Result<String> { | 
|  | let mut evaluated = Vec::new(); | 
|  | let mut is_lifetime = false; | 
|  |  | 
|  | for segment in segments { | 
|  | match segment { | 
|  | Segment::String(segment) => { | 
|  | evaluated.push(segment.value.clone()); | 
|  | } | 
|  | Segment::Apostrophe(span) => { | 
|  | if is_lifetime { | 
|  | return Err(Error::new(*span, "unexpected lifetime")); | 
|  | } | 
|  | is_lifetime = true; | 
|  | } | 
|  | Segment::Env(var) => { | 
|  | let resolved = match std::env::var(&var.value) { | 
|  | Ok(resolved) => resolved, | 
|  | Err(_) => { | 
|  | return Err(Error::new( | 
|  | var.span, | 
|  | &format!("no such env var: {:?}", var.value), | 
|  | )); | 
|  | } | 
|  | }; | 
|  | let resolved = resolved.replace('-', "_"); | 
|  | evaluated.push(resolved); | 
|  | } | 
|  | Segment::Modifier(colon, ident) => { | 
|  | let last = match evaluated.pop() { | 
|  | Some(last) => last, | 
|  | None => { | 
|  | return Err(Error::new2(colon.span, ident.span(), "unexpected modifier")) | 
|  | } | 
|  | }; | 
|  | match ident.to_string().as_str() { | 
|  | "lower" => { | 
|  | evaluated.push(last.to_lowercase()); | 
|  | } | 
|  | "upper" => { | 
|  | evaluated.push(last.to_uppercase()); | 
|  | } | 
|  | "snake" => { | 
|  | let mut acc = String::new(); | 
|  | let mut prev = '_'; | 
|  | for ch in last.chars() { | 
|  | if ch.is_uppercase() && prev != '_' { | 
|  | acc.push('_'); | 
|  | } | 
|  | acc.push(ch); | 
|  | prev = ch; | 
|  | } | 
|  | evaluated.push(acc.to_lowercase()); | 
|  | } | 
|  | "camel" => { | 
|  | let mut acc = String::new(); | 
|  | let mut prev = '_'; | 
|  | for ch in last.chars() { | 
|  | if ch != '_' { | 
|  | if prev == '_' { | 
|  | for chu in ch.to_uppercase() { | 
|  | acc.push(chu); | 
|  | } | 
|  | } else if prev.is_uppercase() { | 
|  | for chl in ch.to_lowercase() { | 
|  | acc.push(chl); | 
|  | } | 
|  | } else { | 
|  | acc.push(ch); | 
|  | } | 
|  | } | 
|  | prev = ch; | 
|  | } | 
|  | evaluated.push(acc); | 
|  | } | 
|  | _ => { | 
|  | return Err(Error::new2( | 
|  | colon.span, | 
|  | ident.span(), | 
|  | "unsupported modifier", | 
|  | )); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | let mut pasted = evaluated.into_iter().collect::<String>(); | 
|  | if is_lifetime { | 
|  | pasted.insert(0, '\''); | 
|  | } | 
|  | Ok(pasted) | 
|  | } |