<?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>Nicollet.Net &#187; Victor Nicollet</title>
	<atom:link href="http://www.nicollet.net/author/arkadir/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.nicollet.net</link>
	<description>Everyone Loves Me</description>
	<lastBuildDate>Mon, 23 Jan 2012 16:55:59 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=</generator>
		<item>
		<title>You&#8217;re not a person</title>
		<link>http://www.nicollet.net/2009/08/youre-not-a-person/</link>
		<comments>http://www.nicollet.net/2009/08/youre-not-a-person/#comments</comments>
		<pubDate>Thu, 13 Aug 2009 23:00:54 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Imperative]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Bugs]]></category>
		<category><![CDATA[Documentation]]></category>
		<category><![CDATA[Psychology]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1120</guid>
		<description><![CDATA[WEEK 1 In this application, every person belongs to exactly one team. WEEK 4 We need to manage external contractors. We could use the &#8220;person&#8221; object. WEEK 5 Hey, we need to assign a team to every person. Let&#8217;s create an &#8220;external&#8221; team. WEEK 127 Did you see that newspaper article about our company? They [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>WEEK 1</p>
<p style="padding-left: 30px;"><em>In this application, every person belongs to exactly one team.</em></p>
<p>WEEK 4</p>
<p style="padding-left: 30px;"><em>We need to manage external contractors. We could use the &#8220;person&#8221; object.</em></p>
<p>WEEK 5</p>
<p style="padding-left: 30px;"><em>Hey, we need to assign a team to every person. Let&#8217;s create an &#8220;external&#8221; team.</em></p>
<p>WEEK 127</p>
<p style="padding-left: 30px;"><em>Did you see that newspaper article about our company? They say we have an average of 30 people on every team. Do we even have 30-people teams?</em></p>
</blockquote>
<p>Names are short. They can only convey a very limited amount of information. Even worse, that information tends to be different from its meaning in standard English: by declaring in week one that every person belongs to a team, the project designers separated the Application::Person (always in a team) from the English::Person (might be in zero, one or more teams). By week four, this separation vanished from the minds of most of the team. A developer noticed that &#8220;<em>English::Contractor is-a English::Person</em>&#8221; and mistakenly translated it to &#8220;<em>Application::Contractor is-a Application::Person</em>&#8220;.</p>
<p>This was the first mistake. Why didn&#8217;t he notice?</p>
<p>A positive property is what you <em>can</em> do with a thing.With the Person object, you can store a name, login, password and phone number!This is exactly we you needed! Those positive properties you need that the object doesn&#8217;t provide, you can always add them through inheritance or composition, and that&#8217;s still less work than implementing everything or having to refactor the code. A negative property is what you <em>cannot</em> do with an object no matter how hard you try. With the Person object, you cannot remain on your own without a team! But our brains are biased to look for positive properties first, and passively ignore negative properties until it&#8217;s too late. Positive properties are about the solution solving the problem. Negative properties are about the solution not being applicable.</p>
<p>The second mistake was, by far, the worst. So they finally noticed that negative property that blasted all their model away. And they went on with it, patching the issue by altering the meaning of Application::Team. It originally a project team within the company, it then represented a named group of people that could be a project team or the group of external contractors. This <em>is</em> refactoring: no matter how you look at it, you change the behavior of an object and let it propagate throughout the project, so you better be careful about where it propagates! In this case, they weren&#8217;t careful about propagating the change of meaning to the documentation and user interaction part of the project, who mistakenly kept the old meaning of Application::Team. This led to a naive PR team issuing a statement that included the &#8220;external&#8221; group as if it were a project team.</p>
<p>It&#8217;s always helpful to have an anal-retentive person in a group, preferably in a position of authority that lets them veto such changes, and who is vigilant enough to spot that &#8220;external&#8221; team early on in the design.</p>
<p>The real mistake was allowing a negative property to slip into the design. Negative properties hinder reuse, <em>by definition</em>. Sure, allowing a person to belong to zero-one-many teams is hard on every piece of code that must work on teams, because the writers have to remember to check whether the person has a team in the first place. But it has to be done. Doing it may even bring to light some issues in the original requirements (&#8220;So what happens when a person changes teams between the moment team bonuses are computed and the moment they are paid out?&#8221;) that would become annoying later on.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/08/youre-not-a-person/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Find the bug!</title>
		<link>http://www.nicollet.net/2009/08/find-the-bug/</link>
		<comments>http://www.nicollet.net/2009/08/find-the-bug/#comments</comments>
		<pubDate>Thu, 13 Aug 2009 06:49:21 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Dynamic]]></category>
		<category><![CDATA[Bugs]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Unicode]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1115</guid>
		<description><![CDATA[This code summarizes some text by removing anything past 255 characters. It&#8217;s done before inserting things into a database, so that the brutal cut of a VARCHAR(255) doesn&#8217;t leave a truncated string but rather three nice dots. function summarize($text) { assert (is_string($text)); if (strlen($text) &#60; 255) return $text; return substr($text,0,253).'...'; } Yet, however simple it [...]]]></description>
			<content:encoded><![CDATA[<p>This code summarizes some text by removing anything past 255 characters. It&#8217;s done before inserting things into a database, so that the brutal cut of a VARCHAR(255) doesn&#8217;t leave a truncated string but rather three nice dots.</p>
<pre style="padding-left: 30px;">function summarize($text)
{
  assert (is_string($text));
  if (strlen($text) &lt; 255) return $text;
  return substr($text,0,253).'...';
}</pre>
<p>Yet, however simple it might be, this function contains a bug. One day, your database will return a weird error when an user tries to save the data. If you&#8217;re lucky, it will happen to a lone user who will complain and move on. If you&#8217;re unlucky, it will happen during a million-line import and crash everything, or it will display strange things on the screenof every user around.</p>
<p>Can you find why?</p>
<p><span id="more-1115"></span>The basic assumption made here is that substr is a sane text manipulation function that reads in a valid piece of text and returns a <strong>valid</strong>, albeit shorter, piece of text. This assumption is, of course, incorrect.</p>
<p>Recently, the internet has migrated towards Unicode representation of text in order to take into account the variety of languages with weird letters. I happen to speak one. We have interesting letters like Œ and interesting currencies like €. And don&#8217;t even get me started on Japanese, Hindi or Korean.</p>
<p>PHP does not support Unicode, it only supports ISO-8859-1 and assumes that every single string in a program is an ISO-8859-1 string. If you wish to store a character like € in PHP, you need to do so using the 256 characters from the ISO-8859-1 character set, which means you will end up using a combination of those characters to represent your single € character.</p>
<p>Could be &#8220;EUR&#8221;. And let the huddled eastern masses use romaji, pinyin and whatever.</p>
<p>Could be &#8220;&amp;euro;&#8221; or a similar clean HTML-friendly encoding of Unicode text. But that would be using six characters for one.</p>
<p>Or it could be UTF-8, which was designed to let Unicode code points move unhurt through a stringent encoding environment like ISO-8859-1. It still does so by using multiple characters from ISO-8859-1 to represent a single Unicode character (this is a technically incorrect metaphor, but bear with me). So € is represented as three characters: â‚¬.</p>
<p>However, to PHP&#8217;s substr function, every UTF-8 string remains an ISO-8859-1 string. This means that if the three characters representing the euro end up being straddled across the 255-character limit, the function will slice right through them. And &#8220;â‚&#8230;&#8221; is not a valid UTF-8 code point, so anything that expects a valid UTF-8 encoding is allowed to throw up with an error, which leads to the database refusing the connection and your user wondering what the problem is.</p>
<p>The solution would be, of course, to use PHP&#8217;s multiple byte encoding functions, such as <code>mb_substr</code>. The latter is UTF-8 aware and therefore never slices up a character in mid-sequence.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/08/find-the-bug/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>From PHP to Firebug</title>
		<link>http://www.nicollet.net/2009/08/from-php-to-firebug/</link>
		<comments>http://www.nicollet.net/2009/08/from-php-to-firebug/#comments</comments>
		<pubDate>Wed, 12 Aug 2009 19:08:31 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Dynamic]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1112</guid>
		<description><![CDATA[I often encounter problems with the typical approach of using var_dump (or Zend_Debug::Dump) to trace through PHP code: Does not work on a page that has to redirect to another page. Results are difficult to see if the page is queried through AJAX. The crash may happen so utterly that no data is actually output, [...]]]></description>
			<content:encoded><![CDATA[<p>I often encounter problems with the typical approach of using var_dump (or Zend_Debug::Dump) to trace through PHP code:</p>
<ul>
<li>Does not work on a page that has to redirect to another page.</li>
<li>Results are difficult to see if the page is queried through AJAX.</li>
<li>The crash may happen so utterly that no data is actually output, or it&#8217;s well hidden, or otherwise destroyed by output buffering mishaps.</li>
<li>I have to look around the page to find it, and it also destroys my page layout.</li>
</ul>
<p>The other possibility commonly used is to use error_log or log or printing to stderr or printing to a file, all of which rely on access to the server or setting up a way of displaying the data and filtering through it to see only relevant information.</p>
<p>I could bring in some logging facility from a framework (such as Symphony or Zend, which has a nice one), but I&#8217;d rather not add overweight dependencies—the Zend_Log_Writer_Firebug setup is kind of scary if you&#8217;re not already using enough bits of Zend to make it worth it.</p>
<p>So, I set out to design a system that would make things simpler. It works by sending &#8216;console.log()&#8217; instructions to Firebug to display whatever it needs to display, and keeping things in the session until they can be displayed.</p>
<p>The code:</p>
<pre><code><span style="color: #000000;"><span style="color: #0000bb;"> </span><span style="color: #007700;">function </span><span style="color: #0000bb;">jslog</span><span style="color: #007700;">()
 {
   if (</span><span style="color: #0000bb;"><a title="Open the PHP manual for defined" href="http://www.php.net/defined" target="_blank"><strong>defined</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">'NO_JSLOG'</span><span style="color: #007700;">))
     return;

   </span><span style="color: #0000bb;">$args </span><span style="color: #007700;">= </span><span style="color: #0000bb;"><a title="Open the PHP manual for func_get_args" href="http://www.php.net/func_get_args" target="_blank"><strong>func_get_args</strong></a></span><span style="color: #007700;">();
   if (empty(</span><span style="color: #0000bb;">$args</span><span style="color: #007700;">)) {
     return </span><span style="color: #0000bb;"><a title="Open the PHP manual for create_function" href="http://www.php.net/create_function" target="_blank"><strong>create_function</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">'$x'</span><span style="color: #007700;">, </span><span style="color: #dd0000;">
       'if (strpos($x,"&lt;/head&gt;") &gt; 0){
         $s = "&lt;script type=text/javascript&gt;";
         $e = "&lt;/script&gt;&lt;/head&gt;";
         $x = str_replace("&lt;/head&gt;",$s.$_SESSION["jslog"].$e,$x);
         $_SESSION["jslog"] = "";
       } return $x;'</span><span style="color: #007700;">);
   }

   </span><span style="color: #0000bb;">$_SESSION</span><span style="color: #007700;">[</span><span style="color: #dd0000;">'jslog'</span><span style="color: #007700;">] .=
     </span><span style="color: #dd0000;">'console.log('</span><span style="color: #007700;">.</span><span style="color: #0000bb;"><a title="Open the PHP manual for implode" href="http://www.php.net/implode" target="_blank"><strong>implode</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">','</span><span style="color: #007700;">,</span><span style="color: #0000bb;"><a title="Open the PHP manual for array_map" href="http://www.php.net/array_map" target="_blank"><strong>array_map</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">'json_encode'</span><span style="color: #007700;">,</span><span style="color: #0000bb;">$args</span><span style="color: #007700;">)).</span><span style="color: #dd0000;">');'</span><span style="color: #007700;">;
 }</span></span></code></pre>
<p>Nothing overly complex. Initialization happens as a simple <code>ob_start(jslog())</code>, and everything can be disabled with a well-timed <code>define('NO_JSLOG','')</code> if you don&#8217;t have Firebug running. Typical usage includes being echo-like:</p>
<pre style="padding-left: 30px;">jslog("Hello");</pre>
<p>Being var_dump-like:</p>
<pre style="padding-left: 30px;">jslog($_POST);</pre>
<p>Being printf-like:</p>
<pre style="padding-left: 30px;">jslog('Earned $%.2f today', $dollars);</pre>
<p>Being the best of both worlds:</p>
<pre style="padding-left: 30px;">jslog('My session is %o and my post is %o', $_SESSION, $_POST);</pre>
<p>Displayed objects are converted to JSON and sent to Firebug, where they can be explored with the nifty DOM explorer tab that is so much easier to use than looking at var_dumped data.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/08/from-php-to-firebug/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Best Practices</title>
		<link>http://www.nicollet.net/2009/08/best-practices/</link>
		<comments>http://www.nicollet.net/2009/08/best-practices/#comments</comments>
		<pubDate>Mon, 10 Aug 2009 20:03:14 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Imperative]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Productivity]]></category>
		<category><![CDATA[Psychology]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1109</guid>
		<description><![CDATA[There are hundreds of things that can go wrong even in the simplest situations. I&#8217;ve already explained why the real value of a domain expert is precisely to identify in advance everything that could go wrong with a project, so that it can be avoided. Consider a comment form on a website. Nothing too fancy: [...]]]></description>
			<content:encoded><![CDATA[<p>There are hundreds of things that can go wrong even in the simplest situations. I&#8217;ve already explained why <a href="http://www.nicollet.net/2009/07/empty-lists/" target="_blank">the real value of a domain expert</a> is precisely to identify in advance everything that could go wrong with a project, so that it can be avoided.</p>
<p>Consider a comment form on a website. Nothing too fancy: the user fills in the &#8220;Name&#8221;, &#8220;Website (optional)&#8221; and &#8220;Comment&#8221; areas on a form, clicks the &#8220;Submit&#8221; button, and the page reloads with the comment on the page. No login required, no AJAX, no special effects. There are many things that can go wrong with this setup, and <em>will </em>go wrong if left in the hands of an inexperienced developer. They can be inconvenient, annoying or outright dangerous.</p>
<p>For example,</p>
<ul>
<li><strong>Double-posting. </strong>When the submit button is clicked, the form sends a request to the server with the comment to be added. The server responds with the new list of comments. The user clicks the &#8220;refresh&#8221; button while on that page, or navigates to another page and presses the &#8220;back&#8221; button. This cause the browser to send the request again, so the comment appears twice in the comment list. If using POST, this is slightly less dangerous : the user might get an annoying &#8220;Submit again?&#8221; window instead of double-posting.</li>
<li><strong>SQL Injection</strong>. It is highly probable that the comments will be stored in an SQL-accessible database. If the code constructing the SQL query is not properly written, an appropriately chosen value for the comment fields can result in nasty things happening to the database.</li>
<li><strong>Cross-Site Request Forgery</strong>. Suppose that posting the form creates a GET request like:
<pre>http://yourdomain/postcomment?name={name}&amp;text={text}</pre>
<p>Knowing this, I can include an image tag in a forum, with a source attribute that matches the posting of a spam comment on your website. Every visitor of that forum page will send that request automatically (browsers auto-fetch images by default) and spam your comment list.</li>
<li><strong>Script Injection</strong>. The text entered by users must be displayed back to the visitors. If that text is not escaped before being output, an malicious attacker can submit a comment containing a dangerous script like:
<pre>document.location = "<a href="http://www.youtube.com/watch?v=f2b1D5w82yU">http://www.youtube.com/watch?v=f2b1D5w82yU</a>";</pre>
</li>
<li> <strong>Encoding Issues</strong>. What happens if the page is encoded in UTF-8 but I send you ISO-8859-1 text? Conversely, what happens if the page is encoded in ISO-8859-1 and I copy-paste my comment from Microsoft Word? For that matter, what is the encoding of the database? What is the encoding of your <em>string literals</em>?</li>
<li><strong>No Validation</strong>. User forgets to enter a name or a comment. No server-side check is made to determine whether the posted comment is valid and you get a mix of ugly empty comments and/or server error messages.</li>
<li><strong>Lossy Validation</strong>. You have to prevent people from posting with no name or no comment body. This means errors will be displayed on the page and, if the detection of such errors happens on the server after the initial post, it&#8217;s easy to forget <em>displaying back the text the user entered in the first place</em>. &#8220;Sorry, you forgot to enter a name so I&#8217;ve thrown your ten-line comment away&#8221; [<a name="ft1-from" href="#ft1">#</a>]</li>
<li><strong>Does not work in Internet Explorer</strong>. There are many possible causes for it, such as <a href="http://www.thefutureoftheweb.com/blog/button-wont-submit-in-ie" target="_blank">respecting W3C specifications</a>.</li>
<li><strong>Legal Issues</strong>. If a malicious commenter uses your page as a soapbox for illegal activities, some countries will hold you responsible. For instance, in France, you can be condemned if anonymous posters engage in holocaust denial on your website.</li>
</ul>
<p>That&#8217;s nine, just thinking about the obvious problems that would happen if following the simplest approach to this, and I have seen many of them happen in three situations: novice programmers (such as interns), freelancers and low-wage programmers. The worst offender is by far the code written in naive PHP, which has the peculiarity of &#8220;the simplest thing&#8221; being almost always &#8220;the incorrect thing&#8221; as well.</p>
<p>Still, if you can&#8217;t let an intern write a simple user comments page, what are you going to let interns do?</p>
<p>All of the above issues are easy to correct once you know about them. Always send data as POST, check the referrer, convert everything to UTF-8, validate your data, use prepared statements instead of inline SQL, respond with a 303 redirect to a GET page, include the posted data and any errors in the session and display them back in the form if present, take all your dynamic generation text through an an HTML escaping function, add &#8220;type=submit&#8221; to buttons, and add a quick moderation tool to hide unwanted messages quickly.</p>
<p>Knowing about the issues and acting to prevent them is the hard part, which is why every project should have at least one experienced developer who knows about the errors. Or be using a framework that prevents such errors from happening in the first place (then again, if <a href="http://framework.zend.com/manual/en/zend.form.quickstart.html#zend.form.quickstart.puttingtogether" target="_blank">the documentation for Zend_Form</a> has an &#8220;user refreshes page, double-posts by mistake&#8221; error, who can we trust?)</p>
<p>Although it has been taken over by marketing folks, there are still good thinks to be said about &#8220;best practices&#8221;. The basic idea is to have a set of practices available for the less experienced developers to follow. Such practices are usually very simple to understand and follow (never display data in a POST controller, never change the model significantly in a GET controller), reasonably simple to verify automatically (assert that no output happened as part of a POST controller response) and have the immediate effect of preventing a classic mistake (no re-post on a page refresh).</p>
<p>I&#8217;m a big proponent of enforcing good code through practices first, and then code-based contraptions if developers insist on ignoring them. The problem with going for the contraptions first is you have to explain how to use the contraptions anyway, and people will be tempted to move around the contraptions and still write bad code.</p>
<p>If your code is reviewed by a compiler or an automatic code analysis tool, you can learn how to game the system. This results in code that does not trigger the alarms, while still being bad. Compare with having your code reviewed by a live person, who is experienced and anal-retentive about respecting practices and makes it horribly clear that if you don&#8217;t follow them, you will be forced to follow them, on your free time before you can commit your code. Such reviews leave no room for wiggling, and as long as the judgment of the reviewer is fair, will actually motivate the team to respect the standards.</p>
<hr />[<a name="ft1" href="#ft1-from">#</a>] Viadeo actually did even worse things to me (&#8220;<em>Sorry, I forgot to tell you that you were only allowed 255 characters in this box, so I&#8217;ve deleted everything for you so you can try again. Oh, and don&#8217;t try the back button of your browser, I have also deleted your input on the previous page.</em>&#8220;) so I suspect it has been written by Java rookies with close oversight by non-technical management.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/08/best-practices/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>JSOS : JS-PHP mapping</title>
		<link>http://www.nicollet.net/2009/08/jsos-js-php-mapping/</link>
		<comments>http://www.nicollet.net/2009/08/jsos-js-php-mapping/#comments</comments>
		<pubDate>Fri, 07 Aug 2009 05:43:14 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Dynamic]]></category>
		<category><![CDATA[AJAX]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[JSOS]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[RPC]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1104</guid>
		<description><![CDATA[I&#8217;ve been working on Javascript-PHP remote call mapping techniques. A set of PHP classes with static functions are selected as a public interface and automatically exported so that the Javascript can call them. Usually, this kind of mapping involves three difficult points: Detecting what functions are exported and building the appropriate JS code. Detecting what [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been working on Javascript-PHP remote call mapping techniques. A set of PHP classes with static functions are selected as a public interface and automatically exported so that the Javascript can call them. Usually, this kind of mapping involves three difficult points:</p>
<ul>
<li>Detecting what functions are exported and building the appropriate JS code.</li>
<li>Detecting what function the JS is calling.</li>
<li>Transforming data between JS and PHP.</li>
</ul>
<p>I solve these with glob(), __autoload and json_encode (respectively) in a little prototype I called JSOS (<span style="text-decoration: underline;">J</span>ava<span style="text-decoration: underline;">S</span>cript <span style="text-decoration: underline;">O</span>r <span style="text-decoration: underline;">S</span>omething). <em>jQuery</em> on the client side provides me with synchronous AJAX queries. The code (PHP with echoed Javascript) looks like this, and is placed in a controller:</p>
<pre style="padding-left: 30px;"><code><span style="color: #000000;"><span style="color: #0000bb;">&lt;?php
 </span><span style="color: #007700;">function </span><span style="color: #0000bb;">__autoload</span><span style="color: #007700;">(</span><span style="color: #0000bb;">$classname</span><span style="color: #007700;">)
 {
   require_once(</span><span style="color: #0000bb;">$classname </span><span style="color: #007700;">. </span><span style="color: #dd0000;">'.inc.php'</span><span style="color: #007700;">);
 }

 if (!isset(</span><span style="color: #0000bb;">$_POST</span><span style="color: #007700;">[</span><span style="color: #dd0000;">'cls'</span><span style="color: #007700;">])) {
   echo </span><span style="color: #dd0000;">'&lt;html&gt;&lt;head&gt;&lt;title&gt;JSOS&lt;/title&gt;'</span><span style="color: #007700;">;
   echo </span><span style="color: #dd0000;">'&lt;script type="text/javascript" src="jquery.js"&gt;&lt;/script&gt;'</span><span style="color: #007700;">;
   echo </span><span style="color: #dd0000;">'&lt;script type="text/javascript"&gt;var jsos={};'</span><span style="color: #007700;">;
   echo </span><span style="color: #dd0000;">'$.ajaxSetup({async:false,timeout:5000});'</span><span style="color: #007700;">;
   echo </span><span style="color: #dd0000;">'jsos.$=function(c,f,a){'</span><span style="color: #007700;">;
   echo </span><span style="color: #dd0000;">'var o={exception:"Server disconnect"},'</span><span style="color: #007700;">;
   echo </span><span style="color: #dd0000;">'t={cls:c,func:f};'</span><span style="color: #007700;">;
   echo </span><span style="color: #dd0000;">'for(var i in a)t[""+i]=a[i];$.post(".",t,'</span><span style="color: #007700;">;
   echo </span><span style="color: #dd0000;">'function(d){o=d},"json");if("exception" in o)throw o.exception;'</span><span style="color: #007700;">;
   echo </span><span style="color: #dd0000;">'return o.result};'</span><span style="color: #007700;">;

   foreach (</span><span style="color: #0000bb;"><a title="Open the PHP manual for glob" href="http://www.php.net/glob" target="_blank"><strong>glob</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">'*.inc.php'</span><span style="color: #007700;">) as </span><span style="color: #0000bb;">$file</span><span style="color: #007700;">)
   {
 </span><span style="color: #0000bb;">    $class </span><span style="color: #007700;">= </span><span style="color: #0000bb;"><a title="Open the PHP manual for str_replace" href="http://www.php.net/str_replace" target="_blank"><strong>str_replace</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">'.inc.php'</span><span style="color: #007700;">, </span><span style="color: #dd0000;">''</span><span style="color: #007700;">, </span><span style="color: #0000bb;">$file</span><span style="color: #007700;">);
     echo </span><span style="color: #dd0000;">'jsos.' </span><span style="color: #007700;">. </span><span style="color: #0000bb;"><a title="Open the PHP manual for strtolower" href="http://www.php.net/strtolower" target="_blank"><strong>strtolower</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$class</span><span style="color: #007700;">) . </span><span style="color: #dd0000;">'={};'</span><span style="color: #007700;">;
     </span><span style="color: #0000bb;">$text </span><span style="color: #007700;">= </span><span style="color: #0000bb;"><a title="Open the PHP manual for file_get_contents" href="http://www.php.net/file_get_contents" target="_blank"><strong>file_get_contents</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$file</span><span style="color: #007700;">);

     </span><span style="color: #0000bb;"><a title="Open the PHP manual for preg_match_all" href="http://www.php.net/preg_match_all" target="_blank"><strong>preg_match_all</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">'/public static function ([A-Za-z_0-9]+)\(([^)]*)\)/'</span><span style="color: #007700;">,
                    </span><span style="color: #0000bb;">$text</span><span style="color: #007700;">, </span><span style="color: #0000bb;">$func</span><span style="color: #007700;">);

     foreach (</span><span style="color: #0000bb;">$func</span><span style="color: #007700;">[</span><span style="color: #0000bb;">1</span><span style="color: #007700;">] as </span><span style="color: #0000bb;">$id </span><span style="color: #007700;">=&gt; </span><span style="color: #0000bb;">$value</span><span style="color: #007700;">) {
       echo </span><span style="color: #dd0000;">'jsos.'</span><span style="color: #007700;">.</span><span style="color: #0000bb;"><a title="Open the PHP manual for strtolower" href="http://www.php.net/strtolower" target="_blank"><strong>strtolower</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$class</span><span style="color: #007700;">).</span><span style="color: #dd0000;">'.'</span><span style="color: #007700;">.</span><span style="color: #0000bb;"><a title="Open the PHP manual for strtolower" href="http://www.php.net/strtolower" target="_blank"><strong>strtolower</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$value</span><span style="color: #007700;">).</span><span style="color: #dd0000;">'=function('</span><span style="color: #007700;">;

 </span><span style="color: #0000bb;">      $args </span><span style="color: #007700;">= </span><span style="color: #0000bb;"><a title="Open the PHP manual for explode" href="http://www.php.net/explode" target="_blank"><strong>explode</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">','</span><span style="color: #007700;">, </span><span style="color: #0000bb;">$func</span><span style="color: #007700;">[</span><span style="color: #0000bb;">2</span><span style="color: #007700;">][</span><span style="color: #0000bb;">$id</span><span style="color: #007700;">]);

       foreach (</span><span style="color: #0000bb;"><a title="Open the PHP manual for array_keys" href="http://www.php.net/array_keys" target="_blank"><strong>array_keys</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$args</span><span style="color: #007700;">) as </span><span style="color: #0000bb;">$key</span><span style="color: #007700;">) {
 </span><span style="color: #0000bb;">        $args</span><span style="color: #007700;">[</span><span style="color: #0000bb;">$key</span><span style="color: #007700;">] = </span><span style="color: #0000bb;"><a title="Open the PHP manual for str_replace" href="http://www.php.net/str_replace" target="_blank"><strong>str_replace</strong></a></span><span style="color: #007700;">(array(</span><span style="color: #dd0000;">' '</span><span style="color: #007700;">, </span><span style="color: #dd0000;">'$'</span><span style="color: #007700;">), </span><span style="color: #dd0000;">''</span><span style="color: #007700;">, </span><span style="color: #0000bb;">$args</span><span style="color: #007700;">[</span><span style="color: #0000bb;">$key</span><span style="color: #007700;">]);
       }

       </span><span style="color: #0000bb;">$args </span><span style="color: #007700;">= </span><span style="color: #0000bb;"><a title="Open the PHP manual for array_filter" href="http://www.php.net/array_filter" target="_blank"><strong>array_filter</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$args</span><span style="color: #007700;">);

       echo </span><span style="color: #0000bb;"><a title="Open the PHP manual for implode" href="http://www.php.net/implode" target="_blank"><strong>implode</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">','</span><span style="color: #007700;">, </span><span style="color: #0000bb;">$args</span><span style="color: #007700;">).</span><span style="color: #dd0000;">'){return jsos.$'</span><span style="color: #007700;">;
       echo </span><span style="color: #dd0000;">'("'</span><span style="color: #007700;">.</span><span style="color: #0000bb;">$class</span><span style="color: #007700;">.</span><span style="color: #dd0000;">'","'</span><span style="color: #007700;">.</span><span style="color: #0000bb;"><a title="Open the PHP manual for strtolower" href="http://www.php.net/strtolower" target="_blank"><strong>strtolower</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$value</span><span style="color: #007700;">).</span><span style="color: #dd0000;">'",['</span><span style="color: #007700;">.</span><span style="color: #0000bb;"><a title="Open the PHP manual for implode" href="http://www.php.net/implode" target="_blank"><strong>implode</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">','</span><span style="color: #007700;">,</span><span style="color: #0000bb;">$args</span><span style="color: #007700;">).</span><span style="color: #dd0000;">'])};'</span><span style="color: #007700;">;
     }
   }

   echo </span><span style="color: #dd0000;">'&lt;/script&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;'</span><span style="color: #007700;">;
   exit;
 }

 </span><span style="color: #0000bb;">$args </span><span style="color: #007700;">= array();
 foreach (</span><span style="color: #0000bb;">$_POST </span><span style="color: #007700;">as </span><span style="color: #0000bb;">$key </span><span style="color: #007700;">=&gt; </span><span style="color: #0000bb;">$val</span><span style="color: #007700;">) {
   if (</span><span style="color: #0000bb;">$key </span><span style="color: #007700;">=== </span><span style="color: #dd0000;">'cls'</span><span style="color: #007700;">)
 </span><span style="color: #0000bb;">    $cls </span><span style="color: #007700;">= </span><span style="color: #0000bb;">$val</span><span style="color: #007700;">;
   elseif (</span><span style="color: #0000bb;">$key </span><span style="color: #007700;">=== </span><span style="color: #dd0000;">'func'</span><span style="color: #007700;">)
   </span><span style="color: #0000bb;">  $func </span><span style="color: #007700;">= </span><span style="color: #0000bb;">$val</span><span style="color: #007700;">;
   else
     </span><span style="color: #0000bb;">$args</span><span style="color: #007700;">[(int)</span><span style="color: #0000bb;">$key</span><span style="color: #007700;">] = </span><span style="color: #0000bb;">$val</span><span style="color: #007700;">;
 }

 </span><span style="color: #0000bb;"><a title="Open the PHP manual for ksort" href="http://www.php.net/ksort" target="_blank"><strong>ksort</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$args</span><span style="color: #007700;">);

 if (!</span><span style="color: #0000bb;"><a title="Open the PHP manual for class_exists" href="http://www.php.net/class_exists" target="_blank"><strong>class_exists</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$cls</span><span style="color: #007700;">)) {
   echo </span><span style="color: #dd0000;">'{exception:"No such package!"}'</span><span style="color: #007700;">;
   exit;
 }

 if (!</span><span style="color: #0000bb;"><a title="Open the PHP manual for method_exists" href="http://www.php.net/method_exists" target="_blank"><strong>method_exists</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;">$cls</span><span style="color: #007700;">, </span><span style="color: #0000bb;">$func</span><span style="color: #007700;">)) {
   echo </span><span style="color: #dd0000;">'{exception:"No such method!"}'</span><span style="color: #007700;">;
   exit;
 }

 try {
   </span><span style="color: #0000bb;">$result </span><span style="color: #007700;">= </span><span style="color: #0000bb;"><a title="Open the PHP manual for call_user_func_array" href="http://www.php.net/call_user_func_array" target="_blank"><strong>call_user_func_array</strong></a></span><span style="color: #007700;">(array(</span><span style="color: #0000bb;">$cls</span><span style="color: #007700;">, </span><span style="color: #0000bb;">$func</span><span style="color: #007700;">), </span><span style="color: #0000bb;">$args</span><span style="color: #007700;">);
   echo </span><span style="color: #0000bb;"><a title="Open the PHP manual for json_encode" href="http://www.php.net/json_encode" target="_blank"><strong>json_encode</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;"><a title="Open the PHP manual for compact" href="http://www.php.net/compact" target="_blank"><strong>compact</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">'result'</span><span style="color: #007700;">));
   exit;
 }
 catch (</span><span style="color: #0000bb;">Exception $e</span><span style="color: #007700;">) {
   </span><span style="color: #0000bb;">$exception </span><span style="color: #007700;">= </span><span style="color: #dd0000;">"$e"</span><span style="color: #007700;">;
   echo </span><span style="color: #0000bb;"><a title="Open the PHP manual for json_encode" href="http://www.php.net/json_encode" target="_blank"><strong>json_encode</strong></a></span><span style="color: #007700;">(</span><span style="color: #0000bb;"><a title="Open the PHP manual for compact" href="http://www.php.net/compact" target="_blank"><strong>compact</strong></a></span><span style="color: #007700;">(</span><span style="color: #dd0000;">'exception'</span><span style="color: #007700;">));
   exit;
 }
 </span></span></code></pre>
<p>All files in the current directory with a &#8216;.inc.php&#8217; extension are assumed to contain a similarly-named class, and all public static functions of that class are exported. For example, suppose Test.inc.php contains the following definition:</p>
<pre style="padding-left: 30px;">class Test
{
  public static function Run($a) { return "$a$a"; }
}</pre>
<p>Then, to call the Test::Run function from the above page, one would simply type in Javascript:</p>
<pre style="padding-left: 30px;">jsos.test.run('Hello');</pre>
<p>And indeed, with only five lines of code, the result to a client request is computed on the server and displayed on the client again, without issues. Here&#8217;s the result running in Firebug:</p>
<p><a href="http://www.nicollet.net/wp-content/uploads/2009/08/jsos.png"><img class="aligncenter size-full wp-image-1105" title="jsos" src="http://www.nicollet.net/wp-content/uploads/2009/08/jsos.png" alt="jsos" width="634" height="215" /></a></p>
<p>Further work should include handling errors (right now, if the server encounters an error or outputs non-JSON data, the call will die with a &#8220;Server disconnect&#8221; exception), which makes debugging easier than having to wade through the Net tab of Firebug.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/08/jsos-js-php-mapping/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Easier Unit Tests</title>
		<link>http://www.nicollet.net/2009/08/easier-unit-tests/</link>
		<comments>http://www.nicollet.net/2009/08/easier-unit-tests/#comments</comments>
		<pubDate>Thu, 06 Aug 2009 06:50:24 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Dynamic]]></category>
		<category><![CDATA[AJAX]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Bugs]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Productivity]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1099</guid>
		<description><![CDATA[Automated unit testing has three main advantages: It forces you to express in detail what you want the unit to do (so that a computer may then test it by checking the results). For an unit to be testable, its results should be checked as &#8220;correct&#8221; or &#8220;incorrect&#8221; on their own or with minimal context. [...]]]></description>
			<content:encoded><![CDATA[<p>Automated unit testing has three main advantages:</p>
<ol>
<li>It forces you to express in detail what you want the unit to do (so that a computer may then test it by checking the results).</li>
<li>For an unit to be testable, its results should be checked as &#8220;correct&#8221; or &#8220;incorrect&#8221; on their own or with minimal context. This makes code easier to reuse.</li>
<li>Since it&#8217;s automatic, you do not need to test manually every time you make a change: the tests will run hourly or nightly (or for every build) and tell you what went wrong.</li>
</ol>
<p>The cost, however, is that unit testing code must be written for every unit. That code needs to create an object, manipulate it, then check its return values for validity. And test-driven-development advocates insist that such code should be written before the actual unit (make it compile, make it fail, make it pass).</p>
<p>I do understand their position: if you&#8217;re thinking of very specific corner cases that you might forget about while writing the unit, you might as well write tests for these corner cases <em>first</em>. But what about the <em>simple</em>, core functionality of the units? If a programmer sets their minds to testing an unit, they can usually look at the value and decide &#8220;it&#8217;s correct&#8221; or &#8220;it&#8217;s wrong&#8221;, and that process is orders of magnitude faster than writing an assertion that checks whether the value is correct. We&#8217;re not talking about complex ten-argument dozen-property function on a deep-inherited object here, it&#8217;s more of a &#8220;this string goes in, that string goes out&#8221; concept.</p>
<p>An example would be a &#8220;slugify&#8221; function : plug an arbitrary string into it, and a cleaned up lowercase hyphen-separated string comes out. As long as you are mindful of corner cases (weird characters, encodings, empty strings and so on) testing this function manually is quite simple. You certainly lose the benefit of point 1 (no more expressing the details of what the function should do in advance of writing it) but you can keep the benefits of 2 (whether automatic or manual, tests make code reusable) and you can even get the benefits of 3 by saving your code as an unit test.</p>
<p>This is what happens in the console-like scaffold I have been working on (you can click to enlarge):</p>
<p><a href="http://www.nicollet.net/wp-content/uploads/2009/08/autotest.png"><img class="aligncenter size-medium wp-image-1100" title="autotest" src="http://www.nicollet.net/wp-content/uploads/2009/08/autotest-300x255.png" alt="autotest" width="300" height="255" /></a></p>
<p>You type in code in the text box. Clicking &#8220;Run&#8221; (or pressing TAB RET) sends the code through AJAX to the server and retrieves the <code>var_dump</code>ed result (as well as a highlighted version of the code). The mindful programmer can use this console to preemptively test and debug code. The excellent programmer will even adapt their code to make such testing easier, and being able to use any kind of code in the barren context of this console means that code can be used anywhere.</p>
<p>The real hit there is that little <strong>Add Cog</strong> icon from the FamFamFam Silk icon set. It&#8217;s truly beautiful and adds a touch of antialiased pixel art to an otherwise bland rounded-edges Web 2.0 console. And what it does is even better: when clicking that icon, the corresponding piece of PHP is automatically added to the database of unit tests, as a test that runs the code on the green background and asserts that the output of the code is identical to the text on the grey background.</p>
<p>This makes the &#8220;for every bug you find, write an unit test that finds that bug&#8221; insanely easy to do: find the bug using the console, correct the code, check with the console that the bug is dealt with, and then add the final &#8220;bug corrected&#8221; console run to the unit test database. The work spent debugging a program from this console is never lost, it all ends up being an automated test with one single click.</p>
<p>This also makes writing unit tests much more developer-friendly: the console lets you test your code as you write it, without having to write controllers or views or whatever other contraption you need to display the results. An excellent choice for testing-averse developers.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/08/easier-unit-tests/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Nice One</title>
		<link>http://www.nicollet.net/2009/07/nice-one/</link>
		<comments>http://www.nicollet.net/2009/07/nice-one/#comments</comments>
		<pubDate>Fri, 31 Jul 2009 07:13:00 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Random]]></category>
		<category><![CDATA[Bugs]]></category>
		<category><![CDATA[Useless]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1095</guid>
		<description><![CDATA[void User::DeleteAccount() { // You cannot delete accounts. It's illegal, we must keep a trace of every // account on the system. Use User::DisableAccount() instead. // Why is there a DeleteAccount() function then? Because once upon a // time, when there was no DeleteAccount() function, a smartass though // "hey, they forgot to write a [...]]]></description>
			<content:encoded><![CDATA[<pre><span style="color: #3366ff;">void </span>User::DeleteAccount()
{
<span style="color: #008000;">  // You cannot delete accounts. It's illegal, we must keep a trace of every
  // account on the system. Use User::DisableAccount() instead. 

  // Why is there a DeleteAccount() function then? Because once upon a
  // time, when there was no DeleteAccount() function, a smartass though
  // "hey, they forgot to write a DeleteAccount()" and promptly wrote it
  // himself. 

  // So, this function remained here as a warning for you: you obviously
  // didn't get the "you cannot delete accounts" memo, because you came
  // looking for a function to do just that. Do not try to delete
  // accounts, my friend. Do not stray from the righeous path. Disable
  // the accounts instead.</span><strong>
</strong>
  assert (<span style="color: #3366ff;">false</span>);
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/07/nice-one/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Keep Your URLs together</title>
		<link>http://www.nicollet.net/2009/07/keep-your-urls-together/</link>
		<comments>http://www.nicollet.net/2009/07/keep-your-urls-together/#comments</comments>
		<pubDate>Thu, 30 Jul 2009 07:23:46 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Dynamic]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1092</guid>
		<description><![CDATA[I have worked with many novice Agile developers, and many of them tend to make the same mistake we all did while developing web sites. They are writing some kind of functionality, and they need to display some information or post back some data to the server, so they have to make up a new [...]]]></description>
			<content:encoded><![CDATA[<p>I have worked with many novice Agile developers, and many of them tend to make the same mistake we all did while developing web sites. They are writing some kind of functionality, and they need to display some information or post back some data to the server, so they have to make up a new URL on the spot.</p>
<p>Being Agile, they don&#8217;t have an existing detailed specification to tell them what the URL should be. And they&#8217;re in the middle of writing something that&#8217;s quite complex, so thay can&#8217;t dedicate too much brain power to perform a proper choice. The end result is a hardcoded URL that they <em>will</em> need to change later on.</p>
<p>The problem here is that when an URL changes, everyone has to check their own files for uses of that URL, and correct it. Yes, it would be possible to add a permanent redirect (and it often is a good idea on a live website so that the <span style="text-decoration: line-through;">search engine</span> google references can be kept) but these do not play nice with POST requests, and what would be the point if the site has not gone live yet? So, people forget incorrect URLs in the middle of their files, and it takes a reasonable amount of examining crawler logs to find and replace them.</p>
<p>My usual practice is to have a central list of all URLs. Since I tend to work with an __autoload strategy, I just create an Url class and use members of that class to return properly HTML-formatted URLs : <code>&lt;?=htmlspecialchars(URLROOT.'/account/confirm/'.urlencode($id))?&gt;</code> becomes the cleaner <code>&lt;?=Url::ConfirmAccount($id)?&gt;</code>, and the actual account is hardcoded within the Url class as:</p>
<pre style="padding-left: 30px;">class Url
{
  const ROOT = 'http://mydomain.com';

  static function ConfirmAccount($id)
  {
    assert(is_int($id));
    return self::Local('account','confirm',$id);
  }

  private static function Local()
  {
    $url = self::ROOT;
    $get = '';

    foreach (func_get_args() as $segment)
      if (is_array($segment))
        foreach ($segment as $getkey =&gt; $getval)
          $get .= ($get === '' ? '?' : '&amp;')
               .  urlencode($getkey)
               . '=' . urlencode($getval);
      else
        $url .= '/' . urlencode($segment);

    return htmlspecialchars($url.$get);
  }
}</pre>
<p>So that the url-encoding of the segments, and the final cleanup of any HTML special chars that could have remained within the URL, are performed by the function automatically. Any associative arrays found in the argument list are converted to GET arguments that are also properly formatted and appended to the URL. Using the URL in a non-HTML environment, such as a text document or a <em>Location:</em> header, requires reversing the entity encoding beforehand, but this situation should be rare enough.</p>
<p>It would of course be proper to construct the ROOT constant from the requested domain name rather than hard-coding it. I have not done it here in order to keep the example short.</p>
<p>The benefits of this approach are many:</p>
<ul>
<li>Specifying the URL of the account confirmation page is not done by a random page anymore, it&#8217;s done by the Url class. The random page merely has to state that it wants to link to the account confirmation page. In case of a change of the account confirmation URL (such as /account-confirm instead of /account/confirm) all modifications will occur in a single place.</li>
<li>The programmer that uses the URL does not need to remember the format used to provide the data: if an URL can be built from several arguments, those arguments can be named, documented and checked by the PHP code.</li>
<li>Everything within the URL is properly escaped before it is returned : the output of an URL function is always a properly formatted URL with all special characters encoded as HTML entities. This way, no invalid URLs will ever appear within the code.</li>
</ul>
<p>Of course, in order to work, functions of the Url class should never be called with constant arguments: that would be akin to hardcoding those addresses. While the other benefits remain, changing the meaning of these arguments would have the same rippling effects over code. So, whenever you need to call a function with constant arguments, create a new function that explains what the url-with-constant-arguments is. For instance, &#8220;ConfirmAccount(0)&#8221; might be described as &#8220;ConfirmRootAccount()&#8221;, thereby shielding you from a change in the meaning of what a root account is.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/07/keep-your-urls-together/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CSS Indentation Trick</title>
		<link>http://www.nicollet.net/2009/07/css-indentation-trick/</link>
		<comments>http://www.nicollet.net/2009/07/css-indentation-trick/#comments</comments>
		<pubDate>Tue, 28 Jul 2009 08:17:15 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Dynamic]]></category>
		<category><![CDATA[CSS]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1090</guid>
		<description><![CDATA[While reading a WordPress stylesheet recently, I stumbled upon an interesting way to nest CSS selectors and their associated rules. To illustrate, here&#8217;s a listamatic example of list stylesheet: ul#navlist { margin: 0 0 0 30px; padding: 0; width: 12.5%; } #navlist li { list-style-type: none; background-color: #191970; color: #daa520; border: .2em solid #daa520; font-weight: [...]]]></description>
			<content:encoded><![CDATA[<p>While reading a WordPress stylesheet recently, I stumbled upon an interesting way to nest CSS selectors and their associated rules. To illustrate, here&#8217;s a <a href="http://css.maxdesign.com.au/listamatic/" target="_blank">listamatic</a> example of list stylesheet:</p>
<pre style="background: #ffffff none repeat scroll 0% 0%; color: #000000; padding-left: 30px;"><span style="color:#800000; font-weight:bold; ">ul</span><span style="color:#808030; ">#</span>navlist
<span style="color:#800080; ">{</span>
<span style="color:#bb7977; font-weight:bold; ">margin</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">0</span> <span style="color:#008c00; ">0</span> <span style="color:#008c00; ">0</span> <span style="color:#008c00; ">30</span><span style="color:#006600; ">px</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">padding</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">0</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">width</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">12.5</span><span style="color:#006600; ">%</span><span style="color:#800080; ">;</span>
<span style="color:#800080; ">}</span>

<span style="color:#808030; ">#</span>navlist <span style="color:#800000; font-weight:bold; ">li</span>
<span style="color:#800080; ">{</span>
<span style="color:#bb7977; font-weight:bold; ">list-style-type</span><span style="color:#808030; ">:</span> <span style="color:#074726; ">none</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">background-color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">191970</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">daa520</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">border</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">.2</span><span style="color:#006600; ">em</span> <span style="color:#074726; ">solid</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">daa520</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">font-weight</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">600</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">text-align</span><span style="color:#808030; ">:</span> <span style="color:#074726; ">center</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">padding</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">.3</span><span style="color:#006600; ">em</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">margin-bottom</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">.1</span><span style="color:#006600; ">em</span><span style="color:#800080; ">;</span>
<span style="color:#800080; ">}</span>

<span style="color:#808030; ">#</span>navlist <span style="color:#800000; font-weight:bold; ">li</span> <span style="color:#800000; font-weight:bold; ">a</span>
<span style="color:#800080; ">{</span>
<span style="color:#bb7977; font-weight:bold; ">color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">daa520</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">text-decoration</span><span style="color:#808030; ">:</span> <span style="color:#074726; ">none</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">display</span><span style="color:#808030; ">:</span> <span style="color:#074726; ">block</span><span style="color:#800080; ">;</span>
<span style="color:#800080; ">}</span>

<span style="color:#808030; ">#</span>navlist <span style="color:#800000; font-weight:bold; ">li</span> <span style="color:#800000; font-weight:bold; ">a</span><span style="color:#800080; ">:</span><span style="color:#800000; font-weight:bold; ">hover</span>
<span style="color:#800080; ">{</span>
<span style="color:#bb7977; font-weight:bold; ">background-color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">faebd7</span><span style="color:#800080; ">;</span>
<span style="color:#bb7977; font-weight:bold; ">color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">191970</span><span style="color:#800080; ">;</span>
<span style="color:#800080; ">}</span></pre>
<p>And here&#8217;s the reindented version:</p>
<pre style="background: #ffffff none repeat scroll 0% 0%; color: #000000; padding-left: 30px;"><span style="color:#800000; font-weight:bold; ">ul</span><span style="color:#808030; ">#</span>navlist <span style="color:#800080; ">{</span>
  <span style="color:#bb7977; font-weight:bold; ">margin</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">0</span> <span style="color:#008c00; ">0</span> <span style="color:#008c00; ">0</span> <span style="color:#008c00; ">30</span><span style="color:#006600; ">px</span><span style="color:#800080; ">;</span>
  <span style="color:#bb7977; font-weight:bold; ">padding</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">0</span><span style="color:#800080; ">;</span>
  <span style="color:#bb7977; font-weight:bold; ">width</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">12.5</span><span style="color:#006600; ">%</span><span style="color:#800080; ">;</span>
<span style="color:#800080; ">}</span>
  <span style="color:#808030; ">#</span>navlist <span style="color:#800000; font-weight:bold; ">li</span> <span style="color:#800080; ">{</span>
    <span style="color:#bb7977; font-weight:bold; ">list-style-type</span><span style="color:#808030; ">:</span> <span style="color:#074726; ">none</span><span style="color:#800080; ">;</span>
    <span style="color:#bb7977; font-weight:bold; ">background-color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">191970</span><span style="color:#800080; ">;</span>
    <span style="color:#bb7977; font-weight:bold; ">color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">daa520</span><span style="color:#800080; ">;</span>
    <span style="color:#bb7977; font-weight:bold; ">border</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">.2</span><span style="color:#006600; ">em</span> <span style="color:#074726; ">solid</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">daa520</span><span style="color:#800080; ">;</span>
    <span style="color:#bb7977; font-weight:bold; ">font-weight</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">600</span><span style="color:#800080; ">;</span>
    <span style="color:#bb7977; font-weight:bold; ">text-align</span><span style="color:#808030; ">:</span> <span style="color:#074726; ">center</span><span style="color:#800080; ">;</span>
    <span style="color:#bb7977; font-weight:bold; ">padding</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">.3</span><span style="color:#006600; ">em</span><span style="color:#800080; ">;</span>
    <span style="color:#bb7977; font-weight:bold; ">margin-bottom</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">.1</span><span style="color:#006600; ">em</span><span style="color:#800080; ">;</span>
  <span style="color:#800080; ">}</span>
    <span style="color:#808030; ">#</span>navlist <span style="color:#800000; font-weight:bold; ">li</span> <span style="color:#800000; font-weight:bold; ">a</span> <span style="color:#800080; ">{</span>
      <span style="color:#bb7977; font-weight:bold; ">color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">daa520</span><span style="color:#800080; ">;</span>
      <span style="color:#bb7977; font-weight:bold; ">text-decoration</span><span style="color:#808030; ">:</span> <span style="color:#074726; ">none</span><span style="color:#800080; ">;</span>
      <span style="color:#bb7977; font-weight:bold; ">display</span><span style="color:#808030; ">:</span> <span style="color:#074726; ">block</span><span style="color:#800080; ">;</span>
    <span style="color:#800080; ">}</span>
    <span style="color:#808030; ">#</span>navlist <span style="color:#800000; font-weight:bold; ">li</span> <span style="color:#800000; font-weight:bold; ">a</span><span style="color:#800080; ">:</span><span style="color:#800000; font-weight:bold; ">hover</span> <span style="color:#800080; ">{</span>
      <span style="color:#bb7977; font-weight:bold; ">background-color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">faebd7</span><span style="color:#800080; ">;</span>
      <span style="color:#bb7977; font-weight:bold; ">color</span><span style="color:#808030; ">:</span> <span style="color:#008c00; ">#</span><span style="color:#008000; ">191970</span><span style="color:#800080; ">;</span>
    <span style="color:#800080; ">}</span></pre>
<p>The idea is to combine the common prefixes in selectors as branches of a tree, and then indent each node of the tree by its depth in the tree (and place it right below its parent node or elder sibling). From what I gather, the position of the braces varies depending on personal styles, but the basic indentation rules apply as such.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/07/css-indentation-trick/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Scanners, Again</title>
		<link>http://www.nicollet.net/2009/07/scanners-again/</link>
		<comments>http://www.nicollet.net/2009/07/scanners-again/#comments</comments>
		<pubDate>Mon, 27 Jul 2009 11:23:45 +0000</pubDate>
		<dc:creator>Victor Nicollet</dc:creator>
				<category><![CDATA[Random]]></category>
		<category><![CDATA[djvu]]></category>
		<category><![CDATA[Objective Caml]]></category>
		<category><![CDATA[Productivity]]></category>
		<category><![CDATA[scanner]]></category>
		<category><![CDATA[Useless]]></category>

		<guid isPermaLink="false">http://www.nicollet.net/?p=1086</guid>
		<description><![CDATA[I&#8217;ve already ranted about my document scanner suite. I have recently updated it to add new features. The basic workflow goes like this: You run the &#8220;scan&#8221; command. This usually happens by clicking the desktop icon for the launcher, but you can also run it on a command line. The program prompts you for a [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve already <a href="http://www.nicollet.net/2009/06/scanners/" target="_blank">ranted about my document scanner suite</a>. I have recently updated it to add new features.</p>
<p>The basic workflow goes like this:</p>
<ul>
<li>You run the &#8220;scan&#8221; command. This usually happens by clicking the desktop icon for the launcher, but you can also run it on a command line.</li>
<li>The program prompts you for a document name. Aside from being different from any existing document name (to avoid accidental overwriting) you are free to choose any valid file name.</li>
<li>The program starts scanning pages. Every time a page is scanned, a  preview is shown and the user can accept or try again. Every time a page is accepted, the user is allowed to scan another page or stop scanning.</li>
<li>Every scanned page is saved to TIFF on the fly. Once all pages have been retrieved, they are converted to PNM, then to DJVU. This conversion step takes around two minutes per page on my computer. Then, all DJVU files are bundled together as a single file.</li>
<li>The bundled DJVU is stored both locally and on a backup server through FTP.</li>
</ul>
<p>Once the manual scan-preview-confirm process has ended, the lengthy compression and upload stage starts, but is completely non-interactive. It is therefore possible to start scanning another document (or do something else) while it finishes.</p>
<p>I have also reduced the resolution from 300 dpi to 150 dpi, as it remains quite readable. This has resulted in a reduction in file size from around 8MiB PNG files to 2MiB TIFF files, which are in turn compressed to 1MiB DJVU files. My current library of scanned pages (mostly administrative documents, reports and contracts) weighs in at around 150MiB instead of the previous 1.1GiB.</p>
<p>Below is a scan of <a href="http://en.wikipedia.org/wiki/Papier_d%27Arm%C3%A9nie">Papier d&#8217;Arménie</a> made by my delightful assistant:</p>
<p><a href="http://www.nicollet.net/files/pouf.djvu"><img class="aligncenter" src="http://www.nicollet.net/files/pouf.png" alt="" width="100" height="138" /></a></p>
<p>The Objective Caml source code for running this little baby follows below:</p>
<pre style="color:#000000;background:#ffffff;"><span style="color:#800000; font-weight:bold; ">exception</span> CommandFailed <span style="color:#800000; font-weight:bold; ">of</span> <span style="color:#800000; font-weight:bold; ">int</span>

<span style="color:#800000; font-weight:bold; ">let</span> run command <span style="color:#808030; ">=</span>
  print_endline command <span style="color:#808030; ">;</span>
  <span style="color:#800000; font-weight:bold; ">let</span> result <span style="color:#808030; ">=</span> Sys<span style="color:#008c00; ">.</span>command command <span style="color:#800000; font-weight:bold; ">in</span>
    <span style="color:#800000; font-weight:bold; ">if</span> result <span style="color:#808030; ">&lt;</span><span style="color:#808030; ">&gt;</span> <span style="color:#008c00; ">0</span> <span style="color:#800000; font-weight:bold; ">then</span> <span style="color:#800000; font-weight:bold; ">raise</span> <span style="color:#808030; ">(</span>CommandFailed result<span style="color:#808030; ">)</span>

<span style="color:#800000; font-weight:bold; ">let</span> ask request <span style="color:#808030; ">=</span>
  print_endline <span style="color:#808030; ">(</span> <span style="color:#0000e6; ">"# "</span> <span style="color:#808030; ">^</span> request <span style="color:#808030; ">)</span> <span style="color:#808030; ">;</span>
  read_line <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span>

<span style="color:#800000; font-weight:bold; ">let</span> tmp ext <span style="color:#808030; ">=</span>
  Filename<span style="color:#008c00; ">.</span>temp_file <span style="color:#0000e6; ">""</span> ext

<span style="color:#800000; font-weight:bold; ">let</span> say format <span style="color:#808030; ">=</span>
  Printf<span style="color:#008c00; ">.</span>printf <span style="color:#808030; ">(</span><span style="color:#0000e6; ">"# "</span> <span style="color:#808030; ">^</span><span style="color:#808030; ">^</span> format<span style="color:#808030; ">)</span>

<span style="color:#696969; ">(* Scan a page, display the result, ask if the user wants to keep it</span>
<span style="color:#696969; ">   (tries again until it gets the scan right) and returns the filename</span>
<span style="color:#696969; ">   where the successful scan was saved. *)</span>
<span style="color:#800000; font-weight:bold; ">let</span> <span style="color:#800000; font-weight:bold; ">rec</span> scan_to_tiff <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span> <span style="color:#808030; ">=</span>
  <span style="color:#800000; font-weight:bold; ">let</span> file <span style="color:#808030; ">=</span> tmp <span style="color:#0000e6; ">".tiff"</span> <span style="color:#800000; font-weight:bold; ">in</span>
    run <span style="color:#808030; ">(</span><span style="color:#0000e6; ">"scanimage -l 0 -t 0 -x 215 -y 297 --brightness -22 "</span>
         <span style="color:#808030; ">^</span> <span style="color:#0000e6; ">"--contrast 22 --resolution 150 --progress --mode Gray "</span>
         <span style="color:#808030; ">^</span> <span style="color:#0000e6; ">"--format=tiff &gt; "</span> <span style="color:#808030; ">^</span> file<span style="color:#808030; ">)</span> <span style="color:#808030; ">;</span>
    run <span style="color:#808030; ">(</span><span style="color:#0000e6; ">"display "</span> <span style="color:#808030; ">^</span> file<span style="color:#808030; ">)</span> <span style="color:#808030; ">;</span>
    <span style="color:#800000; font-weight:bold; ">if</span> ask <span style="color:#0000e6; ">"keep this page? [Yn]"</span> <span style="color:#808030; ">&lt;</span><span style="color:#808030; ">&gt;</span> <span style="color:#0000e6; ">"n"</span> <span style="color:#800000; font-weight:bold; ">then</span>
      file
    <span style="color:#800000; font-weight:bold; ">else</span>
      scan_to_tiff <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span>

<span style="color:#696969; ">(* Scan individual pages (using scan_to_tiff) until the user decides to</span>
<span style="color:#696969; ">   stop. If an individual scan fails due to system errors, allows retrying.</span>
<span style="color:#696969; ">   Returns the list of all filenames the user agreed with. *)</span>
<span style="color:#800000; font-weight:bold; ">let</span> <span style="color:#800000; font-weight:bold; ">rec</span> scan_list_to_tiff <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span> <span style="color:#808030; ">=</span>
  <span style="color:#800000; font-weight:bold; ">try</span>
    <span style="color:#800000; font-weight:bold; ">let</span> file <span style="color:#808030; ">=</span> scan_to_tiff <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span> <span style="color:#800000; font-weight:bold; ">in</span>
      <span style="color:#800000; font-weight:bold; ">if</span> ask <span style="color:#0000e6; ">"scan another page? [Yn]"</span> <span style="color:#808030; ">&lt;</span><span style="color:#808030; ">&gt;</span> <span style="color:#0000e6; ">"n"</span> <span style="color:#800000; font-weight:bold; ">then</span>
        file <span style="color:#808030; ">:</span><span style="color:#808030; ">:</span> scan_list_to_tiff <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span>
      <span style="color:#800000; font-weight:bold; ">else</span>
        <span style="color:#808030; ">[</span>file<span style="color:#808030; ">]</span>
  <span style="color:#800000; font-weight:bold; ">with</span> CommandFailed i <span style="color:#808030; ">-&gt;</span>
    say <span style="color:#0000e6; ">"command failed with exit code </span><span style="color:#0f69ff; ">%d</span><span style="color:#0f69ff; ">\n</span><span style="color:#0000e6; ">"</span> i <span style="color:#808030; ">;</span>
    <span style="color:#800000; font-weight:bold; ">if</span> ask <span style="color:#0000e6; ">"try again? [Yn]"</span> <span style="color:#808030; ">&lt;</span><span style="color:#808030; ">&gt;</span> <span style="color:#0000e6; ">"n"</span> <span style="color:#800000; font-weight:bold; ">then</span>
      scan_list_to_tiff <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span>
    <span style="color:#800000; font-weight:bold; ">else</span>
      <span style="color:#808030; ">[</span><span style="color:#808030; ">]</span>

<span style="color:#696969; ">(* Turn individual image into djvu image. Returns djvu filename</span>
<span style="color:#696969; ">   if successful. *)</span>
<span style="color:#800000; font-weight:bold; ">let</span> <span style="color:#800000; font-weight:bold; ">rec</span> tiff_to_djvu file <span style="color:#808030; ">=</span>
  <span style="color:#800000; font-weight:bold; ">let</span> pnm <span style="color:#808030; ">=</span> tmp <span style="color:#0000e6; ">".ppm"</span> <span style="color:#800000; font-weight:bold; ">in</span>
  <span style="color:#800000; font-weight:bold; ">let</span> djvu <span style="color:#808030; ">=</span> tmp <span style="color:#0000e6; ">".djvu"</span> <span style="color:#800000; font-weight:bold; ">in</span>
    run <span style="color:#808030; ">(</span> <span style="color:#0000e6; ">"convert "</span> <span style="color:#808030; ">^</span> file <span style="color:#808030; ">^</span> <span style="color:#0000e6; ">" "</span> <span style="color:#808030; ">^</span> pnm <span style="color:#808030; ">)</span> <span style="color:#808030; ">;</span>
    run <span style="color:#808030; ">(</span> <span style="color:#0000e6; ">"cpaldjvu "</span> <span style="color:#808030; ">^</span> pnm <span style="color:#808030; ">^</span> <span style="color:#0000e6; ">" "</span> <span style="color:#808030; ">^</span> djvu <span style="color:#808030; ">)</span> <span style="color:#808030; ">;</span>
    djvu

<span style="color:#696969; ">(* Turn a set of images into individual djvu pages. Allow skipping</span>
<span style="color:#696969; ">   or retrying on error during the conversion process. *)</span>
<span style="color:#800000; font-weight:bold; ">let</span> <span style="color:#800000; font-weight:bold; ">rec</span> tiff_list_to_djvu_list <span style="color:#808030; ">=</span> <span style="color:#800000; font-weight:bold; ">function</span>
  <span style="color:#808030; ">|</span> <span style="color:#808030; ">[</span><span style="color:#808030; ">]</span> <span style="color:#808030; ">-&gt;</span> <span style="color:#808030; ">[</span><span style="color:#808030; ">]</span>
  <span style="color:#808030; ">|</span> file <span style="color:#808030; ">:</span><span style="color:#808030; ">:</span> <span style="color:#800000; font-weight:bold; ">list</span> <span style="color:#808030; ">-&gt;</span>
    <span style="color:#800000; font-weight:bold; ">try</span>
      tiff_to_djvu file <span style="color:#808030; ">:</span><span style="color:#808030; ">:</span> tiff_list_to_djvu_list <span style="color:#800000; font-weight:bold; ">list</span>
    <span style="color:#800000; font-weight:bold; ">with</span> CommandFailed i <span style="color:#808030; ">-&gt;</span>
      say <span style="color:#0000e6; ">"command failed with exit code </span><span style="color:#0f69ff; ">%d</span><span style="color:#0f69ff; ">\n</span><span style="color:#0000e6; ">"</span> i <span style="color:#808030; ">;</span>
      <span style="color:#800000; font-weight:bold; ">if</span> ask <span style="color:#0000e6; ">"try again? [Yn]"</span> <span style="color:#808030; ">&lt;</span><span style="color:#808030; ">&gt;</span> <span style="color:#0000e6; ">"n"</span> <span style="color:#800000; font-weight:bold; ">then</span>
        tiff_list_to_djvu_list <span style="color:#808030; ">(</span>file <span style="color:#808030; ">:</span><span style="color:#808030; ">:</span> <span style="color:#800000; font-weight:bold; ">list</span><span style="color:#808030; ">)</span>
      <span style="color:#800000; font-weight:bold; ">else</span>
        tiff_list_to_djvu_list <span style="color:#800000; font-weight:bold; ">list</span>

<span style="color:#696969; ">(* Turn a list of individual djvu files into a bundled djvu file. *)</span>
<span style="color:#800000; font-weight:bold; ">let</span> <span style="color:#800000; font-weight:bold; ">rec</span> make_djvu_bundle file <span style="color:#800000; font-weight:bold; ">list</span> <span style="color:#808030; ">=</span>
  <span style="color:#800000; font-weight:bold; ">try</span>
    <span style="color:#800000; font-weight:bold; ">if</span>  <span style="color:#800000; font-weight:bold; ">list</span> <span style="color:#808030; ">=</span> <span style="color:#808030; ">[</span><span style="color:#808030; ">]</span> <span style="color:#800000; font-weight:bold; ">then</span>
      <span style="color:#800000; font-weight:bold; ">false</span>
    <span style="color:#800000; font-weight:bold; ">else</span> <span style="color:#800000; font-weight:bold; ">if</span> List<span style="color:#008c00; ">.</span>tl <span style="color:#800000; font-weight:bold; ">list</span> <span style="color:#808030; ">=</span> <span style="color:#808030; ">[</span><span style="color:#808030; ">]</span> <span style="color:#800000; font-weight:bold; ">then</span>
      <span style="color:#808030; ">(</span> run <span style="color:#808030; ">(</span> <span style="color:#0000e6; ">"cp "</span> <span style="color:#808030; ">^</span> List<span style="color:#008c00; ">.</span>hd <span style="color:#800000; font-weight:bold; ">list</span> <span style="color:#808030; ">^</span> <span style="color:#0000e6; ">" "</span> <span style="color:#808030; ">^</span> file <span style="color:#808030; ">)</span> <span style="color:#808030; ">;</span> <span style="color:#800000; font-weight:bold; ">true</span> <span style="color:#808030; ">)</span>
    <span style="color:#800000; font-weight:bold; ">else</span>
      <span style="color:#808030; ">(</span> run <span style="color:#808030; ">(</span> <span style="color:#0000e6; ">"djvm "</span> <span style="color:#808030; ">^</span> file <span style="color:#808030; ">^</span> <span style="color:#0000e6; ">" "</span> <span style="color:#808030; ">^</span> String<span style="color:#008c00; ">.</span>concat <span style="color:#0000e6; ">" "</span> <span style="color:#800000; font-weight:bold; ">list</span><span style="color:#808030; ">)</span> <span style="color:#808030; ">;</span> <span style="color:#800000; font-weight:bold; ">true</span> <span style="color:#808030; ">)</span>
  <span style="color:#800000; font-weight:bold; ">with</span> CommandFailed i <span style="color:#808030; ">-&gt;</span>
    say <span style="color:#0000e6; ">"command failed with exit code </span><span style="color:#0f69ff; ">%d</span><span style="color:#0f69ff; ">\n</span><span style="color:#0000e6; ">"</span> i <span style="color:#808030; ">;</span>
    <span style="color:#800000; font-weight:bold; ">if</span> ask <span style="color:#0000e6; ">"try again? [Yn]"</span> <span style="color:#808030; ">&lt;</span><span style="color:#808030; ">&gt;</span> <span style="color:#0000e6; ">"n"</span> <span style="color:#800000; font-weight:bold; ">then</span>
      make_djvu_bundle file <span style="color:#800000; font-weight:bold; ">list</span>
    <span style="color:#800000; font-weight:bold; ">else</span>
     <span style="color:#808030; ">(</span> say <span style="color:#0000e6; ">"scan aborted"</span> <span style="color:#808030; ">;</span> <span style="color:#800000; font-weight:bold; ">false</span> <span style="color:#808030; ">)</span>

<span style="color:#696969; ">(* Choose a name for the output djvu file *)</span>
<span style="color:#800000; font-weight:bold; ">let</span> <span style="color:#800000; font-weight:bold; ">rec</span> choose_djvu_filename <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span> <span style="color:#808030; ">=</span>
  <span style="color:#800000; font-weight:bold; ">let</span> path <span style="color:#808030; ">=</span> <span style="color:#0000e6; ">"/home/arkadir/docs/"</span> <span style="color:#800000; font-weight:bold; ">in</span>
  <span style="color:#800000; font-weight:bold; ">let</span> name <span style="color:#808030; ">=</span> ask <span style="color:#0000e6; ">"document name (extension will be added automatically) ?"</span> <span style="color:#800000; font-weight:bold; ">in</span>
    <span style="color:#800000; font-weight:bold; ">if</span> name <span style="color:#808030; ">&lt;</span><span style="color:#808030; ">&gt;</span> <span style="color:#0000e6; ">""</span> &amp;&amp; name <span style="color:#808030; ">&lt;</span><span style="color:#808030; ">&gt;</span> Filename<span style="color:#008c00; ">.</span>basename name <span style="color:#800000; font-weight:bold; ">then</span>
      <span style="color:#808030; ">(</span> say <span style="color:#0000e6; ">"incorrect filename"</span> <span style="color:#808030; ">;</span> choose_djvu_filename <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span> <span style="color:#808030; ">)</span>
    <span style="color:#800000; font-weight:bold; ">else</span> <span style="color:#800000; font-weight:bold; ">if</span> Sys<span style="color:#008c00; ">.</span>file_exists <span style="color:#808030; ">(</span>Filename<span style="color:#008c00; ">.</span>concat path <span style="color:#808030; ">(</span>name <span style="color:#808030; ">^</span> <span style="color:#0000e6; ">".djvu"</span><span style="color:#808030; ">)</span><span style="color:#808030; ">)</span> <span style="color:#800000; font-weight:bold; ">then</span>
      <span style="color:#808030; ">(</span> say <span style="color:#0000e6; ">"file already exists"</span> <span style="color:#808030; ">;</span> choose_djvu_filename <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span> <span style="color:#808030; ">)</span>
    <span style="color:#800000; font-weight:bold; ">else</span>
      Filename<span style="color:#008c00; ">.</span>concat path <span style="color:#808030; ">(</span>name <span style="color:#808030; ">^</span> <span style="color:#0000e6; ">".djvu"</span><span style="color:#808030; ">)</span>

<span style="color:#696969; ">(* Upload a file to an ftp server. *)</span>
<span style="color:#800000; font-weight:bold; ">let</span> <span style="color:#800000; font-weight:bold; ">rec</span> upload_file file <span style="color:#808030; ">=</span>
  <span style="color:#800000; font-weight:bold; ">try</span>
    run <span style="color:#808030; ">(</span> <span style="color:#0000e6; ">"ncftpput -f /home/arkadir/docs/ftp.cfg /home/www/blog/docs "</span> <span style="color:#808030; ">^</span> file <span style="color:#808030; ">)</span>
  <span style="color:#800000; font-weight:bold; ">with</span> CommandFailed i <span style="color:#808030; ">-&gt;</span>
    say <span style="color:#0000e6; ">"command failed with exit code </span><span style="color:#0f69ff; ">%d</span><span style="color:#0f69ff; ">\n</span><span style="color:#0000e6; ">"</span> i  <span style="color:#808030; ">;</span>
    <span style="color:#800000; font-weight:bold; ">if</span> ask <span style="color:#0000e6; ">"try again? [Yn]"</span> <span style="color:#808030; ">&lt;</span><span style="color:#808030; ">&gt;</span> <span style="color:#0000e6; ">"n"</span> <span style="color:#800000; font-weight:bold; ">then</span>
      upload_file file
    <span style="color:#800000; font-weight:bold; ">else</span>
      say <span style="color:#0000e6; ">"upload aborted"</span>

<span style="color:#696969; ">(* Complete process *)</span>
<span style="color:#800000; font-weight:bold; ">let</span> <span style="color:#008c00; ">_</span> <span style="color:#808030; ">=</span>
  <span style="color:#800000; font-weight:bold; ">let</span> name <span style="color:#808030; ">=</span> choose_djvu_filename <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span> <span style="color:#800000; font-weight:bold; ">in</span>
  <span style="color:#800000; font-weight:bold; ">let</span> files <span style="color:#808030; ">=</span> tiff_list_to_djvu_list <span style="color:#808030; ">(</span>scan_list_to_tiff <span style="color:#808030; ">(</span><span style="color:#808030; ">)</span><span style="color:#808030; ">)</span> <span style="color:#800000; font-weight:bold; ">in</span>
    <span style="color:#800000; font-weight:bold; ">if</span> make_djvu_bundle name files <span style="color:#800000; font-weight:bold; ">then</span>
      upload_file name</pre>
<p>This requires the classic djvuLibre utils to be installed (cpaldjvu and djvm), as well as imagemagick (convert) and ncftp (ncftpput). Scanning happens with sane (scanimage). Some files are also uploaded to my web server, where I use &#8220;convert -thumbnail&#8221; to create thumbnails from DJVU files.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.nicollet.net/2009/07/scanners-again/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

