Skip to content

Commit

Permalink
Makes include directive parameters evaluatable
Browse files Browse the repository at this point in the history
  • Loading branch information
nokome committed Dec 27, 2014
1 parent 79c1d6d commit f17387d
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 121 deletions.
185 changes: 98 additions & 87 deletions cpp/stencila/stencil-directives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ void Stencil::Execute::parse(const std::string& attribute){
static const boost::regex pattern(
"^" \
"(\\w+(\\s*,\\s*\\w+)*)" \
"(\\s+format\\s+(.+?))?" \
"(\\s+width\\s+(.+?))?" \
"(\\s+height\\s+(.+?))?" \
"(\\s+units\\s+(.+?))?" \
"(\\s+size\\s+(.+?))?" \
"(((eval)\\s+)?\\s+format\\s+(.+?))?" \
"(((eval)\\s+)?\\s+width\\s+(.+?))?" \
"(((eval)\\s+)?\\s+height\\s+(.+?))?" \
"(((eval)\\s+)?\\s+units\\s+(.+?))?" \
"(((eval)\\s+)?\\s+size\\s+(.+?))?" \
"(\\s+(const))?" \
"(\\s+(show))?" \
"$"
Expand All @@ -86,43 +86,18 @@ void Stencil::Execute::parse(const std::string& attribute){
)) throw DirectiveException("context-invalid",context);
}

format = match[4].str();
if(format.length() and not(
format=="text" or
format=="png" or format=="jpg" or format=="svg"
)) throw DirectiveException("format-invalid",format);

width = match[6].str();
height = match[8].str();
units = match[10].str();

size = match[12].str();
if(size.length()){
static const boost::regex pattern("^([0-9]*\\.?[0-9]+)x([0-9]*\\.?[0-9]+)(\\w+)?$");
boost::smatch match;
if(boost::regex_search(size, match, pattern)){
width = match[1].str();
height = match[2].str();
units = match[3].str();
} else {
throw DirectiveException("size-invalid",size);
}
}

if(not width.length()) width = "17";
if(not height.length()) height = "17";

if(units.length()){
if(not(
units=="cm" or units=="in" or units=="px"
)) throw DirectiveException("units-invalid",units);
} else {
units = "cm";
}

constant = match[14].str()=="const";
show = match[16].str()=="show";

