feat(builder): Partial MethodBuilder impl

Including documentation at least on the method builder part. The
great thing is that fully working examples are now included on
every type !

Now more involved part starts ... namely setting up the individual call
method signatures.
This commit is contained in:
Sebastian Thiel
2015-03-04 15:24:23 +01:00
parent 8746f5e0e2
commit 01db89057d
9 changed files with 931 additions and 75 deletions

View File

@@ -1,12 +1,13 @@
<%
from util import (iter_nested_types, new_context, rust_comment, rust_doc_comment,
rust_module_doc_comment, rust_doc_test_norun, canonical_type_name,
rust_test_fn_invisible)
mb_type, singular, rust_test_fn_invisible, put_and)
nested_schemas = list(iter_nested_types(schemas))
c = new_context(resources)
hub_type = canonical_type_name(canonicalName)
%>\
<%namespace name="lib" file="lib/lib.mako"/>\
<%namespace name="mutil" file="lib/util.mako"/>\
@@ -32,7 +33,7 @@ use std::marker::PhantomData;
use std::borrow::BorrowMut;
use std::cell::RefCell;
pub use cmn::{Hub, Resource, Part, ResponseResult, RequestResult, NestedType};
pub use cmn::{Hub, MethodBuilder, Resource, Part, ResponseResult, RequestResult, NestedType};
// ########
// HUB ###
@@ -45,7 +46,7 @@ pub use cmn::{Hub, Resource, Part, ResponseResult, RequestResult, NestedType};
/// Instantiate a new hub
///
<%block filter="rust_doc_comment">\
${lib.hub_usage_example()}\
<%lib:hub_usage_example/>\
</%block>
pub struct ${hub_type}<C, NC, A> {
client: RefCell<C>,
@@ -67,6 +68,12 @@ impl<'a, C, NC, A> ${hub_type}<C, NC, A>
_m: PhantomData,
}
}
% for resource in sorted(c.rta_map.keys()):
pub fn ${resource}(&'a self) -> ${mb_type(resource)}<'a, C, NC, A> {
${mb_type(resource)} { hub: &self }
}
% endfor
}
@@ -84,4 +91,39 @@ ${schema.new(s, c)}
## We have to find them and process them as normal types
% for s in nested_schemas:
${schema.new(s, c)}
% endfor
% endfor
// ###################
// MethodBuilders ###
// #################
% for resource, methods in c.rta_map.iteritems():
/// A builder providing access to all methods supported on *${singular(resource)}* resources.
/// It is usually not used directly, but through the `${hub_type}` hub.
///
/// # Example
///
/// Instantiate a resource builder
///
<%block filter="rust_doc_test_norun, rust_doc_comment">\
${mutil.test_prelude()}\
<%block filter="rust_test_fn_invisible">\
${lib.test_hub(canonical_type_name(canonicalName))}\
// Usually you wouldn't stick this into a variable, but keep calling `MethodBuilders`
// like ${put_and(sorted('`%s(...)`' % f for f in methods))}
let rb = hub.${resource}();
</%block>
</%block>
pub struct ${mb_type(resource)}<'a, C, NC, A>
where NC: 'a,
C: 'a,
A: 'a, {
hub: &'a ${hub_type}<C, NC, A>
}
impl<'a, C, NC, A> MethodBuilder for ${mb_type(resource)}<'a, C, NC, A> {}
% endfor

View File

@@ -12,19 +12,13 @@
# fr == fattest resource, the fatter, the more important, right ?
fr = None
fr = sorted(schemas.values(), key=lambda s: (len(c.sta_map.get(s.id, [])), len(s.get('properties', []))), reverse=True)[0]
# resouce -> [activity, ...]
amap = dict()
for an in c.fqan_map:
resource, activity = activity_split(an)
amap.setdefault(resource, list()).append(activity)
%>\
# Features
Handle the following *Resources* with ease ...
% for r in sorted(amap.keys()):
* ${split_camelcase_s(r)} (${put_and(md_italic(sorted(amap[r])))})
% for r in sorted(c.rta_map.keys()):
* ${split_camelcase_s(r)} (${put_and(md_italic(sorted(c.rta_map[r])))})
% endfor
# Structure of this Library

