#!/usr/bin/env perl use FindBin; use Cwd; use lib "$FindBin::Bin/lib"; use Mojolicious::Lite; use Mojo::Util qw(md5_sum spurt); use Mojo::Loader qw(data_section); use Bootylicious; use Bootylicious::Timestamp; app->home->parse($ENV{BOOTYLICIOUS_HOME} || getcwd()); unshift @{app->plugins->namespaces}, 'Bootylicious::Plugin'; plugin 'booty_config' => {file => app->home->rel_file('bootylicious.conf')}; plugin 'markdown_parser'; plugin 'model'; my $ALIAS_RE = qr/[a-zA-Z0-9-_]+/; my $TAG_RE = qr/[\w\s-]+/; if (@ARGV && $ARGV[0] eq '--create-config') { if (-e 'bootylicious.conf') { die "bootylicious.conf already exists\n"; } spurt(data_section(__PACKAGE__, 'bootylicious.conf'), 'bootylicious.conf'); exit; } get '/' => \&index => 'root'; get '/index' => \&index => 'index'; sub index { my $self = shift; my $timestamp = $self->param('timestamp'); my $pager = $self->get_articles(timestamp => $timestamp); $self->stash(articles => $pager->articles, pager => $pager); $self->render_smart('index'); } get '/articles/:year/:month' => [year => qr/\d+/, month => qr/\d+/] => {year => undef, month => undef} => sub { my $self = shift; my $year = $self->stash('year'); my $month = $self->stash('month'); my $archive = $self->get_archive(year => $year, month => $month); $self->stash(archive => $archive); $self->render_smart; } => 'articles'; get '/articles/:year/:month/:alias' => [year => qr/\d+/, month => qr/\d+/, alias => $ALIAS_RE] => sub { my $self = shift; my $article = $self->get_article(@{$self->stash}{qw/year month alias/}); return $self->reply->not_found unless $article; $self->stash(article => $article); $self->render_smart; } => 'article'; post '/articles/:year/:month/:alias/comment' => sub { my $self = shift; return $self->reply->not_found unless $self->comments_enabled; my $article = $self->get_article(@{$self->stash}{qw/year month alias/}); return $self->reply->not_found unless $article && $article->comments_enabled; my $validator = $self->create_validator; my $comment_name = md5_sum($article->created->year, $article->created->month, $article->name); $validator->field('author')->required(1); $validator->field('email')->email(1); $validator->field('content')->length(0); # bot protection $validator->field($comment_name)->required(1); return $self->render('article', article => $article) unless $self->validate($validator); my $comment_params = $validator->values; $comment_params->{content} = delete $comment_params->{$comment_name}; my $comment = $article->comment(%$comment_params); return $self->redirect_to($self->href_to_article($article) ->fragment('comment-' . $comment->number)); } => 'comment'; get '/comments' => sub { my $self = shift; return $self->reply->not_found unless $self->comments_enabled; $self->render_smart; } => 'comments'; get '/tags/:tag' => [tag => $TAG_RE] => sub { my $self = shift; my $tag = $self->stash('tag'); my $timestamp = $self->param('timestamp'); my $pager = $self->get_articles_by_tag($tag, timestamp => $timestamp); return $self->reply->not_found unless $pager->articles->size; $self->stash(articles => $pager->articles, pager => $pager); $self->render_smart; } => 'tag'; get '/tags' => sub { my $self = shift; my $cloud = $self->get_tag_cloud; $self->stash(tags => $cloud); $self->render_smart; } => 'tags'; get '/pages/:name' => [name => $ALIAS_RE] => sub { my $self = shift; my $name = $self->stash('name'); my $page = $self->get_page($name); return $self->reply->not_found unless $page; $self->stash(page => $page); $self->render_smart; } => 'page'; get '/drafts/:name' => [name => $ALIAS_RE] => sub { my $self = shift; my $name = $self->stash('name'); my $draft = $self->get_draft($name); return $self->reply->not_found unless $draft; $self->stash(draft => $draft); $self->render_smart; } => 'draft'; app->start; __DATA__ @@ index.html.ep % stash description => config('descr'); % if ($articles->size == 0) {
Nothing here yet :(
% } % while (my $article = $articles->next) { %= include 'index-item', article => $article; % } %= include 'index-pager', pager => $pager; @@ index-item.html.ep

<%= link_to_article $article %>