format.eval = match[5].str()=="eval";
format.expr = match[6].str();
width.eval = match[9].str()=="eval";
width.expr = match[10].str();
height.eval = match[13].str()=="eval";
height.expr = match[14].str();
units.eval = match[17].str()=="eval";
units.expr = match[18].str();
size.eval = match[21].str()=="eval";
size.expr = match[22].str();
constant = match[24].str()=="const";
show = match[26].str()=="show";
} else {
throw DirectiveException("syntax",attribute);
}
Expand Down Expand Up @@ -177,36 +152,78 @@ void Stencil::Execute::render(Stencil& stencil, Node node, Context* context){
if(hash==current) return;
else node.attr("data-hash",hash);

// Get code and execute it
// Get code and return if zero length
std::string code = node.text();
if(code.length()>0){
// Execute
std::string result = context->execute(code,stencil.hash_,format,width,height,units);
// Remove any existing output
Node next = node.next_element();
if(next and next.attr("data-output")=="true") next.destroy();
// Append new output
if(format.length()){
Xml::Document doc;
Node output;
if(format=="text"){
output = doc.append("samp",result);
}
else if(format=="png" or format=="svg"){
output = doc.append("img",{
{"src",result},
{"style","width:"+width+units+";height:"+height+units}
});
}
else {
Stencil::error(node,"format-invalid",format);
}
if(output){
// Flag output node
output.attr("data-output","true");
// Create a copy immeadiately after code directive
node.after(output);
}
if(code.length()==0) return;

// Evaluate parameters within context and check their values
format.evaluate(context);
if(format.value.length() and not(
format.value=="text" or
format.value=="png" or format.value=="jpg" or format.value=="svg"
)) throw DirectiveException("format-invalid",format.value);

width.evaluate(context);
height.evaluate(context);
units.evaluate(context);

size.evaluate(context);
if(size.value.length()){
static const boost::regex pattern("^([0-9]*\\.?[0-9]+)x([0-9]*\\.?[0-9]+)(\\w+)?$");
boost::smatch match;
if(boost::regex_search(size.value, match, pattern)){
width.value = match[1].str();
height.value = match[2].str();
units.value = match[3].str();
} else {
throw DirectiveException("size-invalid",size.value);
}
}

if(not width.value.length()) width.value = "17";
if(not height.value.length()) height.value = "17";

if(units.value.length()){
if(not(
units.value=="cm" or units.value=="in" or units.value=="px"
)) throw DirectiveException("units-invalid",units.value);
} else {
units.value = "cm";
}

// Execute code
std::string result = context->execute(code,stencil.hash_,
format.value,
width.value,
height.value,
units.value
);
// Remove any existing output
Node next = node.next_element();
if(next and next.attr("data-output")=="true") next.destroy();

// Append new output
if(format.value.length()){
// Append output element
Xml::Document doc;
Node output;
if(format.value=="text"){
output = doc.append("samp",result);
}
else if(format.value=="png" or format.value=="svg"){
output = doc.append("img",{
{"src",result},
{"style","width:"+width.value+units.value+";height:"+height.value+units.value}
});
}
else {
throw DirectiveException("format-invalid",format.value);
}
if(output){
// Flag output node
output.attr("data-output","true");
// Create a copy immeadiately after code directive
node.after(output);
}
}

Expand Down Expand Up @@ -526,10 +543,10 @@ void Stencil::Include::parse(const std::string& attribute){
boost::smatch match;
static const boost::regex pattern("^(((eval)\\s+)?(.+?))(\\s+select\\s+((eval)\\s+)?(.+?))?$");
if(boost::regex_search(attribute, match, pattern)) {
address = match[4].str();
address_eval = match[3].str()=="eval";
select = match[8].str();
select_eval = match[7].str()=="eval";
address.expr = match[4].str();
address.eval = match[3].str()=="eval";
select.expr = match[8].str();
select.eval = match[7].str()=="eval";
} else {
throw DirectiveException("syntax","");
}
Expand All @@ -542,11 +559,6 @@ void Stencil::Include::parse(Node node){
void Stencil::Include::render(Stencil& stencil, Node node, Context* context){
parse(node);

// Obtain string representation of include_expr
std::string address_use;
if(address_eval) address_use = context->write(address);
else address_use = address;

// If this node has been rendered before then there will be
// a `data-included` node. If it does not yet exist then append one.
Node included = node.select("[data-included]");
Expand All @@ -561,15 +573,14 @@ void Stencil::Include::render(Stencil& stencil, Node node, Context* context){
//Obtain the included stencil...
Node includee;
//Check to see if this is a "self" include, otherwise obtain the includee
if(address_use==".") includee = node.root();
else includee = Component::get(address_use).as<Stencil>();
address.evaluate(context);
if(address.value==".") includee = node.root();
else includee = Component::get(address.value).as<Stencil>();
// ...select from it
if(select.length()>0){
std::string select_use;
if(select_eval) select_use = context->write(select);
else select_use = select;
select.evaluate(context);
if(select.value.length()){
// ...append the selected nodes.
for(Node node : includee.filter(select_use)){
for(Node node : includee.filter(select.value)){
// Append the node first to get a copy of it which can be modified
Node appended = included.append(node);
// Remove `macro` declaration if any so that element gets rendered
Expand Down
31 changes: 22 additions & 9 deletions cpp/stencila/stencil.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,22 @@ class Stencil : public Component, public Xml::Document {

struct Directive {
typedef std::string Name;

typedef std::string Expression;

struct Evaluatable {
bool eval = false;
std::string expr;
std::string value;

std::string evaluate(Context* context) {
if(eval and expr.length()) value = context->write(expr);
else value = expr;
return value;
}

};

typedef bool Flag;
};

Expand Down Expand Up @@ -318,11 +333,11 @@ class Stencil : public Component, public Xml::Document {
struct Execute : Directive {
bool valid;
std::vector<Name> contexts;
Expression format;
Expression width;
Expression height;
Expression units;
std::string size;
Evaluatable format;
Evaluatable width;
Evaluatable height;
Evaluatable units;
Evaluatable size;
Flag constant = false;
Flag show = false;

Expand Down Expand Up @@ -460,10 +475,8 @@ class Stencil : public Component, public Xml::Document {
* An `include` directive (e.g. `<div data-include="stats/t-test select #macros #text #simple-paragraph" />` )
*/
struct Include : Directive {
std::string address;
bool address_eval = false;
std::string select;
bool select_eval = false;
Evaluatable address;
Evaluatable select;

Include(void);
Include(const std::string& attribute);
Expand Down
45 changes: 20 additions & 25 deletions cpp/tests/stencil-directives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ BOOST_AUTO_TEST_CASE(exec){

{
E e("r format text");
BOOST_CHECK_EQUAL(e.format,"text");
BOOST_CHECK_EQUAL(e.format.expr,"text");
}{
E e("r format png");
BOOST_CHECK_EQUAL(e.format,"png");
BOOST_CHECK_EQUAL(e.format.expr,"png");
}{
E e("r format svg");
BOOST_CHECK_EQUAL(e.format,"svg");
BOOST_CHECK_EQUAL(e.format.expr,"svg");
}{
try {
E e("r format gnp");
Expand All @@ -49,21 +49,16 @@ BOOST_AUTO_TEST_CASE(exec){

{
E e("r format png width 19");
BOOST_CHECK_EQUAL(e.width,"19");
BOOST_CHECK_EQUAL(e.height,"17");
BOOST_CHECK_EQUAL(e.units,"cm");
BOOST_CHECK_EQUAL(e.width.expr,"19");
}

{
E e("py,r format png width 10 units cm size 4.2x8.4in");
BOOST_CHECK_EQUAL(e.contexts.size(),2);
BOOST_CHECK_EQUAL(e.contexts[0],"py");
BOOST_CHECK_EQUAL(e.contexts[1],"r");
BOOST_CHECK_EQUAL(e.format,"png");
BOOST_CHECK_EQUAL(e.size,"4.2x8.4in");
BOOST_CHECK_EQUAL(e.width,"4.2");
BOOST_CHECK_EQUAL(e.height,"8.4");
BOOST_CHECK_EQUAL(e.units,"in");
BOOST_CHECK_EQUAL(e.format.expr,"png");
BOOST_CHECK_EQUAL(e.size.expr,"4.2x8.4in");
}{
try {
E e("r format png size 10x10km");
Expand Down Expand Up @@ -148,28 +143,28 @@ BOOST_AUTO_TEST_CASE(includ){
typedef Stencil::Include I;
{
I i("x");
BOOST_CHECK_EQUAL(i.address,"x");
BOOST_CHECK_EQUAL(i.address_eval,false);
BOOST_CHECK_EQUAL(i.select,"");
BOOST_CHECK_EQUAL(i.address.expr,"x");
BOOST_CHECK_EQUAL(i.address.eval,false);
BOOST_CHECK_EQUAL(i.select.expr,"");
}{
I i("x select y");
BOOST_CHECK_EQUAL(i.address,"x");
BOOST_CHECK_EQUAL(i.select,"y");
BOOST_CHECK_EQUAL(i.select_eval,false);
BOOST_CHECK_EQUAL(i.address.expr,"x");
BOOST_CHECK_EQUAL(i.select.expr,"y");
BOOST_CHECK_EQUAL(i.select.eval,false);
}{
I i(". select #id .class");
BOOST_CHECK_EQUAL(i.address,".");
BOOST_CHECK_EQUAL(i.select,"#id .class");
BOOST_CHECK_EQUAL(i.address.expr,".");
BOOST_CHECK_EQUAL(i.select.expr,"#id .class");
}{
I i("eval x+'stencil'");
BOOST_CHECK_EQUAL(i.address,"x+'stencil'");
BOOST_CHECK_EQUAL(i.address_eval,true);
BOOST_CHECK_EQUAL(i.address.expr,"x+'stencil'");
BOOST_CHECK_EQUAL(i.address.eval,true);
}{
I i("eval 'address'+'/'+'of/stencil' select eval '#macro-id'");
BOOST_CHECK_EQUAL(i.address,"'address'+'/'+'of/stencil'");
BOOST_CHECK_EQUAL(i.address_eval,true);
BOOST_CHECK_EQUAL(i.select,"'#macro-id'");
BOOST_CHECK_EQUAL(i.select_eval,true);
BOOST_CHECK_EQUAL(i.address.expr,"'address'+'/'+'of/stencil'");
BOOST_CHECK_EQUAL(i.address.eval,true);
BOOST_CHECK_EQUAL(i.select.expr,"'#macro-id'");
BOOST_CHECK_EQUAL(i.select.eval,true);
}
}

Expand Down

0 comments on commit f17387d

Please sign in to comment.