Basti's Scratchpad on the Internet

Posts tagged "blog":

25 May 2018

GDPR Compliance

Personal email is dead. The signal-to-noise ratio of my personal email account has been deeply negative for years. But the last few days have been especially riveting, with a torrent of GDPR-compliance emails from just about every company that has ever gotten their hands on my email address. Anecdotally, if spam makes up about 90% of all email traffic, and the last few days have seen a ten-fold increase in traffic due to GDPR emails, we might even have "defeated spam" for a few days! Yay internet!

But sadly, I can't add to the signal-to-noise ratio, because this website does not collect any email addresses. And even more sadly, it doesn't even collect IP addresses, or use any kind of analytics at all. Sorry about that. I honestly do not know how many people read my stuff, and I can not rank my blog posts by their popularity. And I like it that way. It prevents me from getting on any kind of treadmill to please any kind of imagined audience.

That is… with one exception: Comments on this website are powered by Disqus, and I'm sure Disqus collects all kinds of data about all kinds of things. That's why comments are now hidden behind a "Load Disqus Comments" button. I promise you that no foreign Javascript is executed unless you press that button1. And honestly, GDPR didn't even have anything to do with that. I just didn't want any foreign JavaScript.

Footnotes:

1

Unless katex.min.js somehow is loading external javascript…

Tags: privacy blog

Blogging with org-static-blog (Updated)

A while ago, I scratched an old itch and wrote my own static site generator, called org-static-blog. It is a simple thing: You hand it a directory full of *.org files with a #+title: and #+date:, and it assembles a bunch of HTML pages and an RSS feed from them. There are no external dependencies beyond Emacs.

Today, I released version 1.1.0 of org-static-blog, which introduces two new features: speed, and tags. You can now—optionally—add #+filetags: to your *.org files, (setq org-static-blog-enable-tags t), and org-static-blog will add tag links to every blog post, create tag indices, and add <category> tags to the RSS feed.

Update:

As Kaushal Modi pointed out in the comments, org-mode uses #+filetags: to set tags for entire files. My first implementation used #+tags:. This is now deprecated. #+tags: still works, but issues a warning, and will be removed in the future. Sorry for the inconvenience.

Tags: blog

Speeding up org-static-blog

Three years ago, I had enough of all the static site generators out there. Over the life of this blog, I had used Octopress, then Pelican, then Coleslaw, then org-mode, and then wrote my own static site generator, org-static-blog. Above all, org-static-blog is simple. It iterates over all *.org files in org-static-blog-posts-directory, and then exports all of these files to HTML. Simple is good. Simple is reliable. Simple means I can fix things.

However, simple can also mean inefficient. Most glaringly, org-static-blog exports every single blog post three times every time you publish: Once to render the HTML, then once to render the RSS feed, then once to render the Index and Archive pages.

Today, I finally tackled this problem: Now, org-static-blog only exports each post once, when the *.org file changes. The RSS feed, the Index page, and the Archive page simply read the already-rendered HTML instead of exporting again.

Thus, a full rebuild of this blog and all of its 85 posts used to take 2:12 min, and now takes 42 s. More importantly, if only one org file changed, the rebuild used to take 1:08 min, and now takes 1.5 s. Things like this are hugely satisfying to me!

Tags: org-mode emacs blog

Blogging with Emacs

When I first started blogging, it was on blogger.com (on the now-abandoned domain daskrachen.com). On blogger, writing new posts (back then) involved typing raw HTML into a web form. Not what I would call ideal. This improved somewhat when they introduced a fancy rich text editor that would automatically transform beautiful text into a horrible formatting mess.

Thus I switched. Getting my blog posts out of blogger was… Let's just say that I lost anything I didn't have a plain-text backup of. And I switched to Pelican, a static site generator written in Python. It worked beautifully, until I updated something, at which point it resorted to just throwing errors. Now I don't have anything in particular against Python stack traces, but these particular traces traced deep into stuff that was (then?) too complex for me to understand.

Thus I switched again. This time to C()λ∈slaw■, a static site generator written in Common Lisp. Mainly because I was interested in Common Lisp at the time. It worked really well. However, this was supposed to give me a chance to delve into Common Lisp, and I failed to understand C()λ∈slaw■'s code. Realistically though, this is probably not C()λ∈slaw■'s fault. My knowledge of Common Lisp is far from perfect.

Thus it was time to switch again. Having been enamored with Emacs for the last few years, it made sense to blog with Emacs as well. Besides, I am kind of fed up with the many conflicting flavors of Markdown out there and have switched my personal note-taking to Org mode long ago. So let's set up Emacs and Org as a blogging platform!