View File

@@ -1,4 +1,6 @@
<%! from util import (schema_markers, rust_doc_comment, mangle_ident, to_rust_type, put_and, IO_TYPES, activity_split) %>\
<%! from util import (schema_markers, rust_doc_comment, mangle_ident, to_rust_type, put_and,
IO_TYPES, activity_split, enclose_in)
%>\
## Create new schema with everything.
## 's' contains the schema structure from json to build
<%def name="new(s, c)">\
@@ -34,8 +36,7 @@ ${s.get('description', 'There is no detailed description.')}
# Activities
This type is used in activities, which are methods you may call on this type or where this type is involved in.
The list links the activity name, along with information about where it is used (one of ${put_and(list('*%s*' % t
for t in IO_TYPES))}).
The list links the activity name, along with information about where it is used (one of ${put_and(enclose_in('*', IO_TYPES))}).
${''.join("* %s (%s)\n" % (activity_split(a)[1], iot and '|'.join(iot) or 'none')
for a, iot in c.sta_map[s.id].iteritems())}

View File

@@ -101,8 +101,17 @@ def put_and(l):
return l[0]
return ', '.join(l[:-1]) + ' and ' + l[-1]
# ['foo', ...] with e == '*' -> ['*foo*', ...]
def enclose_in(e, l):
return ['%s%s%s' % (e, s, e) for s in l]
def md_italic(l):
return ['*%s*' % s for s in l]
return enclose_in('*', l)
def singular(s):
if s[-1] == 's':
return s[:-1]
return s
def split_camelcase_s(s):
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', s)
@@ -226,44 +235,6 @@ def schema_markers(s, c):
# -------------------------
## @name Activity Utilities
# @{
# Returns (A, B) where
# A: { SchemaTypeName -> { fqan -> ['request'|'response', ...]}
# B: { fqan -> activity_method_data }
# fqan = fully qualified activity name
def build_activity_mappings(activities):
res = dict()
fqan = dict()
for an, a in activities.iteritems():
if 'methods' not in a:
continue
for mn, m in a.methods.iteritems():
assert m.id not in fqan
fqan[m.id] = m
for in_out_type_name in IO_TYPES:
t = m.get(in_out_type_name, None)
if t is None:
continue
tn = to_rust_type(None, None, t, allow_optionals=False)
info = res.setdefault(tn, dict())
io_info = info.setdefault(m.id, [])
io_info.append(in_out_type_name)
# end for each io type
# handle delete/getrating/(possibly others)
# delete: has no response or request
# getrating: response is a 'SomethingResult', which is still related to activities name
# the latter is used to deduce the resource name
an, _ = activity_split(m.id)
tn = activity_name_to_type_name(an)
info = res.setdefault(tn, dict())
if m.id not in info:
info.setdefault(m.id, [])
# end handle other cases
# end for each method
# end for each activity
return res, fqan
# return (name, method)
def activity_split(fqan):
t = fqan.split('.')
@@ -281,12 +252,54 @@ def iter_acitivities(c):
## -- End Activity Utilities -- @}
Context = collections.namedtuple('Context', ['sta_map', 'fqan_map'])
Context = collections.namedtuple('Context', ['sta_map', 'fqan_map', 'rta_map'])
# return a newly build context from the given data
def new_context(resources):
# Returns (A, B) where
# A: { SchemaTypeName -> { fqan -> ['request'|'response', ...]}
# B: { fqan -> activity_method_data }
# fqan = fully qualified activity name
def build_activity_mappings(activities):
res = dict()
fqan = dict()
for an, a in activities.iteritems():
if 'methods' not in a:
continue
for mn, m in a.methods.iteritems():
assert m.id not in fqan
fqan[m.id] = m
for in_out_type_name in IO_TYPES:
t = m.get(in_out_type_name, None)
if t is None:
continue
tn = to_rust_type(None, None, t, allow_optionals=False)
info = res.setdefault(tn, dict())
io_info = info.setdefault(m.id, [])
io_info.append(in_out_type_name)
# end for each io type
# handle delete/getrating/(possibly others)
# delete: has no response or request
# getrating: response is a 'SomethingResult', which is still related to activities name
# the latter is used to deduce the resource name
an, _ = activity_split(m.id)
tn = activity_name_to_type_name(an)
info = res.setdefault(tn, dict())
if m.id not in info:
info.setdefault(m.id, [])
# end handle other cases
# end for each method
# end for each activity
return res, fqan
# end utility
sta_map, fqan_map = build_activity_mappings(resources)
return Context(sta_map, fqan_map)
rta_map = dict()
for an in fqan_map:
resource, activity = activity_split(an)
rta_map.setdefault(resource, list()).append(activity)
return Context(sta_map, fqan_map, rta_map)
# Expects v to be 'v\d+', throws otherwise
def to_api_version(v):
@@ -296,3 +309,7 @@ def to_api_version(v):
# build a full library name (non-canonical)
def library_name(name, version):
return name + to_api_version(version)
# return type name of a resource builder, from a resource name
def mb_type(r):
return "%sMethodBuilder" % canonical_type_name(r)

