Compare commits

..

No commits in common. "master" and "wiki" have entirely different histories.

51 changed files with 103 additions and 17420 deletions

39
CallingFromPython.md Normal file
View file

@ -0,0 +1,39 @@
# Getting Started #
The easiest way to call oohembed from python is by using [python-oembed](http://code.google.com/p/python-oembed/)
[Download](http://code.google.com/p/python-oembed/downloads/list) and install python-oembed to get started.
# Using python-oembed with oohembed #
below sample shows how to use oohembed for various services:
```
import oembed
import pprint
consumer = oembed.OEmbedConsumer()
endpoint = oembed.OEmbedEndpoint('http://oohembed.com/oohembed','*')
consumer.addEndpoint(endpoint)
#now this consumer can be used with several oEmbed providers.
response = consumer.embed('http://www.flickr.com/photos/wizardbt/2584979382/')
pprint.pprint(response.getData())
response = consumer.embed('http://www.youtube.com/watch?v=vk1HvP7NO5w')
pprint.pprint(response.getData())
response = consumer.embed('http://www.metacafe.com/watch/1350976/funny_call/')
pprint.pprint(response.getData())
response = consumer.embed('http://twitter.com/mai_co_jp/statuses/822499364')
pprint.pprint(response.getData())
response = consumer.embed('http://en.wikipedia.org/wiki/Life_on_Mars_(TV_series)')
pprint.pprint(response.getData())
#Other elements can be accessed like;
print response['url']
print response['html']
```

51
LICENSE
View file

@ -1,51 +0,0 @@
oohEmbed
========
Copyright (c) 2008, Deepak Sarda
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the oohEmbed project nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Jinja
=====
Jinja is a template language that oohEmbed uses for generating the
homepage at oohembed.com. Jinja is licensed under a BSD License.
See http://jinja.pocoo.org/ for more details
Python-Markdown
===============
Python-Markdown is an implementation of Markdown markup syntax
in Python. It is under a GPL2 license or BSD license.
See http://www.freewisdom.org/projects/python-markdown/
Beautiful Soup
==============
Beautiful Soup is a HTML/XML parser designed for quick turnaround
projects like screen-scraping. It is licensed under a BSD style license.
See http://www.crummy.com/software/BeautifulSoup/ for more.

49
ProjectHome.md Normal file
View file

@ -0,0 +1,49 @@
This project hosts the source code that powers oohEmbed.com
## No Longer Supported ##
oohEmbed.com was [acquired by Embedly](http://blog.embed.ly/oohembed) and since then, the site runs on Embedly's infrastructure and not using the code derived from this project.
This project is basically in archive mode now. Feel free to fork it and use it to host your own instances on App Engine. If you prefer not to use App Engine, then a few changes should get the code up & running in any Python WSGI environment.
## What is oohEmbed? ##
In a nutshell: _oohEmbed is an oEmbed compatible provider of HTML embed codes for various web sites._
What is this oEmbed? From [oembed.com](http://www.oembed.com/):
> oEmbed is a format for allowing an embedded representation of a URL on third party sites. The simple API allows a website to display embedded content (such as photos or videos) when a user posts a link to that resource, without having to parse the resource directly.
Still don't get it? Perhaps an example will make things clear. If you make a URL request like this:
> `http://oohembed.com/oohembed/?url=http%3A//www.amazon.com/Myths-Innovation-Scott-Berkun/dp/0596527055/`
You will get this as the response:
```
{
"asin": "0596527055",
"title": "The Myths of Innovation",
"url": "http://ecx.images-amazon.com/images/I/31%2BfVjL2nqL.jpg",
"thumbnail_width": "48",
"height": "500",
"width": "317",
"version": "1.0",
"author_name": "Scott Berkun",
"provider_name": "Amazon Product Image",
"thumbnail_url": "http://ecx.images-amazon.com/images/I/31%2BfVjL2nqL._SL75_.jpg",
"type": "photo",
"thumbnail_height": "75",
"author_url": "http://www.amazon.com/gp/redirect.html%3FASIN=0596527055%26location=/Myths-Innovation-Scott-Berkun/dp/0596527055"
}
```
That should make everything clear. No? Then head over to http://oohEmbed.com/ for more details!
## Brief Overview ##
oohEmbed is running on Google App Engine and so what you see in the [source repository](http://code.google.com/p/oohembed/source/browse/) here is a Python app meant to run on App Engine. Thus, some familiarity with App Engine would be a good thing to have before jumping into the code.
Although oohEmbed is a Google App Engine app, it contains very little App Engine specific code. Apart from the use of `urlfetch`, everything else is standard Python and should run anywhere else that Python runs.
The codebase is so small that hopefully, it is self-explanatory :-) If you have any questions, please feel free to ask!

55
README
View file

@ -1,55 +0,0 @@
# Welcome to oohEmbed!
## oohEmbed What?
In a nutshell: __oohEmbed is an oEmbed compatible provider of HTML embed codes for various web sites.__
What is this oEmbed? From [oembed.com](http://www.oembed.com/):
> oEmbed is a format for allowing an embedded representation of a URL on third party sites.
> The simple API allows a website to display embedded content (such as photos or videos) when
> a user posts a link to that resource, without having to parse the resource directly.
Still don't get it? Perhaps an example will make things clear. If you make a URL request like this:
http://oohembed.com/oohembed/?url=http%3A//www.amazon.com/Myths-Innovation-Scott-Berkun/dp/0596527055/
You will get this as the response:
{
"asin": "0596527055",
"title": "The Myths of Innovation",
"url": "http://ecx.images-amazon.com/images/I/31%2BfVjL2nqL.jpg",
"thumbnail_width": "48",
"height": "500",
"width": "317",
"version": "1.0",
"author_name": "Scott Berkun",
"provider_name": "Amazon Product Image",
"thumbnail_url": "http://ecx.images-amazon.com/images/I/31%2BfVjL2nqL._SL75_.jpg",
"type": "photo",
"thumbnail_height": "75",
"author_url": "http://www.amazon.com/gp/redirect.html%3FASIN=0596527055%26location=/Myths-Innovation-Scott-Berkun/dp/0596527055"
}
That should make everything clear. No? Then head over to http://oohEmbed.com/ for more details!
## What's in the source?
If you are reading this file, you've chanced upon the source code that powers oohEmbed.com
Yes, the code running behind oohEmbed.com is open source (BSD License) and is available at
http://code.google.com/p/oohembed/
## Brief Overview
oohEmbed is running on Google App Engine and so what you see in the source files is a Python
app meant to run on App Engine. Thus, some familiarity with App Engine would be a good thing
to have before jumping into the code.
Although oohEmbed is a Google App Engine app, there is very little App Engine specific in the
code itself. Apart from the use of `urlfetch`, everything else is standard Python and should
run anywhere that Python runs.
The codebase is so small that hopefully, it is self-explanatory! If you have any questions,
please feel free to ask!

File diff suppressed because it is too large Load diff

View file

@ -1,41 +0,0 @@
application: oohembed
version: vanilla
runtime: python
api_version: 1
default_expiration: "15d"
handlers:
- url: /dumble/
static_files: dumble/index.html
upload: dumble/index.html
- url: /favicon.ico
static_files: static/favicon.ico
upload: static/favicon.ico
- url: /robots.txt
static_files: static/robots.txt
upload: static/robots.txt
- url: /crossdomain.xml
static_files: static/crossdomain.xml
upload: static/crossdomain.xml
- url: /endpoints.json
static_files: static/endpoints.json
upload: static/endpoints.json
mime_type: application/json
- url: /dumble
static_dir: dumble
- url: /static
static_dir: static
- url: /admin/.*
script: main.py
login: admin
- url: /.*
script: main.py

View file

@ -1,376 +0,0 @@
String.prototype.supplant = function (o) {
/* http://javascript.crockford.com/remedial.html */
return this.replace(/{([^{}]*)}/g,
function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
}
);
};
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, "");
}
var DUMBLE_DEBUG = false;
if (location.hostname.toLowerCase() == 'localhost') {
DUMBLE_DEBUG = true;
}
var firelog = function(str) {
if(DUMBLE_DEBUG) {
if (window.console && window.console.firebug) {
console.log(str);
}
}
}
var getUrlParam = function(param) {
var regex = '[?&]' + param + '=([^&#]*)';
var results = (new RegExp(regex)).exec(window.location.search);
if(results) return results[1];
return '';
}
$.ajaxSetup({
cache: true
});
OohembedProvider = function(url, caption, notes) {
var url = url
var caption = caption
var notes = notes.trim()
var ratings = /(\d{1,2}) ?\/ ?(\d{1,2})/i .exec(notes)
var stars = ''
if (ratings && (parseInt(ratings[1]) <= parseInt(ratings[2]))) {
stars = ' <img src="images/' + Math.round(5*ratings[1]/ratings[2]) +
'_stars.png" height=16 class="stars" />'
notes = notes.replace(ratings[0], '')
} else {
// the note starts with *, ends with * or is entirely *
ratings = /^\*{2,5}\s|\s\*{2,5}$|^\*{2,5}$/i .exec(notes)
if (ratings) {
stars = ' <img src="images/' + ratings[0].trim().length
+ '_stars.png" height=16 class="stars" /> '
notes = notes.replace(ratings[0], '').trim()
}
}
if (stars) {
firelog('stars are: ' + stars)
notes = notes ? notes + '<br/>' + stars : stars
}
this.template = '<div class="link"><h3><a href="{url}" target="_blank">{caption}</a></h3>{notes}</div>'
var elem = $(this.template.supplant({url: url, caption: caption, notes: notes}));
this.video_template = '<div class="video">{embed}<span class="caption">{caption}</span>{notes}</div>'
this.photo_template = '<div class="photo"><a href="{url}" target="_blank"><img src="{img_url}" alt="{caption}" title="{caption}" width="{width}" height="{height}" /></a><span class="caption">{caption}</span>{notes}</div>{html}'
this.quote_template = '<div class="quote">&#8220;{text}&#8221;&nbsp;&nbsp;' +
'<span class="source"><a href="{url}" target="_blank">{source}</a></span></div><div style="margin: 1em;">{notes}</div>{html}';
$.getJSON('http://'+location.host+'/oohembed/?url=' + escape(url) + '&format=json&maxwidth=480&callback=?',
function(data) {
firelog('received data: ' + data.provider_name);
if (data.type == 'video') {
var snip = video_template.supplant({
embed: data.html,
caption: caption,
notes: notes?notes:''});
firelog('replacing elem with: ' + snip);
elem.html(snip);
} else if (data.type == 'photo') {
var credit = ''
if (data.title && data.author_name) {
credit = '<em>'+data.title+' by '+data.author_name+'</em>'
}
notes = notes ? (notes + (credit ? '<br/>'+credit : '')) : credit
var snip = photo_template.supplant({
url: url,
img_url: data.url,
width: data.width,
height: data.height,
caption: caption,
notes: notes,
html: data.html?'<div><blockquote>'+data.html+'</blockquote></div>':''});
firelog('replacing elem with: ' + snip);
elem.html(snip);
} else if (data.type == 'link') {
if (data.title) {
var source = data.author_name ? data.author_name : 'source'
var snip = quote_template.supplant({
url: url,
text: data.title,
html: data.html?'<div><blockquote>'+data.html+'</blockquote></div>':'',
source: source,
notes: notes?notes:''});
firelog('replacing elem with: ' + snip);
elem.html(snip);
}
}
}
);
return elem;
}
GenericImageProvider = function(url, caption, notes) {
this.re = /.*(jpeg|jpg|png|bmp|gif)$/i
this.template = '<div class="photo"><img src="{url}" alt="{caption}" title="{caption}" /><span class="caption">{caption}</span>{notes}</div>'
var matches = this.re.exec(url);
if (!matches) {
return false;
}
var elem = this.template.supplant({url: url, caption: caption, notes: notes});
return $(elem);
}
var Providers = new Array(
GenericImageProvider,
OohembedProvider
);
var Analytics = Analytics ? Analytics : {
pageTracker: null,
init: function(page) {
if (DUMBLE_DEBUG) {return}
$.getScript("http://www.google-analytics.com/ga.js", function(page) {
Analytics.pageTracker = _gat._getTracker("UA-1736551-3");
Analytics.pageTracker._initData();
if(page) { Analytics.trackPage(page); }
});
},
trackPage: function(page) {
if (DUMBLE_DEBUG) {return}
if (!this.pageTracker) {
this.init(page);
} else {
this.pageTracker._trackPageview(page);
}
}
}
var Dumble = Dumble ? Dumble : {
currentUser: 'antrix',
currentTag: 'linker',
currentData: [],
currentURL: function() {
return this.urlFor(this.currentUser, this.currentTag);
},
urlFor: function(user, tag) {
return 'http://feeds.delicious.com/v2/json/' + user + ( tag ? '/' + tag : '');
},
friendsURLFor: function(user) {
return 'http://feeds.delicious.com/v2/json/networkmembers/' + user;
},
tagsURLFor: function(user) {
return 'http://feeds.delicious.com/v2/json/tags/' + user;
},
permalink: function(user, tag) {
if(!user) {user = this.currentUser}
if(!tag && user == this.currentUser) {tag = this.currentTag}
return location.protocol + '//' + location.host + location.pathname + '?u=' + user
+ (tag ? '&t=' + tag : '');
},
writeCookie: function() {
$.cookie('dumble010608', 'u='+this.currentUser+';t='+this.currentTag, {expires: 365});
/* Google Analytics */
Analytics.trackPage("/dumble/"+this.currentUser+"/"+this.currentTag);
},
readCookie: function() {
var prefs = $.cookie('dumble010608');
if (!prefs) return;
var data = prefs.split(';');
for (i=0; i<data.length; i++) {
if (data[i].charAt(0)=='u') {
this.currentUser = data[i].substring(2, data[i].length);
}
if (data[i].charAt(0)=='t') {
this.currentTag = data[i].substring(2, data[i].length);
}
}
},
updatePageFor: function(user, tag) {
if (typeof _lastUser == 'undefined' || _lastUser != user) {
_lastUser = user;
this.currentUser = user;
this.updateFriends();
this.updateTags();
}
this.currentTag = tag ? tag : '';
this.currentData = [];
$('#sourceUser').val(this.currentUser);
$('#sourceTag').val(this.currentTag);
$('#permalink').attr('href', this.permalink());
$('#rss-feed-body').attr('href', 'http://feeds.delicious.com/v2/rss/'+this.currentUser+'/'+this.currentTag);
$('#header h2').html('auto tumbling <em>'+this.currentUser+'&rsquo;s</em> delicious links'
+ (tag ? ' tagged <em>' + tag + '</em>' : ''));
this.writeCookie();
this.updateHistory();
this.updatePage();
},
insertItems: function() {
var count = 0;
while (this.currentData.length > 0) {
var item = this.currentData.shift();
$.each(Providers, function() {
var v = this(item.u, item.d, item.n ? item.n : '');
if (v) {
$('#dynposts').append(
$('<div class="post"></div>\n').hide().prepend(v));
count += 1;
return false;
}
});
if (count >= (DUMBLE_DEBUG ? 5 : 20)) {
break;
}
}
if (this.currentData.length > 0) {
$('#previous-next').fadeIn(1000);
}
$('.post').fadeIn(3000);
},
updateHistory: function() {
var string = this.currentUser + (this.currentTag ? '/' + this.currentTag : '');
var e = $('<li><a href="{l}" onClick="javascript:Dumble.updatePageFor(\'{n}\', \'{t}\');return false;">'.supplant({n: this.currentUser, t: this.currentTag, l: this.permalink()})
+string+ '</a></li>');
$('#history ul').prepend(e);
if ($('#history ul li').length == 2) {
$('#history ul').slideDown('fast');
$('#history-clear').show();
$('#history').fadeIn('slow');
}
},
updateTags: function() {
$('#tags-list').fadeOut(1000);
$('#tags h3').text("{name}'s top tags".supplant({name: this.currentUser}));
$.getJSON(this.tagsURLFor(this.currentUser) + '?count=20&sort=count&callback=?',
function(tags) {
var tgt = $('#tags-list');
tgt.empty();
if(tags) { /* Delicious returns tags as {tag1: count1, 'foo': 20, 'bar': 30} */
$.each(tags, function(tag, count) {
var e = $('<li><a href="' +Dumble.permalink(Dumble.currentUser, tag)+ '" onClick="javascript:Dumble.updatePageFor(\'{name}\', \'{tag}\');return false;">{tag}</a></li>'.supplant({name: Dumble.currentUser, tag: tag}));
tgt.append(e);
});
}
if(tgt.html() == '') {
tgt.text(Dumble.currentUser + " hasn't tagged any links!");
}
$('#tags-list').fadeIn(1000);
});
},
updateFriends: function() {
$('#friends-list').fadeOut(1000);
$('#friends h3').text("Explore {name}'s network".supplant({name: this.currentUser}));
$('#networklink a').attr('href', 'http://delicious.com/network?add=' + this.currentUser)
.text("Add {name} to your delicious network".supplant({name: this.currentUser}));
$.getJSON(this.friendsURLFor(this.currentUser) + '?callback=?',
function(names) {
var tgt = $('#friends-list');
tgt.empty();
$.each(names, function() {
var name = this.user;
var e = $('<li><a href="' +Dumble.permalink(name)+ '" onClick="javascript:Dumble.updatePageFor(\'{name}\');return false;">{name}</a></li>'.supplant({name: name}));
tgt.append(e);
});
if (!tgt.text()) {
tgt.text(Dumble.currentUser + "'s network is empty! Is this an anti-social person? ;-)");
}
$('#friends-list').fadeIn(1000);
});
},
updatePage: function(URL) {
$('body').css({ cursor: 'wait' });
$('#previous-next').fadeOut(2000);
if (this.currentData.length <= 0) {
$('#dynposts').fadeOut(1000).empty().fadeIn(1);
$.getJSON((URL ? URL : this.currentURL())+'?count=100&callback=?',
function(data) {
if (data.length > 0) {
Dumble.currentData = data;
Dumble.insertItems();
} else {
$('#dynposts').append(
$('<div class="post"> \
<h3 style="text-align: center;">\
No items found :-(</h3></div>\n').hide());
$('.post').fadeIn(3000);
}
$('#about').slideUp('fast');
$('body').css({ cursor: 'default' });
});
} else {
this.insertItems();
$('body').css({ cursor: 'default' });
}
}
} /* End Dumble namespace */
$(document).ready(function() {
/* Some page setup first */
$('#about').hide();
$('#previous-next').hide();
$('#history').hide();
/* Is our location URL the base Dumble app url or does it have u=? & t=? */
var isBaseURL = true;
var m = getUrlParam('u');
if (m) {
Dumble.currentUser = m;
Dumble.currentTag = '';
isBaseURL = false;
}
m = getUrlParam('t');
if (m) {
Dumble.currentTag = m;
isBaseURL = false;
}
m = unescape(getUrlParam('title'));
if (m) {
$('#header h1 a').text(m);
window.document.title = m;
}
if (isBaseURL) {
/* The initial page loaded via the root Dumble app url */
Dumble.readCookie();
}
$('#aboutHeader,#updateSource').hover(
function() {
$(this).css({ cursor: 'pointer' });
},
function() {
$(this).css({ cursor: 'default' });
})
$('#aboutHeader').click(
function() {
$('#about').slideToggle('fast');
Analytics.trackPage("/dumble/--about-header--/");
});
$('#sourceForm').submit(function() {
Dumble.updatePageFor($('#sourceUser').val(), $('#sourceTag').val());
$('#sourceUser').blur(); $('#sourceTag').blur();
return false;
});
Dumble.updatePageFor(Dumble.currentUser, Dumble.currentTag);
}); /* End $(document).ready() block */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

View file

@ -1,445 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Dumble : auto tumble your delicious links</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="description" content="Dumble is an experiment in creating a tumblelog from a user's delicious links."/>
<!--<link rel="shortcut icon" href="{Favicon}">
<link rel="alternate" type="application/rss+xml" title="RSS" id="rss-feed-head" href=""/>
-->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.2/jquery.min.js"></script>
<script type="text/javascript" src="jquery.cookie.js"></script>
<script type="text/javascript" src="dumble.js"></script>
<style type="text/css" media="screen">
/* CSS Reset by Eric Meyer
* http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-weight: inherit;
font-style: inherit;
font-size: 100%;
font-family: inherit;
vertical-align: baseline;
}
/* remember to define focus styles! */
:focus {
outline: 0;
}
body {
line-height: 1;
color: black;
background: white;
}
ol, ul {
list-style: none;
}
/* tables still need 'cellspacing="0"' in the markup */
table {
border-collapse: separate;
border-spacing: 0;
}
caption, th, td {
text-align: left;
font-weight: normal;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: "";
}
blockquote, q {
quotes: "" "";
}
body {
background:#262523;
color: #ddc;
font: normal 14px "Trebuchet MS", Helvetica, Arial, sans-serif;
line-height: 1.5em;
}
b, strong {
font-weight: bold;
}
abbr,acronym {
border-bottom:1px dotted #CC9933;
cursor:help;
}
em, i {
font-style: italic;
}
h1 {
font-size: 2.5em;
font-weight: bold;
line-height: 1em;
}
h2 {
color: #ccb;
font-size: 1em;
font-weight: normal;
padding-left: 3em;
}
p {
margin: 0 0 1.5em;
}
a, a:visited, a:active {
color: #94a970;
text-decoration: underline;
}
a:hover {
color: #262523;
text-decoration: none;
}
abbr {
border-bottom-width: 1px;
border-bottom-style: dotted;
}
blockquote {
background-color: #4f534f;
border: 1px solid #333;
font-style: italic;
margin: 1em 2em 1.5em 1em;
padding: .25em 1em;
}
ul, ol {
margin: 1.5em 3em;
}
ul {
list-style: disc;
}
ol {
list-style: decimal;
}
input {
color: #ddc;
background-color: #363b39;
font-size: 0.9em;
padding: 0.3em;
border: 1px solid #ddc;
margin-right: 1em;
}
button {
color: #ddc;
background-color: #334B42;
font-size: 0.9em;
padding: 0.3em;
border: 1px solid #ddc;
}
/* Header */
#header {
margin: 1em auto;
width: 40em;
}
#header a, #header a:visited, #header a:active {
color: #eef;
text-decoration: none;
}
#header a:hover {
color: #ccb;
}
#friends {
border-top: 1px solid #555;
}
.wordlist {
margin: 1em;
padding: 1em 0em 0em 0em;
}
.wordlist br {clear: both}
.wordlist ul { list-style: none;}
.wordlist li {
margin-bottom: 0.5em; padding: 0; display: block; float: left; width: 25%;
}
/* Posts */
#posts {
margin: 0 auto;
width: 42em;
}
/* Individual Post */
.post {
background-color: #363b39;
border: 1px solid #494949;
margin: 1.5em 0;
padding: 1em;
}
.post .date {
display: block;
font-size: 1.5em;
font-weight: normal;
margin-bottom: .5em;
text-align: right;
}
.post .date a, .post .date a:visited, .post .date a:active {
color: #94a970;
text-decoration: none;
}
.post .date a:hover {
color: #262523;
}
.post .date a .diff {
font-size: 1.6em;
line-height: 0;
}
/* Titles */
.regular h3, .conversation h3, .link h3 {
color: #94a970;
font-size: 1.5em;
line-height: 1em;
margin-bottom: .5em;
}
/* Quote Posts */
.quote {
font-size: 1.5em;
line-height: 1em;
}
.quote .source {
font-size: smaller; font-style: italic;
}
.quote .source a {
text-decoration: none;
}
/* Link Posts */
.link h3 a, .link h3 a:visited, .link h3 a:active {
text-decoration: none;
}
.link h3 a:hover {
text-decoration: none;
}
/* Photo/Video/Audio */
.photo, .video, .audio {
text-align: center;
overflow: auto;
}
.photo img {
background-color: #404040;
border: 1px solid #333;
margin: 1em 0 .5em;
padding: 5px;
vertical-align: middle;
}
.photo .stars {
background-color: transparent;
border: 0pt none;
margin: 0;
padding: 0;
vertical-align: middle;
}
.photo .caption, .video .caption, .audio .caption {
display: block;
text-align: center;
font-size: 1.5em;
font-weight: normal;
margin-top: .5em;
margin-bottom: .5em;
}
/* Conversations */
.conversation ul {
list-style: none;
}
.conversation ul li {
border-bottom: 1px solid #565b59;
padding: .25em 0;
}
.conversation ul li .person {
color: #565b59;
display: block;
float: left;
width: 9em;
}
.conversation ul li .line {
display: block;
margin-left: 10em;
}
/* Previous and Next Links */
#previous-next {
font-size: 1.25em;
margin: 3em 0 2em;
text-align: center;
}
#previous-next .pagecount {
margin: 0 1.5em;
}
#previous-next a, #previous-next a:visited, #previous-next a:active {
text-decoration: none;
}
#previous-next a:hover {
color: #ddc;
}
/* Footer */
#footer {
border-top: 1px solid #555;
color: #555;
margin: 0 auto;
width: 40em;
}
#footer ul {
list-style: none;
text-align: center;
}
#footer ul li {
display: inline;
margin: 0 1em;
}
#footer ul li a, #footer ul li a:visited, #footer ul li a:active {
color: #94a970;
text-decoration: none;
}
#footer ul li a:hover {
text-decoration: underline;
}
#history {
text-align: left;
position: absolute;
width: 15em;
background-color: #363b39;
border: 1px solid #494949;
margin-left: 44em;
padding: 0em;
}
#history h2 {text-align: center; padding: 0.2em; border-bottom: 1px solid #494949;
font-size: 1.2em;}
#history ul {list-style: none; margin: 1em 1em 0.5em 1em;}
#history-clear a, #history-clear a:visited,
#history-clear a:active, #history-clear a:hover {text-decoration: none; color: #555;}
</style>
<!--[if IE]>
<style type="text/css" media="screen">
.conversation ul li {
width: 98%;
}
</style>
<![endif]-->
</head>
<body>
<div id="header">
<h1><a href=".">Dumble: delicious tumblelog</a></h1>
<h2>auto tumble your delicious links</h2>
</div>
<div id="posts">
<div id="history"><h2>Session History</h2><ul></ul>
<h2 id="history-clear">
<a href="" onClick="javascript:$('#history-clear').hide();$('#history ul').slideUp('slow').empty();Dumble.updateHistory();return false;">Clear?</a>
</h2>
</div>
<div class="post" style="background-color: #334B42;">
<div class="regular">
<h3 id="aboutHeader" style="color: #eef; text-align: center;">What is Dumble?</h3>
<div id="about"><strong>Dumble</strong> is an <a href="http://oohembed.com/" target="_blank">oohEmbed</a> powered <a href="http://antrix.net/journal/techtalk/dumble.html" target="_blank">experiment</a> in creating a <a href="http://en.wikipedia.org/wiki/Tumblelog" target="_blank">tumblelog</a> from a user's <a href="http://delicious.com/" target="_blank">delicious.com</a> links.<br/><br/>To create a tumblelog for a different delicious user, just modify the details below.
<div style="margin: 1em 1em 0em 1em; padding: 1em 1em 0em 1em;">
<form id="sourceForm" style="margin-bottom: 2em;">
Username: <input id="sourceUser" type="text" size="10"/>
A tag (optional): <input id="sourceTag" type="text" size="10"/>
<button type="submit">Update</button>
</form>
</div>
<div id="tags" class="wordlist">
<h3 style="text-align: center; color: #ddc;"></h3>
<ul id="tags-list" class="wordlist"></ul><br/>
</div>
<div style="border-top: 1px solid #555; padding: 1em; margin: 1em;">
Need a link to this page? &nbsp;<a id='permalink' href="">Here you go!</a>
&nbsp;Or get the feed! &nbsp;<a id='rss-feed-body' style="border: none;" href=""><img style="vertical-align: middle" src="images/feed-icon.png" alt="RSS Feed icon"/></a><br/><br/>
There's also a <a href="javascript:{var b='http://oohembed.com/dumble/?u='; var u=document.location.href;m = /del\.icio\.us\/([\w\.]+)\/?/i.exec(u);if(m && m[1] != 'tag' && m[1] != 'network' && m[1] != 'subscriptions' && m[1] != 'url' && m[1] != 'for') {var go = b+m[1]; n = /del\.icio\.us\/\w+\/(\w+)\/?/i.exec(u); if (n) {go = go + '&t=' + n[1];} document.location.href=go;} else {alert('Could not figure out delicious user name');}}">View in Dumble</a> bookmarklet and a similar <a href="http://antrix.net/stuff/gm/dumble.oohembed.user.js">greasemonkey script</a> to integrate Dumble with delicious.
</div>
<div id="friends" class="wordlist">
<h3 style="text-align: center; color: #ddc;"></h3>
<ul id="friends-list" class="wordlist"></ul><br/>
<div id="networklink" style="text-align: center; margin-top: 1em;">
<a target="_blank"></a>
</div>
</div>
</div>
</div> <!-- end regular post -->
</div>
<div id="dynposts">
<!-- dynamic posts inserted here -->
</div>
<div id="previous-next">
<a href="" onClick="javascript:Analytics.trackPage('/dumble/--more--/');Dumble.updatePage();return false;">More?</a>
</div>
</div> <!-- end posts-->
<div id="footer">
<ul>
<li>Engine <a href="http://jquery.com/" target="_blank">jQuery</a></li>
<li>Designer <a href="http://cubicle17.com/" target="_blank">Bill Israel</a></li>
<li>Code Monkey <a href="http://antrix.net/" target="_blank">Deepak Sarda</a></li>
</ul>
</div>
</body>
</html>

