Commits

Fred T-H  committed 1e67526

Initial commit

This ports ports and refactors the blog engine used for fred.ca
so that it can be used for more general projects.

This refactoring also adds basic support for markdown syntax to
be used within the ErlyDTL templates by wrapping them in
{% markdown %} {% endmarkdown %} tags (which can not be nested).

Moreover, I've included a build system to create a release so
custom binaries can be generated for whoever wants them, or just
dependencies to be used with an existing Erlang install.

The demo blog (in the demo-blog) directory shows a basic blog
config and generation to be had to get things going.

To compile, have agner and an agnerized rebar installed (agner
install rebar), and then do:
rebar get-deps
rebar compile
To build dependencies, go into rel/ and run
./build.sh
The resulting erlang release is called in demo-blog/run.sh and will
build the demo blog app with the release.

  • Participants

Comments (0)

Files changed (22)

File apps/blogerl/.agner.config

+{applications,[]}.
+{code_paths,["ebin"]}.
+{deps_dir,"deps"}.
+{description,[]}.
+{homepage,"http://google.com/#q="}.
+{install_dirs,["doc","ebin","include","priv"]}.
+{keywords,[]}.
+{rebar_commands,["compile"]}.
+{rebar_compatible,false}.
+{rebar_fetch_deps_commands,["get-deps"]}.
+{requires,["erlydtl","markdown","mochiweb"]}.

File apps/blogerl/Emakefile

+{"src/*", [debug_info, {outdir, "ebin/"}]}.

File apps/blogerl/ebin/blogerl.app

+{application, blogerl,
+ [{description, "A tiny blog engine that generates flat HTML"},
+  {vsn, "0.9.0"},
+  {modules, [blog]},
+  {applications, [stdlib, kernel, erlydtl, markdown]},
+  {agner, [{requires, ["erlydtl","markdown","mochiweb"]}]}]}.

File apps/blogerl/src/blog.erl

