Browse Source

Added URL mapping support

master
Artyom Beilis 13 years ago
parent
commit
3dd03ebe82
8 changed files with 710 additions and 2 deletions
  1. +3
    -0
      CMakeLists.txt
  2. +14
    -1
      bin/cppcms_tmpl_cc
  3. +129
    -1
      cppcms/application.h
  4. +80
    -0
      cppcms/url_mapper.h
  5. +1
    -0
      header_file_template.h
  6. +131
    -0
      src/application.cpp
  7. +226
    -0
      src/url_mapper.cpp
  8. +126
    -0
      tests/url_mapper_test.cpp

+ 3
- 0
CMakeLists.txt View File

@@ -236,6 +236,7 @@ set(CPPCMS_SOURCES
src/applications_pool.cpp
src/application.cpp
src/url_dispatcher.cpp
src/url_mapper.cpp
src/http_cookie.cpp
src/http_file.cpp
src/http_content_type.cpp
@@ -467,6 +468,7 @@ set(ALL_TESTS
serialization_test
status_test
xss_test
url_mapper_test
)
foreach(TEST ${ALL_TESTS})
add_executable(${TEST} tests/${TEST}.cpp)
@@ -544,6 +546,7 @@ add_test(content_type_parser_test content_type_parser_test)
add_test(cache_backend_test cache_backend_test)
add_test(serialization_test serialization_test)
add_test(xss_test xss_test)
add_test(url_mapper_test url_mapper_test)

add_test(status_test
status_test "-c" "${CNF}/status_test.js"


+ 14
- 1
bin/cppcms_tmpl_cc View File

@@ -80,7 +80,7 @@ class html_type:


class view_block:
pattern=r'^<%\s*view\s+(\w+)\s+uses\s+(:?:?\w+(::\w+)?)(\s+extends\s+(:?:?\w+(::\w+)?))?\s*%>$'
pattern=r'^<%\s*view\s+(\w+)\s+uses\s+(:?:?\w+(::\w+)*)(\s+extends\s+(:?:?\w+(::\w+)?))?\s*%>$'
type='view'
topmost = 0
def declare(self):
@@ -539,6 +539,18 @@ class gettext_block:
else:
output( "out()<<cppcms::locale::format(cppcms::locale::translate(%s)) %% (%s);" % (s , ') % ('.join(params)))

class url_block:
pattern=r'^<%\s*url\s*('+str_match+')\s*(using(.*))?\s*%>$'
def use(self,m):
s=m.group(1)
params=[]
if m.group(3):
params=make_format_params(m.group(4))
if not params:
output( "out()<<content.url(%s);" % s)
else:
output( "out()<<content.url(%s, %s);" % (s , ', '.join(params)))


class include_block:
pattern=r'^<%\s*include\s+([a-zA_Z]\w*(::\w+)?)\s*\(\s*(.*)\)\s*%>$';
@@ -648,6 +660,7 @@ def main():
skin_block(), view_block(), if_block(), template_block(), end_block(), else_block(), \
cpp_include_block(), \
gettext_block(),ngettext_block(), \
url_block(), \
foreach_block(), item_block(), empty_block(),separator_block(), \
include_block(), \
html_type(), form_block(), \


+ 129
- 1
cppcms/application.h View File

@@ -41,6 +41,7 @@ namespace cppcms {

class service;
class url_dispatcher;
class url_mapper;
class applications_pool;
class application;
class base_content;
@@ -55,6 +56,9 @@ namespace cppcms {
namespace json {
class value;
}
namespace filters {
class streamable;
}


///
@@ -124,12 +128,23 @@ namespace cppcms {

///
/// Get a dispatched class -- class that responsible on mapping between URLs and a member
/// functions of application class. This member function is application specific and not
/// functions of application class.
///
/// This member function is application specific and not
/// Connection specific.
///
url_dispatcher &dispatcher();

///
/// Get a url_mapper class -- class that responsible on mapping between real objects and
/// urls displayer on the page.
///
/// This member function is application specific and not
/// Connection specific.
///
url_mapper &mapper();

///
/// Get a cache_interface instance. Same as context().cache();
///
cache_interface &cache();
@@ -170,6 +185,25 @@ namespace cppcms {
///
void render(std::string skin,std::string template_name,std::ostream &out,base_content &content);


///
/// Render a template \a template_name of default skin using this application as content.
///
/// Side effect requires: output stream for response class, causes all updated session
/// data be saved and all headers be written. You can't change headers after calling this function.
///
void render(std::string template_name);
///
/// Render a template \a template_name of \a skin skin using this application as content
///
/// Side effect requires: output stream for response class, causes all updated session
/// data be saved and all headers be written. You can't change headers after calling this function.
///
void render(std::string skin,std::string template_name);




///
/// Register an application \a app as child. Ownership of app is not transfered to parent, however
/// it would shared it's parent reference count.
@@ -274,7 +308,101 @@ namespace cppcms {
///
virtual void clear();

///
/// Translate a message in current locale for given \a message in \a context
///
std::string translate(char const *context,char const *message);
///
/// Translate a message in current locale for given \a message
///
std::string translate(char const *message);
///
/// Translate a message in current locale for given \a single and \a plural form for number \a n in \a context.
///
std::string translate(char const *context,char const *single,char const *plural,int n);
///
/// Translate a message in current locale for given \a single and \a plural form for number \a n
///
std::string translate(char const *single,char const *plural,int n);


///
/// Map url-key \a key to actual URL, with parameters
///
/// Key format is (/|(../)+)?real_key, where real key is the value that is defined
/// in the mapper.
///
/// - If the ket does not start with "/" or "../" then current dispatcher is used
/// - If it starts with / then the actual mapper being used is root()->mapper()
/// - If it starts with ../ then the actual mapper being used is parent()->mapper()
/// - If it starts with ../../ then the actual mapper being used is parent()->parent()->mapper() and so on
///
std::string url(std::string const &key);

///
/// Map url-key \a key to actual URL, with parameter p1
///
/// Key format is (/|(../)+)?real_key, where real key is the value that is defined
/// in the mapper.
///
/// - If the ket does not start with "/" or "../" then current dispatcher is used
/// - If it starts with / then the actual mapper being used is root()->mapper()
/// - If it starts with ../ then the actual mapper being used is parent()->mapper()
/// - If it starts with ../../ then the actual mapper being used is parent()->parent()->mapper() and so on
///
std::string url(std::string const &key,
filters::streamable const &p1);

///
/// Map url-key \a key to actual URL, with parameters p1, p2
///
/// Key format is (/|(../)+)?real_key, where real key is the value that is defined
/// in the mapper.
///
/// - If the ket does not start with "/" or "../" then current dispatcher is used
/// - If it starts with / then the actual mapper being used is root()->mapper()
/// - If it starts with ../ then the actual mapper being used is parent()->mapper()
/// - If it starts with ../../ then the actual mapper being used is parent()->parent()->mapper() and so on
///
std::string url(std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2);

///
/// Map url-key \a key to actual URL, with parameters p1, p2, p3
///
/// Key format is (/|(../)+)?real_key, where real key is the value that is defined
/// in the mapper.
///
/// - If the ket does not start with "/" or "../" then current dispatcher is used
/// - If it starts with / then the actual mapper being used is root()->mapper()
/// - If it starts with ../ then the actual mapper being used is parent()->mapper()
/// - If it starts with ../../ then the actual mapper being used is parent()->parent()->mapper() and so on
///
std::string url(std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2,
filters::streamable const &p3);

///
/// Map url-key \a key to actual URL, with parameters p1, p2, p3, p4
///
/// Key format is (/|(../)+)?real_key, where real key is the value that is defined
/// in the mapper.
///
/// - If the ket does not start with "/" or "../" then current dispatcher is used
/// - If it starts with / then the actual mapper being used is root()->mapper()
/// - If it starts with ../ then the actual mapper being used is parent()->mapper()
/// - If it starts with ../../ then the actual mapper being used is parent()->parent()->mapper() and so on
///
std::string url(std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2,
filters::streamable const &p3,
filters::streamable const &p4);

private:
url_mapper &get_mapper_for_key(std::string const &key,std::string &real_key);

void recycle();
void parent(application *parent);


+ 80
- 0
cppcms/url_mapper.h View File

@@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008-2010 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCMS_URL_MAPPER_H
#define CPPCMS_URL_MAPPER_H

#include <cppcms/defs.h>
#include <booster/noncopyable.h>
#include <booster/hold_ptr.h>
#include <cppcms/filters.h>
#include <string>
#include <vector>


namespace cppcms {
///
/// \brief class for mapping URLs - oposite of dispatch
///
class CPPCMS_API url_mapper : public booster::noncopyable {
public:
url_mapper();
~url_mapper();
std::string root();
void root(std::string const &r);

void assign(std::string const &key,std::string const &url);

void set_value(std::string const &key,std::string const &value);
void clear_value(std::string const &key);
void map( std::ostream &out,
std::string const &key);

void map( std::ostream &out,
std::string const &key,
filters::streamable const &p1);

void map( std::ostream &out,
std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2);

void map( std::ostream &out,
std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2,
filters::streamable const &p3);

void map( std::ostream &out,
std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2,
filters::streamable const &p3,
filters::streamable const &p4);
private:
std::string real_map(std::string const &key,std::vector<std::string> const &params);

struct data;
booster::hold_ptr<data> d;
};

};

#endif

+ 1
- 0
header_file_template.h View File

@@ -16,3 +16,4 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////


+ 131
- 0
src/application.cpp View File

@@ -21,14 +21,19 @@
#include <cppcms/application.h>
#include <cppcms/http_context.h>
#include <cppcms/service.h>
#include <cppcms/filters.h>
#include <cppcms/cppcms_error.h>
#include <cppcms/url_dispatcher.h>
#include <cppcms/url_mapper.h>
#include <cppcms/applications_pool.h>
#include <cppcms/http_response.h>
#include <cppcms/views_pool.h>

#include <set>
#include <vector>
#include <sstream>

#include <booster/locale/message.h>

#include <cppcms/config.h>
#ifdef CPPCMS_USE_EXTERNAL_BOOST
@@ -50,6 +55,7 @@ struct application::_data {
booster::shared_ptr<http::context> conn;
int pool_id;
url_dispatcher url;
url_mapper url_map;
std::vector<application *> managed_children;
};

@@ -92,6 +98,11 @@ url_dispatcher &application::dispatcher()
return d->url;
}

url_mapper &application::mapper()
{
return d->url_map;
}

booster::shared_ptr<http::context> application::get_context()
{
return root()->d->conn;
@@ -196,6 +207,29 @@ void application::render(std::string skin,std::string template_name,base_content
service().views_pool().render(skin,template_name,response().out(),content);
}

void application::render(std::string template_name)
{
base_content *cnt = dynamic_cast<base_content *>(this);
if(!cnt) {
throw cppcms_error( "Can't use application::render(std::string) when the application "
"is not derived from base_content");
}
render(template_name,*cnt);
}

void application::render(std::string skin,std::string template_name)
{
base_content *cnt = dynamic_cast<base_content *>(this);
if(!cnt) {
throw cppcms_error( "Can't use application::render(std::string,std::string) when the application "
"is not derived from base_content");
}
render(skin,template_name,*cnt);
}




void application::render(std::string template_name,std::ostream &out,base_content &content)
{
service().views_pool().render(context().skin(),template_name,out,content);
@@ -222,6 +256,103 @@ void application::recycle()
assign_context(booster::shared_ptr<http::context>());
}

std::string application::translate(char const *ctx,char const *message)
{
return booster::locale::translate(ctx,message).str<char>(context().locale());
}
std::string application::translate(char const *message)
{
return booster::locale::translate(message).str<char>(context().locale());
}
std::string application::translate(char const *ctx,char const *single,char const *plural,int n)
{
return booster::locale::translate(ctx,single,plural,n).str<char>(context().locale());
}
std::string application::translate(char const *single,char const *plural,int n)
{
return booster::locale::translate(single,plural,n).str<char>(context().locale());
}


url_mapper &application::get_mapper_for_key(std::string const &key,std::string &real_key)
{
if(!key.empty() && key[0]=='/') {
real_key = key.substr(1);
return root()->mapper();
}
unsigned index = 0;
application *app = this;
while(key.size() >= index+3 && memcmp(key.c_str()+index,"../",3)==0) {
index+=3;
app = app->parent();
}
real_key = key.substr(index);
return app->mapper();
}


std::string application::url(std::string const &key)
{
std::ostringstream ss;
ss.imbue(context().locale());
std::string real_key;
url_mapper &mp = get_mapper_for_key(key,real_key);
mp.map(ss,real_key);
return ss.str();
}

std::string application::url( std::string const &key,
filters::streamable const &p1)
{
std::ostringstream ss;
ss.imbue(context().locale());
std::string real_key;
url_mapper &mp = get_mapper_for_key(key,real_key);
mp.map(ss,real_key,p1);
return ss.str();
}

std::string application::url( std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2)
{
std::ostringstream ss;
ss.imbue(context().locale());
std::string real_key;
url_mapper &mp = get_mapper_for_key(key,real_key);
mp.map(ss,real_key,p1,p2);
return ss.str();
}

std::string application::url( std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2,
filters::streamable const &p3)
{
std::ostringstream ss;
ss.imbue(context().locale());
std::string real_key;
url_mapper &mp = get_mapper_for_key(key,real_key);
mp.map(ss,real_key,p1,p2,p3);
return ss.str();
}

std::string application::url( std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2,
filters::streamable const &p3,
filters::streamable const &p4)
{
std::ostringstream ss;
ss.imbue(context().locale());
std::string real_key;
url_mapper &mp = get_mapper_for_key(key,real_key);
mp.map(ss,real_key,p1,p2,p3,p4);
return ss.str();
}





} // cppcms


+ 226
- 0
src/url_mapper.cpp View File

@@ -0,0 +1,226 @@
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008-2010 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////
#define CPPCMS_SOURCE
#include <cppcms/url_mapper.h>
#include <cppcms/cppcms_error.h>
#include <map>

#include <stdlib.h>

namespace cppcms {
struct url_mapper::data
{
struct entry {
std::vector<std::string> parts;
std::vector<int> indexes;
std::vector<std::string> keys;
};

typedef std::map<size_t,entry> by_size_type;
typedef std::map<std::string,by_size_type> by_key_type;
typedef std::map<std::string,std::string> helpers_type;

by_key_type by_key;
helpers_type helpers;
std::string root;

bool map( std::string const key,
std::vector<std::string> const &params,
std::string &output) const
{
by_key_type::const_iterator kp = by_key.find(key);
if(kp == by_key.end())
return false;
by_size_type::const_iterator sp = kp->second.find(params.size());
if(sp == kp->second.end())
return false;

output.clear();
output+=root;

entry const &formatting = sp->second;

for(size_t i=0;i<formatting.parts.size();i++) {
output += formatting.parts[i];
if( i < formatting.indexes.size() ) {
if(formatting.indexes[i]==0) {
std::string const &hkey = formatting.keys[i];
std::map<std::string,std::string>::const_iterator p = helpers.find(hkey);
if(p != helpers.end()) {
output += p->second;
}
}
else {
output+=params.at(formatting.indexes[i] - 1);
}
}
}

return true;

}

};

void url_mapper::assign(std::string const &key,std::string const &url)
{
data::entry e;
std::string::const_iterator prev = url.begin(), p = url.begin();

int max_index = 0;

while(p!=url.end()) {
if(*p=='{') {
e.parts.push_back(std::string(prev,p));
prev = p;
while(p!=url.end()) {
if(*p=='}') {
std::string const hkey(prev+1,p);
prev = p+1;
if(hkey.size()==0) {
throw cppcms_error("cppcms::url_mapper: empty index between {}");
}
bool all_digits = true;
for(unsigned i=0;all_digits && i<hkey.size();i++) {
if(hkey[i] < '0' || '9' <hkey[i])
all_digits = false;
}
if(!all_digits) {
e.indexes.push_back(0);
e.keys.push_back(hkey);
}
else {
int index = atoi(hkey.c_str());
if(index == 0)
throw cppcms_error("cppcms::url_mapper: index 0 is invalid");
max_index = std::max(index,max_index);
e.indexes.push_back(index);
e.keys.resize(e.keys.size()+1);
}
break;
}
else
p++;
}
if(p==url.end())
throw cppcms_error("cppcms::url_mapper: '{' in url without '}'");
p++;
}
else if(*p=='}') {
throw cppcms_error("cppcms::url_mapper: '}' in url without '{'");
}
else
p++;
}
e.parts.push_back(std::string(prev,p));
d->by_key[key][max_index] = e;
}

void url_mapper::set_value(std::string const &key,std::string const &value)
{
d->helpers[key]=value;
}
void url_mapper::clear_value(std::string const &key)
{
d->helpers.erase(key);
}

url_mapper::url_mapper() : d(new url_mapper::data())
{
}
url_mapper::~url_mapper()
{
}

std::string url_mapper::root()
{
return d->root;
}

void url_mapper::root(std::string const &r)
{
d->root = r;
}

std::string url_mapper::real_map(std::string const &key,std::vector<std::string> const &params)
{
std::string result;
if(!d->map(key,params,result)) {
throw cppcms_error("cppcms::url_mapper:invalid key `" + key + "' given");
}
return result;
}

void url_mapper::map( std::ostream &out,
std::string const &key)
{
std::vector<std::string> params;
out << real_map(key,params);
}

void url_mapper::map( std::ostream &out,
std::string const &key,
filters::streamable const &p1)
{
std::vector<std::string> params(1);
params[0] = p1.get(out);
out << real_map(key,params);
}

void url_mapper::map( std::ostream &out,
std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2)
{
std::vector<std::string> params(2);
params[0] = p1.get(out);
params[1] = p2.get(out);
out << real_map(key,params);
}

void url_mapper::map( std::ostream &out,
std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2,
filters::streamable const &p3)
{
std::vector<std::string> params(3);
params[0] = p1.get(out);
params[1] = p2.get(out);
params[2] = p3.get(out);
out << real_map(key,params);
}

void url_mapper::map( std::ostream &out,
std::string const &key,
filters::streamable const &p1,
filters::streamable const &p2,
filters::streamable const &p3,
filters::streamable const &p4)
{
std::vector<std::string> params(4);
params[0] = p1.get(out);
params[1] = p2.get(out);
params[2] = p3.get(out);
params[3] = p4.get(out);
out << real_map(key,params);
}

}

+ 126
- 0
tests/url_mapper_test.cpp View File

@@ -0,0 +1,126 @@
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008-2010 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////
#include <cppcms/url_mapper.h>
#include <cppcms/cppcms_error.h>
#include <sstream>
#include "test.h"


std::string value(std::ostringstream &s)
{
std::string res = s.str();
s.str("");
return res;
}

int main()
{
try {
cppcms::url_mapper m;
m.assign("foo","/foo");
m.assign("foo","/foo/{1}");
m.assign("foo","/foo/{1}/{2}");
m.assign("foo","/foo/{1}/{2}/{3}");
m.assign("foo","/foo/{1}/{2}/{3}/{4}");
m.assign("bar","/bar/{lang}/{1}");
m.assign("bar","/bar/{2}/{1}");
m.assign("test1","{1}x");
m.assign("test2","x{1}");
m.set_value("lang","en");
m.root("test.com");
TEST(m.root()=="test.com");

std::ostringstream ss;
m.map(ss,"foo");
TEST(value(ss) == "test.com/foo");


m.map(ss,"foo",1);
TEST(value(ss) == "test.com/foo/1");
m.map(ss,"foo",1,2);
TEST(value(ss) == "test.com/foo/1/2");
m.map(ss,"foo",1,2,3);
TEST(value(ss) == "test.com/foo/1/2/3");
m.map(ss,"foo",1,2,3,4);
TEST(value(ss) == "test.com/foo/1/2/3/4");

m.map(ss,"foo",1,"a","b",5);
TEST(value(ss) == "test.com/foo/1/a/b/5");
m.map(ss,"bar",1);
TEST(value(ss) == "test.com/bar/en/1");
m.map(ss,"bar",1,"ru");
TEST(value(ss) == "test.com/bar/ru/1");
m.root("");
m.map(ss,"test1",10);
TEST(value(ss) == "10x");
m.map(ss,"test2",10);
TEST(value(ss) == "x10");

try {
m.assign("x","a{");
TEST(0);
}
catch(cppcms::cppcms_error const &e) {}
catch(...) { TEST(0); }

try {
m.assign("x","a}");
TEST(0);
}
catch(cppcms::cppcms_error const &e) {}
catch(...) { TEST(0); }
try {
m.assign("x","a{0}");
TEST(0);
}
catch(cppcms::cppcms_error const &e) {}
catch(...) { TEST(0); }
try {
m.map(ss,"undefined");
TEST(0);
}
catch(cppcms::cppcms_error const &e) {}
catch(...) { TEST(0); }
try {
m.map(ss,"undefined");
TEST(0);
}
catch(cppcms::cppcms_error const &e) {}
catch(...) { TEST(0); }
try {
m.map(ss,"test1",1,2);
TEST(0);
}
catch(cppcms::cppcms_error const &e) {}
catch(...) { TEST(0); }


}
catch(std::exception const &e) {
std::cerr << "Fail" << e.what() << std::endl;
return 1;
}
std::cout << "ok" << std::endl;
return 0;
}


Loading…
Cancel
Save