View file

@ -1,51 +0,0 @@
/**
* Cookie plugin
*
* Copyright (c) 2006 Klaus Hartl (stilbuero.de)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
jQuery.cookie = function(name, value, options) {
if (typeof value != 'undefined') { // name and value given, set cookie
options = options || {};
if (value === null) {
value = '';
options.expires = -1;
}
var expires = '';
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
var date;
if (typeof options.expires == 'number') {
date = new Date();
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
date = options.expires;
}
expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
}
// CAUTION: Needed to parenthesize options.path and options.domain
// in the following expressions, otherwise they evaluate to undefined
// in the packed version for some reason...
var path = options.path ? '; path=' + (options.path) : '';
var domain = options.domain ? '; domain=' + (options.domain) : '';
var secure = options.secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
} else { // only name given, get cookie
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
};

File diff suppressed because it is too large Load diff

View file

@ -1,267 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="description" content="oohEmbed is an oEmbed compatible provider of HTML embed codes for various web sites"/>
<title>oohEmbed.com - your one-stop oEmbed provider</title>
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/reset/reset-min.css" />
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/base/base-min.css" />
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/fonts/fonts-min.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.2/jquery.min.js"></script>
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<style type="text/css">
html, body {
height: 100%;
background-color: #EFEFEF;
}
body {
margin: 1em;
color: black;
/*font-family: Consolas, 'Lucida Console', 'DejaVu Sans Mono', Monaco, 'Courier New', monospace; */
font-family: monospace;
}
pre {
background-color: #e0e0e0;
border: 1px solid #999;
padding: 8px;
margin: 0px 20px 10px 0px;
}
code {
background-color: #e0e0e0;
border: 1px solid #999;
padding: 0px 2px;
}
.naked {
margin: 0em;
}
.naked li {
list-style-type: none;
margin-left: 0;
padding: 0.5em;
text-indent: -0.5em;
}
</style>
</head>
<body>
{% filter markdown %}
# Welcome to oohEmbed!
## oohEmbed What?
In a nutshell: __oohEmbed is an oEmbed compatible provider of HTML embed codes for various web sites.__
What is this oEmbed? From [oembed.com](http://www.oembed.com/):
> oEmbed is a format for allowing an embedded representation of a URL on third party sites.
> The simple API allows a website to display embedded content (such as photos or videos) when
> a user posts a link to that resource, without having to parse the resource directly.
Don't get it? Perhaps an example will make things clear. If you make a URL request like this:
{% endfilter %}
<pre>http://oohembed.com/oohembed/?url=http%3A//www.amazon.com/Myths-Innovation-Scott-Berkun/dp/0596527055/</pre>
{% filter markdown %}
You will get this as the response:
{% endfilter %}
<pre>{
"type": "photo",
"version": "1.0",
"provider_name": "Amazon Product Image",
"url": "http://ecx.images-amazon.com/images/I/31%2BfVjL2nqL.jpg",
"height": "500",
"width": "317",
"thumbnail_url": "http://ecx.images-amazon.com/images/I/31%2BfVjL2nqL._SL75_.jpg",
"thumbnail_height": "75",
"thumbnail_width": "48",
"asin": "0596527055",
"title": "The Myths of Innovation",
"author_name": "Scott Berkun",
"author_url": "http://www.amazon.com/gp/redirect.html%3FASIN=0596527055%26location=/Myths-Innovation-Scott-Berkun/dp/0596527055"
}</pre>
{% filter markdown %}
There, that should make everything clear. Still no? Then perhaps you should go and read this excellent article introducing [oEmbed & oohEmbed](http://ciaranmcnulty.com/blog/2009/01/embedding-third-party-content-in-your-site-using-oembed) by Ciaran McNulty. Once you are done reading that, come back here for our Q&A!
## Q & A
1. How is this useful?
I built oohEmbed to further development of [Dumble](/dumble/?u=antrix&t=dumbletest), my delicious.com tumblelog.
But I imagine oohEmbed could be useful in several other places. For example, someone could build a
blog engine plugin backed by the oohEmbed web service that makes embedding content into blog posts
easier. (If you do build such a plugin, let me know!)
Scroll down to see a list of [consumer sites and apps](#consumers) that use oohEmbed.
2. So what sites can oohEmbed provide information for?
Quite a few to start with - scroll down to see [the list](#configuration). I'll be adding more sites as I find time!
3. Is oohEmbed really oembed.com spec compliant?
Almost. oohEmbed differs in some ways from the spec.
* It usually ignores the maxwidth/maxheight request parameters.
* Only JSON responses are provided. I don't have any plans of supporting XML.
* You can specify a JSON callback.
4. JSON Callback?
To overcome the [same origin policy](http://en.wikipedia.org/wiki/Same_origin_policy) of web browsers, you can specify a `callback` parameter as part of your request.
e.g., if you specify `callback=myCallback` as part of the request, then the JSON response object will be wrapped as a parameter to the `myCallback` function.
5. Will this callback expose my site to cross-site attacks?
Possibly. Although you have to trust that I won't be doing anything funny or malicious intentionally, oohEmbed itself is just a proxy to the target website.
So if the target website sends back something bad, all bets are off!
6. So I can't sue you if anything goes wrong?
No! By using this service, you agree that I will not be held liable for anything that goes wrong. I do appreciate [bug reports](http://code.google.com/p/oohembed/issues/list) though.
7. Is it free to use?
Yes. At the moment, all this service costs me is $10/year for the domain name and several hours of my free time.
Google App Engine kindly hosts the app for free (thanks!) so that's taken care of.
8. But is it _really free_?
Oh yes, it is _really free_ too! You can get access to the code that powers oohEmbed.com from the [oohEmbed project on Google Code](http://oohembed.googlecode.com).
The code is available under a liberal BSD license so feel free to use it as you wish. Of course, I'd appreciate any code contributions too!
8. So I can use the site as much as I want?
Yes, but to a limit. If I detect abuse, I reserve the right to throttle your use or worse, ban you from accessing the service.
9. Great, how do I sign-up?!
Umm.. there is no sign-up. Here's your service, run with it: `http://oohembed.com/oohembed/?url=`
10. I'm all fired up. Where's the oEmbed provider configuration for the oohEmbed service to get me going?
Just scroll down this page for the [API endpoint and supported URL schemes](#configuration).
11. I notice that you also support websites which already have an oEmbed compliant provider. Why?
Two reasons:
* They don't support the 'callback' parameter for JSON requests
* As a developer, wouldn't it be nice if there was just one API endpoint for all sites
instead of having to support one endpoint for each?
12. I represent one of the sites for which oohEmbed provides responses and I don't like it.
Fair enough. Drop me an <a href="mailto:deepak@antrix.deletethis.net">email</a> and I'll stop supporting your site.
13. I would rather not bounce my requests through oohEmbed. Can I just get your list of oEmbed compliant providers instead?
You are in luck! The list of all oEmbed compliant endpoints that oohEmbed knows about can be [downloaded as a JSON file](/static/endpoints.json).
14. How do I report bugs? Do you take feature requests?
Sure. Please report bugs & enhancement requests using the [issue tracker](http://code.google.com/p/oohembed/issues/) on Google Code.
15. Anything else?
Nothing, except that oohEmbed wouldn't be possible without the folks behind the oEmbed.com spec so kudos to them.
And thanks again to Google for creating App Engine!
16. Who are you?
I am Deepak Sarda and you can find me <a href="http://antrix.net/" rel="me">here</a>.
## <a name='configuration'>Configuration</a>
There's just one API endpoint: <code>http://oohembed.com/oohembed/</code>. The supported URL schemes are listed below. If you call the API endpoint with a URL that doesn't confirm to one of the schemes listed below, you will get a 404 response. As mentioned earlier, you can use a `callback` parameter when calling the endpoint.
Click on any item in the list below to see configuration details for that item.
{% endfilter %}
<ol>
{% for p in providers %}
<li style="margin-bottom: 1.5em;"><strong class="provider-title">{{ p.title }}</strong>
<ul class="naked provider-body"><li>{{ p.description }}</li>
<li><em>URL scheme:</em> <code>{{ p.url|escape }}</code></li>
<li><em>Example:</em> <code>
{{ '<a target="_blank" href="http://'+ hostname + '/oohembed/?url='+(p.example_url|urlencode)+'">http://'+hostname+'/oohembed/?url='+(p.example_url|urlencode)+'</a>' }}</code></li></ul>
</li>
{% endfor %}
</ol>
{% filter markdown %}
## <a name='consumers'>Consumers</a>
These consumers currently use the oohEmbed service.
* [Dumble](/dumble/?u=antrix&t=linker) of course, being the first consumer and my reason for creating oohEmbed.
* [280 Slides](http://blog.280north.com/2008/06/25/280-slides-gets-smarter-about-links/), a snazzy web-app to create presentations.
* [Buckybase](http://buckybase.blogspot.com/2008/06/oembed-support.html), a social web database with bidirectional hyperlinks.
* [Indy.com](http://www.indy.com/), a local entertainment site by The Indianapolis Star.
* [Ars Technica](http://arstechnica.com/), a technology news and analysis site.
* [DropAVideo](http://dropavideo.com/), a Brazilian video micro-blogging service.
* [Zenbe Shareflow](http://www.zenbe.com/shareflow), a team collaboration service.
* [Hostelz.com](http://www.hostelz.com/), a hostel review site.
* [StatusNet](http://status.net/), a popular open-source microblogging platform.
Moreover, oohEmbed support is already baked into these libraries:
* [django-oembed](http://code.google.com/p/django-oembed/)
* [ruby-oembed](http://github.com/judofyr/ruby-oembed/tree/master)
* [oembed_links gem](http://github.com/netshade/oembed_links/tree/master)
* [jquery-oembed](http://code.google.com/p/jquery-oembed/)
* [drupal-oembed](http://github.com/voxpelli/drupal-oembed/tree/master)
* [Typo3 Extension](http://typo3.org/extensions/repository/view/oembed/current/)
If you are using oohEmbed, please leave a comment below and I'll add you to this list!
Consumers may also be interested in [downloading the list of oEmbed compliant endpoints](/static/endpoints.json) that oohEmbed knows about and use this information to make direct requests to the respective endpoints.
## Changes
* _19th November, 2010:_ Added downloadable list of oEmbed compliant providers. New support for Clikthrough, Photobucket, Kinomap, dotsub & YFrog.
* _17th April, 2010:_ New dotsub.com support.
* _25th February, 2010:_ Fix Amazon support. Switch Youtube to upstream oembed endpoint.
* _2nd September, 2009:_ New yfrog.com support. New xkcd support.
* _31st August, 2009:_ Chocochip release with bunch of newly supported sites and lots of bug fixes. [Release Notes](http://code.google.com/p/oohembed/wiki/chocochip).
* _6th June, 2009:_ Added missing width, height elements to Youtube response. Added title, author and thumbnail elements to Metacafe response.
* _10th May, 2009:_ New LiveJournal UserPic provider. More data returned in Youtube provider. Removed Pownce provider. Updated Consumers section.
* _31st March, 2009:_ Updates to Consumers section.
* _2nd March, 2009:_ New twitpic.com support.
* _3rd February, 2009:_ New 5min.com, nfb.ca & thedailyshow.com support.
* _14th November, 2008:_ Released oohEmbed source. Fixed mime-type of responses. Moved Dumble to new delicious.com API.
* _23rd July, 2008:_ New Vimeo support. Some text encoding related fixes.
* _30th June, 2008:_ New SlideShare provider. Cleaned up homepage (added consumers). Several minor fixes.
* _12th June, 2008:_ New IMDb.com movie photo/link provider.
* _11th June, 2008:_ New Wordpress.com blogs support. Several fixes in [Dumble](/dumble/).
* _3rd June, 2008:_ New hulu.com & revision3.com support
* _1st June, 2008:_ Launch
{% endfilter %}
<hr/>
<script type="text/javascript">
$(document).ready(function() {
$('.provider-body').hide();
$('.provider-title').hover(
function() {
$(this).css({ cursor: 'pointer' });
},
function() {
$(this).css({ cursor: 'default' });
}
);
$('.provider-title').click(
function() {
$(this).next('.provider-body').slideToggle('fast');
}
);
});
</script>
{% if production %}
<a name='comments'></a>
<div style="margin-top: 1em;" id="disqus_thread"></div><script type="text/javascript" src="http://disqus.com/forums/oohembed/embed.js"></script><noscript><a href="http://oohembed.disqus.com/?url=ref">View the forum thread.</a></noscript><a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-1736551-3");
pageTracker._initData();
pageTracker._trackPageview();
</script>
{% endif %}
</body>
</html>

View file

@ -1,72 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja
~~~~~
Jinja is a `sandboxed`_ template engine written in pure Python. It
provides a `Django`_ like non-XML syntax and compiles templates into
executable python code. It's basically a combination of Django templates
and python code.
Nutshell
--------
Here a small example of a Jinja template::
{% extends 'base.html' %}
{% block title %}Memberlist{% endblock %}
{% block content %}
<ul>
{% for user in users %}
<li><a href="{{ user.url|e }}">{{ user.username|e }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Philosophy
----------
Application logic is for the controller but don't try to make the life
for the template designer too hard by giving him too few functionality.
For more informations visit the new `jinja webpage`_ and `documentation`_.
Note
----
This is the Jinja 1.0 release which is completely incompatible with the
old "pre 1.0" branch. The old branch will still receive security updates
and bugfixes but the 1.0 branch will be the only version that receives
support.
If you have an application that uses Jinja 0.9 and won't be updated in
the near future the best idea is to ship a Jinja 0.9 checkout together
with the application.
The `Jinja tip`_ is installable via `easy_install` with ``easy_install
Jinja==dev``.
.. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
.. _Django: http://www.djangoproject.com/
.. _jinja webpage: http://jinja.pocoo.org/
.. _documentation: http://jinja.pocoo.org/documentation/index.html
.. _Jinja tip: http://dev.pocoo.org/hg/jinja-main/archive/tip.tar.gz#egg=Jinja-dev
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from jinja.environment import Environment
from jinja.datastructure import Markup
from jinja.plugin import jinja_plugin_factory as template_plugin_factory
from jinja.loaders import FileSystemLoader, PackageLoader, DictLoader, \
ChoiceLoader, FunctionLoader, MemcachedFileSystemLoader
from jinja.utils import from_string
__all__ = ['Environment', 'Markup', 'FileSystemLoader', 'PackageLoader',
'DictLoader', 'ChoiceLoader', 'FunctionLoader',
'MemcachedFileSystemLoader', 'from_string']
__version__ = '1.2'

View file

@ -1,85 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja._native
~~~~~~~~~~~~~
This module implements the native base classes in case of not
having a jinja with the _speedups module compiled.
Note that if you change semantics here you have to edit the
_speedups.c file to in order to support those changes for jinja
setups with enabled speedup module.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from jinja.datastructure import Deferred
from jinja.utils import deque
class BaseContext(object):
def __init__(self, undefined_singleton, globals, initial):
self._undefined_singleton = undefined_singleton
self.current = current = {}
self._stack = deque([current, initial, globals])
self.globals = globals
self.initial = initial
self._push = self._stack.appendleft
self._pop = self._stack.popleft
def stack(self):
return list(self._stack)[::-1]
stack = property(stack)
def pop(self):
"""Pop the last layer from the stack and return it."""
rv = self._pop()
self.current = self._stack[0]
return rv
def push(self, data=None):
"""
Push one layer to the stack and return it. Layer must be
a dict or omitted.
"""
data = data or {}
self._push(data)
self.current = self._stack[0]
return data
def __getitem__(self, name):
"""
Resolve one item. Restrict the access to internal variables
such as ``'::cycle1'``. Resolve deferreds.
"""
if not name.startswith('::'):
for d in self._stack:
if name in d:
rv = d[name]
if rv.__class__ is Deferred:
rv = rv(self, name)
# never touch the globals!
if d is self.globals:
self.initial[name] = rv
else:
d[name] = rv
return rv
return self._undefined_singleton
def __setitem__(self, name, value):
"""Set a variable in the outermost layer."""
self.current[name] = value
def __delitem__(self, name):
"""Delete a variable in the outermost layer."""
if name in self.current:
del self.current[name]
def __contains__(self, name):
""" Check if the context contains a given variable."""
for layer in self._stack:
if name in layer:
return True
return False

View file

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.constants
~~~~~~~~~~~~~~~
Various constants.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
#: list of lorem ipsum words used by the lipsum() helper function
LOREM_IPSUM_WORDS = u'''\
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
auctor augue bibendum blandit class commodo condimentum congue consectetuer
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
penatibus per pharetra phasellus placerat platea porta porttitor posuere
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
viverra volutpat vulputate'''

View file

@ -1,708 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.datastructure
~~~~~~~~~~~~~~~~~~~
Module that helds several data types used in the template engine.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from jinja.exceptions import TemplateSyntaxError, TemplateRuntimeError
_missing = object()
def contextcallable(f):
"""
Mark a function context callable.
"""
f.jinja_context_callable = True
return f
def unsafe(f):
"""
Mark function as unsafe.
"""
f.jinja_unsafe_call = True
return f
def make_undefined(implementation):
"""
Creates an undefined singleton based on a given implementation.
It performs some tests that make sure the undefined type implements
everything it should.
"""
self = object.__new__(implementation)
self.__reduce__()
return self
class AbstractUndefinedType(object):
"""
Base class for any undefined type.
"""
__slots__ = ()
def __init__(self):
raise TypeError('cannot create %r instances' %
self.__class__.__name__)
def __setattr__(self, name, value):
raise AttributeError('%r object has no attribute %r' % (
self.__class__.__name__,
name
))
def __eq__(self, other):
return self is other
def __ne__(self, other):
return self is not other
def __copy__(self):
return self
__deepcopy__ = __copy__
def __repr__(self):
return 'Undefined'
def __reduce__(self):
raise TypeError('undefined objects have to provide a __reduce__')
class SilentUndefinedType(AbstractUndefinedType):
"""
An object that does not exist.
"""
__slots__ = ()
def __add__(self, other):
"""Any operator returns the operand."""
return other
__sub__ = __mul__ = __div__ = __rsub__ = __rmul__ = __div__ = __mod__ =\
__radd__ = __rmod__ = __add__
def __getitem__(self, arg):
"""Getting any item returns `Undefined`"""
return self
def __iter__(self):
"""Iterating over `Undefined` returns an empty iterator."""
if False:
yield None
def __getattr__(self, arg):
"""Getting any attribute returns `Undefined`"""
return self
def __nonzero__(self):
"""`Undefined` is considered boolean `False`"""
return False
def __len__(self):
"""`Undefined` is an empty sequence"""
return 0
def __str__(self):
"""The string representation is empty."""
return ''
def __unicode__(self):
"""The unicode representation is empty."""
return u''
def __int__(self):
"""Converting `Undefined` to an integer ends up in ``0``"""
return 0
def __float__(self):
"""Converting `Undefined` to an float ends up in ``0.0``"""
return 0.0
def __call__(self, *args, **kwargs):
"""Calling `Undefined` returns `Undefined`"""
return self
def __reduce__(self):
"""Helper for pickle."""
return 'SilentUndefined'
class ComplainingUndefinedType(AbstractUndefinedType):
"""
An object that does not exist.
"""
__slots__ = ()
def __len__(self):
"""Getting the length raises error."""
raise TemplateRuntimeError('Operated on undefined object')
def __iter__(self):
"""Iterating over `Undefined` raises an error."""
raise TemplateRuntimeError('Iterated over undefined object')
def __nonzero__(self):
"""`Undefined` is considered boolean `False`"""
return False
def __str__(self):
"""The string representation raises an error."""
raise TemplateRuntimeError('Undefined object rendered')
def __unicode__(self):
"""The unicode representation raises an error."""
self.__str__()
def __call__(self, *args, **kwargs):
"""Calling `Undefined` returns `Undefined`"""
raise TemplateRuntimeError('Undefined object called')
def __reduce__(self):
"""Helper for pickle."""
return 'ComplainingUndefined'
#: the singleton instances for the undefined objects
SilentUndefined = make_undefined(SilentUndefinedType)
ComplainingUndefined = make_undefined(ComplainingUndefinedType)
#: jinja 1.0 compatibility
Undefined = SilentUndefined
UndefinedType = SilentUndefinedType
class FakeTranslator(object):
"""
Default null translator.
"""
def gettext(self, s):
"""
Translate a singular string.
"""
return s
def ngettext(self, s, p, n):
"""
Translate a plural string.
"""
if n == 1:
return s
return p
class Deferred(object):
"""
Object marking an deferred value. Deferred objects are
objects that are called first access in the context.
"""
def __init__(self, factory):
self.factory = factory
def __call__(self, context, name):
return self.factory(context.environment, context, name)
class Markup(unicode):
"""
Compatibility for Pylons and probably some other frameworks.
It's only used in Jinja environments with `auto_escape` set
to true.
"""
def __html__(self):
return unicode(self)
class TemplateData(Markup):
"""
Subclass of unicode to mark objects that are coming from the
template. The autoescape filter can use that.
"""
# import these here because those modules import Deferred and Undefined
# from this module.
try:
# try to use the c implementation of the base context if available
from jinja._speedups import BaseContext
except ImportError:
# if there is no c implementation we go with a native python one
from jinja._native import BaseContext
class Context(BaseContext):
"""
Dict like object containing the variables for the template.
"""
def __init__(self, *args, **kwargs):
environment = args[0]
if not kwargs and len(args) == 2 and isinstance(args[1], dict):
base = args[1]
else:
base = dict(*args[1:], **kwargs)
super(Context, self).__init__(environment.undefined_singleton,
environment.globals, base)
self._translate_func = None
self.cache = {}
self.environment = environment
def to_dict(self):
"""
Convert the context into a dict. This skips the globals.
"""
result = {}
for layer in self.stack[1:]:
for key, value in layer.iteritems():
if key.startswith('::'):
continue
result[key] = value
return result
def set_nonlocal(self, name, value):
"""
Set a value in an outer scope.
"""
for layer in self.stack[:0:-1]:
if name in layer:
layer[name] = value
return
self.initial[name] = value
def translate_func(self):
"""
The translation function for this context. It takes
4 parameters. The singular string, the optional plural one,
The name of the variable in the replacements dict and the
replacements dict. This is only used by the i18n system
internally the simplified version (just one argument) is
available in the template for the user too.
"""
if self._translate_func is not None:
return self._translate_func
translator = self.environment.get_translator(self)
gettext = translator.gettext
ngettext = translator.ngettext
def translate(s, p=None, n=None, r=None):
if p is None:
s = gettext(s)
else:
s = ngettext(s, p, r[n])
# apply replacement substitution only if replacements
# are given. This is the case for {% trans %}...{% endtrans %}
# but for the "_()" syntax and a trans tag without a body.
if r is not None:
return s % r
return s
translate.__doc__ = Context.translate_func.__doc__
self._translate_func = translate
return translate
translate_func = property(translate_func, doc=translate_func.__doc__)
def __repr__(self):
"""
String representation of the context.
"""
return 'Context(%r)' % self.to_dict()
def __pretty__(self, p, cycle):
if cycle:
return p.text('Context({...})')
p.begin_group(9, 'Context({')
for idx, (key, value) in enumerate(self.to_dict().iteritems()):
if idx:
p.text(',')
p.breakable()
p.pretty(key)
p.text(': ')
p.pretty(value)
p.end_group(9, '})')
class LoopContext(object):
"""
Simple class that provides special loop variables.
Used by `Environment.iterate`.
"""
jinja_allowed_attributes = ['index', 'index0', 'length', 'parent',
'even', 'odd', 'revindex0', 'revindex',
'first', 'last']
def __init__(self, seq, parent, loop_function):
self.loop_function = loop_function
self.parent = parent
self._stack = []
if loop_function is None:
self.push(seq)
def push(self, seq):
"""
Push a sequence to the loop stack. This is used by the
recursive for loop.
"""
# iteration over None is catched, but we don't catch iteration
# over undefined because that behavior is handled in the
# undefined singleton
if seq is None:
seq = ()
length = 0
else:
try:
length = len(seq)
except (AttributeError, TypeError):
seq = list(seq)
length = len(seq)
self._stack.append({
'index': -1,
'seq': seq,
'length': length
})
return self
def pop(self):
"""Remove the last layer from the loop stack."""
return self._stack.pop()
iterated = property(lambda s: s._stack[-1]['index'] > -1)
index0 = property(lambda s: s._stack[-1]['index'])
index = property(lambda s: s._stack[-1]['index'] + 1)
revindex0 = property(lambda s: s._stack[-1]['length'] -
s._stack[-1]['index'] - 1)
revindex = property(lambda s: s._stack[-1]['length'] -
s._stack[-1]['index'])
length = property(lambda s: s._stack[-1]['length'])
even = property(lambda s: s._stack[-1]['index'] % 2 == 1)
odd = property(lambda s: s._stack[-1]['index'] % 2 == 0)
first = property(lambda s: s._stack[-1]['index'] == 0)
last = property(lambda s: s._stack[-1]['index'] ==
s._stack[-1]['length'] - 1)
def __iter__(self):
s = self._stack[-1]
for idx, item in enumerate(s['seq']):
s['index'] = idx
yield item
def __len__(self):
return self._stack[-1]['length']
def __call__(self, seq):
if self.loop_function is not None:
return self.loop_function(seq)
raise TemplateRuntimeError('In order to make loops callable you have '
'to define them with the "recursive" '
'modifier.')
def __repr__(self):
if self._stack:
return '<LoopContext %d/%d%s>' % (
self.index,
self.length,
self.loop_function is not None and ' recursive' or ''
)
return '<LoopContext (empty)>'
class CycleContext(object):
"""
Helper class used for cycling.
"""
def __init__(self, seq=None):
self.pos = -1
# bind the correct helper function based on the constructor signature
if seq is not None:
self.seq = seq
self.length = len(seq)
self.cycle = self.cycle_static
else:
self.cycle = self.cycle_dynamic
def cycle_static(self):
"""Helper function for static cycling."""
self.pos = (self.pos + 1) % self.length
return self.seq[self.pos]
def cycle_dynamic(self, seq):
"""Helper function for dynamic cycling."""
self.pos = pos = (self.pos + 1) % len(seq)
return seq[pos]
class SuperBlock(object):
"""
Helper class for ``{{ super() }}``.
"""
jinja_allowed_attributes = ['name']
def __init__(self, name, blocks, level, context):
self.name = name
self.context = context
if name in blocks:
self.stack = blocks[name]
self.level = level
else:
self.stack = None
def __call__(self, offset=1):
if self.stack is not None:
level = self.level + (offset - 1)
if level < len(self.stack):
return self.stack[level](self.context)
raise TemplateRuntimeError('no super block for %r' % self.name)
def __repr__(self):
return '<SuperBlock %r>' % self.name
class StateTest(object):
"""
Wrapper class for basic lambdas in order to simplify
debugging in the parser. It also provides static helper
functions that replace some lambda expressions
"""
def __init__(self, func, msg):
self.func = func
self.msg = msg
def __call__(self, token):
return self.func(token)
def expect_token(*types, **kw):
"""Scans until one of the given tokens is found."""
msg = kw.pop('msg', None)
if kw:
raise TypeError('unexpected keyword argument %r' % iter(kw).next())
if len(types) == 1:
if msg is None:
msg = "expected '%s'" % types[0]
return StateTest(lambda t: t.type == types[0], msg)
if msg is None:
msg = 'expected one of %s' % ', '.join(["'%s'" % type
for type in types])
return StateTest(lambda t: t.type in types, msg)
expect_token = staticmethod(expect_token)
class Token(object):
"""
Token class.
"""
__slots__ = ('lineno', 'type', 'value')
def __init__(self, lineno, type, value):
self.lineno = lineno
self.type = intern(str(type))
self.value = value
def __str__(self):
from jinja.lexer import keywords, reverse_operators
if self.type in keywords:
return self.type
elif self.type in reverse_operators:
return reverse_operators[self.type]
return self.value
def __repr__(self):
return 'Token(%r, %r, %r)' % (
self.lineno,
self.type,
self.value
)
class TokenStreamIterator(object):
"""
The iterator for tokenstreams. Iterate over the stream
until the eof token is reached.
"""
def __init__(self, stream):
self._stream = stream
def __iter__(self):
return self
def next(self):
token = self._stream.current
if token.type == 'eof':
self._stream.close()
raise StopIteration()
self._stream.next()
return token
class TokenStream(object):
"""
A token stream wraps a generator and supports pushing tokens back.
It also provides some functions to expect tokens and similar stuff.
Important note: Do never push more than one token back to the
stream. Although the stream object won't stop you
from doing so, the behavior is undefined. Multiple
pushed tokens are only used internally!
"""
def __init__(self, generator, filename):
self._next = generator.next
self._pushed = []
self.current = Token(1, 'initial', '')
self.filename = filename
self.next()
def __iter__(self):
return TokenStreamIterator(self)
def bound(self):
"""Return True if the token stream is bound to a parser."""
return self.parser is not None
bound = property(bound, doc=bound.__doc__)
def lineno(self):
"""The current line number."""
return self.current.lineno
lineno = property(lineno, doc=lineno.__doc__)
def __nonzero__(self):
"""Are we at the end of the tokenstream?"""
return bool(self._pushed) or self.current.type != 'eof'
eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
def look(self):
"""See what's the next token."""
if self._pushed:
return self._pushed[-1]
old_token = self.current
self.next()
new_token = self.current
self.current = old_token
self.push(new_token)
return new_token
def push(self, token):
"""Push a token back to the stream."""
self._pushed.append(token)
def skip(self, n):
"""Got n tokens ahead."""
for x in xrange(n):
self.next()
def shift(self, token):
"""
Push one token into the stream.
"""
old_current = self.current
self.next()
self.push(self.current)
self.push(old_current)
self.push(token)
self.next()
def next(self):
"""Go one token ahead."""
if self._pushed:
self.current = self._pushed.pop()
elif self.current.type != 'eof':
try:
self.current = self._next()
except StopIteration:
self.close()
def read_whitespace(self):
"""Read all the whitespace, up to the next tag."""
lineno = self.current.lineno
buf = []
while self.current.type == 'data' and not \
self.current.value.strip():
buf.append(self.current.value)
self.next()
if buf:
return Token(lineno, 'data', u''.join(buf))
def close(self):
"""Close the stream."""
self.current = Token(self.current.lineno, 'eof', '')
self._next = None
def expect(self, token_type, token_value=_missing):
"""Expect a given token type and return it"""
if self.current.type != token_type:
raise TemplateSyntaxError("expected token %r, got %r" %
(token_type, self.current.type),
self.current.lineno,
self.filename)
elif token_value is not _missing and \
self.current.value != token_value:
raise TemplateSyntaxError("expected %r, got %r" %
(token_value, self.current.value),
self.current.lineno,
self.filename)
try:
return self.current
finally:
self.next()
class TemplateStream(object):
"""
Wraps a genererator for outputing template streams.
"""
def __init__(self, gen):
self._gen = gen
self._next = gen.next
self.buffered = False
def disable_buffering(self):
"""
Disable the output buffering.
"""
self._next = self._gen.next
self.buffered = False
def enable_buffering(self, size=5):
"""
Enable buffering. Buffer `size` items before
yielding them.
"""
if size <= 1:
raise ValueError('buffer size too small')
self.buffered = True
def buffering_next():
buf = []
c_size = 0
push = buf.append
next = self._gen.next
try:
while True:
item = next()
if item:
push(item)
c_size += 1
if c_size >= size:
raise StopIteration()
except StopIteration:
if not c_size:
raise
return u''.join(buf)
self._next = buffering_next
def __iter__(self):
return self
def next(self):
return self._next()

View file

@ -1,213 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.debugger
~~~~~~~~~~~~~~
This module implements helper function Jinja uses to give the users a
possibility to develop Jinja templates like they would debug python code.
It seamlessly integreates into the python traceback system, in fact it
just modifies the trackback stack so that the line numbers are correct
and the frame information are bound to the context and not the frame of
the template evaluation loop.
To achive this it raises the exception it cought before in an isolated
namespace at a given line. The locals namespace is set to the current
template context.
The traceback generated by raising that exception is then either returned
or linked with the former traceback if the `jinja._debugger` module is
available. Because it's not possible to modify traceback objects from the
python space this module is needed for this process.
If it's not available it just ignores the other frames. Because this can
lead to actually harder to debug code there is a setting on the jinja
environment to disable the debugging system.
The isolated namespace which is used to raise the exception also contains
a `__loader__` name that helds a reference to a PEP 302 compatible loader.
Because there are currently some traceback systems (such as the paste
evalexception debugger) that do not provide the frame globals when
retrieving the source from the linecache module, Jinja injects the source
to the linecache module itself and changes the filename to a URL style
"virtual filename" so that Jinja doesn't acidentally override other files
in the linecache.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import sys
from random import randrange
# if we have extended debugger support we should really use it
try:
from jinja._debugger import *
has_extended_debugger = True
except ImportError:
has_extended_debugger = False
# we need the RUNTIME_EXCEPTION_OFFSET to skip the not used frames
from jinja.utils import reversed, RUNTIME_EXCEPTION_OFFSET
def fake_template_exception(exc_type, exc_value, tb, filename, lineno,
source, context_or_env, tb_back=None):
"""
Raise an exception "in a template". Return a traceback
object. This is used for runtime debugging, not compile time.
"""
# some traceback systems allow to skip frames
__traceback_hide__ = True
# create the namespace which will be the local namespace
# of the new frame then. Some debuggers show local variables
# so we better inject the context and not the evaluation loop context.
from jinja.datastructure import Context
if isinstance(context_or_env, Context):
env = context_or_env.environment
namespace = context_or_env.to_dict()
else:
env = context_or_env
namespace = {}
# no unicode for filenames
if isinstance(filename, unicode):
filename = filename.encode('utf-8')
# generate an jinja unique filename used so that linecache
# gets data that doesn't interfere with other modules
if filename is None:
vfilename = 'jinja://~%d' % randrange(0, 10000)
filename = '<string>'
else:
vfilename = 'jinja://%s' % filename
# now create the used loaded and update the linecache
loader = TracebackLoader(env, source, filename)
loader.update_linecache(vfilename)
globals = {
'__name__': vfilename,
'__file__': vfilename,
'__loader__': loader
}
# use the simple debugger to reraise the exception in the
# line where the error originally occoured
globals['__exception_to_raise__'] = (exc_type, exc_value)
offset = '\n' * (lineno - 1)
code = compile(offset + 'raise __exception_to_raise__[0], '
'__exception_to_raise__[1]',
vfilename or '<template>', 'exec')
try:
exec code in globals, namespace
except:
exc_info = sys.exc_info()
# if we have an extended debugger we set the tb_next flag so that
# we don't loose the higher stack items.
if has_extended_debugger:
if tb_back is not None:
tb_set_next(tb_back, exc_info[2])
if tb is not None:
tb_set_next(exc_info[2].tb_next, tb.tb_next)
# otherwise just return the exc_info from the simple debugger
return exc_info
def translate_exception(template, context, exc_type, exc_value, tb):
"""
Translate an exception and return the new traceback.
"""
# depending on the python version we have to skip some frames to
# step to get the frame of the current template. The frames before
# are the toolchain used to render that thing.
for x in xrange(RUNTIME_EXCEPTION_OFFSET):
tb = tb.tb_next
result_tb = prev_tb = None
initial_tb = tb
# translate all the jinja frames in this traceback
while tb is not None:
if tb.tb_frame.f_globals.get('__jinja_template__'):
debug_info = tb.tb_frame.f_globals['debug_info']
# the next thing we do is matching the current error line against the
# debugging table to get the correct source line. If we can't find the
# filename and line number we return the traceback object unaltered.
error_line = tb.tb_lineno
for code_line, tmpl_filename, tmpl_line in reversed(debug_info):
if code_line <= error_line:
source = tb.tb_frame.f_globals['template_source']
tb = fake_template_exception(exc_type, exc_value, tb,
tmpl_filename, tmpl_line,
source, context, prev_tb)[-1]
break
if result_tb is None:
result_tb = tb
prev_tb = tb
tb = tb.tb_next
# under some conditions we cannot translate any frame. in that
# situation just return the original traceback.
return (exc_type, exc_value, result_tb or intial_tb)
def raise_syntax_error(exception, env, source=None):
"""
This method raises an exception that includes more debugging
informations so that debugging works better. Unlike
`translate_exception` this method raises the exception with
the traceback.
"""
exc_info = fake_template_exception(exception, None, None,
exception.filename,
exception.lineno, source, env)
raise exc_info[0], exc_info[1], exc_info[2]
class TracebackLoader(object):
"""
Fake importer that just returns the source of a template. It's just used
by Jinja interally and you shouldn't use it on your own.
"""
def __init__(self, environment, source, filename):
self.loader = environment.loader
self.source = source
self.filename = filename
def update_linecache(self, virtual_filename):
"""
Hacky way to let traceback systems know about the
Jinja template sourcecode. Very hackish indeed.
"""
# check for linecache, not every implementation of python
# might have such an module (this check is pretty senseless
# because we depend on cpython anway)
try:
from linecache import cache
except ImportError:
return
data = self.get_source(None)
cache[virtual_filename] = (
len(data),
None,
data.splitlines(True),
virtual_filename
)
def get_source(self, impname):
"""Return the source as bytestring."""
source = ''
if self.source is not None:
source = self.source
elif self.loader is not None:
try:
source = self.loader.get_source(self.filename)
except TemplateNotFound:
pass
if isinstance(source, unicode):
source = source.encode('utf-8')
return source

View file

@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.defaults
~~~~~~~~~~~~~~
Jinja default filters and tags.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from jinja.filters import FILTERS as DEFAULT_FILTERS
from jinja.tests import TESTS as DEFAULT_TESTS
from jinja.utils import NAMESPACE as DEFAULT_NAMESPACE
__all__ = ['DEFAULT_FILTERS', 'DEFAULT_TESTS', 'DEFAULT_NAMESPACE']

View file

@ -1,395 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.environment
~~~~~~~~~~~~~~~~~
Provides a class that holds runtime and parsing time options.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from jinja.lexer import Lexer
from jinja.parser import Parser
from jinja.loaders import LoaderWrapper
from jinja.datastructure import SilentUndefined, Markup, Context, FakeTranslator
from jinja.utils import collect_translations, get_attribute
from jinja.exceptions import FilterNotFound, TestNotFound, \
SecurityException, TemplateSyntaxError
from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
__all__ = ['Environment']
#: minor speedup
_getattr = getattr
class Environment(object):
"""
The Jinja environment.
The core component of Jinja is the `Environment`. It contains
important shared variables like configuration, filters, tests,
globals and others.
"""
def __init__(self,
block_start_string='{%',
block_end_string='%}',
variable_start_string='{{',
variable_end_string='}}',
comment_start_string='{#',
comment_end_string='#}',
trim_blocks=False,
auto_escape=False,
default_filters=None,
template_charset='utf-8',
charset='utf-8',
namespace=None,
loader=None,
filters=None,
tests=None,
context_class=Context,
undefined_singleton=SilentUndefined,
disable_regexps=False,
friendly_traceback=True,
translator_factory=None):
"""
Here the possible initialization parameters:
========================= ============================================
`block_start_string` * the string marking the begin of a block.
this defaults to ``'{%'``.
`block_end_string` * the string marking the end of a block.
defaults to ``'%}'``.
`variable_start_string` * the string marking the begin of a print
statement. defaults to ``'{{'``.
`comment_start_string` * the string marking the begin of a
comment. defaults to ``'{#'``.
`comment_end_string` * the string marking the end of a comment.
defaults to ``'#}'``.
`trim_blocks` * If this is set to ``True`` the first newline
after a block is removed (block, not
variable tag!). Defaults to ``False``.
`auto_escape` If this is set to ``True`` Jinja will
automatically escape all variables using xml
escaping methods. If you don't want to
escape a string you have to wrap it in a
``Markup`` object from the
``jinja.datastructure`` module. If
`auto_escape` is ``True`` there will be also
a ``Markup`` object in the template
namespace to define partial html fragments.
Note that we do not recommend this feature.
`default_filters` list of tuples in the form (``filter_name``,
``arguments``) where ``filter_name`` is the
name of a registered filter and
``arguments`` a tuple with the filter
arguments. The filters specified here will
always be applied when printing data to the
template. *new in Jinja 1.1*
`template_charset` The charset of the templates. Defaults
to ``'utf-8'``.
`charset` Charset of all string input data. Defaults
to ``'utf-8'``.
`namespace` Global namespace for all templates.
`loader` Specify a template loader.
`filters` dict of filters or the default filters if
not defined.
`tests` dict of tests of the default tests if not
defined.
`context_class` the context class this template should use.
See the `Context` documentation for more
details.
`undefined_singleton` The singleton value that is used for missing
variables. *new in Jinja 1.1*
`disable_regexps` Disable support for regular expresssions.
`friendly_traceback` Set this to `False` to disable the developer
friendly traceback rewriting. Whenever an
runtime or syntax error occours jinja will
try to make a developer friendly traceback
that shows the error in the template line.
This however can be annoying when debugging
broken functions that are called from the
template. *new in Jinja 1.1*
`translator_factory` A callback function that is called with
the context as first argument to get the
translator for the current instance.
*new in Jinja 1.2*
========================= ============================================
All of these variables except those marked with a star (*) are
modifiable after environment initialization.
"""
# lexer / parser information
self.block_start_string = block_start_string
self.block_end_string = block_end_string
self.variable_start_string = variable_start_string
self.variable_end_string = variable_end_string
self.comment_start_string = comment_start_string
self.comment_end_string = comment_end_string
self.trim_blocks = trim_blocks
# other stuff
self.template_charset = template_charset
self.charset = charset
self.loader = loader
if filters is None:
filters = DEFAULT_FILTERS.copy()
self.filters = filters
if tests is None:
tests = DEFAULT_TESTS.copy()
self.tests = tests
self.default_filters = default_filters or []
self.context_class = context_class
self.undefined_singleton = undefined_singleton
self.disable_regexps = disable_regexps
self.friendly_traceback = friendly_traceback
# global namespace
if namespace is None:
namespace = DEFAULT_NAMESPACE.copy()
self.globals = namespace
# jinja 1.0 compatibility
if auto_escape:
self.default_filters.append(('escape', (True,)))
self.globals['Markup'] = Markup
# and here the translator factory
self.translator_factory = translator_factory
# create lexer
self.lexer = Lexer(self)
def loader(self, value):
"""
Get or set the template loader.
"""
self._loader = LoaderWrapper(self, value)
loader = property(lambda s: s._loader, loader, doc=loader.__doc__)
def parse(self, source, filename=None):
"""
Parse the sourcecode and return the abstract syntax tree. This tree
of nodes is used by the `translators`_ to convert the template into
executable source- or bytecode.
.. _translators: translators.txt
"""
parser = Parser(self, source, filename)
return parser.parse()
def lex(self, source, filename=None):
"""
Lex the given sourcecode and return a generator that yields tokens.
The stream returned is not usable for Jinja but can be used if
Jinja templates should be processed by other tools (for example
syntax highlighting etc)
The tuples are returned in the form ``(lineno, token, value)``.
"""
return self.lexer.tokeniter(source, filename)
def from_string(self, source):
"""
Load and parse a template source and translate it into eval-able
Python code. This code is wrapped within a `Template` class that
allows you to render it.
"""
from jinja.translators.python import PythonTranslator
try:
rv = PythonTranslator.process(self, Parser(self, source).parse(),
source)
except TemplateSyntaxError, e:
# on syntax errors rewrite the traceback if wanted
if not self.friendly_traceback:
raise
from jinja.debugger import raise_syntax_error
if __debug__:
__traceback_hide__ = True
raise_syntax_error(e, self, source)
else:
return rv
def get_template(self, filename):
"""
Load a template from a loader. If the template does not exist, you
will get a `TemplateNotFound` exception.
"""
return self._loader.load(filename)
def to_unicode(self, value):
"""
Convert a value to unicode with the rules defined on the environment.
"""
# undefined and None expand to ""
if value in (None, self.undefined_singleton):
return u''
# things that are already unicode can pass. As long as nobody
# does ugly things with the class it works for jinja too
elif isinstance(value, unicode):
return value
# otherwise try to use __unicode__ or decode __str__
try:
return unicode(value)
except UnicodeError:
return str(value).decode(self.charset, 'ignore')
def get_translator(self, context):
"""
Return the translator for i18n.
A translator is an object that provides the two functions
``gettext(string)`` and ``ngettext(singular, plural, n)``. Note
that both of them have to return unicode!
"""
if self.translator_factory is not None:
return self.translator_factory(context)
return FakeTranslator()
def get_translations(self, name):
"""
Load template `name` and return all translatable strings (note that
that it really just returns the strings form this template, not from
the parent or any included templates!)
"""
return collect_translations(self.loader.parse(name))
def get_translations_for_string(self, string):
"""
Like `get_translations`, but the translations are loaded from a
normal string that represents the template.
"""
return collect_translations(self.parse(string))
def apply_filters(self, value, context, filters):
"""
Apply a list of filters on the variable.
"""
# some traceback systems allow to skip frames. but allow
# disabling that via -O to not make things slow
if __debug__:
__traceback_hide__ = True
cache = context.cache
for key in filters:
if key in cache:
func = cache[key]
else:
filtername, args = key
if filtername not in self.filters:
raise FilterNotFound(filtername)
cache[key] = func = self.filters[filtername](*args)
value = func(self, context, value)
return value
def perform_test(self, context, testname, args, value):
"""
Perform a test on a variable.
"""
# some traceback systems allow to skip frames. but allow
# disabling that via -O to not make things slow
if __debug__:
__traceback_hide__ = True
key = (testname, args)
if key in context.cache:
func = context.cache[key]
else:
if testname not in self.tests:
raise TestNotFound(testname)
context.cache[key] = func = self.tests[testname](*args)
return not not func(self, context, value)
def get_attribute(self, obj, name):
"""
Get one attribute from an object.
"""
# some traceback systems allow to skip frames. but allow
# disabling that via -O to not make things slow
if __debug__:
__traceback_hide__ = True
try:
return obj[name]
except (TypeError, KeyError, IndexError, AttributeError):
try:
return get_attribute(obj, name)
except (AttributeError, SecurityException):
pass
if obj is self.undefined_singleton:
return _getattr(obj, name)
return self.undefined_singleton
def get_attributes(self, obj, attributes):
"""
Get some attributes from an object. If attributes is an
empty sequence the object is returned as it.
"""
get = self.get_attribute
for name in attributes:
obj = get(obj, name)
return obj
def call_function(self, f, context, args, kwargs, dyn_args, dyn_kwargs):
"""
Function call helper. Called for all functions that are passed
any arguments.
"""
# some traceback systems allow to skip frames. but allow
# disabling that via -O to not make things slow
if __debug__:
__traceback_hide__ = True
if dyn_args is not None:
args += tuple(dyn_args)
if dyn_kwargs is not None:
kwargs.update(dyn_kwargs)
if _getattr(f, 'jinja_unsafe_call', False) or \
_getattr(f, 'alters_data', False):
return self.undefined_singleton
if _getattr(f, 'jinja_context_callable', False):
args = (self, context) + args
return f(*args, **kwargs)
def call_function_simple(self, f, context):
"""
Function call without arguments. Because of the smaller signature and
fewer logic here we have a bit of redundant code.
"""
# some traceback systems allow to skip frames. but allow
# disabling that via -O to not make things slow
if __debug__:
__traceback_hide__ = True
if _getattr(f, 'jinja_unsafe_call', False) or \
_getattr(f, 'alters_data', False):
return self.undefined_singleton
if _getattr(f, 'jinja_context_callable', False):
return f(self, context)
return f()
def finish_var(self, value, ctx):
"""
As long as no write_var function is passed to the template
evaluator the source generated by the python translator will
call this function for all variables.
"""
# some traceback systems allow to skip frames. but allow
# disabling that via -O to not make things slow
if __debug__:
__traceback_hide__ = True
if value is None:
return u''
elif value is self.undefined_singleton:
return unicode(value)
elif _getattr(value, 'jinja_no_finalization', False):
return value
val = self.to_unicode(value)
if self.default_filters:
val = self.apply_filters(val, ctx, self.default_filters)
return val

View file

@ -1,91 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.exceptions
~~~~~~~~~~~~~~~~
Jinja exceptions.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
class TemplateError(RuntimeError):
pass
class SecurityException(TemplateError):
"""
Raise if the template designer tried to do something dangerous.
"""
class FilterNotFound(KeyError, TemplateError):
"""
Raised if a filter does not exist.
"""
def __init__(self, message):
KeyError.__init__(self, message)
class FilterArgumentError(TypeError, TemplateError):
"""
An argument passed to the filter was invalid.
"""
def __init__(self, message):
TypeError.__init__(self, message)
class TestNotFound(KeyError, TemplateError):
"""
Raised if a test does not exist.
"""
def __init__(self, message):
KeyError.__init__(self, message)
class TestArgumentError(TypeError, TemplateError):
"""
An argument passed to a test function was invalid.
"""
def __init__(self, message):
TypeError.__init__(self, message)
class TemplateNotFound(IOError, TemplateError):
"""
Raised if a template does not exist.
"""
def __init__(self, name):
IOError.__init__(self, name)
self.name = name
class TemplateSyntaxError(SyntaxError, TemplateError):
"""
Raised to tell the user that there is a problem with the template.
"""
def __init__(self, message, lineno, filename):
SyntaxError.__init__(self, message)
self.lineno = lineno
self.filename = filename
class TemplateRuntimeError(TemplateError):
"""
Raised by the template engine if a tag encountered an error when
rendering.
"""
class TemplateIncludeError(TemplateError):
"""
Raised by the `ControlledLoader` if recursive includes where
detected.
"""

View file

@ -1,986 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.filters
~~~~~~~~~~~~~
Bundled jinja filters.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import re
from random import choice
from operator import itemgetter
from urllib import urlencode, quote
from jinja.utils import urlize, escape, reversed, sorted, groupby, \
get_attribute, pformat
from jinja.datastructure import TemplateData
from jinja.exceptions import FilterArgumentError, SecurityException
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
def stringfilter(f):
"""
Decorator for filters that just work on unicode objects.
"""
def decorator(*args):
def wrapped(env, context, value):
nargs = list(args)
for idx, var in enumerate(nargs):
if isinstance(var, str):
nargs[idx] = env.to_unicode(var)
return f(env.to_unicode(value), *nargs)
return wrapped
try:
decorator.__doc__ = f.__doc__
decorator.__name__ = f.__name__
except:
pass
return decorator
def simplefilter(f):
"""
Decorator for simplifying filters. Filter arguments are passed
to the decorated function without environment and context. The
source value is the first argument. (like stringfilter but
without unicode conversion)
"""
def decorator(*args):
def wrapped(env, context, value):
return f(value, *args)
return wrapped
try:
decorator.__doc__ = f.__doc__
decorator.__name__ = f.__name__
except:
pass
return decorator
def do_replace(s, old, new, count=None):
"""
Return a copy of the value with all occurrences of a substring
replaced with a new one. The first argument is the substring
that should be replaced, the second is the replacement string.
If the optional third argument ``count`` is given, only the first
``count`` occurrences are replaced:
.. sourcecode:: jinja
{{ "Hello World"|replace("Hello", "Goodbye") }}
-> Goodbye World
{{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
-> d'oh, d'oh, aaargh
"""
if not isinstance(old, basestring) or \
not isinstance(new, basestring):
raise FilterArgumentError('the replace filter requires '
'string replacement arguments')
if count is None:
return s.replace(old, new)
if not isinstance(count, (int, long)):
raise FilterArgumentError('the count parameter of the '
'replace filter requires '
'an integer')
return s.replace(old, new, count)
do_replace = stringfilter(do_replace)
def do_upper(s):
"""
Convert a value to uppercase.
"""
return s.upper()
do_upper = stringfilter(do_upper)
def do_lower(s):
"""
Convert a value to lowercase.
"""
return s.lower()
do_lower = stringfilter(do_lower)
def do_escape(attribute=False):
"""
XML escape ``&``, ``<``, and ``>`` in a string of data. If the
optional parameter is `true` this filter will also convert
``"`` to ``&quot;``. This filter is just used if the environment
was configured with disabled `auto_escape`.
This method will have no effect it the value is already escaped.
"""
#: because filters are cached we can make a local alias to
#: speed things up a bit
e = escape
def wrapped(env, context, s):
if isinstance(s, TemplateData):
return s
elif hasattr(s, '__html__'):
return s.__html__()
#: small speedup, do not convert to unicode if we already
#: have an unicode object.
if s.__class__ is not unicode:
s = env.to_unicode(s)
return e(s, attribute)
return wrapped
def do_xmlattr(autospace=False):
"""
Create an SGML/XML attribute string based on the items in a dict.
All values that are neither `none` nor `undefined` are automatically
escaped:
.. sourcecode:: html+jinja
<ul{{ {'class': 'my_list', 'missing': None,
'id': 'list-%d'|format(variable)}|xmlattr }}>
...
</ul>
Results in something like this:
.. sourcecode:: html
<ul class="my_list" id="list-42">
...
</ul>
As you can see it automatically prepends a space in front of the item
if the filter returned something. You can disable this by passing
`false` as only argument to the filter.
*New in Jinja 1.1*
"""
e = escape
def wrapped(env, context, d):
if not hasattr(d, 'iteritems'):
raise TypeError('a dict is required')
result = []
for key, value in d.iteritems():
if value not in (None, env.undefined_singleton):
result.append(u'%s="%s"' % (
e(env.to_unicode(key)),
e(env.to_unicode(value), True)
))
rv = u' '.join(result)
if autospace:
rv = ' ' + rv
return rv
return wrapped
def do_capitalize(s):
"""
Capitalize a value. The first character will be uppercase, all others
lowercase.
"""
return s.capitalize()
do_capitalize = stringfilter(do_capitalize)
def do_title(s):
"""
Return a titlecased version of the value. I.e. words will start with
uppercase letters, all remaining characters are lowercase.
"""
return s.title()
do_title = stringfilter(do_title)
def do_dictsort(case_sensitive=False, by='key'):
"""
Sort a dict and yield (key, value) pairs. Because python dicts are
unsorted you may want to use this function to order them by either
key or value:
.. sourcecode:: jinja
{% for item in mydict|dictsort %}
sort the dict by key, case insensitive
{% for item in mydict|dicsort(true) %}
sort the dict by key, case sensitive
{% for item in mydict|dictsort(false, 'value') %}
sort the dict by key, case insensitive, sorted
normally and ordered by value.
"""
if by == 'key':
pos = 0
elif by == 'value':
pos = 1
else:
raise FilterArgumentError('You can only sort by either '
'"key" or "value"')
def sort_func(value, env):
if isinstance(value, basestring):
value = env.to_unicode(value)
if not case_sensitive:
value = value.lower()
return value
def wrapped(env, context, value):
items = value.items()
items.sort(lambda a, b: cmp(sort_func(a[pos], env),
sort_func(b[pos], env)))
return items
return wrapped
def do_default(default_value=u'', boolean=False):
"""
If the value is undefined it will return the passed default value,
otherwise the value of the variable:
.. sourcecode:: jinja
{{ my_variable|default('my_variable is not defined') }}
This will output the value of ``my_variable`` if the variable was
defined, otherwise ``'my_variable is not defined'``. If you want
to use default with variables that evaluate to false you have to
set the second parameter to `true`:
.. sourcecode:: jinja
{{ ''|default('the string was empty', true) }}
"""
def wrapped(env, context, value):
if (boolean and not value) or value in (env.undefined_singleton, None):
return default_value
return value
return wrapped
def do_join(d=u''):
"""
Return a string which is the concatenation of the strings in the
sequence. The separator between elements is an empty string per
default, you can define ith with the optional parameter:
.. sourcecode:: jinja
{{ [1, 2, 3]|join('|') }}
-> 1|2|3
{{ [1, 2, 3]|join }}
-> 123
"""
def wrapped(env, context, value):
return env.to_unicode(d).join([env.to_unicode(x) for x in value])
return wrapped
def do_count():
"""
Return the length of the value. In case if getting an integer or float
it will convert it into a string an return the length of the new
string. If the object has no length it will of corse return 0.
"""
def wrapped(env, context, value):
try:
if type(value) in (int, float, long):
return len(str(value))
return len(value)
except TypeError:
return 0
return wrapped
def do_reverse():
"""
Return a reversed list of the sequence filtered. You can use this
for example for reverse iteration:
.. sourcecode:: jinja
{% for item in seq|reverse %}
{{ item|e }}
{% endfor %}
"""
def wrapped(env, context, value):
try:
return value[::-1]
except:
l = list(value)
l.reverse()
return l
return wrapped
def do_center(value, width=80):
"""
Centers the value in a field of a given width.
"""
return value.center(width)
do_center = stringfilter(do_center)
def do_first():
"""
Return the frist item of a sequence.
"""
def wrapped(env, context, seq):
try:
return iter(seq).next()
except StopIteration:
return env.undefined_singleton
return wrapped
def do_last():
"""
Return the last item of a sequence.
"""
def wrapped(env, context, seq):
try:
return iter(reversed(seq)).next()
except StopIteration:
return env.undefined_singleton
return wrapped
def do_random():
"""
Return a random item from the sequence.
"""
def wrapped(env, context, seq):
try:
return choice(seq)
except IndexError:
return env.undefined_singleton
return wrapped
def do_urlencode():
"""
urlencode a string or directory.
.. sourcecode:: jinja
{{ {'foo': 'bar', 'blub': 'blah'}|urlencode }}
-> foo=bar&blub=blah
{{ 'Hello World' }}
-> Hello%20World
"""
def wrapped(env, context, value):
if isinstance(value, dict):
tmp = {}
for key, value in value.iteritems():
key = env.to_unicode(key).encode(env.charset)
value = env.to_unicode(value).encode(env.charset)
tmp[key] = value
return urlencode(tmp)
else:
return quote(env.to_unicode(value).encode(env.charset))
return wrapped
def do_jsonencode():
"""
JSON dump a variable. just works if simplejson is installed.
.. sourcecode:: jinja
{{ 'Hello World'|jsonencode }}
-> "Hello World"
"""
global simplejson
try:
simplejson
except NameError:
import simplejson
return lambda e, c, v: simplejson.dumps(v)
def do_filesizeformat():
"""
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
bytes, etc).
"""
def wrapped(env, context, value):
# fail silently
try:
bytes = float(value)
except TypeError:
bytes = 0
if bytes < 1024:
return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
elif bytes < 1024 * 1024:
return "%.1f KB" % (bytes / 1024)
elif bytes < 1024 * 1024 * 1024:
return "%.1f MB" % (bytes / (1024 * 1024))
return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
return wrapped
def do_pprint(verbose=False):
"""
Pretty print a variable. Useful for debugging.
With Jinja 1.2 onwards you can pass it a parameter. If this parameter
is truthy the output will be more verbose (this requires `pretty`)
"""
def wrapped(env, context, value):
return pformat(value, verbose=verbose)
return wrapped
def do_urlize(value, trim_url_limit=None, nofollow=False):
"""
Converts URLs in plain text into clickable links.
If you pass the filter an additional integer it will shorten the urls
to that number. Also a third argument exists that makes the urls
"nofollow":
.. sourcecode:: jinja
{{ mytext|urlize(40, True) }}
links are shortened to 40 chars and defined with rel="nofollow"
"""
return urlize(value, trim_url_limit, nofollow)
do_urlize = stringfilter(do_urlize)
def do_indent(s, width=4, indentfirst=False):
"""
{{ s|indent[ width[ indentfirst[ usetab]]] }}
Return a copy of the passed string, each line indented by
4 spaces. The first line is not indented. If you want to
change the number of spaces or indent the first line too
you can pass additional parameters to the filter:
.. sourcecode:: jinja
{{ mytext|indent(2, True) }}
indent by two spaces and indent the first line too.
"""
indention = ' ' * width
if indentfirst:
return u'\n'.join([indention + line for line in s.splitlines()])
return s.replace('\n', '\n' + indention)
do_indent = stringfilter(do_indent)
def do_truncate(s, length=255, killwords=False, end='...'):
"""
Return a truncated copy of the string. The length is specified
with the first parameter which defaults to ``255``. If the second
parameter is ``true`` the filter will cut the text at length. Otherwise
it will try to save the last word. If the text was in fact
truncated it will append an ellipsis sign (``"..."``). If you want a
different ellipsis sign than ``"..."`` you can specify it using the
third parameter.
.. sourcecode jinja::
{{ mytext|truncate(300, false, '&raquo;') }}
truncate mytext to 300 chars, don't split up words, use a
right pointing double arrow as ellipsis sign.
"""
if len(s) <= length:
return s
elif killwords:
return s[:length] + end
words = s.split(' ')
result = []
m = 0
for word in words:
m += len(word) + 1
if m > length:
break
result.append(word)
result.append(end)
return u' '.join(result)
do_truncate = stringfilter(do_truncate)
def do_wordwrap(s, pos=79, hard=False):
"""
Return a copy of the string passed to the filter wrapped after
``79`` characters. You can override this default using the first
parameter. If you set the second parameter to `true` Jinja will
also split words apart (usually a bad idea because it makes
reading hard).
"""
if len(s) < pos:
return s
if hard:
return u'\n'.join([s[idx:idx + pos] for idx in
xrange(0, len(s), pos)])
# code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
return reduce(lambda line, word, pos=pos: u'%s%s%s' %
(line, u' \n'[(len(line)-line.rfind('\n') - 1 +
len(word.split('\n', 1)[0]) >= pos)],
word), s.split(' '))
do_wordwrap = stringfilter(do_wordwrap)
def do_wordcount(s):
"""
Count the words in that string.
"""
return len([x for x in s.split() if x])
do_wordcount = stringfilter(do_wordcount)
def do_textile(s):
"""
Prase the string using textile.
requires the `PyTextile`_ library.
.. _PyTextile: http://dealmeida.net/projects/textile/
"""
from textile import textile
return textile(s.encode('utf-8')).decode('utf-8')
do_textile = stringfilter(do_textile)
def do_markdown(s):
"""
Parse the string using markdown.
requires the `Python-markdown`_ library.
.. _Python-markdown: http://www.freewisdom.org/projects/python-markdown/
"""
from markdown import markdown
return markdown(s.encode('utf-8')).decode('utf-8')
do_markdown = stringfilter(do_markdown)
def do_rst(s):
"""
Parse the string using the reStructuredText parser from the
docutils package.
requires `docutils`_.
.. _docutils: http://docutils.sourceforge.net/
"""
from docutils.core import publish_parts
parts = publish_parts(source=s, writer_name='html4css1')
return parts['fragment']
do_rst = stringfilter(do_rst)
def do_int(default=0):
"""
Convert the value into an integer. If the
conversion doesn't work it will return ``0``. You can
override this default using the first parameter.
"""
def wrapped(env, context, value):
try:
return int(value)
except (TypeError, ValueError):
try:
return int(float(value))
except (TypeError, ValueError):
return default
return wrapped
def do_float(default=0.0):
"""
Convert the value into a floating point number. If the
conversion doesn't work it will return ``0.0``. You can
override this default using the first parameter.
"""
def wrapped(env, context, value):
try:
return float(value)
except (TypeError, ValueError):
return default
return wrapped
def do_string():
"""
Convert the value into an string.
"""
return lambda e, c, v: e.to_unicode(v)
def do_format(*args):
"""
Apply python string formatting on an object:
.. sourcecode:: jinja
{{ "%s - %s"|format("Hello?", "Foo!") }}
-> Hello? - Foo!
Note that you cannot use the mapping syntax (``%(name)s``)
like in python. Use `|dformat` for that.
"""
def wrapped(env, context, value):
return env.to_unicode(value) % args
return wrapped
def do_dformat(d):
"""
Apply python mapping string formatting on an object:
.. sourcecode:: jinja
{{ "Hello %(username)s!"|dformat({'username': 'John Doe'}) }}
-> Hello John Doe!
This is useful when adding variables to translateable
string expressions.
*New in Jinja 1.1*
"""
if not isinstance(d, dict):
raise FilterArgumentError('dict required')
def wrapped(env, context, value):
return env.to_unicode(value) % d
return wrapped
def do_trim(value):
"""
Strip leading and trailing whitespace.
"""
return value.strip()
do_trim = stringfilter(do_trim)
def do_capture(name='captured', clean=False):
"""
Store the value in a variable called ``captured`` or a variable
with the name provided. Useful for filter blocks:
.. sourcecode:: jinja
{% filter capture('foo') %}
...
{% endfilter %}
{{ foo }}
This will output "..." two times. One time from the filter block
and one time from the variable. If you don't want the filter to
output something you can use it in `clean` mode:
.. sourcecode:: jinja
{% filter capture('foo', True) %}
...
{% endfilter %}
{{ foo }}
"""
if not isinstance(name, basestring):
raise FilterArgumentError('You can only capture into variables')
def wrapped(env, context, value):
context[name] = value
if clean:
return TemplateData()
return value
return wrapped
def do_striptags(value):
"""
Strip SGML/XML tags and replace adjacent whitespace by one space.
*new in Jinja 1.1*
"""
return ' '.join(_striptags_re.sub('', value).split())
do_striptags = stringfilter(do_striptags)
def do_slice(slices, fill_with=None):
"""
Slice an iterator and return a list of lists containing
those items. Useful if you want to create a div containing
three div tags that represent columns:
.. sourcecode:: html+jinja
<div class="columwrapper">
{%- for column in items|slice(3) %}
<ul class="column-{{ loop.index }}">
{%- for item in column %}
<li>{{ item }}</li>
{%- endfor %}
</ul>
{%- endfor %}
</div>
If you pass it a second argument it's used to fill missing
values on the last iteration.
*new in Jinja 1.1*
"""
def wrapped(env, context, value):
result = []
seq = list(value)
length = len(seq)
items_per_slice = length // slices
slices_with_extra = length % slices
offset = 0
for slice_number in xrange(slices):
start = offset + slice_number * items_per_slice
if slice_number < slices_with_extra:
offset += 1
end = offset + (slice_number + 1) * items_per_slice
tmp = seq[start:end]
if fill_with is not None and slice_number >= slices_with_extra:
tmp.append(fill_with)
result.append(tmp)
return result
return wrapped
def do_batch(linecount, fill_with=None):
"""
A filter that batches items. It works pretty much like `slice`
just the other way round. It returns a list of lists with the
given number of items. If you provide a second parameter this
is used to fill missing items. See this example:
.. sourcecode:: html+jinja
<table>
{%- for row in items|batch(3, '&nbsp;') %}
<tr>
{%- for column in row %}
<tr>{{ column }}</td>
{%- endfor %}
</tr>
{%- endfor %}
</table>
*new in Jinja 1.1*
"""
def wrapped(env, context, value):
result = []
tmp = []
for item in value:
if len(tmp) == linecount:
result.append(tmp)
tmp = []
tmp.append(item)
if tmp:
if fill_with is not None and len(tmp) < linecount:
tmp += [fill_with] * (linecount - len(tmp))
result.append(tmp)
return result
return wrapped
def do_sum():
"""
Sum up the given sequence of numbers.
*new in Jinja 1.1*
"""
def wrapped(env, context, value):
return sum(value)
return wrapped
def do_abs():
"""
Return the absolute value of a number.
*new in Jinja 1.1*
"""
def wrapped(env, context, value):
return abs(value)
return wrapped
def do_round(precision=0, method='common'):
"""
Round the number to a given precision. The first
parameter specifies the precision (default is ``0``), the
second the rounding method:
- ``'common'`` rounds either up or down
- ``'ceil'`` always rounds up
- ``'floor'`` always rounds down
If you don't specify a method ``'common'`` is used.
.. sourcecode:: jinja
{{ 42.55|round }}
-> 43
{{ 42.55|round(1, 'floor') }}
-> 42.5
*new in Jinja 1.1*
"""
if not method in ('common', 'ceil', 'floor'):
raise FilterArgumentError('method must be common, ceil or floor')
if precision < 0:
raise FilterArgumentError('precision must be a postive integer '
'or zero.')
def wrapped(env, context, value):
if method == 'common':
return round(value, precision)
import math
func = getattr(math, method)
if precision:
return func(value * 10 * precision) / (10 * precision)
else:
return func(value)
return wrapped
def do_sort(reverse=False):
"""
Sort a sequence. Per default it sorts ascending, if you pass it
`True` as first argument it will reverse the sorting.
*new in Jinja 1.1*
"""
def wrapped(env, context, value):
return sorted(value, reverse=reverse)
return wrapped
def do_groupby(attribute):
"""
Group a sequence of objects by a common attribute.
If you for example have a list of dicts or objects that represent persons
with `gender`, `first_name` and `last_name` attributes and you want to
group all users by genders you can do something like the following
snippet:
.. sourcecode:: html+jinja
<ul>
{% for group in persons|groupby('gender') %}
<li>{{ group.grouper }}<ul>
{% for person in group.list %}
<li>{{ person.first_name }} {{ person.last_name }}</li>
{% endfor %}</ul></li>
{% endfor %}
</ul>
As you can see the item we're grouping by is stored in the `grouper`
attribute and the `list` contains all the objects that have this grouper
in common.
*New in Jinja 1.2*
"""
def wrapped(env, context, value):
expr = lambda x: env.get_attribute(x, attribute)
return sorted([{
'grouper': a,
'list': list(b)
} for a, b in groupby(sorted(value, key=expr), expr)],
key=itemgetter('grouper'))
return wrapped
def do_getattribute(attribute):
"""
Get one attribute from an object. Normally you don't have to use this
filter because the attribute and subscript expressions try to either
get an attribute of an object or an item. In some situations it could
be that there is an item *and* an attribute with the same name. In that
situation only the item is returned, never the attribute.
.. sourcecode:: jinja
{{ foo.bar }} -> {{ foo|getattribute('bar') }}
*New in Jinja 1.2*
"""
def wrapped(env, context, value):
try:
return get_attribute(value, attribute)
except (SecurityException, AttributeError):
return env.undefined_singleton
return wrapped
def do_getitem(key):
"""
This filter basically works like the normal subscript expression but
it doesn't fall back to attribute lookup. If an item does not exist for
an object undefined is returned.
.. sourcecode:: jinja
{{ foo.bar }} -> {{ foo|getitem('bar') }}
*New in Jinja 1.2*
"""
def wrapped(env, context, value):
try:
return value[key]
except (TypeError, KeyError, IndexError, AttributeError):
return env.undefined_singleton
return wrapped
FILTERS = {
'replace': do_replace,
'upper': do_upper,
'lower': do_lower,
'escape': do_escape,
'e': do_escape,
'xmlattr': do_xmlattr,
'capitalize': do_capitalize,
'title': do_title,
'default': do_default,
'join': do_join,
'count': do_count,
'dictsort': do_dictsort,
'length': do_count,
'reverse': do_reverse,
'center': do_center,
'title': do_title,
'capitalize': do_capitalize,
'first': do_first,
'last': do_last,
'random': do_random,
'urlencode': do_urlencode,
'jsonencode': do_jsonencode,
'filesizeformat': do_filesizeformat,
'pprint': do_pprint,
'indent': do_indent,
'truncate': do_truncate,
'wordwrap': do_wordwrap,
'wordcount': do_wordcount,
'textile': do_textile,
'markdown': do_markdown,
'rst': do_rst,
'int': do_int,
'float': do_float,
'string': do_string,
'urlize': do_urlize,
'format': do_format,
'dformat': do_dformat,
'capture': do_capture,
'trim': do_trim,
'striptags': do_striptags,
'slice': do_slice,
'batch': do_batch,
'sum': do_sum,
'abs': do_abs,
'round': do_round,
'sort': do_sort,
'groupby': do_groupby,
'getattribute': do_getattribute,
'getitem': do_getitem
}

View file

@ -1,513 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.lexer
~~~~~~~~~~~
This module implements a Jinja / Python combination lexer. The
`Lexer` class provided by this module is used to do some preprocessing
for Jinja.
On the one hand it filters out invalid operators like the bitshift
operators we don't allow in templates. On the other hand it separates
template code and python code in expressions.
Because of some limitations in the compiler package which are just
natural but annoying for Jinja, the lexer also "escapes" non names that
are not keywords. The Jinja parser then removes those escaping marks
again.
This is required in order to make "class" and some other python keywords
we don't use valid identifiers.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import re
import unicodedata
from jinja.datastructure import TokenStream, Token
from jinja.exceptions import TemplateSyntaxError
from jinja.utils import set, sorted
from weakref import WeakValueDictionary
__all__ = ['Lexer', 'Failure', 'keywords']
# cache for the lexers. Exists in order to be able to have multiple
# environments with the same lexer
_lexer_cache = WeakValueDictionary()
# static regular expressions
whitespace_re = re.compile(r'\s+(?um)')
name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?ms)')
integer_re = re.compile(r'\d+')
float_re = re.compile(r'\d+\.\d+')
regex_re = re.compile(r'@/([^/\\]*(?:\\.[^/\\]*)*)*/[a-z]*(?ms)')
# set of used keywords
keywords = set(['and', 'block', 'cycle', 'elif', 'else', 'endblock',
'endfilter', 'endfor', 'endif', 'endmacro', 'endraw',
'endtrans', 'extends', 'filter', 'for', 'if', 'in',
'include', 'is', 'macro', 'not', 'or', 'pluralize', 'raw',
'recursive', 'set', 'trans', 'print', 'call', 'endcall'])
# bind operators to token types
operators = {
'+': 'add',
'-': 'sub',
'/': 'div',
'//': 'floordiv',
'*': 'mul',
'%': 'mod',
'**': 'pow',
'~': 'tilde',
'!': 'bang',
'@': 'at',
'[': 'lbracket',
']': 'rbracket',
'(': 'lparen',
')': 'rparen',
'{': 'lbrace',
'}': 'rbrace',
'==': 'eq',
'!=': 'ne',
'>': 'gt',
'>=': 'gteq',
'<': 'lt',
'<=': 'lteq',
'=': 'assign',
'.': 'dot',
':': 'colon',
'|': 'pipe',
',': 'comma'
}
reverse_operators = dict([(v, k) for k, v in operators.iteritems()])
assert len(operators) == len(reverse_operators), 'operators dropped'
operator_re = re.compile('(%s)' % '|'.join([re.escape(x) for x in
sorted(operators, key=lambda x: -len(x))]))
def unescape_string(lineno, filename, s):
r"""
Unescape a string. Supported escapes:
\a, \n, \r\, \f, \v, \\, \", \', \0
\x00, \u0000, \U00000000, \N{...}
Not supported are \101 because imho redundant.
"""
result = []
write = result.append
simple_escapes = {
'a': '\a',
'n': '\n',
'r': '\r',
'f': '\f',
't': '\t',
'v': '\v',
'\\': '\\',
'"': '"',
"'": "'",
'0': '\x00'
}
unicode_escapes = {
'x': 2,
'u': 4,
'U': 8
}
chariter = iter(s)
next_char = chariter.next
try:
for char in chariter:
if char == '\\':
char = next_char()
if char in simple_escapes:
write(simple_escapes[char])
elif char in unicode_escapes:
seq = [next_char() for x in xrange(unicode_escapes[char])]
try:
write(unichr(int(''.join(seq), 16)))
except ValueError:
raise TemplateSyntaxError('invalid unicode codepoint',
lineno, filename)
elif char == 'N':
if next_char() != '{':
raise TemplateSyntaxError('no name for codepoint',
lineno, filename)
seq = []
while True:
char = next_char()
if char == '}':
break
seq.append(char)
try:
write(unicodedata.lookup(u''.join(seq)))
except KeyError:
raise TemplateSyntaxError('unknown character name',
lineno, filename)
else:
write('\\' + char)
else:
write(char)
except StopIteration:
raise TemplateSyntaxError('invalid string escape', lineno, filename)
return u''.join(result)
def unescape_regex(s):
"""
Unescape rules for regular expressions.
"""
buffer = []
write = buffer.append
in_escape = False
for char in s:
if in_escape:
in_escape = False
if char not in safe_chars:
write('\\' + char)
continue
write(char)
return u''.join(buffer)
class Failure(object):
"""
Class that raises a `TemplateSyntaxError` if called.
Used by the `Lexer` to specify known errors.
"""
def __init__(self, message, cls=TemplateSyntaxError):
self.message = message
self.error_class = cls
def __call__(self, lineno, filename):
raise self.error_class(self.message, lineno, filename)
class LexerMeta(type):
"""
Metaclass for the lexer that caches instances for
the same configuration in a weak value dictionary.
"""
def __call__(cls, environment):
key = hash((environment.block_start_string,
environment.block_end_string,
environment.variable_start_string,
environment.variable_end_string,
environment.comment_start_string,
environment.comment_end_string,
environment.trim_blocks))
# use the cached lexer if possible
if key in _lexer_cache:
return _lexer_cache[key]
# create a new lexer and cache it
lexer = type.__call__(cls, environment)
_lexer_cache[key] = lexer
return lexer
class Lexer(object):
"""
Class that implements a lexer for a given environment. Automatically
created by the environment class, usually you don't have to do that.
Note that the lexer is not automatically bound to an environment.
Multiple environments can share the same lexer.
"""
__metaclass__ = LexerMeta
def __init__(self, environment):
# shortcuts
c = lambda x: re.compile(x, re.M | re.S)
e = re.escape
# lexing rules for tags
tag_rules = [
(whitespace_re, None, None),
(float_re, 'float', None),
(integer_re, 'integer', None),
(name_re, 'name', None),
(string_re, 'string', None),
(regex_re, 'regex', None),
(operator_re, 'operator', None)
]
#: if variables and blocks have the same delimiters we won't
#: receive any variable blocks in the parser. This variable is `True`
#: if we need that.
self.no_variable_block = (
(environment.variable_start_string is
environment.variable_end_string is None) or
(environment.variable_start_string ==
environment.block_start_string and
environment.variable_end_string ==
environment.block_end_string)
)
# assamble the root lexing rule. because "|" is ungreedy
# we have to sort by length so that the lexer continues working
# as expected when we have parsing rules like <% for block and
# <%= for variables. (if someone wants asp like syntax)
# variables are just part of the rules if variable processing
# is required.
root_tag_rules = [
('comment', environment.comment_start_string),
('block', environment.block_start_string)
]
if not self.no_variable_block:
root_tag_rules.append(('variable',
environment.variable_start_string))
root_tag_rules.sort(lambda a, b: cmp(len(b[1]), len(a[1])))
# block suffix if trimming is enabled
block_suffix_re = environment.trim_blocks and '\\n?' or ''
# global lexing rules
self.rules = {
'root': [
# directives
(c('(.*?)(?:%s)' % '|'.join(
['(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*%s)' % (
e(environment.block_start_string),
e(environment.block_start_string),
e(environment.block_end_string)
)] + [
'(?P<%s_begin>\s*%s\-|%s)' % (n, e(r), e(r))
for n, r in root_tag_rules
])), ('data', '#bygroup'), '#bygroup'),
# data
(c('.+'), 'data', None)
],
# comments
'comment_begin': [
(c(r'(.*?)((?:\-%s\s*|%s)%s)' % (
e(environment.comment_end_string),
e(environment.comment_end_string),
block_suffix_re
)), ('comment', 'comment_end'), '#pop'),
(c('(.)'), (Failure('Missing end of comment tag'),), None)
],
# blocks
'block_begin': [
(c('(?:\-%s\s*|%s)%s' % (
e(environment.block_end_string),
e(environment.block_end_string),
block_suffix_re
)), 'block_end', '#pop'),
] + tag_rules,
# raw block
'raw_begin': [
(c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
e(environment.block_start_string),
e(environment.block_start_string),
e(environment.block_end_string),
e(environment.block_end_string),
block_suffix_re
)), ('data', 'raw_end'), '#pop'),
(c('(.)'), (Failure('Missing end of raw directive'),), None)
]
}
# only add the variable rules to the list if we process variables
# the variable_end_string variable could be None and break things.
if not self.no_variable_block:
self.rules['variable_begin'] = [
(c('\-%s\s*|%s' % (
e(environment.variable_end_string),
e(environment.variable_end_string)
)), 'variable_end', '#pop')
] + tag_rules
def tokenize(self, source, filename=None):
"""
Works like `tokeniter` but returns a tokenstream of tokens and not a
generator or token tuples. Additionally all token values are already
converted into types and postprocessed. For example keywords are
already keyword tokens, not named tokens, comments are removed,
integers and floats converted, strings unescaped etc.
"""
def generate():
for lineno, token, value in self.tokeniter(source, filename):
if token in ('comment_begin', 'comment', 'comment_end'):
continue
elif token == 'data':
try:
value = str(value)
except UnicodeError:
pass
elif token == 'name':
value = str(value)
if value in keywords:
token = value
value = ''
elif token == 'string':
value = unescape_string(lineno, filename, value[1:-1])
try:
value = str(value)
except UnicodeError:
pass
elif token == 'regex':
args = value[value.rfind('/') + 1:]
value = unescape_regex(value[2:-(len(args) + 1)])
if args:
value = '(?%s)%s' % (args, value)
elif token == 'integer':
value = int(value)
elif token == 'float':
value = float(value)
elif token == 'operator':
token = operators[value]
value = ''
yield Token(lineno, token, value)
return TokenStream(generate(), filename)
def tokeniter(self, source, filename=None):
"""
This method tokenizes the text and returns the tokens in a generator.
Use this method if you just want to tokenize a template. The output
you get is not compatible with the input the jinja parser wants. The
parser uses the `tokenize` function with returns a `TokenStream` and
keywords instead of just names.
"""
source = '\n'.join(source.splitlines())
pos = 0
lineno = 1
stack = ['root']
statetokens = self.rules['root']
source_length = len(source)
balancing_stack = []
while True:
# tokenizer loop
for regex, tokens, new_state in statetokens:
m = regex.match(source, pos)
# if no match we try again with the next rule
if not m:
continue
# we only match blocks and variables if brances / parentheses
# are balanced. continue parsing with the lower rule which
# is the operator rule. do this only if the end tags look
# like operators
if balancing_stack and \
tokens in ('variable_end', 'block_end'):
continue
# tuples support more options
if isinstance(tokens, tuple):
for idx, token in enumerate(tokens):
# hidden group
if token is None:
g = m.group(idx)
if g:
lineno += g.count('\n')
continue
# failure group
elif token.__class__ is Failure:
raise token(lineno, filename)
# bygroup is a bit more complex, in that case we
# yield for the current token the first named
# group that matched
elif token == '#bygroup':
for key, value in m.groupdict().iteritems():
if value is not None:
yield lineno, key, value
lineno += value.count('\n')
break
else:
raise RuntimeError('%r wanted to resolve '
'the token dynamically'
' but no group matched'
% regex)
# normal group
else:
data = m.group(idx + 1)
if data:
yield lineno, token, data
lineno += data.count('\n')
# strings as token just are yielded as it, but just
# if the data is not empty
else:
data = m.group()
# update brace/parentheses balance
if tokens == 'operator':
if data == '{':
balancing_stack.append('}')
elif data == '(':
balancing_stack.append(')')
elif data == '[':
balancing_stack.append(']')
elif data in ('}', ')', ']'):
if not balancing_stack:
raise TemplateSyntaxError('unexpected "%s"' %
data, lineno,
filename)
expected_op = balancing_stack.pop()
if expected_op != data:
raise TemplateSyntaxError('unexpected "%s", '
'expected "%s"' %
(data, expected_op),
lineno, filename)
# yield items
if tokens is not None:
if data:
yield lineno, tokens, data
lineno += data.count('\n')
# fetch new position into new variable so that we can check
# if there is a internal parsing error which would result
# in an infinite loop
pos2 = m.end()
# handle state changes
if new_state is not None:
# remove the uppermost state
if new_state == '#pop':
stack.pop()
# resolve the new state by group checking
elif new_state == '#bygroup':
for key, value in m.groupdict().iteritems():
if value is not None:
stack.append(key)
break
else:
raise RuntimeError('%r wanted to resolve the '
'new state dynamically but'
' no group matched' %
regex)
# direct state name given
else:
stack.append(new_state)
statetokens = self.rules[stack[-1]]
# we are still at the same position and no stack change.
# this means a loop without break condition, avoid that and
# raise error
elif pos2 == pos:
raise RuntimeError('%r yielded empty string without '
'stack change' % regex)
# publish new function and start again
pos = pos2
break
# if loop terminated without break we havn't found a single match
# either we are at the end of the file or we have a problem
else:
# end of text
if pos >= source_length:
return
# something went wrong
raise TemplateSyntaxError('unexpected char %r at %d' %
(source[pos], pos), lineno,
filename)

View file

@ -1,822 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.loaders
~~~~~~~~~~~~~
Jinja loader classes.
:copyright: 2007 by Armin Ronacher, Bryan McLemore.
:license: BSD, see LICENSE for more details.
"""
import codecs
try:
from hashlib import sha1
except ImportError:
from sha import new as sha1
import time
from os import path
from threading import Lock
from jinja.parser import Parser
from jinja.translators.python import PythonTranslator, Template
from jinja.exceptions import TemplateNotFound, TemplateSyntaxError, \
TemplateIncludeError
from jinja.utils import CacheDict
#: when updating this, update the listing in the jinja package too
__all__ = ['FileSystemLoader', 'PackageLoader', 'DictLoader', 'ChoiceLoader',
'FunctionLoader', 'MemcachedFileSystemLoader']
def get_template_filename(searchpath, name):
"""
Return the filesystem filename wanted.
"""
return path.join(searchpath, *[p for p in name.split('/')
if p and p[0] != '.'])
def get_cachename(cachepath, name, salt=None):
"""
Return the filename for a cached file.
"""
return path.join(cachepath, 'jinja_%s.cache' %
sha1('jinja(%s|%s)tmpl' %
(name, salt or '')).hexdigest())
def _loader_missing(*args, **kwargs):
"""Helper function for `LoaderWrapper`."""
raise RuntimeError('no loader defined')
class LoaderWrapper(object):
"""
Wraps a loader so that it's bound to an environment.
Also handles template syntax errors.
"""
def __init__(self, environment, loader):
self.environment = environment
self.loader = loader
if self.loader is None:
self.get_source = self.parse = self.load = _loader_missing
self.available = False
else:
self.available = True
def __getattr__(self, name):
"""
Not found attributes are redirected to the loader
"""
return getattr(self.loader, name)
def get_source(self, name, parent=None):
"""Retrieve the sourcecode of a template."""
# just ascii chars are allowed as template names
name = str(name)
return self.loader.get_source(self.environment, name, parent)
def parse(self, name, parent=None):
"""Retreive a template and parse it."""
# just ascii chars are allowed as template names
name = str(name)
return self.loader.parse(self.environment, name, parent)
def load(self, name, translator=PythonTranslator):
"""
Translate a template and return it. This must not necesarily
be a template class. The javascript translator for example
will just output a string with the translated code.
"""
# just ascii chars are allowed as template names
name = str(name)
try:
return self.loader.load(self.environment, name, translator)
except TemplateSyntaxError, e:
if not self.environment.friendly_traceback:
raise
__traceback_hide__ = True
from jinja.debugger import raise_syntax_error
raise_syntax_error(e, self.environment)
def get_controlled_loader(self):
"""
Return a loader that runs in a controlled environment. (Keeps
track of templates that it loads and is not thread safe).
"""
return ControlledLoader(self.environment, self.loader)
def _loader_missing(self, *args, **kwargs):
"""Helper method that overrides all other methods if no
loader is defined."""
raise RuntimeError('no loader defined')
def __nonzero__(self):
return self.available
class ControlledLoader(LoaderWrapper):
"""
Used for template extending and including.
"""
def __init__(self, environment, loader):
LoaderWrapper.__init__(self, environment, loader)
self._stack = []
def get_controlled_loader(self):
raise TypeError('Cannot get new controlled loader from an already '
'controlled loader.')
def mark_as_processed(self):
"""Mark the last parsed/sourced/included template as processed."""
if not self._stack:
raise RuntimeError('No template for marking found')
self._stack.pop()
def _controlled(method):
def new_method(self, name, *args, **kw):
if name in self._stack:
raise TemplateIncludeError('Circular imports/extends '
'detected. %r appeared twice.' %
name)
self._stack.append(name)
return method(self, name, *args, **kw)
try:
new_method.__name__ = method.__name__
new_method.__doc__ = method.__doc__
except AttributeError:
pass
return new_method
get_source = _controlled(LoaderWrapper.get_source)
parse = _controlled(LoaderWrapper.parse)
load = _controlled(LoaderWrapper.load)
del _controlled
class BaseLoader(object):
"""
Use this class to implement loaders.
Just inherit from this class and implement a method called
`get_source` with the signature (`environment`, `name`, `parent`)
that returns sourcecode for the template.
For more complex loaders you probably want to override `load` to
or not use the `BaseLoader` at all.
"""
def parse(self, environment, name, parent):
"""
Load and parse a template
"""
source = self.get_source(environment, name, parent)
return Parser(environment, source, name).parse()
def load(self, environment, name, translator):
"""
Load and translate a template
"""
ast = self.parse(environment, name, None)
return translator.process(environment, ast)
def get_source(self, environment, name, parent):
"""
Override this method to get the source for a template.
"""
raise TemplateNotFound(name)
class CachedLoaderMixin(object):
"""
Mixin this class to implement simple memory and disk caching. The
memcaching just uses a dict in the loader so if you have a global
environment or at least a global loader this can speed things up.
If the memcaching is enabled you can use (with Jinja 1.1 onwards)
the `clear_memcache` function to clear the cache.
For memcached support check the `MemcachedLoaderMixin`.
"""
def __init__(self, use_memcache, cache_size, cache_folder, auto_reload,
cache_salt=None):
if use_memcache:
self.__memcache = CacheDict(cache_size)
else:
self.__memcache = None
self.__cache_folder = cache_folder
if not hasattr(self, 'check_source_changed'):
self.__auto_reload = False
else:
self.__auto_reload = auto_reload
self.__salt = cache_salt
self.__times = {}
self.__lock = Lock()
def clear_memcache(self):
"""
Clears the memcache.
"""
if self.__memcache is not None:
self.__memcache.clear()
def load(self, environment, name, translator):
"""
Load and translate a template. First we check if there is a
cached version of this template in the memory cache. If this is
not the cache check for a compiled template in the disk cache
folder. And if none of this is the case we translate the temlate,
cache and return it.
"""
self.__lock.acquire()
try:
# caching is only possible for the python translator. skip
# all other translators
if translator is not PythonTranslator:
return super(CachedLoaderMixin, self).load(
environment, name, translator)
tmpl = None
save_to_disk = False
push_to_memory = False
# auto reload enabled? check for the last change of
# the template
if self.__auto_reload:
last_change = self.check_source_changed(environment, name)
else:
last_change = None
# check if we have something in the memory cache and the
# memory cache is enabled.
if self.__memcache is not None:
if name in self.__memcache:
tmpl = self.__memcache[name]
# if auto reload is enabled check if the template changed
if last_change and last_change > self.__times[name]:
tmpl = None
push_to_memory = True
else:
push_to_memory = True
# mem cache disabled or not cached by now
# try to load if from the disk cache
if tmpl is None and self.__cache_folder is not None:
cache_fn = get_cachename(self.__cache_folder, name, self.__salt)
if last_change is not None:
try:
cache_time = path.getmtime(cache_fn)
except OSError:
cache_time = 0
if last_change is None or (cache_time and
last_change <= cache_time):
try:
f = file(cache_fn, 'rb')
except IOError:
tmpl = None
save_to_disk = True
else:
try:
tmpl = Template.load(environment, f)
finally:
f.close()
else:
save_to_disk = True
# if we still have no template we load, parse and translate it.
if tmpl is None:
tmpl = super(CachedLoaderMixin, self).load(
environment, name, translator)
# save the compiled template on the disk if enabled
if save_to_disk:
f = file(cache_fn, 'wb')
try:
tmpl.dump(f)
finally:
f.close()
# if memcaching is enabled and the template not loaded
# we add that there.
if push_to_memory:
self.__times[name] = time.time()
self.__memcache[name] = tmpl
return tmpl
finally:
self.__lock.release()
class MemcachedLoaderMixin(object):
"""
Uses a memcached server to cache the templates.
Requires the memcache library from `tummy`_ or the cmemcache library
from `Gijsbert de Haan`_.
With Jinja 1.2 onwards you can also provide a `client` keyword argument
that takes an already instanciated memcache client or memcache client
like object.
.. _tummy: http://www.tummy.com/Community/software/python-memcached/
.. _Gisjsbert de Haan: http://gijsbert.org/cmemcache/
"""
def __init__(self, use_memcache, memcache_time=60 * 60 * 24 * 7,
memcache_host=None, item_prefix='template/', client=None):
if memcache_host is None:
memcache_host = ['127.0.0.1:11211']
if use_memcache:
if client is None:
try:
try:
from cmemcache import Client
except ImportError:
from memcache import Client
except ImportError:
raise RuntimeError('the %r loader requires an installed '
'memcache module' %
self.__class__.__name__)
client = Client(list(memcache_host))
self.__memcache = client
self.__memcache_time = memcache_time
else:
self.__memcache = None
self.__item_prefix = item_prefix
self.__lock = Lock()
def load(self, environment, name, translator):
"""
Load and translate a template. First we check if there is a
cached version of this template in the memory cache. If this is
not the cache check for a compiled template in the disk cache
folder. And if none of this is the case we translate the template,
cache and return it.
"""
self.__lock.acquire()
try:
# caching is only possible for the python translator. skip
# all other translators
if translator is not PythonTranslator:
return super(MemcachedLoaderMixin, self).load(
environment, name, translator)
tmpl = None
push_to_memory = False
# check if we have something in the memory cache and the
# memory cache is enabled.
if self.__memcache is not None:
bytecode = self.__memcache.get(self.__item_prefix + name)
if bytecode:
tmpl = Template.load(environment, bytecode)
else:
push_to_memory = True
# if we still have no template we load, parse and translate it.
if tmpl is None:
tmpl = super(MemcachedLoaderMixin, self).load(
environment, name, translator)
# if memcaching is enabled and the template not loaded
# we add that there.
if push_to_memory:
self.__memcache.set(self.__item_prefix + name, tmpl.dump(),
self.__memcache_time)
return tmpl
finally:
self.__lock.release()
class BaseFileSystemLoader(BaseLoader):
"""
Baseclass for the file system loader that does not do any caching.
It exists to avoid redundant code, just don't use it without subclassing.
How subclassing can work:
.. sourcecode:: python
from jinja.loaders import BaseFileSystemLoader
class MyFileSystemLoader(BaseFileSystemLoader):
def __init__(self):
BaseFileSystemLoader.__init__(self, '/path/to/templates')
The base file system loader only takes one parameter beside self which
is the path to the templates.
"""
def __init__(self, searchpath):
self.searchpath = path.abspath(searchpath)
def get_source(self, environment, name, parent):
filename = get_template_filename(self.searchpath, name)
if path.exists(filename):
f = codecs.open(filename, 'r', environment.template_charset)
try:
return f.read()
finally:
f.close()
else:
raise TemplateNotFound(name)
class FileSystemLoader(CachedLoaderMixin, BaseFileSystemLoader):
"""
Loads templates from the filesystem:
.. sourcecode:: python
from jinja import Environment, FileSystemLoader
e = Environment(loader=FileSystemLoader('templates/'))
You can pass the following keyword arguments to the loader on
initialization:
=================== =================================================
``searchpath`` String with the path to the templates on the
filesystem.
``use_memcache`` Set this to ``True`` to enable memory caching.
This is usually a good idea in production mode,
but disable it during development since it won't
reload template changes automatically.
This only works in persistent environments like
FastCGI.
``memcache_size`` Number of template instance you want to cache.
Defaults to ``40``.
``cache_folder`` Set this to an existing directory to enable
caching of templates on the file system. Note
that this only affects templates transformed
into python code. Default is ``None`` which means
that caching is disabled.
``auto_reload`` Set this to `False` for a slightly better
performance. In that case Jinja won't check for
template changes on the filesystem.
``cache_salt`` Optional unique number to not confuse the
caching system when caching more than one
template loader in the same folder. Defaults
to the searchpath. *New in Jinja 1.1*
=================== =================================================
"""
def __init__(self, searchpath, use_memcache=False, memcache_size=40,
cache_folder=None, auto_reload=True, cache_salt=None):
BaseFileSystemLoader.__init__(self, searchpath)
if cache_salt is None:
cache_salt = self.searchpath
CachedLoaderMixin.__init__(self, use_memcache, memcache_size,
cache_folder, auto_reload, cache_salt)
def check_source_changed(self, environment, name):
filename = get_template_filename(self.searchpath, name)
if path.exists(filename):
return path.getmtime(filename)
return -1
class MemcachedFileSystemLoader(MemcachedLoaderMixin, BaseFileSystemLoader):
"""
Loads templates from the filesystem and caches them on a memcached
server.
.. sourcecode:: python
from jinja import Environment, MemcachedFileSystemLoader
e = Environment(loader=MemcachedFileSystemLoader('templates/',
memcache_host=['192.168.2.250:11211']
))
You can pass the following keyword arguments to the loader on
initialization:
=================== =================================================
``searchpath`` String with the path to the templates on the
filesystem.
``use_memcache`` Set this to ``True`` to enable memcached caching.
In that case it behaves like a normal
`FileSystemLoader` with disabled caching.
``memcache_time`` The expire time of a template in the cache.
``memcache_host`` a list of memcached servers.
``item_prefix`` The prefix for the items on the server. Defaults
to ``'template/'``.
=================== =================================================
"""
def __init__(self, searchpath, use_memcache=True,
memcache_time=60 * 60 * 24 * 7, memcache_host=None,
item_prefix='template/'):
BaseFileSystemLoader.__init__(self, searchpath)
MemcachedLoaderMixin.__init__(self, use_memcache, memcache_time,
memcache_host, item_prefix)
class BasePackageLoader(BaseLoader):
"""
Baseclass for the package loader that does not do any caching.
It accepts two parameters: The name of the package and the path relative
to the package:
.. sourcecode:: python
from jinja.loaders import BasePackageLoader
class MyPackageLoader(BasePackageLoader):
def __init__(self):
BasePackageLoader.__init__(self, 'my_package', 'shared/templates')
The relative path must use slashes as path delimiters, even on Mac OS
and Microsoft Windows.
It uses the `pkg_resources` libraries distributed with setuptools for
retrieving the data from the packages. This works for eggs too so you
don't have to mark your egg as non zip safe. If pkg_resources is not
available it just falls back to path joining relative to the package.
"""
def __init__(self, package_name, package_path, force_native=False):
try:
import pkg_resources
except ImportError:
raise RuntimeError('setuptools not installed')
self.package_name = package_name
self.package_path = package_path
self.force_native = force_native
def _get_load_func(self):
if hasattr(self, '_load_func'):
return self._load_func
try:
from pkg_resources import resource_exists, resource_string
if self.force_native:
raise ImportError()
except ImportError:
basepath = path.dirname(__import__(self.package_name, None, None,
['__file__']).__file__)
def load_func(name):
filename = path.join(basepath, *(
self.package_path.split('/') +
[p for p in name.split('/') if p != '..'])
)
if path.exists(filename):
f = file(filename)
try:
return f.read()
finally:
f.close()
else:
def load_func(name):
path = '/'.join([self.package_path] + [p for p in name.split('/')
if p != '..'])
if resource_exists(self.package_name, path):
return resource_string(self.package_name, path)
self._load_func = load_func
return load_func
def get_source(self, environment, name, parent):
load_func = self._get_load_func()
contents = load_func(name)
if contents is None:
raise TemplateNotFound(name)
return contents.decode(environment.template_charset)
class PackageLoader(CachedLoaderMixin, BasePackageLoader):
"""
Loads templates from python packages using setuptools.
.. sourcecode:: python
from jinja import Environment, PackageLoader
e = Environment(loader=PackageLoader('yourapp', 'template/path'))
You can pass the following keyword arguments to the loader on
initialization:
=================== =================================================
``package_name`` Name of the package containing the templates.
``package_path`` Path of the templates inside the package.
``use_memcache`` Set this to ``True`` to enable memory caching.
This is usually a good idea in production mode,
but disable it during development since it won't
reload template changes automatically.
This only works in persistent environments like
FastCGI.
``memcache_size`` Number of template instance you want to cache.
Defaults to ``40``.
``cache_folder`` Set this to an existing directory to enable
caching of templates on the file system. Note
that this only affects templates transformed
into python code. Default is ``None`` which means
that caching is disabled.
``auto_reload`` Set this to `False` for a slightly better
performance. In that case Jinja won't check for
template changes on the filesystem. If the
templates are inside of an egg file this won't
have an effect.
``cache_salt`` Optional unique number to not confuse the
caching system when caching more than one
template loader in the same folder. Defaults
to ``package_name + '/' + package_path``.
*New in Jinja 1.1*
=================== =================================================
Important note: If you're using an application that is inside of an
egg never set `auto_reload` to `True`. The egg resource manager will
automatically export files to the file system and touch them so that
you not only end up with additional temporary files but also an automatic
reload each time you load a template.
"""
def __init__(self, package_name, package_path, use_memcache=False,
memcache_size=40, cache_folder=None, auto_reload=True,
cache_salt=None):
BasePackageLoader.__init__(self, package_name, package_path)
if cache_salt is None:
cache_salt = package_name + '/' + package_path
CachedLoaderMixin.__init__(self, use_memcache, memcache_size,
cache_folder, auto_reload, cache_salt)
def check_source_changed(self, environment, name):
from pkg_resources import resource_exists, resource_filename
fn = resource_filename(self.package_name, '/'.join([self.package_path] +
[p for p in name.split('/') if p and p[0] != '.']))
if resource_exists(self.package_name, fn):
return path.getmtime(fn)
return -1
class BaseFunctionLoader(BaseLoader):
"""
Baseclass for the function loader that doesn't do any caching.
It just accepts one parameter which is the function which is called
with the name of the requested template. If the return value is `None`
the loader will raise a `TemplateNotFound` error.
.. sourcecode:: python
from jinja.loaders import BaseFunctionLoader
templates = {...}
class MyFunctionLoader(BaseFunctionLoader):
def __init__(self):
BaseFunctionLoader(templates.get)
"""
def __init__(self, loader_func):
self.loader_func = loader_func
def get_source(self, environment, name, parent):
rv = self.loader_func(name)
if rv is None:
raise TemplateNotFound(name)
if isinstance(rv, str):
return rv.decode(environment.template_charset)
return rv
class FunctionLoader(CachedLoaderMixin, BaseFunctionLoader):
"""
Loads templates by calling a function which has to return a string
or `None` if an error occoured.
.. sourcecode:: python
from jinja import Environment, FunctionLoader
def my_load_func(template_name):
if template_name == 'foo':
return '...'
e = Environment(loader=FunctionLoader(my_load_func))
Because the interface is limited there is no way to cache such
templates. Usually you should try to use a loader with a more
solid backend.
You can pass the following keyword arguments to the loader on
initialization:
=================== =================================================
``loader_func`` Function that takes the name of the template to
load. If it returns a string or unicode object
it's used to load a template. If the return
value is None it's considered missing.
``getmtime_func`` Function used to check if templates requires
reloading. Has to return the UNIX timestamp of
the last template change or ``-1`` if this template
does not exist or requires updates at any cost.
``use_memcache`` Set this to ``True`` to enable memory caching.
This is usually a good idea in production mode,
but disable it during development since it won't
reload template changes automatically.
This only works in persistent environments like
FastCGI.
``memcache_size`` Number of template instance you want to cache.
Defaults to ``40``.
``cache_folder`` Set this to an existing directory to enable
caching of templates on the file system. Note
that this only affects templates transformed
into python code. Default is ``None`` which means
that caching is disabled.
``auto_reload`` Set this to `False` for a slightly better
performance. In that case of `getmtime_func`
not being provided this won't have an effect.
``cache_salt`` Optional unique number to not confuse the
caching system when caching more than one
template loader in the same folder.
=================== =================================================
"""
def __init__(self, loader_func, getmtime_func=None, use_memcache=False,
memcache_size=40, cache_folder=None, auto_reload=True,
cache_salt=None):
BaseFunctionLoader.__init__(self, loader_func)
# when changing the signature also check the jinja.plugin function
# loader instantiation.
self.getmtime_func = getmtime_func
if auto_reload and getmtime_func is None:
auto_reload = False
CachedLoaderMixin.__init__(self, use_memcache, memcache_size,
cache_folder, auto_reload, cache_salt)
def check_source_changed(self, environment, name):
return self.getmtime_func(name)
class DictLoader(BaseLoader):
"""
Load templates from a given dict:
.. sourcecode:: python
from jinja import Environment, DictLoader
e = Environment(loader=DictLoader(dict(
layout='...',
index='{% extends 'layout' %}...'
)))
This loader does not have any caching capabilities.
"""
def __init__(self, templates):
self.templates = templates
def get_source(self, environment, name, parent):
if name in self.templates:
return self.templates[name]
raise TemplateNotFound(name)
class ChoiceLoader(object):
"""
A loader that tries multiple loaders in the order they are given to
the `ChoiceLoader`:
.. sourcecode:: python
from jinja import ChoiceLoader, FileSystemLoader
loader1 = FileSystemLoader("templates1")
loader2 = FileSystemLoader("templates2")
loader = ChoiceLoader([loader1, loader2])
"""
def __init__(self, loaders):
self.loaders = list(loaders)
def get_source(self, environment, name, parent):
for loader in self.loaders:
try:
return loader.get_source(environment, name, parent)
except TemplateNotFound, e:
if e.name != name:
raise
continue
raise TemplateNotFound(name)
def parse(self, environment, name, parent):
for loader in self.loaders:
try:
return loader.parse(environment, name, parent)
except TemplateNotFound, e:
if e.name != name:
raise
continue
raise TemplateNotFound(name)
def load(self, environment, name, translator):
for loader in self.loaders:
try:
return loader.load(environment, name, translator)
except TemplateNotFound, e:
if e.name != name:
raise
continue
raise TemplateNotFound(name)

View file

@ -1,792 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.nodes
~~~~~~~~~~~
This module implements additional nodes derived from the ast base node.
It also provides some node tree helper functions like `in_lineno` and
`get_nodes` used by the parser and translator in order to normalize
python and jinja nodes.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from itertools import chain
from copy import copy
def get_nodes(nodetype, tree, exclude_root=True):
"""
Get all nodes from nodetype in the tree excluding the
node passed if `exclude_root` is `True` (default).
"""
if exclude_root:
todo = tree.get_child_nodes()
else:
todo = [tree]
while todo:
node = todo.pop()
if node.__class__ is nodetype:
yield node
todo.extend(node.get_child_nodes())
class NotPossible(NotImplementedError):
"""
If a given node cannot do something.
"""
class Node(object):
"""
Jinja node.
"""
def __init__(self, lineno=None, filename=None):
self.lineno = lineno
self.filename = filename
def get_items(self):
return []
def get_child_nodes(self):
return [x for x in self.get_items() if isinstance(x, Node)]
def allows_assignments(self):
return False
def __repr__(self):
return 'Node()'
class Text(Node):
"""
Node that represents normal text.
"""
def __init__(self, text, variables, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.text = text
self.variables = variables
def get_items(self):
return [self.text] + list(self.variables)
def __repr__(self):
return 'Text(%r, %r)' % (
self.text,
self.variables
)
class NodeList(list, Node):
"""
A node that stores multiple childnodes.
"""
def __init__(self, data, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
list.__init__(self, data)
def get_items(self):
return list(self)
def __repr__(self):
return 'NodeList(%s)' % list.__repr__(self)
class Template(Node):
"""
Node that represents a template.
"""
def __init__(self, extends, body, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.extends = extends
self.body = body
def get_items(self):
return [self.extends, self.body]
def __repr__(self):
return 'Template(%r, %r)' % (
self.extends,
self.body
)
class ForLoop(Node):
"""
A node that represents a for loop
"""
def __init__(self, item, seq, body, else_, recursive, lineno=None,
filename=None):
Node.__init__(self, lineno, filename)
self.item = item
self.seq = seq
self.body = body
self.else_ = else_
self.recursive = recursive
def get_items(self):
return [self.item, self.seq, self.body, self.else_, self.recursive]
def __repr__(self):
return 'ForLoop(%r, %r, %r, %r, %r)' % (
self.item,
self.seq,
self.body,
self.else_,
self.recursive
)
class IfCondition(Node):
"""
A node that represents an if condition.
"""
def __init__(self, tests, else_, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.tests = tests
self.else_ = else_
def get_items(self):
result = []
for test in self.tests:
result.extend(test)
result.append(self.else_)
return result
def __repr__(self):
return 'IfCondition(%r, %r)' % (
self.tests,
self.else_
)
class Cycle(Node):
"""
A node that represents the cycle statement.
"""
def __init__(self, seq, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.seq = seq
def get_items(self):
return [self.seq]
def __repr__(self):
return 'Cycle(%r)' % (self.seq,)
class Print(Node):
"""
A node that represents variable tags and print calls.
"""
def __init__(self, expr, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.expr = expr
def get_items(self):
return [self.expr]
def __repr__(self):
return 'Print(%r)' % (self.expr,)
class Macro(Node):
"""
A node that represents a macro.
"""
def __init__(self, name, arguments, body, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.name = name
self.arguments = arguments
self.body = body
def get_items(self):
return [self.name] + list(chain(*self.arguments)) + [self.body]
def __repr__(self):
return 'Macro(%r, %r, %r)' % (
self.name,
self.arguments,
self.body
)
class Call(Node):
"""
A node that represents am extended macro call.
"""
def __init__(self, expr, body, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.expr = expr
self.body = body
def get_items(self):
return [self.expr, self.body]
def __repr__(self):
return 'Call(%r, %r)' % (
self.expr,
self.body
)
class Set(Node):
"""
Allows defining own variables.
"""
def __init__(self, name, expr, scope_local, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.name = name
self.expr = expr
self.scope_local = scope_local
def get_items(self):
return [self.name, self.expr, self.scope_local]
def __repr__(self):
return 'Set(%r, %r, %r)' % (
self.name,
self.expr,
self.scope_local
)
class Filter(Node):
"""
Node for filter sections.
"""
def __init__(self, body, filters, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.body = body
self.filters = filters
def get_items(self):
return [self.body] + list(self.filters)
def __repr__(self):
return 'Filter(%r, %r)' % (
self.body,
self.filters
)
class Block(Node):
"""
A node that represents a block.
"""
def __init__(self, name, body, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.name = name
self.body = body
def replace(self, node):
"""
Replace the current data with the copied data of another block
node.
"""
assert node.__class__ is Block
self.lineno = node.lineno
self.filename = node.filename
self.name = node.name
self.body = copy(node.body)
def clone(self):
"""
Create an independent clone of this node.
"""
return copy(self)
def get_items(self):
return [self.name, self.body]
def __repr__(self):
return 'Block(%r, %r)' % (
self.name,
self.body
)
class Include(Node):
"""
A node that represents the include tag.
"""
def __init__(self, template, lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.template = template
def get_items(self):
return [self.template]
def __repr__(self):
return 'Include(%r)' % (
self.template
)
class Trans(Node):
"""
A node for translatable sections.
"""
def __init__(self, singular, plural, indicator, replacements,
lineno=None, filename=None):
Node.__init__(self, lineno, filename)
self.singular = singular
self.plural = plural
self.indicator = indicator
self.replacements = replacements
def get_items(self):
rv = [self.singular, self.plural, self.indicator]
if self.replacements:
rv.extend(self.replacements.values())
rv.extend(self.replacements.keys())
return rv
def __repr__(self):
return 'Trans(%r, %r, %r, %r)' % (
self.singular,
self.plural,
self.indicator,
self.replacements
)
class Expression(Node):
"""
Baseclass for all expressions.
"""
class BinaryExpression(Expression):
"""
Baseclass for all binary expressions.
"""
def __init__(self, left, right, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.left = left
self.right = right
def get_items(self):
return [self.left, self.right]
def __repr__(self):
return '%s(%r, %r)' % (
self.__class__.__name__,
self.left,
self.right
)
class UnaryExpression(Expression):
"""
Baseclass for all unary expressions.
"""
def __init__(self, node, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.node = node
def get_items(self):
return [self.node]
def __repr__(self):
return '%s(%r)' % (
self.__class__.__name__,
self.node
)
class ConstantExpression(Expression):
"""
any constat such as {{ "foo" }}
"""
def __init__(self, value, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.value = value
def get_items(self):
return [self.value]
def __repr__(self):
return 'ConstantExpression(%r)' % (self.value,)
class UndefinedExpression(Expression):
"""
represents the special 'undefined' value.
"""
def __repr__(self):
return 'UndefinedExpression()'
class RegexExpression(Expression):
"""
represents the regular expression literal.
"""
def __init__(self, value, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.value = value
def get_items(self):
return [self.value]
def __repr__(self):
return 'RegexExpression(%r)' % (self.value,)
class NameExpression(Expression):
"""
any name such as {{ foo }}
"""
def __init__(self, name, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.name = name
def get_items(self):
return [self.name]
def allows_assignments(self):
return self.name != '_'
def __repr__(self):
return 'NameExpression(%r)' % self.name
class ListExpression(Expression):
"""
any list literal such as {{ [1, 2, 3] }}
"""
def __init__(self, items, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.items = items
def get_items(self):
return list(self.items)
def __repr__(self):
return 'ListExpression(%r)' % (self.items,)
class DictExpression(Expression):
"""
any dict literal such as {{ {1: 2, 3: 4} }}
"""
def __init__(self, items, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.items = items
def get_items(self):
return list(chain(*self.items))
def __repr__(self):
return 'DictExpression(%r)' % (self.items,)
class SetExpression(Expression):
"""
any set literal such as {{ @(1, 2, 3) }}
"""
def __init__(self, items, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.items = items
def get_items(self):
return self.items[:]
def __repr__(self):
return 'SetExpression(%r)' % (self.items,)
class ConditionalExpression(Expression):
"""
{{ foo if bar else baz }}
"""
def __init__(self, test, expr1, expr2, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.test = test
self.expr1 = expr1
self.expr2 = expr2
def get_items(self):
return [self.test, self.expr1, self.expr2]
def __repr__(self):
return 'ConstantExpression(%r, %r, %r)' % (
self.test,
self.expr1,
self.expr2
)
class FilterExpression(Expression):
"""
{{ foo|bar|baz }}
"""
def __init__(self, node, filters, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.node = node
self.filters = filters
def get_items(self):
result = [self.node]
for filter, args in self.filters:
result.append(filter)
result.extend(args)
return result
def __repr__(self):
return 'FilterExpression(%r, %r)' % (
self.node,
self.filters
)
class TestExpression(Expression):
"""
{{ foo is lower }}
"""
def __init__(self, node, name, args, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.node = node
self.name = name
self.args = args
def get_items(self):
return [self.node, self.name] + list(self.args)
def __repr__(self):
return 'TestExpression(%r, %r, %r)' % (
self.node,
self.name,
self.args
)
class CallExpression(Expression):
"""
{{ foo(bar) }}
"""
def __init__(self, node, args, kwargs, dyn_args, dyn_kwargs,
lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.node = node
self.args = args
self.kwargs = kwargs
self.dyn_args = dyn_args
self.dyn_kwargs = dyn_kwargs
def get_items(self):
return [self.node, self.args, self.kwargs, self.dyn_args,
self.dyn_kwargs]
def __repr__(self):
return 'CallExpression(%r, %r, %r, %r, %r)' % (
self.node,
self.args,
self.kwargs,
self.dyn_args,
self.dyn_kwargs
)
class SubscriptExpression(Expression):
"""
{{ foo.bar }} and {{ foo['bar'] }} etc.
"""
def __init__(self, node, arg, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.node = node
self.arg = arg
def get_items(self):
return [self.node, self.arg]
def __repr__(self):
return 'SubscriptExpression(%r, %r)' % (
self.node,
self.arg
)
class SliceExpression(Expression):
"""
1:2:3 etc.
"""
def __init__(self, start, stop, step, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.start = start
self.stop = stop
self.step = step
def get_items(self):
return [self.start, self.stop, self.step]
def __repr__(self):
return 'SliceExpression(%r, %r, %r)' % (
self.start,
self.stop,
self.step
)
class TupleExpression(Expression):
"""
For loop unpacking and some other things like multiple arguments
for subscripts.
"""
def __init__(self, items, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.items = items
def get_items(self):
return list(self.items)
def allows_assignments(self):
for item in self.items:
if not item.allows_assignments():
return False
return True
def __repr__(self):
return 'TupleExpression(%r)' % (self.items,)
class ConcatExpression(Expression):
"""
For {{ foo ~ bar }}. Because of various reasons (especially because
unicode conversion takes place for the left and right expression and
is better optimized that way)
"""
def __init__(self, args, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.args = args
def get_items(self):
return list(self.args)
def __repr__(self):
return 'ConcatExpression(%r)' % (self.items,)
class CompareExpression(Expression):
"""
{{ foo == bar }}, {{ foo >= bar }} etc.
"""
def __init__(self, expr, ops, lineno=None, filename=None):
Expression.__init__(self, lineno, filename)
self.expr = expr
self.ops = ops
def get_items(self):
return [self.expr] + list(chain(*self.ops))
def __repr__(self):
return 'CompareExpression(%r, %r)' % (
self.expr,
self.ops
)
class MulExpression(BinaryExpression):
"""
{{ foo * bar }}
"""
class DivExpression(BinaryExpression):
"""
{{ foo / bar }}
"""
class FloorDivExpression(BinaryExpression):
"""
{{ foo // bar }}
"""
class AddExpression(BinaryExpression):
"""
{{ foo + bar }}
"""
class SubExpression(BinaryExpression):
"""
{{ foo - bar }}
"""
class ModExpression(BinaryExpression):
"""
{{ foo % bar }}
"""
class PowExpression(BinaryExpression):
"""
{{ foo ** bar }}
"""
class AndExpression(BinaryExpression):
"""
{{ foo and bar }}
"""
class OrExpression(BinaryExpression):
"""
{{ foo or bar }}
"""
class NotExpression(UnaryExpression):
"""
{{ not foo }}
"""
class NegExpression(UnaryExpression):
"""
{{ -foo }}
"""
class PosExpression(UnaryExpression):
"""
{{ +foo }}
"""

File diff suppressed because it is too large Load diff

View file

@ -1,166 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.plugin
~~~~~~~~~~~~
Support for the `GeneralTemplateInterface`__ and the Buffet interface.
Do not use this module on your own. We don't recommend those interfaces!
If you are able to, you should really use Jinja without those abstraction
layers.
__ http://trac.pocoo.org/wiki/GeneralTemplateInterface
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from warnings import warn
from jinja.environment import Environment
from jinja.loaders import FunctionLoader, FileSystemLoader, PackageLoader
from jinja.exceptions import TemplateNotFound
class BuffetPlugin(object):
"""
Implements the Jinja buffet plugin. Well. It works for pylons and should
work for TurboGears too if their plugin system would work.
"""
def __init__(self, extra_vars_func=None, options=None):
if 'jinja.environment' in options:
self.env = options['jinja.environment']
else:
opt = {}
for key, value in options.iteritems():
if key.startswith('jinja.') and key != 'jinja.extension':
opt[key[6:]] = value
loader_func = opt.pop('loader_func', None)
getmtime_func = opt.pop('getmtime_func', None)
use_memcache = opt.pop('use_memcache', False)
memcache_size = opt.pop('memcache_size', 40)
cache_folder = opt.pop('cache_folder', None)
auto_reload = opt.pop('auto_reload', True)
if 'searchpath' in opt:
opt['loader'] = FileSystemLoader(opt.pop('searchpath'),
use_memcache, memcache_size,
cache_folder, auto_reload)
elif 'package' in opt:
opt['loader'] = PackageLoader(opt.pop('package'),
opt.pop('package_path', ''),
use_memcache, memcache_size,
cache_folder, auto_reload)
elif loader_func is not None:
opt['loader'] = FunctionLoader(loader_func, getmtime_func,
use_memcache, memcache_size,
cache_folder, auto_reload)
self.env = Environment(**opt)
self.extra_vars_func = extra_vars_func
self.extension = options.pop('jinja.extension', 'html')
def load_template(self, templatename, template_string=None):
if template_string is not None:
return self.env.from_string(template_string)
if templatename.startswith('!'):
jinja_name = templatename[1:]
else:
jinja_name = templatename.replace('.', '/') + '.' + self.extension
return self.env.get_template(jinja_name)
def render(self, info, format='html', fragment=False, template=None):
if isinstance(template, basestring):
template = self.load_template(template)
if self.extra_vars_func:
info.update(self.extra_vars_func())
return template.render(info)
def jinja_plugin_factory(options):
"""
Basic implementation of the `GeneralTemplateInterface`.
Supports ``loader_func`` and ``getmtime_func``, as well as
string and file loading but ignores ``mode`` since it's a
text based template engine.
All options passed to this function are forwarded to the
jinja environment. Exceptions are the following keys:
=================== =================================================
``environment`` If this is provided it must be the only
configuration value and it's used as jinja
environment.
``searchpath`` If provided a new file system loader with this
search path is instanciated.
``package`` Name of the python package containing the
templates. If this and ``package_path`` is
defined a `PackageLoader` is used.
``package_path`` Path to the templates inside of a package.
``loader_func`` Function that takes the name of the template to
load. If it returns a string or unicode object
it's used to load a template. If the return
value is None it's considered missing.
``getmtime_func`` Function used to check if templates requires
reloading. Has to return the UNIX timestamp of
the last template change or 0 if this template
does not exist or requires updates at any cost.
``use_memcache`` Set this to ``True`` to enable memory caching.
This is usually a good idea in production mode,
but disable it during development since it won't
reload template changes automatically.
This only works in persistent environments like
FastCGI.
``memcache_size`` Number of template instance you want to cache.
Defaults to ``40``.
``cache_folder`` Set this to an existing directory to enable
caching of templates on the file system. Note
that this only affects templates transformed
into python code. Default is ``None`` which means
that caching is disabled.
``auto_reload`` Set this to `False` for a slightly better
performance. In that case of `getmtime_func`
not being provided this won't have an effect.
=================== =================================================
"""
warn(DeprecationWarning('general plugin interface implementation '
'deprecated because not an accepted '
'standard.'))
if 'environment' in options:
env = options['environment']
if not len(options) == 1:
raise TypeError('if environment provided no other '
'arguments are allowed')
else:
loader_func = options.pop('loader_func', None)
getmtime_func = options.pop('getmtime_func', None)
use_memcache = options.pop('use_memcache', False)
memcache_size = options.pop('memcache_size', 40)
cache_folder = options.pop('cache_folder', None)
auto_reload = options.pop('auto_reload', True)
if 'searchpath' in options:
options['loader'] = FileSystemLoader(options.pop('searchpath'),
use_memcache, memcache_size,
cache_folder, auto_reload)
elif 'package' in options:
options['loader'] = PackageLoader(options.pop('package'),
options.pop('package_path', ''),
use_memcache, memcache_size,
cache_folder, auto_reload)
elif loader_func is not None:
options['loader'] = FunctionLoader(loader_func, getmtime_func,
use_memcache, memcache_size,
cache_folder, auto_reload)
env = Environment(**options)
def render_function(template, values, options):
if options.get('is_string'):
tmpl = env.from_string(template)
else:
try:
tmpl = env.get_template(template)
except TemplateNotFound:
return
return tmpl.render(**values)
return render_function

View file

@ -1,143 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.tests
~~~~~~~~~~~
Jinja test functions. Used with the "is" operator.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import re
number_re = re.compile(r'^-?\d+(\.\d+)?$')
regex_type = type(number_re)
def test_odd():
"""
Return true if the variable is odd.
"""
return lambda e, c, v: v % 2 == 1
def test_even():
"""
Return true of the variable is even.
"""
return lambda e, c, v: v % 2 == 0
def test_defined():
"""
Return true if the variable is defined:
.. sourcecode:: jinja
{% if variable is defined %}
value of variable: {{ variable }}
{% else %}
variable is not defined
{% endif %}
See also the ``default`` filter.
"""
return lambda e, c, v: v is not e.undefined_singleton
def test_lower():
"""
Return true if the variable is lowercase.
"""
return lambda e, c, v: isinstance(v, basestring) and v.islower()
def test_upper():
"""
Return true if the variable is uppercase.
"""
return lambda e, c, v: isinstance(v, basestring) and v.isupper()
def test_numeric():
"""
Return true if the variable is numeric.
"""
return lambda e, c, v: isinstance(v, (int, long, float)) or (
isinstance(v, basestring) and
number_re.match(v) is not None)
def test_sequence():
"""
Return true if the variable is a sequence. Sequences are variables
that are iterable.
"""
def wrapped(environment, context, value):
try:
len(value)
value.__getitem__
except:
return False
return True
return wrapped
def test_matching(regex):
r"""
Test if the variable matches the regular expression given. Note that
you have to escape special chars using *two* backslashes, these are
*not* raw strings.
.. sourcecode:: jinja
{% if var is matching @/^\d+$/ %}
var looks like a number
{% else %}
var doesn't really look like a number
{% endif %}
"""
def wrapped(environment, context, value):
if type(regex) is regex_type:
regex_ = regex
else:
if environment.disable_regexps:
raise RuntimeError('regular expressions disabled.')
if isinstance(regex, unicode):
regex_ = re.compile(regex, re.U)
elif isinstance(regex, str):
regex_ = re.compile(regex)
else:
return False
return regex_.search(value) is not None
return wrapped
def test_sameas(other):
"""
Check if an object points to the same memory address than another
object:
.. sourcecode:: jinja
{% if foo.attribute is sameas(false) %}
the foo attribute really is the `False` singleton
{% endif %}
*New in Jinja 1.2*
"""
return lambda e, c, v: v is other
TESTS = {
'odd': test_odd,
'even': test_even,
'defined': test_defined,
'lower': test_lower,
'upper': test_upper,
'numeric': test_numeric,
'sequence': test_sequence,
'matching': test_matching,
'sameas': test_sameas
}

View file

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.translators
~~~~~~~~~~~~~~~~~
The submodules of this module provide translators for the jinja ast.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
class Translator(object):
"""
Base class of all translators.
"""
def process(environment, tree, source=None):
"""
Process the given ast with the rules defined in
environment and return a translated version of it.
The translated object can be anything. The python
translator for example outputs Template instances,
a javascript translator would probably output strings.
This is a static function.
"""
pass
process = staticmethod(process)

File diff suppressed because it is too large Load diff

View file

@ -1,644 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja.utils
~~~~~~~~~~~
Utility functions.
**license information**: some of the regular expressions and
the ``urlize`` function were taken from the django framework.
:copyright: 2007 by Armin Ronacher, Lawrence Journal-World.
:license: BSD, see LICENSE for more details.
"""
import re
import sys
import string
from types import MethodType, FunctionType
from jinja import nodes
from jinja.exceptions import SecurityException, TemplateNotFound
from jinja.datastructure import TemplateData
# the python2.4 version of deque is missing the remove method
# because a for loop with a lookup for the missing value written
# in python is slower we just use deque if we have python2.5 or higher
try:
from collections import deque
deque.remove
except (ImportError, AttributeError):
class deque(list):
"""
Minimal subclass of list that provides the deque
interface used by the native `BaseContext` and the
`CacheDict`
"""
def appendleft(self, item):
list.insert(self, 0, item)
def popleft(self):
return list.pop(self, 0)
def clear(self):
del self[:]
# support for a working reversed() in 2.3
try:
reversed = reversed
except NameError:
def reversed(iterable):
if hasattr(iterable, '__reversed__'):
return iterable.__reversed__()
try:
return iter(iterable[::-1])
except TypeError:
return iter(tuple(iterable)[::-1])
# set support for python 2.3
try:
set = set
except NameError:
from sets import Set as set
# sorted support (just a simplified version)
try:
sorted = sorted
except NameError:
_cmp = cmp
def sorted(seq, cmp=None, key=None, reverse=False):
rv = list(seq)
if key is not None:
cmp = lambda a, b: _cmp(key(a), key(b))
rv.sort(cmp)
if reverse:
rv.reverse()
return rv
# group by support
try:
from itertools import groupby
except ImportError:
class groupby(object):
def __init__(self, iterable, key=lambda x: x):
self.keyfunc = key
self.it = iter(iterable)
self.tgtkey = self.currkey = self.currvalue = xrange(0)
def __iter__(self):
return self
def next(self):
while self.currkey == self.tgtkey:
self.currvalue = self.it.next()
self.currkey = self.keyfunc(self.currvalue)
self.tgtkey = self.currkey
return (self.currkey, self._grouper(self.tgtkey))
def _grouper(self, tgtkey):
while self.currkey == tgtkey:
yield self.currvalue
self.currvalue = self.it.next()
self.currkey = self.keyfunc(self.currvalue)
#: function types
callable_types = (FunctionType, MethodType)
#: number of maximal range items
MAX_RANGE = 1000000
_word_split_re = re.compile(r'(\s+)')
_punctuation_re = re.compile(
'^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
'|'.join([re.escape(p) for p in ('(', '<', '&lt;')]),
'|'.join([re.escape(p) for p in ('.', ',', ')', '>', '\n', '&gt;')])
)
)
_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
#: used by from_string as cache
_from_string_env = None
def escape(s, quote=None):
"""
SGML/XML escape an unicode object.
"""
s = s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
if not quote:
return s
return s.replace('"', "&quot;")
def urlize(text, trim_url_limit=None, nofollow=False):
"""
Converts any URLs in text into clickable links. Works on http://,
https:// and www. links. Links can have trailing punctuation (periods,
commas, close-parens) and leading punctuation (opening parens) and
it'll still do the right thing.
If trim_url_limit is not None, the URLs in link text will be limited
to trim_url_limit characters.
If nofollow is True, the URLs in link text will get a rel="nofollow"
attribute.
"""
trim_url = lambda x, limit=trim_url_limit: limit is not None \
and (x[:limit] + (len(x) >=limit and '...'
or '')) or x
words = _word_split_re.split(text)
nofollow_attr = nofollow and ' rel="nofollow"' or ''
for i, word in enumerate(words):
match = _punctuation_re.match(word)
if match:
lead, middle, trail = match.groups()
if middle.startswith('www.') or (
'@' not in middle and
not middle.startswith('http://') and
len(middle) > 0 and
middle[0] in string.letters + string.digits and (
middle.endswith('.org') or
middle.endswith('.net') or
middle.endswith('.com')
)):
middle = '<a href="http://%s"%s>%s</a>' % (middle,
nofollow_attr, trim_url(middle))
if middle.startswith('http://') or \
middle.startswith('https://'):
middle = '<a href="%s"%s>%s</a>' % (middle,
nofollow_attr, trim_url(middle))
if '@' in middle and not middle.startswith('www.') and \
not ':' in middle and _simple_email_re.match(middle):
middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
if lead + middle + trail != word:
words[i] = lead + middle + trail
return u''.join(words)
def from_string(source):
"""
Create a template from the template source.
"""
global _from_string_env
if _from_string_env is None:
from jinja.environment import Environment
_from_string_env = Environment()
return _from_string_env.from_string(source)
#: minor speedup
_getattr = getattr
def get_attribute(obj, name):
"""
Return the attribute from name. Raise either `AttributeError`
or `SecurityException` if something goes wrong.
"""
if not isinstance(name, basestring):
raise AttributeError(name)
if name[:2] == name[-2:] == '__':
raise SecurityException('not allowed to access internal attributes')
if getattr(obj, '__class__', None) in callable_types and \
name.startswith('func_') or name.startswith('im_'):
raise SecurityException('not allowed to access function attributes')
r = _getattr(obj, 'jinja_allowed_attributes', None)
if r is not None and name not in r:
raise SecurityException('disallowed attribute accessed')
# attribute lookups convert unicode strings to ascii bytestrings.
# this process could raise an UnicodeEncodeError.
try:
return _getattr(obj, name)
except UnicodeError:
raise AttributeError(name)
def safe_range(start, stop=None, step=None):
"""
"Safe" form of range that does not generate too large lists.
"""
if step is None:
step = 1
if stop is None:
r = xrange(0, start, step)
else:
r = xrange(start, stop, step)
if len(r) > MAX_RANGE:
def limit():
i = 0
for item in r:
i += 1
yield item
if i >= MAX_RANGE:
break
return list(limit())
return list(r)
def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
"""
Generate some lorem impsum for the template.
"""
from jinja.constants import LOREM_IPSUM_WORDS
from random import choice, random, randrange
words = LOREM_IPSUM_WORDS.split()
result = []
for _ in xrange(n):
next_capitalized = True
last_comma = last_fullstop = 0
word = None
last = None
p = []
# each paragraph contains out of 20 to 100 words.
for idx, _ in enumerate(xrange(randrange(min, max))):
while True:
word = choice(words)
if word != last:
last = word
break
if next_capitalized:
word = word.capitalize()
next_capitalized = False
# add commas
if idx - randrange(3, 8) > last_comma:
last_comma = idx
last_fullstop += 2
word += ','
# add end of sentences
if idx - randrange(10, 20) > last_fullstop:
last_comma = last_fullstop = idx
word += '.'
next_capitalized = True
p.append(word)
# ensure that the paragraph ends with a dot.
p = u' '.join(p)
if p.endswith(','):
p = p[:-1] + '.'
elif not p.endswith('.'):
p += '.'
result.append(p)
if not html:
return u'\n\n'.join(result)
return u'\n'.join([u'<p>%s</p>' % escape(x) for x in result])
def watch_changes(env, context, iterable, *attributes):
"""
Wise replacement for ``{% ifchanged %}``.
"""
# find the attributes to watch
if attributes:
tests = []
tmp = []
for attribute in attributes:
if isinstance(attribute, (str, unicode, int, long, bool)):
tmp.append(attribute)
else:
tests.append(tuple(attribute))
if tmp:
tests.append(tuple(attribute))
last = tuple([object() for x in tests])
# or no attributes if we watch the object itself
else:
tests = None
last = object()
# iterate trough it and keep check the attributes or values
for item in iterable:
if tests is None:
cur = item
else:
cur = tuple([env.get_attributes(item, x) for x in tests])
if cur != last:
changed = True
last = cur
else:
changed = False
yield changed, item
watch_changes.jinja_context_callable = True
def render_included(env, context, template_name):
"""
Works like djangos {% include %} tag. It doesn't include the
template but load it independently and renders it to a string.
"""
#XXX: ignores parent completely!
tmpl = env.get_template(template_name)
return tmpl.render(context.to_dict())
render_included.jinja_context_callable = True
# python2.4 and lower has a bug regarding joining of broken generators.
# because of the runtime debugging system we have to keep track of the
# number of frames to skip. that's what RUNTIME_EXCEPTION_OFFSET is for.
try:
_test_singleton = object()
def _test_gen_bug():
raise TypeError(_test_singleton)
yield None
''.join(_test_gen_bug())
except TypeError, e:
if e.args and e.args[0] is _test_singleton:
capture_generator = u''.join
RUNTIME_EXCEPTION_OFFSET = 1
else:
capture_generator = lambda gen: u''.join(tuple(gen))
RUNTIME_EXCEPTION_OFFSET = 2
del _test_singleton, _test_gen_bug
def pformat(obj, verbose=False):
"""
Prettyprint an object. Either use the `pretty` library or the
builtin `pprint`.
"""
try:
from pretty import pretty
return pretty(obj, verbose=verbose)
except ImportError:
from pprint import pformat
return pformat(obj)
def buffereater(f, template_data=False):
"""
Used by the python translator to capture output of substreams.
(macros, filter sections etc)
"""
def wrapped(*a, **kw):
__traceback_hide__ = True
rv = capture_generator(f(*a, **kw))
if template_data:
rv = TemplateData(rv)
return rv
return wrapped
def empty_block(context):
"""
An empty callable that just returns an empty decorator.
Used to represent empty blocks.
"""
if 0: yield None
def collect_translations(ast):
"""
Collect all translatable strings for the given ast. The
return value is a list of tuples in the form ``(lineno, singular,
plural)``. If a translation doesn't require a plural form the
third item is `None`.
"""
todo = [ast]
result = []
while todo:
node = todo.pop()
if node.__class__ is nodes.Trans:
result.append((node.lineno, node.singular, node.plural))
elif node.__class__ is nodes.CallExpression and \
node.node.__class__ is nodes.NameExpression and \
node.node.name == '_':
if len(node.args) == 1 and not node.kwargs and not node.dyn_args \
and not node.dyn_kwargs and \
node.args[0].__class__ is nodes.ConstantExpression:
result.append((node.lineno, node.args[0].value, None))
todo.extend(node.get_child_nodes())
result.sort(lambda a, b: cmp(a[0], b[0]))
return result
class DebugHelper(object):
"""
Debugging Helper. Available in the template as "debug".
"""
jinja_context_callable = True
jinja_allowed_attributes = ['filters']
def __init__(self):
raise TypeError('cannot create %r instances' %
self.__class__.__name__)
def __call__(self, env, context):
"""Print a nice representation of the context."""
return pformat(context.to_dict(), verbose=True)
def filters(self, env, context, builtins=True):
"""List the filters."""
from inspect import getdoc
strip = set()
if not builtins:
from jinja.defaults import DEFAULT_FILTERS
strip = set(DEFAULT_FILTERS.values())
filters = env.filters.items()
filters.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
result = []
for name, f in filters:
if f in strip:
continue
doc = '\n'.join([' ' + x for x in (getdoc(f) or '').splitlines()])
result.append('`%s`\n\n%s' % (name, doc))
return '\n\n'.join(result)
filters.jinja_context_callable = True
def tests(self, env, context, builtins=True):
"""List the tests."""
from inspect import getdoc
strip = set()
if not builtins:
from jinja.defaults import DEFAULT_TESTS
strip = set(DEFAULT_TESTS.values())
tests = env.tests.items()
tests.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
result = []
for name, f in tests:
if f in strip:
continue
doc = '\n'.join([' ' + x for x in (getdoc(f) or '').splitlines()])
result.append('`%s`\n\n%s' % (name, doc))
return '\n\n'.join(result)
tests.jinja_context_callable = True
def __str__(self):
print 'use debug() for debugging the context'
#: the singleton instance of `DebugHelper`
debug_helper = object.__new__(DebugHelper)
class CacheDict(object):
"""
A dict like object that stores a limited number of items and forgets
about the least recently used items::
>>> cache = CacheDict(3)
>>> cache['A'] = 0
>>> cache['B'] = 1
>>> cache['C'] = 2
>>> len(cache)
3
If we now access 'A' again it has a higher priority than B::
>>> cache['A']
0
If we add a new item 'D' now 'B' will disappear::
>>> cache['D'] = 3
>>> len(cache)
3
>>> 'B' in cache
False
If you iterate over the object the most recently used item will be
yielded First::
>>> for item in cache:
... print item
D
A
C
If you want to iterate the other way round use ``reverse(cache)``.
Implementation note: This is not a nice way to solve that problem but
for smaller capacities it's faster than a linked list.
Perfect for template environments where you don't expect too many
different keys.
"""
def __init__(self, capacity):
self.capacity = capacity
self._mapping = {}
self._queue = deque()
# alias all queue methods for faster lookup
self._popleft = self._queue.popleft
self._pop = self._queue.pop
self._remove = self._queue.remove
self._append = self._queue.append
def copy(self):
"""
Return an shallow copy of the instance.
"""
rv = CacheDict(self.capacity)
rv._mapping.update(self._mapping)
rv._queue = self._queue[:]
return rv
def get(self, key, default=None):
"""
Return an item from the cache dict or `default`
"""
if key in self:
return self[key]
return default
def setdefault(self, key, default=None):
"""
Set `default` if the key is not in the cache otherwise
leave unchanged. Return the value of this key.
"""
if key in self:
return self[key]
self[key] = default
return default
def clear(self):
"""
Clear the cache dict.
"""
self._mapping.clear()
self._queue.clear()
def __contains__(self, key):
"""
Check if a key exists in this cache dict.
"""
return key in self._mapping
def __len__(self):
"""
Return the current size of the cache dict.
"""
return len(self._mapping)
def __repr__(self):
return '<%s %r>' % (
self.__class__.__name__,
self._mapping
)
def __getitem__(self, key):
"""
Get an item from the cache dict. Moves the item up so that
it has the highest priority then.
Raise an `KeyError` if it does not exist.
"""
rv = self._mapping[key]
if self._queue[-1] != key:
self._remove(key)
self._append(key)
return rv
def __setitem__(self, key, value):
"""
Sets the value for an item. Moves the item up so that it
has the highest priority then.
"""
if key in self._mapping:
self._remove(key)
elif len(self._mapping) == self.capacity:
del self._mapping[self._popleft()]
self._append(key)
self._mapping[key] = value
def __delitem__(self, key):
"""
Remove an item from the cache dict.
Raise an `KeyError` if it does not exist.
"""
del self._mapping[key]
self._remove(key)
def __iter__(self):
"""
Iterate over all values in the cache dict, ordered by
the most recent usage.
"""
return reversed(self._queue)
def __reversed__(self):
"""
Iterate over the values in the cache dict, oldest items
coming first.
"""
return iter(self._queue)
__copy__ = copy
def __deepcopy__(self):
"""
Return a deep copy of the cache dict.
"""
from copy import deepcopy
rv = CacheDict(self.capacity)
rv._mapping = deepcopy(self._mapping)
rv._queue = deepcopy(self._queue)
return rv
NAMESPACE = {
'range': safe_range,
'debug': debug_helper,
'lipsum': generate_lorem_ipsum,
'watchchanges': watch_changes,
'rendertemplate': render_included
}

View file

@ -1,216 +0,0 @@
"""oohEmbed
Your one-stop oEmbed provider
See http://code.google.com/p/oohembed/
and http://oohembed.com/
Copyright (c) 2008-2009, Deepak Sarda
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the oohEmbed project nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import wsgiref.handlers
import datetime
import logging
import os
import urllib
import email.utils
import time
from google.appengine.ext import webapp
from google.appengine.api import memcache
from jinja import Environment, FileSystemLoader
from provider import *
HTTP_DATE_FMT = "%a, %d %b %Y %H:%M:%S GMT"
CACHE_SEP = "=+=+="
CACHE_TIME = 2*24*60*60
class EndPoint(webapp.RequestHandler):
providers = Provider.get_providers()
def get(self):
query_url = urllib.unquote(self.request.get('url').encode('utf-8')).strip().rstrip("#")
resp_format = self.request.get('format', default_value='json')
logging.debug('Incoming request for %s' % query_url)
if 'Development' in os.environ['SERVER_SOFTWARE']:
self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
else:
self.response.headers['Content-Type'] = 'application/json'
if not query_url:
self.error(400)
self.response.out.write('Missing url parameter in request')
return
if not resp_format in ['json', 'jsonp']:
self.error(501)
self.response.out.write('Only json format is currently supported')
return
callback = self.request.get('callback').encode('utf-8')
# TODO: Make sure callback name here is a valid Javascript
# variable name using a whitelist. Otherwise raise 400
extra_params = {}
if self.request.get('maxwidth'):
extra_params['maxwidth'] = self.request.get('maxwidth').encode('utf-8')
if self.request.get('maxheight'):
extra_params['maxheight'] = self.request.get('maxheight').encode('utf-8')
# Check memcache
cached_item = memcache.get(make_key(query_url, extra_params))
if cached_item:
logging.debug('Cache hit for url %s' % query_url)
resp, timestamp = cached_item.split(CACHE_SEP)
self.send_response(resp, last_modified=timestamp, callback=callback)
return
resp = None
for p in self.providers:
try:
resp = p.provide(query_url, extra_params)
if resp:
# Save to memcache first
timestamp = datetime.datetime.now().strftime(HTTP_DATE_FMT)
if not memcache.set(make_key(query_url, extra_params), resp + CACHE_SEP + timestamp, time=CACHE_TIME):
logging.error('Failed saving cache for url %s' % query_url)
else:
logging.debug('Saved url response to cache for url %s' % query_url)
self.send_response(resp, last_modified=timestamp, callback=callback)
return
except UnsupportedUrlError, e:
pass
except HTTPError, e:
self.error(e.code)
if e.content:
self.response.out.write(e.content)
else:
self.response.out.write("Encountered HTTP Error %s fetching " +
"url from the remote host" % e.code)
return
except OohEmbedError, e:
logging.error("Throwing OohEmbed error", exc_info=True)
self.error(500)
return self.response.out.write(e.reason)
except Exception, e:
logging.error("Throwing 500 error", exc_info=True)
self.error(500)
return self.response.out.write("Unrecoverable error. Please try again." +
" If the error persists, please file a bug at http://oohembed.googlecode.com/")
self.error(404)
self.response.out.write('Could not determine suitable ' +
'representation for queried URL')
return
def send_response(self, resp, last_modified=None, callback=None):
if 'Development' not in os.environ['SERVER_SOFTWARE']:
self.response.headers['Expires'] = email.utils.formatdate(time.time() + CACHE_TIME, usegmt=True)
self.response.headers['Cache-Control'] = 'max-age=%d' % int(CACHE_TIME)
if last_modified:
if last_modified == self.request.headers.get('If-Modified-Since', '-1'):
self.error(304)
return
else:
self.response.headers['Last-Modified'] = last_modified.encode('utf-8')
if callback:
self.response.headers['Content-Type'] = 'text/javascript'
resp = '%s(%s);' % (callback, resp)
self.response.out.write(resp)
class AdminEndPoint(webapp.RequestHandler):
def get(self):
if self.request.get('flushcache'):
result = memcache.flush_all()
response = 'memcache flush ' + (result and 'succeeded' or 'failed')
return self.response.out.write(response)
elif self.request.get('infocache'):
result = memcache.get_stats()
if result:
body = '<br/>'.join(('<b>%s</b>: %s\n' % t for t in result.iteritems()))
else:
body = 'failed to retrieve memcache stats'
response = '<html><title>Memcache Stats</title><body>' + body
return self.response.out.write(response)
else:
return self.response.out.write('What function did you want? "infocache" or "flushcache"?')
class MainPage(webapp.RequestHandler):
providers = Provider.get_providers()
def get(self):
providers = [{'title': p.title, 'url': p.url, \
'example_url': p.example_url, \
'description': p.__doc__} \
for p in self.providers]
providers.sort(lambda x, y: cmp(x['title'].lower(), y['title'].lower()))
if 'Development' in os.environ['SERVER_SOFTWARE']:
production = False
else:
production = True
env = Environment(loader=FileSystemLoader(os.path.dirname(__file__)))
tmpl = env.get_template('index.jinja')
hostname = os.environ['HTTP_HOST'].lower()
self.response.out.write(tmpl.render(providers=providers,
production=production, hostname=hostname))
urls = [('/', MainPage),
('/oohembed\/?', EndPoint),
('/admin/', AdminEndPoint)
]
def main():
if 'Development' in os.environ['SERVER_SOFTWARE']:
logging.getLogger().setLevel(logging.DEBUG)
else:
logging.getLogger().setLevel(logging.INFO)
application = webapp.WSGIApplication(urls, debug=True)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load diff

View file

@ -1,8 +0,0 @@
from base import Provider
from utils import OohEmbedError, UnsupportedUrlError, HTTPError, make_key
import photoprovider
import videoprovider
import linkprovider
import oembedprovider
__all__ = ["Provider", "OohEmbedError", "UnsupportedUrlError", "HTTPError", "make_key"]

View file

@ -1,35 +0,0 @@
import re
from google.appengine.api import urlfetch
"""Plugin infrastructure based on Marty Alchin's post at
http://gulopine.gamemusic.org/2008/jan/10/simple-plugin-framework/
"""
class ProviderMount(type):
def __init__(cls, name, bases, attrs):
if not hasattr(cls, 'plugins'):
cls.plugins = []
else:
cls.plugins.append(cls)
def get_providers(self, *args, **kwargs):
return [p(*args, **kwargs) for p in self.plugins]
class Provider(object):
"""
Mount point for plugins which refer to actions that can be performed.
Plugins implementing this reference should provide the following attributes:
======== ========================================================
title the site/api for which this provider works
url friendly url description - ombed.com's configuration URL scheme
url_re the regex pattern for the URLs which the provider works for
example_url An exemplary URL that this provider should be able to work with
======== ========================================================
With the provided url_re, this class' constructor will create a
class attribute named `url_regex`.
"""
__metaclass__ = ProviderMount
def __init__(self):
self.__class__.url_regex = re.compile(self.__class__.url_re, re.I|re.UNICODE)

View file

@ -1,114 +0,0 @@
[
{
"url": "http://*.blip.tv/*",
"url_re": "blip\\.tv/.+",
"example_url": "http://pycon.blip.tv/file/2058801/",
"endpoint_url": "http://blip.tv/oembed/",
"title": "blip.tv"
},
{
"url": "http://*.dailymotion.com/*",
"url_re": "dailymotion\\.com/.+",
"example_url": "http://www.dailymotion.com/video/x5ioet_phoenix-mars-lander_tech",
"endpoint_url": "http://www.dailymotion.com/api/oembed/",
"title": "Dailymotion"
},
{
"url": "http://*.flickr.com/photos/*",
"url_re": "flickr\\.com/photos/[-.\\w@]+/\\d+/?",
"example_url": "http://www.flickr.com/photos/fuffer2005/2435339994/",
"endpoint_url": "http://www.flickr.com/services/oembed/",
"title": "Flickr Photos"
},
{
"url": "http://www.hulu.com/watch/*",
"url_re": "hulu\\.com/watch/.*",
"example_url": "http://www.hulu.com/watch/20807/late-night-with-conan",
"endpoint_url": "http://www.hulu.com/api/oembed.json",
"title": "Hulu"
},
{
"url": "http://*.nfb.ca/film/*",
"url_re": "nfb\\.ca/film/[-\\w]+/?",
"example_url": "http://www.nfb.ca/film/blackfly/",
"endpoint_url": "http://www.nfb.ca/remote/services/oembed/",
"title": "National Film Board of Canada"
},
{
"url": "http://qik.com/*",
"url_re": "qik\\.com/\\w+",
"example_url": "http://qik.com/video/86776",
"endpoint_url": "http://qik.com/api/oembed.json",
"title": "Qik Video"
},
{
"url": "http://*.revision3.com/*",
"url_re": "revision3\\.com/.+",
"example_url": "http://revision3.com/diggnation/2008-04-17xsanned/",
"endpoint_url": "http://revision3.com/api/oembed/",
"title": "Revision3"
},
{
"url": "http://*.scribd.com/*",
"url_re": "scribd\\.com/.+",
"example_url": "http://www.scribd.com/doc/17896323/Indian-Automobile-industryPEST",
"endpoint_url": "http://www.scribd.com/services/oembed",
"title": "Scribd"
},
{
"url": "http://*.viddler.com/explore/*",
"url_re": "viddler\\.com/explore/.*/videos/\\w+/?",
"example_url": "http://www.viddler.com/explore/engadget/videos/14/",
"endpoint_url": "http://lab.viddler.com/services/oembed/",
"title": "Viddler Video"
},
{
"url": "http://www.vimeo.com/* and http://www.vimeo.com/groups/*/videos/*",
"url_re": "vimeo\\.com/.*",
"example_url": "http://www.vimeo.com/1211060",
"endpoint_url": "http://www.vimeo.com/api/oembed.json",
"title": "Vimeo"
},
{
"url": "http://*.youtube.com/watch*",
"url_re": "youtube\\.com/watch.+v=[\\w-]+&?",
"example_url": "http://www.youtube.com/watch?v=vk1HvP7NO5w",
"endpoint_url": "http://www.youtube.com/oembed",
"title": "YouTube"
},
{
"url": "http://dotsub.com/view/*",
"url_re": "dotsub\\.com/view/[-\\da-zA-Z]+$",
"example_url": "http://dotsub.com/view/10e3cb5e-96c7-4cfb-bcea-8ab11e04e090",
"endpoint_url": "http://dotsub.com/services/oembed",
"title": "dotSUB.com"
},
{
"url": "http://yfrog.(com|ru|com.tr|it|fr|co.il|co.uk|com.pl|pl|eu|us)/*",
"url_re": "yfrog\\.(com|ru|com\\.tr|it|fr|co\\.il|co\\.uk|com\\.pl|pl|eu|us)/[a-zA-Z0-9]+$",
"example_url": "http://yfrog.com/0wgvcpj",
"endpoint_url": "http://www.yfrog.com/api/oembed",
"title": "YFrog"
},
{
"url": "http://*.clikthrough.com/theater/video/*",
"url_re": "clikthrough\\.com/theater/video/\\d+$",
"example_url": "http://www.clikthrough.com/theater/video/55",
"endpoint_url": "http://clikthrough.com/services/oembed",
"title": "Clikthrough"
},
{
"url": "http://*.kinomap.com/*",
"url_re": "kinomap\\.com/.+",
"example_url": "http://www.kinomap.com/kms-vzkpc7",
"endpoint_url": "http://www.kinomap.com/oembed",
"title": "Kinomap"
},
{
"url": "http://*.photobucket.com/albums/*|http://*.photobucket.com/groups/*",
"url_re": "photobucket\\.com/(albums|groups)/.+$",
"example_url": "http://img.photobucket.com/albums/v211/JAV123/Michael%20Holland%20Candle%20Burning/_MG_5661.jpg",
"endpoint_url": "http://photobucket.com/oembed",
"title": "Photobucket"
}
]

View file

@ -1,143 +0,0 @@
import logging
import re
import urllib
from django.utils import simplejson as json
from BeautifulSoup import BeautifulSoup, NavigableString
from base import Provider
from utils import *
class TwitterStatusProvider(Provider):
"""Provides info on a particular tweet as a link type oEmbed response"""
title = 'Twitter Status'
url = 'http://twitter.com/*/statuses/*'
url_re = r'twitter\.com/(?P<user>\w+)/statuses/(?P<status>\d+)'
example_url = 'http://twitter.com/mai_co_jp/statuses/822499364'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
fetch_url = 'http://twitter.com/statuses/show/' + matches.group('status') + '.json'
result = get_url(fetch_url)
try:
parsed = json.loads(result)
except:
logging.error("error decoding as json. String was\n%s" % result, exc_info=True)
raise OohEmbedError("Error decoding response")
response = {'type': u'link', 'version': u'1.0', 'provider_name': self.title}
if not 'text' in parsed:
raise OohEmbedError("Error decoding response")
else:
response['title'] = parsed['text']
if 'user' in parsed:
u = parsed['user']
if 'name' in u:
response['author_name'] = u['name']
elif 'screen_name' in u:
response['author_name'] = u['screen_name']
if 'url' in u:
response['author_url'] = u['url']
if 'profile_image_url' in u:
response['thumbnail_url'] = u['profile_image_url']
response['thumbnail_width'] = 48
response['thumbnail_height'] = 48
json_response = json.dumps(response, ensure_ascii=False, indent=0)
return json_response
class WikipediaProvider(Provider):
"""Returns lead content from a Wikipedia page as 'html' attribute of link type oEmbed response"""
title = 'Wikipedia'
url = 'http://*.wikipedia.org/wiki/*'
#url_re = r'wikipedia\.org/wiki/(?P<title>[-\w\.\(\)]+)'
url_re = r'wikipedia\.org/wiki/(?P<title>[^&=]+)'
example_url = 'http://en.wikipedia.org/wiki/Life_on_Mars_(TV_series)'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
fetch_url = query_url + '?action=render'
result = get_url(fetch_url)
soup = BeautifulSoup(result)
page = u''
count = 0
for para in soup('p', recursive=False):
page += unicode(para)
count += 1
if count >= 3: break
response = {'type': u'link', 'version': u'1.0', 'provider_name': self.title}
page_title = unicode(matches.group('title'), 'utf-8')
page_title = urllib.unquote(page_title).replace('_', ' ')
response['title'] = page_title
response['html'] = page
json_response = json.dumps(response, ensure_ascii=True, indent=0)
return json_response
class WordpressProvider(Provider):
"""Returns lead content from a Wordpress.com blog post page as 'html' attribute of link type oEmbed response"""
title = 'Wordpress.com'
url = 'http://*.wordpress.com/yyyy/mm/dd/*'
url_re = r'wordpress\.com/\d{4}/\d{2}/\d{2}/(?P<slug>[-\w\.]+)'
example_url = 'http://martinpitt.wordpress.com/2008/05/07/my-computer-discovered-playing-games/'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
fetch_url = query_url
result = get_url(fetch_url)
soup = BeautifulSoup(result)
response = {'type': u'link', 'version': u'1.0', 'provider_name': self.title}
response['title'] = unicode(soup.title.string)
content = soup.find('div', 'snap_preview')
if not content:
logging.error("Didn't find any snap_preview node on this page: %s" % query_url)
raise OohEmbedError("Could not parse the Wordpress page")
page = u''
count = 1000
para = content.first()
if not para:
logging.error("Didn't find any first paragraph on this page: %s" % query_url)
raise OohEmbedError("Could not parse the Wordpress page")
while len(page) <= count:
page += unicode(para)
para = para.nextSibling
if not para:
break
if len(page) > count:
page = page[:count] + ' ...'
response['html'] = unicode(BeautifulSoup(page))
json_response = json.dumps(response, ensure_ascii=False, indent=0)
return json_response

View file

@ -1,41 +0,0 @@
import os
import re
import urllib
from django.utils import simplejson as json
from base import ProviderMount, Provider
from utils import *
class Proxy():
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
params = {'url': query_url, 'format': 'json'}
if extra_params:
params.update(extra_params)
fetch_url = self.endpoint_url + urllib.urlencode(params)
result = get_url(fetch_url)
return result
def load_providers():
"""Loads OEmbed compliant providers from a json source"""
fp = open(os.path.join(os.path.split(__file__)[0], "endpoints.json"))
providers = json.load(fp)
for provider in providers:
provider["endpoint_url"] = provider["endpoint_url"] + "?" # For ease in Proxy.provide()
provider["__doc__"] = "Just a proxy for the original oEmbed compliant service"
clazz_name = ''.join(provider["title"].strip().split()) # remove whitespace
clazz_name = str(clazz_name) # coerce to string
clazz = ProviderMount(clazz_name, (Provider, Proxy), provider)
load_providers()

View file

@ -1,245 +0,0 @@
import logging
import re
import urllib
import xml.etree.cElementTree as ET
import base64
import hashlib
import hmac
import time
from django.utils import simplejson as json
from BeautifulSoup import BeautifulSoup, NavigableString
from base import Provider
from utils import *
from secrets import *
class ImdbProvider(object):
"""Photo and some metadata for IMDb movie urls. Check sample response to see what metadata beyond that
specified by the oEmbed spec is returned. Note that sometimes, a photo can't be found in which case
you will get a link type response."""
title = 'IMDb'
url = r'http://*.imdb.com/title/tt*/'
url_re = r'imdb.com/title/(?P<resource>tt\d{7,7})'
example_url = 'http://www.imdb.com/title/tt0468569/'
IMDB_NS = '{http://webservice.imdb.com/doc/2006-12-15/}'
def set_value(self, elem, tag, d, key):
"""Check `tag` with Element `elem`. If exists, set `text` of tag
as value of `key` in dictionary `d`. NOTE: `d` is modified for caller."""
e = elem.find('.//' + self.IMDB_NS + tag)
if e is not None and e.text:
d[key] = e.text
return True
else:
return False
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
resource_id = matches.group('resource')
params = urllib.urlencode({'ResourceId': resource_id})
fetch_url = 'http://cc00.clearspring.com/imdb/LookupTitle?' + params
result = get_url(fetch_url)
response = {'type': u'photo', 'version': u'1.0', 'provider_name': self.title}
tree = ET.fromstring(result)
if not self.set_value(tree, 'Source', response, 'url'):
response['type'] = 'link'
else:
self.set_value(tree, 'Width', response, 'width')
self.set_value(tree, 'Height', response, 'height')
self.set_value(tree, 'Title', response, 'title')
self.set_value(tree, 'Year', response, 'year')
e = tree.find('.//'+self.IMDB_NS+'Director')
if e:
self.set_value(e, 'Name', response, 'author_name')
self.set_value(e, 'NameId', response, 'author_url')
if self.set_value(tree, 'PlotSummary', response, 'html'):
response['html'] = u'<p>' + response['html'] + u'</p>'
self.set_value(tree, 'Average', response, 'rating')
json_response = json.dumps(response, ensure_ascii=False, indent=1)
return json_response
class AmazonProvider(Provider):
"""Product images (and author_name for books) for Amazon products. Will soon honour maxwidth/maxheight"""
title = 'Amazon Product Image'
url_re = r'amazon\.(?:com|co\.uk|de|ca|jp)/.*/?(?:gp/product|o/ASIN|obidos/ASIN|dp)/(?P<asin>\w{8,11})[/\?]?'
url = 'http://*.amazon.(com|co.uk|de|ca|jp)/*/(gp/product|o/ASIN|obidos/ASIN|dp)/*'
example_url = 'http://www.amazon.com/Myths-Innovation-Scott-Berkun/dp/0596527055'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
params = {'Service': 'AWSECommerceService',
'AWSAccessKeyId': AWS_ACCESS_KEY_ID, # Please don't abuse!
'AssociateTag': 'antrixnet-20',
'Operation': 'ItemLookup',
'ResponseGroup': 'Images,ItemAttributes',
'Style': 'http://oohembed.com/static/amazon_json.xsl',
'ContentType': 'text/javascript',
'IdType': 'ASIN',
'Timestamp': time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()), #ISO 8601
'ItemId': matches.group('asin')}
str_to_sign = "GET" + "\n" + "xml-us.amznxslt.com" + "\n" + "/onca/xml" + "\n"
str_to_sign = str_to_sign + urllib.urlencode(sorted(params.items())) # All query params sorted
signature = hmac.new(key=AWS_SECRET_ACCESS_KEY, msg=str_to_sign,
digestmod=hashlib.sha256).digest()
signature = base64.encodestring(signature).strip("\n") # base64.urlsafe_b64encode(signature)
params['Signature'] = signature # Add the Signature to the query params
fetch_url = 'http://xml-us.amznxslt.com/onca/xml?' + urllib.urlencode(params)
result = get_url(fetch_url)
try:
parsed = json.loads(result)
except:
logging.error("error decoding as json. String was\n%s" % result, exc_info=True)
raise OohEmbedError("Error decoding response from Amazon.")
item = parsed['Item']
# The returned item contains small, medium and large image details
# Each size is in nested dict in `item` with keyname `img_<size>`.
# We pick the one we want and move it up to the item dict.
item.update(item['img_large'])
# Now we create a response by selecting all needed key/value pairs from `item`.
# This mostly means removing `img_*` keys since the size we want is already
# in top-level of `item`.
# However, sometimes we don't get image details so 'url', 'thumbnail_url', etc.,
# attribute values will be empty strings. So we also prune those now.
selected = dict((k, v) for k, v in item.iteritems()
if not k.startswith('img_') and v)
if not 'url' in selected:
# Return a standard Amazon.com logo
selected['url'] = \
'http://images.amazon.com/images/G/01/x-locale/browse/upf/amzn-logo-5.gif'
selected['width'] = 140
selected['height'] = 66
response = {'type': u'photo', 'version': u'1.0', 'provider_name': self.title}
response.update(selected)
# The returned url includes Subscription ID, etc. Replace it.
response['author_url'] = query_url
json_response = json.dumps(response, ensure_ascii=False, indent=1)
return json_response
class TwitPicProvider(Provider):
"""Photo and thumbnail for TwitPic.com photos."""
title = 'TwitPic'
url = r'http://*.twitpic.com/*'
url_re = r'twitpic.com/(?P<id>\w+)'
example_url = 'http://www.twitpic.com/1pz6z'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
photo_url = 'http://twitpic.com/show/full/' + matches.group('id')
thumb_url = 'http://twitpic.com/show/thumb/' + matches.group('id')
response = {'type': u'photo', 'version': u'1.0', 'provider_name': self.title,
'thumbnail_url': thumb_url, 'thumbnail_width': 150, 'thumbnail_height': 150,
'url': photo_url}
json_response = json.dumps(response, ensure_ascii=False, indent=1)
return json_response
class PhodroidProvider(Provider):
"""Provider for phodroid.com photos."""
title = 'Phodroid Photos'
url = r'http://*.phodroid.com/*/*/*'
url_re = r'phodroid.com/(?P<id>\d\d/\d\d/\w+)/?'
example_url = 'http://phodroid.com/09/06/k3q6bd'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
photo_url = 'http://s.phodroid.com/' + matches.group('id') + '.jpg'
response = {'type': u'photo', 'version': u'1.0', 'provider_name': self.title,
'url': photo_url}
json_response = json.dumps(response, ensure_ascii=False, indent=1)
return json_response
class LJAvatarProvider(Provider):
"""Avatar image for LiveJournal user. Uses http://ljpic.seacrow.com/"""
title = 'LiveJournal UserPic'
url = r'http://*.livejournal.com/'
url_re = r'(?P<id>\w+).livejournal.com/?$'
example_url = 'http://jace.livejournal.com'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
fetch_url = 'http://ljpic.seacrow.com/json/' + matches.group('id')
result = get_url(fetch_url)
try:
parsed = json.loads(result)
except:
logging.error("error decoding as json. String was\n%s" % result, exc_info=True)
raise OohEmbedError("Error decoding response from LJPic.")
response = {'type': u'photo', 'version': u'1.0', 'provider_name': self.title,
'url': parsed['image'], 'author_name': parsed['name']}
json_response = json.dumps(response, ensure_ascii=False, indent=1)
return json_response
class XKCDProvider(Provider):
"""Provides the comic image link for an xkcd.com comic page"""
title = 'XKCD Comic'
url = r'http://*.xkcd.com/*/'
url_re = r'xkcd\.com/\d+/?$'
example_url = 'http://xkcd.com/310/'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
result = get_url(query_url)
soup = BeautifulSoup(result)
photo = soup.find('div', id='contentContainer').find('img')
response = {'type': u'photo', 'version': u'1.0', 'provider_name': self.title,
'url': photo['src'], 'title': photo['alt'], 'author_name': 'Randall Munroe',
'author_url': 'http://xkcd.com/'}
json_response = json.dumps(response, ensure_ascii=False, indent=1)
return json_response

View file

@ -1,110 +0,0 @@
import logging
import xml.etree.cElementTree as ET
from google.appengine.api import urlfetch
# xml to dict stuff from
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/410469
class XmlListConfig(list):
def __init__(self, aList):
for element in aList:
if element:
# treat like dict
if len(element) == 1 or element[0].tag != element[1].tag:
self.append(XmlDictConfig(element))
# treat like list
elif element[0].tag == element[1].tag:
self.append(XmlListConfig(element))
elif element.text:
text = element.text.strip()
if text:
self.append(text)
class XmlDictConfig(dict):
'''
Example usage:
>>> tree = ElementTree.parse('your_file.xml')
>>> root = tree.getroot()
>>> xmldict = XmlDictConfig(root)
Or, if you want to use an XML string:
>>> root = ElementTree.XML(xml_string)
>>> xmldict = XmlDictConfig(root)
And then use xmldict for what it is... a dict.
'''
def __init__(self, parent_element):
if parent_element.items():
self.update(dict(parent_element.items()))
for element in parent_element:
if element:
# treat like dict - we assume that if the first two tags
# in a series are different, then they are all different.
if len(element) == 1 or element[0].tag != element[1].tag:
aDict = XmlDictConfig(element)
# treat like list - we assume that if the first two tags
# in a series are the same, then the rest are the same.
else:
# here, we put the list in dictionary; the key is the
# tag name the list elements all share in common, and
# the value is the list itself
aDict = {element[0].tag: XmlListConfig(element)}
# if the tag has attributes, add those to the dict
if element.items():
aDict.update(dict(element.items()))
self.update({element.tag: aDict})
# this assumes that if you've got an attribute in a tag,
# you won't be having any text. This may or may not be a
# good idea -- time will tell. It works for the way we are
# currently doing XML configuration files...
elif element.items():
self.update({element.tag: dict(element.items())})
# finally, if there are no child tags and no attributes, extract
# the text
else:
self.update({element.tag: element.text})
def xml2dict(xml_string):
"""Returns a dictionary representation of xml in string `xml_string`"""
root = ET.fromstring(xml_string)
return XmlDictConfig(root)
class OohEmbedError(Exception):
def __init__(self, value):
self.reason = value
def __str__(self):
return repr(self.reason)
class UnsupportedUrlError(OohEmbedError):
def __init__(self):
super(UnsupportedUrlError, self).__init__("This provider does not support this URL")
class HTTPError(Exception):
def __init__(self, url, code, content=""):
self.url = url
self.code = code
self.content = content
def __str__(self):
return "HTTPError %s on url %s" % (self.url, self.code)
def get_url(url):
try:
result = urlfetch.fetch(url, headers={'User-Agent': 'oohEmbed.com'})
if result.status_code != 200:
logging.debug('Error code %s while fetching url: %s' % (result.status_code, url))
raise HTTPError(url, result.status_code, result.content)
else:
return result.content
except urlfetch.Error, e:
logging.warn("Error fetching url %s" % url, exc_info=True)
raise OohEmbedError("Error fetching url %s" % query_url)
def make_key(query_url, extra_params):
keys = sorted(extra_params.keys())
return query_url + "|".join(["%s:%s" % (key, extra_params[key]) for key in keys])

View file

@ -1,308 +0,0 @@
import logging
import re
import urllib
import xml.etree.cElementTree as ET
from django.utils import simplejson as json
import feedparser
from base import Provider
from utils import *
from secrets import *
class YoutubeProvider(object):
"""Provides the flash video embed code
__NOTE:__ This is deprecated now. Youtube is handled via the upstream
oembed provider. See oembedprovider.py """
title = 'Youtube'
url = 'http://*.youtube.com/watch*'
url_re = r'youtube\.com/watch.+v=(?P<videoid>[\w-]+)&?'
example_url = 'http://www.youtube.com/watch?v=vk1HvP7NO5w'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
fetch_url = 'http://gdata.youtube.com/feeds/api/videos/' + matches.group('videoid') + '?alt=json'
try:
result = urlfetch.fetch(fetch_url)
if result.status_code != 200:
logging.error('Youtube returned error (code %s): "%s" for url: %s' % (result.status_code, result.content, query_url))
return None
except urlfetch.Error, e:
logging.error("error fetching url %s" % query_url, exc_info=True)
return None
try:
parsed = json.loads(result.content)
entry = parsed['entry']
except:
logging.error("error decoding as json. String was\n%s" % result.content, exc_info=True)
return None
author_name = entry['author'][0]['name']['$t']
author_url = 'http://www.youtube.com/user/' + author_name
title = entry['title']['$t']
response = {'type': u'video', 'version': u'1.0', 'provider_name': self.title,
'title': title, 'author_name': author_name, 'author_url': author_url,
'width': 425, 'height': 344}
thumbnails = entry['media$group']['media$thumbnail']
for thumb in thumbnails:
if thumb['url'].endswith('1.jpg'):
response['thumbnail_url'] = thumb['url']
response['thumbnail_width'] = thumb['width']
response['thumbnail_height'] = thumb['height']
break
html = "<embed src='http://www.youtube.com/v/%s&fs=1' allowfullscreen='true' " \
"type='application/x-shockwave-flash' wmode='transparent' width='425' " \
"height='344'></embed>" % matches.group('videoid')
response['html'] = html
json_response = json.dumps(response, ensure_ascii=False, indent=1)
return json_response
class MetacafeProvider(Provider):
"""Provides the flash video embed code"""
title = 'Metacafe'
url = 'http://*.metacafe.com/watch/*'
url_re = r'metacafe\.com/watch/(?P<videoid>[-\w]+)/.+'
example_url = 'http://www.metacafe.com/watch/1350976/funny_call/'
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
fetch_url = 'http://www.metacafe.com/api/item/' + matches.group('videoid') + '/'
try:
result = urlfetch.fetch(fetch_url)
if result.status_code != 200:
logging.error('Metacafe returned error (code %s): "%s" for url: %s' % (result.status_code, result.content, query_url))
return None
except urlfetch.Error, e:
logging.error("error fetching url %s" % query_url, exc_info=True)
return None
try:
parsed = feedparser.parse(result.content)
entry = parsed['entries'][0]
except:
logging.error("error decoding feed. String was\n%s" % result.content, exc_info=True)
return None
title = entry['title']
author_name = entry['author']
response = {'type': u'video', 'version': u'1.0', 'provider_name': self.title,
'title': title, 'author_name': author_name, 'width': 425, 'height': 344,
'thumbnail_url': 'http://www.metacafe.com/thumb/' + matches.group('videoid') + '.jpg',
'thumbnail_width': 136, 'thumbnail_height': 81
}
response['html'] = "<embed src='http://www.metacafe.com/fplayer/%s/movie.swf' style='width:400px; height:345px;' width='400' height='345' wmode='transparent' type='application/x-shockwave-flash'></embed>" % matches.group('videoid')
json_response = json.dumps(response, ensure_ascii=False, indent=1)
return json_response
class GoogleVideoProvider(Provider):
"""Provides the flash video embed code"""
title = 'Google Video'
url = 'http://video.google.com/videoplay?*'
url_re = r'video\.google\.com/videoplay.+docid=(?P<videoid>[\d-]+)&?'
example_url = 'http://video.google.com/videoplay?docid=8372603330420559198'
json_template = u"""{
"version": "1.0",
"type": "video",
"provider_name": "Google Video",
"width": 400,
"height": 326,
"html": "<embed style='width:400px; height:326px;' type='application/x-shockwave-flash' src='http://video.google.com/googleplayer.swf?docId=%s&amp;hl=en' width='400' height='326'></embed>"
}"""
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
return self.json_template % matches.group('videoid')
class CollegeHumorVideoProvider(Provider):
"""Provides the flash video embed code"""
title = 'CollegeHumor Video'
url = 'http://*.collegehumor.com/video:*'
url_re = r'collegehumor\.com/video:(?P<videoid>[\d]+)'
example_url = 'http://www.collegehumor.com/video:1772239'
json_template = u"""{
"version": "1.0",
"type": "video",
"provider_name": "CollegeHumor Video",
"width": 480,
"height": 360,
"html": "<embed style='width:480px; height:360px;' width='480' height='360' type='application/x-shockwave-flash' src='http://www.collegehumor.com/moogaloop/moogaloop.swf?clip_id=%s&fullscreen=1' ></embed>"
}"""
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
return self.json_template % matches.group('videoid')
class FunnyOrDieProvider(Provider):
"""Provides the flash video embed code"""
title = 'Funny or Die Video'
url = 'http://*.funnyordie.com/videos/*'
url_re = r'funnyordie\.com/videos/(?P<videoid>\w+)'
example_url = 'http://www.funnyordie.com/videos/eae26bb96d'
json_template = u"""{
"version": "1.0",
"type": "video",
"provider_name": "Funny Or Die Video",
"width": 464,
"height": 388,
"html": "<embed width='464' height='388' flashvars='key=%s' allowfullscreen='true' quality='high' src='http://www2.funnyordie.com/public/flash/fodplayer.swf?7228' type='application/x-shockwave-flash'></embed>"
}"""
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
return self.json_template % matches.group('videoid')
class FiveMinVideoProvider(Provider):
"""Provides the flash video embed code"""
title = '5min.com'
url = 'http://*.5min.com/Video/*'
url_re = r'5min\.com/Video/.*-(?P<videoid>[\d]+)$'
example_url = 'http://www.5min.com/Video/Chocolate-Marquise-Recipe-89007978'
json_template = u"""{
"version": "1.0",
"type": "video",
"provider_name": "5min.com Video",
"width": 480,
"height": 401,
"html": "<embed style='width:480px; height:401px;' width='480' height='401' type='application/x-shockwave-flash' src='http://www.5min.com/Embeded/%s/' allowFullscreen='true'></embed>"
}"""
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
return self.json_template % matches.group('videoid')
class DailyShowVideoProvider(Provider):
"""Provides the flash video embed code"""
title = 'Daily Show with Jon Stewart'
url = 'http://*.thedailyshow.com/video/*'
url_re = r'thedailyshow\.com/video/index\.jhtml.*videoId=(?P<videoid>[\d]+)'
example_url = 'http://www.thedailyshow.com/video/index.jhtml?videoId=210855&title=CNN%27s-Magic-Wall-Conspiracy-Thriller'
json_template = u"""{
"version": "1.0",
"type": "video",
"provider_name": "Daily Show with Jon Stewart",
"width": 360,
"height": 301,
"html": "<embed style='width:360px; height:301px;' width='360' height='301' type='application/x-shockwave-flash' src='http://media.mtvnservices.com/mgid:cms:item:comedycentral.com:%s' allowFullscreen='true'></embed>"
}"""
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
return self.json_template % matches.group('videoid')
class SlideShareProvider(Provider):
"""Provides the embed code for slideshow"""
title = 'SlideShare'
url = 'http://*.slideshare.net/*'
url_re = r'slideshare\.net/.+'
example_url = 'http://www.slideshare.net/igniteportland/' \
'how-to-run-a-startup-without-losing-your-mind'
_api_key = SLIDESHARE_KEY
_api_secret = SLIDESHARE_SECRET
_api_url = 'http://www.slideshare.net/api/2/get_slideshow?'
def fetch_info(self, query_url):
from hashlib import sha1
import time
ts = int(time.time()) # unix timestamp
params = {'api_key': self._api_key,
'ts': ts,
'hash': sha1(self._api_secret + str(ts)).hexdigest(),
'slideshow_url': query_url}
fetch_url = self._api_url + urllib.urlencode(params)
result = get_url(fetch_url)
return result
def provide(self, query_url, extra_params=None):
matches = self.url_regex.search(query_url)
if not matches:
raise UnsupportedUrlError()
result = self.fetch_info(query_url)
if not result:
raise OohEmbedError("Did not get response from SlideShare")
if "SlideShareServiceError" in result:
error_msg = ET.fromstring(result)
error_msg = error_msg.find("Message")
if error_msg is not None:
raise OohEmbedError("SlideShare returned error: %s" % error_msg.text)
else:
logging.error("SlideShare error response: %s" % result)
raise OohEmbedError("SlideShare returned error: %s" % result)
result = xml2dict(result)
response = {'version' : '1.0',
'type': 'rich',
'provider_name': self.title
}
response['title'] = result['Title']
# Slideshare's embed code is wrapped in an extra
# left-aligned div. Strip that div out
m = re.match(r'<div.*?>(?P<code>.+)</div>', result['Embed'], re.I)
if not m:
raise OohEmbedError("Could not parse response from SlideShare")
response['html'] = m.group('code')
m = re.search(r'width="(?P<width>\d+)"', response['html'], re.I)
if m:
response['width'] = m.group('width')
m = re.search(r'height="(?P<height>\d+)"', response['html'], re.I)
if m:
response['height'] = m.group('height')
try:
response['author_name'] = result['Username']
response['author_url'] = 'http://www.slideshare.net/'+result['Username']
except KeyError:
pass
response['thumbnail_url'] = result['ThumbnailURL']
json_response = json.dumps(response, ensure_ascii=False, indent=1)
return json_response

View file

@ -1,116 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:aws="http://webservices.amazon.com/AWSECommerceService/2005-10-05" exclude-result-prefixes="aws">
<xsl:output method="text"/>
<!-- +- -->
<!-- | Base Template Match, General JSON Format -->
<!-- +- -->
<xsl:template match="/">
<xsl:value-of select="aws:ItemLookupResponse/aws:OperationRequest/aws:Arguments/aws:Argument[@Name = 'CallBack']/@Value" /><xsl:text> { "Item" : </xsl:text><xsl:apply-templates/><xsl:text> } </xsl:text>
</xsl:template>
<xsl:template match="aws:RequestId"></xsl:template>
<xsl:template match="aws:RequestProcessingTime"></xsl:template>
<xsl:template match="aws:Items">
<xsl:apply-templates select="aws:Item"/>
</xsl:template>
<!-- +- -->
<!-- | Fetch ASIN, URL, Title, Price, Description and return as oembed.com photo type json object -->
<!-- +- -->
<xsl:template match="aws:Item">
<xsl:text> {</xsl:text>
<xsl:text>"asin":"</xsl:text><xsl:value-of select="aws:ASIN"/><xsl:text>",</xsl:text>
<xsl:text>"author_url":"</xsl:text><xsl:value-of select="aws:DetailPageURL"/><xsl:text>",</xsl:text>
<xsl:text>"author_name":"</xsl:text><xsl:value-of select="aws:ItemAttributes/aws:Author"/><xsl:text>",</xsl:text>
<xsl:text>"title":"</xsl:text><xsl:apply-templates select="aws:ItemAttributes/aws:Title"/><xsl:text>",</xsl:text>
<xsl:text>"thumbnail_url":"</xsl:text><xsl:value-of select="aws:SmallImage/aws:URL"/><xsl:text>",</xsl:text>
<xsl:text>"thumbnail_height":"</xsl:text><xsl:value-of select="aws:SmallImage/aws:Height"/><xsl:text>",</xsl:text>
<xsl:text>"thumbnail_width":"</xsl:text><xsl:value-of select="aws:SmallImage/aws:Width"/><xsl:text>",</xsl:text>
<xsl:text>"img_large": {</xsl:text>
<xsl:text>"height":"</xsl:text><xsl:value-of select="aws:LargeImage/aws:Height"/><xsl:text>",</xsl:text>
<xsl:text>"width":"</xsl:text><xsl:value-of select="aws:LargeImage/aws:Width"/><xsl:text>",</xsl:text>
<xsl:text>"url":"</xsl:text><xsl:value-of select="aws:LargeImage/aws:URL"/><xsl:text>"</xsl:text>
<xsl:text>},</xsl:text>
<xsl:text>"img_medium": {</xsl:text>
<xsl:text>"height":"</xsl:text><xsl:value-of select="aws:MediumImage/aws:Height"/><xsl:text>",</xsl:text>
<xsl:text>"width":"</xsl:text><xsl:value-of select="aws:MediumImage/aws:Width"/><xsl:text>",</xsl:text>
<xsl:text>"url":"</xsl:text><xsl:value-of select="aws:MediumImage/aws:URL"/><xsl:text>"</xsl:text>
<xsl:text>},</xsl:text>
<xsl:text>"img_small": {</xsl:text>
<xsl:text>"height":"</xsl:text><xsl:value-of select="aws:SmallImage/aws:Height"/><xsl:text>",</xsl:text>
<xsl:text>"width":"</xsl:text><xsl:value-of select="aws:SmallImage/aws:Width"/><xsl:text>",</xsl:text>
<xsl:text>"url":"</xsl:text><xsl:value-of select="aws:SmallImage/aws:URL"/><xsl:text>"</xsl:text>
<xsl:text>}</xsl:text>
<xsl:text>} </xsl:text>
</xsl:template>
<!-- +- -->
<!-- | Title Template, used to strip out quotation marks (which would break the javascript) -->
<!-- +- -->
<xsl:template match="aws:Title">
<xsl:call-template name="find-and-replace">
<xsl:with-param name="str" select="."/>
<xsl:with-param name="target">"</xsl:with-param>
<xsl:with-param name="replacement" select="''"/>
</xsl:call-template>
</xsl:template>
<!-- +- -->
<!-- | Description Template, used to strip out quotation marks, newlines (which would break the javascript) -->
<!-- +- -->
<xsl:template match="aws:Content">
<xsl:variable name="x">
<xsl:call-template name="find-and-replace">
<xsl:with-param name="str" select="."/>
<xsl:with-param name="target">"</xsl:with-param>
<xsl:with-param name="replacement" select="''"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="y">
<xsl:call-template name="find-and-replace">
<xsl:with-param name="str" select="string($x)"/>
<xsl:with-param name="target" select="'&#10;'"/>
<xsl:with-param name="replacement" select="''"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="z">
<xsl:call-template name="find-and-replace">
<xsl:with-param name="str" select="string($y)"/>
<xsl:with-param name="target" select="'&#13;'"/>
<xsl:with-param name="replacement" select="''"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$z"/>
</xsl:template>
<!-- +- -->
<!-- | Search-and-Replace Template, swaps one string (target) with another (replacement) -->
<!-- +- -->
<xsl:template name="find-and-replace">
<xsl:param name="str"/>
<xsl:param name="target"/>
<xsl:param name="replacement"/>
<xsl:choose>
<xsl:when test="$target and contains($str, $target)">
<xsl:value-of select="substring-before($str, $target)" disable-output-escaping="yes"/>
<xsl:value-of select="$replacement" disable-output-escaping="yes"/>
<xsl:call-template name="find-and-replace">
<xsl:with-param name="str" select="substring-after($str, $target)"/>
<xsl:with-param name="target" select="$target"/>
<xsl:with-param name="replacement" select="$replacement"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str" disable-output-escaping="yes"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

View file

@ -1,6 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" />
</cross-domain-policy>

View file

@ -1 +0,0 @@
../provider/endpoints.json

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,2 +0,0 @@
User-Agent: *
Disallow: /oohembed/

15
chocochip.md Normal file
View file

@ -0,0 +1,15 @@
# Chocochip #
Features/Fixes I intend to address in the _chocochip_ release.
* ~~Propagate error messages from upstream providers back to client~~
* ~~memcache responses~~
* ~~Set proper http expires headers in response~~
* ~~update slideshare provider to v2 api~~
* add new providers where feasible: ~~dailymotion~~, ~~blip.tv~~, revver, laconica, ~~phodroid.com~~, vzaar.com, ~~scribd~~
* hopefully bring back imdb provider
* ~~add crossdomain.xml~~
Striked out items are done!
Please test the chocochip branch at this staging url: http://chocochip.latest.oohembed.appspot.com/