Before we start though, a short disclaimer: This will be a very bare bones blogging engine. It will consist of some articles, a front page, an archive page, and an RSS feed. And you will have to manage the front page and RSS feed semi-manually. No tags, no fancy history. Just what you see here.

On the plus side, this will be implemented entirely within Emacs and very simple to understand. Writing a new blog post will be as simple as writing an Org file and hitting a key combination! And you will get all of Org's fancy syntax highlighting and export magic for free!

Getting the pages to work is rather simple: You have to create a "publishing project" that specifies a base-directory where your Org files live and a publishing-directory, where the HTML files are going to be stored. Since this is Emacs, you could make your publishing directory any TRAMP path you like and insta-publish your workings!

(BTW, I am using Org 8.2.2 and I believe you need at least 8.0 for these examples to work)

(require 'ox-html)
(require 'ox-rss)
(require 'ox-publish)
(setq org-publish-project-alist
      '(("blog-content"
         :base-directory "~/Projects/blog/posts"
         :html-extension "html"
         :base-extension "org"
         :publishing-directory "~/Projects/blog/publish"
         :publishing-function (org-html-publish-to-html)
         :recursive t          ; descend into sub-folders?
         :section-numbers nil  ; don't create numbered sections
         :with-toc nil         ; don't create a table of contents
         :with-latex t         ; do use MathJax for awesome formulas!
         :html-head-extra ""   ; extra <head> entries go here
         :html-preamble ""     ; this stuff is put before your post
         :html-postamble ""    ; this stuff is put after your post
)))

Now hit M-x org-publish, type in blog-content, and you have a blog! Awesome! We are done here.

Well, how about an archive page that lists all your previous blog entries?

Emacs can auto-generate this for you. Simply add these lines to blog-content:

         :auto-sitemap t
         :sitemap-filename "archive.org"
         :sitemap-title "Archive"
         :sitemap-sort-files anti-chronologically
         :sitemap-style list
         :makeindex t

Also, you can put something like

<a href="archive.html">Other posts</a>

into your :html-postamble to make every page link to this. You can also add your Disqus snippet there to enable comments.

Adding a front page is simple, too. My front page is simply a normal page called index.org, which contains links and slugs for every article I want to have on the front page. For example:

 #+TITLE: RECENT POSTS

 * [[file:2013-05-30-speeding-up-matplotlib.org][Speeding up Matplotlib]]
 #+include: "~/Projects/blog/posts/2013-05-30-speeding-up-matplotlib.org" :lines "4-9"
 [[file:2013-05-30-speeding-up-matplotlib.org][read more...]]

But a blog is more than just text. There are images and CSS, too. I keep all that stuff in a separate directory and use a separate publishing project to copy it over to the publishing directory. Just add to your publishing-alist:

("blog-static"
 :base-directory "~/Projects/blog/static"
 :base-extension "png\\|jpg\\|css"
 :publishing-directory "~/Projects/blog/publish/static"
 :recursive t
 :publishing-function org-publish-attachment)

Setting up the RSS feed works similarly. The RSS feed is created from a single Org file. Create a new publishing project and put it into your publishing-alist

("blog-rss"
 :base-directory "~/Projects/blog/posts"
 :base-extension "org"
 :publishing-directory "~/Projects/blog/publish"
 :publishing-function (org-rss-publish-to-rss)
 :html-link-home "http://bastibe.de/"
 :html-link-use-abs-url t
 :exclude ".*"
 :include ("rss.org")
 :with-toc nil
 :section-numbers nil
 :title "Bastis Scratchpad on the Internet")

Make sure to exclude this rss.org from the blog-content project by adding it's name to the :exclude variable though. This rss.org file should contain headlines for every blog post. Every headline needs a publishing date and a permalink as property and the body of the post as content:

 * Speeding up Matplotlib
 :PROPERTIES:
 :RSS_PERMALINK: "http://bastibe.de/2013-05-30-speeding-up-matplotlib.html"
 :PUBDATE: <2013-05-30>
 :END:
 #+include: "~/Projects/blog/posts/2013-05-30-speeding-up-matplotlib.org" :lines "4-"

I exclude the first three lines, since they only contain #+title, #+date, and #+tags. You should at least exclude the #+title line. Otherwise, ox-rss will get confused about which title to choose for the feed.

You can even create a meta publishing project that executes all three projects in one fell swoop!

("blog"
 :components ("blog-content" "blog-static" "blog-rss"))

There is one more thing that is kind of fiddly though: As I said, I use Disqus for comments, but I don't want to have comment boxes on the front page or the archive. Thankfully though, ox-html allows you to set :html-preamble and :html-postamble to a function, in which case that function can decide what pre/postamble to draw! The function can take an optional argument that contains a plist of article metadata. In this case, I decide on the :title metadata whether to print the archive link and Disqus, only the archive link, or neither:

:html-postamble
(lambda (info)
  "Do not show disqus for Archive and Recent Posts"
  (cond ((string= (car (plist-get info :title)) "Archive")
         "")
        ((string= (car (plist-get info :title)) "Recent Posts")
         "<div id=\"archive\"><a href=\"archive.html\">Other posts</a></div>")
        (t
    "<div id=\"archive\"><a href=\"archive.html\">Other posts</a></div>
     <div id=\"disqus_thread\"></div>
     <script type=\"text/javascript\">
     ..."

This should get you started! For completeness, here is my complete configuration:

(require 'ox-html)
(require 'ox-rss)
(require 'ox-publish)
(setq org-publish-project-alist
      '(("blog"
         :components ("blog-content" "blog-static" "blog-rss"))
        ("blog-content"
         :base-directory "~/Projects/blog/posts"
         :html-extension "html"
         :base-extension "org"
         :publishing-directory "~/Projects/blog/publish"
         :publishing-function (org-html-publish-to-html)
         :auto-sitemap t
         :sitemap-filename "archive.org"
         :sitemap-title "Archive"
         :sitemap-sort-files anti-chronologically
         :sitemap-style list
         :makeindex t
         :recursive t
         :section-numbers nil
         :with-toc nil
         :with-latex t
         :html-head-include-default-style nil
         :html-head-include-scripts nil
         :html-head-extra
         "<link rel=\"alternate\" type=\"appliation/rss+xml\"
                href=\"http://bastibe.de/rss.xml\"
                title=\"RSS feed for bastibe.de\">
          <link href='http://fonts.googleapis.com/css?family=Roboto&subset=latin' rel='stylesheet' type='text/css'>
          <link href='http://fonts.googleapis.com/css?family=Ubuntu+Mono' rel='stylesheet' type='text/css'>
          <link href= \"static/style.css\" rel=\"stylesheet\" type=\"text/css\" />
          <title>Basti's Scratchpad on the Internet</title>
          <meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=UTF-8\" />
          <meta name=\"viewport\" content=\"initial-scale=1,width=device-width,minimum-scale=1\">"
         :html-preamble
         "<div class=\"header\">
              <a href=\"http://bastibe.de\">Basti's Scratchpad on the Internet</a>
              <div class=\"sitelinks\">
                  <a href=\"http://alpha.app.net/bastibe\">alpha.app.net</a>  | <a href=\"http://github.com/bastibe\">Github</a>
              </div>
          </div>"
         :html-postamble
         (lambda (info)
           "Do not show disqus for Archive and Recent Posts"
           (cond ((string= (car (plist-get info :title)) "Archive") "")
                 ((string= (car (plist-get info :title)) "Recent Posts")
                  "<div id=\"archive\"><a href=\"archive.html\">Other posts</a></div>")
                 (t
             "<div id=\"archive\"><a href=\"archive.html\">Other posts</a></div>
              <div id=\"disqus_thread\"></div>
              <script type=\"text/javascript\">
              /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
              var disqus_shortname = 'bastibe';
              /* * * DON'T EDIT BELOW THIS LINE * * */
              (function() {
                var dsq = document.createElement('script');
                dsq.type = 'text/javascript';
                dsq.async = true;
                dsq.src = 'http://' + disqus_shortname + '.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\">comments powered by Disqus.</a></noscript>
              <a href=\"http://disqus.com\" class=\"dsq-brlink\">comments powered by <span class=\"logo-disqus\">Disqus</span></a>")))
         :exclude "rss.org\\|archive.org\\|theindex.org")
        ("blog-rss"
         :base-directory "~/Projects/blog/posts"
         :base-extension "org"
         :publishing-directory "~/Projects/blog/publish"
         :publishing-function (org-rss-publish-to-rss)
         :html-link-home "http://bastibe.de/"
         :html-link-use-abs-url t
         :exclude ".*"
         :include ("rss.org")
         :with-toc nil
         :section-numbers nil
         :title "Bastis Scratchpad on the Internet")
        ("blog-static"
         :base-directory "~/Projects/blog/static"
         :base-extension "png\\|jpg\\|css"
         :publishing-directory "~/Projects/blog/publish/static"
         :recursive t
         :publishing-function org-publish-attachment)))

All other sources, including the source code to all blog posts, can be found on Github (the master branch contains HTML, the source branch contains Org).

Addendum: I have since discovered that org-rss-publish-to-rss only handles top-level headlines, but disregards second-level or higher-level headlines. Thus, if you have a post with nested headlines, your RSS feed will only include the text of the top-level one. To fix this, I advised org-rss-publish-to-rss to use org-html-headline for non-top-level headlines like this:

(defadvice org-rss-headline
  (around my-rss-headline (headline contents info) activate)
  "only use org-rss-headline for top level headlines"
  (if (< (org-export-get-relative-level headline info) 2)
      ad-do-it
    (setq ad-return-value (org-html-headline headline contents info))))

Now, the RSS feed includes the full text of all articles.

Tags: emacs blog

Blogging with Pelican (and not Octopress)

For a while now, I have been moving more and more services I use off Google. The reasons for that are manyfold, and few of them have anything to do with Google being evil or not–just to get that out of the way.

One of the last holdouts has been my neglected Blogspot blog. And one of the reasons for it being neglected is that it was hosted on Blogspot. Now don't get me wrong here, Blogspot is a terriffic blogging platform. You have this very nice nearly-WYSIWYG text editor right in your browser, you can upload images, you can publish instantly to your blog… Basically everything is taken care of for you conveniently right there in your browser. Google Style.

Its just that I don't like to work that way. I like plain text. I like typing stuff into a plain text editor. I like to be in control. And Blogspot might be convenient, but it did not make me feel like I was in control. In fact, I lost at least one article to Blogspot for unknown reasons.

Enter static site generators. The idea is that instead of writing rich text into some website, you create you content however you want on your own computer, then use a static generator which converts it into a set of static HTML pages and upload those to your website. Now all of the creation process is happening on your computer. You are in control. Probably the most popular program to do that is Jekyll.

The second part of the equation is some kind of publishing platform. With these static site generators, really any web server does that trick. Just push your generated HTML files to the server and be done with it. Even cooler is Github. Using Github pages, you can use your existing Github account and infrastructure to publish your blog just by pushing the HTML to Github. This is seriously cool!

So, I set out and tried Octopress, which combines these two things into a nice blogging platform. Honestly though, I don't know much Ruby and all that rake workflow did not make me much more comfortable than pushing stuff into Blogspot.

Hence, I looked for alternatives. What I ended up with is Pelican, a very simple static site generator written in Python. Finally, this is a codebase that is easy enough for me to understand and modify. If there is any trouble with my blog, I will be (and have been) able to just look at the source code and figure out what is going wrong. I like this!

To publish a new blog post, I will start by writing the post in Markdown (a format I understand), process it using the very simple command line pelican -s my_config_file.py posts/, and push the result to GitHub. Easy as pie. And I feel like I am in control again!

Actually, if this is just a bit too technical for you, check out Calepin instead. It uses the very same Pelican engine, but instead of fiddling with Git, you just put your markdown files into your Dropbox, and–poof–you magically have a Blog!

If you want to see my blog as a repo on Github, just go have a look (the master branch contains the HTML, the source branch contains the configuration and Markdown).

Tags: blog

How to get solarized syntax highlighting on your blog

Yesterday, I spent a few hours creating a solarized like CSS file for use with my new blogging tool. You could just scrape the settings from this very website, but for convenience, here they are:

/* Text */
.codehilite .t   { color: #586e75 }

/* Whitespace */
.codehilite .w   { color: #073642 }

/* Error */
.codehilite .err { color: #cb4b16; }

/* Keyword */
.codehilite .k   { color: #859900 }
.codehilite .kc  { color: #2aa198 } /* Keyword.Constant */
.codehilite .kd  { color: #268bd2 } /* Keyword.Declaration */
.codehilite .kn  { color: #b58900 } /* Keyword.Namespace */
.codehilite .kp  { color: #859900 } /* Keyword.Pseudo */
.codehilite .kr  { color: #073642 } /* Keyword.Reserved */
.codehilite .kt  { color: #b58900 } /* Keyword.Type */

/* Name */
.codehilite .n   { color: #586e75 }
.codehilite .na  { color: #2aa198 } /* Name.Attribute */
.codehilite .nb  { color: #268bd2 } /* Name.Builtin */
.codehilite .nc  { color: #268bd2 } /* Name.Class */
.codehilite .ne  { color: #cb4b16 } /* Name.Error */
.codehilite .no  { color: #2aa198 } /* Name.Constant */
.codehilite .nd  { color: #2aa198 } /* Name.Decorator */
.codehilite .ni  { color: #2aa198; font-weight: bold } /* Name.Entity */
.codehilite .nf  { color: #268bd2 } /* Name.Function */
.codehilite .nn  { color: #586e75; } /* Name.Namespace */
.codehilite .nt  { color: #2aa198; font-weight: bold } /* Name.Tag */
.codehilite .nv  { color: #cb4b16 } /* Name.Variable */

/* Builtin */
.codehilite .b   { color: #859900 }
.codehilite .bp  { color: #586e75 } /* Name.Builtin.Pseudo */

/* Variable */
.codehilite .v   { color: #586e75 }
.codehilite .vc  { color: #586e75 } /* Name.Variable.Class */
.codehilite .vg  { color: #268bd2 } /* Name.Variable.Global */
.codehilite .vi  { color: #268bd2 } /* Name.Variable.Instance */

/* Literal */

/* Literal.Number */
.codehilite .m { color: #268bd2 } /* Literal.Number */
.codehilite .mf { color: #268bd2 } /* Literal.Number.Float */
.codehilite .mh { color: #268bd2 } /* Literal.Number.Hex */
.codehilite .mi { color: #268bd2 } /* Literal.Number.Integer */
.codehilite .mo { color: #268bd2 } /* Literal.Number.Oct */

/* Literal.String */
.codehilite .s { color: #2aa198 }
.codehilite .sb { color: #2aa198 } /* Literal.String.Backtick */
.codehilite .sc { color: #2aa198 } /* Literal.String.Char */
.codehilite .sd { color: #2aa198 } /* Literal.String.Doc */
.codehilite .s2 { color: #2aa198 } /* Literal.String.Double */
.codehilite .se { color: #cb4b16 } /* Literal.String.Escape */
.codehilite .sh { color: #2aa198 } /* Literal.String.Heredoc */
.codehilite .si { color: #cb4b16 } /* Literal.String.Interpol */
.codehilite .sx { color: #2aa198 } /* Literal.String.Other */
.codehilite .sr { color: #cb4b16 } /* Literal.String.Regex */
.codehilite .s1 { color: #2aa198 } /* Literal.String.Single */
.codehilite .ss { color: #cb4b16 } /* Literal.String.Symbol */

/* Literal.Integer */
.codehilite .il { color: #268bd2 } /* Literal.Number.Integer.Long */

/* Operator */
.codehilite .o  { color: #586e75 }
.codehilite .ow { color: #859900 } /* Operator.Word */

/* Punctuation */
.codehilite .p  { color: #586e75 }

/* Comment */
.codehilite .c { color: #93a1a1; font-style: italic }
.codehilite .cm { color: #93a1a1; } /* Comment.Multiline */
.codehilite .cp { color: #93a1a1 } /* Comment.Preproc */
.codehilite .c1 { color: #93a1a1; } /* Comment.Single */
.codehilite .cs { color: #93a1a1; } /* Comment.Special */

.codehilite .hll { background-color: #dc322f }

/* Generic */
.codehilite .g { color: #586e75 }
.codehilite .gd { color: #586e75 } /* Generic.Deleted */
.codehilite .ge { font-style: italic } /* Generic.Emph */
.codehilite .gr { color: #586e75 } /* Generic.Error */
.codehilite .gh { color: #586e75; font-weight: bold } /* Generic.Heading */
.codehilite .gi { color: #586e75 } /* Generic.Inserted */
.codehilite .go { color: #586e75 } /* Generic.Output */
.codehilite .gp { color: #586e75 } /* Generic.Prompt */
.codehilite .gs { font-weight: 586e75 } /* Generic.Strong */
.codehilite .gu { color: #586e75; font-weight: bold } /* Generic.Subheading */
.codehilite .gt { color: #586e75 } /* Generic.Traceback */
code, pre {
	background: #fdf6e3;
	-webkit-box-shadow: inset 0 0 2px #000000;
	-moz-box-shadow: inset 0 0 2px #000000;
	box-shadow: inset 0 0 2px #000000;
	color: #586e75;
	margin-left: 0px;
	font-family: 'Droid Sans Mono', monospace;
	padding: 2px;
	-webkit-border-radius: 4px;
	-moz-border-radius: 4px;
	border-radius: 4px;
	-moz-background-clip: padding;
	-webkit-background-clip: padding-box;
	background-clip: padding-box;
}

Keep in mind though that I have no formal knowledge of CSS whatsoever beyond what I could gather from these very files.