diff options
94 files changed, 5041 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..998bb52 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +_site/*
\ No newline at end of file diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..44d3f59 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +pygments: 1 diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..04a4e96 --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <title>{{ page.title }}</title> + <meta name="author" content="franck cuny" /> + <link href="http://lumberjaph.net/index.atom" rel="alternate" title="Tom Preston-Werner" type="application/atom+xml" /> + + <!-- syntax highlighting CSS --> + <link rel="stylesheet" href="/static/css/syntax.css" type="text/css" /> + + <!-- Homepage CSS --> + <link rel="stylesheet" href="/static/css/screen.css" type="text/css" media="screen, projection" /> + +</head> +<body> + +<div class="site"> + <div class="title"> + <a href="/">I'm a lumberjaph</a> + <a class="extra" href="/">home</a> + <a class="extra" href="/archives.html">archives</a> + <a class="extra" href="/about/">about</a> + <a class="extra" href="/talks/">talks</a> + <a class="extra" href="/contact/">contact</a> + </div> + + {{ content }} + + <div class="footer"> + <div class="contact"> + <p> + franck cuny<br /> + franck@lumberjaph.net + </p> + </div> + <div class="contact"> + <p> + <a href="http://github.com/franckcuny/">github.com/franckcuny</a><br /> + <a href="http://twitter.com/franckcuny/">twitter.com/franckcuny</a><br /> + </p> + </div> + <div class="rss"> + <a href="http://lumberjaph.net/index.atom"> + <img src="/static/imgs/rss.png" alt="Subscribe to RSS Feed" /> + </a> + </div> + </div> +</div> + + +<script type="text/javascript"> + + var _gaq = _gaq || []; + _gaq.push(['_setAccount', 'UA-117415-21']); + _gaq.push(['_trackPageview']); + + (function() { + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + })(); + +</script> + +</body> +</html> diff --git a/_layouts/post.html b/_layouts/post.html new file mode 100644 index 0000000..c79b965 --- /dev/null +++ b/_layouts/post.html @@ -0,0 +1,35 @@ +--- +layout: default +--- +<div id="post"> + <h2><a href="{{ page.url }}"> + {{ page.title }} + </a></h2> + <div id="date">published {{ page.date | date_to_string }}</div> + {{ content }} + <hr /> + <div id="disqus"> +<div id="disqus_thread"></div> +<script type="text/javascript"> + /** + * var disqus_identifier; [Optional but recommended: Define a unique identifier (e.g. post id or slug) for this thread] + */ + (function() { + var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; + dsq.src = 'http://lumberjaph.disqus.com/embed.js'; + (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); + })(); +</script> +<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript=lumberjaph">comments powered by Disqus.</a></noscript> +<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a> +<script type="text/javascript"> +var disqus_shortname = 'lumberjaph'; +(function () { + var s = document.createElement('script'); s.async = true; + s.src = 'http://disqus.com/forums/lumberjaph/count.js'; + (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s); +}()); +</script> + + </div> +</div>
\ No newline at end of file diff --git a/_layouts/static.html b/_layouts/static.html new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/_layouts/static.html diff --git a/_posts/2008-06-14-how-to-use-vim-as-a-personal-wiki.textile b/_posts/2008-06-14-how-to-use-vim-as-a-personal-wiki.textile new file mode 100644 index 0000000..faa4c03 --- /dev/null +++ b/_posts/2008-06-14-how-to-use-vim-as-a-personal-wiki.textile @@ -0,0 +1,50 @@ +--- +layout: post +category: app +title: how to use vim as a personal wiki +--- + +There is differents reasons to want a personal wiki on your machine: + + * privacy + * having it everywhere + +I've tested some wiki, like tiddlywiki, but I've found nothing that was really what i wanted. The main inconveniance for me, is the need to use a webbrowser. A browser is not a text processor, so it's really painfull to use them for writing. + +I've started to try to use vim as wiki. Why would i want to use something like vim for this ? well, it's plain text (easy to grep, or to write script for manipulating data), application independent, it's a real text processor, you can customize it, and most importantly, i know how to use it, ... + +I've got a 'wiki' directory in my home directory, with all my files in it. I use git to versionize it (you can use svn if you prefer, there is no difference for this usage). In my .vimrc, i've added this instruction: + +bc. set exrc + +In my wiki directory, i've got another .vimrc with some specific mapping: + +{% highlight vim %} +map ,I <esc>:e index.mkd <cr> +map ,T <esc>:e todo.mkd <cr> +map ,S <esc>:e someday.mkd <cr> +map ,c <esc>:s/^ /c/<cr> +map ,w <esc>:s/^ /w/<cr> +map ,x <esc>:s/^ /x/<cr> +map gf :e <cfile>.mkd<cr> " open page +map <backspace> :bp<cr> +imap \date <c-R>=strftime("%Y-%m-%d")<cr> +set tabstop=2 " Number of spaces <tab> counts for. +set shiftwidth=2 " Unify +set softtabstop=2 " Unify +{% endhighlight %} + +I organize my files in directory. I've got a "work", "lists", "recipes", "misc", ... and I put my files in this directory. + +I've got an index page, with links to main section. I don't have wikiword in camelcase or things like that, so if i want to put a link to a page, i juste wrote the link this way 'dir_name/page_name', then, i juste have to hit 'gf' on this link to open the page. I also use this place as a todo list manager. I've got one paragrah per day, like this : + +bc. 2008-06-14 + - [@context] task 1 + - [@context] task 2 + ... + +and a bunch of vim mapping for marking complete (,c), work in progress (,w) or cancelled (,x). + +If i don't have a deadline for a particular task, I use a 'someday' file, where the task is put with a context. + +The good things with markdown, is that the syntax is easy to use, and it's easy to convert to HTML. diff --git a/_posts/2008-06-15-rtgi-recrute.textile b/_posts/2008-06-15-rtgi-recrute.textile new file mode 100644 index 0000000..b31b798 --- /dev/null +++ b/_posts/2008-06-15-rtgi-recrute.textile @@ -0,0 +1,14 @@ +--- +layout: post +category: linkfluence +title: RTGI recrute +--- + +<a href="http://www.rtgi.fr">RTGI</a> recrute pour 2 postes differents. Le premier, pour un poste "d'écologue", le second pour un poste développeur. La description des annonces est disponible <a href="http://franck.breizhdev.net/upload/0805_profil_poste_tech.pdf">ici</a> + +Vous pouvez voir differents travaux realises par RTGI ici: + + * "blogopole":http://www.blogopole.fr/ + * "segoland":http://www.visualcomplexity.com/vc/project.cfm?id=473 + * "blogopole bis":http://www.visualcomplexity.com/vc/project.cfm?id=400 + * "pw08":http://www.visualcomplexity.com/vc/project.cfm?id=542 diff --git a/_posts/2008-06-17-vim-function-for-creating-new-task.textile b/_posts/2008-06-17-vim-function-for-creating-new-task.textile new file mode 100644 index 0000000..81d2420 --- /dev/null +++ b/_posts/2008-06-17-vim-function-for-creating-new-task.textile @@ -0,0 +1,26 @@ +--- +layout: post +category: app +title: Vim function for creating new task +--- + +I've added a new function to my .vimrc for creating quickly a new task: + +{% highlight vim %} +function! CreateTask() + let context = input("Enter context: ") + exe ":set noautoindent" + exe "normal 0" + exe "normal o \<tab>- [@".context."] " + exe ":set autoindent" + exe ":startinsert" +endfunction +{% endhighlight %} + +and then this mapping: + +{% highlight vim %} +map ct <esc>:call CreateTask()<cr> +{% endhighlight %} + +Now, I've just to hit *,n*, type my context, a new line will be inserted and I just have to create my task. diff --git a/_posts/2008-06-18-keep-your-zshrc-simple.textile b/_posts/2008-06-18-keep-your-zshrc-simple.textile new file mode 100644 index 0000000..b3fd48e --- /dev/null +++ b/_posts/2008-06-18-keep-your-zshrc-simple.textile @@ -0,0 +1,35 @@ +--- +layout: post +category: app +title: keep your zshrc simple +--- + +Keep your .zshrc simple. Mine looks like this : + +{% highlight bash %} +autoload -U compinit zrecompile +zsh_cache=${HOME}/.zsh_cache +mkdir -p $zsh_cache +compinit -d $zsh_cache/zcomp-$HOST +for f in ~/.zshrc $zsh_cache/zcomp-$HOST; do + zrecompile -p $f && rm -f $f.zwc.old +done +setopt extended_glob +for zshrc_snipplet in ~/.zsh.d/S[0-9][0-9]*[^~] ; do + source $zshrc_snipplet +done +function history-all { history -E 1 } +{% endhighlight %} + +and then, in my .zsh.d directory, i've got: + +bc. S10_zshopts +S20_environment +S30_binds +S40_completion +S50_aliases +S60_prompt +S71_ssh +S72_git + +All my aliases are in the same file, it's much easier to search/find/add diff --git a/_posts/2008-06-20-mirror-cpan.textile b/_posts/2008-06-20-mirror-cpan.textile new file mode 100644 index 0000000..461aeaa --- /dev/null +++ b/_posts/2008-06-20-mirror-cpan.textile @@ -0,0 +1,38 @@ +--- +layout: post +category: perl +title: Mirror cpan +--- + +For the last 10 months, I've been living with no internet connection at home (not on purpose, but this is another story), so I've tried to be as much as possible independent from the web. I've started to use git for being able to work off-line, I use Vim as a wiki on my computer, my blog engine for writing post off-line, ... + +As as perl developer, I use a lot the CPAN. So, I've start to mirror the CPAN on my computer. Here is how: + +First, you will need the minicpan: + +{% highlight bash %} +cpan CPAN::Mini +{% endhighlight %} + +Then, edit a .minicpanrc file and add the following: + +bc. local: /path/to/my/mirror/cpan +remote: ftp://ftp.demon.co.uk/pub/CPAN/ + +And to finish, add this in your crontab: + +bc. 5 14 * * * /usr/local/bin/minicpan > /dev/null 2>&1 + +Everyday, at 14h05, your cpan will be updated. + +Now use the CPAN cli: + +{% highlight bash %} +sudo cpan +{% endhighlight %} + +and do the following + +bc. cpan[1]> o conf urllist unshift file:///path/to/my/mirror/cpan + +And voilà , I've got my own minicpan on my computer, so I can install everything when I need it, being off-line or not. diff --git a/_posts/2008-06-21-debug-your-dbix-class-queries.textile b/_posts/2008-06-21-debug-your-dbix-class-queries.textile new file mode 100644 index 0000000..3d0d787 --- /dev/null +++ b/_posts/2008-06-21-debug-your-dbix-class-queries.textile @@ -0,0 +1,21 @@ +--- +layout: post +category: perl +title: debug your DBIx::Class queries +--- + +If you use DBIx::Class and you want to see what the SQL generated looks like, you can set the environment variable DBIC_TRACE. + +{% highlight vim %} +% DBIC_TRACE=1 my_programme.pl +{% endhighlight %} + +And all the SQL will be printed on STDERR. + +If you give a filename to the variable, like this + +{% highlight vim %} +% DBIC_TRACE="1=/tmp/sql.debug" +{% endhighlight %} + +all the statements will be printed in this file. diff --git a/_posts/2008-06-24-ack.textile b/_posts/2008-06-24-ack.textile new file mode 100644 index 0000000..a410984 --- /dev/null +++ b/_posts/2008-06-24-ack.textile @@ -0,0 +1,23 @@ +--- +category: app +title: Ack +layout: post +--- + +*"Ack is designed as a replacement for 99% of the uses of grep."* + +"Ack":http://search.cpan.org/~petdance/ack-1.84/ack is a real nice tool for searching your source code. It's faster than grep because he already knows what you want : searching sources files :) + +By default it will not search in SCM files (.svn, .cvs, ...), backups files (source.pl~, source.pl.bak, ...). You can specify what kind of files you want (--perl --cc, ...), make it match some regex with --match, ... + +And you can set some defaults configuration in a .ackrc file ! Mine looks like this: + +bc. --sort-files +--color +--context=1 +--follow + + +p. Check also: "vim with ack integration":http://use.perl.org/~Ovid/journal/36430?from=rss. + +Oh, and it's the only program with --thpppt option. diff --git a/_posts/2008-06-26-git-branch-everywhere.textile b/_posts/2008-06-26-git-branch-everywhere.textile new file mode 100644 index 0000000..1c32000 --- /dev/null +++ b/_posts/2008-06-26-git-branch-everywhere.textile @@ -0,0 +1,38 @@ +--- +layout: post +category: app +title: Git branch everywhere +--- + +The current trend is to have the name of the current git branch everywhere. Personnaly I display it in my vim's status bar, and in my zsh prompt. + +Here is my vimrc configuration for this (I'm not the author of this function, and can't remember where I saw it first): + +{% highlight bash %} +set statusline=%<[%n]%m%r%h%w%{'['.(&fenc!=''?&fenc:&enc).':'.&ff}%{g:gitCurrentBranch}%{']'}%y\ %F%=%l,%c%V%8P +autocmd BufEnter * :call CurrentGitBranch() + +let g:gitCurrentBranch = '' +function! CurrentGitBranch() + let cwd = getcwd() + cd %:p:h + let branch = matchlist(system('/usr/local/git/bin/git branch -a --no-color'), '\v\* (\w*)\r?\n') + execute 'cd ' . cwd + if (len(branch)) + let g:gitCurrentBranch = '][git:' . branch[1] . '' + else + let g:gitCurrentBranch = '' + endif + return g:gitCurrentBranch +endfunction +{% endhighlight %} + +and my zshrc: + +{% highlight bash %} +local git_b +git_b='$(get_git_prompt_info '%b')' +PROMPT="%(?..%U%?%u:) $git_b %40<...<%/%(#.%U>%u.%B>%b) " +{% endhighlight %} + +with the following script "S55_git":http://www.jukie.net/~bart/conf/zsh.d/S55_git diff --git a/_posts/2008-06-27-dotfiles-and-scm.textile b/_posts/2008-06-27-dotfiles-and-scm.textile new file mode 100644 index 0000000..1868df9 --- /dev/null +++ b/_posts/2008-06-27-dotfiles-and-scm.textile @@ -0,0 +1,55 @@ +--- +layout: post +category: app +title: Dotfiles and SCM +--- + +All my dotfiles are stored in a SCM. Most of the time I'm on my main computer, but I can be working on a server or a different workstation. In this case, I like to have all my configurations for zsh, vim, screen, etc. + +So, instead of copying my files over different computers, I put everything in a private repostiroy, and when I'm on a new computer, I just have to checkout it. If I do a modification on a machine, I just need to commit it, and I can have the modification everywhere else. + +I've got a $HOME/dotfiles directory, which is versionned (with git in my case). All my configurations file are stored here. + +In this directory, as I'm avery lazy person, I've created a Makefile. Each time I create a new file, I add it to the makefile at the same time. The content of the Makefile is the following: + +bc. DOTFILES := $(shell pwd) +all: shell code perl plagger web +shell: +ln -fs $(DOTFILES)/zshrc ${HOME}/.zshrc +ln -fns $(DOTFILES)/zsh.d ${HOME}/.zsh.d +ln -fs $(DOTFILES)/inputrc ${HOME}/.inputrc +ln -fs $(DOTFILES)/screenrc ${HOME}/.screenrc +ln -fns $(DOTFILES)/screen ${HOME}/.screen +ln -fs $(DOTFILES)/profile ${HOME}/.profile +ln -fs $(DOTFILES)/gnupg ${HOME}/.gnupg code: +ln -fs $(DOTFILES)/vimrc ${HOME}/.vimrc +ln -fs $(DOTFILES)/gvimrc ${HOME}/.gvimrc +ln -fns $(DOTFILES)/vim ${HOME}/.vim +ln -fs $(DOTFILES)/ackrc ${HOME}/.ackrc +ln -fs $(DOTFILES)/gitignore ${HOME}/.gitignore +ln -fs $(DOTFILES)/gitconfig ${HOME}/.gitconfig +ln -fs $(DOTFILES)/psqlrc ${HOME}/.psqlrc perl: +ln -fs $(DOTFILES)/proverc ${HOME}/.proverc +ln -fs $(DOTFILES)/pause ${HOME}/.pause +ln -fs $(DOTFILES)/perltidyrc ${HOME}/.perltidyrc +ln -fns $(DOTFILES)/module-starter ${HOME}/.module-starter plagger: +ln -fns $(DOTFILES)/plagger ${HOME}/.plagger web: +ln -fns $(DOTFILES)/irssi ${HOME}/.irssi +ln -fns $(DOTFILES)/vimperator ${HOME}/.vimperator +ln -fs $(DOTFILES)/vimperatorrc ${HOME}/.vimperatorrc +ln -fs $(DOTFILES)/flickrrc ${HOME}/.flickrrc +ln -fs $(DOTFILES)/rtorrent.rc ${HOME}/.rtorrent.rc + +So next time I want to deploy my dotfiles on a new computer, I can do + +{% highlight vim %} +make all +{% endhighlight %} + +or + +{% highlight vim %} +make perl code vim +{% endhighlight %} + +and I can start coding some perl with vim. diff --git a/_posts/2008-06-30-upgrading-to-perl-5.10.textile b/_posts/2008-06-30-upgrading-to-perl-5.10.textile new file mode 100644 index 0000000..8c122a2 --- /dev/null +++ b/_posts/2008-06-30-upgrading-to-perl-5.10.textile @@ -0,0 +1,29 @@ +--- +layout: post +category: perl +title: Upgrading to perl 5.10 +--- + +Get the list of your installed 5.8 modules: + +{% highlight bash %} +perl -MExtUtils::Installed -e'print join("\n", new ExtUtils::Installed->modules)' > module.list +{% endhighlight %} + +then install Perl 5.10: + +{% highlight bash %} +wget http://www.cpan.org/src/perl-5.10.0.tar.gz +tar xzf perl-5.10.0.tar.gz +cd perl-5.10.0 +sh Configure -de -Dprefix=/opt/perl -Duserelocatableinc +make && make test +sudo make install +/opt/perl/bin/perl -e 'use feature qw(say); say "hi"' +{% endhighlight %} + +and then re-install your modules + +{% highlight bash %} +cpan `cat module.list` +{% endhighlight %} diff --git a/_posts/2008-08-08-customize-your-mysql-prompt.textile b/_posts/2008-08-08-customize-your-mysql-prompt.textile new file mode 100644 index 0000000..bf21d9e --- /dev/null +++ b/_posts/2008-08-08-customize-your-mysql-prompt.textile @@ -0,0 +1,14 @@ +--- +layout: post +category: app +title: Customize your mysql prompt +--- + +To customize your mysql prompt, create a .my.cnf file in your $HOME then add the following: + +bc. [mysql] +prompt="\\u [\\d] >" + +It will look like this: + +bc. username [dabatases_name] > diff --git a/_posts/2008-08-16-my-lifestream-page.textile b/_posts/2008-08-16-my-lifestream-page.textile new file mode 100644 index 0000000..51895bd --- /dev/null +++ b/_posts/2008-08-16-my-lifestream-page.textile @@ -0,0 +1,58 @@ +--- +layout: post +category: perl +title: my lifestream page +--- + +For a while I've wanted to mash-up the different services I use on a single page. My first attempt was with <a href="http://plagger.org/trac">plagger</a>, and publishing all the information on my google calendar. That wasn't really interesting, as I don't need to archived all this stuff. After this one, I've tried to make a static page, using <a href="http://developer.yahoo.com/yui/">the yahoo! library interface</a> stuff. A page with tabs, each tabs for a service. The content was generated with Plagger and Template Toolkit. It was working fine, but I had to do some HTML, check why some some stuff were not working, etc. And as I'm not a designer, and as I'm incapable to think a good user interface, I was quickly fed up with this solution. + +I use <a href="http://pipes.yahoo.com/pipes/">yahoo! pipes</a> for mashing some feeds, and saw that you can get a result from your feed in JSON. I've started to think about a simple page with just some javascript, a simple css, and using the JSON result. So I've created a feed that combine the services that I use (<a title="pipe" href="http://pipes.yahoo.com/franckcuny/myfeeds " target="_blank">the pipe is here</a>), created <a href="http://franck.breizhdev.net/">a simple HTML page</a>, read some jquery documentation on how to read a JSON file, and created this page. The code is really simple. For the flickr stream, I use the code generated from the <a title="flickr badge generator" href="http://www.flickr.com/badge.gne">badge generator</a>. I've done some modifications, like removing the table, removing some classes, altering the css, ... The remaining code is + + +{% highlight html %} +<div id="flickr_badge_uber_wrapper"> + <script src="http://www.flickr.com/badge_code_v2.gne?count=9&display=latest&size=s&layout=h&source=user&user=27734462%40N00" type="text/javascript"></script></div> +{% endhighlight %} + +For displaying the result of the JSON feed, I've found some simple code, that I've modified a bit to feet my purpose. + + +{% highlight javascript %} +$(function(){ + var CssToAdd = new Object(); + $.getJSON('http://pipes.yahoo.com/pipes/pipe.run?_id=NMLU4MNq3RGDW_8fpwt1Yg&_render=json&_callback=?', + function(data){ + $('div#loading-area').hide('fast'); + $.each(data.value.items, function(i,item){ + var a_class; + + try { + if (item.link.match(/last/)) { + a_class = 'lastfm'; + }else if (item.link.match(/pownce/)) { + a_class = 'lastfm'; + }else if (item.link.match(/breizhdev/)) { + a_class = 'blog'; + }else if (item.id.content.match(/google/)) { + a_class = 'greader'; + }else{ + a_class = 'delicious'; + } + }catch(err){ + a_class = 'delicious'; + } + + $('<a>').attr('href',item.link).text(item.title).addClass(a_class).appendTo('#sample-feed-block'); + }); + $('#sample-feed-block a').wrapAll('<ul>').wrap('<li>'); + }); +}); +{% endhighlight %} + +As my javascript's skills are near 0, I've no doubt that there is some better way to do this. Feel free to send me a patch ;) + +The page is available <a href="http://franck.breizhdev.net/">here</a>. + +If you want to copy this page, and use your own pipe, don't forget to append "<strong>=json&_callback=?</strong>" at the end of the url of the JSON version of the feed. + +The thing that I really like with this page, it's that everything fit in a single HTML file. The jquery.min.js is hosted on the <a title="jquery" href="http://jquery.com">jquery site</a>, the icons are favicons pulled directly from the sites, so anyone can juste copy this page, change the address of the pipe, modify the flickr call, play a bit with the embded CSS, and it's done. diff --git a/_posts/2008-08-19-offlineimap-on-osx.textile b/_posts/2008-08-19-offlineimap-on-osx.textile new file mode 100644 index 0000000..1ec6ba7 --- /dev/null +++ b/_posts/2008-08-19-offlineimap-on-osx.textile @@ -0,0 +1,28 @@ +--- +layout: post +category: app +title: offlineimap on osx +--- + +If you are using offlineimap on leopard, on an imap connection with ssl (like gmail) and it keep crashing because of the following error: + +{% highlight bash %} +File "/Library/Python/2.5/site-packages/offlineimap/imaplibutil.py", line 70, in _read +return self.sslsock.read(n) +MemoryError +{% endhighlight %} + +you can fix it with this fix: + +{% highlight bash %} +sudo vim /Library/Python/2.5/site-packages/offlineimap/imaplibutil.py +70 +{% endhighlight %} + +then, comment line 70 and add this line + +{% highlight python %} +return self.sslsock.read(min(n, 16384)) +#return self.sslsock.read(n) +{% endhighlight %} + +you can read a description of the bug <a href="http://bugs.python.org/issue1389051">here</a>. diff --git a/_posts/2008-08-21-le-goulet.textile b/_posts/2008-08-21-le-goulet.textile new file mode 100644 index 0000000..084072f --- /dev/null +++ b/_posts/2008-08-21-le-goulet.textile @@ -0,0 +1,8 @@ +--- +layout: post +category: misc +title: le goulet +--- + +<div style="text-align: left; padding: 3px;"><a title="photo sharing" href="http://www.flickr.com/photos/franck_/2783797596/"><img style="border: solid 2px #000000;" src="http://farm4.static.flickr.com/3096/2783797596_afc07c61ab.jpg" alt="" /></a></div> +<a title="goulet de brest" href="http://fr.wikipedia.org/wiki/Goulet_de_Brest">le goulet</a>, <a title="rade de brest" href="http://fr.wikipedia.org/wiki/Rade_de_Brest">rade de brest</a> diff --git a/_posts/2008-08-30-intention-cloud.textile b/_posts/2008-08-30-intention-cloud.textile new file mode 100644 index 0000000..3a96e6f --- /dev/null +++ b/_posts/2008-08-30-intention-cloud.textile @@ -0,0 +1,9 @@ +--- +layout: post +category: perl +title: intention cloud +--- + +The intention cloud is back! + +<a href="http://ic.breizhdev.net"><img title="intention cloud" src="http://bwoup.com/mygfx/Websites/gt_webs-intentioncloud.jpg" alt="intention cloud" width="480" height="291" /></a> diff --git a/_posts/2008-12-05-vim-and-git.textile b/_posts/2008-12-05-vim-and-git.textile new file mode 100644 index 0000000..1a137b6 --- /dev/null +++ b/_posts/2008-12-05-vim-and-git.textile @@ -0,0 +1,45 @@ +--- +layout: post +category: app +title: vim and git +--- + +idea from "Ovid's journal":http://use.perl.org/~Ovid/journal/37966 (ovid is full of really good ideas for vim): + +to get a quick git diff in my vim session, put this in your .vimrc + +{% highlight vim %} +map ,gh :call SourceDiff() " gh for git history + +function! SourceDiff() + let filename = bufname("%") + let command = 'git log -5 --pretty=format:"%h - (%ar) %an - %s" "'.filename.'"' + let result = split( system(command), "\n" ) + + if empty(result) + echomsg("No past revisions for " . filename) + return + endif + + " get the list of files + let revision = PickFromList('revision', result) + + if strlen(revision) + let items = split(revision, " ") + execute '!git diff ' . items[0] . ' -- "' . filename .'" | less' + endif +endfunction +{% endhighlight %} + +the output will look like + +bc. Choose a revision: +1: ea0bb4d - (3 days ago) franck cuny - fix new_freq +2: a896ac7 - (5 weeks ago) franck cuny - fix typo +3: c9bc5fd - (5 weeks ago) franck cuny - update test +4: e9de4be - (5 weeks ago) franck cuny - change the way we rewrite and check an existing url +5: 3df1fd6 - (7 weeks ago) franck cuny - put id category + +You choose the revision you want to check the diff against, and you got a (colorless) diff in your vim buffer. + + diff --git a/_posts/2009-01-18-rtgi-recrute-encore.textile b/_posts/2009-01-18-rtgi-recrute-encore.textile new file mode 100644 index 0000000..7d0545c --- /dev/null +++ b/_posts/2009-01-18-rtgi-recrute-encore.textile @@ -0,0 +1,7 @@ +--- +layout: post +category: linkfluence +title: RTGI recrute (encore) +--- + +RTGI recrute un administrateur reseau, la description du poste est disponible <a href="http://lumberjaph.net/~franck/stuff/0901_profil_admin_sys.pdf">ici</a>. diff --git a/_posts/2009-02-17-tidify-a-json-in-vim.textile b/_posts/2009-02-17-tidify-a-json-in-vim.textile new file mode 100644 index 0000000..016e9fd --- /dev/null +++ b/_posts/2009-02-17-tidify-a-json-in-vim.textile @@ -0,0 +1,21 @@ +--- +layout: post +category: perl +title: tidify a json in vim +--- + +If you have to edit json files from vim, you may want to make them more readable, here is how you can do this: + +start by installing the JSON::XS perl module from the CPAN + +{% highlight perl %} +sudo cpan JSON::XS +{% endhighlight %} + +then, edit your .vimrc and add the following + +{% highlight vim %} +map <leader>jt <Esc>:%!json_xs -f json -t json-pretty<CR> +{% endhighlight %} + +now while editing a json file, you can hit *,jt* (or whatever your leader is set to) and tidify a json. diff --git a/_posts/2009-03-08-belgian-perl-workshop-09.textile b/_posts/2009-03-08-belgian-perl-workshop-09.textile new file mode 100644 index 0000000..9e72d8c --- /dev/null +++ b/_posts/2009-03-08-belgian-perl-workshop-09.textile @@ -0,0 +1,23 @@ +--- +layout: post +category: conference +title: belgian perl workshop 09 +--- + +last weekend my co-workers and I went to the "Belgian Perl Workshop 09":http://conferences.mongueurs.net/bpw2009/. I attended the following presentations: + + * "KiokuDB":http://conferences.mongueurs.net/bpw2009/talk/1720, by nothingmuch. Slides are available "here":http://www.iinteractive.com/kiokudb/talks/bpw2009.xul. We were able to talk with him during the afternoon, we might we use it at "work":http://rtgi.fr + + * "Painless XSLT with Perl":http://conferences.mongueurs.net/bpw2009/talk/1740, by andrew shitov. Was interesting, even if I don't do any XSLT anymore. Again, some ideas might be used for work. + + * "What are you pretending to be ?":http://conferences.mongueurs.net/bpw2009/talk/1792, by liz. That's a hell of a hack. The module is available on the "cpan":http://search.cpan.org/~elizabeth/persona/ + + * "Regular Expressions and Unicode Guru":http://conferences.mongueurs.net/bpw2009/event/473, by abigail. Feel better to know that I'm not the only one suffering with unicode in Perl ;). Learn some stuff like how to create a custom character classe, etc. + + * "Catalyst":http://conferences.mongueurs.net/bpw2009/event/474, by matt trout. Ok, we're using Catalyst at work for our webservices. So we allready know about catalyst, but we were curious. And as i was hoping, we learn some nice tweaks. We discovered "Catalyst::Model::Adaptor":http://search.cpan.org/perldoc?Catalyst::Model::Adaptor, so we don't have to do some horrible stuff in our Controller any more, and some other interesting stuff were put in this talk. And matt is a really good speaker, manage to keep an audiance amused and interested. + + * "Catalyst & AWS":http://conferences.mongueurs.net/bpw2009/event/476, by matt trout. Once again, a really good talk by matt. Some good advices, and a lot of fun. + +We didn't stay long for the social event in the evening; we had booked a hotel in bruxelles. But i'm glad that we were able to get to this perl workshop, it was well-organised, good talks, meet nice people, and learn some stuff. All in all, a good day :) + +some photos are available on "my flickr account":http://www.flickr.com/photos/franck_/sets/72157614745345532/ diff --git a/_posts/2009-04-05-the-intentioncloud-strike-back.textile b/_posts/2009-04-05-the-intentioncloud-strike-back.textile new file mode 100644 index 0000000..83a114e --- /dev/null +++ b/_posts/2009-04-05-the-intentioncloud-strike-back.textile @@ -0,0 +1,41 @@ +--- +layout: post +title: the intentioncloud strike back +category: perl +--- + +I've decided to rewrite the intention cloud. Still with "Catalyst":http://dev.catalystframework.org/wiki/, but I've replaced prototype with "jquery":http://jquery.com this time. I've end up with less code than the previous version. For the moment, only google is available, but I will add overture, and may be more engines. + +There is still some bug to fix, some tests to add, and I will be able to restore the "intentioncloud.net":http://intentioncloud.net domain. + +It's really easy to plug a database to a catalyst application using "Catalyst::Model::DBIC::Schema":http://p3rl.org/Catalyst::Model::DBIC::Schema. Via the helper, you can tell the model to use "DBIx::Class::Schema::Loader":http://p3rl.org/DBIx:/Class::Schema::Loader, so the table informations will be loaded from the database at runtime. You end up with a code that looks like + +{% highlight perl %} +package intentioncloud::Model::DB; +use strict; +use base 'Catalyst::Model::DBIC::Schema'; +__PACKAGE__->config(schema_class => 'intentioncloud::Schema',); +1; +{% endhighlight %} + +and the schema: + +{% highlight perl %} +package intentioncloud::Schema; +use strict; +use base qw/DBIx::Class::Schema::Loader/; +__PACKAGE__->loader_options(relationships => 1); +1; +{% endhighlight %} + +Now, to do a query: + +{% highlight perl %} +my $rs = $c->model('DB::TableName')->find(1); +{% endhighlight %} + +and your done ! + +The code for the intentioncloud is avaible on "github":http://github.com/franckcuny/intentioncloud/tree/master. + + diff --git a/_posts/2009-04-14-git-and-prove.textile b/_posts/2009-04-14-git-and-prove.textile new file mode 100644 index 0000000..fca6329 --- /dev/null +++ b/_posts/2009-04-14-git-and-prove.textile @@ -0,0 +1,31 @@ +--- +layout: post +category: app +title: git and prove +--- + +A little trick to force you to run your tests before a commit: + +in a repositorie, create the following file *.git/hooks/pre-commit* with this content: + +{% highlight bash %} +#!/bin/sh +if [ -d t ]; then + res=`prove t` + if [ $? -gt 0 ]; then + echo "tests fails" + exit 1 + fi +fi +if [ -d xt ]; then + res=`prove xt` + if [ $? -gt 0 ]; then + echo "tests fails" + exit 1 + fi +fi +{% endhighlight %} + +and don't forget to chmod with +x. + +Now, when you will do your next commit, your test suit will be executed. If the tests fails, the commit will be rejected. diff --git a/_posts/2009-04-25-controll-xmms2-from-vim.textile b/_posts/2009-04-25-controll-xmms2-from-vim.textile new file mode 100644 index 0000000..6b713c3 --- /dev/null +++ b/_posts/2009-04-25-controll-xmms2-from-vim.textile @@ -0,0 +1,18 @@ +--- +layout: post +category: app +title: controll xmms2 from vim +--- + +a really basic way to controll xmms2 from your vim session: + +{% highlight vim %} +map <leader>xn <Esc>:!xmms2 next<CR><CR> +map <leader>xb <Esc>:!xmms2 previous<CR><CR> +map <leader>xP <Esc>:!xmms2 pause<CR><CR> +map <leader>xp <Esc>:!xmms2 play<CR><CR> +map <leader>xs <Esc>:!xmms2 stop<CR><CR> +{% endhighlight %} + +now, type *,xn* in vim, and xmms2 will start to play the next track from your playlist. + diff --git a/_posts/2009-04-27-a-simple-feed-aggregator-with-modern-perl-part-1.textile b/_posts/2009-04-27-a-simple-feed-aggregator-with-modern-perl-part-1.textile new file mode 100644 index 0000000..01febfa --- /dev/null +++ b/_posts/2009-04-27-a-simple-feed-aggregator-with-modern-perl-part-1.textile @@ -0,0 +1,153 @@ +--- +title: A simple feed aggregator with modern Perl - part 1 +category: perl +layout: post +--- + +Following "matt's post":http://www.shadowcat.co.uk/blog/matt-s-trout/iron-man/ about people not blogging enough about Perl, I've decided to try to post once a week about Perl. So I will start by a series of articles about what we call *modern Perl*. For this, I will write a simple feed agregator (using "Moose":http://search.cpan.org/~drolsky/Moose-0.75/lib/Moose.pm, "DBIx::Class":http://search.cpan.org/perldoc?DBIx::Class, "KiokuDB":http://search.cpan.org/perldoc?KiokuDB, some tests, and a basic frontend (with "Catalyst":http://search.cpan.org/perldoc?Catalyst). This article will be split in four parts: + + * the first one will explain how to create a schema using *DBIx::Class* + * the second will be about the aggregator. I will use *Moose** and **KiokuDB* + * the third one will be about writing tests with *Test::Class* + * the last one will focus on *Catalyst* + +The code of these modules will be available on my github account at the +same time each article is published. + +bc. disclaimer: +I'm not showing you how to write the perfect feed aggregator. The purpose of +this series of articles is only to show you how to write a simple aggregator +using modern Perl. + + +h3. The database schema + +We will use a database to store a list of feeds and feed entries. As I don't like, no, wait, I *hate* SQL, I will use an ORM for accessing the database. For this, my choice is *DBIx::Class*, the best ORM available in Perl. + +bc. If you never have used an ORM before, ORM stands for Object Relational +Mapping. It's a SQL to OO mapper that creates an abstract encapsulation of +your databases operations. *DBIx::Class*' purpose is to represent "queries in +your code as perl-ish as possible. + +For a basic aggregator we need: + + * a table for the list of feeds + * a table for the entries + +We will create these two tables using *DBIx::Class*. For this, we first create a Schema module. I use *Module::Setup*, but you can use *Module::Starter* or whatever you want. + +{% highlight bash %} +module-setup MyModel +cd MyModel +vim lib/MyModel.pm +{% endhighlight %} + +{% highlight perl %} +package MyModel; +use base qw/DBIx::Class::Schema/; +__PACKAGE__->load_classes(); +1; +{% endhighlight %} + +So, we have just created a schema class. The *load_classes* method loads all the classes that reside under the *MyModel* namespace. We now create the result class *MyModel::Feed* in *lib/MyModel/Feed.pm*: + +{% highlight perl %} +package MyModel::Feed; +use base qw/DBIx::Class/; +__PACKAGE__->load_components(qw/Core/); +__PACKAGE__->table('feed'); +__PACKAGE__->add_columns(qw/ feedid url /); +__PACKAGE__->set_primary_key('feedid'); +__PACKAGE__->has_many(entries => 'MyModel::Entry', 'feedid'); +1; +{% endhighlight %} + +Pretty self explanatory: we declare a result class that uses the table feed, with two columns: *feedid* and *url*, *feedid* being the primary key. The *has_many* method declares a one-to-many relationship. + +Now the result class *MyModel::Entry* in *lib/MyModel/Entry.pm*: + +{% highlight perl %} +package MyModel::Entry; +use base qw/DBIx::Class/; +__PACKAGE__->load_components(qw/Core/); +__PACKAGE__->table('entry'); +__PACKAGE__->add_columns(qw/ entryid permalink feedid/); +__PACKAGE__->set_primary_key('entryid'); +__PACKAGE__->belongs_to(feed => 'MyModel::Feed', 'feedid'); +1; +{% endhighlight %} + +Here we declare *feed* as a foreign key, using the column name *feedid*. + +You can do a more complex declaration of your schema. Let's say you want to declare the type of your fields, you can do this: + +{% highlight perl %} +__PACKAGE__->add_columns( + 'permalink' => { + 'data_type' => 'TEXT', + 'is_auto_increment' => 0, + 'default_value' => undef, + 'is_foreign_key' => 0, + 'name' => 'url', + 'is_nullable' => 1, + 'size' => '65535' + }, +); +{% endhighlight %} + +*DBIx::Class* also provides hooks for the deploy command. If you are using MySQL, you may need a InnoDB table. In your class, you can add this: + +{% highlight perl %} +sub sqlt_deploy_hook { + my ($self, $sqlt_table) = @_; + $sqlt_table->extra( + mysql_table_type => 'InnoDB', + mysql_charset => 'utf8' + ); +} +{% endhighlight %} + +next time you call deploy on this table, the hook will be sent to *SQL::Translator::Schema*, and force the type of your table to InnoDB, and the charset to utf8. + +Now that we have a *DBIx::Class* schema, we need to deploy it. For this, I always do the same thing: create a *bin/deploy_mymodel.pl* script with the following code: + +{% highlight perl %} +use strict; +use feature 'say'; +use Getopt::Long; +use lib('lib'); +use MyModel; + +GetOptions( + 'dsn=s' => \my $dsn, + 'user=s' => \my $user, + 'passwd=s' => \my $passwd +) or die usage(); + +my $schema = MyModel->connect($dsn, $user, $passwd); +say 'deploying schema ...'; +$schema->deploy; + +say 'done'; + +sub usage { + say + 'usage: deploy_mymodel.pl --dsn $dsn --user $user --passwd $passwd'; +} +{% endhighlight %} + +This script will deploy for you the schema (you need to create the database first if using with mysql). + +Executing the following command: + +{% highlight bash %} +perl bin/deploy_mymodel.pl --dsn dbi:SQLite:model.db +{% endhighlight %} + +generate a *model.db* database so we can work and test it. Now that we got our (really) simple *MyModel* schema, we can start to hack on our aggregator. + +"link to the code":http://github.com/franckcuny/ironman-mymodel/tree/master + +bc. while using *DBIx::Class*, you may want to take a look at the generated +queries. For this, export *DBIC_TRACE=1* in your environment, and +the queries will be printed on STDERR. diff --git a/_posts/2009-04-28-a-simple-feed-aggregator-with-modern-perl-part-2.textile b/_posts/2009-04-28-a-simple-feed-aggregator-with-modern-perl-part-2.textile new file mode 100644 index 0000000..258b0d8 --- /dev/null +++ b/_posts/2009-04-28-a-simple-feed-aggregator-with-modern-perl-part-2.textile @@ -0,0 +1,263 @@ +--- +layout: post +title: A simple feed aggregator with modern Perl - part 2 +category: perl +--- + +bc. I've choose to write about a feed aggregator because it's one of the +things I'm working on at "RTGI":http://rtgi.eu/ (with web crawler stuffs, +gluing datas with search engine, etc) + +For the feed aggregator, I will use *Moose*, *KiokuDB* and our *DBIx::Class* schema. Before we get started, I'd would like to give a short introduction to Moose and KiokuDB. + +bq. *Moose*: Moose is a "A postmodern object system for Perl 5". Moose brings to OO Perl some really nice concepts like roles, a better syntax, "free" constructor and destructor, ... If you don't already know Moose, check "http://www.iinteractive.com/moose/":http://www.iinteractive.com/moose/ for more informations. + +bq. *KiokuDB*: KiokuDB is a Moose based frontend to various data stores [...] Its purpose is to provide persistence for "regular" objects with as little effort as possible, without sacrificing control over how persistence is actually done, especially for harder to serialize objects. [...] KiokuDB is meant to solve two related persistence problems: + + * Store arbitrary objects without changing their class definitions or worrying about schema details, and without needing to conform to the limitations of a relational model. + * Persisting arbitrary objects in a way that is compatible with existing data/code (for example interoperating with another app using *CouchDB* with *JSPON* semantics). + +I will store each feed entry in KiokuDB. I could have chosen to store them as plain text in JSON files, in my DBIx::Class model, etc. But as I want to show you new and modern stuff, I will store them in Kioku using the DBD's backend. + +h3. And now for something completely different, code! + +First, we will create a base module named *MyAggregator*. + +{% highlight bash %} +module-setup MyAggregator +{% endhighlight %} + +We will now edit *lib/MyAggregator.pm* and write the following code: + +{% highlight perl %} +package MyAggregator; +use Moose; +1; +{% endhighlight %} + +As you can see, there is no *use strict; use warnings* here: Moose automatically turns on these pragmas. We don't have to write the new method either, as it's provided by Moose. + +For parsing feeds, we will use *XML::Feed*, and we will use it in a Role. If you don't know what roles are: + +bq. Roles have two primary purposes: as interfaces, and as a means of code +reuse. Usually, a role encapsulates some piece of behavior or state that can +be shared between classes. It is important to understand that roles are not +classes. You cannot inherit from a role, and a role cannot be instantiated. + +So, we will write our first role, *lib/MyAggregator/Roles/Feed.pm*: + +{% highlight perl %} +package MyAggregator::Roles::Feed; +use Moose::Role; +use XML::Feed; +use feature 'say'; + +sub feed_parser { + my ($self, $content) = @_; + my $feed = eval { XML::Feed->parse($content) }; + if ($@) { + my $error = XML::Feed->errstr || $@; + say "error while parsing feed : $error"; + } + $feed; +} +1; +{% endhighlight %} + +This one is pretty simple. It will read a content, try to parse it, and return a XML::Feed object. If it can't parse the feed, the error will be shown, and the result will be set to undef. + +Now, a second role will be used to fetch the feed, and do basic caching, *lib/MyAggregator/Roles/UserAgent.pm*: + +{% highlight perl %} +package MyAggregator::Roles::UserAgent; +use Moose::Role; +use LWP::UserAgent; +use Cache::FileCache; +use URI; + +has 'ua' => ( + is => 'ro', + isa => 'Object', + lazy => 1, + default => sub { LWP::UserAgent->new(agent => 'MyUberAgent'); } +); +has 'cache' => ( + is => 'rw', + isa => 'Cache::FileCache', + lazy => 1, + default => + sub { Cache::FileCache->new({namespace => 'myaggregator',}); } +); + +sub fetch_feed { + my ($self, $url) = @_; + + my $req = HTTP::Request->new(GET => URI->new($url)); + my $ref = $self->cache->get($url); + if (defined $ref && $ref->{LastModified} ne '') { + $req->header('If-Modified-Since' => $ref->{LastModified}); + } + + my $res = $self->ua->request($req); + $self->cache->set( + $url, + { ETag => $res->header('Etag') || '', + LastModified => $res->header('Last-Modified') || '' + }, + '5 days', + ); + $res; +} +1; +{% endhighlight %} + +This role has 2 attributes: *ua* and *cache*. The *ua* attribute is our UserAgent. 'lazy' means that it will not be constructed until I call + +bc. $self->ua->request + +I use *Cache::FileCache* for doing basic caching so I don't fetch or parse the feed if it's unnecessary, and I use the Etag and Last-Modified header to check the validity of my cache. + +The only method of this role is *fetch_feed*. It will fetch an URL if it's not already in the cache, and return a *HTTP::Response* object. + +Now, I create an Entry class in *lib/MyAggregator/Entry.pm*: + +{% highlight perl %} +package MyAggregator::Entry; +use Moose; +use Digest::SHA qw(sha256_hex); +has 'author' => (is => 'rw', isa => 'Str'); +has 'content' => (is => 'rw', isa => 'Str'); +has 'title' => (is => 'rw', isa => 'Str'); +has 'id' => (is => 'rw', isa => 'Str'); +has 'date' => (is => 'rw', isa => 'Object'); +has 'permalink' => ( + is => 'rw', + isa => 'Str', + required => 1, + trigger => sub { + my $self = shift; + $self->id(sha256_hex $self->permalink); + } +); +1; +{% endhighlight %} + +Here the *permalink* has a trigger attribute: each entry has a unique *ID*, constructed with a sha256 value from the *permalink*. So, when we fill the *permalink* accessor, the *ID* is automatically set. + +We can now change our *MyAggregator* module like this: + +{% highlight perl %} +package MyAggregator; +use feature ':5.10'; +use MyModel; +use Moose; +use MyAggregator::Entry; +use KiokuDB; +use Digest::SHA qw(sha256_hex); +with 'MyAggregator::Roles::UserAgent', 'MyAggregator::Roles::Feed'; + +has 'context' => (is => 'ro', isa => 'HashRef'); +has 'schema' => ( + is => 'ro', + isa => 'Object', + lazy => 1, + default => sub { MyModel->connect($_[0]->context->{dsn}) }, +); +has 'kioku' => ( + is => 'rw', + isa => 'Object', + lazy => 1, + default => sub { + my $self = shift; + KiokuDB->connect($self->context->{kioku_dir}, create => 1); + } +); + +sub run { + my $self = shift; + + my $feeds = $self->schema->resultset('Feed')->search(); + while (my $feed = $feeds->next) { + my $res = $self->fetch_feed($feed->url); + if (!$res || !$res->is_success) { + say "can't fetch " . $feed->url; + } + else { + $self->dedupe_feed($res, $feed->id); + } + } +} + +sub dedupe_feed { + my ($self, $res, $feed_id) = @_; + + my $feed = $self->feed_parser(\$res->content); + return if (!$feed); + foreach my $entry ($feed->entries) { + next + if $self->schema->resultset('Entry') + ->find(sha256_hex $entry->link); + my $meme = MyAggregator::Entry->new( + permalink => $entry->link, + title => $entry->title, + author => $entry->author, + date => $entry->issued, + content => $entry->content->body, + ); + + + $self->kioku->txn_do( + scope => 1, + body => sub { + $self->kioku->insert($meme->id => $meme); + } + ); + $self->schema->txn_do( + sub { + $self->schema->resultset('Entry')->create( + { entryid => $meme->id, + permalink => $meme->permalink, + feedid => $feed_id, + } + ); + } + ); + } +} +1; +{% endhighlight %} + + + * the with function composes roles into a class. So my MyAggregator class has a fetch_feed and parse_feed methods, and all the attributes of our roles + * context is a HashRef that contains the configuration + * schema is our MyModel schema + * kioku is a connection to our kiokudb backend + + +Two methods in this object: *run* and *dedupe*. + +The *run* method gets the list of feeds (line 28, via the *search*). For each feed return by the search, we try to fetch it, and if it's successful, we dedupe the entries. To dedupe the entries, we check if the permalink is alread in the database (line 45, via the *find*). If we already have this entry, we skip this one, and do the next one. If it's a new entry, we create a *MyAggregator::Entry* object, with the content, date, title, ... we store this object in kiokudb (line 55, we create a transaction, and do our insertion in the transaction), and create a new entry in the MyModel database (line 61, we enter in transaction too, and insert the entry in the database). + +And to run this, a little script: + +{% highlight perl %} +use strict; +use MyAggregator; +use YAML::Syck; +my $agg = MyAggregator->new(context => LoadFile shift); +$agg->run; +{% endhighlight %} + +so we can run our aggregator like this: + +{% highlight bash %} +perl bin/aggregator.pl conf.yaml +{% endhighlight %} + +And it's done :) We got a really basic aggregator now. If you want to improve this one, you would like to improve the dedupe process, using the permalink, the date and/or the title, as this one is too much basic. In the next article we will write some tests for this aggregator using Test::Class. + +big thanks to "tea":http://bunniesincyberspace.wordpress.com/ and "blob":http://code.google.com/p/tinyaml/ for reviewing and fixing my broken english in the first 2 parts. + +"the code is available on github":http://github.com/franckcuny/ironman-myaggregator/tree/master + +Part 3 and 4 next week. diff --git a/_posts/2009-05-04-rtgi-and-perl-conferences.textile b/_posts/2009-05-04-rtgi-and-perl-conferences.textile new file mode 100644 index 0000000..4903838 --- /dev/null +++ b/_posts/2009-05-04-rtgi-and-perl-conferences.textile @@ -0,0 +1,7 @@ +--- +layout: post +category: conference +title: RTGI and Perl conferences +--- + +<a href="http://rtgi.fr">RTGI</a> will be one of the sponsors of the <a href="http://conferences.mongueurs.net/fpw2009/">French Perl Workshop 2009</a>. I (or Camille, not sure yet) will also give a <a href="http://conferences.mongueurs.net/fpw2009/talk/1934">talk</a> about the Perl and CPAN community on the web. Camille (for sure this time) will also give do this talk at the <a href="Yhttp://yapceurope2009.org/ye2009/">YAPC::EU</a> at Lisbon this summer <a href="http://yapceurope2009.org/ye2009/talk/2061">(and in english this time)</a>. diff --git a/_posts/2009-05-06-a-simple-feed-aggregator-with-modern-perl-part-3.textile b/_posts/2009-05-06-a-simple-feed-aggregator-with-modern-perl-part-3.textile new file mode 100644 index 0000000..3da3485 --- /dev/null +++ b/_posts/2009-05-06-a-simple-feed-aggregator-with-modern-perl-part-3.textile @@ -0,0 +1,262 @@ +--- +layout: post +title: A simple feed aggregator with modern Perl - part 3 +category: perl +--- + +Now that we have our aggregator, we have to write our tests. For this I will use Test::Class. Ovid have wrote an "excellent":http://www.modernperlbooks.com/mt/2009/03/organizing-test-suites-with-testclass.html "serie":http://www.modernperlbooks.com/mt/2009/03/reusing-test-code-with-testclass.html "of":http://www.modernperlbooks.com/mt/2009/03/making-your-testing-life-easier.html "articles":http://www.modernperlbooks.com/mt/2009/03/using-test-control-methods-with-testclass.html "about Test::Class":http://www.modernperlbooks.com/mt/2009/03/working-with-testclass-test-suites.html. You should really read this, because I will not enter in details. + +We have two things to test: + + * roles + * aggregator + +h3. Roles + +For this, we create the following files: + + * t/tests/Test/TestObject.pm + * t/tests/Test/MyRoles.pm + * t/tests/Test/MyAggregator.pm + * t/run.t + +We will write our *run.t*: + +{% highlight perl %} +use lib 't/test'; +use Test::MyRoles; +Test::Class->runtests; +{% endhighlight %} + +this test load our tests and run them. + +This is a just a class for the tests, that load our 2 roles. + +now the roles' tests: + +{% highlight perl %} +package Test::MyRoles; + +use strict; +use warnings; +use base 'Test::Class'; +use Test::Exception; +use Test::More; + +sub class {'Test::TestObject'} + +sub url {"http://lumberjaph.net/blog/index.php/feed/"} + +sub startup : Tests(startup => 1) { + my $test = shift; + use_ok $test->class, "use ok"; + `rm -rf /tmp/FileCache/myaggregator/`; +} + +sub constructor : Tests(1) { + my $test = shift; + can_ok $test->class, 'new'; +} + +sub fetch_feed : Tests(5) { + my $test = shift; + can_ok $test->class, 'fetch_feed'; + + ok my $obj = $test->class->new(), '... object is created'; + my $res = $obj->fetch_feed($test->url); + is $res->code, "200", "... fetch is a success"; + like $res->content, qr/lumberjaph/, "... and content is good"; + + # now data should be in cache + my $ref = $obj->cache->get($test->url); + ok defined $ref, "... url is now in cache"; + + $res = $obj->fetch_feed($test->url); + is $res->code, "304", "... already in cache"; +} + +sub feed_parser : Tests(3) { + my $test = shift; + can_ok $test->class, 'feed_parser'; + + my $ua = LWP::UserAgent->new; + my $res = $ua->get($test->url); + ok my $obj = $test->class->new(), "... object is created"; + my $feed = $obj->feed_parser(\$res->content); + isa_ok $feed, "XML::Feed::Format::RSS"; +} + +1; +{% endhighlight %} + +As you can see, some methods have an attribute, which indicate this method as a test method. + +The startup method is run as the first method each time the tests are executed. In our case, we test if we can load our class, and we delete the cache of the aggregator. + +We have a "constructor" test, that check we can do a new on our class. + +Now we have to tests our 2 methods from the roles. We will test the fetch_feed method first. + +First, we indicate the number of tests that will be executed (6 in our case). Then we can write the test in the method: + + * create an object + * fetch an url, and test the HTTP code of the response + * check if the content look like something we want + * now the data should be in cache, and the a new fetch of the url should return a 304 HTTP code + +The second method to test is feed_parser. This method will do 3 tests. + + * create an object + * we manually fetch the content from a feed + * send this content to feed_parser + * the result should return a XML::Feed::Format::RSS object + +When you run the tests now + +bc. prove t/run.t + +the following result is produced: + +{% highlight perl %} +t/run.t .. +1..11 +ok 1 - use Test::TestObject; +# +# Test::MyRoles->constructor +ok 2 - Test::TestObject->can('new') +# +# Test::MyRoles->feed_parser +ok 3 - Test::TestObject->can('feed_parser') +ok 4 - ... object is created +ok 5 - The object isa XML::Feed::Format::RSS +# +# Test::MyRoles->fetch_feed +ok 6 - Test::TestObject->can('fetch_feed') +ok 7 - ... object is created +ok 8 - ... fetch is a success +ok 9 - ... and content is good +ok 10 - ... url is now in cache +ok 11 - ... already in cache +ok +All tests successful. +Files=1, Tests=11, 3 wallclock secs ( 0.03 usr 0.01 sys + 0.66 cusr 0.09 csys = 0.79 CPU) +Result: PAS +{% endhighlight %} + +h3. Aggregator + +As we have our tests for the roles, we can write the tests for the +aggregator now. First, we add a new line in *t/run.t* + +{% highlight perl %} +use Test::MyAggregator +{% endhighlight %} + +We edit our *t/tests/Test/MyAggregator.pm*: + +{% highlight perl %} +package Test::MyAggregator; + +use strict; +use warnings; +use base 'Test::Class'; +use Test::Exception; +use Test::More; + +sub class {'MyAggregator'} + +sub context { + { dsn => 'dbi:SQLite:dbname=/tmp/myaggregator.db', + kioku_dir => 'dbi:SQLite:/tmp/mykioku.db', + }; +} + +sub startup : Tests(startup => 2) { + my $test = shift; + use_ok $test->class, "use ok"; + `touch /tmp/myaggregator.db`; + my $context = $test->context; + my $dsn = $context->{dsn}; + my $schema = MyModel->connect($dsn); + $schema->deploy; + + ok $schema->resultset('Feed')->create( + { feedid => 1, + url => 'http://lumberjaph.net/blog/index.php/feed/', + } + ), + "... insert one feed in the db"; +} + +sub shutdown : Tests(shutdown => 2) { + my $test = shift; + ok unlink '/tmp/myaggregator.db', '... unlink db test'; + ok unlink '/tmp/mykioku.db', '... unlink kioku test'; +} + +sub constructor : Tests(1) { + my $test = shift; + can_ok $test->class, 'new'; +} + +sub dedupe_feed : Tests(4) { + my $test = shift; + + my $context = $test->context; + my $ua = LWP::UserAgent->new; + my $res = $ua->get("http://lumberjaph.net/blog/index.php/feed/"); + + ok my $obj = $test->class->new(context => $context), + "... MyAggregator created"; + + $obj->dedupe_feed($res, 1); + + my $schema = MyModel->connect($context->{dsn}); + is $schema->resultset('Entry')->search()->count, 10, + '... 10 entries in the db'; + + my $first = $schema->resultset('Entry')->search()->first; + my $res_kiokudb; + $obj->kioku->txn_do( + scope => 1, + body => sub { + $res_kiokudb = $obj->kioku->lookup($first->id); + } + ); + + ok $res_kiokudb, '... got an object'; + is $res_kiokudb->permalink, $first->permalink, '... content is valid'; +} + +1; +{% endhighlight %} + +The startup test create a database from our model, and insert a feed. The shutdown test remove the 2 database that we will use (MyModel and kiokudb). + +The dedupe_feed is really simple. We create a MyAggregator object, fetch a rss feed manually, and dedup the result. Now we check the result in the MyModel database: do we have 10 entries ? if it's the case, we are good. We fetch a result from this db, and check if it's also present in KiokuDB, and if the permalink is the same for the two. So with 4 tests, we do a simple check of our class. + +Execute the tests (you can comment the roles' tests in run.t): + +{% highlight perl %} +t/run.t .. +1..9 +ok 1 - use MyAggregator; +ok 2 - ... insert one feed in the db +# +# Test::MyAggregator->constructor +ok 3 - MyAggregator->can('new') +# +# Test::MyAggregator->dedupe_feed +ok 4 - ... MyAggregator created +ok 5 - ... 10 entries in the db +ok 6 - ... got an object +ok 7 - ... content is valid +ok 8 +ok 9 +ok +All tests successful. +Files=1, Tests=9, 3 wallclock secs ( 0.01 usr 0.01 sys + 1.39 cusr 0.12 csys = 1.53 CPU) +Result: PASS +{% endhighlight %} + +We have our tests, so next step is the Catalyst frontend. As for the precedents parts, "the code is available on github":http://github.com/franckcuny/ironman-myaggregator/tree/master diff --git a/_posts/2009-05-13-a-simple-feed-aggregator-with-modern-perl-part-4.textile b/_posts/2009-05-13-a-simple-feed-aggregator-with-modern-perl-part-4.textile new file mode 100644 index 0000000..ca7849e --- /dev/null +++ b/_posts/2009-05-13-a-simple-feed-aggregator-with-modern-perl-part-4.textile @@ -0,0 +1,187 @@ +--- +layout: post +title: A simple feed aggregator with modern Perl - part 4 +category: perl +--- + +We have the model, the aggregator (and some tests), now we can do a basic frontend to read our feed. For this I will create a webapp using "catalyst":http://www.catalystframework.org. + +"Catalyst::Devel":http://search.cpan.org/perldoc?Catalyst::Devel is required for developping catalyst application, so we will install it first: + +{% highlight perl %} +cpan Catalyst::Devel +{% endhighlight %} + +Now we can create our catalyst application using the helper: + +{% highlight perl %} +catalyst.pl MyFeedReader +{% endhighlight %} + +This command initialise the framework for our application *MyFeedReader*. A number of files are created, like the structure of the MVC directory, some tests, helpers, ... + +We start by creating a view, using "TTSite":http://search.cpan.org/perldoc?Catalyst::View::TT. TTSite generate some templates for us, and the configuration for this template. We will also have a basic CSS, a header, footer, etc. + +{% highlight bash %} +cd MyFeedReader +perl script/myfeedreader_create.pl view TT TTSite +{% endhighlight %} + +TTSite files are under *root/src* and *root/lib*. A *MyAggregator/View/TT.pm* file is also created. We edit it to make it look like this: + +{% highlight perl %} +__PACKAGE__->config({ + INCLUDE_PATH => [ + MyFeedReader->path_to( 'root', 'src' ), + MyFeedReader->path_to( 'root', 'lib' ) + ], + PRE_PROCESS => 'config/main', + WRAPPER => 'site/wrapper', + ERROR => 'error.tt2', + TIMER => 0, + TEMPLATE_EXTENSION => '.tt2', +}); +{% endhighlight %} + +Now we create our first template, in *root/src/index.tt2* + +bq. to <a href="/feed/">your feeds</a> + +If you start the application (using _perl script/myfeedreader_server.pl_) and point your browser on http://localhost:3000/, this template will be rendered. + +We need two models, one for KiokuDB and another one for MyModel: + +*lib/MyFeedReader/Model/KiokuDB.pm* + +{% highlight perl %} +package MyFeedReader::Model::KiokuDB; +use Moose; +BEGIN { extends qw(Catalyst::Model::KiokuDB) } +1; +{% endhighlight %} + +we edit the configuration file (*myfeedreader.conf*), and set the dsn for our kiokudb backend + +{% highlight perl %} + <Model KiokuDB> + dsn dbi:SQLite:../MyAggregator/foo.db + </Model> +{% endhighlight %} + +*lib/MyFeedReader/Model/MyModel.pm* + +{% highlight perl %} +package MyFeedReader::Model::MyModel; +use base qw/Catalyst::Model::DBIC::Schema/; +1; +{% endhighlight %} + +and the configuration: + +{% highlight perl %} +<Model MyModel> + connect_info dbi:SQLite:../MyModel/model.db + schema_class MyModel +</Model> +{% endhighlight %} + +We got our view and our model, we can do the code for the controller. We need 2 controller, one for the feed, and one for the entries. The Feed controller will list them and display entries titles for a given feed. The Entry controller will just display them. + +*lib/MyFeedReader/Controller/Feed.pm* + +{% highlight perl %} +package MyFeedReader::Controller::Feed; +use strict; +use warnings; +use parent 'Catalyst::Controller'; + +__PACKAGE__->config->{namespace} = 'feed'; + +sub index : Path : Args(0) { + my ( $self, $c ) = @_; + $c->stash->{feeds} + = [ $c->model('MyModel')->resultset('Feed')->search() ]; +} + +sub view : Chained('/') : PathPart('feed/view') : Args(1) { + my ( $self, $c, $id ) = @_; + $c->stash->{feed} + = $c->model('MyModel')->resultset('Feed')->find($id); +} + +1; +{% endhighlight %} + +The function *index* list the feeds, while the function *view* list the entries for a give feed. We use the chained action mechanism to dispatch this url, so we can have urls like this _/feed/*_ + +We create our 2 templates (for index and view): + +*root/src/feed/index.tt2* + +{% highlight perl %} +<ul> + [% FOREACH feed IN feeds %] + <li><a href="/feed/view/[% feed.id %]">[% feed.url %]</a></li> + [% END %] +</ul> +{% endhighlight %} + +*root/src/feed/vew.tt2* + +{% highlight perl %} +<h1>[% feed.url %]</h1> + +<h3>entries</h3> +<ul> + [% FOREACH entry IN feed.entries %] + <li><a href="/entry/[% entry.id %]">[% entry.permalink %]</a></li> + [% END %] +</ul> +{% endhighlight %} + +If you point your browser to + +bc. http://localhost:3000/feed/ + +you will see this: + +!/static/imgs/list_feed.png(list feeds)! + +Now the controller for displaying the entries: + +{% highlight perl %} +package MyFeedReader::Controller::Entry; +use strict; +use warnings; +use MyAggregator::Entry; +use parent 'Catalyst::Controller'; + +__PACKAGE__->config->{namespace} = 'entry'; + +sub view : Chained('/') : PathPart('entry') : Args(1) { + my ( $self, $c, $id ) = @_; + $c->stash->{entry} = $c->model('KiokuDB')->lookup($id); +} + +1; +{% endhighlight %} + +The function *view* fetch an entry from the kiokudb backend, and store it in the stash, so we can use it in our template. + +*root/src/entry/view.tt2* + +{% highlight perl %} +<h1><a href="[% entry.permalink %]">[% entry.title %]</a></h1> +<span>Posted [% entry.date %] by [% entry.author %]</span> +<div id="content"> + [% entry.content %] +</div> +{% endhighlight %} + +If you point your browser to an entry (something like *http://localhost:3000/entry/somesha256value*), you will see an entry: + +!/static/imgs/show_entry.png(show entry)! + +Et voila, we are done with a really basic feed reader. You can add methods to add or delete feed, mark an entry as read, ... + +"The code is available on github":http://github.com/franckcuny/ironman-myfeedreader/tree/master diff --git a/_posts/2009-05-18-a-simple-feed-aggregator-with-modern-perl-part-4.1.textile b/_posts/2009-05-18-a-simple-feed-aggregator-with-modern-perl-part-4.1.textile new file mode 100644 index 0000000..2577b4f --- /dev/null +++ b/_posts/2009-05-18-a-simple-feed-aggregator-with-modern-perl-part-4.1.textile @@ -0,0 +1,157 @@ +--- +layout: post +title: A simple feed aggregator with modern Perl - part 4.1 +category: perl +--- + +You can thanks "bobtfish":http://github.com/bobtfish for being such a pedantic guy, 'cause now you will have a better chained examples. He forked my repository from github and fix some code that I'll explain here. + +h3. lib/MyFeedReader.pm + +{% highlight perl %} + package MyFeedReader; ++use Moose; ++use namespace::autoclean; + +-use strict; +-use warnings; +- +-use Catalyst::Runtime '5.70'; ++use Catalyst::Runtime '5.80'; + +-use parent qw/Catalyst/; ++extends 'Catalyst'; +{% endhighlight %} + +You can see that he use "Moose":http://search.cpan.org/perldoc?Moose, so we can remove + +{% highlight perl %} +use strict; +use warnings; +{% endhighlight %} + +and have a more elegant way to inherit from "Catalyst":http://search.cpan.org/perldoc?Catalyst with + +{% highlight perl %} +extends 'Catalyst'; +{% endhighlight %} + +instead of + +{% highlight perl %} +use parent qw/Catalyst/; +{% endhighlight %} + +He also have updated the *Catalyst::Runtime* version, and added *namespace::autoclean*. The purpose of this module is to keep imported methods out of you namespace. Take a look at the "documentation":http://search.cpan.org/perldoc?namespace::autoclean, it's easy to understand how and why it's usefull. + +h3. lib/MyFeedReader/Controller/Root.pm + +{% highlight perl %} +-use strict; +-use warnings; +-use parent 'Catalyst::Controller'; ++use Moose; ++use namespace::autoclean; ++BEGIN { extends 'Catalyst::Controller' } + +-sub index :Path :Args(0) { ++sub root : Chained('/') PathPart() CaptureArgs(0) {} ++ ++sub index : Chained('root') PathPart('') Args(0) { + my ( $self, $c ) = @_; + + # Hello World + $c->response->body( $c->welcome_message ); + } + +-sub default :Path { ++sub default : Private { + my ( $self, $c ) = @_; + $c->response->body( 'Page not found' ); + $c->response->status(404); +{% endhighlight %} + +A new method, *root*, that will be the root path for our application. All our methods will be chained from this action. If start you catalyst server and go to *http://localhost:3000/* you will be served with the Catalyst's welcome message as before. + +h3. lib/MyFeedReader/Controller/Entry.pm + +{% highlight perl %} +-use warnings; ++use Moose; + use MyAggregator::Entry; +-use parent 'Catalyst::Controller'; +- +-__PACKAGE__->config->{namespace} = 'entry'; ++use namespace::autoclean; ++BEGIN { extends 'Catalyst::Controller'; } + +-sub view : Chained('/') : PathPart('entry') : Args(1) { ++sub view : Chained('/root') : PathPart('entry') : Args(1) { + my ( $self, $c, $id ) = @_; + + $c->stash->{entry} = $c->model('KiokuDB')->lookup($id); + } + +-1; +- ++__PACKAGE__->meta->make_immutable; +{% endhighlight %} + +We extends the _Catalyst::Controller_ in a Moose way, and the _make_immutable_ instruction is a Moose recommanded best practice (you can alsa add _no Moose_ after the make_immutable). + +h3. lib/MyFeedreader/Controller/Feed.pm + +{% highlight perl %} ++use Moose; ++use namespace::autoclean; ++BEGIN { extends 'Catalyst::Controller' } + +-use strict; +-use warnings; +-use parent 'Catalyst::Controller'; ++sub feed : Chained('/root') PathPart('feed') CaptureArgs(0) {} + +-__PACKAGE__->config->{namespace} = 'feed'; +- +-sub index : Path : Args(0) { ++sub index : Chained('feed') PathPart('') Args(0) { + my ( $self, $c ) = @_; + + $c->stash->{feeds} + = [ $c->model('MyModel')->resultset('Feed')->search() ]; + } + +-sub view : Chained('/') : PathPart('feed/view') : Args(1) { ++sub view : Chained('feed') : PathPart('view') : Args(1) { + my ( $self, $c, $id ) = @_; + + $c->stash->{feed} + = $c->model('MyModel')->resultset('Feed')->find($id); + } + +-1; ++__PACKAGE__->meta->make_immutable; +{% endhighlight %} + +We got _feed_ which is chained to root. _index_ is chained to feed, and take no arguments. This method display the list of our feeds. And we got the _view_ method, chained to feed too, but with one argument, that display the content of an entry. + +If you start the application, you will see the following routes: + +{% highlight perl %} + .-------------------------------------+--------------------------------------. + | Path Spec | Private | + +-------------------------------------+--------------------------------------+ + | /root/entry/* | /root (0) | + | | => /entry/view | + | /root/feed | /root (0) | + | | -> /feed/feed (0) | + | | => /feed/index | + | /root/feed/view/* | /root (0) | + | | -> /feed/feed (0) | + | | => /feed/view | + | /root | /root (0) | + | | => /index | + '-------------------------------------+--------------------------------------' +{% endhighlight %} + +I hope you got a better idea about chained action in catalyst now. And again, thanks to bobtfish for the code. diff --git a/_posts/2009-05-22-modules-i-like---module-setup.textile b/_posts/2009-05-22-modules-i-like---module-setup.textile new file mode 100644 index 0000000..75428b3 --- /dev/null +++ b/_posts/2009-05-22-modules-i-like---module-setup.textile @@ -0,0 +1,75 @@ +--- +layout: post +category: perl +title: modules I like Module::Setup +--- + +"Module::Setup":http://search.cpan.org/perldoc?Module::Setup by "Yappo":http://blog.yappo.jp/ is a really nice module. I don't like "Module::Starter":http://search.cpan.org/perldoc?Module::Starter, it's not easy to create template to make it do what you need. With Module::Setup you can create flavors for any type of modules you want. Most of the modules I create for work use Moose, and I like to use Test::Class too. I've created a Moose flavor for creating this kind of modules. + +h3. Creating a Moose flavor for Module::Setup + +First, you tell it to init a new flavor: + +{% highlight bash %} +module-setup --init moose +{% endhighlight %} + +Module::Setup ask what is your favorite SCM. For me, it's git. It will create files in *$HOME/.module-setup/flavors/moose/*. + +Start by editing *$HOME/.module-setup/flavors/moose/template/lib/____var-module_path-var____.pm* to make it look like this + +{% highlight perl %} +- use strict; +- use warnings; ++ use Moose; +{% endhighlight %} + +Add *requires 'Moose'* in *Makefile.PL*. Create a *t/tests/Test/____var-module_path-var____.pm* file with the following content: + +{% highlight perl %} +package Test :: [%module %]; + +use strict; +use warnings; +use base 'Test::Class'; +use Test::Exception; +use Test::More; + +sub class {'[% module %]'} + +sub startup : Tests(startup => 1) { + my $test = shift; + use_ok $test->class, "use ok"; +} + +sub shutdown : Tests(shutdown) { + my $test = shift; +} + +sub constructor : Tests(1) { + my $test = shift; + can_ok $test->class, 'new'; +} + +1; +{% endhighlight %} + +You will have a Test::Class test ready with basic tests already implemented. + +If you want to share this template at $work, easy: + +{% highlight bash %} +module-setup --pack DevMoose moose > DevMoose.pm +{% endhighlight %} + +You just have to send DevMoose.pm to who need it, and he will be able to import it with + +{% highlight bash %} +module-setup --init --flavor-class=+DevMoose moose +{% endhighlight %} + +Now you can create a new module + +{% highlight bash %} +module-setup MY::AWESOME::MODULE moose +{% endhighlight %} diff --git a/_posts/2009-05-30-catalystx-dispatcher-asgraph.textile b/_posts/2009-05-30-catalystx-dispatcher-asgraph.textile new file mode 100644 index 0000000..af46c6b --- /dev/null +++ b/_posts/2009-05-30-catalystx-dispatcher-asgraph.textile @@ -0,0 +1,29 @@ +--- +layout: post +title: CatalystX::Dispatcher::AsGraph +category: perl +--- + +This morning I saw "this post":http://marcus.nordaaker.com/2009/05/awesome-route-graph-with-mojoxroutesasgraph/ from marcus ramberg about "MojoX::Routes::AsGraph":http://search.cpan.org/perldoc?MojoX::Routes::AsGraph. I liked the idea. But as I Catalyst instead of Mojo, I thought I could give a try and do the same thing for Catalyst dispatcher, and I've coded CatalystX::Dispatcher::AsGraph. For the moment only private actions are graphed. + +!/static/imgs/routes-300x249.png(routes)! + +You use it like this: + +bc. perl bin/catalyst_graph_dispatcher.pl --appname Arkham --output routes.png + +You can create a simple script to output as text if you prefer: + +{% highlight perl %} +#!/usr/bin/perl -w +use strict; +use CatalystX::Dispatcher::AsGraph; + +my $graph = CatalystX::Dispatcher::AsGraph->new_with_options(); +$graph->run; +print $graph->graph->as_txt; +{% endhighlight %} + +The code is on "github":http://github.com/franckcuny/CatalystX--Dispatcher--AsGraph/tree/master for the moment. + +For thoses who are interested by visualization, I'll publish soon some (at least I think) really nice visualisations about CPAN, Perl, and his community, that we have created at "$work":http://rtgi.fr. diff --git a/_posts/2009-06-06-modules-i-like-web-scraper.textile b/_posts/2009-06-06-modules-i-like-web-scraper.textile new file mode 100644 index 0000000..ab78fae --- /dev/null +++ b/_posts/2009-06-06-modules-i-like-web-scraper.textile @@ -0,0 +1,100 @@ +--- +layout: post +category: perl +title: modules I like Web::Scraper +--- + +For "$work":http://rtgi.fr I need to write scrapers. It used to be boring and painful. But thanks to "miyagawa":http://search.cpan.org/~miyagawa/, this is not true anymore. "Web::Scraper":http://search.cpan.org/perldoc?Web::Scraper offer a nice API: you can write your rules using XPath, you can chaine rules, a nice and simple syntax, etc. + +I wanted to export my data from my last.fm account but there is no API for this, so I would need to scrap them. All the data are available "as a web page":http://www.last.fm/user/franckcuny/tracks that list your music. So the scraper need to find how many pages, and find the content on each page to extract a list of your listening. + +For the total of pages, it's easy. Let's take a look at the HTML code and search for something like this: + +{% highlight html %} +<a class="lastpage" href="/user/franckcuny/tracks?page=272">272</a> +{% endhighlight %} + +the information is in a class *lastpage*. + +Now we need to find our data: I need the artist name, the song name and the date I played this song. + +All this data are in a *table*, and each new entry is in a *td*. + +{% highlight html %} +<tr id="r9_1580_1920248170" class="odd"> +[...] + <td class="subjectCell"> + <a href="/music/Earth">Earth</a> + <a href="/music/Earth/_/Sonar+and+Depth+Charge">Sonar and Depth Charge</a> + </td> +[...] +<td class="dateCell last"> + <abbr title="2009-05-13T15:18:25Z">13 May 3:18pm</abbr> +</td> +{% endhighlight %} + +It's simple: information about a song are stored in *subjectcell*, and the artist and song title are each in a tag *a*. The date is in a *dateCell*, and we need the *title* from the *abbr* tag. + +The scraper we need to write is + +{% highlight perl %} +my $scrap = scraper { + process 'a[class="lastpage"]', 'last' => 'TEXT'; + process 'tr', 'songs[]' => scraper { + process 'abbr', 'date' => '@title'; + process 'td[class="subjectCell"]', 'song' => scraper { + process 'a', 'info[]' => 'TEXT'; + }; + } +}; +{% endhighlight %} + +The first rule extract the total of page. The second iter on each *tr* and store the content in an array named *songs*. This *tr* need to be scraped. So we look the the *abbr* tag, and store in *date* the property *title*. Then we look for the song and artitst information. We look for the *td* with a class named *subjectCell*, a extract all links. + +Our final script will look like this: + +{% highlight perl %} +#!/usr/bin/perl -w +use strict; +use feature ':5.10'; + +use Web::Scraper; +use URI; +use IO::All -utf8; + +my $username = shift; +my $output = shift; + +my $scrap = scraper { + process 'a[class="lastpage"]', 'last' => 'TEXT'; + process 'tr', 'songs[]' => scraper { + process 'abbr', 'date' => '@title'; + process 'td[class="subjectCell"]', 'song' => scraper { + process 'a', 'info[]' => 'TEXT'; + }; + } +}; + +my $url = "http://www.last.fm/user/" . $username . "/tracks?page="; +scrap_lastfm(1); + +sub scrap_lastfm { + my $page = shift; + my $scrap_uri = $url . $page; + say $scrap_uri; + my $res = $scrap->scrape(URI->new($scrap_uri)); + my $lastpage = $res->{last}; + foreach my $record (@{$res->{songs}}) { + my $line = join("\t", @{$record->{song}->{info}}, $record->{date}); + $line . "\n" >> io $output; + } + $page++; + scrap_lastfm($page) if $page <= $lastpage; +} +{% endhighlight %} + +You can use this script like this: + +{% highlight bash %} +perl lastfmscraper.pl franckcuny store_data.txt +{% endhighlight %} diff --git a/_posts/2009-06-12-shape-of-cpan.textile b/_posts/2009-06-12-shape-of-cpan.textile new file mode 100644 index 0000000..0b051ab --- /dev/null +++ b/_posts/2009-06-12-shape-of-cpan.textile @@ -0,0 +1,50 @@ +--- +layout: post +category: graph +title: shape of CPAN +--- + +My talk at the "FPW":http://conferences.mongueurs.net/fpw2009/ this year is about the shape of the Perl and CPAN community. This talk was prepared by some "$coworkers":http://labs.rtgi.eu/ and me. + +!/static/imgs/draft_cpan_prelimsmall.png(map of the Perl community on the web)! + +We generated two maps (authors and modules) using the CPANTS' data. For the websites, we crawled a seed generated from the CPAN pages of the previous authors. + +Each of this graphs are generated using a "force base algorithm":http://en.wikipedia.org/wiki/Force-based_algorithms, with the help of "Gephi":http://gephi.org/. + +All the map are available in PDF files, in creative common licence. The slides are in french, but I will explain the three maps here. + + * "slides (french)":http://labs.rtgi.eu/fpw09/resources/slides/ + * "authors map":http://labs.rtgi.eu/fpw09/resources/pdf/cpan_authors_core_march2009.pdf + * "modules map":http://labs.rtgi.eu/fpw09/resources/pdf/cpan_packages_core_march2009.pdf + * "community maps":http://labs.rtgi.eu/fpw09/resources/pdf/cpan-web-may2009-poster.pdf + * "community map (flash version)":http://labs.rtgi.eu/fpw09/map/ + * "cpan-explorer.org":http://cpan-explorer.org/ + +h3. CPAN's modules + +The first map is about the modules available on the CPAN. We selected a list of modules which are listed as dependancies by at least 10 others modules, and the modules who used them. This graph is composed of 7193 nodes (or modules) and 17510 edges. Some clusters are interesting: + + * LWP and URI are really the center of the CPAN + * a lot of web modules (XML::*, TemplateToolkit, HTML::Parser, ...) + * TK is isolated from the CPAN + * Moose, DBIx::Class and Catalyst are forming a group. This data are from march, we will try to do a newer version of this map this summer. This one will be really interesting as Catalyst have switched to Moose + +h3. The CPAN's authors + +This map is about the authors on the CPAN. There is about 700 authors and their connections. Each time an author use a module of another author, a link is created. + + * Modern Perl, constitued by Moose, Catalyst, DBIx::Class. Important authors are Steven, Sartak, perigin, jrockway, mstrout, nothingmuch, marcus ramberg + * Slaven Rezi? and others TK developpers are on the border + +h3. Web map + +We crawled the web using the seed generated using the CPAN's authors pages. + + * again, the "modern group", on the top of the map, with Moose/Catalyst/DBIx::Class developpers + * some enterprises, like shadowcat and iinteractive in the middle of the "modern Perl", Booking in the middle of the YAPC's websites (they are a major sponsor of this events), 6apart, ... + * perl.org is the reference for the Perl community (the site is oriented on their sides) + * cpan.org is the reference for the open source community + * github is in the center of the Perl community. It's widely adopted by the Perl developpers. It offers all the "social media" features that are missing on the CPAN + +I hope you like this visualisations, have fun analyzing them :) diff --git a/_posts/2009-06-17-xmobar-on-debian-sid.textile b/_posts/2009-06-17-xmobar-on-debian-sid.textile new file mode 100644 index 0000000..7382b1a --- /dev/null +++ b/_posts/2009-06-17-xmobar-on-debian-sid.textile @@ -0,0 +1,34 @@ +--- +layout: post +category: app +title: xmobar on debian SID +--- + +If you are using "xmonad":http://www.xmonad.org/ and "xmobar":http://code.haskell.org/~arossato/xmobar/ on "debian SID":http://www.debian.org/ on a laptop, and don't see any battery information, you may have test this "solution":http://5e6n1.wordpress.com/2009/03/30/xmobar-battery-plugin-using-sysfs-not-procfs/. + +If this didn't solve your problem, try this patch on SysfsBatt.hs : + +{% highlight haskell %} +52c52 +< let path = sysfsPath ++ p ++ "/charge_full" +--- +> let path = sysfsPath ++ p ++ "/energy_full" +62c62 +< let path = sysfsPath ++ p ++ "/charge_now" +--- +> let path = sysfsPath ++ p ++ "/energy_now" +74c74 +< else showWithColors (printf "%.2f%%") stateInPercentage +--- +> else showWithColors (printf "Batt: %.2f%%") stateInPercentage +{% endhighlight %} + +Then as before: + +{% highlight bash %} +runhaskell Setup.lhs configure --user +runhaskell Setup.lhs build +runhaskell Setup.lhs install --user +{% endhighlight %} + +Battery information should be visible. diff --git a/_posts/2009-06-22-modules-i-like-getopt-long-and-moosex-getopt.textile b/_posts/2009-06-22-modules-i-like-getopt-long-and-moosex-getopt.textile new file mode 100644 index 0000000..4f9470d --- /dev/null +++ b/_posts/2009-06-22-modules-i-like-getopt-long-and-moosex-getopt.textile @@ -0,0 +1,133 @@ +--- +layout: post +category: perl +title: Modules I like Getopt::Long and MooseX::Getopt +--- + +h3. Getopt::Long + +"Getopt::long":http://search.cpan.org/perldoc?Getopt::Long is a useful module to parse command line arguements. + +A basic usage is something like this: + +{% highlight perl %} +#!/usr/bin/perl -w +use strict; +use YAML::Syck; +use Getopt::Long; + +GetOptions('config=s' => \my $cfg_file,); + +my $config = LoadFile $cfg_file +{% endhighlight %} + +In *GetOptions*, we require a value for config with *config=s*. If we wante an integer, we replace 's' with 'i', and for a floating point, with 'f'. + +Call your script : + +{% highlight bash %} + script.pl --config=file.yml #this one works + script.pl --config file.yml #this one too! + script.pl -c file.yml #and this one too +{% endhighlight %} + +The three syntaxes are understood. + +A good practices is to combine this module with "Pod::Usage":http://search.cpan.org/perldoc?Pod::Usage. Let's do some modifications on the example: + +{% highlight perl %} +#!/usr/bin/perl -w +use strict; +use YAML::Syck; +use Getopt::Long; +use Pod::Usage; + +GetOptions('config=s' => \my $cfg_file,) or pod2usage(2); +pod2usage(2) unless @ARGV > 0; + +my $config = LoadFile $cfg_file + +__END__ +=head1 NAME + +uberscript + +=head1 SYNOPSIS + +uberscript [options] + +Options: + + --config config file + +=head1 Options + +=over 4 + +=item B<config> + +Path to the config file +{% endhighlight %} + +then + +{% highlight bash %} +$ perl uberscript + +Usage: + uberscript [options] + + Options: + --config config file +{% endhighlight %} + +From now if we call our script without argument, the POD will be printed on STDIN. + +h3. MooseX::Getopt + +"MooseX::Getopt":http://search.cpan.org/perldoc?MooseX::Getopt) is a Role that add a <code>new_with_options</code> to your object. We create a basic Object : + +{% highlight perl %} +package OurShinyObject; + +use Moose; +with qw/MooseX::Getopt/; + +has 'config' => (isa => 'Str', is => 'ro', required => 1); +has 'context' => ( + isa => 'HashRef', + is => 'rw', + lazy => 1, + traits => ['NoGetopt'], + default => sub { LoadFile shift->config } +); + +... +{% endhighlight %} + +create a script to call this object + +{% highlight perl %} +#!/usr/bin/perl -w +use strict; +use OurShinyObject; + +my $obj = OurShinyObject->new_from_options(); + +{% endhighlight %} + +bc. script.pl --config file.yml + +The role will set our attribute **context** using the value from the argument set on the command line. + +The + +{% highlight perl %} +traits => ['NoGetopt'] +{% endhighlight %} + +indicate that this attributes will be not be read from the command line. An alternate way to do this is to prefix the attributes with *_*. + +h3. conclusion (?) + +When you write a script, even if you're sure you will never need to have more than one argument, or that you never will have to update the code, *please* consider to use of *Getopt::Long* instead of a *shift @ARGV*, because we all know that you will at a certain point update this script and you will more than one argument :). diff --git a/_posts/2009-06-25-how-to-prevent-some-components-to-be-loaded-by-catalyst.textile b/_posts/2009-06-25-how-to-prevent-some-components-to-be-loaded-by-catalyst.textile new file mode 100644 index 0000000..fefbede --- /dev/null +++ b/_posts/2009-06-25-how-to-prevent-some-components-to-be-loaded-by-catalyst.textile @@ -0,0 +1,26 @@ +--- +layout: post +category: perl +title: How to prevent some components to be loaded by Catalyst +--- + +Let's say you have a "large" [Catalyst](http://search.cpan.org/perldoc?Catalyst) application, with a lot of compoments. When you deploy your application, or when you want to test it while your developping, you may not want to have some of thoses components loaded (you don't have all the dependencies, they are incompatible, etc...). Catalyst use [Module::Pluggable](http://search.cpan.org/perldoc?Module::Pluggable) to load the components, so you can easily configure this. In your application's configuration, add: + +{% highlight yaml %} +setup_components: + except: + - MyApp::Model::AAAA + - MyAPP::Model::BBBB::REST + ... +{% endhighlight %} + +Module::Pluggable have some other interesting features. You may have a second Catalyst application, and want to use one or more components from this one. You can easily do this: + +{% highlight yaml %} +setup_components: + search_path: + - MyApp + - MyOtherApp::Model +{% endhighlight %} + + diff --git a/_posts/2009-06-30-private-and-protected-methods-with-moose.textile b/_posts/2009-06-30-private-and-protected-methods-with-moose.textile new file mode 100644 index 0000000..3734fe3 --- /dev/null +++ b/_posts/2009-06-30-private-and-protected-methods-with-moose.textile @@ -0,0 +1,48 @@ +--- +layout: post +category: perl +title: Private and protected methods with Moose +--- +Yesterday, one of our interns asked me a question about private method in <a href="http://www.iinteractive.com/moose/">Moose</a>. I told him that for Moose as for Perl, there is no such things as private method. By convention, methods prefixed with '_' are considered private. + +But I was curious to see if it would be something complicated to implement in Moose. First, I've started to look at how the 'augment' keyword is done. I've then hacked Moose directly to add the private keyword. After asking advice to <a href="http://blog.woobling.org/">nothingmuch</a>, he recommended me that I implement this in a MooseX::* module instead. The result is <a href="http://github.com/franckcuny/MooseX--MethodPrivate/tree/master">here</a>. + +From the synopsis, MooseX::MethodPrivate do: + +{% highlight perl %} +package Foo; +use MooseX::MethodPrivate; + +private 'foo' => sub { +}; + +protected 'bar' => sub { +}; + + +my $foo = Foo->new; +$foo->foo; # die, can't call foo because it's a private method +$foo->bar; # die, can't call bar because it's a protected method + +package Bar; +use MooseX::MethodPrivate; +extends qw/Foo/; + +sub baz { + my $self = shift; + $self->foo; #die, can't call foo because it's a private method + $self->bar; # ok, can call this method because we extends Foo and + # it's a protected method +} +{% endhighlight %} + +I was surprised to see how easy it's to extend Moose syntax. All I've +done was this: + +{% highlight perl %} + Moose::Exporter->setup_import_methods( + with_caller => [qw( private protected )],); +{% endhighlight %} + +and write the 'private' and 'protected' sub. I'm sure there is some stuff I can do to improve this, but for a first test, I'm happy with the result and still amazed how easy it was to add this two keywords. + diff --git a/_posts/2009-07-07-cpan-and-auto-install.textile b/_posts/2009-07-07-cpan-and-auto-install.textile new file mode 100644 index 0000000..d79f6c0 --- /dev/null +++ b/_posts/2009-07-07-cpan-and-auto-install.textile @@ -0,0 +1,13 @@ +--- +layout: post +category: perl +title: CPAN and auto-install +--- + +When you install a module from the "CPAN":http://search.cpan.org, and this module requires other modules, the cpan shell will ask you if you want to install them. When you are installing "Catalyst":http://www.catalystframework.org/, it may take a while, and you may not want to spend your afternoon in front of the prompt answering "yes" every 5 seconds. + +The solution is to set the environment variable *PERL_MM_USE_DEFAULT*. Next time you want to install a big app: + +bc. PERL_MM_USE_DEFAULT=1 cpan Catalyst KiokuDB + +and your done. diff --git a/_posts/2009-07-16-cpanhq-and-dependencies-graph.textile b/_posts/2009-07-16-cpanhq-and-dependencies-graph.textile new file mode 100644 index 0000000..0d9854b --- /dev/null +++ b/_posts/2009-07-16-cpanhq-and-dependencies-graph.textile @@ -0,0 +1,17 @@ +--- +layout: post +category: perl +title: CPANHQ and dependencies graph +--- + +CPANHQ is a new project that "aims to be a community-driven, meta-data-enhanced alternative to such sites as "search.cpan.org":http://search.cpan.org/ and "kobesearch.cpan.org":http://kobesearch.cpan.org/. + +I believe that a good vizualisation can help to have a better understanding of datas. One of the missing thing on the actual search.cpan.org is the lack of informations about a distribution's dependencies. So my first contribution to the CPANHQ project was to add such informations. + +!/static/imgs/cpanhq-dep.png(cpanhq deps)! + +For each distributions, a graph is generated for the this distribution. For this, I use "Graph::Easy":http://search.cpan.org/perldoc?Graph::Easy and data available from the CPANHQ database. I alsa include a simple list of the dependencies after the graph. + +Only the first level dependencies are displayed, as the distribution's metadata are analysed when the request is made. I could follow all the dependencies when the request is made, but for some distribution it could take a really long time, and it's not suitable for this kind of services. + +*edit*: you can found CPANHQ here : "CPANHQ on github":http://github.com/bricas/cpanhq/tree/master. diff --git a/_posts/2009-07-26-apply-a-role-to-a-moose-object.textile b/_posts/2009-07-26-apply-a-role-to-a-moose-object.textile new file mode 100644 index 0000000..40ace15 --- /dev/null +++ b/_posts/2009-07-26-apply-a-role-to-a-moose-object.textile @@ -0,0 +1,38 @@ +--- +layout: post +title: Apply a role to a Moose object +category: perl +--- + +h2. Apply a role to a Moose object + +You can apply a role to a Moose object. You can do something like + +{% highlight perl %} +#!/usr/bin/perl -w +use strict; +use feature ':5.10'; + +package foo; +use Moose::Role; +sub baz { + say 'i can haz baz'; +} + +package bar; +use Moose; +1; + +package main; + +my $test = bar->new; +say "i can't haz baz" if !$test->can("baz"); + +foo->meta->apply($test); +$test->baz; +{% endhighlight %} + +with the following output: + +bc. i can't haz baz +i can haz baz diff --git a/_posts/2009-07-26-cpan-explorer.textile b/_posts/2009-07-26-cpan-explorer.textile new file mode 100644 index 0000000..bb532cf --- /dev/null +++ b/_posts/2009-07-26-cpan-explorer.textile @@ -0,0 +1,9 @@ +--- +layout: post +category: graph +title: cpan-explorer +--- + +We ("RTGI":http://rtgi.fr) have been working to update the "cpan-explorer":http://cpan-explorer.org. A new version will be available this week, before YAPC::EU. Three new maps have been created, using different informations than the previous one, and you will be able to search and pinpoint the browsable maps. + +!/static/imgs/authorsmap.png(authors map)! diff --git a/_posts/2009-07-28-cpan-explorer-update-and-three-new-maps.textile b/_posts/2009-07-28-cpan-explorer-update-and-three-new-maps.textile new file mode 100644 index 0000000..e9ea414 --- /dev/null +++ b/_posts/2009-07-28-cpan-explorer-update-and-three-new-maps.textile @@ -0,0 +1,29 @@ +--- +layout: post +category: graph +title: cpan-explorer update and three new maps +--- + +The site "cpan-explorer":http://cpan-explorer.org/ have been update with three new maps for the "YAPC::EU":http://yapceurope2009.org/ye2009/. This three maps are different from the previous one. This time, instead of having a big photography of the distributions and authors on the CPAN, Task::Kensho have been used to obtain a representation of what we call the *modern Perl*. + +h3. distributions map + +!/static/imgs/moosedist.png(moose)! + +Task::Kensho acted as the seed for this map. Task::Kensho contains a list of modules recommended to do modern Perl development. So we extracted the modules that have a dependancie toward one of these modules, and create the graph with this data. + +h3. authors map + +The authors listed on this map are the one from the previous map. There is a far less authors thant the previous authors map, but it's more readable. A lot of informations are on the map : label size, node size, edge size, color of the node. + +h3. web communities map + +This map look a lot like the previous one, as we used nearly the same data. The seed have been extended with a few websites only. + +h3. cpan-explorer + +cpan-explorer is now hosted on a wordpress, so you can leave comments or suggestions for new maps you would like to see (a focus on web development modules, tests::* module, etc ...). All the new maps are also searchable, and give you a permalink for you search ("I'm here":http://cpan-explorer.org/2009/07/28/new-web-communities-map-for-yapceu/#dist%3Dlumberjaph.net and "here":http://cpan-explorer.org/2009/07/28/version-of-the-authors-graph-for-yapceu/#author%3Dfranck) + +I will give a talk at the "YAPC::EU":http://yapceurope2009.org/ye2009/talk/2061 about this work. Also, each map have been printed, and will be given for the auction. + +This work is a collective effort from all the guys at "RTGI":http://rtgi.fr/ (antonin created the template for wordpress, niko the js and the tools to extract information from the SVG for the searchable map, julian helped me to create the graphs and extract valuable informations, and I got a lot of feedback from others coworkers), thanks guys!. diff --git a/_posts/2009-08-23-perl-5.10.1-released.textile b/_posts/2009-08-23-perl-5.10.1-released.textile new file mode 100644 index 0000000..da1590f --- /dev/null +++ b/_posts/2009-08-23-perl-5.10.1-released.textile @@ -0,0 +1,32 @@ +--- +layout: post +category: perl +title: Perl 5.10.1 released +--- + +"Perl 5.10.1":http://www.cpan.org/modules/by-authors/id/D/DA/DAPM/perl-5.10.1.tar.bz2 "have been release":http://www.nntp.perl.org/group/perl.perl5.porters/2009/08/msg150172.html. You can download it from the CPAN, or if you can't wait, "here":http://www.iabyn.com/tmp/perl-5.10.1.tar.bz2. + +Next, you need to build it: + +{% highlight bash %} +mkdir ~/perl/5.10.1 +cd ~/code/build/perl-5.10.1 +perl Configure -de -Dprefix=${HOME}/perl/5.10.1/ +make +make test +make install +{% endhighlight %} + +Add the path to your environment + +{% highlight bash %} +export PATH=${HOME}/perl/5.10.1/bin:$PATH +{% endhighlight %} + +and install your CPAN's modules: + +{% highlight bash %} +PERL_MM_USE_DEFAULT=1 cpan Bundle::CPANxxl Task::Kensho +{% endhighlight %} + +Now you can start to play with it :) diff --git a/_posts/2009-08-23-perl5lib-and-zsh.textile b/_posts/2009-08-23-perl5lib-and-zsh.textile new file mode 100644 index 0000000..bfa733f --- /dev/null +++ b/_posts/2009-08-23-perl5lib-and-zsh.textile @@ -0,0 +1,17 @@ +--- +layout: post +category: perl +title: $PERL5LIB and zsh +--- + +in my .zsh.d/S80_perl + +{% highlight bash %} +BASE_PATH=~/code/work/rtgi +for perl_lib in $(ls $BASE_PATH); do + if [ -f $BASE_PATH/$perl_lib/Makefile.PL ]; then + PERL5LIB=${PERL5LIB:+$PERL5LIB:}$BASE_PATH/$perl_lib/lib + fi +done +export PERL5LIB +{% endhighlight %} diff --git a/_posts/2009-08-31-osdc.fr.textile b/_posts/2009-08-31-osdc.fr.textile new file mode 100644 index 0000000..de605c9 --- /dev/null +++ b/_posts/2009-08-31-osdc.fr.textile @@ -0,0 +1,11 @@ +--- +layout: post +category: conference +title: OSDC.fr +--- + +The 2nd and 3rd of October, will be held in Paris the <a href="http://act.osdc.fr/osdc2009fr/index.html">Open Source Developers Conference</a>. The purpose of this conference is to gather developers from various open source communities (Perl, Ruby, Python, ...) during two days. + +It's still possible de submit a talk about the subject of your choice, and it's important to note that this conference is free. + +<a href="http://rtgi.fr">RTGI</a> is proud to be one of the sponsor of this event :). diff --git a/_posts/2009-10-03-teh-batmoose-at-osdc.fr.textile b/_posts/2009-10-03-teh-batmoose-at-osdc.fr.textile new file mode 100644 index 0000000..d726a5b --- /dev/null +++ b/_posts/2009-10-03-teh-batmoose-at-osdc.fr.textile @@ -0,0 +1,11 @@ +--- +layout: post +category: conference +title: teh batmoose at osdc.fr +--- + +Today I presented a talk about Moose at <a href="http://osdc.fr/">OSDC.fr</a>. The slides are available "here":http://franck.lumberjaph.net/stuff/Introduction_a_Moose.pdf. + +And big thanks to my friend <a href="http://www.bwoup.com/">Morgan</a> for his illustration of the batmoose :) + +!/static/imgs/batmoose_1024cut-300x225.png(batmoose)! diff --git a/_posts/2009-11-09-modules-i-like-devel-declare.textile b/_posts/2009-11-09-modules-i-like-devel-declare.textile new file mode 100644 index 0000000..79f7bf5 --- /dev/null +++ b/_posts/2009-11-09-modules-i-like-devel-declare.textile @@ -0,0 +1,248 @@ +--- +layout: post +category: perl +title: Modules I like Devel::Declare +--- + +For "$work":http://linkfluence.net/, I've been working on a job queue system, using Moose, Catalyst (for a REST API) and DBIx::Class to store the jobs and some meta (yeah I know, there is not enough job queue system already, the world really needs a new one ...). + +Basicaly, I've got a XXX::Worker class that all the workers extends. This class provide methods for fetching job, add a new job, mark a job as fail, retry, ... + +The main loop in the XXX::Worker class look like this: + +{% highlight perl %} +# $context is a hashref with some info the job or method may need +while (1) { + my @jobs = $self->fetch_jobs(); + foreach my $job (@jobs) { + my $method = $job->{funcname}; + $self->$method($context, $job); + } + $self->wait; +} +{% endhighlight %} + +and the worker look like this + +{% highlight perl %} +package MyWorker; +use Moose; +extends 'XXX::Worker'; + +sub foo { + my ($self, $context, $job) = @_; + + # do something + $self->job_success(); +} +{% endhighlight %} + +But as I'm using Moose, I want to add more sugar to the syntax, so writing a new worker would be really more easy. + +Here comes "Devel::Declare":http://search.cpan.org/perldoc?Devel::Declare. + +The syntax I want for my worker is this one: + +{% highlight perl %} +work foo { + $self->logger->info("start to work on job"); + # do something with $job +}; + +work bar { + # do something with $job +}; + +success foo { + $self->logger->info("woot job success"); +}; + +fail bar { + $self->logger->info("ho noez this one failed"); +}; +{% endhighlight %} + +Where with '*work*' I write the code the writer will execute on a task, '*success*', a specific code that will be executed after a job is marked as successfull, and '*fail*' for when the job fail. + +I will show how to add the '*work*' keyword. I start by writing a new package: + +{% highlight perl %} +package XXX::Meta; + +use Moose; +use Moose::Exporter; +use Moose::Util::MetaRole; + +use Devel::Declare; + +use XXX::Meta::Class; +use XXX::Keyword::Work; + +Moose::Exporter->setup_import_methods(); + +sub init_meta { + my ($me, %options) = @_; + + my $for = $options{for_class}; + + XXX::Keyword::Work->install_methodhandler(into => $for,); + + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => $for, + metaclass_roles => ['XXX::Meta::Class'], + ); + +} + +1; +{% endhighlight %} + +The *init_meta* method is provided by Moose: (from the POD) + +bq. The *init_meta* method sets up the metaclass object for the class specified by *for_class*. This method injects a a meta accessor into the class so you can get at this object. It also sets the class's superclass to base_class, with Moose::Object as the default. + +So I inject into the class that will use XXX::Meta a new metaclass, XXX::Meta::Class. + +Let's take a look to XXX::Meta::Class: + +{% highlight perl %} +package XXX::Meta::Class; + +use Moose::Role; +use Moose::Meta::Class; +use MooseX::Types::Moose qw(Str ArrayRef ClassName Object); + +has work_metaclass => ( + is => 'ro', + isa => Object, + builder => '_build_metaclass', + lazy => 1, +); + +has 'local_work' => ( + traits => ['Array'], + is => 'ro', + isa => ArrayRef [Str], + required => 1, + default => sub { [] }, + auto_deref => 1, + handles => {'_add_work' => 'push',} +); + +sub _build_metaclass { + my $self = shift; + return Moose::Meta::Class->create_anon_class( + superclasses => [$self->method_metaclass], + cache => 1, + ); +} + +sub add_local_method { + my ($self, $method, $name, $code) = @_; + + my $method_name = $method . "_" . $name; + my $body = $self->work_metaclass->name->wrap( + $code, + original_body => $code, + name => $method_name, + package_name => $self->name, + ); + + my $method_add = "_add_" . $method; + $self->add_method($method_name, $body); + $self->$method_add($method_name); +} + +1; +{% endhighlight %} + +Here I add to the *->meta* provided by Moose '*local_work*', which is an array that contains all my '*work*' methods. So each time I do something like + +{% highlight perl %} +work foo {}; + +work bar {}; +{% endhighlight %} + +in my worker, I add this method to **->meta->local_work**. + +And the class for our keyword work: + +{% highlight perl %} +package XXX::Keyword::Work; + +use strict; +use warnings; + +use Devel::Declare (); +use Sub::Name; + +use base 'Devel::Declare::Context::Simple'; + +sub install_methodhandler { + my $class = shift; + my %args = @_; + { + no strict 'refs'; + *{$args{into} . '::work'} = sub (&) { }; + } + + my $ctx = $class->new(%args); + Devel::Declare->setup_for( + $args{into}, + { work => { + const => sub { $ctx->parser(@_) } + }, + } + ); +} + +sub parser { + my $self = shift; + $self->init(@_); + + $self->skip_declarator; + my $name = $self->strip_name; + $self->strip_proto; + $self->strip_attrs; + + my $inject = $self->scope_injector_call(); + $self->inject_if_block( + $inject . " my (\$self, \$content, \$job) = \@_; "); + + my $pack = Devel::Declare::get_curstash_name; + Devel::Declare::shadow_sub( + "${pack}::work", + sub (&) { + my $work_method = shift; + $pack->meta->add_local_method('work', $name, $work_method); + } + ); + return; +} + +1; +{% endhighlight %} + +The *install_methodhandler* add the *work* keyword, with a block of code. This code is sent to the parser, that will add more sugar. With the inject_if_block, I inject the following line + +{% highlight perl %} +my ($self, $context, $job) = @_; +{% endhighlight %} + +as this will always be my 3 arguments for a work method. + +Now, for each new worker, I write something like this: + +{% highlight perl %} +package MyWorker; +use Moose; +extends 'XXX::Worker'; +use XXX::Meta; + +work foo {}; +{% endhighlight %} + +The next step is too find the best way to reduce the first four lines to two. + +(some of this code is ripped from other modules that use Devel::Declare. The best way to learn what you can do with this module is to read code from other modules that use it) diff --git a/_posts/2009-11-17-sd-the-peer-to-peer-bug-tracking-system.textile b/_posts/2009-11-17-sd-the-peer-to-peer-bug-tracking-system.textile new file mode 100644 index 0000000..3b4f69f --- /dev/null +++ b/_posts/2009-11-17-sd-the-peer-to-peer-bug-tracking-system.textile @@ -0,0 +1,167 @@ +--- +layout: post +category: app +title: sd the peer to peer bug tracking system +--- + +<a href="http://syncwith.us/sd/">SD</a> is a peer to peer bug tracking system build on top of <a href="http://syncwith.us/">Prophet</a>. Prophet is <strong> A grounded, semirelational, peer to peer replicated, disconnected, versioned, property database with self-healing conflict resolution</strong>. SD can be used alone, on an existing bug tracking system (like RT or redmine or github) and it plays nice with git. + +Why should you use SD ? Well, at <a href="http://linkfluence.net/">$work</a> we are using <a href="http://www.redmine.org/">redmine</a> as our ticket tracker. I spend a good part of my time in a terminal, and checking the ticket system, adding a ticket, etc, using the browser, is annoying. I prefer something which I can use in my terminal and edit with my <a href="http://www.vim.org/">$EDITOR</a>. So if you recognize yourself in this description, you might want to take a look at SD. + +bq. In the contrib directory of the SD distribution, you will find a SD ticket syntax file for vim. + +h3. how to do some basic stuff with sd + +We will start by initializing a database. By default + +{% highlight bash %} +sd init +{% endhighlight %} + +will create a *.sd* directory in your $HOME. If you want to create in a specific path, you will need to set the SD_REPO in your env. + +{% highlight bash %} +SD_REPO=~/code/myproject/sd sd init +{% endhighlight %} + +The init command creates an sqlite database and a config file. The config file is in the same format as the one used by git. + +Now we can create a ticket: + +{% highlight bash %} +SD_REPO=~/code/myproject/sd ticket create +{% endhighlight %} + +This will open your $EDITOR, the part you need to edit are specified. After editing this file, you will get something like this: + +bc. Created ticket 11 (437b823c-8f69-46ff-864f-a5f74964a73f) +Created comment 12 (f7f9ee13-76df-49fe-b8b2-9b94f8c37989) + +You can view the created ticket: + +{% highlight bash %} +SD_REPO=~/code/myproject/sd ticket show 11 +{% endhighlight %} + +and the content of your ticket will be displayed. + +You can list and filter your tickets: + +{% highlight bash %} +SD_REPO=~/code/myproject/sd ticket list +SD_REPO=~/code/myproject/sd search --regex foo +{% endhighlight %} + +You can edit the SD configuration using the config tool or editing directly the file. SD will look for three files : /etc/sdrc, $HOME/.sdrc or the config file in your replica (in our exemple, ~/code/myproject/sd/config). + +For changing my email address, I can do it this way: + +{% highlight bash %} +SD_REPO=~/code/myproject/sd config user.email-address franck@lumberjaph.net +{% endhighlight %} + +or directly + +{% highlight bash %} +SD_REPO=~/code/myproject/sd config edit +{% endhighlight %} + +and update the user section. + +h3. sd with git + +SD provides a script for git: *git-sd*. + +Let's start by creating a git repository: + +{% highlight bash %} +mkdir ~/code/git/myuberproject +cd ~/code/git/myuberproject +git init +{% endhighlight %} + +SD comes with a git hook named "git-post-commit-close-ticket" (in the contrib directory). We will copy this script to <strong>.git/hooks/post-commit</strong>. + +now we can initialize our sd database + +{% highlight bash %} +git-sd init +{% endhighlight %} + +git-sd will try to find which email you have choosen for this project using git config, and use the same address for it's configuration. + +Let's write some code for our new project + +{% highlight perl %} +#!/usr/bin/env perl +use strict; +use warnings; +print "hello, world\n"; +{% endhighlight %} + +then + +{% highlight bash %} +git add hello.pl +git commit -m "first commit" hello.pl +{% endhighlight %} + +now we can create a new entry + +{% highlight bash %} +git-sd ticket create # create a ticket to replace print with say +{% endhighlight %} + +We note the UUID for the ticket: in my exemple, the following output is produced: + +bc. Created ticket 11 (92878841-d764-4ac9-8aae-cd49e84c1ffe) +Created comment 12 (ddb1e56e-87cb-4054-a035-253be4bc5855) + +so my UUID is <strong>92878841-d764-4ac9-8aae-cd49e84c1ffe</strong>. + +Now, I fix my bug + +{% highlight bash %} +#!/usr/bin/env perl +use strict; +use 5.010; +use warnings; +say "hello, world"; +{% endhighlight %} + +and commit it + +{% highlight bash %} +git commit -m "Closes 92878841-d764-4ac9-8aae-cd49e84c1ffe" hello.pl +{% endhighlight %} + +If I do a + +{% highlight bash %} +git ticket show 92878841-d764-4ac9-8aae-cd49e84c1ffe +{% endhighlight %} + +The ticket will be marked as closed. + +h3. sd with github + +Let's say you want to track issues from a project (I will use <a href="http://plackperl.org/">Plack</a> for this exemple) that is hosted on github. + +{% highlight bash %} +git clone git://github.com/miyagawa/Plack.git +git-sd clone --from "github:http://github.com/miyagawa/Plack" +# it's the same as +git-sd clone --from "github:miyagawa/Plack" +# or if you don't want to be prompted for username and password each time +git-sd clone --from github:http://githubusername:apitoken@github.com/miyagawa/Plack.git +{% endhighlight %} + +It will ask for you github username and your API token, and clone the database. + +Later, you can publish your sd database like this: + +{% highlight bash %} +git-sd push --to "github:http://github.com/$user/$project" +{% endhighlight %} + +Now you can code offline with git, and open/close tickets using SD :) diff --git a/_posts/2009-12-13-riak-perl-and-kiokudb.textile b/_posts/2009-12-13-riak-perl-and-kiokudb.textile new file mode 100644 index 0000000..c0a51de --- /dev/null +++ b/_posts/2009-12-13-riak-perl-and-kiokudb.textile @@ -0,0 +1,155 @@ +--- +layout: post +category: perl +title: Riak, Perl and KiokuDB +--- + +As I was looking for a system to store documents at <a href="http://linkfluence.net">$work</a>, <a href="http://www.basho.com/Riak.html">Riak</a> was pointed to me by one of my coworkers. I'm looking for a solution of this type to store various types of documents, from HTML pages to json. I need a system that is distributed, faul tolerant, and that works with Perl. + +So Riak is a document based database, it's key value, no sql, REST, and in Erlang. You can read more about it <a href="http://riak.basho.com/nyc-nosql/">here</a> or watch an introduction <a href="http://vimeo.com/6973519">here</a>. Like <ahref="http://couchdb.apache.org/">CouchDB</a>, Riak provides a REST interface, so you don't have to write any Erlang code. + +One of the nice things with Riak it's that it let you defined the N, R and W value for each operation. This values are: + + * N: the number of replicas of each value to store + * R: the number of replicas required to perform a read operation + * W: the number of replicas needed for a write operation + +Riak comes with library for python ruby PHP and even javascript, but not for Perl. As all these libraries are just communicating with Riak via the REST interface, I've <a href="http://github.com/franckcuny/anyevent-riak">started to write one</a> using AnyEvent::HTTP, and <a href="http://github.com/franckcuny/kiokudb-backend-riak">also a backend for KiokuDB</a>. + +h3. Installing and using Riak + +If you interested in Riak, you can install it easily. First, you will need the Erlang VM. On debian, a simple + +{% highlight bash %} +sudo aptitude install erlang +{% endhighlight %} + +install everything you need. Next step is to install Riak: + +{% highlight bash %} +wget http://hg.basho.com/riak/get/riak-0.6.2.tar.gz +tar xzf riak-0.6.2.tar.gz +cd riak +make +export RIAK=`pwd` +{% endhighlight %} + +Now, you can start to use it with + +{% highlight bash %} +./start-fresh config/riak-demo.erlenv +{% endhighlight %} + +or if you want to test it in cluster mode, you can write a configuration like this: + +{% highlight erlang %} + {cluster_name, "default"}. + {ring_state_dir, "priv/ringstate"}. + {ring_creation_size, 16}. + {gossip_interval, 60000}. + {storage_backend, riak_fs_backend}. + {riak_fs_backend_root, "/opt/data/riak/"}. + {riak_cookie, riak_demo_cookie}. + {riak_heart_command, "(cd $RIAK; ./start-restart.sh $RIAK/config/riak-demo.erlenv)"}. + {riak_nodename, riakdemo}. + {riak_hostname, "192.168.0.11"}. + {riak_web_ip, "192.168.0.11"}. + {riak_web_port, 8098}. + {jiak_name, "jiak"}. + {riak_web_logdir, "/tmp/riak_log"}. +{% endhighlight %} + +Copy this config on a second server, edit it to replace the riak_hostname and riak_nodename. On the first server, start it like show previously, then on the second, with + +{% highlight bash %} +./start-join.sh config/riak-demo.erlenv 192.168.0.11 +{% endhighlight %} + +where the IP address it the address of the first node in your cluster. + +Let's check if everything works: + +{% highlight bash %} +curl -X PUT -H "Content-type: application/json" \ + http://192.168.0.11:8098/jiak/blog/lumberjaph/ \ + -d "{\"bucket\":\"blog\",\"key\":\"lumberjaph\",\"object\":{\"title\":\"I'm a lumberjaph, and I'm ok\"},\"links\":[]}" + +curl -i http://192.168.0.11:8098/jiak/blog/lumberjaph/ +{% endhighlight %} + +will output (with the HTTP blabla) + +{% highlight javascript %} + {"object":{"title":"I'm a lumberjaph, and I'm ok"},"vclock":"a85hYGBgzGDKBVIsbGubKzKYEhnzWBlCTs08wpcFAA==","lastmod":"Sun, 13 Dec 2009 20:28:04 GMT","vtag":"5YSzQ7sEdI3lABkEUFcgXy","bucket":"blog","key":"lumberjaph","links":[]} +{% endhighlight %} + +h3. Using Riak with Perl and KiokuDB + +I need to store various things in Riak: html pages, json data, and objects using KiokuDB. I've started to write a client for Riak with AnyEvent, so I can do simple operations at the moment, (listing information about a bucket, defining a new bucket with a specific schema, storing, retriving and deleting documents). To create a client, you need to + +{% highlight perl %} +my $client = AnyEvent::Riak->new( + host => 'http://127.0.0.1:8098', + path => 'jiak', +); +{% endhighlight %} + +As Riak exposes to you it's N, R, and W value, you can also set them in creation the client: + +{% highlight perl %} +my $client = AnyEvent::Riak->new( + host => 'http://127.0.0.1:8098', + path => 'jiak', + r => 2, + w => 2, + dw => 2, +); +{% endhighlight %} + +where: + + * the W and DW values define that the request returns as soon as at least W nodes have received the request, and at least DW nodes have stored it in their storage backend. + * with the R value, the request returns as soon as R nodes have responded with a value or an error. You can also set this values when calling fetch, store and delete. By default, the value is set to 2. + +So, if you wan to store a value, retrieve it, then delete it, you can do: + +{% highlight perl %} +my $store = + $client->store({bucket => 'foo', key => 'bar', object => {baz => 1},}) + ->recv; +my $fetch = $client->fetch('foo', 'bar')->recv; +my $delete = $client->delete('foo', 'bar')->recv; +{% endhighlight %} + +If there is an error, the croak method from AnyEvent is used, so you may +prefer to do this: + +{% highlight perl %} +use Try::Tiny; +try { + my $fetch = $client->fetch('foo', 'baz')->recv; +} +catch { + my $err = decode_json $_; + say "error: code => " . $err->[0] . " reason => " . $err->[1]; +}; +{% endhighlight %} + +The error contains an array, with the first value the HTTP code, and the second value the reason of the error given by Riak. + +At the moment, the KiokuDB backend is not complete, but if you want to start to play with is, all you need to do is: + +{% highlight perl %} +my $dir = KiokuDB->new( + backend => KiokuDB::Backend::Riak->new( + db => AnyEvent::Riak->new( + host => 'http://localhost:8098', + path => 'jiak', + ), + bucket => 'kiokudb', + ), +); + +$dir->txn_do(sub { $dir->insert($key => $object) }); +{% endhighlight %} + diff --git a/_posts/2009-12-20-moosex-net-api.textile b/_posts/2009-12-20-moosex-net-api.textile new file mode 100644 index 0000000..939122f --- /dev/null +++ b/_posts/2009-12-20-moosex-net-api.textile @@ -0,0 +1,110 @@ +--- +layout: post +category: perl +title: MooseX::Net::API +--- + +h3. Net::Twitter + +I've been asked for "$work":http://linkfluence.net to write an API client for "backtype":http://www.backtype.com/, as we plan to integrate it in one of our services. A couple of days before I was reading the "Net::Twitter":http://search.cpan.org/perldoc?Net::Twitter source code, and I've found interesting how "semifor":http://blog.questright.com/ wrote it. + +Basically, what Net::Twitter does is this: for each API method, there is a *twitter_api_method* method, where the only code for this method is an API specification of the method. Let's look at the public timeline method: + +{% highlight perl %} +twitter_api_method home_timeline => ( + description => <<'', +Returns the 20 most recent statuses, including retweets, posted by the +authenticating user and that user's friends. This is the equivalent of +/timeline/home on the Web. + + path => 'statuses/home_timeline', + method => 'GET', + params => [qw/since_id max_id count page/], + required => [], + returns => 'ArrayRef[Status]', +); +{% endhighlight %} + +The *twitter_api_method* method is exported with Moose::Exporter. It generates a sub called *home_timeline* that is added to the class. + +h3. MooseX::Net::API + +As I've found this approch nice and simple, I thought about writing a "little framework":http://github.com/franckcuny/moosex-net-api to easily write API client this way. I will show how I've write a "client for the Backtype API":http://github.com/franckcuny/net-backtype using this (I've wrote some other client for private API at works too). + +h3. Backtype API + +First we defined our class: + +{% highlight perl %} +package Net::Backtweet; +use Moose; +use MooseX::Net::API; +{% endhighlight %} + +MooseX::Net::API export two methods: *net_api_declare* and *net_api_method*. The first method is for all the paramters that are common for each method. For Backtype, I'll get this: + +{% highlight perl %} +net_api_declare backtweet => ( + base_url => 'http://backtweets.com', + format => 'json', + format_mode => 'append', +); +{% endhighlight %} + +This set + + * the base URL for the API + * the format is JSON + * some API use an extension at the name of the method to determine the format. "append" do this. + +Right now three formats are supported: xml json and yaml. Two modes are supported: _append_ and _content-type_. + +Now the *net_api_method* method. + +{% highlight perl %} +net_api_method backtweet_search => ( + path => '/search', + method => 'GET', + params => [qw/q since key/], + required => [qw/q key/], + expected => [qw/200/], +); +{% endhighlight %} + + * path: path for the method (required) + * method: how to acces this resource (GET POST PUT and DELETE are supported) (required) + * params: list of parameters to access this resource (required) + * required: which keys are required + * expected: list of HTTP code accepted + +To use it: + +{% highlight perl %} +my $backtype = Net::Bactype->new(); +my $res = + $backtype->backtweet_search(q => "http://lumberjaph.net", key => "foo"); +warn Dump $res->{tweets}; +{% endhighlight %} + +h3. MooseX::Net::API implementation + +Now, what is done by the framework. The *net_api_declare* method add various attributes to the class: + + * api_base_url: base URL of the API + * api_format: format for the query + * api_format_mode: how the format is used (append or content-type) + * api_authentication: if the API requires authentication + * api_username: the username for accessing the resource + * api_password: the password + * api_authentication: does the resource requires to be authenticated + +It will also apply two roles, for serialization and deserialization, unless you provides your own roles for this. You can provides your own method for useragent and authentication too (the module only do basic authentication). + +For the *net_api_method* method, you can overload the authentication (in case some resources requires authentication). You can also overload the default code generated. + +In case there is an error, an MooseX::Net::API::Error will be throw. + +h3. Conclusion + +Right now, this module is not finished. I'm looking for suggestions (what should be added, done better, how I can improve stuff, ...). I'm not aiming to handle all possibles API, but at least most of the REST API avaible. I've uploaded a first version of +"MooseX::Net::API":http://search.cpan.org/perldoc?MooseX::Net::API and "Net::Backtype":http://search.cpan.org/perldoc?Net::Backtype on CPAN, and "the code":http://github.com/franckcuny/net-backtype is also "available on github":http://github.com/franckcuny/moosex-net-api. diff --git a/_posts/2009-12-21-tatsumaki-or-how-to-write-a-nice-webapp-in-less-than-two-hours.textile b/_posts/2009-12-21-tatsumaki-or-how-to-write-a-nice-webapp-in-less-than-two-hours.textile new file mode 100644 index 0000000..bd042e4 --- /dev/null +++ b/_posts/2009-12-21-tatsumaki-or-how-to-write-a-nice-webapp-in-less-than-two-hours.textile @@ -0,0 +1,102 @@ +--- +layout: post +category: perl +title: Tatsumaki, or how to write a nice webapp in less than two hours +--- + +Until today, I had a script named "lifestream.pl". This script was triggered via cron once every hour, to fetch various feeds from services I use (like <a href="http://github.com/">github</a>, <a href="http://identi.ca/">identi.ca</a>, ...) and to process the result through a template and dump the result in a HTML file. + +Today I was reading <a href="http://github.com/miyagawa/Tatsumaki">Tatsumaki's code</a> and some examples (<a href="http://github.com/gugod/Social">Social</a> and <a href="http://github.com/miyagawa/Subfeedr">Subfeedr</a>). Tatsumaki is a "port" <a href="http://www.tornadoweb.org/">tornado</a> (a non blocking server in Python), based on Plack and AnyEvent. I though that using this to replace my old lifestream script would be a good way to test it. Two hours later I have a complete webapp that works (and the code is available <a href="http://github.com/franckcuny/lifestream">here</a>). + +The code is really simple: first, I define an handler for my HTTP request. As I have only one things to do (display entries), the handler is really simple: + +{% highlight perl %} +package Lifestream::Handler; +use Moose; +extends 'Tatsumaki::Handler'; + +sub get { + my $self = shift; + my %params = %{$self->request->params}; + $self->render( + 'lifestream.html', + { memes => $self->application->memes($params{page}), + services => $self->application->services + } + ); +} +1; +{% endhighlight %} + +For all the get request, 2 methods are called : <strong>memes</strong> and <strong>services</strong>. The <strong>memes</strong> get a list of memes to display on the page. The services get the list of the various services I use (to display them on a sidebar). + +Now, as I don't want to have anymore my lifestream.pl script in cron, I will let Tatsumaki do the polling. For this, I add a service to my app, which is just a worker. + +{% highlight perl %} +package Lifestream::Worker; +use Moose; +extends 'Tatsumaki::Service'; +use Tatsumaki::HTTPClient; + +sub start { + my $self = shift; + my $t; + $t = AE::timer 0, 1800, sub { + scalar $t; + $self->fetch_feeds; + }; +} + +sub fetch_feeds { + my ($self, $url) = @_; + Tatsumaki::HTTPClient->new->get( + $url, + sub { + #do the fetch and parsing stuff + } + ); +} +{% endhighlight %} + +From now, every 60 minutes, feeds will be checked. Tatsumaki::HTTPClient is a HTTP client based on AnyEvent::HTTP. + +Let's write the app now + +{% highlight perl %} +package Lifestream; + +use Moose; +extends "Tatsumaki::Application"; + +use Lifestream::Handler; +use Lifestream::Worker; + +sub app { + my ($class, %args) = @_; + my $self = $class->new(['/' => 'Lifestream::Handler',]); + $self->config($args{config}); + $self->add_service(Lifestream::Worker->new(config => $self->config)); + $self; +} + +sub memes { +} + +sub services { +} +{% endhighlight %} + +The <strong>memes</strong> and <strong>services</strong> method called from the handler are defined here. In the app method, I "attch" the "/" path to the handler, and I add the service. + +and to launch the app + +{% highlight perl %} +my $app = Lifestream->app(config => LoadFile($config)); +require Tatsumaki::Server; +Tatsumaki::Server->new( + port => 9999, + host => 0, +)->run($app); +{% endhighlight %} + +And that's it, I now have a nice webapp, with something like only 200 LOC. I will keep playing with <a href="http://www.slideshare.net/miyagawa/tatsumaki">Tatsumaki</a> as I have more ideas (and probably subfeedr too). Thanks to <a href="http://bulknews.typepad.com/">miyagawa</a> for all this code. diff --git a/_posts/2010-01-31-dancer-1.130.textile b/_posts/2010-01-31-dancer-1.130.textile new file mode 100644 index 0000000..241deca --- /dev/null +++ b/_posts/2010-01-31-dancer-1.130.textile @@ -0,0 +1,49 @@ +--- +layout: post +category: perl +title: Dancer 1.130 +--- + +"Alexis":http://www.sukria.net/ ("sukria":http://search.cpan.org/~sukria/) released "Dancer":http://search.cpan.org/perldoc?Dancer 1.130 this weekend. Dancer is a small and nice web framework based on ruby's "sinatra":http://www.sinatrarb.com/. + +Dancer have few dependancies (and it doesn't depends anymore on CGI.pm). The path dispatching is done using rules declared with HTTP methods (get/post/put/delete), and they are mapped to a sub-routine which is returned as the response to the request. Sessions are supported, and two template engines (one of them is Template Toolkit) comes with the Core. Dancer::Template::MicroTemplate is also available on CPAN if you need a light template engine. + +You can easily test it with a simple script + +{% highlight perl %} +#!/usr/bin/env perl +use Dancer; + +get '/' => sub { + return "dancer"; +}; + +get '/:name' => sub { + return params->{name} . " is dancing"; +}; + +dance; +{% endhighlight %} + +and execute this script, point your browser to http://127.0.0.1:3000, and voila. + +Dancer provides also a small helper to write a new application: + +bc. dancer -a MyApplication + +If you create an application with this script, an **app.psgi** file will be created. You can now execute + +bc. plackup --port 8080 + +(which comes with "Plack":http://search.cpan.org/perldoc?Plack the "Perl Web Server":http://plackperl.org/) and test if everything works fine: + +bc. curl http://localhost:8080 + +This release remove some components from the core and they are now available as differents CPAN distributions. Two new keyword have also been added, *header* and *prefix*. + +If you want to read more about Dancer: + + * "Dancer's documentation":http://search.cpan.org/perldoc?Dancer + * "review by xsawyerx":http://blogs.perl.org/users/sawyer_x/2010/01/i-gotz-me-a-dancer.html + * "gugod's review":http://gugod.org/2009/12/dancer.html + * "sukria's blog":http://www.sukria.net/fr/archives/tag/dancer/ diff --git a/_posts/2010-02-03-sd-and-github.textile b/_posts/2010-02-03-sd-and-github.textile new file mode 100644 index 0000000..c95c008 --- /dev/null +++ b/_posts/2010-02-03-sd-and-github.textile @@ -0,0 +1,20 @@ +--- +layout: post +category: app +title: SD and github +--- + +If you are using the version of <a href="http://syncwith.us/">SD</a> hosted on <a href="http://github.com/bestpractical/sd">github</a>, you can now clone and pull issues very easily. First, + +{% highlight bash %} +$ git config --global github.user franckcuny +$ git config --global github.token myapitoken +{% endhighlight %} + +This will set in your <strong>.gitconfig</strong> your github username and api token. Now, when you clone or pull some issues using sd: + +{% highlight bash %} +$ git sd clone --from github:sukria/Dancer +{% endhighlight %} + +sd will check your .gitconfig to find your credentials. diff --git a/_posts/2010-03-07-github-explorer-a-preview.textile b/_posts/2010-03-07-github-explorer-a-preview.textile new file mode 100644 index 0000000..8b8fadc --- /dev/null +++ b/_posts/2010-03-07-github-explorer-a-preview.textile @@ -0,0 +1,21 @@ +--- +layout: post +category: graph +title: github explorer - a preview +--- + +bq. *You may want to see the final version here: "github explorer":httpp://lumberjaph.net/blog/index.php/2010/03/25/github-explorer/* + +For the last weeks, I've been working on the successor of "CPAN Explorer":http://cpan-explorer.org/. This time, I've decided to create some visualizations (probably 8) of the various communities using "Github":http://github.com/. I'm happy with the result, and will soon start to publish the maps (statics and interactives) with some analyses. I'm publishing two previews: the Perl community and the european developers. These are not final results. The colors, fonts, and layout may change. But the structure of the graphs will be the same. All the data was collected using the "github API":http://develop.github.com/. + +<a href="http://www.flickr.com/photos/franck_/4413528529/sizes/l/">!/static/imgs/github-perl-preview.png(the Perl community on github)</a> + +Each node on the graph represents a developer. When a developer "follows" another developer on github, a link between them is created. The color on the Perl community map represent the countries of the developer. One of the most visible things on this graph is that the japanese community is tighly connected and shares very little contact with the foreign developers. miyagawa obviously acts as a glue between japanese and worldwide Perl hackers. + +<a href="http://www.flickr.com/photos/franck_/4414287310/sizes/o/in/photostream/">!static/imgs/github-europe-preview.png(European developers on github)</a> + +The second graph is a little bit more complex. It represents the European developers on github. Here the colors represent the languages used by the developers. It appears that ruby is by far the most represented language on github, as it dominates the whole map. Perl is the blue cluster at the bottom of the map, and the green snake is... Python. + +Thanks to "bl0b":http://code.google.com/p/tinyaml/ for his suggestions :) + + diff --git a/_posts/2010-03-19-easily-create-rest-interface-with-the-dancer-1.170.textile b/_posts/2010-03-19-easily-create-rest-interface-with-the-dancer-1.170.textile new file mode 100644 index 0000000..20dfe1d --- /dev/null +++ b/_posts/2010-03-19-easily-create-rest-interface-with-the-dancer-1.170.textile @@ -0,0 +1,146 @@ +--- +layout: post +category: perl +title: Easily create REST interface with the Dancer 1.170 +--- + +This week, with "Alexi":http://www.sukria.net/fr/'s help, "I've been working on":http://github.com/sukria/Dancer on adding auto-(de)serialization to Dancer's request. This features will be available in the next "Dancer":http://perldancer.org/ version, the 1.170 (which will be out before April). + +The basic idea was to provides to developer a simple way to access data that have been send in a serialized format, and to properly serialize the response. + +At the moment, the supported serializers are : + + * Dancer::Serialize::JSON + * Dancer::Serialize::YAML + * Dancer::Serialize::XML + * Dancer::Serialize::Mutable + +h3. Configuring an application to use the serializer + +To activate serialization in your application: + +bc. set serializer => 'JSON'; + +or in your configuration file: + +bc. serializer: "JSON" + +h3. A simple handler + +Let's create a new dancer application (you can fetch the source on +[github](http://github.com/franckcuny/dancerREST) : + +bq. dancer -a dancerREST +cd dancerREST +vim dancerREST.pm + +then + +{% highlight perl %} +package dancerREST; +use Dancer ':syntax'; + +my %users = (); + +post '/api/user/' => sub { + my $params = request->params; + if ($params->{name} && $params->{id}) { + if (exists $users{$params->{id}}) { + return {error => "user already exists"}; + } + $users{$params->{id}} = {name => $params->{name}}; + return {id => $params->{id}, name => $params->{name}}; + } + else { + return {error => "name is missing"}; + } +}; + +true; +{% endhighlight %} + +We can test if everything works as expected: + +bq. plackup app.psgi & +curl -H "Content-Type: application/json" -X POST http://localhost:5000/api/user/ -d '{"name":"foo","id":1}' +# => {"name":"foo","id":"1"} + +Now we add a method to fetch a list of users, and a method to get a +specific user: + +{% highlight perl %} +# return a specific user +get '/api/user/:id' => sub { + my $params = request->params; + if (exists $users{$params->{id}}) { + return $users{$params->{id}}; + } + else { + return {error => "unknown user"}; + } +}; + +# return a list of users +get '/api/user/' => sub { + my @users; + push @users, {name => $users{$_}->{name}, id => $_} + foreach keys %users; + return \@users; +}; +{% endhighlight %} + +If we want to fetch the full list: + +bc. curl -H "Content-Type: application/json" http://localhost:5000/api/user/ +# => [{"name":"foo","id":"1"}] + +and a specific user: + +bc. curl -H "Content-Type: application/json" http://localhost:5000/api/user/1 +# => {"name":"foo"} + +h3. The mutable serializer + +The mutable serializer will try to load an appropriate serializer guessing from the *Content-Type* and *Accept-Type* header. You can also overload this by adding a *content_type=application/json* parameter to your request. + +While setting your serializer to mutable, your let your user decide which format they prefer between YAML, JSON and XML. + +h3. And the bonus + +Dancer provides now a new method to the request object : *is_ajax*. Now you can write something like + +{% highlight perl %} +get '/user/:id' => sub { + my $params = request->params; + my $user = $users{$params->{id}}; + my $result; + if (!$user) { + _render_user({error => "unknown user"}); + } + else { + _render_user($user); + } +}; + +sub _render_user { + my $result = shift; + if (request->is_ajax) { + return $result; + } + else { + template 'user.tt', $result; + } +} +{% endhighlight %} + +If we want to simulate an AJAX query: + +bc. curl -H "X-Requested-With: XMLHttpRequest" http://localhost:5000/user/1 + +and we will obtain our result in JSON. But we can also test without the X-Requested-With: + +bc. curl http://localhost:5000/user/1 + +and the template will be rendered. + +Hope you like this new features. I've also been working on something similar for "Tatsumaki":http://github.com/miyagawa/tatsumaki. diff --git a/_posts/2010-03-25-github-explorer.textile b/_posts/2010-03-25-github-explorer.textile new file mode 100644 index 0000000..a1f96a0 --- /dev/null +++ b/_posts/2010-03-25-github-explorer.textile @@ -0,0 +1,140 @@ +--- +layout: post +category: graph +title: Github explorer +--- + +bq. *More informations about the poster are available on "this post":http://lumberjaph.net/blog/index.php/2010/04/02/github-poster/* + +Last year, with help from my coworkers at "Linkfluence":http://linkfluence.net/, I created two sets of maps of the "Perl":http://perl.org and "CPAN":http://search.cpan.org/'s community. For this, I collected data from CPAN to create three maps: + + * "dependencies between distributions":http://cpan-explorer.org/2009/07/28/new-version-of-the-distributions-map-for-yapceu/ + + * "which authors wre important in term of reliability":http://cpan-explorer.org/2009/07/28/version-of-the-authors-graph-for-yapceu/ + + * "and how the websites theses authors are structured":http://cpan-explorer.org/2009/07/28/new-web-communities-map-for-yapceu/ + +I wanted to do something similar again, but not with the same data. So I took a look at what could be a good subject. One of the things that we saw from the map of the websites is the importance [github](http://github.com/) is gaining inside the Perl community. Github provides a [really good API](http://develop.github.com/), so I started to play with it. + +bq. This graph will be printed on a poster, size will be "A2":http://en.wikipedia.org/wiki/A2_paper_size and "A1":http://en.wikipedia.org/wiki/A1_paper_size". Please, contact me *(franck.cuny [at] linkfluence.net)* if you will be interested by one. + +<center>!/static/imgs/general.png(global)</center> + +<center>!/static/imgs/zoom.png(zoom)</center> + +This time, I didn't aim for the Perl community only, but the whole github communities. I've created several graphs: + +bq. all the graph are available "on my flickr account":http://www.flickr.com/photos/franck_/sets/72157623447857405/ + + * "a graph of all languages":http://www.flickr.com/photos/franck_/4460144638/ + * "a graph of the Perl community":http://www.flickr.com/photos/franck_/4456072255/in/set-72157623447857405/ + * "a graph of the Ruby community":http://www.flickr.com/photos/franck_/4456914448/ + * "a graph of the Python community":http://www.flickr.com/photos/franck_/4456118597/in/set-72157623447857405/ + * "a graph of the PHP community":http://www.flickr.com/photos/franck_/4456830956/in/set-72157623447857405/ + * "a graph of the European community":http://www.flickr.com/photos/franck_/4456862434/in/set-72157623447857405/ + * "a graph of the Japan community":http://www.flickr.com/photos/franck_/4456129655/in/set-72157623447857405/ + +I think a disclaimer is important at this point. I know that github doesn't represent the whole open source community. With these maps, I don't claim to represent what the open source world looks like right now. This is not a troll about which language is best, or used at large. It's *ONLY* about github. + +Also, I won't provide deep analysis for each of these graphs, as I lack insight about some of those communities. So feel free to "re-use the graphs":http://franck.lumberjaph.net/graphs.tgz and provide your own analyses. + +h3. Methodology + +I didn't collect all the profiles. We (with "Guilhem":http://twitter.com/gfouetil decided to limit to peoples who are followed by at least two other people. We did the same thing for repositories, limiting to repositories which are at least forked once. Using this technique, more than 17k profiles have been collected, and nearly as many repositories. + +For each profile, using the github API, I've tried to determine what the main language for this person is. And with the help of the "geonames":http://www.geonames.org, find the right country to attach the profile to. + +Each profile is represented by a node. For each node, the following attributes are set: + + * name of the profile + * main language used by this profile, determined by github + * name of the country + * follower count + * following count + * repository count + +An edge is a link between two profiles. Each time someone follows another profile, a link is created. By default, the weight of this link is 1. For each project this person forked from the target profile, the weight is incremented. + +As always, I've used "Gephi":http://gephi.org/ (now in version 0.7) to create the graphs. Feel free to download the various graph files and use them with Gephi. + +h3. Github + +bq. properties of the graph: 16443 nodes / 130650 edges + +<a href="http://www.flickr.com/photos/franck_/4460144638/" title="Github - All - by languages by franck.cuny, on Flickr"><img src="http://farm5.static.flickr.com/4027/4460144638_48e7d83e80.jpg" width="482" height="500" alt="Github - All - by languages" /></a> + +The first map is about all the languages available on github. This one was really heavy, with more than 17k nodes, and 130k edges. The final version of the graph use the 2270 more connected nodes. + +You can't miss Ruby on this map. As github uses Ruby on Rails, it's not really surprising that the Ruby community has a particular interest on this website. The main languages on github are what we can expect, with PHP, Python, Perl, Javascript. + +Some languages are not really well represented. We can assume that most Haskell projects might use darcs, and therefore are not on github. Some other languages may use other platforms, like launchpad, or sourceforge. + +h3. Perl + +bq. properties of the graph: 365 nodes / 4440 edges + +<a href="http://www.flickr.com/photos/franck_/4456842344/" title="Perl community on Github by franck.cuny, on Flickr"><img src="http://farm5.static.flickr.com/4002/4456842344_06f39127a8.jpg" width="500" height="437" alt="Perl community on Github" /></a> + +The Perl community is split into two parts. On the left side, there is the occidental community, driven by people like "Florian":http://github.com/rafl, "Yuval":http://github.com/nothingmuch, "rjbs":http://github.com/rjbs, ... The second part are the japanese Perl hackers, with <a href="http://github.com/tokuhirom">Tokhuirom</a>, <a href="http://github.com/typester">Typester</a>, <a href="http://github.com/yappo">Yappo</a>, ... And in between them, <a href="http://github.com/miyagawa">Miyagawa</a> acts as a glue. This map looks a lot like the previous map of the CPAN. We can see that this community is international, with the exception of Japan that don't mix with others. + +There is no main project on github that gathers people, even though we can see a fair amount of <strong>MooseX::</strong> projects. Most of the developers will work on different modules, that may not have the same purpose. Lately we have seen a fair amount of work on various <a href="http://plackperl.org/">Plack</a> stuff, mainly middleware, but also HTTP servers (twiggy, starman, ...) and web framework (<a href="http://perldancer.org/">dancer</a>). + +One important project that is not (deliberately) represented on this graph is the <a href="http://github.com/gitpan">gitpan</a>, <a href="http://github.com/schwern">Schwern</a>'s project. The gitpan is an import of all the CPAN modules, with complete history using the Backpan. + +To conclude about Perl, there are only 365 nodes on this graph, but no less than 4440 edges. That's nearly two times the number of edges compared to the Python community. Perl is a really well structured community, probably thanks to the CPAN, which already acted as hub for contributors. + +h3. Python + +<blockquote>properties of the graph: 532 nodes / 2566 edges</blockquote> + +<a href="http://www.flickr.com/photos/franck_/4456118597/" title="Python community, by country, on Github by franck.cuny, on Flickr"><img src="http://farm3.static.flickr.com/2676/4456118597_9d39f8d413.jpg" width="470" height="500" alt="Python community, by country, on Github" /></a> + +The Python community looks a lot like the Perl community, but only in the structure of the graph. If we look closely, <a href="http://www.djangoproject.com/">Django</a> is the main project that represent Python on Github, in contrast with Perl where there is no leader. Some small projects gather small community of developers. + +h3. PHP + +<blockquote>properties of the graph: 301 nodes / 1071 edges</blockquote> + +<a href="http://www.flickr.com/photos/franck_/4456830956/" title="PHP community on Github by franck.cuny, on Flickr"><img src="http://farm5.static.flickr.com/4033/4456830956_ef0e8f3587.jpg" width="500" height="372" alt="PHP community on Github" /></a> + +PHP is the only community that is structured this way on Github. We can clearly see that people are structured based on a project where they mainly contribute. + +<a href="http://cakephp.org/">CakePHP</a> and <a href="http://www.symfony-project.org/">Symphony</a> are the two main projects. Nearly all the projects gather an international community, at the exception of a few japanese-only projects + +h3. Ruby + +<blockquote>properties of the graph: 3742 nodes / 24571 edges</blockquote> + +<a href="http://www.flickr.com/photos/franck_/4456914448/" title="Ruby community, by country, on Github by franck.cuny, on Flickr"><img src="http://farm5.static.flickr.com/4012/4456914448_8089c3acca.jpg" width="500" height="469" alt="Ruby community, by country, on Github" /></a> + +As for the Github graph, we can clearly see that some countries are isolated. On the right side, we have: the Japan community is at the bottom; the Spanish at the top. Australian are represented on the upper right corner, while on the left side we got the Brazilians. + +The main projects that gather most of the hackers are <a href="http://rubyonrails.org/">Rails</a> and <a href="http://sinatrarb.com/">Sinatra</a>, two famous web frameworks. + +h3. Europe + +<blockquote>properties of the graph: 2711 nodes / 11259 edges</blockquote> + +<a href="http://www.flickr.com/photos/franck_/4456862434/" title="Europe community on Github by franck.cuny, on Flickr"><img src="http://farm5.static.flickr.com/4062/4456862434_324e7b2c75.jpg" width="500" height="450" alt="Europe community on Github" /></a> + +This one shows interesting features. Some countries are really isolated. If we look at Spain, we can see a community of Ruby programmers, with an important connectivity between them, but no really strong connection with any foreign developers. We can clearly see the Perl community exists as only one community, and is not split by country. The same is true for Python. + +h3. Japanese hackers community + +<blockquote>properties of the graph: 559 nodes / 5276 edges</blockquote> + +<a href="http://www.flickr.com/photos/franck_/4456129655/" title="Japan community on github by franck.cuny, on Flickr"><img src="http://farm3.static.flickr.com/2800/4456129655_8c6f7f20a0.jpg" width="500" height="410" alt="Japan community on github" /></a> + +This community is unique on github. In 2007, <a href="http://github.com/yappo">Yappo</a> created <a href="http://coderepos.org/share/">coderepos.org</a>, a repository for open source developers in Japan. It was a subversion repository, with Trac as an HTTP front-end. It gathered around 900 developers, with all kind of projects (Perl, Python, Ruby, Javascript, ...). Most of these users have switched to github now. + +Three main communities are visible on this graph: Perl; Ruby; PHP. As always, the Javascript community as a glue between them. And yes, we can confirm that Perl is big in Japan. + +We have seen in the previous graph that the Japanese hackers are always isolated. We can assume that their language is an obstacle. + +This is a really well-connected graph too. + +h3. Conclusions and graphs + +I may have not provided a deep analysis of all the graph. I don't have knowledge of most of the community outside of Perl. Feel free to <a href="http://franck.lumberjaph.net/graphs.tgz">download the graph</a>, to load them in <a href="http://gephi.org/">Gephi</a>, experiment, and provides your own thoughts. + +I would like to thanks everybody at <a href="http://twitter.com/linkfluence">Linkfluence</a> (<a href="http://twitter.com/gfouetil">guilhem</a> for his advices, <a href="http://twitter.com/cmaussan">camille</a> for giving me time to work on this, and antonin for the amazing poster), who have helped me and let me use time and resources to finish this work. Special thanks to <a href="http://code.google.com/p/tinyaml/">blob</a> for reviewing my prose and <a href="http://twitter.com/cdlm">cdlm</a> for the discussion :) diff --git a/_posts/2010-04-02-github-poster.textile b/_posts/2010-04-02-github-poster.textile new file mode 100644 index 0000000..b111771 --- /dev/null +++ b/_posts/2010-04-02-github-poster.textile @@ -0,0 +1,27 @@ +--- +layout: post +category: graph +title: Github Poster +--- + +The "Github poster":http://fr.linkfluence.net/posters/ is available as a PDF on the "linkfluence atlas":http://fr.linkfluence.net/insights-2-0/atlas/. + +It's distributed under a "Attribution-Noncommercial-No Derivative Works 3.0 Unported":http://creativecommons.org/licenses/by-nc-nd/3.0/ Creative Commons license, and you are free to print it. + +It's optimized for a "A2":http://en.wikipedia.org/wiki/A2_paper_size size. You shouldn't have any problem to print it at a bigger size though, as it's vectorial. + +We ("linkfluence":http://linkfluence.net/) really wanted to print and send the poster to people who were interested. But this is too complicated and too much work to handle by ourselves. If you do print a poster, please send us a notice, for we would love to know where it may end up :) + +However, for lazy/busy people, I plan to attend the following Perl conferences : + + * "French Perl Workshop":http://journeesperl.fr/fpw2010/ + * "Belgian Perl Workshop":http://www.perlworkshop.be/bpw2010/ + * "YAPC::EU":http://conferences.yapceurope.org/ye2010/ + +so if you are interested in buying a poster and contact me early enough, I'll print it and bring it with me. The cost should be between 35 and 50 euros per poster (this is the raw cost). + +I would like to thank all the people who emailed me, and I'm really sorry I'm unable to provide each of you a poster. + +<center> +<a href="http://fr.linkfluence.net/posters/"><img style="border: 1px solid #000;" src="http://fr.linkfluence.net/wp-content/images/atlas/github_thumb.png" /></a> +</center> diff --git a/_posts/2010-04-03-more-fun-with-tatsumaki-and-plack.textile b/_posts/2010-04-03-more-fun-with-tatsumaki-and-plack.textile new file mode 100644 index 0000000..21aaa0c --- /dev/null +++ b/_posts/2010-04-03-more-fun-with-tatsumaki-and-plack.textile @@ -0,0 +1,166 @@ +--- +layout: post +category: perl +title: More fun with Tatsumaki and Plack +--- + +Lately I've been toying a lot with "Plack":http://plackperl.org/ and two Perl web framework: "Tatsumaki":http://search.cpan.org/perldoc?Tatsumaki and "Dancer":http://search.cpan.org/perldoc?Dancer. I use both of them for different purposes, as their features complete each other. + +h3. Plack + +If you don't already know what Plack is, you would want to take a look at the following Plack resources: + + * "Plack (redesigned) website":http://plackperl.org + * "Plack documentation":http://search.cpan.org/perldoc?Plack + * "miyagawa's screencast":http://bulknews.typepad.com/blog/2009/11/plack-and-psgi-screencast-and-feedbacks.html + * "Plack advent calendar":http://advent.plackperl.org/ + +.bq As "sukria":http://www.sukria.net/ is planning to talk about "Dancer":http://perldancer.org during the "FPW 2010":http://journeesperl.fr/fpw2010/index.html, I will probably do a talk about Plack. + +After reading some code, I've started to write two middleware: the first one add ETag header to the HTTP response, and the second one provides a way to limit access to your application. + +h4. Plack::Middleware::ETag + +This middleware is really simple: for each request, an "ETag":http://en.wikipedia.org/wiki/HTTP_ETag header is added to the response. The ETag value is a sha1 of the response's content. In case the content is a file, it works like apache, using various information from the file: inode, modified time and size. This middleware can be used with "Plack::Middleware::ConditionalGET":http://search.cpan.org/perldoc?Plack::Middleware::ConditionalGET, so the client will have the ETag information for the page, and when he will do a request next time, it will send an "if-modified" header. If the ETag is the same, a 304 response will be send, meaning the content have not been modified. This module is "available on CPAN":http://search.cpan.org/perldoc?Plack::Middleware::ETag. + +Let's see how it works. First, we create a really simple application (we call it app.psgi): + +{% highlight perl %} +#!/usr/bin/env perl +use strict; +use warnings; +use Plack::Builder; + +builder { + enable "Plack::Middleware::ConditionalGET"; + enable "Plack::Middleware::ETag"; + sub { + ['200', ['Content-Type' => 'text/html'], ['Hello world']]; + }; +}; +{% endhighlight %} + +Now we can test it: + +{% highlight bash %} +> plackup app.psgi& +> curl -D - http://localhost:5000 +HTTP/1.0 200 OK +Date: Sat, 03 Apr 2010 09:31:43 GMT +Server: HTTP::Server::PSGI +Content-Type: text/html +ETag: 7b502c3a1f48c8609ae212cdfb639dee39673f5e +Content-Length: 11 + +> curl -H "If-None-Match: 7b502c3a1f48c8609ae212cdfb639dee39673f5e" -D - http://localhost:5000 +HTTP/1.0 304 Not Modified +Date: Sat, 03 Apr 2010 09:31:45 GMT +Server: HTTP::Server::PSGI +ETag: 7b502c3a1f48c8609ae212cdfb639dee39673f5e +{% endhighlight %} + +h4. Plack::Middleware::Throttle + +"With this middleware":http://github.com/franckcuny/plack--middleware--throttle, you can control how many times you want to provide an access to your application. This module is not yet on CPAN, has I want to add some features, but you can get the code on github. There is four methods to control access: + + * Plack::Middleware::Throttle::Hourly: how many times in one hour someone can access the application + * P::M::T::Daily: the same, but for a day + * P::M::T::Interval: which interval the client must wait between two query + * by combining the three previous methods + +To store sessions informations, you can use any cache backend that provides *get*, *set* and *incr* methods. By default, if no backend is provided, it will store informations in a hash. You can easily modify the defaults throttling strategies by subclassing all the classes. + +Let's write another application to test it: + +{% highlight perl %} +#!/usr/bin/env perl +use strict; +use warnings; +use Plack::Builder; + +builder { + enable "Plack::Middleware::Throttle::Hourly", max => 2; + sub { + ['200', ['Content-Type' => 'text/html'], ['Hello world']]; + }; +}; +{% endhighlight %} + +then test + +{% highlight bash %} +$ curl -D - http://localhost:5000/ +HTTP/1.0 200 OK +Date: Sat, 03 Apr 2010 09:57:40 GMT +Server: HTTP::Server::PSGI +Content-Type: text/html +X-RateLimit-Limit: 2 +X-RateLimit-Remaining: 1 +X-RateLimit-Reset: 140 +Content-Length: 11 + +Hello world + +$ curl -D - http://localhost:5000/ +HTTP/1.0 200 OK +Date: Sat, 03 Apr 2010 09:57:40 GMT +Server: HTTP::Server::PSGI +Content-Type: text/html +X-RateLimit-Limit: 2 +X-RateLimit-Remaining: 0 +X-RateLimit-Reset: 140 +Content-Length: 11 + +Hello world + +$ curl -D - http://localhost:5000/ +HTTP/1.0 503 Service Unavailable +Date: Sat, 03 Apr 2010 09:57:41 GMT +Server: HTTP::Server::PSGI +Content-Type: text/plain +X-RateLimit-Reset: 139 +Content-Length: 15 + +Over rate limit +{% endhighlight %} + +Some HTTP headers are added to the response : + + * *X-RateLimit-Limit*: how many request can be done + * *X-RateLimit-Remaining*: how many requests are available + * *X-RateLimit-Reset*: when will the counter be reseted (in seconds) + +This middleware could be a very good companion to the "Dancer REST stuff":http://www.sukria.net/fr/archives/2010/03/19/let-the-dancer-rest/ "added recently":http://lumberjaph.net/blog/index.php/2010/03/19/easily-create-rest-interface-with-the-dancer-1170/. + +h3. another Tatsumaki application with Plack middlewares + +To demonstrate the use of this two middleware, "I've wrote a small application":http://github.com/franckcuny/feeddiscovery with Tatsumaki. This application fetch a page, parse it to find all the feeds declared, and return a JSON with the result. + +{% highlight bash %} + GET http://feeddiscover.tirnan0g.org/?url=http://lumberjaph.net/blog/ +{% endhighlight %} + +will return + +{% highlight javascript %} + [{"href":"http://lumberjaph.net/blog/index.php/feed/","type":"application/rss+xml","title":"iâm a lumberjaph RSS Feed"}] +{% endhighlight %} + +This application is composed of one handler, that handle only *GET* request. The request will fetch the url given in the *url* parameter, scrap the content to find the links to feeds, and cache the result with Redis. The response is a JSON string with the informations. + +The interesting part is the app.psgi file: + +{% highlight perl %} +my $app = Tatsumaki::Application->new(['/' => 'FeedDiscovery::Handler'],); + +builder { + enable "Plack::Middleware::ConditionalGET"; + enable "Plack::Middleware::ETag"; + enable "Plack::Middleware::Throttle::Hourly", + backend => Redis->new(server => '127.0.0.1:6379',), + max => 100; + $app; +}; +{% endhighlight %} + +The application itself is really simple: for a given url, the Tatsumaki::HTTPClient fetch an url, I use "Web::Scraper":http://search.cpan.org/perldoc?Web::Scraper to find the *link rel="alternate"* from the page, if something is found, it's stored in Redis, then a JSON string is returned to the client. diff --git a/_posts/2010-04-14-presque-a-redis-tatsumaki-based-message-queue.textile b/_posts/2010-04-14-presque-a-redis-tatsumaki-based-message-queue.textile new file mode 100644 index 0000000..c23d43d --- /dev/null +++ b/_posts/2010-04-14-presque-a-redis-tatsumaki-based-message-queue.textile @@ -0,0 +1,87 @@ +--- +layout: post +category: perl +title: presque, a Redis / Tatsumaki based message queue +--- + +"presque":http://github.com/franckcuny/presque/tree/ is a small message queue service build on top of "redis":http://code.google.com/p/redis/ and "Tatsumaki":http://search.cpan.org/perldoc?Tatsumaki. It's heavily inspired by "RestMQ":http://github.com/gleicon/restmq and "resque":http://github.com/defunkt/resque. + + * Communications are done in JSON over HTTP + * Queues and messages are organized as REST resources + * A worker can be writen in any language that make a HTTP request and read JSON + * Thanks to redis, the queues are persistent + +h3. Overview + +resque need a configuration file, writen in YAML that contains the host and port for the Redis server. + +{% highlight yaml %} +redis: + host: 127.0.0.1 + port: 6379 +{% endhighlight %} + +Let's start the server: + +{% highlight bash %} +$ plackup app.psgi --port 5000 +{% endhighlight %} + +The applications provides some HTTP routes: + + * */*: a basic HTML page with some information about the queues + * */q/*: REST API to get and post job to a queue + * */j/*: REST API to get some information about a queue + * */control/*: REST API to control a queue (start or stop consumers) + * */stats/*: REST API to fetch some stats (displayed on the index page) + +Queues are created on the fly, when a job for an unknown queue is inserted. When a new job is created, the JSON send in the POST will be stored "as is". There is no restriction on the schema or the content of the JSON. + +Creating a new job simply consist to : + +{% highlight bash %} +curl -X POST "http://localhost:5000/q/foo" -d '{"foo":"bar", "foo2":"bar" }' +{% endhighlight %} + +and fetching the job: + +{% highlight bash %} +curl "http://localhost:5000/q/foo" +{% endhighlight %} + +When a job is fetched, it's removed from the queue. + +h3. A basic worker + +I've also uploaded "presque::worker":http://github.com/franckcuny/presque-worker/tree/ to github. It's based on "AnyEvent::HTTP":http://search.cpan.org/perldoc?AnyEvent::HTTP and "Moose":http://search.cpan.org/perldoc?Moose. Let's write a basic worker using this class: + +{% highlight perl %} +use strict; +use warnings; +use 5.012; # w00t + +package simple::worker; +use Moose; +extends 'presque::worker'; + +sub work { + my ($self, $job) = @_; + say "job's done"; + ...; # yadda yadda! + return; +} + +package main; +use AnyEvent; + +my $worker = + simple::worker->new(base_uri => 'http://localhost:5000', queue => 'foo'); + +AnyEvent->condvar->recv; +{% endhighlight %} + +A worker have to extends the presque::worker class, and implement the method *work*. When the object is created, the class check if this method is avalaible. You can also provide a **fail** method, which will be called when an error occur. + +h3. The future + +I plan to add support for "websocket":http://en.wikipedia.org/wiki/WebSocket, and probably "XMPP":http://en.wikipedia.org/wiki/Xmpp. More functionalities to the worker too: logging, forking, handling many queues, ... I would like to add priorities to queue also, and maybe scheluding job for a given date (not sure if it's feasable with Redis). diff --git a/_posts/2010-04-19-the-dancer-ecosystem.textile b/_posts/2010-04-19-the-dancer-ecosystem.textile new file mode 100644 index 0000000..9c33d11 --- /dev/null +++ b/_posts/2010-04-19-the-dancer-ecosystem.textile @@ -0,0 +1,107 @@ +--- +layout: post +category: perl +title: The Dancer Ecosystem +--- + +Even though it's still a young project, an active community is starting to emerge around <a href="http://search.cpan.org/perldoc?Dancer">Dancer</a>. Some modules start to appear on CPAN and github to add functionalities, or to extend existing ones. + +h3. Templates + +By default, Dancer comes with support for two templating systems: <a href="http://search.cpan.org/dist/Template-Toolkit/">Template Toolkit</a> and Dancer::Template::Simple, a small templating engine written by <a href="http://www.sukria.net/">sukria</a>. But support for other templating systems are available: + + + * <a href="http://search.cpan.org/perldoc?Dancer::Template::Tenjin">Dancer::Template::Tenjin</a> by ido + * <a href="http://search.cpan.org/perldoc?Dancer::Template::Sandbox">Dancer::Template::Sandbox</a> by Sam Graham + * <a href="http://search.cpan.org/perldoc?Dancer::Template::Tiny">Dancer::Template::Tiny</a> by Sawyer + * <a href="http://search.cpan.org/perldoc?Dancer::Template::MicroTemplate">Dancer::Template::MicroTemplate</a> by me + * <a href="http://search.cpan.org/perldoc?Dancer::Template::Mason">Dancer::Template::Mason</a> by Yanick Champoux + * <a href="http://search.cpan.org/perldoc?Dancer::Template::Haml">Dancer::Template::Haml</a> by David Moreno + +h3. Logger + +Out of the box, Dancer only has a simple logging system to write to file, but more logging sytems are available: + +<ul> +<li><a href="http://search.cpan.org/perldoc?Dancer::Logger::Syslog">Dancer::Logger::Syslog</a> by sukria</li> +<li><a href="http://search.cpan.org/perldoc?Dancer::Logger::LogHandler">Dancer::Logger::LogHandler</a> by me</li> +<li><a href="http://github.com/franckcuny/Dancer-Logger-PSGI">Dancer::Logger::PSGI</a> by me</li> +</ul> + +The last one is for writing directly your log message via <ah href="http://search.cpan.org/perldoc?Plack">Plack</a>. You can use a middleware like <a href="http://search.cpan.org/~miyagawa/Plack-0.9932/lib/Plack/Middleware/LogDispatch.pm">P::M::LogDispatch</a> or <a href="http://search.cpan.org/~miyagawa/Plack-0.9932/lib/Plack/Middleware/Log4perl.pm">P::M::Log4perl</a> to handle logs for your application. Even better, if you use <a href="http://github.com/miyagawa/Plack-Middleware-ConsoleLogger">P::M::ConsoleLogger</a>, you can have logs from your Dancer application in your javascript console. + +### Debug + +To debug your application with Plack, you can use the awesome <a href="http://search.cpan.org/perldoc?Plack::Middleware::Debug">Plack::Middleware::Debug</a>. I've writen <a href="http://github.com/franckcuny/dancer-debug">Dancer::Debug</a> (which requires my fork of <a href="http://github.com/franckcuny/Plack-Middleware-Debug">P::M::Debug</a>), a middleware that add panels, with specific informations for Dancer applications. + +<a href="http://www.flickr.com/photos/franck_/4535496880/" title="Dancer::Debug middleware by franck.cuny, on Flickr"><img src="http://farm3.static.flickr.com/2750/4535496880_37e5e68a57.jpg" width="500" height="313" alt="Dancer::Debug middleware" /></a> + +To activate this middleware, update your app.psgi to make it look like this: + +{% highlight perl %} +my $handler = sub { + my $env = shift; + my $request = Dancer::Request->new($env); + Dancer->dance($request); +}; +$handler = builder { + enable "Debug", panels => [ + qw/Dancer::Settings Dancer::Logger Environment Memory + ModuleVersions Response Session Parameters Dancer::Version / + ]; + $handler; +}; +{% endhighlight %} + +h3. Plugins + +Dancer has support for plugins since a few version. There is not a lot of plugins at the moment, but this will soon improve. Plugins support is one of the top priorities for the 1.2 release. + +h4. Dancer::Plugin::REST + +<a href="http://github.com/sukria/Dancer-Plugin-REST">This one is really nice</a>. This plugin, used with the serialization stuff, allow you to write easily REST application. + +{% highlight perl %} +resource user => get => sub { # return user where id = params->{id} }, + create => sub { # create a new user with params->{user} }, + delete => sub { # delete user where id = params->{id} }, + update => sub { # update user with params->{user} }; +{% endhighlight %} + +And you got the following routes: + + * GET /user/:id + * GET /user/:id.:format + * POST /user/create + * POST /user/create.:format + * DELETE /user/:id + * DELETE /user/:id.:format + * PUT /user/:id + * PUT /user/:id.:format + +h4. Dancer::Plugin::Database + +<a href="http://github.com/bigpresh/Dancer-Plugin-Database">This plugin</a>, by bigpresh, add the <strong>database</strong> keyword to your app. + +{% highlight perl %} +use Dancer; +use Dancer::Plugin::Database; + +# Calling the database keyword will get you a connected DBI handle: +get '/widget/view/:id' => sub { + my $sth = database->prepare('select * from widgets where id = ?', + {}, params->{id}); + $sth->execute; + template 'display_widget', {widget => $sth->fetchrow_hashref}; +}; +{% endhighlight %} + +h4. Dancer::Plugin::SiteMap + +<a href="http://search.cpan.org/perldoc?Dancer::Plugin::SiteMap">With this plugin</a>, by James Ronan, a <a href="http://en.wikipedia.org/wiki/Sitemap">sitemap</a> of your application is created. + +<blockquote>Plugin module for the Dancer web framwork that automagically adds sitemap routes to the webapp. Currently adds /sitemap and /sitemap.xml where the former is a basic HTML list and the latter is an XML document of URLS.</blockquote> + +h3. you can help! :) + +There is still a lot of stuff to do. Don't hesitate to come on #dancer@irc.perl.org to discuss ideas or new features that you would like. diff --git a/_posts/2010-06-10-moosex-net-api-update.textile b/_posts/2010-06-10-moosex-net-api-update.textile new file mode 100644 index 0000000..155b691 --- /dev/null +++ b/_posts/2010-06-10-moosex-net-api-update.textile @@ -0,0 +1,137 @@ +--- +layout: post +category: perl +title: Moosex::Net::API - update +--- + +"MooseX::Net::API":http://github.com/franckcuny/moosex-net-api is a module to help writing clients for RESTful (and even non-RESTful) WebServices: + +{% highlight perl %} +package my::api; +use MooseX::Net::API; + +net_api_declare myapi => ( + api_base_url => 'http://api....', + api_format => 'json', +); + +net_api_method users => ( + method => 'GET', + path => '/users/:country', + description => 'fetch a list of users', + params => [qw/country/], + expected => [qw/200 404/], +); +{% endhighlight %} + +We've been using this module at work for the last few months on various internal APIs, and I'm pretty pleased with the result so far. + +Lately I've started to rework the core. I've tried to split most of the functionalities into roles, and rework the code that generates the various methods. I've also added methods to access miscellaneous information : + +{% highlight perl %} +my $client = my::api->new; + +# to get a list of API methods +$client->meta->get_all_net_api_methods(); + +# find an API method +my $method = $client->meta->find_net_api_method_by_name('users'); + +# and now informations about the method +say $method->documentation; + +name: users +description: fetch a list of useres +method: GET +path: /users/:country +arguments: country +expected: 200, 404 +{% endhighlight %} + +It's not yet complete, but a new version will be available soon on CPAN. Here is a list of some more features I plan to add quickly: + + * better internal API + * better authorization support (OAuth!) + * add more methods to provide better introspection + * better unserialization + * more tests and better documentation + * generate POD via a PODWeaver plugin ? + * plugins ? + * renaming ? (not sure it really fits in the MooseX:: namespace) + +h3. http-console + +I've also started "*Net::HTTP::Console*":http://github.com/franckcuny/net-http-console. It's inspired by "http-console":http://github.com/cloudhead/http-console. It relies on MX::Net::API, and can use any libraries written with MX::Net::API, as well as any *raw* RESTful API. As an example, let's use it on twitter. + +{% highlight bash %} +http-console --url http://api.twitter.com --format json + +http://127.0.0.1:5984> GET /1/statuses/public_timeline +[ + { + "source" : "web", + "favorited" : false, + "geo" : null, + "coordinates" : null, + "place" : null, + ... + } +] + +http://127.0.0.1:5984> show headers +cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0 +last-modified: Mon, 07 Jun 2010 15:27:12 GMT +x-transaction: 1275924432-94882-31146 +x-ratelimit-reset: 1275925258 +... +{% endhighlight %} + +You can call any method from the twitter API (at the exception of the ones that require authentication: it's not supported yet). + +You can also use it with any library that uses MX::Net::API: + +{% highlight bash %} +http-console --lib Net::Backtweet + +http://api.backtweet.com> help command +available commands: +- tweets_by_url +- stats_by_url +- good_tweets_by_url + +http://api.backtype.com> help command tweets_by_url +name: tweets_by_url +description: Retrieve tweets that link to a given URL, whether the links are shortened or unshortened. +method: GET +path: /tweets/search/links + +http://api.backtype.com> stats_by_url {"q":"http://lumberjaph.net","key":s3kr3t"} +{ + "tweetcount" : 388 +} +{% endhighlight %} + +Arguments to the methods are serialized in JSON format. Not sure if it's the best idea I will see if it needs improvement while using it. You can also perform POST and PUT with content. + +{% highlight bash %} + http://localhost:5984> POST /test_rtgi_fetcher {"foo":"bar"} + { + "ok" : true, + "rev" : "1-fe67006eb0e02e5f0057b5b2a6672391", + "id" : "fe3175615a34eb28153479307c000f26" + } +{% endhighlight %} + +It's far from being complete at the moment, but I will extend it quickly. Right now, you can define global headers, and get help for all methods in your MX::Net::API library. Authentication is on top of my priority list, as is alias creation, so instead of doing (on a non-moosex::net::api lib): + +bc. GET /users/ + +you will do: + +bc. alias users/:country as users + +then: + +bc. users {"country":"france"} + +(and yes, I've switched from wordpress to "blawd":http://github.com/perigrin/blawd) diff --git a/_posts/2010-06-13-fpw2010-summary.textile b/_posts/2010-06-13-fpw2010-summary.textile new file mode 100644 index 0000000..6044e6b --- /dev/null +++ b/_posts/2010-06-13-fpw2010-summary.textile @@ -0,0 +1,29 @@ +--- +layout: post +category: conference +title: FPW 2010 summary +--- + +First, no more "welsh":http://en.wikipedia.org/wiki/Welsh_rarebit. Ever. + +Even if Calais was not the easiest destination for every one, it was a really fun and intersting two days. I met nice folks, had great discussions and drink good beers. + +For those who missed this workshop, a short summary for you: + + * had excellent discussions with "sukria":http://www.sukria.net/fr/ about the future of "Dancer":http://github.com/sukria/dancer + * fun facts about time zone and unicode with "maddingue":http://twitter.com/maddingue, "rgs":http://twitter.com/octoberequus and "fperrad":http://github.com/fperrad + * interesting chat with fperrad about lua and parrot + * convinced more people that Plack *is* the future for Perl Web development + * beers, and more beers with sukria, jerome, "arnaud":http://twitter.com/ephoz, rgs, "camille":http://twitter.com/cmaussan and "stephane":http://twitter.com/straux. + * talked with "Marc":http://www.tinybox.net/ about Plack, Dancer, and other stuff + +!http://farm5.static.flickr.com/4045/4695068097_1193f8c4d6.jpg(diner) + +My slides are available online (in french): + + * github explorer: "slideshare":http://www.slideshare.net/franckcuny/github-explorer and "PDF":http://franck.lumberjaph.net/slides/github-explorer.pdf + * introduction to plack: "slideshare":http://www.slideshare.net/franckcuny/introduction-a-plack and "PDF":http://franck.lumberjaph.net/slides/introduction_a_plack.pdf + +And to finish, two other summaries: "sukria's one":http://www.sukria.net/fr/archives/2010/06/12/french-perl-workshop-2010-report/ and "twitter's timeline":http://twitter.com/#search?q=%23fpw2010; and "some pictures on flickr.":http://www.flickr.com/photos/franck_/sets/72157624263416548/ + +*Thanks to laurent and sebastien for their hard work on organizing this conference.* diff --git a/_posts/2010-06-20-dancer-meeting.textile b/_posts/2010-06-20-dancer-meeting.textile new file mode 100644 index 0000000..e7423cd --- /dev/null +++ b/_posts/2010-06-20-dancer-meeting.textile @@ -0,0 +1,31 @@ +--- +layout: post +category: perl +title: Monthly Dancer meeting +--- + +I've been contributing to Dancer for a few months now, and the discussions occurs mainly on IRC (irc.perl.org, #dancer) or on "the Mailing List":http://lists.perldancer.org/cgi-bin/listinfo/dancer-users. + +Last weekend, I had the occasion to meet sukria during the French Perl Workshop. This has been really productive, we had the occasion to talk about Plack, the templating system, websocket, ... and I really think we should have met before. It was also the occasion to meet another contributor, eiro, with whom I've been able to share some knowledge about Plack. + +During the workshop, I made a talk about Plack and the Middlewares. The direct result of this is "the last feature added by sukria":http://github.com/sukria/Dancer/commit/5ee83a5206e08256d7326f92c2f2f62c5e035ba9#L0R440: middlewares can be set in the configuration file of your Dancer application, and will be loaded for you. + +The next release of Dancer won't generate an *app.psgi* file anymore, so you will only need to edit your environment file (like _deployement.yaml_), and add the following configuration: + +{% highlight yaml %} +warnings: 1 +auto_reload: 1 +plack_middlewares: + Debug: + - panels + - + - Response + - Dancer::Version + - Dancer::Settings +{% endhighlight %} + +and your application will load some "Plack::Middleware::Debug":http://search.cpan.org/perldoc?Plack::Middleware::Debug and "Dancer::Debug":http://search.cpan.org/dist/Dancer-Debug/ panels. + +Sukria has suggested a monthly drinkup meeting for people in/near Paris, to talk about Dancer and Plack, in a pub or another place where we can bring a laptop, have some beers and share idea/codes and other exchange technicals thoughts. + +I hope to meet more Dancer developers and users in a near future (sawyer at Pise maybe ?). diff --git a/_posts/2010-06-25-presque-new_features.textile b/_posts/2010-06-25-presque-new_features.textile new file mode 100644 index 0000000..94d86f0 --- /dev/null +++ b/_posts/2010-06-25-presque-new_features.textile @@ -0,0 +1,41 @@ +--- +layout: post +category: perl +title: presque +--- + +I've added a few new features to "presque":http://github.com/franckcuny/presque. + +"presque":http://lumberjaph.net/presque-a-redis---tatsumaki-based-message-queue.html is a persistant job queue based on "Redis":http://github.com/antirez/redis and "Tatsumaki":http://github.com/miyagawa/Tatsumaki. + +A short list of current features implemented: + + * jobs are JSON object + * possibility to stop/start queues + * jobs can be delayed to run after a certain date in the future + * workers can register themself, doing this, you can know when a worker started, what he have done, ... + * statistics about queue, jobs, and workers + * possible to store and fetch jobs in batch + * a job can be unique + +The REST interface is simple, and there is only a few methods. It's fast (I will provide numbers soon from our production environment), and workers can be implemented in any languages. + +There have been a lot of refactoring lately. The main features missing right now are a simple HTML interface that will display various informations, pulling the data from the REST API (hint : if someone want to help to design this one ... :) ), websocket (sending a message to all workers). + +There is a Perl client to the REST API: "net::presque":http://github.com/franckcuny/net-presque, that you can use with "net::http::console":http://github.com/franckcuny/net-http-console: + +{% highlight bash %} +perl bin/http-console --api_lib Net::Presque --url http://localhost:5000 +http://localhost:5000> fetch_job {"queue_name":"twitter_stream"} +{ + "text" : "Australias new prime minister - julia gillard is our 27th prime minister.", + "user" : "Lov3LifeAlways" +} +{% endhighlight %} + +I've also wrote a better "worker for Perl":http://github.com/franckcuny/presque-worker. It's a Moose::Role that you apply to your class. You need to write a *work* method, and your done. This worker handle retries, provide a logger, ... As for "resque":http://github.com/defunkt/resque, there is two dispatcher: + + * normal : the worker grab a job, process it, then ask for the next job + * fork : the worker grab a job, fork, let the child do the job and exit, while the parent ask for the next job. As resque says, "Resque assumes chaos". And me too, I like (ordered) chaos + +I hope to finish the documentation and to writes one or two more workers as example (maybe in Python and javascript/node.js) soon to be able to tag a first version, and to collect some info about how many jobs have been processed at work (we use it to do url resolution and collect twitter data among few other things). Although I'm not sure I will release it to CPAN. diff --git a/_posts/2010-06-30-github-poster-to-ship.textile b/_posts/2010-06-30-github-poster-to-ship.textile new file mode 100644 index 0000000..40f0e75 --- /dev/null +++ b/_posts/2010-06-30-github-poster-to-ship.textile @@ -0,0 +1,14 @@ +--- +layout: post +category: graph +title: Github Communities Posters for shipping +--- + +I've finally found a printer that can do international shipping for reasonable costs. The price will be 65€, and a paypal account will be setup soon. + +<center> +<a href="http://fr.linkfluence.net/posters/"><img style="border: 1px solid #000;" src="http://fr.linkfluence.net/wp-content/images/atlas/github_thumb.png" /></a> +</center> + +So, if you're interested by a poster in A1 size, [send me a mail](/contact/). I'll need at least 15 persons to be able to do this. So contact me, and I will keep you informed. + diff --git a/_posts/2010-09-10-dancer-summer-of-code.textile b/_posts/2010-09-10-dancer-summer-of-code.textile new file mode 100644 index 0000000..a74f6b3 --- /dev/null +++ b/_posts/2010-09-10-dancer-summer-of-code.textile @@ -0,0 +1,128 @@ +--- +layout: post +category: perl +title: Dancer's Summer of Code +--- + +h3. Middleware + +After the "French Perl Workshop":http://journeesperl.fr/fpw2010/, we decided to focus our efforts on bringing middleware into Dancer. As the .psgi script is now obsolete, we wanted to simplify the middleware configuration for users not familiar with Plack. + +It's now possible to load a middleware by adding it to your configuration: + +{% highlight yaml %} +plack_middlewares: + Debug: + - panels + - + - DBITrace + - Memory + - Timer +{% endhighlight %} + +h3. YAPC::Eu 2010 + +During YAPC::Eu, I've been happy to meet with "squeeks":http://github.com/squeeks, "sawyer":http://blogs.perl.org/users/sawyer_x/ and "Martin Berends":http://github.com/mberends. Sadly, we didn't have much time to talk and do some coding. + +I had already met Martin during the FPW, where he started to port Dancer to Perl6. His first objective is to have "HTTP::Server::Simple":http://github.com/mberends/http-server-simple work. I was really impressed with his works; if I manage to find some spare time soon, I will join his effort. + +h3. Dancer's application + +In august, "alexis":http://www.sukria.net/ brought a big effort to refactor the core of Dancer, to add the possibility to "plug" components to your application. This required a lot of rewriting, but in the meantime, we added more tests to ensure nothing would break. + +With this feature, you can do the following: + +{% highlight yaml %} +package myapp::forum; +use Dancer ':syntax'; + +before => sub { + ... +}; + +get '/' => sub { + ... +}; + +package myapp:blog; +use Dancer ':syntax'; + +load_app 'myapp::forum', prefix => '/forum'; + +before => sub { + ... +}; + +get '/' => sub { + ... +}; +{% endhighlight %} + +Now you can request */* and */forum*. The before filter declared in the package *myapp::forum* will be executed when the */forum* path is matched, and the filter in *myapp::blog* will be executed for */*. + +h3. QA + +The weekend following the YAPC::EU, we held a small hackaton/QA day on irc. Not many people were present, but we managed to achieve some results: + + * reached the 1K tests + * documentation cleanup + * added Hooks + * improved our code coverage + +Today our code average is over 92%, and we have more than 1200 tests. + +With the new hook system, two new keywords have been added: *before_template* and *after*. They work as the *before* keyword, except the *before_template* is executed before sending the tokens to the template, so you can modify them (a small example can be found in the "Dancer::Plugin::i18n":http://github.com/franckcuny/dancer-plugin-i18n). The *after* is executed before the response is sent to the user. + +Sukria has also set up an autobuild system for our two main branches. Every 15 minutes, the test suite is executed when there is a new commit, and builds a report. Code coverage is also measured, so we can always know the state of our various development cycles. + +h3. WebSocket + +This is the question that came back from time to time: when/will Dancer support websocket ? + +We investigated various ways to do this: + + * new async. handler + * writing our own implementation + * ... + +I didn't want to write a websocket implementation for Dancer, as the spec are not yet final and it's not easy to do. Thanks to "clkao":http://github.com/clkao, we didn't have to care about all this, as he already wrote a Plack middleware for this: "Web::Hippie":http://search.cpan.org/perldoc?Web::Hippie::Pipe. + +So, what we did, is to use this middleware and add some syntactic sugar so people can use it easily in their applications. A small application is available "here":http://github.com/franckcuny/dancer-chat. + +This is not yet complete, it's only available in the 'devel' branch, and subject to change. A small code sample: + +{% highlight yaml %} +websocket '/new_listener' => sub { + my $env = request->env; + my $room = $env->{'hippie.args'}; + my $topic = $env->{'hippie.bus'}->topic($room); + $env->{'hippie.listener'}->subscribe($topic); +}; + +websocket '/message' => sub { + my $env = request->env; + my $room = $env->{'hippie.args'}; + my $topic = $env->{'hippie.bus'}->topic($room); + + my $msg = $env->{'hippie.message'}; + $msg->{time} = time; + $msg->{address} = $env->{REMOTE_ADDR}; + $topic->publish($msg); +}; +{% endhighlight %} + +As you can see, a lot of stuff can be improved quite easily in terms of syntax. + +h3. Deployment + +We're also in the process of reworking our current Deployment documentation. Lots of people are trying to deploy Dancer using various configurations, and not all are well documented, or don't work as expected. If you use Dancer, and have deployed an application in a way not documened in our Deployement documentation, please join us on irc (#dancer on irc.perl.org) or contact us on the mailing list, or even better, send us a patch, so we can improve this part. + +h3. Future + +There is also our next Dancer's meeting meeting to organize, at the end of Septembre. + +In October will take place the 2nd OSDC.fr, where I will talk about Plack, and alexis will present Dancer. + +I want to thank my company ("linkfluence":http://linkfluence.net) and my boss ("camille":http://twitter.com/cmaussan) for giving me time to code on Dancer at work. + +As always, thanks to blob for reviewing my (slightly improving) english :) diff --git a/archives.html b/archives.html new file mode 100644 index 0000000..4d608b3 --- /dev/null +++ b/archives.html @@ -0,0 +1,12 @@ +--- +layout: default +title: Archives +--- + +<h2>archive</h2> +<ul id="archive"> +{% for post in site.posts %} +<li><a href="{{ post.url }}">{{ post.title }}</a> <abbr>{{ post.date | date_to_string }}</abbr></li> +{% endfor %} +</ul> + diff --git a/contact/index.html b/contact/index.html new file mode 100644 index 0000000..820d4ed --- /dev/null +++ b/contact/index.html @@ -0,0 +1,44 @@ +--- +layout: default +--- + + <h1>Contact</h1> + <p> + You can reach me + via franck@lumberjaph.net. You can also find me on irc + as <strong>franck</strong> on irc.perl.org. + <br /><br /> + my PGP key is + <pre> +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.9 (GNU/Linux) + +mQENBElTqSgBCADMD7Zq0z0wbLj9/w5ODY2QXoH2P+2BCvcFOI7q1YywA95KmAnL +bpa5SBa3w115Dio0wVTQbJkAD2uEwkLEeP4k00WHVmZgDnXoS1tUDjz5WB22XAKe +SZpOpNjzIEhBlfjdwOiazVcXv+w/ewTDTg/uah0DPFdxI4BMV2Z4bp+z5cYgEoMm +tAcsA7bu81rPg+QgLq/8lYYS8VkHJBH86RTCqf0M/bV/3qYWW0D/OEGb82WP4AVx +3PXaConW2XvAq71x0tmSqhIJEHL8/gw25b8JlGcutHtsyfv4fFZ0FxU9G2Y9t7Ws +aYWUr8epYJ1mgMJ0i0wcZFOrkb+9/Zsf4UoXABEBAAG0I2ZyYW5jayBjdW55IDxm +cmFuY2tAbHVtYmVyamFwaC5uZXQ+iQE2BBMBAgAgBQJJU6koAhsjBgsJCAcDAgQV +AggDBBYCAwECHgECF4AACgkQQSckVhprDCgjHAgAisZ9gK0TSVoQmiA4+fOVHOh6 +AvhR8GKAskCqMjGBlJAfPAj8e2Yi+rrUlRXwT3crIwoJf8P6uxqX/9h8TvZ7N8ww +d6SwgF8nFFeKmrlh1qZvPH4ya+airGOj2UihrSk+uhbEzCL5aH/2U3bsHGtp5VXZ +1S5NKNGuAobEx7eoae4sTnKo2LsiK9BOaFiwsvpjFCDBbS43y75QqZFEDzYaH+e6 +3j6XIPDfwV7YX3DwgZGtZu2uYYgO/gQdLS2zTQfMqXw+ao86bPJ50GfFuCB0YhiT +FPs71BjVhZjRI6n4DDGShhvYeghnp8iPPWUfYxD6JTfvi5dn+w8+cXIk1L856rkB +DQRJU6koAQgAk7PZlQtANqMMfG68sYrngoJjxd2EMevuCaNDvBEZ8vzWOgip4Etq +FCrcXPWEyHFNacHrhDyVqncQ6uJQKqmezhyyv0gzfrReahAX7Pn5zjwgssgEzuMS +MsxVyN/c0kV/rx+1IqNkIc0B7MDtTkSdm5i7Tc+6UgPgKc+B3xwmHo0/dkDguJp6 +Z/+fB6Ldz/vu1GTNMf3T5zAK06g8/LWRAQ5gILn3Vp1dGWQb0nGYXkk/hPFRMnpm +hSI/p5YgxCPauEHeFmbYEVbKSUqE65MkH5uYmYeaOvvprnsIj3AizJmt9Mc5Cf8C +kmfqYBvCfHLVDTpQBxNFdt74d3aagiwprQARAQABiQEfBBgBAgAJBQJJU6koAhsM +AAoJEEEnJFYaawwo8x8IALfxFR0ZfurYhlsI/iRN0dB+Bk9SjBAmM9mnftSq4WWq +ILm2QfHML8t9zBh9x/LG2QY1gXJbK5l06/pkR2rRALp9MqKfcXawGQrn4UIaLwJG +dhuQy1hfhOiMfSclXzwAHYkdIR7W/eznnmC6LA7nQEvGN56/hbfIO40mZFPHnVTe +KSNrfqF6K9oCukXamRDMuoMqkskL5srRgPFYjq/cQMME9dAOnXhDYHa+nxb8ovfa +BoyYN6zomCvWHhUWIwBE67CZeidq91UkdyR36G/WBK7SP8VPQGCMIHhQkRq8Piuh +sk8ACnF+ktMJwi6WAOVz2jhj9wWiHdDqsiz4oz9Lw0g= +=uiDe +-----END PGP PUBLIC KEY BLOCK----- + </pre> + </p> diff --git a/favicon.ico b/favicon.ico Binary files differnew file mode 100644 index 0000000..d7f9f80 --- /dev/null +++ b/favicon.ico diff --git a/index.atom b/index.atom new file mode 100644 index 0000000..53a3a59 --- /dev/null +++ b/index.atom @@ -0,0 +1,27 @@ +--- +layout: nil +--- +<?xml version="1.0" encoding="utf-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"> + + <title>I'm a lumberjaph</title> + <link href="http://lumberjaph.net/atom.xml" rel="self"/> + <link href="http://lumberjaph.net/"/> + <updated>{{ site.time | date_to_xmlschema }}</updated> + <id>http://lumberjaph.net/</id> + <author> + <name>franck cuny</name> + <email>franck@lumberjaph.net</email> + </author> + + {% for post in site.posts %} + <entry> + <title>{{ post.title }}</title> + <link href="http://lumberjaph.net{{ post.url }}"/> + <updated>{{ post.date | date_to_xmlschema }}</updated> + <id>http://lumberjaph.net{{ post.id }}</id> + <content type="html">{{ post.content | xml_escape }}</content> + </entry> + {% endfor %} + +</feed>
\ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..332140a --- /dev/null +++ b/index.html @@ -0,0 +1,11 @@ +--- +layout: default +title: I'm a lumberjaph +--- +{% assign first_post = site.posts.first %} +<div id="post"> + <h2><a href="{{ first_post.url }}">{{ first_post.title }}</a></h2> + <div id="date">published {{ first_post.date | date_to_string }}</div> + {{ first_post.content }} + <a id="more" href="{{ first_post.url }}#disqus">Comments »</a> +</div> diff --git a/static/css/reset.css b/static/css/reset.css new file mode 100644 index 0000000..b002e1a --- /dev/null +++ b/static/css/reset.css @@ -0,0 +1,98 @@ +/* +html5doctor.com Reset Stylesheet +v1.4 +2009-07-27 +Author: Richard Clark - http://richclarkdesign.com +*/ + +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, +del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, +b, i, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, dialog, figure, footer, header, +hgroup, menu, nav, section, menu, +time, mark, audio, video { + margin:0; + padding:0; + border:0; + outline:0; + font-size:100%; + vertical-align:baseline; + background:transparent; +} +body { + line-height:1; +} + +article, aside, dialog, figure, footer, header, +hgroup, nav, section { + display:block; +} + +nav ul { + list-style:none; +} + +blockquote, q { + quotes:none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content:''; + content:none; +} + +a { + margin:0; + padding:0; + border:0; + font-size:100%; + vertical-align:baseline; + background:transparent; +} + +ins { + background-color:#ff9; + color:#000; + text-decoration:none; +} + +mark { + background-color:#ff9; + color:#000; + font-style:italic; + font-weight:bold; +} + +del { + text-decoration: line-through; +} + +abbr[title], dfn[title] { + border-bottom:1px dotted #000; + cursor:help; +} + +table { + border-collapse:collapse; + border-spacing:0; +} + +hr { + display:block; + height:1px; + border:0; + border-top:1px solid #cccccc; + margin:1em 0; + padding:0; +} + +input, select { + vertical-align:middle; +} diff --git a/static/css/screen.css b/static/css/screen.css new file mode 100644 index 0000000..aa55e3f --- /dev/null +++ b/static/css/screen.css @@ -0,0 +1,197 @@ +/*****************************************************************************/ +/* +/* Common +/* +/*****************************************************************************/ + +/* Global Reset */ + +* { + margin: 0; + padding: 0; +} + +html, body { + height: 100%; +} + +body { + background-color: white; + font: 13.34px helvetica, arial, clean, sans-serif; + *font-size: small; + text-align: center; +} + +h1, h2, h3, h4, h5, h6 { + font-size: 100%; +} + +h1 { + margin-bottom: 1em; +} + +p { + margin: 1em 0; +} + +a { + color: #00a; +} + +a:hover { + color: black; +} + +a:visited { + color: #a0a; +} + +table { + font-size: inherit; + font: 100%; +} + +/*****************************************************************************/ +/* +/* Home +/* +/*****************************************************************************/ + +ul.posts { + list-style-type: none; + margin-bottom: 2em; +} + + ul.posts li { + line-height: 1.75em; + } + + ul.posts span { + color: #aaa; + font-family: Monaco, "Courier New", monospace; + font-size: 80%; + } + +/*****************************************************************************/ +/* +/* Site +/* +/*****************************************************************************/ + +.site { + font-size: 110%; + text-align: justify; + width: 50em; + margin: 3em auto 2em auto; + line-height: 1.5em; +} + +.title { + color: #a00; + font-weight: bold; + margin-bottom: 2em; +} + + .site .title a { + color: #a00; + text-decoration: none; + } + + .site .title a:hover { + color: black; + } + + .site .title a.extra { + color: #aaa; + text-decoration: none; + margin-left: 1em; + } + + .site .title a.extra:hover { + color: black; + } + + .site .meta { + color: #aaa; + } + + .site .footer { + font-size: 80%; + color: #666; + border-top: 4px solid #eee; + margin-top: 2em; + overflow: hidden; + } + + .site .footer .contact { + float: left; + margin-right: 3em; + } + + .site .footer .contact a { + color: #8085C1; + } + + .site .footer .rss { + margin-top: 1.1em; + margin-right: -.2em; + float: right; + } + + .site .footer .rss img { + border: 0; + } + +/*****************************************************************************/ +/* +/* Posts +/* +/*****************************************************************************/ + +#post { + +} + + /* standard */ + + #post pre { + border: 1px solid #ddd; + background-color: #eef; + padding: 0 .4em; + } + + #post ul, + #post ol { + margin-left: 1.25em; + } + + #post code { + border: 1px solid #ddd; + background-color: #eef; + font-size: 95%; + padding: 0 .2em; + } + + #post pre code { + border: none; + } + + /* terminal */ + + #post pre.terminal { + border: 1px solid black; + background-color: #333; + color: white; + } + + #post pre.terminal code { + background-color: #333; + } + +#related { + margin-top: 2em; +} + + #related h2 { + margin-bottom: 1em; + }
\ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..03d2041 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,186 @@ +@import url(http://fonts.googleapis.com/css?family=Droid+Sans:regular,bold); +@import url(http://fonts.googleapis.com/css?family=Droid+Serif:regular); + +body { + font-family: 'Droid Sans',Verdana, sans-serif; + font-size: 1em; + line-height: 16pt; + background: #EEEEEE; +} + +a, a:visited { + color: #666666; +} + +a:hover { + color: #999999, +} + +h1 { + font-size: 24pt; +} + +h2 { + font-size: 20pt; + padding-top: 12px; + padding-bottom: 12px; +} + +h3 { + font-size: 14pt; + margin-top: 10px; +} + +h1 a, h1 a:visited { + color: #C33C33; +} + +ul { + list-style: disc; +} + +li { + margin-left: 2em; +} + +li#title { + color: red; + font-weight: bold; +} +li.sep { + color: white; +} +li.section { + font-weight: bold; +} + +p, h1, h2, h3, ul { + margin-bottom: 0.5em; +} + +section { + margin-top: 2em; +} + +section h1 { + font-size: 20pt; + color: #C33C33; + margin-bottom: 8px; +} + +section small { + font-size: 10pt; + color: gray; +} + +section div.highlight { + padding: 10px 0px 20px 25px; +} + +#container { + margin-top: 10px; + margin-bottom: 10px; + margin-left: auto; + margin-right: auto; + width: 960px; + background: #FFFFFF; + padding: 20px; +} + +header { + margin: -20px -20px 20px -20px; + padding: 5px; + background: #000000; +} + +header h1 { + color: #C33C33; + font-size: 28pt; +} + +header h1 a { + text-decoration: none; +} + +header #nav { + display: inline; + list-style-type: none; + list-style: none; +} + +header #nav li { + display: inline; + margin-left: 0; + margin-right: 10px; +} + +header #nav li a { + padding-top: 0px; + color: #FFFFFF; + text-decoration: none; +} + +header #nav li a:hover { + color: #CCCCCC; +} + +footer { + margin: 0 -20px -20px -20px; + padding: 10px; + background: #000000; +} + +footer p { + color: #FFFFFF; +} + +footer p img { + margin-bottom: -5px; +} + +div.highlight { + background-color: #333333; + border: 5px solid #CCCCCC; + margin: 20px; + overflow: auto; + padding: 5px; +} + +.clear { + clear: both; +} + +.right { + float: right; +} + +img { + margin-top: 10px; + margin-bottom: 10px; + display: block; + border: 1px solid black; + margin-left: auto; + margin-right: auto; +} + +.entry:after { + content: '\002701'; + font-size: 18pt; + text-align: center; + clear:both; + display: block; + margin: 5ex 0; +} + +/*code {*/ + /*margin-top: 15px;*/ + /*margin-bottom: 15px;*/ + /*font-size: 0.9em;*/ + /*font-weight: bold;*/ + /*display: block; */ + /*padding-top: 10px; */ + /*padding-left: 10px; */ + /*padding-bottom: 10px; */ + /*background-color: #000000; */ + /*color: #ffffff*/ +/*}*/ diff --git a/static/css/syntax.css b/static/css/syntax.css new file mode 100644 index 0000000..2774b76 --- /dev/null +++ b/static/css/syntax.css @@ -0,0 +1,60 @@ +.highlight { background: #ffffff; } +.highlight .c { color: #999988; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { font-weight: bold } /* Keyword */ +.highlight .o { font-weight: bold } /* Operator */ +.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ +.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #999999 } /* Generic.Heading */ +.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { font-weight: bold } /* Keyword.Constant */ +.highlight .kd { font-weight: bold } /* Keyword.Declaration */ +.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #009999 } /* Literal.Number */ +.highlight .s { color: #d14 } /* Literal.String */ +.highlight .na { color: #008080 } /* Name.Attribute */ +.highlight .nb { color: #0086B3 } /* Name.Builtin */ +.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ +.highlight .no { color: #008080 } /* Name.Constant */ +.highlight .ni { color: #800080 } /* Name.Entity */ +.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ +.highlight .nn { color: #555555 } /* Name.Namespace */ +.highlight .nt { color: #000080 } /* Name.Tag */ +.highlight .nv { color: #008080 } /* Name.Variable */ +.highlight .ow { font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #009999 } /* Literal.Number.Float */ +.highlight .mh { color: #009999 } /* Literal.Number.Hex */ +.highlight .mi { color: #009999 } /* Literal.Number.Integer */ +.highlight .mo { color: #009999 } /* Literal.Number.Oct */ +.highlight .sb { color: #d14 } /* Literal.String.Backtick */ +.highlight .sc { color: #d14 } /* Literal.String.Char */ +.highlight .sd { color: #d14 } /* Literal.String.Doc */ +.highlight .s2 { color: #d14 } /* Literal.String.Double */ +.highlight .se { color: #d14 } /* Literal.String.Escape */ +.highlight .sh { color: #d14 } /* Literal.String.Heredoc */ +.highlight .si { color: #d14 } /* Literal.String.Interpol */ +.highlight .sx { color: #d14 } /* Literal.String.Other */ +.highlight .sr { color: #009926 } /* Literal.String.Regex */ +.highlight .s1 { color: #d14 } /* Literal.String.Single */ +.highlight .ss { color: #990073 } /* Literal.String.Symbol */ +.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #008080 } /* Name.Variable.Class */ +.highlight .vg { color: #008080 } /* Name.Variable.Global */ +.highlight .vi { color: #008080 } /* Name.Variable.Instance */ +.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/static/imgs/authorsmap.png b/static/imgs/authorsmap.png Binary files differnew file mode 100644 index 0000000..7f4e740 --- /dev/null +++ b/static/imgs/authorsmap.png diff --git a/static/imgs/batmoose_1024cut-300x225.png b/static/imgs/batmoose_1024cut-300x225.png Binary files differnew file mode 100644 index 0000000..9a770e6 --- /dev/null +++ b/static/imgs/batmoose_1024cut-300x225.png diff --git a/static/imgs/cpanhq-dep.png b/static/imgs/cpanhq-dep.png Binary files differnew file mode 100644 index 0000000..f0d264c --- /dev/null +++ b/static/imgs/cpanhq-dep.png diff --git a/static/imgs/draft_cpan_prelimsmall.png b/static/imgs/draft_cpan_prelimsmall.png Binary files differnew file mode 100644 index 0000000..431b863 --- /dev/null +++ b/static/imgs/draft_cpan_prelimsmall.png diff --git a/static/imgs/general.png b/static/imgs/general.png Binary files differnew file mode 100644 index 0000000..578edfb --- /dev/null +++ b/static/imgs/general.png diff --git a/static/imgs/github-europe-preview.png b/static/imgs/github-europe-preview.png Binary files differnew file mode 100644 index 0000000..c14690a --- /dev/null +++ b/static/imgs/github-europe-preview.png diff --git a/static/imgs/github-perl-preview.png b/static/imgs/github-perl-preview.png Binary files differnew file mode 100644 index 0000000..45f0806 --- /dev/null +++ b/static/imgs/github-perl-preview.png diff --git a/static/imgs/list_feed.png b/static/imgs/list_feed.png Binary files differnew file mode 100644 index 0000000..8ecf200 --- /dev/null +++ b/static/imgs/list_feed.png diff --git a/static/imgs/me.jpg b/static/imgs/me.jpg Binary files differnew file mode 100644 index 0000000..bce6603 --- /dev/null +++ b/static/imgs/me.jpg diff --git a/static/imgs/moosedist.png b/static/imgs/moosedist.png Binary files differnew file mode 100644 index 0000000..9e36bf2 --- /dev/null +++ b/static/imgs/moosedist.png diff --git a/static/imgs/routes-300x249.png b/static/imgs/routes-300x249.png Binary files differnew file mode 100644 index 0000000..f2b9ec2 --- /dev/null +++ b/static/imgs/routes-300x249.png diff --git a/static/imgs/rss.png b/static/imgs/rss.png Binary files differnew file mode 100644 index 0000000..d6ecb16 --- /dev/null +++ b/static/imgs/rss.png diff --git a/static/imgs/show_entry.png b/static/imgs/show_entry.png Binary files differnew file mode 100644 index 0000000..bf23be5 --- /dev/null +++ b/static/imgs/show_entry.png diff --git a/static/imgs/zoom.png b/static/imgs/zoom.png Binary files differnew file mode 100644 index 0000000..e9300f4 --- /dev/null +++ b/static/imgs/zoom.png diff --git a/talks/index.html b/talks/index.html new file mode 100644 index 0000000..56d9046 --- /dev/null +++ b/talks/index.html @@ -0,0 +1,44 @@ +--- +layout: default +--- + <h1>Talks & Articles</h1> + <h2>Previous and Upcoming Talks</h2> + <h3>2010</h3> + <ul> + <li><em>June 2010, FPW 2010</em> — Github + Explorer<br /><small>Presentation de + github-explorer</small><br /><a href="http://www.slideshare.net/franckcuny/github-explorer">Slides</a> + - <a href="http://franck.lumberjaph.net/slides/github-explorer.pdf">PDF</a></li> + <li><em>June 2010, FPW 2010</em> — Introduction à + Plack<br /><small>Introductoin à Plack: PSGI, les + Handlers, les Middlewares, + ...</small><br /><a href="http://www.slideshare.net/franckcuny/introduction-a-plack">Slides</a> + - <a href="http://franck.lumberjaph.net/slides/introduction_a_plack.pdf">PDF</a></li> + </ul> + <h3>2009</h3> + <ul> + <li><em>October 2009, OSDC.fr</em> — Introduction à + Moose<br /> <small>Introduction à Moose: survol rapide des roles, du + MOP, ...</small><br /><a + href="http://www.slideshare.net/franckcuny/introduction-a-moose">Slides</a> + - <a + href="http://labs.linkfluence.net/osdcfr09/Introduction_a_Moose.pdf">PDF</a></li> + <li><em>August 2009, YAPC::EU 2009</em> — CPAN Explorer<br + /> <small>A talk about the shape of the CPAN, and a map of his + community. </small><br /><a + href="http://www.slideshare.net/franckcuny/map-of-the-perl-and-cpan-community">Slides</a> - <a + href="http://labs.linkfluence.net/yapceu09/talk.pdf">PDF</a></li> + <li><em>June 2009, FPW 2009</em> — CPAN + Explorer<br /><small>Présentation réalisée au FPW09, sur la + communauté des developpeurs Perl via le CPAN et le web.</small><br /><a + href="http://www.slideshare.net/franckcuny/cartographie-du-cpan-et-de-sa-communaut">Slides</a> + - <a + href="http://labs.linkfluence.net/fpw09/resources/slides/">PDF</a></li> + </ul> + <h2>Articles</h2> + <h3>2009</h3> + <ul> + <li><em>Catalyst Advent Calendar</em> — <a + href="http://www.catalystframework.org/calendar/2009/19">Writing REST + services with Catalyst</a></li> + </ul> |