<%= include 'article-meta', article => $article %>
<%= render_article_or_preview $article %>
% if (comments_enabled) {
<%= link_to_comments $article %>
% }
@@ index-pager.html.ep
<%= link_to_page 'index', $pager->prev_timestamp => begin %><%= strings 'later' %><% end %> <%= link_to_page 'index', $pager->next_timestamp => begin %><%= strings 'earlier' %><% end %>
@@ articles.html.ep % stash title => strings('archive'), description => strings('archive-description');

<%= strings 'archive' %>


% if ($archive->is_yearly) { %= include 'archive-yearly', archive => $archive; % } % else { %= include 'archive-monthly', articles => $archive->articles; % }
@@ archive-yearly.html.ep % while (my $year = $archive->next) {

<%= $year->year %>

% } @@ archive-monthly.html.ep % while (my $article = $articles->next) {
  • <%= link_to_article $article %>
    <%= include 'article-meta', article => $article; %>
  • % } @@ article-meta.html.ep
    % my $author = article_author $article; <%= date $article->created %> <%= $author ? 'by '.$author : '' %>
    <%= tags_links $article %>
    @@ index.rss.ep <%= config 'title' %> <%= href_to_rss->to_abs %> <%= config 'description' %> % my $first = $pager->articles->first; % my $first_created = $first ? $first->created % : Bootylicious::Timestamp->new(epoch => 0); <%= date_rss $first_created %> <%= generator %> % while (my $article = $pager->articles->next) { <%= $article->title %> <%= href_to_article($article)->to_abs %> % if ($article->link) { <%= permalink_to($article->link) if $article->link %> % } ]]> % foreach my $tag (@{$article->tags}) { <%= $tag %> % } % if (comments_enabled) { <%= href_to_comments($article)->to_abs %> % } <%= date_rss $article->created %> <%= href_to_article($article)->to_abs %> % } @@ comments.rss.ep % my $comments = get_recent_comments(10); <%= config 'title' %> <%= href_to_comments_rss->to_abs %> <%= config 'description' %> % my $first = $comments->first; % my $first_created = $first ? $first->created % : Bootylicious::Timestamp->new(epoch => 0); <%= date_rss $first_created %> <%= generator %> % while (my $comment = $comments->next) { <%= $comment->author %> on <%= $comment->article->title %> <%= href_to_comment($comment)->to_abs %> ]]> <%= date_rss $comment->created %> <%= href_to_comment($comment)->to_abs %> % } @@ layouts/wrapper.rss.ep % die('RSS disabled') unless config 'rss_enabled'; <%= content %> @@ tags.html.ep % stash title => strings('tags'), description => strings('tags-description');

    <%= strings 'tags' %>


    % while (my $tag = $tags->next) { <%= link_to_tag $tag %> (<%= $tag->count %>) % }
    @@ tag.html.ep % stash title => $tag, description => strings('tag-description', $tag);

    <%= strings 'tag' %> <%= $tag %> % if (config 'rss_enabled') { <%= link_to_tag $tag => { format => 'rss'} => begin %>RSS<% end %> % }


    % while (my $article = $articles->next) { <%= link_to_article $article %>
    %= include 'article-meta', article => $article; % }
    %= include 'tag-pager', pager => $pager, tag => $tag; @@ tag-pager.html.ep
    <%= link_to_page 'tag' => {tag => $tag} => $pager->prev_timestamp => begin %><%= strings 'later' %><% end %> <%= link_to_page 'tag' => {tag => $tag} => $pager->next_timestamp => begin %><%= strings 'earlier' %><% end %>
    @@ tag.rss.ep %= include 'index', format => 'rss'; @@ article.html.ep % stash title => $article->title, description => $article->description;

    <%= link_to_article $article %>

    <%= include 'article-meta', article => $article %>
    <%= render_article $article %>
    %= include 'article-pingbacks', pingbacks => $article->pingbacks if $article->pingbacks->size; %= include 'article-comments', comments => $article->comments if comments_enabled && $article->comments->size; %= include 'article-comment-form' if comments_enabled; %= include 'article-pager', next => $article->next, prev => $article->prev;
    @@ article-pingbacks.html.ep

    Pingbacks

    @@ article-comments.html.ep

    Comments (<%= $comments->size %>)

    % while (my $comment = $comments->next) {
    <%= gravatar $comment->email %> <%= comment_author $comment %> says:
    <%= date $comment->created %>
    <%== render_comment $comment %>
    % }
    @@ article-comment-form.html.ep
    % if ($article->comments_enabled) {

    Add comment

    Fields marked * are required.
    This form has a bot protection mechanism, that requires Cookies.
    Please, don't disable them.
    %= form_for 'comment' => {year => $article->created->year, month => $article->created->month, alias => $article->name}, method => 'post' => begin
    <%= input_tag 'author', class => 'comment' %>
    <%= validator_error 'author' %>
    <%= input_tag 'email', class => 'comment' %> <%= link_to 'http://gravatar.com' => begin %>Gravatar<% end %>-friendly
    <%= validator_error 'email' %>
    <%= input_tag 'url', class => 'comment' %>
    <%= validator_error 'url' %> % use Mojo::Util 'md5_sum'; % my $comment_name = md5_sum($article->created->year, $article->created->month, $article->name);
    <%= text_area 'content', class => 'bpr' %> <%= text_area $comment_name %>
    <%= validator_error $comment_name %>
    Paragraphs are created automatically. Available tags: [quote], [code].
    <%= submit_button 'Post comment' %> % end
    % } % else {

    Comments for this article has been disabled

    % }
    @@ article-pager.html.ep
    % if ($prev) { <%= link_to_article $prev %>   % } | % if ($next) {  <%= link_to_article $next %> % }
    @@ page.html.ep % stash title => $page->title, description => $page->description;

    <%= $page->title %>

    <%== render_page $page %>
    @@ draft.html.ep % stash title => $draft->{title}, description => strings('draft');

    <%= $draft->title %>

    <%= tags_links $draft %>
    <%== render_article $draft %>
    @@ not_found.html.ep % stash title => 'Not found', description => 'Not found';

    404


    <%= strings 'not-found' %>
    @@ exception.html.ep % stash title => 'Error', description => 'Error';

    500


    <%= strings 'error' %>
    @@ layouts/wrapper.html.ep <%= $title ? "$title / " : '' %><%= config 'title' %> <%= stylesheet '/styles.css' %> % if (config 'rss_enabled') { % } % if (comments_enabled && config 'rss_enabled') { % } <%= meta %> <%= js %> <%= css %>
    <%= content %>
    @@ styles.css html, body {height: 100%;margin:0} body {background: #fff;font-family: Georgia, "Bitstream Charter", serif;line-height:25px} h1,h2,h3,h4,h5 {font-family: times, "Times New Roman", times-roman, serif; line-height: 40px; letter-spacing: -1px; color: #444; margin: 0 0 0 0; padding: 0 0 0 0; font-weight: 100;} a,a:active {color:#555} a:hover{color:#000} a:visited{color:#000} img{border:0px} pre{line-height:18px;border:2px solid #ccc;background:#eee;padding:1em;overflow:auto;overflow-y:visible;width:600px;} blockquote{border:2px solid #ccc;background:#eee;padding:1em} #body {width:65%;min-height:100%;height:auto !important;height:100%;margin:0 auto -6em;} #header {text-align:center;padding:2em 0em 0.5em 0em;border-bottom: 1px solid #000} h1#title{font-size:3em} h2#description{font-size:1.5em;color:#999} span#author {font-weight:bold} span#about {font-style:italic} #menu {padding-top:1em;text-align:right} #content {background:#FFFFFF} .article-meta {line-height:18px;color:#999;margin-left:10px;font-size:small;font-style:italic;padding-bottom:0.5em} .artcle-content {} .modified {margin:0px} .tags a{color:#999} .text {padding:2em;} .text h1.title {font-size:2.5em} .error {padding:2em;text-align:center} .more {margin-left:10px;padding-bottom:1em;} #pager {text-align:center;padding:2em; color:#ccc}} #pager span.active {color:#000} #pager span.arrow {background:#fff} #subfooter {padding:2em;border-top:#000000 1px solid} #footer{width:65%;margin:auto;font-size:80%;text-align:center;padding:2em 0em 2em 0em;border-top:#000000 1px solid;height:2em;} .center {text-align:center} .push {height:6em} input.comment, textarea {font-size:150%;width:60%} textarea {height:200px} textarea.bpr {display: none} label {color:#999} .required {color:red} #pingbacks, #comments, #comment-form {padding:1em 0px} #pingbacks .content, #comments .content, #comment-form .content {padding-left:1em} #comments .comment .meta {line-height:18px;color:#999;margin-left:10px;font-size:small;font-style:italic;padding-bottom:0.5em} #comments .comment .author {font-weight:bold} #comments .comment .content {padding-bottom:2em} .tip {font-size:smaller;color:#bbb;font-style:italic;padding-left:1em} .tip a{color:#bbb} .tip-required {padding:1em 0px;} .tip-required-bot {font-style:italic;font-size:small;color:#999;line-height:18px} .comment-tags {font-style:italic;font-size:small;color:#999;line-height:18px;margin-bottom:1em} .content div.error {text-align:left;padding:0;color:red} img.gravatar {float:left; padding-right:0.5em} .comment-counter {color:#999;font-size:small;margin-left:10px} .comment-counter a {color:#999} @@ rss.png (base64) iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ bWFnZVJlYWR5ccllPAAAAlJJREFUeNqkU0toU0EUPfPJtOZDm9gSPzWVKloXgiCCInXTRTZVQcSN LtyF6qILFwoVV+7EjR9oFy7VlSAVF+ouqMWWqCCIrbYSosaARNGmSV7ee+OdyUsMogtx4HBn5t1z 7twz85jWGv8zZHaUmRjlHBnBkRYSCSnog/wzuECZMzxgDNPEW5E0ASHTl4qf6h+KD6iwUpwyuRCw kcCCNSPoRsNZKeS31D8WTOHLkqoagbQhV+sV1fDqEJQoidSCCMiMjskZU9HU4AAJpJsC0gokTGVD XnfhA0DRL7+Hn38M/foOeOUzOJEZs+2Cqy5F1iXs3PZLYEGl+ux1NF7eAmpfIXedQOjYbYgdh9tk Y3oTsDAnNCewPZqF8/SKjdqs+7aCj5wFDkwSlUEvzFgyPK8twNvuBv3GzixgzfgcQmNXqW/68IgE is+BvRPQ0fXE9eC7Lvy/Cfi5G8DSQ7DkTrCxKbrgJPSTS5TUDQwfgWvIBO0Dvv+bgPFAz12Dzl4E 7p5svpQ9p6HLy9DFF2CD+9sCHpG9DgHHeGAExDglZnLAj09APgts2N089pdFsPjmXwIuHAJk8JKL rXtuDWtWtQwWiliScFapQJedKxKsVFA0KezVUeMvprcfHDkua6uRzqsylQ2hE2ZPqXAld+/tTfIg I56VgNG1SDkuhmIb+3tELCLRTYYpRdVDFpwgCJL2fJfXFufLS4Xl6v3z7zBvXkdqUxjJc8M4tC2C fdDoNe62XPaCaOEBVOjbm++YnSphpuSiZAR6CFQS4h//ZJJD7acAAwCdOg/D5ZiZiQAAAABJRU5E rkJggg== @@ bootylicious.conf %# Bootylicious configuration %# %# This configuration is in JSON format, that is preprocessed by Mojo::Template. %# Yes, you can use Perl here. { %# Change this to something not guessable %# "secret" : "Unique string", %# Blog description settings %# "author" : "whoami", %# "email" : "", %# "title" : "Just another blog", %# "about" : "Perl hacker", %# "description" : "I do not know if I need this", %# Article rendering settings %# "cuttag" : "[cut]", %# "cuttext" : "Keep reading", %# "pagelimit" : 10, %# "datefmt" : "%a, %d %b %Y" %# Appearence settings %# "menu" : [ %# "index", "/", %# "tags", "/tags.html", %# "archive", "/articles.html" %# ], %# "footer" : "Powered by Bootylicious", %# "theme" : "", %# Additional HTML tags %# "meta" : [], %# "css" : [], %# "js" : [], %# Plugins %# "plugins" : [ %# "admin", {"username" : "foo", "password" : "bar"} %# "search", %# "google_analytics", {"urchin" : "UA-12345"}, %# "pingback" %# ], %# Enabling/disabling comments globbaly %# "comments_enabled" : true, %# Enabling/disabling rss %# "rss_enabled" : true, %# System settings %# "perl5lib" : "", %# "loglevel" : "error", %# Path settings %# "articles_directory" : "articles", %# "pages_directory" : "pages", %# "drafts_directory" : "drafts", %# "public_directory" : "public", %# "templates_directory" : "templates", %# Don't worry about trailing comma "ok" : true }