I've been trying to make Gibe, my not-yet-released web log software, less and less about my needs and more about being able to cope with others' needs. That means ripping out a lot of stuff that's custom to me, but somehow making it still available on my own web log. I've made some progress with that - I've made the comment format customisable, as well as found a solution to add custom scripts like Google Analytics and a syntax highlighter. It also means making it easy to add new stuff, which is what my experiment over the weekend was - to add a set of social bookmarking links to posts, so that people can post interesting links to places like Digg, Reddit, del.icio.us, and South Africa's Muti.
The sociable Wordpress plugin is one I've seen around, and I figured I might as well see how it was put together. At its core, it is a list of social bookmarking sites and how to post to them:
$sociable_known_sites = Array(
'blinkbits' => Array(
'favicon' => 'blinkbits.png',
'url' => 'http://www.blinkbits.com/bookmarklets/save.php?v=1&source_url=PERMALINK&title=TITLE&body=TITLE',
),
'BlinkList' => Array(
'favicon' => 'blinklist.png',
'url' => 'http://www.blinklist.com/index.php?Action=Blink/addblink.php&Url=PERMALINK&Title=TITLE',
'description' => 'Description',
),
'BlogMemes' => Array(
'favicon' => 'blogmemes.png',
'url' => 'http://www.blogmemes.net/post.php?url=PERMALINK&title=TITLE',
),
...
);
First order of business was to convert that into a Python data structure. Besides taking a detour via the itertools module just for fun, it didn't take too much effort:
#!/usr/bin/env python
import sys
import itertools
import re
if len(sys.argv) > 1:
fp = open(sys.argv[1])
else:
fp = sys.stdin
lines = fp.readlines()
beg = re.compile('^\$sociable_known_sites')
end = re.compile('^\);')
def find_beginning(item):
return not beg.match(item)
def find_end(item):
return not end.match(item)
def dropbefore(lines):
for line in itertools.dropwhile(find_beginning, lines):
yield line
def dropafter(lines):
for line in itertools.takewhile(find_end, lines):
yield line
start_item = re.compile(r"""^\t('[^']*') => Array\(""")
favicon_item = re.compile(r"""\t\t('favicon') => '([^']*)',""")
mid_item = re.compile(r"""\t\t('[^']*') => ('[^']*'),""")
end_item = re.compile(r"""\t\),""")
def start_item_handle(m):
print "\t%s: {" % (m.groups()[0],)
def favicon_item_handle(m):
k, v = m.groups()
print "\t\t%s: turbogears.url('/tg_widgets/tgsociable/images/%s')," % (k, v)
def mid_item_handle(m):
k, v = m.groups()
v = v.replace("&", "&")
print "\t\t%s: %s," % (k, v)
def end_item_handle(m):
print "\t},"
handlers = [
(start_item, start_item_handle),
(favicon_item, favicon_item_handle),
(mid_item, mid_item_handle),
(end_item, end_item_handle),
]
def handle_lines(lines):
for line in dropafter(dropbefore(lines)):
line = line.rstrip()
for regex, handler in handlers:
m = regex.search(line)
if m:
handler(m)
break
print "import turbogears"
print "all_sites = {"
handle_lines(lines)
print "}"
This outputs the expected Python code (using turbogears.url in a not-to-correct way...):
import turbogears
all_sites = {
'blinkbits': {
'favicon': turbogears.url('/tg_widgets/tgsociable/images/blinkbits.png'),
'url': 'http://www.blinkbits.com/bookmarklets/save.php?v=1&source_url=PERMALINK&title=TITLE&body=TITLE',
},
'BlinkList': {
'favicon': turbogears.url('/tg_widgets/tgsociable/images/blinklist.png'),
'url': 'http://www.blinklist.com/index.php?Action=Blink/addblink.php&Url=PERMALINK&Title=TITLE',
},
...
}
Next, displaying the actual HTML. At this point, the PHP is not conducive to programmatic Python conversion:
$html .= "\n<div class=\"sociable\">\n<span class=\"sociable_tagline\">\n";
$html .= get_option("sociable_tagline");
$html .= "\n\t<span>" . __("These icons link to social bookmarking sites where readers can share and discover new web pages.", 'sociable') . "</span>";
$html .= "\n</span>\n<ul>\n";
foreach($display as $sitename) {
// if they specify an unknown or inactive site, ignore it
if (!in_array($sitename, $active_sites))
continue;
$site = $sociable_known_sites[$sitename];
$html .= "\t<li>";
$url = $site['url'];
$url = str_replace('PERMALINK', $permalink, $url);
$url = str_replace('TITLE', $title, $url);
$url = str_replace('RSS', $rss, $url);
$url = str_replace('BLOGNAME', $blogname, $url);
$url = str_replace('VERSION', $sociable_version, $url);
$html .= "<a href=\"$url\" title=\"$sitename\"";
if ($site['description'])
$html .= " onfocus=\"sociable_description_link(this, '{$site['description']}')\"";
$html .= ">";
$html .= "<img src=\"$imagepath{$site['favicon']}\" title=\"$sitename\" alt=\"$sitename\" class=\"sociable-hovers";
if ($site['class'])
$html .= " sociable_{$site['class']}";
$html .= "\" />";
$html .= "</a></li>\n";
}
$html .= "</ul>\n</div>\n";
return $html;
This turns out a lot prettier thanks to the templating engine (Kid, in this case):
class SociableWidget(widgets.Widget):
template = """
<div class="sociable" xmlns:py="http://purl.org/kid/ns#">
<span class="sociable_tagline">
<strong py:content="sociable_tagline">get_option("sociable_tagline");</strong>
<span py:content="sociable_tagline_description">_("These icons link to social bookmarking sites where readers can share and discover new web pages.", 'sociable')</span>
</span>
<ul>
<li py:for="site in sites">
<a py:attrs="site['anchor_attrs']">
<img py:attrs="site['img_attrs']" />
</a>
</li>
</ul>
</div>
"""
There's a bit more work to make it automatically include the right CSS and Javascript, and to make it configurable (including allowing for extra sites to be added) from the caller of the widget:
css = [
widgets.CSSLink("tgsociable", "css/sociable.css"),
]
javascript = [
widgets.JSLink("tgsociable", "javascript/description_selection.js"),
]
params_doc = {
'active_sites' : 'Sites to display sociable icons for',
'sociable_tagline' : 'Tag line heading',
'sociable_tagline_description' : 'Tag line explanation',
'extra_sites' : 'Sites not in the existing sites list that you want to use',
}
params = params_doc.keys()
active_sites = ["Digg", "Reddit", "del.icio.us"]
sociable_tagline = "Share and Enjoy:"
sociable_tagline_description = "These icons link to social bookmarking sites where readers can share and discover new web pages."
extra_sites = {
# Example:
# 'muti': {
# 'favicon': 'http://muti.co.za/images/favicon.ico',
# 'url': 'http://muti.co.za/submit?url=PERMALINK&title=TITLE',
# },
}
def update_params(self, d):
super(SociableWidget, self).update_params(d)
active_sites = d['active_sites']
d['sites'] = []
my_all_sites = all_sites.copy()
my_all_sites.update(d['extra_sites'])
for sitename in active_sites:
if sitename not in my_all_sites:
continue
site = my_all_sites[sitename]
url = site['url'];
url = url.replace('PERMALINK', d['post_url'])
url = url.replace('TITLE', d['post_title'])
url = url.replace('RSS', d['blog_rss'])
url = url.replace('BLOGNAME', d['blog_name'])
sociable_version = "2.0"
url = url.replace('VERSION', sociable_version)
anchor_attrs = {}
anchor_attrs['href'] = url
if 'description' in site:
anchor_attrs['onfocus'] = "sociable_description_link(this, '%s')" % (site['description'],)
img_attrs = {}
img_attrs['src'] = site['favicon']
img_attrs['title'] = sitename
img_attrs['alt'] = sitename
img_attrs['class'] = "sociable_hovers"
if 'class' in site:
img_attrs['class'] += " " + site['class']
d['sites'].append(dict(anchor_attrs = anchor_attrs, img_attrs = img_attrs))
To use the widget is trivial. First, create the widget instance:
# No configuration - shows Digg, Reddit, and del.icio.us
widget = SociableWidget()
# Choose which sites to show:
widget = SociableWidget(active_sites = [#39;del.icio.us'])
# Add extra sites of your own
extra_sites = {
'muti': {
'favicon': 'http://muti.co.za/images/favicon.ico',
'url': 'http://muti.co.za/submit?url=PERMALINK&title=TITLE',
}
widget = SociableWidget(extra_sites = extra_sites, active_sites = ['muti', 'del.icio.us'])
You can then pass this widget into a template. In the template, you need to provide the post URL, post title, RSS feed, and blog name when you display the widget - something like:
<span py:replace="ET(widget.display(post_url=tg.base_url,
post_title=post.title, blog_name = blog.name,
blog_rss = tg.url_for('rss2.0.xml')))" />
At this point, this is just a plain TurboGears widget, that can be used in any TurboGears application (converting it to ToscaWidgets would be pretty trivial, and then it'll also be usable from Pylons and other platforms that are supported). Hooking it up so that it automatically gets displayed in Gibe posts took a bit more work, and I'll post about that later.
You can download the TGSociable widget on my TGSociable page. You can also follow other posts about TGSociable here - I'll use the tag tgsociable.
2 old-style comments
jens persson — May 13, 2007 at 08:29 PM.
I tried to install tgsociable with gibe, and unfortunately it didn't work :-(
The patch was simple though:
In i]gibeplugin.py change the definition of post_top_widgets to take a forth argument.
Best regards and thanks for your work
Max Kima — March 27, 2008 at 05:19 PM.