Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking PR Custom Derive (#146) #835

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# Generated by Cargo
/target/
/clap-test/target/
/clap-macros/target/

# Cargo files
Cargo.lock
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ yaml-rust = { version = "0.3.5", optional = true }
clippy = { version = "~0.0.112", optional = true }

[dev-dependencies]
regex = "~0.1.80"
regex = "~0.1.69"
clap-macros = { path = "clap-macros" }

[features]
default = ["suggestions", "color", "wrap_help"]
Expand Down
11 changes: 11 additions & 0 deletions clap-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "clap-macros"
version = "0.1.0"
authors = ["Wim Looman <[email protected]>"]

[lib]
proc-macro = true

[dependencies]
syn = "0.11"
quote = "0.3"
136 changes: 136 additions & 0 deletions clap-macros/src/attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use syn;
use quote;

pub struct Attribute {
key: String,
values: Vec<syn::Lit>,
}

impl Attribute {
pub fn new(key: String) -> Attribute {
Attribute {
key: key,
values: vec![],
}
}

pub fn push(&mut self, value: syn::Lit) { self.values.push(value) }

pub fn values(&self) -> Vec<String> {
self.values
.iter()
.map(|s| match *s {
syn::Lit::Str(ref s, _) => s.clone(),
_ => panic!("clap-macros: multi-valued attributes must be strings"),
})
.collect()
}

fn only_value(&self) -> &syn::Lit {
if self.values.len() == 1 {
&self.values[0]
} else {
panic!("clap-macros: expected a single value for attribute '{}' but had multiple",
self.key);
}
}
}

impl<'a> Into<syn::Lit> for &'a Attribute {
fn into(self) -> syn::Lit { self.only_value().clone() }
}

impl<'a> Into<&'a syn::Lit> for &'a Attribute {
fn into(self) -> &'a syn::Lit { self.only_value() }
}

impl<'a> Into<&'a str> for &'a Attribute {
fn into(self) -> &'a str {
if let &syn::Lit::Str(ref value, _) = self.only_value() {
value
} else {
panic!("Expected string value for attribute {} but got a {:?}",
self.key,
self.only_value());
}
}
}

impl<'a> Into<&'a [u8]> for &'a Attribute {
fn into(self) -> &'a [u8] {
if let &syn::Lit::ByteStr(ref value, _) = self.only_value() {
value
} else if let &syn::Lit::Str(ref value, _) = self.only_value() {
value.as_bytes()
} else {
panic!("Expected bytestring value for attribute {} but got a {:?}",
self.key,
self.only_value());
}
}
}

impl<'a> Into<u8> for &'a Attribute {
fn into(self) -> u8 {
if let &syn::Lit::Byte(ref value) = self.only_value() {
*value
} else if let &syn::Lit::Str(ref value, _) = self.only_value() {
value.parse().unwrap()
} else {
panic!("Expected byte value for attribute {} but got a {:?}",
self.key,
self.only_value());
}
}
}

impl<'a> Into<char> for &'a Attribute {
fn into(self) -> char {
if let &syn::Lit::Char(ref value) = self.only_value() {
*value
} else if let &syn::Lit::Str(ref value, _) = self.only_value() {
assert!(value.len() == 1);
value.chars().next().unwrap()
} else {
panic!("Expected char value for attribute {} but got a {:?}",
self.key,
self.only_value());
}
}
}

impl<'a> Into<u64> for &'a Attribute {
fn into(self) -> u64 {
if let &syn::Lit::Int(ref value, _) = self.only_value() {
*value
} else if let &syn::Lit::Str(ref value, _) = self.only_value() {
value.parse().unwrap()
} else {
panic!("Expected int value for attribute {} but got a {:?}",
self.key,
self.only_value());
}
}
}

impl<'a> Into<bool> for &'a Attribute {
fn into(self) -> bool {
if let &syn::Lit::Bool(ref value) = self.only_value() {
*value
} else if let &syn::Lit::Str(ref value, _) = self.only_value() {
value.parse().unwrap()
} else {
panic!("Expected bool value for attribute {} but got a {:?}",
self.key,
self.only_value());
}
}
}

impl<'a> quote::ToTokens for &'a mut Attribute {
fn to_tokens(&self, tokens: &mut quote::Tokens) { self.only_value().to_tokens(tokens) }
}

impl quote::ToTokens for Attribute {
fn to_tokens(&self, tokens: &mut quote::Tokens) { self.only_value().to_tokens(tokens) }
}
181 changes: 181 additions & 0 deletions clap-macros/src/attrs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};

use syn;

use attr::Attribute;

pub struct Attributes {
pub summary: String,
pub docs: String,
map: BTreeMap<String, (RefCell<usize>, Attribute)>,
}

pub struct FieldAttributes {
empty: Attributes,
map: HashMap<syn::Ident, (RefCell<usize>, Attributes)>,
}