+-module(blog).
+-export([main/1, run/1]).
+-compile(export_all).
+
+-define(conf(A,B), proplists:get_value(A,B)).
+-define(conf(A,B,C), proplists:get_value(B, proplists:get_value(A,C))).
+
+-record(conf, {index,
+               index_tpl,
+               index_out,
+               rss_tpl,
+               rss_out,
+               rss_num=all,
+               src,
+               out,
+               markdown=[],
+               vars=[]
+              }).
+
+-record(article, {date,
+                  title,
+                  slug,
+                  file,
+                  text,
+                  lg=en}).
+
+run(BlogDir) ->
+    io:format("Starting... "),
+    statistics(wall_clock),
+    main(BlogDir),
+    {_, T} = statistics(wall_clock),
+    io:format("Done in ~p ms~n",[T]),
+    init:stop().
+
+main(BlogDir) ->
+    Conf = #conf{} = load_conf(BlogDir),
+    recursive_copy(Conf#conf.src, Conf#conf.out),
+    Index = get_entries(Conf),
+    Entries = compile_files(Index, Conf#conf.vars, Conf#conf.markdown),
+    save_entries(Conf#conf.out, Entries),
+    create_index(Conf#conf.index_tpl, Conf#conf.index_out, Entries, Conf#conf.vars),
+    rss(Conf#conf.rss_tpl, Conf#conf.rss_out, Conf#conf.rss_num, Entries, Conf#conf.vars).
+
+load_conf(Base) ->
+    AbsolutePath = filename:absname(Base),
+    {ok, Conf} = file:consult(filename:join(AbsolutePath, "conf.cfg")),
+    Src = filename:join(AbsolutePath, ?conf(sourcedir, Conf)),
+    Out = filename:join(AbsolutePath, ?conf(outdir, Conf)),
+    #conf{
+      src = Src,
+      out = Out,
+      index = element(2, file:consult(filename:join(AbsolutePath,
+                 ?conf(index, files, Conf)))),
+      index_tpl = filename:join(Src, ?conf(index, tpl, Conf)),
+      index_out = filename:join(Out, ?conf(index, out, Conf)),
+      rss_tpl = filename:join(Src, ?conf(rss, tpl, Conf)),
+      rss_out = filename:join(Out, ?conf(rss, out, Conf)),
+      rss_num = ?conf(rss, num_entries, Conf),
+      markdown = ?conf(markdown, Conf),
+      vars = ?conf(vars, Conf)
+    }.
+
+recursive_copy(From, To) ->
+    {ok, Files} = file:list_dir(From),
+    {ok, Re} = re:compile("\\.(tpl|cfg|swp)$"), % skip useless files
+    [ok = rec_copy(From, To, X) || X <- Files, nomatch =:= re:run(X, Re)],
+    ok.
+
+rec_copy(FromPath, ToPath, FileName) ->
+    From = filename:join(FromPath, FileName),
+    To = filename:join(ToPath, FileName),
+    case filetype(From) of
+        directory ->
+            ok = filelib:ensure_dir(To),
+            recursive_copy(From, To);
+        file ->
+            ok = filelib:ensure_dir(To),
+            {ok, _} = file:copy(From, To),
+            ok
+    end.
+
+get_entries(Conf=#conf{}) ->
+     [#article{date=Date,
+               title=Title,
+               slug=sluggify(Title),
+               file=filename:join(Conf#conf.src, File)} ||
+        {Date, Title, File} <- Conf#conf.index].
+
+compile_files([], _, _) -> [];
+compile_files([A = #article{file=File} | Rest], Vars, Markdown) ->
+    MarkdownRequired = fun(Re) ->
+        case re:run(File, Re) of
+            nomatch -> false;
+            _ -> true
+        end
+    end,
+    case lists:any(MarkdownRequired, Markdown) of
+        true ->
+            {ok, Bin} = file:read_file(File),
+            MD = markdown(Bin),
+            {ok, tpl} = erlydtl:compile(MD, tpl,
+                         [{vars, Vars}, {doc_root, filename:dirname(File)}]);
+        false ->
+            ok = erlydtl:compile(File, tpl, [{vars, Vars}])
+    end,
+    {ok, Text} = tpl:render([]),
+    [A#article{text=Text} | compile_files(Rest, Vars, Markdown)].
+
+markdown(Bin) ->
+    iolist_to_binary(parse(binary_to_list(Bin))).
+
+% ideally we'd get a more clever algorithm but eh
+parse([]) -> [];
+parse("{% markdown %}" ++ Rest) ->
+    {MD, Other} = markdown(Rest, []),
+    [MD | parse(Other)];
+parse([Char | Rest]) ->
+    [Char | parse(Rest)].
+
+markdown("{% endmarkdown %}" ++ Rest, Acc) ->
+    {markdown:conv(lists:reverse(Acc)), Rest};
+markdown([], _) ->
+    error("Markdown closing tag ({% endmarkdown %}) not found");
+markdown([Char|Rest], Acc) ->
+    markdown(Rest, [Char|Acc]).
+
+save_entries(_, []) -> ok;
+save_entries(Path, [A = #article{} | Rest]) ->
+    ok = file:write_file(filename:join(Path, A#article.slug++".html"),
+                         A#article.text),
+    save_entries(Path, Rest).
+
+create_index(Src, Out, Pages, Vars) ->
+    Index = [[{date, format_date(A#article.date)},
+              {title, A#article.title},
+              {slug, A#article.slug}] || A <- Pages],
+    ok = erlydtl:compile(Src, tpl, [{vars,�[{pages, lists:reverse(lists:sort(Index))}] ++ Vars}]),
+    {ok, Text} = tpl:render([]),
+    ok = file:write_file(Out, Text).
+
+rss(Src, Out, Num, Pages, Vars) ->
+    Index = lists:reverse(lists:sort(Pages)),
+    Articles = [[{date, A#article.date},
+                 {title, A#article.title},
+                 {slug, A#article.slug},
+                 {desc,
+                  "<![CDATA["++mochiweb_html:to_html({<<"div">>, 
+                                        [],
+                                        article(mochiweb_html:parse(T))})++"]]>"}] ||
+                  A=#article{text=T} <- Index],
+    [#article{date=LatestDate}|_] = Index,
+    ok = erlydtl:compile(Src,
+                         tpl,
+                         [{vars, [{articles, lists:sublist((Articles), Num)},
+                                  {latest_date, LatestDate}] ++ Vars}]),
+    {ok, Text} = tpl:render([]),
+    ok = file:write_file(Out, Text).
+
+%% no need to be efficient, just make sense. worrying about French characters
+%% only (and then some), mainly because I am French speaking.
+sluggify(Str) ->
+    Patterns = [{"[��������]", "a"},
+               {"[��������]", "e"},
+               {"[��������]", "i"},
+               {"[��������]", "o"},
+               {"[��������]", "u"},
+               {"[���]", "y"},
+               {"[��]", "c"},
+               {"[^\\w\\d_-]", "-"},
+               {"[-]{2,}", "-"},
+               {"(?:^-)|(?:-$)", ""}],
+    Slug = lists:foldl(fun({Pat, Rep}, Slug) ->
+                       re:replace(Slug, Pat, Rep, [global, {return, binary}])
+                       end,
+                       Str,
+                       Patterns),
+    string:to_lower(binary_to_list(Slug)).
+
+filetype(FileName) ->
+    case filelib:is_dir(FileName) of
+        true -> directory;
+        false ->
+            case filelib:is_file(FileName) of
+                true -> file;
+                false -> unknown  % symlinks?
+            end
+    end.
+
+%% Use the mochiweb_html tree and find the article we need.
+article({<<"article">>, _, Content}) -> Content;
+article({_, _, Tree}) -> article(Tree);
+article(Bin) when is_binary(Bin) -> not_found;
+article([]) -> not_found;
+article([H|T]) ->
+    case article(H) of
+        not_found -> article(T);
+        Article -> Article
+    end.
+
+%% "Thu, 22 Jul 2010 00:00:00 EST" -> 2010 07 22
+format_date(Str) ->
+    {match, [Day,Month,Year]} = re:run(Str,
+                                       "([\\d]{1,2}) ([\\w]{3}) ([\\d]{4})",
+                                       [{capture, all_but_first, list}]),
+    string:join([Year, month(Month), Day], " ").
+
+month("Jan") -> "01";
+month("Feb") -> "02";
+month("Mar") -> "03";
+month("Apr") -> "04";
+month("May") -> "05";
+month("Jun") -> "06";
+month("Jul") -> "07";
+month("Aug") -> "08";
+month("Sep") -> "09";
+month("Oct") -> "10";
+month("Nov") -> "11";
+month("Dec") -> "12".

File demo-blog/.hgignore

+#use glob syntax.
+syntax: glob
+
+*.swp
+*.beam
+*.dump
+demo-blog/compiled/*
+rel/blogerl/*
+apps/blogerl/deps/*
+apps/markdown
+apps/mochiweb
+apps/erlydtl

File demo-blog/compiled/.this-file-intentionally-left-blank

Empty file added.

File demo-blog/conf.cfg

+%% template compiling config
+{sourcedir, "src/"}.
+{outdir, "compiled/"}.
+{index, [{files, "index.cfg"}, {tpl, "index.tpl"}, {out, "index.html"}]}.
+{markdown, [".md.tpl"]}.
+%% note: RSS uses the <article> tag from HTML5 to find the entry's content
+%% It must be part of your template in order to be used.
+{rss, [{tpl, "rss.tpl"}, {out, "feed.rss"}, {num_entries, 5}]}.
+
+%% config available within the templates
+%{vars,
+% [{url, [{base, "/"},
+%         {img, "/static/img/"},
+%         {js, "/static/js/"},
+%         {css, "/static/css/"}]}]}.
+%% Without a web server, just to test, replace the path
+{vars,
+ [{url, [{base, "file:///home/ferd/code/blogerl/demo-blog/compiled/"},
+         {img, "file:///home/ferd/code/blogerl/demo-blog/compiled/static/img/"},
+         {js, "file:///home/ferd/code/blogerl/demo-blog/compiled/static/js/"},
+         {css, "file:///home/ferd/code/blogerl/demo-blog/compiled/static/css/"}]}]}.

File demo-blog/index.cfg

+{"Tue, 13 Jul 2010 00:00:00 EDT", "Hello, World", "hello.tpl"}.
+{"Sun, 10 Jul 2011 00:00:00 EST", "Markdown Test", "markdown.md.tpl"}.
+{"Wed, 14 Jul 2010 00:00:00 EST", "Second article", "second.tpl"}.

File demo-blog/run.sh

+../rel/blogerl/bin/erl -run blog run "./" -noshell

File demo-blog/src/base.tpl

+{# base template. Name 'base.tpl' reserved. #}<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
+    <link rel="shortcut icon" href="{{ url.img }}favicon.ico" type="image/x-icon"/>
+    <title>Blogerl Demo -> {% block title %}home{% endblock %}</title>
+    <link rel="stylesheet" href="{{ url.css }}screen.css" media="screen, projection" />
+    <link href="{{ url.base }}feed.rss" type="application/rss+xml" rel="alternate" title="ferd.ca newsfeed" />
+  </head>
+
+  <body>
+    <header>
+	  <h1><a href="{{ url.base }}" title="home">Blogerl Demo</a></h1>
+    </header>
+    
+    <article>
+        {% block date %}
+        {% endblock %}
+        {% block content %}
+        {% endblock %}
+    </article>
+  
+    <footer>
+	  <div class="contact">
+	    <ul>
+          <li><a href="mailto:mononcqc+blogerl@gmail.com">Fred T-H</a></li>
+          <li><a href="http://twitter.com/mononcqc/">twitter</a></li>
+          <li><a href="http://learnyousomeerlang.com">Learn You Some Erlang</a></li>
+	    </ul>
+	  </div>
+    </footer>
+  </body>
+</html>
+

File demo-blog/src/hello.tpl

+{% extends "base.tpl" %}
+
+{% block title %}Hello, World{% endblock %}
+
+{% block content %}
+<h2>Hello, World</h2>
+
+<p>I like cats. I like food. <br />
+   I don't especially like catfood though.</p>
+
+<p>No support for IE;<br />
+   Opera, Firefox, Chrome or Safari<br />
+   Should be enough to make you happy.</p>
+{% endblock %}

File demo-blog/src/index.tpl

+{% extends "base.tpl" %}
+{# index template. Name 'index.tpl' reserved. #}
+
+{% block content %}
+<h2>Oh, Hello There!</h2>
+
+<ul class="index">
+{% for page in pages %}
+    <li>
+        <span class="date">{{ page.date }}</span> &mdash;
+        <a href="{{ url.base }}{{ page.slug }}.html">{{ page.title }}</a>
+    </li>
+{% endfor %}
+</ul>
+
+<img class="index" src="{{ url.img }}squid.png" width="387" height="424" />
+
+{% endblock %}
+

File demo-blog/src/markdown.md.tpl

+{% extends "base.tpl" %}
+
+{% block date %}<span class="date">Who cares about dates?</span>{% endblock %}
+{% block title %}Markdown Test{% endblock %}
+
+{% block content %}
+{% markdown %}
+Markdown Test
+-------------
+
+### Section 1
+
+
+Aliquam erat erat, semper sit amet vehicula non, fringilla vitae diam. Vivamus iaculis dui et nibh placerat imperdiet. Proin leo massa, facilisis blandit eleifend at, consequat quis leo. _Mauris vitae erat nec arcu tristique consequat. Sed nulla orci, ultricies quis tempor vitae, semper id lectus._ Curabitur vel venenatis diam. Sed congue ullamcorper rhoncus. Cras fringilla consectetur tincidunt. Quisque vulputate facilisis nibh, non pharetra urna malesuada eget. Fusce condimentum libero at erat semper quis tristique nunc hendrerit. Aliquam vulputate sollicitudin elit eu volutpat. Aliquam lorem arcu, ornare ac porttitor at, blandit sit amet mi. Phasellus cursus facilisis lectus, et consequat neque elementum nec. Suspendisse auctor mollis turpis, nec volutpat augue sagittis at. Vivamus vitae metus non tellus ultricies imperdiet et eu risus. Nunc non augue orci.
+
+- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+- Suspendisse eu nisi non metus tempus malesuada.
+- Suspendisse vitae justo eu nisi accumsan volutpat.
+- Nunc nec est nibh, at pellentesque neque.
+- Nunc eget enim vel magna ornare rutrum vitae id ipsum.
+- In ornare massa in ligula molestie bibendumr
+
+__Aliquam lorem arcu__, ornare ac porttitor at, blandit sit amet mi. Phasellus cursus facilisis lectus, et consequat neque elementum nec. Suspendisse auctor mollis turpis, nec volutpat augue sagittis at. Vivamus vitae metus non tellus ultricies imperdiet et eu risus. Nunc non augue orci.
+
+Aliquam mi tellus, condimentum id blandit nec, fringilla nec lectus. Nulla facilisi. <a href="">Praesent eget felis dolor</a>. Pellentesque ultricies, [urna sed](http://ferd.ca) tempor dapibus, nulla lorem vestibulum justo, nec tincidunt turpis urna quis eros. Aliquam erat lectus, varius sed mollis quis, pretium sed est. Aliquam erat volutpat. Nulla porta congue neque, nec ornare lectus cursus id. Morbi eros est, fringilla id bibendum et, commodo ac nisl. Cras viverra dapibus molestie.
+
+### Section 2 with rather longer titles to see how they do when compressed.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean eleifend, lacus vitae molestie vehicula, augue lorem blandit lorem, euismod dictum ipsum ipsum porttitor justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla facilisi. Proin mollis vulputate ante, eget molestie tellus eleifend eu. Suspendisse potenti. Nullam sapien dolor, rhoncus condimentum luctus non, tempus nec odio. Donec augue purus, fringilla vitae convallis et, pulvinar eu tellus. Nulla tristique fringilla hendrerit. Donec et orci ut orci aliquet consequat id at sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a condimentum felis.
+
+1. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+2. Suspendisse eu nisi non metus tempus malesuada.
+3. Suspendisse vitae justo eu nisi accumsan volutpat.
+4. Nunc nec est nibh, at pellentesque neque.
+5. Nunc eget enim vel magna ornare rutrum vitae id ipsum.
+6. In ornare massa in ligula molestie bibendum.
+
+Nulla facilisi. Aliquam sed dolor interdum leo interdum pharetra eget vitae augue. Vestibulum vitae justo eu sapien bibendum cursus ac ac lacus. Proin varius faucibus velit, non fringilla lorem tempor in. Nulla tempus ante sed leo ultrices rhoncus. Duis id tellus eget neque sagittis luctus suscipit sit amet diam. Aenean eget pretium magna. Sed elementum pharetra sodales. Maecenas at lectus dolor, sed volutpat arcu. In a sem ac lectus porttitor semper. Donec sollicitudin, est id lobortis lacinia, dui urna volutpat sapien, at sodales leo magna a velit.
+
+    1> cd("d:/code/blogerl").
+    d:/code/blogerl
+    ok
+    2>  make:all([load]).
+    up_to_date
+
+Some Explanatory text
+
+    -module(blog).
+    -export([main/1, run/1]).
+    
+    run(BlogDir) -&gt;
+        main(BlogDir),
+        io:format("ok~n"),
+        halt(0).
+    
+    main(BlogDir) -&gt;
+        {Src, Out, Index, Vars} = load_conf(BlogDir),
+        Pages = [{D, T, sluggify(T), Txt} || {D, T, Txt} &lt;- compile_files(Index, Vars)],
+        recursive_copy(Src, Out),
+        save_pages(Out, Pages),
+        create_index(Src, Out, Pages, Vars),
+        ok.
+
+Followed by a quick conclusion
+
+
+### More tags to test
+
+And some text followed by a blockquote:
+
+> It's the only job I can think of where I get to be both an engineer and an artist.  There's an incredible, rigorous, technical element to it, which I like because you have to do very precise thinking.  On the other hand, it has a wildly creative side where the boundaries of imagination are the only real limitation
+
+And tell mom I said hi.
+
+<dl>
+    <dt>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</dt>
+    <dd>Sed sit amet risus ut sem elementum ultricies nec in quam.</dd>
+
+    <dt>Praesent non quam vel enim porttitor porta sit amet a nisl.</dt>
+    <dd>Fusce vitae turpis id lectus tincidunt sodales.</dd>
+    <dd>Donec condimentum sem ante, ut porta eros.</dd>
+</dl>
+
+In purus elit, tincidunt non tincidunt luctus, bibendum id augue. Donec eget blandit metus. Pellentesque turpis massa, consectetur eget sodales ut, vehicula at est. Donec interdum scelerisque lobortis. Nulla et est turpis. In ornare, lorem ut venenatis rhoncus, enim magna rhoncus lectus, eget dictum turpis tortor eu nisi. Cras accumsan `fun() -&gt; X + 3 end` gravida mauris, a porta neque porttitor vel. Nunc aliquet egestas venenatis. Maecenas vel orci non purus sodales congue sit amet id orci. Sed vel aliquam elit. Mauris iaculis metus velit. Morbi eu felis et dolor consectetur facilisis.
+
+    And
+       let's               !
+                       not
+       forget      default
+            the 
+                        pre
+                
+                value
+
+Nam adipiscing semper erat, quis faucibus tortor consectetur et. Donec sit amet metus dui, eu varius justo. Morbi vel nulla sollicitudin ante ullamcorper mattis. Aenean ut libero diam, tempor condimentum sem. Integer sit amet leo id arcu facilisis sollicitudin. Cras est lorem, feugiat eget sagittis quis, placerat vel massa. Morbi ac elit tellus. In in lectus libero. Duis tincidunt semper mauris nec pretium. Pellentesque sed dui quis lectus faucibus sollicitudin non non est.
+{% endmarkdown %}
+{% endblock %}

File demo-blog/src/rss.tpl

+<?xml version="1.0"?>
+<rss version="2.0">
+  <channel>
+    <title>Ferd.ca</title>
+    <link>{{ url.base }}</link>
+    <description>My own blog about programming and whatnot.</description>
+    <language>en-us</language>
+    <pubDate>{{ latest_date }}</pubDate>
+    <lastBuildDate>{{ latest_date }}</lastBuildDate>
+	<ttl>60</ttl>
+	
+    {% for article in articles %}
+    <item>
+      <title>{{ article.title }}</title>
+      <link>{{ url.base }}{{ article.slug }}.html</link>
+      <description>{{ article.desc }}</description>
+      <pubDate>{{ article.date }}</pubDate>
+      <guid>{{ url.base }}{{ article.slug }}.html</guid>
+    </item>
+    {% endfor %}
+ 
+  </channel>
+</rss>
+

File demo-blog/src/second.tpl

+{% extends "base.tpl" %}
+
+{% block date %}<span class="date">2010-08-12</span>{% endblock %}
+{% block title %}Second Article{% endblock %}
+
+{% block content %}
+<h2>Second Article</h2>
+
+<h3>Section 1</h3>
+
+<p>Aliquam erat erat, semper sit amet vehicula non, fringilla vitae diam. Vivamus iaculis dui et nibh placerat imperdiet. Proin leo massa, facilisis blandit eleifend at, consequat quis leo. <em>Mauris vitae erat nec arcu tristique consequat. Sed nulla orci, ultricies quis tempor vitae, semper id lectus.</em> Curabitur vel venenatis diam. Sed congue ullamcorper rhoncus. Cras fringilla consectetur tincidunt. Quisque vulputate facilisis nibh, non pharetra urna malesuada eget. Fusce condimentum libero at erat semper quis tristique nunc hendrerit. Aliquam vulputate sollicitudin elit eu volutpat. Aliquam lorem arcu, ornare ac porttitor at, blandit sit amet mi. Phasellus cursus facilisis lectus, et consequat neque elementum nec. Suspendisse auctor mollis turpis, nec volutpat augue sagittis at. Vivamus vitae metus non tellus ultricies imperdiet et eu risus. Nunc non augue orci.</p>
+
+<ul>
+    <li>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</li>
+    <li>Suspendisse eu nisi non metus tempus malesuada.</li>
+    <li>Suspendisse vitae justo eu nisi accumsan volutpat.</li>
+    <li>Nunc nec est nibh, at pellentesque neque.</li>
+    <li>Nunc eget enim vel magna ornare rutrum vitae id ipsum.</li>
+    <li>In ornare massa in ligula molestie bibendum.</li>
+</ul>
+
+<p><strong>Aliquam lorem arcu</strong>, ornare ac porttitor at, blandit sit amet mi. Phasellus cursus facilisis lectus, et consequat neque elementum nec. Suspendisse auctor mollis turpis, nec volutpat augue sagittis at. Vivamus vitae metus non tellus ultricies imperdiet et eu risus. Nunc non augue orci.</p>
+
+<p>Aliquam mi tellus, condimentum id blandit nec, fringilla nec lectus. Nulla facilisi. <a href="">Praesent eget felis dolor</a>. Pellentesque ultricies, urna sed tempor dapibus, nulla lorem vestibulum justo, nec tincidunt turpis urna quis eros. Aliquam erat lectus, varius sed mollis quis, pretium sed est. Aliquam erat volutpat. Nulla porta congue neque, nec ornare lectus cursus id. Morbi eros est, fringilla id bibendum et, commodo ac nisl. Cras viverra dapibus molestie.</p>
+
+<h3>Section 2 with rather longer titles to see how they do when compressed.</h3>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean eleifend, lacus vitae molestie vehicula, augue lorem blandit lorem, euismod dictum ipsum ipsum porttitor justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla facilisi. Proin mollis vulputate ante, eget molestie tellus eleifend eu. Suspendisse potenti. Nullam sapien dolor, rhoncus condimentum luctus non, tempus nec odio. Donec augue purus, fringilla vitae convallis et, pulvinar eu tellus. Nulla tristique fringilla hendrerit. Donec et orci ut orci aliquet consequat id at sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a condimentum felis.</p>
+
+<ol>
+    <li>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</li>
+    <li>Suspendisse eu nisi non metus tempus malesuada.</li>
+    <li>Suspendisse vitae justo eu nisi accumsan volutpat.</li>
+    <li>Nunc nec est nibh, at pellentesque neque.</li>
+    <li>Nunc eget enim vel magna ornare rutrum vitae id ipsum.</li>
+    <li>In ornare massa in ligula molestie bibendum.</li>
+</ol>
+
+<p>Nulla facilisi. Aliquam sed dolor interdum leo interdum pharetra eget vitae augue. Vestibulum vitae justo eu sapien bibendum cursus ac ac lacus. Proin varius faucibus velit, non fringilla lorem tempor in. Nulla tempus ante sed leo ultrices rhoncus. Duis id tellus eget neque sagittis luctus suscipit sit amet diam. Aenean eget pretium magna. Sed elementum pharetra sodales. Maecenas at lectus dolor, sed volutpat arcu. In a sem ac lectus porttitor semper. Donec sollicitudin, est id lobortis lacinia, dui urna volutpat sapien, at sodales leo magna a velit.</p>
+
+<pre class="brush:eshell">
+1&gt; cd("d:/code/blogerl").
+d:/code/blogerl
+ok
+2&gt; make:all([load]).
+up_to_date
+</pre>
+
+<p>Some Explanatory text</p>
+
+<pre class="brush:erl">
+-module(blog).
+-export([main/1, run/1]).
+
+run(BlogDir) -&gt;
+    main(BlogDir),
+    io:format("ok~n"),
+    halt(0).
+
+main(BlogDir) -&gt;
+    {Src, Out, Index, Vars} = load_conf(BlogDir),
+    Pages = [{D, T, sluggify(T), Txt} || {D, T, Txt} &lt;- compile_files(Index, Vars)],
+    recursive_copy(Src, Out),
+    save_pages(Out, Pages),
+    create_index(Src, Out, Pages, Vars),
+    ok.
+</pre>
+
+<p>Followed by a quick conclusion</p>
+
+
+<h3>More tags to test</h3>
+
+<p>And some text followed by a blockquote: </p>
+
+<blockquote title="Andy Hertzfeld, about programming">
+    It's the only job I can think of where I get to be both an engineer and an artist.  There's an incredible, rigorous, technical element to it, which I like because you have to do very precise thinking.  On the other hand, it has a wildly creative side where the boundaries of imagination are the only real limitation
+</blockquote>
+
+<p>And tell mom I said hi.</p>
+
+<dl>
+    <dt>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</dt>
+    <dd>Sed sit amet risus ut sem elementum ultricies nec in quam.</dd>
+
+    <dt>Praesent non quam vel enim porttitor porta sit amet a nisl.</dt>
+    <dd>Fusce vitae turpis id lectus tincidunt sodales.</dd>
+    <dd>Donec condimentum sem ante, ut porta eros.</dd>
+</dl>
+
+<p>In purus elit, tincidunt non tincidunt luctus, bibendum id augue. Donec eget blandit metus. Pellentesque turpis massa, consectetur eget sodales ut, vehicula at est. Donec interdum scelerisque lobortis. Nulla et est turpis. In ornare, lorem ut venenatis rhoncus, enim magna rhoncus lectus, eget dictum turpis tortor eu nisi. Cras accumsan <code>fun() -&gt; X + 3 end</code> gravida mauris, a porta neque porttitor vel. Nunc aliquet egestas venenatis. Maecenas vel orci non purus sodales congue sit amet id orci. Sed vel aliquam elit. Mauris iaculis metus velit. Morbi eu felis et dolor consectetur facilisis.</p>
+
+<pre>And
+   let's               !
+                   not
+   forget      default
+        the 
+                    pre
+            
+            value
+</pre>
+
+<p>Nam adipiscing semper erat, quis faucibus tortor consectetur et. Donec sit amet metus dui, eu varius justo. Morbi vel nulla sollicitudin ante ullamcorper mattis. Aenean ut libero diam, tempor condimentum sem. Integer sit amet leo id arcu facilisis sollicitudin. Cras est lorem, feugiat eget sagittis quis, placerat vel massa. Morbi ac elit tellus. In in lectus libero. Duis tincidunt semper mauris nec pretium. Pellentesque sed dui quis lectus faucibus sollicitudin non non est.</p>
+{% endblock %}

File demo-blog/src/static/css/screen.css

+/* CSS Reset courtesy of http:///meyerweb.com/eric/tools/css/reset/ */
+/* v1.0 | 20080212 */
+
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset,
+form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	outline: 0;
+	font-size: 100%;
+	vertical-align: baseline;
+	background: transparent;
+}
+body { line-height: 1; }
+ol, ul { list-style: none; }
+blockquote, q {	quotes: none; }
+blockquote:before, blockquote:after, q:before, q:after {
+	content: '';
+	content: none;
+}
+/* remember to define focus styles! */
+:focus { outline: 0; }
+
+/* remember to highlight inserts somehow! */
+ins { text-decoration: none; }
+del { text-decoration: line-through; }
+
+/* tables still need 'cellspacing="0"' in the markup */
+table { border-collapse: collapse; border-spacing: 0; }
+
+/* Real Styling */
+html {
+    text-align: center;
+    background-color: #f5f5fd; /*#f9f5eb;*/
+    color: #333;
+    font-family: Trebuchet Ms, Arial;
+}
+body {
+    margin: 0 auto;
+    text-align: center;
+}
+
+header, footer, article {
+    display: block;
+    text-align: left;
+}
+
+header {
+    text-align: center;
+    background: #333;
+    font-size: 1.3em;
+    box-shadow: -0.2em 0 0.2em #111; /* Giving up on a clean scrollbar */
+    -moz-box-shadow: -0.2em 0 0.2em #111;
+    -webkit-box-shadow: 0 0 0.2em #111;
+}
+
+header h1 {
+    width: 80%;
+    margin: 0 auto;
+    text-align: left;
+    padding: 0.5em 1.4em;
+    font-family: Georgia;
+    letter-spacing: 0.2em;
+}
+
+header a {
+    color: #f9f5eb;
+    text-decoration: none;
+}
+
+header a:hover {
+    text-decoration: underline;
+}
+
+article {
+    width: 70%;
+    margin: 0 auto;
+    padding: 2em 0.5em 0.5em;
+    color: #666;
+}
+
+article a {
+    color: #888;
+}
+
+article span.date {
+    font-family: monospace;
+    font-size: 0.9em;
+    color: #888;
+}
+
+article h2 {
+    margin: 0.2em 0;
+    font-size: 1.5em;
+    color: #69f;
+    font-family: Trebuchet MS, Arial;
+}
+
+article h3 {
+    margin: 1.2em 0 0.4em;
+    font-size: 1.2em;
+    font-style: italic;
+    color: #69f;
+    font-family: Trebuchet MS, Arial;
+}
+
+article p {
+    margin: 1.2em 0 0.4em;
+    line-height: 1.4em;
+    font-size: 0.9em;
+    color: #333;
+    font-family: Trebuchet MS, Arial;
+}
+
+article dl,
+article ul,
+article ol {
+    margin: 1.2em 0 0.4em 1.5em;
+    color: #333;
+    font-size: 0.9em;
+    line-height: 1.4em;
+}
+
+article ul {
+    list-style-type: disc;
+}
+
+article ol {
+    list-style-type: decimal;
+}
+
+article ul li {
+    padding-left: 0.5em;
+}
+
+article dl dt {
+    font-style: italic;
+    margin-top: 1em;
+    color: #666;
+}
+
+article dl dd {
+    margin-left: 0.7em;
+    list-style-type: disc;
+}
+
+article blockquote {
+    margin: 1.2em 0 0.4em 1.5em;
+    padding: 0.5em;
+    line-height: 1.4em;
+    font-size: 0.9em;
+    color: #555;
+    font-family: Trebuchet MS, Arial;
+    background: #eee;
+    border: 0.1em dotted #ddd;
+}
+
+article code {
+    font-size: 1em;
+    background: #e5e5ff;/*#eeffcc;*/
+    border-bottom: 1px #aaaadd dotted; 
+}
+
+article pre {
+    margin: 1.2em 0 0.4em 1.5em;
+    padding: 0.5em;
+    line-height: 1.4em;
+    font-size: 0.9em;
+    color: #555;
+    width: 90%;
+    overflow: auto;
+}
+
+article pre code {
+    font-size: 1em;
+    color: #555;
+    background: none;
+    border: 0;
+}
+
+article ul.index {
+    float:left;
+    margin-bottom: 2em;
+}
+article img.index {
+    margin: -2em 0 0 0;
+    float: right;
+}
+
+article abbr {
+    border-bottom: 0.1em dotted #666;
+}
+
+footer {
+    clear: both;
+    width: 70%;
+    text-align: right;
+    margin: 3em auto 0;
+    font-size: 0.8em;
+}
+
+footer div.contact {
+    float: right;
+    width: 30%;
+}
+
+footer div.contact ul {
+    border-top: 0.1em #333 solid;
+    margin-bottom: 1em;
+}
+
+footer div.contact ul li {
+    margin: 0.2em 0;
+}
+
+footer div.contact ul li a {
+    text-decoration: none;
+    color: #666;
+}
+
+footer div.contact ul li a:hover {
+    text-decoration: underline;
+    color: #888;
+}

File demo-blog/src/static/img/favicon.ico

Added
New image

File demo-blog/src/static/img/squid.png

Added
New image

File rebar.config

+{sub_dirs, ["apps/blogerl"]}.
+{require_otp_vsn, "R14"}.
+{rebar_plugins, [agner_rebar_plugin]}.
+{lib_dirs, ["apps"]}.

File rel/blogerl/.this-file-left-empty

Empty file added.

File rel/build.sh

+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%! -smp enable -noshell
+main(_) ->
+    {ok, Conf} = file:consult("rel.conf"),
+    {ok, Spec} = reltool:get_target_spec(Conf),
+    reltool:eval_target_spec(Spec, code:root_dir(), "blogerl").

File rel/rel.conf

+{sys, [
+    {lib_dirs, ["../apps/", "../apps/blogerl/deps/"]},
+    {rel, "blogerl", "0.9.0",
+     [kernel,
+      stdlib,
+      compiler,
+      markdown,
+      erlydtl,
+      blogerl]},
+    {boot_rel, "blogerl"},
+    {incl_cond, exclude},
+    {relocatable, true},
+    {profile, embedded},
+    {app_file, strip},
+    {debug_info, strip},
+    {erts, [
+      {mod_cond, derived},
+      {app_file, strip}
+    ]},
+    {app, stdlib, [{mod_cond, derived},
+                   {incl_cond, include}]},
+    {app, syntax_tools, [{mod_cond, derived}, {incl_cond, include}]},
+    {app, kernel, [{incl_cond, include}]},
+    {app, blogerl, [{vsn, "0.9.0"},
+                    {app_file, keep},
+                    {incl_cond, include},
+                    {debug_info, keep}]},
+    {app, markdown, [{app_file, all},
+                     {incl_cond, include}]},
+    {app, erlydtl, [{incl_cond, include}]},
+    {app, mochiweb, [{incl_app_filters, [
+                      "^ebin/mochiutf8.beam$",
+                      "^ebin/mochiweb_charref.beam$",
+                      "^ebin/mochiweb_html.beam$"
+                     ]},
+                     {incl_cond, include}]},
+    {app, compiler, [{incl_cond, include}]}
+    %% also explain other options
+]}.
+