View File

@@ -5,6 +5,9 @@ use std::marker::MarkerTrait;
/// The hub allows to access all resource methods more easily.
pub trait Hub: MarkerTrait {}
/// Identifies types for building methods of a particular type
pub trait MethodBuilder: MarkerTrait {}
/// Identifies types which can be inserted and deleted.
/// Types with this trait are most commonly used by clients of this API.
pub trait Resource: MarkerTrait {}

View File

@@ -43,7 +43,7 @@ impl<'a, C, NC, A> YouTube<C, NC, A>
}
pub fn videos(&'a self) -> videos::Service<'a, C, NC, A> {
videos::Service::new(&self.client, &self.auth)
videos::Service::new(&self)
}
}

View File

@@ -7,6 +7,8 @@ use rustc_serialize;
use hyper;
use oauth2;
use super::super::YouTube;
/// Reresents all aspects of a youtube video resource. May only be partially
/// available
#[derive(RustcEncodable, RustcDecodable, Default, Clone)]
@@ -75,10 +77,7 @@ pub struct Service<'a, C, NC, A>
C: 'a,
A: 'a, {
client: &'a RefCell<C>,
auth: &'a RefCell<A>,
_m: PhantomData<NC>
hub: &'a YouTube<C, NC, A>
}
impl<'a, C, NC, A> Service<'a, C, NC, A>
@@ -86,21 +85,15 @@ impl<'a, C, NC, A> Service<'a, C, NC, A>
C: BorrowMut<hyper::Client<NC>> + 'a,
A: oauth2::GetToken + 'a {
pub fn new(client: &'a RefCell<C>, authenticator: &'a RefCell<A>) -> Service<'a, C, NC, A> {
Service {
client: client,
auth: authenticator,
_m: PhantomData,
}
pub fn new(hub: &'a YouTube<C, NC, A>) -> Service<'a, C, NC, A> {
Service { hub: hub }
}
pub fn insert(&self, parts: &str, video: &Video) -> VideosInsertBuilder<'a, C, NC, A> {
VideosInsertBuilder {
client: self.client,
auth: self.auth,
hub: self.hub,
video: video.clone(),
parts: parts.to_string(),
_m: PhantomData,
}
}
}
@@ -110,12 +103,9 @@ pub struct VideosInsertBuilder<'a, C, NC, A>
C: 'a,
A: 'a {
client: &'a RefCell<C>,
auth: &'a RefCell<A>,
hub: &'a YouTube<C, NC, A>,
video: Video,
parts: String,
_m: PhantomData<NC>
}