<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>/dev/tty</title>
	<atom:link href="http://blog.tty.nl/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.tty.nl</link>
	<description>Notes on Web Development, Computer Programming, and Software Engineering</description>
	<lastBuildDate>Thu, 29 Dec 2011 10:59:13 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Splitting a database dump</title>
		<link>http://blog.tty.nl/2011/12/28/splitting-a-database-dump/</link>
		<comments>http://blog.tty.nl/2011/12/28/splitting-a-database-dump/#comments</comments>
		<pubDate>Wed, 28 Dec 2011 12:43:50 +0000</pubDate>
		<dc:creator>Eduard Lohmann</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Shell]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=578</guid>
		<description><![CDATA[This is a way to split a SQL dump into tables that is relatively easy. It should start with zcat FILE.gz &#124; csplit -ftable &#8211; &#8220;/DROP TABLE/&#8221; {*}, but csplit has bug where reading a lot from standard in does &#8230; <a href="http://blog.tty.nl/2011/12/28/splitting-a-database-dump/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>This is a way to split a SQL dump into tables that is relatively easy.</p>
<p>It should start with zcat FILE.gz | csplit -ftable &#8211; &#8220;/DROP TABLE/&#8221; {*},<br />
but csplit has bug where reading a lot from standard in does not work.<br />
So instead unzip your file first.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">gunzip FILE.gz<br />
csplit -ftable FILE &quot;/DROP TABLE/&quot; {*}</div></div>
<p>Then to give the files meaningful names:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">for FILE in `ls -1 table*`; do <br />
&nbsp; &nbsp; &nbsp;NAME=`head -n1 $FILE | cut -d$'\x60' -f2`<br />
&nbsp; &nbsp; &nbsp;mv $FILE &quot;$NAME.sql&quot;;<br />
done;</div></div>
<p>If your dump does not start witjh a DROP TABLE `name` IF EXISTS,<br />
you will have to change the match expression to csplit a litte.</p>
<p>Hope this is useful to someone.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2011/12/28/splitting-a-database-dump/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Setting up Emacs to compare two git tags</title>
		<link>http://blog.tty.nl/2011/12/09/setting-up-emacs-to-compare-two-git-tags/</link>
		<comments>http://blog.tty.nl/2011/12/09/setting-up-emacs-to-compare-two-git-tags/#comments</comments>
		<pubDate>Fri, 09 Dec 2011 11:30:24 +0000</pubDate>
		<dc:creator>Eduard Lohmann</dc:creator>
				<category><![CDATA[Emacs]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=530</guid>
		<description><![CDATA[Introduction This is a setup for using Emacs to compare two git tags. It is based on Ediff mode and Ediff Trees. Ediff Trees is a useful front-end for comparing large trees of files. To make things easier we have &#8230; <a href="http://blog.tty.nl/2011/12/09/setting-up-emacs-to-compare-two-git-tags/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h2>Introduction</h2>
<p>This is a setup for using Emacs to compare two git tags.  It is based on <a href="//www.emacswiki.org/emacs/EdiffMode" title="Ediff mode">Ediff mode</a> and <a href="http://www.emacswiki.org/emacs/EdiffTrees" title="Ediff Trees">Ediff Trees</a>. Ediff Trees is a useful front-end for comparing large trees of files. To make things easier we have two git clones, one for each tag that we call before and after.</p>
<h2>Needed Code</h2>
<p>Create a file named ediff-trees.el with the following contents and place it in your Emacs load path:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">;;; ediff-trees.el --- Recursively ediff two directory trees<br />
;;;----------------------------------------------------------------------<br />
;; Author: Joao Cachopo &lt;joao.cachopo@inesc-id.pt&gt;<br />
;; Created on: Wed May 10 17:30:49 2006<br />
;; Keywords: ediff, comparing<br />
;; Version: 20071126.1<br />
;;<br />
;; Copyright (C) 2006 Joao Cachopo<br />
<br />
;; This program is not part of GNU Emacs<br />
<br />
;; This program is free software; you can redistribute it and/or<br />
;; modify it under the terms of the GNU General Public License as<br />
;; published by the Free Software Foundation; either version 2, or (at<br />
;; your option) any later version.<br />
<br />
;; This program is distributed in the hope that it will be useful, but<br />
;; WITHOUT ANY WARRANTY; without even the implied warranty of<br />
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. &nbsp;See the GNU<br />
;; General Public License for more details.<br />
<br />
;; You should have received a copy of the GNU General Public License<br />
;; along with GNU Emacs; see the file COPYING. &nbsp;If not, write to the<br />
;; Free Software Foundation, 675 Massachusettes Ave, Cambridge, MA<br />
;; 02139, USA.<br />
<br />
;;; Commentary:<br />
<br />
;; The ediff-trees package is a simple frontend to the emacs' ediff<br />
;; package to allow a simpler comparison of two similar directory<br />
;; trees.<br />
<br />
;; I wrote this package because I often need to compare two different<br />
;; versions of the same directory tree and ediff-directories is not<br />
;; very helpful in this case. &nbsp;Specially when the directory trees to<br />
;; compare are deep and only a few files have changed.<br />
;; Typically, that occurs when I create a copy of some project<br />
;; directory tree either to make some experiments myself or to send to<br />
;; someone else that will return a modified directory tree to me<br />
;; later. &nbsp;(Yes, I heard of version control systems, and I use them<br />
;; regularly. &nbsp;Yet, for several reasons, sometimes that is not an<br />
;; option.)<br />
<br />
;; Later, when I want to integrate the modified directory tree with<br />
;; the original tree, I want to see the differences to the original<br />
;; version, so that I may decide whether to accept the changes or not.<br />
;; This is where this package kicks in...<br />
<br />
;; To use it, just call `ediff-trees', which will ask for two<br />
;; directories to compare. &nbsp;Usually, I give the original directory as<br />
;; the first one and the modified directory as the second one.<br />
<br />
;; ediff-trees recursively descends both directories, collecting the<br />
;; pairs of files that are worth &quot;comparing&quot;: either files that<br />
;; changed, or that appear in one of the two directory trees but not<br />
;; in the other. &nbsp;Then, it shows the first &quot;change&quot; using ediff.<br />
<br />
;; In fact, ediff-trees either uses ediff to compare a file with its<br />
;; changed version, or simply opens a file that occurs in only one of<br />
;; the trees.<br />
<br />
;; The user can then navigate backward and forward in the set of<br />
;; changes by using `ediff-trees-examine-next' and<br />
;; `ediff-trees-examine-previous', respectively. &nbsp;These functions move<br />
;; from one change (quiting the current ediff session or killing the<br />
;; current file buffer) to another. &nbsp;Therefore, by repeatedly using<br />
;; these functions we can go through all the changes. &nbsp;I usually use<br />
;; some global bindings for these functions. &nbsp;Something like this:<br />
;;<br />
;; &nbsp; (global-set-key (kbd &quot;s-SPC&quot;) 'ediff-trees-examine-next)<br />
;; &nbsp; (global-set-key (kbd &quot;S-s-SPC&quot;) 'ediff-trees-examine-previous)<br />
;; &nbsp; (global-set-key (kbd &quot;C-s-SPC&quot;) 'ediff-trees-examine-next-regexp)<br />
;; &nbsp; (global-set-key (kbd &quot;C-S-s-SPC&quot;) 'ediff-trees-examine-previous-regexp))<br />
<br />
;; The `ediff-trees-examine-next-regexp' and<br />
;; `ediff-trees-examine-previous-regexp' skip over the list of changes<br />
;; to a file with a filename that matches a given regexp.<br />
<br />
;; This package allows for some customization. &nbsp;Please, see the<br />
;; ediff-trees group under customize.<br />
<br />
;; Finally, to deal with small changes in the white space I often find<br />
;; it useful to configure ediff like this:<br />
;;<br />
;; &nbsp; (setq ediff-diff-options &quot;-w&quot;)<br />
;; &nbsp; (setq-default ediff-ignore-similar-regions t)<br />
<br />
;;; Code:<br />
<br />
<br />
(require 'ediff)<br />
<br />
(defgroup ediff-trees nil<br />
&nbsp; &quot;Extend ediff to allow comparing two trees recursively.&quot;<br />
&nbsp; :tag &quot;Ediff Trees&quot;<br />
&nbsp; :group 'ediff)<br />
<br />
<br />
(defface ediff-trees-deleted-original-face<br />
&nbsp; '((((class color))<br />
&nbsp; &nbsp; &nbsp;(:background &quot;Pink&quot;))<br />
&nbsp; &nbsp; (t (:inverse-video t)))<br />
&nbsp; &quot;Face for highlighting the buffer when it was deleted from the original tree.&quot;<br />
&nbsp; :group 'ediff-trees)<br />
<br />
(defcustom ediff-trees-file-ignore-regexp<br />
&nbsp; &quot;\\`\\(\\.?#.*\\|.*,v\\|.*~\\|CVS\\|_darcs\\)\\'&quot;<br />
&nbsp; &quot;A regexp matching either files or directories to be ignored<br />
when comparing two trees. &nbsp;If a directory matches the regexp,<br />
then its contents is not scanned by `ediff-trees'.&quot;<br />
&nbsp; :type 'regexp<br />
&nbsp; :group 'ediff-trees)<br />
<br />
<br />
(defcustom ediff-trees-sort-order-regexps nil<br />
&nbsp; &quot;*Specifies a list of regexps that determine the order in which<br />
files will be presented during the ediff-trees session. &nbsp;Files<br />
with filenames matching former regexps appear earlier in the<br />
session. &nbsp;If a filename matches more than one regexp, the first<br />
one wins.&quot;<br />
&nbsp; :type '(repeat regexp)<br />
&nbsp; :group 'ediff-trees)<br />
<br />
<br />
<br />
(defun ediff-trees (root1 root2)<br />
&nbsp; &quot;Starts a new ediff session that recursively compares two<br />
trees.&quot;<br />
&nbsp; (interactive<br />
&nbsp; &nbsp;(let ((dir-A (ediff-get-default-directory-name))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;f)<br />
&nbsp; &nbsp; &nbsp;(list (setq f (ediff-read-file-name &quot;Directory A to compare:&quot; dir-A nil))<br />
&nbsp; &nbsp; &nbsp; &nbsp;(ediff-read-file-name &quot;Directory B to compare:&quot;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(if ediff-use-last-dir<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ediff-last-dir-B<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(ediff-strip-last-dir f))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;nil))))<br />
&nbsp; (ediff-trees-internal root1 root2))<br />
<br />
<br />
;;; Internal variables, used during an ediff-trees session<br />
(defvar ediff-trees-current-file nil)<br />
(defvar ediff-trees-remaining-files (list))<br />
(defvar ediff-trees-examined-files (list))<br />
<br />
<br />
(defun ediff-trees-internal (root1 root2)<br />
&nbsp; (let ((files-changed (ediff-trees-collect-files root1 root2)))<br />
&nbsp; &nbsp; (if (not (null files-changed))<br />
&nbsp; &nbsp; &nbsp; &nbsp; (progn<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (setq ediff-trees-remaining-files files-changed)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (setq ediff-trees-examined-files (list))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (ediff-trees-examine-next 1))<br />
&nbsp; &nbsp; &nbsp; (message &quot;There are no changes between the trees!&quot;))))<br />
<br />
(defun ediff-trees-collect-files (root1 root2)<br />
&nbsp; (ediff-trees-sort-files<br />
&nbsp; &nbsp;(nconc (ediff-trees-collect-changed-files root1 root2)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (mapcar (lambda (el) (cons el nil))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (ediff-trees-collect-new-files root1 root2))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (mapcar (lambda (el) (cons nil el))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (ediff-trees-collect-new-files root2 root1)))))<br />
<br />
<br />
(defun ediff-trees-sort-files (files)<br />
&nbsp; (let ((tagged-files (mapcar (lambda (pair)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (cons (ediff-trees-get-sort-order (or (car pair) (cdr pair)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pair))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; files)))<br />
&nbsp; &nbsp; (mapcar #'cdr<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (sort tagged-files<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (lambda (tf1 tf2)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (let ((order1 (car tf1))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (order2 (car tf2)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (or (&lt; order1 order2)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (and (= order1 order2)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(let ((el1 (or (cadr tf1) (cddr tf1)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(el2 (or (cadr tf2) (cddr tf2))))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(string&lt; el1 el2))))))))))<br />
<br />
<br />
(defun ediff-trees-get-sort-order (pathname)<br />
&nbsp; (let ((order 0)<br />
&nbsp; &nbsp; &nbsp; &nbsp; (sorting-regexps ediff-trees-sort-order-regexps))<br />
&nbsp; &nbsp; (while (and (not (null sorting-regexps))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (not (string-match (pop sorting-regexps) pathname)))<br />
&nbsp; &nbsp; &nbsp; (setq order (+ order 1)))<br />
&nbsp; &nbsp; order))<br />
<br />
<br />
<br />
(defun ediff-trees-collect-changed-files (root1 root2)<br />
&nbsp; (let ((changed (list)))<br />
&nbsp; &nbsp; (dolist (filename (directory-files root1))<br />
&nbsp; &nbsp; &nbsp; (unless (ediff-trees-skip-file-p filename)<br />
&nbsp; &nbsp; &nbsp; &nbsp; (let ((file1 (expand-file-name filename root1))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (file2 (expand-file-name filename root2)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (when (and (file-exists-p file1) (file-exists-p file2))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (if (eql (file-directory-p file1)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(file-directory-p file2))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (cond ((file-directory-p file1)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(setq changed (nconc changed (ediff-trees-collect-changed-files file1 file2))))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((not (ediff-same-file-contents file1 file2))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(push (cons file1 file2) changed)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (let ((msg (format &quot;I cannot compare a directory, '%s', with a file. &nbsp;Continue? &quot;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(if (file-directory-p file1) file1 file2))))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (if (not (y-or-n-p msg))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (error &quot;Aborting ediff-trees&quot;))))))))<br />
&nbsp; &nbsp; changed))<br />
<br />
<br />
(defun ediff-trees-collect-new-files (root1 root2)<br />
&nbsp; &quot;Collect files from root1 that do not appear at root2.&quot;<br />
&nbsp; (let ((new-files (list)))<br />
&nbsp; &nbsp; (dolist (filename (directory-files root1))<br />
&nbsp; &nbsp; &nbsp; (unless (ediff-trees-skip-file-p filename)<br />
&nbsp; &nbsp; &nbsp; &nbsp; (let ((file1 (expand-file-name filename root1))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (file2 (and root2 (expand-file-name filename root2))))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (when (file-exists-p file1)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (cond ((file-directory-p file1)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(setq new-files<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(nconc new-files<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (ediff-trees-collect-new-files file1<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(and (stringp file2)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (file-directory-p file2)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; file2)))))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((or (null file2) (not (file-exists-p file2)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(push file1 new-files)))))))<br />
&nbsp; &nbsp; new-files))<br />
<br />
(defun ediff-trees-skip-file-p (filename)<br />
&nbsp; ;; always ignore . and ..<br />
&nbsp; (or (string= filename &quot;.&quot;)<br />
&nbsp; &nbsp; &nbsp; (string= filename &quot;..&quot;)<br />
&nbsp; &nbsp; &nbsp; (string-match ediff-trees-file-ignore-regexp filename)))<br />
<br />
<br />
(defun ediff-trees-examine-next (num)<br />
&nbsp; (interactive &quot;p&quot;)<br />
&nbsp; (if (&lt; num 0)<br />
&nbsp; &nbsp; (ediff-trees-examine-previous (- num))<br />
&nbsp; &nbsp; (ediff-trees-examine-file<br />
&nbsp; &nbsp; &nbsp;(lambda (file) (zerop (setq num (- num 1))))<br />
&nbsp; &nbsp; &nbsp;(lambda (file) (push file ediff-trees-examined-files))<br />
&nbsp; &nbsp; &nbsp;(lambda () (pop ediff-trees-remaining-files)))))<br />
<br />
<br />
(defun ediff-trees-examine-previous (num)<br />
&nbsp; (interactive &quot;p&quot;)<br />
&nbsp; (if (&lt; num 0)<br />
&nbsp; &nbsp; (ediff-trees-examine-next (- num))<br />
&nbsp; &nbsp; (ediff-trees-examine-file<br />
&nbsp; &nbsp; &nbsp;(lambda (file) (zerop (setq num (- num 1))))<br />
&nbsp; &nbsp; &nbsp;(lambda (file) (push file ediff-trees-remaining-files))<br />
&nbsp; &nbsp; &nbsp;(lambda () (pop ediff-trees-examined-files)))))<br />
<br />
<br />
(defun ediff-trees-examine-next-regexp (regexp)<br />
&nbsp; (interactive &quot;sSearch for (regexp): &quot;)<br />
&nbsp; (ediff-trees-examine-file<br />
&nbsp; &nbsp;(lambda (file) (string-match regexp (or (car file) (cdr file))))<br />
&nbsp; &nbsp;(lambda (file) (push file ediff-trees-examined-files))<br />
&nbsp; &nbsp;(lambda () (pop ediff-trees-remaining-files))))<br />
<br />
<br />
(defun ediff-trees-examine-previous-regexp (regexp)<br />
&nbsp; (interactive &quot;sSearch for (regexp): &quot;)<br />
&nbsp; (ediff-trees-examine-file<br />
&nbsp; &nbsp;(lambda (file) (string-match regexp (or (car file) (cdr file))))<br />
&nbsp; &nbsp;(lambda (file) (push file ediff-trees-remaining-files))<br />
&nbsp; &nbsp;(lambda () (pop ediff-trees-examined-files))))<br />
<br />
<br />
(defun ediff-trees-examine-file (pred save-current-file-fn get-next-file-fn)<br />
&nbsp; (when (eq (current-buffer) ediff-control-buffer)<br />
&nbsp; &nbsp; (ediff-really-quit nil))<br />
&nbsp; (unless (null ediff-trees-current-file)<br />
&nbsp; &nbsp; (funcall save-current-file-fn ediff-trees-current-file)<br />
&nbsp; &nbsp; (when (car ediff-trees-current-file)<br />
&nbsp; &nbsp; &nbsp; (kill-buffer (find-buffer-visiting (car ediff-trees-current-file))))<br />
&nbsp; &nbsp; (when (cdr ediff-trees-current-file)<br />
&nbsp; &nbsp; &nbsp; (kill-buffer (find-buffer-visiting (cdr ediff-trees-current-file))))<br />
&nbsp; &nbsp; (setq ediff-trees-current-file nil))<br />
&nbsp; (let ((next-file (ediff-trees-get-next-file pred save-current-file-fn get-next-file-fn)))<br />
&nbsp; &nbsp; (if (null next-file)<br />
&nbsp; &nbsp; &nbsp; &nbsp; (message &quot;No more files.&quot;)<br />
&nbsp; &nbsp; &nbsp; (progn<br />
&nbsp; &nbsp; &nbsp; &nbsp; (setq ediff-trees-current-file next-file)<br />
&nbsp; &nbsp; &nbsp; &nbsp; (if (and (car next-file) (cdr next-file))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (ediff-files (car next-file) (cdr next-file))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (progn<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (delete-other-windows)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (find-file-read-only (or (car next-file) (cdr next-file)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (when (null (cdr next-file))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (let ((overlay (make-overlay 0 (point-max))))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (overlay-put overlay 'face 'ediff-trees-deleted-original-face)))))))))<br />
<br />
<br />
(defun ediff-trees-get-next-file (pred save-current-file-fn get-next-file-fn)<br />
&nbsp; (let ((return-value 'not-found))<br />
&nbsp; &nbsp; (while (eq return-value 'not-found)<br />
&nbsp; &nbsp; &nbsp; (let ((next-file (funcall get-next-file-fn)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; (cond ((null next-file)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(setq return-value nil))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((funcall pred next-file)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(setq return-value next-file))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (t<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(funcall save-current-file-fn next-file)))))<br />
&nbsp; &nbsp; return-value))<br />
<br />
<br />
(provide 'ediff-trees)</div></div>
<p>This code can be downloaded from <a href="http://www.emacswiki.org/emacs/ediff-trees.el" title="Ediff Trees">the EmacsWiki</a>. We made a change in the two functions ediff-trees-collect-changed-files and ediff-trees-collect-new-files where we added condition (file-exists-p file1) to skip symbolic links that do not refer to an existing file.</p>
<p>Create another file with the following contents, place it in your Emacs load path and load it:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">(assert (and (boundp 'ediff-git-root-before) (stringp ediff-git-root-before)))<br />
(assert (and (boundp 'ediff-git-root-after) &nbsp;(stringp ediff-git-root-after)))<br />
<br />
(require 'ediff-trees)<br />
<br />
(setq-default ediff-ignore-similar-regions t)<br />
(setq-default ediff-split-window-function 'split-window-horizontally)<br />
(setq-default ediff-trees-file-ignore-regexp &quot;^[.]?#\\|~$\\|^[.]git$&quot;)<br />
<br />
(defun next-error-capable-buffer () &quot;Return a 'next-error' capable buffer.&quot;<br />
&nbsp; (ignore-errors (next-error-find-buffer))<br />
)<br />
<br />
(defun kill-all-next-error-capable-buffers () &quot;Kill all 'next-error' capable buffers.&quot;<br />
&nbsp; (interactive)<br />
&nbsp; (let ((buffer (next-error-capable-buffer)))<br />
&nbsp; &nbsp; (when buffer<br />
&nbsp; &nbsp; &nbsp; (message &quot;Killing buffer '%s'&quot; (buffer-name buffer))<br />
&nbsp; &nbsp; &nbsp; (kill-buffer buffer)<br />
&nbsp; &nbsp; &nbsp; (kill-all-next-error-capable-buffers)<br />
&nbsp; &nbsp; )<br />
&nbsp; )<br />
)<br />
<br />
(defun ediff-git-commits (commits) &quot;Start a new ediff session that recursively compares 'before' and 'after'.&quot;<br />
&nbsp; (interactive &quot;sEnter commit(s): &quot;)<br />
&nbsp; (let* ((whitespace-chars &quot; \f\n\r\t&quot;)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(cc-whitespace (concat &quot;[&quot; whitespace-chars &quot;]&quot;))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(re-commit (concat &quot;\\([^&quot; whitespace-chars &quot;]+\\)&quot;))<br />
&nbsp; &nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; (if (string-match (concat &quot;\\`&quot; cc-whitespace &quot;*&quot; re-commit &quot;\\(?:&quot; cc-whitespace &quot;+&quot; re-commit &quot;\\)?&quot; cc-whitespace &quot;*&quot; &quot;\\'&quot;) commits)<br />
&nbsp; &nbsp; &nbsp; (let ((commit-before (match-string 1 commits)) (commit-after (match-string 2 commits)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; (unless commit-after<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (if (y-or-n-p (format &quot;Compare %s with parent? &quot; commit-before))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (setq commit-after commit-before commit-before (concat commit-after &quot;^&quot;))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (let ((commit (read-string &quot;Enter commit for 'after': &quot;)))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (if (string-match (concat &quot;\\`&quot; cc-whitespace &quot;*&quot; re-commit cc-whitespace &quot;*&quot; &quot;\\'&quot;) commit)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (setq commit-after (match-string 1 commit))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (error &quot;Not a valid commit&quot;)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; &nbsp; &nbsp; (let* ((case-fold-search nil) (ok &quot;ok&quot;) (re-command-ok (concat cc-whitespace (regexp-quote ok) &quot;\\'&quot;))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(command-template (concat &quot;unset CDPATH &amp;&amp; cd %s &amp;&amp; git checkout . &amp;&amp; git fetch &amp;&amp; git checkout %s &amp;&amp; echo -n &quot; ok))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(command-before (format command-template ediff-git-root-before commit-before))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(command-after &nbsp;(format command-template ediff-git-root-after commit-after))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (message &quot;Checking out 'before'...&quot;)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (unless (string-match re-command-ok (shell-command-to-string command-before))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (error &quot;Error checking out 'before' (%s)&quot; command-before)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (message &quot;Checking out 'after'...&quot;)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (unless (string-match re-command-ok (shell-command-to-string command-after))<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (error &quot;Error checking out 'after' (%s)&quot; command-after)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; &nbsp; (unless (and (string-equal commits &quot;&quot;) (y-or-n-p &quot;No commit(s) supplied. Keep current checkouts? &quot;))<br />
&nbsp; &nbsp; &nbsp; &nbsp; (error &quot;No valid commit(s) supplied&quot;)<br />
&nbsp; &nbsp; &nbsp; )<br />
&nbsp; &nbsp; )<br />
&nbsp; &nbsp; (kill-all-next-error-capable-buffers)<br />
&nbsp; &nbsp; (message &quot;Comparing...&quot;)<br />
&nbsp; &nbsp; (ediff-trees ediff-git-root-before ediff-git-root-after)<br />
&nbsp; )<br />
)<br />
<br />
(defun visit-next (arg) &quot;In an ediff session, visit next file, else visit next 'next-error' message.&quot;<br />
&nbsp; (interactive &quot;p&quot;)<br />
&nbsp; (if (or (next-error-capable-buffer) (null ediff-trees-current-file))<br />
&nbsp; &nbsp; (next-error arg)<br />
&nbsp; &nbsp; (ediff-trees-examine-next arg)<br />
&nbsp; )<br />
)<br />
<br />
(defun visit-previous (arg) &quot;In an ediff session, visit previous file, else visit previous 'next-error' message.&quot;<br />
&nbsp; (interactive &quot;p&quot;)<br />
&nbsp; (if (or (next-error-capable-buffer) (null ediff-trees-current-file))<br />
&nbsp; &nbsp; (previous-error arg)<br />
&nbsp; &nbsp; (ediff-trees-examine-previous arg)<br />
&nbsp; )<br />
)</div></div>
<p>Make sure you have constants ediff-git-root-before and ediff-git-root-after defined in your config.<br />
Example:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">(defconst ediff-git-root-before &quot;/home/eduard/before&quot;)<br />
(defconst ediff-git-root-after &quot;/home/eduard/after&quot;)</div></div>
<h2>Usage</h2>
<p>To start a new ediff session, execute ediff-git-commits (M-x ediff-git-commits) and enter zero, one or two commits. When comparing is done, execute visit-next(or bind it to a key you like), to go to the next differing files.</p>
<p>Harro &#038; Eduard</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2011/12/09/setting-up-emacs-to-compare-two-git-tags/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Quest for the perfect Erlang development environment</title>
		<link>http://blog.tty.nl/2011/10/20/quest-for-the-perfect-erlang-development-environment/</link>
		<comments>http://blog.tty.nl/2011/10/20/quest-for-the-perfect-erlang-development-environment/#comments</comments>
		<pubDate>Thu, 20 Oct 2011 10:21:35 +0000</pubDate>
		<dc:creator>Ward Bekker</dc:creator>
				<category><![CDATA[Erlang]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=509</guid>
		<description><![CDATA[Erlang is great, and there is a lot of dev tooling available. Unfortunately these best practices are not easy to find for Erlang newbies like me. So I&#8217;ll start writing them down here and grow the list as I&#8217;m moving &#8230; <a href="http://blog.tty.nl/2011/10/20/quest-for-the-perfect-erlang-development-environment/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Erlang is great, and there is a lot of dev tooling available. Unfortunately these best practices are not easy to find for Erlang newbies like me. So I&#8217;ll start writing them down here and grow the list as I&#8217;m moving up the <a href="http://en.wikipedia.org/wiki/Dreyfus_model_of_skill_acquisition">Dreyfus model</a></p>
<p><strong>Get command history for `erl`, the Erlang shell</strong></p>
<ol>
<li>Install <a href="http://freshmeat.net/projects/rlwrap/">rlwrap</a>. On my Mac using <a href="http://mxcl.github.com/homebrew/">Homebrew</a>:
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">brew install rlwrap</div></div>
</li>
<li>Add the alias
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">alias erl='rlwrap -a dummy erl'</div></div>
<p>in your Bash profile. On my Mac it&#8217;s located here:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">~/.profile</div></div>
<p>. Reload your profile like this:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">bash$ source ~/.profile</div></div>
</li>
</ol>
<p><strong>Automatic reloading of re-compiled modules</strong></p>
<ol>
<li>Grab <a href="https://github.com/mochi/mochiweb">Mochiweb&#8217;s</a>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">reload.erl</div></div>
<p>from <a href="https://github.com/mochi/mochiweb/blob/master/src/reloader.erl">here</a>, compile it and put the beam file here:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">~/bin/reloader.beam</div></div>
</li>
<li>Create or edit
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">~/.erlang</div></div>
<p>and add the line</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">code:load_abs(&quot;[YOUR_HOME_DIR_PLZ_REPLACE]/bin/reloader&quot;)</div></div>
<p>.</li>
<li>From now on, when you have a module loaded in the Erlang shell and re-compile it outside your shell, the new version will be reloaded automatically</li>
</ol>
<p><strong>Some nice utility functions for your Erlang shell</strong></p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">** user extended commands **<br />
dbgtc(File) &nbsp; -- use dbg:trace_client() to read data from File<br />
dbgon(M) &nbsp; &nbsp; &nbsp;-- enable dbg tracer on all funs in module M<br />
dbgon(M,Fun) &nbsp;-- enable dbg tracer for module M and function F<br />
dbgon(M,File) -- enable dbg tracer for module M and log to File<br />
dbgadd(M) &nbsp; &nbsp; -- enable call tracer for module M<br />
dbgadd(M,F) &nbsp; -- enable call tracer for function M:F<br />
dbgdel(M) &nbsp; &nbsp; -- disable call tracer for module M<br />
dbgdel(M,F) &nbsp; -- disable call tracer for function M:F<br />
dbgoff() &nbsp; &nbsp; &nbsp;-- disable dbg tracer (calls dbg:stop/0)<br />
l() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -- load all changed modules<br />
la() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-- load all modules<br />
mm() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-- list modified modules</div></div>
<p>These commands are added by:</p>
<ol>
<li>Compiling <a href="https://gist.github.com/1300812">user_default.erl</a> and move the beam file to
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">~/bin/user_default.beam</div></div>
</li>
<li>Create or edit
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">~/.erlang</div></div>
<p>and add the line</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">code:load_abs(&quot;[YOUR_HOME_DIR_PLZ_REPLACE]/bin/user_default&quot;)</div></div>
<p>.</li>
<li>Feel free to add your own shortcuts to your
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">user_default.erl</div></div>
<p>.</li>
</ol>
<p>There are multiple versions of</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">user_default.erl</div></div>
<p>floating around on the interwebs. <a href="http://www.google.com/codesearch#search/&#038;q=lang:%5Eerlang$%20file:user_default.erl&#038;type=cs">So pick the one that feels right</a>.</p>
<p><strong>Practical Erlang testing techniques</strong></p>
<p>Watch the <a href="http://etrepum.github.com/erl_testing_2011/"> Practical Erlang testing techniques presentation</a> from <a href="http://twitter.com/#!/etrepum">Mr. Bob Ippolito</a> for a quick rundown of useful testing libs. </p>
<p>Thanks to <a href="http://twitter.com/#!/andrzejsliwa" title="@andrzejsliwa">@andrzejsliwa</a> for the tips! Please add your tips to the comments and I&#8217;ll update the post.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2011/10/20/quest-for-the-perfect-erlang-development-environment/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Disabling resuming of apps in OSX Lion</title>
		<link>http://blog.tty.nl/2011/10/20/disabling-resuming-of-apps-in-osx-lion/</link>
		<comments>http://blog.tty.nl/2011/10/20/disabling-resuming-of-apps-in-osx-lion/#comments</comments>
		<pubDate>Thu, 20 Oct 2011 07:05:13 +0000</pubDate>
		<dc:creator>Ward Bekker</dc:creator>
				<category><![CDATA[Mac]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=506</guid>
		<description><![CDATA[The new application resume feature of OSX Lion annoys me big time. To disable it, you need to do the following steps: 1) Disable the `Restore windows when quitting and re-opening apps` checkbox in the General Preference window. 2) Clean &#8230; <a href="http://blog.tty.nl/2011/10/20/disabling-resuming-of-apps-in-osx-lion/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>The new application resume feature of OSX Lion annoys me big time. To disable it, you need to do the following steps:</p>
<p>1) Disable the `Restore windows when quitting and re-opening apps` checkbox in the General Preference window.</p>
<div class="thumbnail"><a href="https://skitch.com/wardbekker/gnrua/general"><img src="https://img.skitch.com/20111020-xgumks38ei4i843jpchj73hu5e.preview.jpg" alt="General" /></a></div>
<p>2) Clean out and write-protect the Resume database for the command line:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">rm -rf ~/Library/Saved\ Application\ State/*<br />
chmod -w ~/Library/Saved\ Application\ State/</div></div>
<p>Thanks to <a href="http://twitter.com/#!/andrzejsliwa" title="@andrzejsliwa">@andrzejsliwa</a> for the tip!</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2011/10/20/disabling-resuming-of-apps-in-osx-lion/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A Basic Full Text Search Server in Erlang</title>
		<link>http://blog.tty.nl/2011/10/07/a-basic-full-text-search-server-in-erlang/</link>
		<comments>http://blog.tty.nl/2011/10/07/a-basic-full-text-search-server-in-erlang/#comments</comments>
		<pubDate>Fri, 07 Oct 2011 05:44:25 +0000</pubDate>
		<dc:creator>Ward Bekker</dc:creator>
				<category><![CDATA[Erlang]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=467</guid>
		<description><![CDATA[This post explains how to build a basic full text search server in Erlang. The server has the following features: indexing stemming ranking faceting asynchronous search results web frontend using websockets Familiarity with the OTP design principles is recommended. The &#8230; <a href="http://blog.tty.nl/2011/10/07/a-basic-full-text-search-server-in-erlang/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<code><p>This post explains how to build a basic full text search server in Erlang. The server has the following features:</p>

<ul>
<li>indexing</li>
<li>stemming</li>
<li>ranking</li>
<li>faceting</li>
<li>asynchronous search results</li>
<li>web frontend using websockets</li>
</ul>

<p>Familiarity with the <a href="http://www.erlang.org/doc/design_principles/des_princ.html">OTP design principles</a> is recommended.</p>

<p>The sample application (build with help from my colleague Michel Rijnders <code>&lt;mies@tty.nl&gt;</code>) uses the <a href="http://blog.stackoverflow.com/category/cc-wiki-dump/">Creative Commons Data Dump from StackExchange</a> as demo data.</p>

<p>We cover the following subjects:</p>

<ul>
<li><a href="#sample_application">running the sample application</a></li>
<li><a href="#otp_tree">OTP supervision tree</a></li>
<li><a href="#data_import">importing demo data</a></li>
<li><a href="#indexing">indexing</a></li>
<li><a href="#stemming">stemming</a></li>
<li><a href="#faceting">faceting</a></li>
<li><a href="#query_and_ranking">querying and relevance ranking</a></li>
<li><a href="#display_search">displaying search results</a></li>
<li><a href="#improvements">improvements</a></li>
</ul>

<p><a name="sample_application" ></a></p>

<h2>Running the Sample Application</h2>

<p>Clone the source from GitHub:</p>

<pre><code> git clone git://github.com/tty/async_search.git
</code></pre>

<p>And start the application:</p>

<pre><code>$ rebar get-deps compile &amp;&amp; erl -pa `pwd`/ebin `pwd`/deps/*/ebin +P 134217727
Eshell&gt; application:start(async).
Eshell&gt; stackoverflow_importer_ser:import().
</code></pre>

<p>Visit <a href="http://localhost:3000">http://localhost:3000</a>, you should see the following page:</p>

<p><img src="https://img.skitch.com/20110909-f3i2aiuby9ht1sjh1j5yt42f7x.jpg" alt="http://localhost:3000/" /></p>

<p>Sample ranked search output for the query <code>erlang armstrong</code>:</p>

<div class="thumbnail"><a href="https://skitch.com/wardbekker/fas4h/http-localhost-3000"><img src="https://img.skitch.com/20110909-efekgnjk3hwuuhpea5dq5gi48m.preview.jpg" alt="http://localhost:3000/" /></a></div>

<p>Sample tags facets output for the query <code>java</code>:</p>

<div class="thumbnail"><a href="https://skitch.com/wardbekker/fas51/http-localhost-3000"><img src="https://img.skitch.com/20110909-ed4e8kcaenbn4bkh6ddt342ig2.preview.jpg" alt="http://localhost:3000/" /></a></div>

<p><a name="otp_tree" ></a></p>

<h2>OTP Supervision Tree</h2>

<div class="thumbnail"><a href="https://skitch.com/wardbekker/f2rki/supervisor-tree"><img style="max-width:638px" src="https://img.skitch.com/20110911-kcd3i2gexishp7e92m3mcxpurn.medium.jpg" alt="supervisor tree" /></a></div>

<p>Looking at the OTP application supervision tree is a good way to understand the architecture of an OTP application.</p>

<p>The application supervisor <code>async_sup</code> starts up the following supervisors:</p>

<ul>
<li><code>keyword_sup</code>. A <code>keyword_ser</code> process is created for every unique word in the StackExchange posts. This <code>keyword_ser</code> is linked to the <code>keyword_sup</code> supervisor (a <code>simple_one_for_one</code> supervisor). The <code>keyword_ser</code> child process maintains a list of document positions of a keyword  (an <a href="http://en.wikipedia.org/wiki/Inverted_index">inverted index</a>).</li>
<li><code>facet_sup</code>. A <code>keyword_ser</code> process is also created for every unique facet category in the StackExchange posts. This <code>keyword_ser</code> process is linked to the <code>facet_sup</code> supervisor (a <code>simple_one_for_one</code> supervisor as well). The <code>keyword_ser</code> child process maintains a list of facet values with the IDs of the documents the facets appear in.</li>
</ul>

<p>The application supervisor also start the following <code>gen_server</code> singleton processes:</p>

<ul>
<li><code>stackoverflow_importer_ser</code>. This server imports the demo Stack Overflow data.</li>
<li><code>document_ser</code>. This server holds a copy of all documents, so it can return the original title and body of matching Stack Overflow posts in the results.</li>
<li><code>query_ser</code>. This server's task is to run the actual query and return results.</li>
<li><code>websocket_ser</code>. This server provides a HTTP frontend for the search engine.</li>
</ul>

<p>No attention is given to fault tolerance (apart from the basic restart strategies), thus parts of the search index are lost if a <code>keyword_ser</code> process terminates.</p>

<p><a name="data_import" ></a></p>

<h2>Demo Data Import</h2>

<p>The StackExchange data is provided as XML. Since some of the documents are quite large, it's not recommended to load the full XML documents in memory. The solution is to use a <a href="http://nl.wikipedia.org/wiki/Simple_API_for_XML">SAX parser</a> which treats a XML file as a stream, and triggers events when new elements are discovered. The search server uses the excellent SAX parser from the <a href="http://erlsom.sourceforge.net">Erlsom</a> library by Willem de Jong.</p>

<p>In the example below <code>erlsom:parse_sax</code> reads the XML file from <code>FilePath</code> and calls the function <code>sax_event</code> if an XML element is found.</p>

<script src="https://gist.github.com/1203600.js?file=stackoverflow_importer_ser.erl"></script>

<p>When the element is a <code>row</code> element (i.e. a post element), attributes like <code>Id</code>, <code>Title</code> and <code>Body</code> are stored in a dictionary. For every post a copy of all the attributes in <code>document_ser</code> is saved. This is used for returning the actual posts for a query match.  After that the <code>add_attribute_tokens</code> function is called:</p>

<script src="https://gist.github.com/1203611.js?file=stackoverflow_import_ser.erl"></script>

<p>The <code>add_attribute_tokens</code> function does two things. It calls <code>add_facet</code> (discussed later) and it creates a list of tuples with all the words and their position in the document. This process is called <a href="http://en.wikipedia.org/wiki/Tokenization">tokenization</a>. Each token/position tuple is then submitted to the <code>add_keyword_position</code> function of the <code>keyword_ser</code> for indexing.</p>

<script src="https://gist.github.com/1205618.js?file=stackoverflow_import_ser.erl"></script>

<p><a name="indexing"></a></p>

<h2>Indexing</h2>

<p>Indexing of the tuples, or keywords, is handled by the <code>keyword_ser</code>. For every unique word a <code>keyword_ser</code> process is started if not already present. The state of a <code>keyword_ser</code> process is a dictionary with the document ID as key and a list of positions as value.  The document ID corresponds to the ID of the Stack Overflow post.</p>

<script src="https://gist.github.com/1205643.js?file=keyword_ser.erl"></script>

<p>The <code>keyword_server_name</code> function generates a unique name under which the <code>keyword_ser</code> process is registered, so the module can check if a keyword already has a process or a new process needs to be created.</p>

<script src="https://gist.github.com/1205645.js?file=keyword_ser.erl"></script>

<p><a name="stemming" ></a></p>

<h2>Stemming</h2>

<p><a href="http://en.wikipedia.org/wiki/Stemming">Stemming</a> is the process for reducing inflected words to their base form. <code>Computing</code> and <code>computer</code> both are stemmed to <code>comput</code>. So when a user searches on <code>computing</code>, it also matches text that contains <code>computer</code>.  This makes it possible to return results that are relevant, but do not exactly match the query.</p>

<p>In our sample application all keywords are stemmed using the popular <a href="http://tartarus.org/~martin/PorterStemmer/">Porter Algorithm</a>. The <a href="http://tartarus.org/~martin/PorterStemmer/porter.erl">Erlang implementation</a> by Alden Dima is used in the application.</p>

<script src="https://gist.github.com/1205681.js?file=keyword_ser.erl"></script>

<p><code>erlang:phash2</code> is used to transform the stemmed name to a hash, to make sure the registered process name is valid.</p>

<p><a name="faceting" ></p>

<h2>Faceting</h2>

<p><a href="http://en.wikipedia.org/wiki/Faceted_search">Faceted search</a> is an important navigation feature for search engines. A user can drill down the search results by filtering on pre-defined attributes, like in this example of a digital camera search on CNET:</p>

<p><img src="http://weblogs.asp.net/blogs/drnetjes/CNET_faceted_search.jpg" alt="Faceted search example" title="" /></p>

<p>As mentioned above, the data import the function <code>add_attribute_tokens</code> also calls the <code>add_facet</code> function. Using pattern matching the <code>Tags</code> and the <code>Creationdate</code> attributes are selected for faceting. <code>Tags</code> is a so called multivalue facet, as a Stack Overflow post can have one or more tags assigned. For every tag and creation date the <code>facet_ser:add_facet_value</code> function is called.</p>

<script src="https://gist.github.com/1205699.js?file=stackoverflow_importer_ser.erl"></script>

<p><code>facet_ser</code> works very similar to <code>keyword_ser</code>. For every facet category, <code>Tag</code> or <code>Creationdate</code> in our case, a <code>facet_ser</code> processes is started. The state of a <code>facet_ser</code> is a dictionary with the <code>Tag</code> or <code>Creationdate</code> values as key and their document IDs as dictionary values.</p>

<p><a name="query_and_ranking" ></a></p>

<h2>Querying and Relevance Ranking</h2>

<p>In previous sections is shown:</p>

<ul>
<li>how the XML demo data is parsed.</li>
<li>how this data is stemmed and indexed by creating a <code>keyword_ser</code> process for every unique keyword.</li>
<li>how this data is indexed for faceted search by creating a <code>facet_ser</code> process for every facet category.</li>
</ul>

<p>With the function <code>stackoverflow_importer_ser:import()</code> these steps are executed, and your Erlang node is now ready for querying. So how does that work?</p>

<h3>Querying</h3>

<p>Querying is handled by passing the user's query terms to the function <code>do_async_query</code> of the singleton <code>query_ser</code> server.  When calling this function you need to specify the module, function and optional reference attribute which will be called when query results are available.</p>

<script src="https://gist.github.com/1205827.js?file=query_ser.erl"></script>

<p>In the <code>handle_cast</code> the following steps are executed:</p>

<ul>
<li><code>keyword_ser:do_query</code> return all document ids that contain one or more of the user's query terms, including the relevance ranking score, which will be discussed below.</li>
<li>All original documents are stored during indexing in a <code>document_ser</code> process. All matching documents are collected.</li>
<li>The callback function is invoked with the matching documents and their ranking scores as arguments.</li>
<li>Facet results are retrieved for any <code>FacetCategories</code> that are specified by calling <code>facet_ser:get_facets</code>.</li>
<li>And the callback function is invoked a second time with the facet results as arguments.</li>
</ul>

<script src="https://gist.github.com/1205850.js?file=query_ser.erl"></script>

<h3>Relevance Ranking</h3>

<p><a href="http://en.wikipedia.org/wiki/Relevance_(information_retrieval)">Relevance</a> in this context denotes how well a retrieved document matches the user's search query. Most fulltext search-engines use the <a href="http://en.wikipedia.org/wiki/Okapi_BM25">BM25</a> algorithm to determine the ranking score of each document, so let's use that too.</p>

<p>BM25 calculates a ranking score based on the query term frequency in each documents.</p>

<p>See the <a href="https://github.com/tty/async_search/blob/master/src/async_bm25.erl">async_bm25.erl</a> for the implementation.</p>

<p><a name="display_search" ></a></p>

<h2>Displaying the Search Results</h2>

<p>As discussed, the <code>query_ser:do_async_query</code> can be called to query our full-text search engine. To allow users to send queries and see the result the <code>websocket_ser</code> module is created. This singleton <code>gen_server</code>starts up a <a href="https://github.com/ostinelli/misultin">Misultin HTTP server</a> on Port 3000. If you browse to <a href="http://localhost:3000">http://localhost:3000</a> you will see a search box. Communication with the search engine is done through websockets.</p>

<p>So, when a user posts a query, this message is received by the <code>websockets_ser:handle_websocket</code> receive block.  The <code>query_ser:do_async_query</code> function is called and query results are expected on <code>websockets_ser:query_results</code> function.</p>

<script src="https://gist.github.com/1206002.js?file=websocket_ser.erl"></script>

<p>The <code>query_results</code> function formats the results as HTML and sends this through the websocket. When received, the HTML is appended to the user's page.</p>

<script src="https://gist.github.com/1206014.js?file=websocket_ser.erl"></script>

<p>A similar process is executed when the facet results are received:</p>

<script src="https://gist.github.com/1206022.js?file=websocket_ser.erl"></script>

<p><a name="improvements"></a></p>

<h2>Improvements</h2>

<p>Some obvious features that are lacking from this sample application:</p>

<ul>
<li>The author of this post is an Erlang newbie. Corrections/suggestions to the code are most welcome. You can send them to <code>&lt;ward@tty.nl&gt;</code></li>
<li>Pretty much no attention is given to performance / memory usage.</li>
<li>Fault tolerence for the index data. When a server containing index state dies, it will not be revived.</li>
<li>Tuple structures passed between modules are not specified. Would be nice to use record syntax for it.</li>
<li>No unit/quickcheck/common test added.</li>
<li>No function/type specifications.</li>
<li>etc..</li>
</ul>

<p>So, that why it's called a <em>sample</em> application ;-)</p>

<script type="text/javascript">var _gaq = _gaq || [];_gaq.push(['_setAccount', 'UA-26164383-1']);_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></code>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2011/10/07/a-basic-full-text-search-server-in-erlang/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Erlang Factory Lite Amsterdam Talks Announced</title>
		<link>http://blog.tty.nl/2011/10/03/erlang-factory-lite-amsterdam-speakers-announced/</link>
		<comments>http://blog.tty.nl/2011/10/03/erlang-factory-lite-amsterdam-speakers-announced/#comments</comments>
		<pubDate>Mon, 03 Oct 2011 12:27:06 +0000</pubDate>
		<dc:creator>Ward Bekker</dc:creator>
				<category><![CDATA[Erlang]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=481</guid>
		<description><![CDATA[See http://www.erlang-factory.com/conference/amsterdam for more details and free registration Travis CI – Distributed, Continuous Integration for the open source community. By Ward Bekker / TTY Internet Solutions &#8211; Travis CI is a new continuous integration service for the open source community. &#8230; <a href="http://blog.tty.nl/2011/10/03/erlang-factory-lite-amsterdam-speakers-announced/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>See <a href="http://www.erlang-factory.com/conference/amsterdam">http://www.erlang-factory.com/conference/amsterdam</a> for more details and free registration</p>
<p><strong>Travis CI – Distributed, Continuous Integration for the open source community.</strong></p>
<p><em>By Ward Bekker / TTY Internet Solutions</em> &#8211; Travis CI is a new continuous integration service for the open source community. It started out with a Ruby focus and became an instant success. Recently Erlang support was added. A few well known projects, like eTorrent, Mochiweb, Meck and Elixir, already started using it. In this presentation you will learn how the system works, the vision behind it, the upcoming features the team is working on and how to add your own Erlang projects.</p>
<p><strong>The Erlang trace facility</strong></p>
<p><em>By Jeroen Koops</em> – In this talk, I’ll show how to use Erlang’s low-level trace facility, and the higher-level dbg module that is built on top of it. Finally, I’ll demonstrate how to build a simple tool using the primitives provided by the trace-facility.</p>
<p><strong>Let’s jabber about ejabberd</strong></p>
<p><em>By Ahmed Omar / Nimbuzz</em> &#8211; Just a quick jabber about ejabberd</p>
<p><strong>Zotonic, the Erlang web framework, at MaxClass<br />
</strong><br />
<em>By Marc Worrell / MaxClass</em> &#8211; Zotonic is both an easy to use content management system and a powerful web framework. It’s built on some of the best pieces of Erlang open source software, by experienced web developers. Zotonic comes with an incredible speed out-of-the-box, an extensible infrastructure and most of all, a friendly community. In the first part of this talk, we summarize the history and development of Zotonic, and give a short introduction to the data model and the architecture.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2011/10/03/erlang-factory-lite-amsterdam-speakers-announced/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Travis now available in the Erlang flavor</title>
		<link>http://blog.tty.nl/2011/08/19/travis-now-available-in-the-erlang-flavour/</link>
		<comments>http://blog.tty.nl/2011/08/19/travis-now-available-in-the-erlang-flavour/#comments</comments>
		<pubDate>Fri, 19 Aug 2011 07:49:48 +0000</pubDate>
		<dc:creator>Ward Bekker</dc:creator>
				<category><![CDATA[Erlang]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=458</guid>
		<description><![CDATA[Travis, the very popular and open distributed build system for the Ruby community, has diversified. It now also features first class Erlang support. It came together with help from former colleague Josh Kaldermis and the other Travis devs. Thx guys! &#8230; <a href="http://blog.tty.nl/2011/08/19/travis-now-available-in-the-erlang-flavour/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a href="http://travis-ci.org/">Travis</a>, the very popular and open distributed build system for the Ruby community, has diversified. It now also features first class Erlang support. It came together with help from former colleague <a href="http://twitter.com/#!/joshkalderimis">Josh Kaldermis</a> and the other Travis devs. Thx guys! Also many thanks to <a href="http://www.tty.nl/">TTY Internet Solutions</a> for providing a server for hosting the <a href="http://about.travis-ci.org/docs/dev/worker/">workers</a>.</p>
<p>Currently we provide Erlang/OTP releases R14B01, R14B02 and R14B03. Older versions will be added in the near future. Builds are managed with the excellent <a href="https://github.com/basho/rebar">Rebar</a> tool from the <a href="http://www.basho.com/">Basho</a> folks.</p>
<h2>Projects</h2>
<p>A selection of projects that were added to <a href="http://travis-ci.org/">Travis</a> at the time of writing:</p>
<ul>
<li><a href="http://travis-ci.org/#!/jlouis/etorrent">jlouis/eTorrent</a></li>
<li><a href="http://travis-ci.org/#!/mochi/mochiweb">mochi/Mochiweb</a></li>
<li><a href="http://travis-ci.org/#!/spawngrid/proper_stdlib">spawngrid/Proper_stdlib</a></li>
<li><a href="http://travis-ci.org/#!/seancribbs/neotoma">seancribbs/Neotoma</a></li>
<li><a href="http://travis-ci.org/#!/dreid/gen_bunny">dreid/Gen_Bunny</a></li>
</ul>
<p>So, why not add <a href="http://about.travis-ci.org/">your Erlang project</a> now?</p>
<h2>The near future</h2>
<p>Currently only <a href="http://www.erlang.org/doc/man/eunit.html">eunit</a> tests are run. We are going to add support for:</p>
<ul>
<li><a href="http://www.erlang.org/doc/man/dialyzer.html">Dialyzer</a></li>
<li><a href="http://www.erlang.org/doc/apps/tools/xref_chapter.html">Xref</a></li>
<li><a href="http://www.erlang.org/doc/apps/common_test/index.html">Common_test</a></li>
</ul>
<p>Most of these test can already be run by customizing the <em>script</em> element in the <em>.travis.yml</em>, but we want to make it as convenient as possible.</p>
<p>Oh, and did you add <a href="http://about.travis-ci.org/">your Erlang project</a> already?</p>
<h2>Questions?</h2>
<p>Questions or need help? Join #travis on freenode or <a href="http://twitter.com/#!/wardbekker">contact me on twitter</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2011/08/19/travis-now-available-in-the-erlang-flavour/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Parallel testing: make your CPU cores sweat</title>
		<link>http://blog.tty.nl/2010/10/20/parallel-testing-make-your-cpu-cores-sweat/</link>
		<comments>http://blog.tty.nl/2010/10/20/parallel-testing-make-your-cpu-cores-sweat/#comments</comments>
		<pubDate>Wed, 20 Oct 2010 06:54:14 +0000</pubDate>
		<dc:creator>Ward Bekker</dc:creator>
				<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=449</guid>
		<description><![CDATA[All my fellow team-mates have fast workstations: quad core, 8 gigs of memory. Yay! BUT&#8230;..running our full test suite takes about 45 minutes. Boo! It&#8217;s a mix of Cucumber+webrat integration tests and unit tests. If you look at the cpu &#8230; <a href="http://blog.tty.nl/2010/10/20/parallel-testing-make-your-cpu-cores-sweat/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>All my fellow team-mates have fast workstations: quad core, 8 gigs of memory. Yay! BUT&#8230;..running our full test suite takes about 45 minutes. Boo! It&#8217;s a mix of <a href="http://cukes.info/">Cucumber</a>+<a href="http://github.com/brynary/webrat/wiki">webrat</a> integration tests and unit tests. If you look at the cpu activity it doesn&#8217;t even spike a single core during the test. Memory consumption stays practically flat. That&#8217;s an extremely poor use of all that computing power. No wonder, all test are run sequentially. In our multi-core age that&#8217;s soo 90&#8242;s.</p>
<p>The solution is obvious: you need to parallelize the tests. Every integration test needs a dedicated environment to able to get predictable results. For most integration test this means exclusive access to resources like your database (Mysql), memory caching (Memcached) and/or full text search solutions (Sphinx | Solr). You can design your tests to be collision free, but like most multi-threaded programming that uses shared resource it&#8217;s <a href="http://blogs.msdn.com/b/jmstall/archive/2008/01/30/why-threading-is-hard.aspx">quite difficult to get it right</a>. And debugging weird threading issues will make you want to put pencils in your eyes. Trust me on that.</p>
<p>A more efficient way of creating a dedicated environment for every test is the use of <a href="http://en.wikipedia.org/wiki/Virtual_machine">virtual machines</a> (vm). You replicate your integration test environment on a vm. Make several clones and your now have a pool of vm&#8217;s that can run your tests in parallel and guaranteed exclusivity.</p>
<p>The hard part of this solution;</p>
<ul>
<li>Cucumber and the unit test runner need to be modified to run tests distributed.</li>
<li>Non-<a href="http://en.wikipedia.org/wiki/Hypervisor">hypervisor virtualisation systems</a> like <a href="http://www.virtualbox.org/">Virtualbox</a> and <a href="http://www.vmware.com/products/server/">VMWare Server</a> introduce a significant performance overhead. Hypervisor systems require a dedicated box.</li>
<li>Provisioning of virtual machines can be a chore. Solutions like <a href="http://vagrantup.com/">Vagrant</a> can help with that.</li>
</ul>
<p>But it will be worth it. Your CPU cores are worth it.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2010/10/20/parallel-testing-make-your-cpu-cores-sweat/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Timelaps video of the scrumboard during our latest scrum sprint</title>
		<link>http://blog.tty.nl/2010/10/08/timelaps-video-of-the-scrumboard-during-our-latest-scrum-sprint/</link>
		<comments>http://blog.tty.nl/2010/10/08/timelaps-video-of-the-scrumboard-during-our-latest-scrum-sprint/#comments</comments>
		<pubDate>Fri, 08 Oct 2010 08:05:37 +0000</pubDate>
		<dc:creator>Ward Bekker</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=446</guid>
		<description><![CDATA[We&#8217;ve recorded a timelaps video of the progress on our scrumboard and burdown chart. We&#8217;ve been hacking away for thirteen 9-day sprints with (now) three teams on the San Diego project of VNU Media (link in dutch).]]></description>
			<content:encoded><![CDATA[<p>We&#8217;ve recorded a timelaps video of the progress on our scrumboard and burdown chart. We&#8217;ve been hacking away for thirteen 9-day sprints with (now) three teams on the<a href="http://recruitmentmatters.nl/2010/08/09/vnus-san-diego-een-sneak-preview/"> San Diego project of VNU Media</a> (link in dutch).</p>
<p><object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="480" height="385" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="src" value="http://www.youtube.com/v/0ZjdrYLKOdM?fs=1&amp;hl=en_US" /><param name="allowfullscreen" value="true" /><embed type="application/x-shockwave-flash" width="480" height="385" src="http://www.youtube.com/v/0ZjdrYLKOdM?fs=1&amp;hl=en_US" allowscriptaccess="always" allowfullscreen="true"></embed></object></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2010/10/08/timelaps-video-of-the-scrumboard-during-our-latest-scrum-sprint/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Using VisualVM to fix live Tomcat and JVM problems</title>
		<link>http://blog.tty.nl/2010/09/03/using-visualvm-to-fix-live-tomcat-and-jvm-problems/</link>
		<comments>http://blog.tty.nl/2010/09/03/using-visualvm-to-fix-live-tomcat-and-jvm-problems/#comments</comments>
		<pubDate>Fri, 03 Sep 2010 11:30:56 +0000</pubDate>
		<dc:creator>Almer Thie</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Tomcat]]></category>
		<category><![CDATA[Tooling]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[VisualVM]]></category>

		<guid isPermaLink="false">http://blog.tty.nl/?p=405</guid>
		<description><![CDATA[You have done all your Java implementation, unittesting and perhaps integration testing. You met all specs and passed the acceptance phase, so you&#8217;re going to deploy your .war file to the live environment and install it on Tomcat. All goes well and &#8230; <a href="http://blog.tty.nl/2010/09/03/using-visualvm-to-fix-live-tomcat-and-jvm-problems/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>You have done all your Java implementation, unittesting and perhaps integration testing. You met all specs and passed the acceptance phase, so you&#8217;re going to deploy your .war file to the live environment and install it on Tomcat. All goes well and you continue on other work to be done&#8230; A few hours later the system administrator calls you and asks you why the quad core processor reached 400% of CPU level (where normally it&#8217;s around 100% spread over 4 cores). You did the best you can with testing but still a problem like this can get through. Now the challenge starts: How to find what causes this problem!</p>
<div id="attachment_415" class="wp-caption aligncenter" style="width: 505px"><img class="size-full wp-image-415" title="cpu" src="http://blog.tty.nl/wp-content/uploads/cpu.png" alt="CPU load is far higher then normal. Note the platforms in the graph!" width="495" height="343" /><p class="wp-caption-text">CPU load is far higher then normal. Note the platforms in the graph!</p></div>
<p><span id="more-405"></span><br />
You could think back on what changes you made since the previous release and pinpoint possible problem areas. Then revert this part of the code, redeploy and see what happens. This approach can be tedious and you can end up quite frustrated when this approach didn&#8217;t fix the problem after several tries. It would be nice if you could pinpoint exactly where things go wrong wouldn&#8217;t it?</p>
<h2>VisualVM to the rescue</h2>
<p>Since Java 5.0 Java has this great feature called JMX (Java Management Extensions). This technology enables getting all sorts of information from running JVM&#8217;s. <a href="https://visualvm.dev.java.net/" target="_blank">VisualVM</a> is a tool that uses JMX and gives you detailed info about JVM memory, CPU usage, Garbage Collection but can also profile your objects for CPU and memory usage (local JVM&#8217;s only, otherwise all your live classes would be instrumented which would take down the performance even more). The nice thing is that you can monitor your remote running JVM&#8217;s too! In our case this is exactly what we need since we can&#8217;t reproduce it on our development or acceptance machines. Users could have hit some location on your webapplication in just the kind of way you never tested and therefor can&#8217;t reproduce.</p>
<h2>Enable JMX via Tomcat</h2>
<p>In order to use JMX you should add some variables to the JAVA_OPTS in your Tomcat startup script:</p>
<blockquote><p>-Dcom.sun.management.jmxremote=true<br />
-Dcom.sun.management.jmxremote.port=9090<br />
-Dcom.sun.management.jmxremote.ssl=false<br />
-Dcom.sun.management.jmxremote.authenticate=false</p></blockquote>
<p>You can choose your own jmxremote port and choose to use SSL and authenticate (You should if you need to open up the JMX port for the entire world via your firewall). When we tried it we first opened up only port 9090 on our firewall. This didn&#8217;t work. There are some other ports that are used between JMX and VisualVM (<em>Disclaimer: We have not tested this any further and we are not sure which other ports are needed</em>). We fixed this problem by opening the firewall entirely for just the IP address of the computer we have VisualVM running.</p>
<h2>Connecting VisualVM to the JMX enabled remote JVM</h2>
<p>Now we are ready to connect to the server:</p>
<div id="attachment_420" class="wp-caption aligncenter" style="width: 497px"><img class="size-full wp-image-420  " title="Connecting your JMX enabled remote JVM to your local VisualVM" src="http://blog.tty.nl/wp-content/uploads/visualvm-adding-remote-jmx.png" alt="Connecting you JMX enabled remote JVM to your local VisualVM" width="487" height="379" /><p class="wp-caption-text">Connecting your JMX enabled remote JVM to your local VisualVM</p></div>
<p>Start VisualVM on your machine and create a remote connection by right clicking &#8216;Remote&#8217; and click  &#8217;Add remote host&#8230;&#8217;. Then on your created remote host do a right click again and click &#8216;Add JMX connection&#8230;&#8217;. Type your remote server&#8217;s domainname or IP followed by your remotely configured port number. Now open this connection. You will see a lot of information about your live JVM environment and some tabs. In this article we will need the &#8216;Threads&#8217; tab!</p>
<h2>Pinpointing the problem</h2>
<p>When you open the Threads tab you should see something like the following example:</p>
<div id="attachment_421" class="wp-caption aligncenter" style="width: 563px"><img class="size-large wp-image-421  " title="Normal expected view of tomcat HTTP connections threads" src="http://blog.tty.nl/wp-content/uploads/visualvm-viewing-tomcat-threads-1024x595.png" alt="Normal expected view of tomcat HTTP connections threads" width="553" height="321" /><p class="wp-caption-text">Normal expected view of tomcat HTTP connections threads</p></div>
<p>As you can see some threads are running at some time, handling some HTTP requests. This should not take too much time (the HTTP responses will take to long and users will experience your webapp as slow) but should certainly not take forever! Guess what&#8230; when we clicked our Threads tab we saw something like the example I created below:</p>
<div id="attachment_419" class="wp-caption aligncenter" style="width: 563px"><img class="size-large wp-image-419  " title="Problematic view; some threads are running&amp;nbsp;continuously" src="http://blog.tty.nl/wp-content/uploads/visualvm-viewing-tomcat-threads-problem-1024x595.png" alt="Problematic view; some threads are continuously running" width="553" height="321" /><p class="wp-caption-text">Problematic view; some threads are running continuously</p></div>
<p>Spot the differences. As you can see there are two threads which are running forever: http-8005-191 and http-8005-188. If you scroll up and down you could very well spot some others. Just to be sure that this is your CPU problem check at what time these threads started being in running state and compare this with your CPU graph. We saw that the CPU platforms started at exactly the same time as a thread became permanently running.</p>
<h2>Fixing the problem</h2>
<p>So what causes these threads to be running forever? The answer lies under the &#8216;Thread Dump&#8217; button you can see at the upper right of this Threads tab. Click it to let VisualVM create a dump of all threads stacks. Now remember the ID&#8217;s of our problematic threads and search for them in the thread dump output. The stack trace you see here should lead you directly to the problem you&#8217;ll have to fix! This will probably be some never ending while loop somehow. Never ending recursive calls would probably have alarmed you sooner with a stack overflow exception. In our case it was code like below that caused our pain:</p>
<div class="codecolorer-container java default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:600px;"><div class="java codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #666666; font-style: italic;">// Simplified the code a lot ;)</span><br />
Node node <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Node<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
node.<span style="color: #006633;">addParent</span><span style="color: #009900;">&#40;</span>node<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<br />
<span style="color: #666666; font-style: italic;">//... a lot of other code...</span><br />
<br />
<span style="color: #000000; font-weight: bold;">while</span><span style="color: #009900;">&#40;</span>node.<span style="color: #006633;">hasParent</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span><br />
node <span style="color: #339933;">=</span> node.<span style="color: #006633;">getParent</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">&#125;</span></div></div>
<p>You can imagine we extended our testcases for this particular part of code in the webapplication. We now could reproduce this problem on our development machines and solve it.</p>
<h2>Fixing your problem?</h2>
<p>It could very well be that your problem has a different origin but hopefully this will help you out to solve your problem more easily in that case. If you have different cases which you solved (partly) with what you read in this article, please let us know with a comment to this article and how you solved your problem. This way you can help out others with problems like these too! Now go enjoy your speedy bugfree webapp ;)</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.tty.nl/2010/09/03/using-visualvm-to-fix-live-tomcat-and-jvm-problems/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

