#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![cfg_attr(feature="clippy", deny(clippy))]
#![cfg_attr(not(feature = "with-syntex"), feature(plugin_registrar, rustc_private))]
#[cfg(not(feature = "with-syntex"))]
extern crate rustc_plugin;
#[cfg(not(feature = "with-syntex"))]
extern crate syntax;
#[cfg(feature = "with-syntex")]
extern crate syntex;
#[cfg(feature = "with-syntex")]
extern crate syntex_syntax as syntax;
#[cfg(feature = "with-syntex")]
use std::io;
#[cfg(feature = "with-syntex")]
use std::path::Path;
use syntax::codemap::Span;
use syntax::parse;
use syntax::parse::token::{self, Lit, Literal};
use syntax::ast::{TokenTree, LitKind, StrStyle, Name};
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
use syntax::ext::build::AstBuilder;
#[cfg(not(feature = "with-syntex"))]
#[plugin_registrar]
#[doc(hidden)]
pub fn register(reg: &mut rustc_plugin::Registry) {
reg.register_macro("indoc", expand_indoc);
}
#[cfg(feature = "with-syntex")]
#[doc(hidden)]
pub fn register(reg: &mut syntex::Registry) {
reg.add_macro("indoc", expand_indoc);
}
#[cfg(feature = "with-syntex")]
#[doc(hidden)]
pub fn expand<S, D>(src: S, dst: D) -> io::Result<()>
where S: AsRef<Path>,
D: AsRef<Path>,
{
let mut registry = syntex::Registry::new();
register(&mut registry);
registry.expand("", src.as_ref(), dst.as_ref())
}
fn expand_indoc<'a>(cx: &'a mut ExtCtxt, sp: Span, args: &[TokenTree])
-> Box<MacResult + 'a>
{
if args.len() != 1 {
cx.span_err(
sp,
&format!("argument must be a single string literal, but got {} arguments", args.len()));
return DummyResult::any(sp);
}
let lit = match args[0] {
TokenTree::Token(_, Literal(lit, _name)) => lit,
_ => {
cx.span_err(sp, "argument must be a single string literal");
return DummyResult::any(sp);
}
};
MacEager::expr(cx.expr_lit(sp, match lit {
Lit::Str_(name) =>
LitKind::Str(
token::intern_and_get_ident(
&parse::str_lit(&unindent(name))),
StrStyle::Cooked),
Lit::StrRaw(name, hashes) =>
LitKind::Str(
token::intern_and_get_ident(
&parse::raw_str_lit(&unindent(name))),
StrStyle::Raw(hashes)),
Lit::ByteStr(name) =>
LitKind::ByteStr(
parse::byte_str_lit(&unindent(name))),
Lit::ByteStrRaw(name, _hashes) =>
LitKind::ByteStr(
parse::byte_str_lit(&unindent(name))),
_ => {
cx.span_err(sp, "argument must be a single string literal");
return DummyResult::any(sp);
}
}))
}
fn unindent(name: Name) -> String {
let input = name.as_str();
let ignore_first_line = input.starts_with('\n')
|| input.starts_with("\r\n");
let spaces = input.lines()
.skip(1)
.filter_map(count_spaces)
.min()
.unwrap_or(0);
let mut result = String::with_capacity(input.len());
for (i, line) in input.lines().enumerate() {
if i > 1 || (i == 1 && !ignore_first_line) {
result.push_str("\n");
}
if i == 0 {
result.push_str(line);
} else if line.len() > spaces {
result.push_str(&line[spaces..]);
}
}
result
}
fn count_spaces(line: &str) -> Option<usize> {
for (i, ch) in line.chars().enumerate() {
if ch != ' ' {
return Some(i)
}
}
None
}