impl Default for Attributes {
fn default() -> Self {
Attributes {
summary: "".into(),
docs: "".into(),
map: BTreeMap::new(),
}
}
}

impl Attributes {
pub fn new() -> Self { Default::default() }

pub fn from_attrs(attrs: &[syn::Attribute]) -> Self {
use syn::NestedMetaItem as N;
use syn::MetaItem as M;
let mut claps = BTreeMap::new();
for attr in attrs {
if let syn::MetaItem::List(ref ident, ref values) = attr.value {
if ident != "clap" {
panic!("Attribute other than #[clap(..)] found");
}
for value in values {
match *value {
// #[foo = "bar"]
N::MetaItem(M::NameValue(ref name, ref value)) => {
let &mut (_, ref mut attr) = claps.entry(name.to_string())
.or_insert((RefCell::new(0), Attribute::new(name.to_string())));
attr.push(value.clone());
}
// #[foo]
N::MetaItem(M::Word(ref name)) => {
let &mut (_, ref mut attr) = claps.entry(name.to_string())
.or_insert((RefCell::new(0), Attribute::new(name.to_string())));
attr.push(syn::Lit::Bool(true));
}
// #[derive(..)]
N::MetaItem(M::List(ref ident, ref values)) => {
let &mut (_, ref mut attr) = claps.entry(ident.as_ref().to_string())
.or_insert((RefCell::new(0),
Attribute::new(ident.as_ref().to_string())));
for value in values {
match *value {
N::MetaItem(M::Word(ref name)) => attr.push(name.as_ref().into()),
_ => {
panic!("Invalid clap attribute {} literal value not supported",
quote!(#attr).to_string().replace(" ", ""))
}
}
}
}
_ => {
panic!("Invalid clap attribute {} literal value not supported",
quote!(#attr).to_string().replace(" ", ""))
}
}
}
}
}

let docs = attrs.iter()
.filter(|a| a.is_sugared_doc)
.map(|a| match a.value {
syn::MetaItem::NameValue(_, syn::Lit::Str(ref doc, _)) => doc,
_ => unreachable!(),
})
.fold(String::new(),
|docs, line| docs + line.trim_left_matches('/').trim() + "\n");

let index = docs.find("\n\n");
let (summary, docs) = if let Some(index) = index {
let (summary, docs) = docs.split_at(index);
let (_, docs) = docs.split_at(2);
(summary.into(), docs.into())
} else {
(docs, "".into())
};

Attributes {
summary: summary,
docs: docs,
map: claps,
}
}

pub fn check_used(&self, name: &str, field: Option<&str>) {
for (ref attr, &(ref counter, _)) in &self.map {
if *counter.borrow() == 0 {
match field {
Some(field) => {
println!("clap-macros: unexpected attribute '{}' on field '{}' of struct '{}'",
attr,
field,
name)
}
None => {
println!("clap-macros: unexpected attribute '{}' on struct '{}'",
attr,
name)
}
}
}
}
}

pub fn get(&self, key: &str) -> Option<&Attribute> {
if let Some(&(ref counter, ref attr)) = self.map.get(key) {
*counter.borrow_mut() += 1;
Some(attr)
} else {
None
}
}

pub fn get_bool(&self, key: &str) -> bool { self.get(key).map(|a| a.into()).unwrap_or(false) }
}

impl FieldAttributes {
pub fn check_used(&self, name: &str) {
for (ref field, &(ref counter, ref attrs)) in &self.map {
if *counter.borrow() == 0 {
panic!("clap-macros: didn't access attributes for field '{}' on struct '{}' for some reason",
field,
name);
}
attrs.check_used(name, Some(field.as_ref()));
}
}

pub fn get(&self, field: &syn::Field) -> &Attributes {
if let Some(&(ref counter, ref attrs)) = self.map.get(field.ident.as_ref().unwrap()) {
*counter.borrow_mut() += 1;
attrs
} else {
&self.empty
}
}
}

/// Extracts all clap attributes of the form #[clap(i = V)]
pub fn extract_attrs(ast: &syn::MacroInput) -> (Attributes, FieldAttributes) {
use syn::Body as B;
use syn::VariantData as V;
let empty = Attributes::new();
let root_attrs = Attributes::from_attrs(&ast.attrs);
let field_attrs = match ast.body {
B::Struct(V::Struct(ref fields)) => {
fields.iter()
.map(|field| {
(field.ident.clone().unwrap(),
(RefCell::new(0), Attributes::from_attrs(&field.attrs)))
})
.collect()
}
B::Struct(V::Tuple(_)) => panic!("TODO: tuple struct unsupported msg"),
B::Struct(V::Unit) | B::Enum(_) => HashMap::new(),
};
(root_attrs,
FieldAttributes {
empty: empty,
map: field_attrs,
})
}
Loading