<?xml version="1.0"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Donat Studios</title>
    <link>https://donatstudios.com/</link>
    <description/>
    <atom:link rel="self" type="application/rss+xml" href="https://donatstudios.com/feed.rss"/>
    <image>
      <url>https://donatstudios.com/images/site/moustache.png</url>
      <link>https://donatstudios.com/</link>
      <title>Donat Studios</title>
    </image>
    <item>
      <guid>https://donatstudios.com/probabilistic-filters</guid>
      <title>Thinking About Probabilistic Filters</title>
      <pubDate>Thu, 05 Mar 2026 17:30:18 -0600</pubDate>
      <link>https://donatstudios.com/probabilistic-filters</link>
      <description><![CDATA[<p>I've been a big fan of probabilistic filters such as <a href="https://en.wikipedia.org/wiki/Bloom_filter">Bloom</a>, and (more so) <a href="https://www.cs.cmu.edu/~dga/papers/cuckoo-conext2014.pdf">Cuckoo filters</a> for a while now. I've put them to great use in my personal projects, as well as in major data processing projects at work. They fall into a neat class of data structures that let you trade certainty for performance.</p>
<p>They are wildly useful for membership checks without loading the entire set into memory in cases where occasional false positives are acceptable. This can make certain cases of checks far more reasonable where scanning the entire set every time would be prohibitively expensive.</p>
<p>Whenever I try to explain them to someone, they tend to get confused. To that end I wanted to put together an oversimplified explanation I can point friends and coworkers to on how to think about these sorts of filters when these questions arise.</p>
<p>This will not fully explain how probabilistic filters work on a technical level. It will arm you with a simplified way to think about them. A working mental model.</p>
<h2>Definition</h2>
<p>The general idea of a probabilistic set-membership filter is that it is a memory-efficient way to check membership in a set. An item once added to the filter is guaranteed to <strong>always</strong> pass the filter. An item never added to the filter will <em>probably</em> fail the filter but may sometimes pass.</p>
<h3>Example</h3>
<p>A good example of probabilistic filtering in action is Google Safe Browsing. Instead of sending your browser an enormous list of 5-billion+ known dangerous URLs, Google sends a compact probabilistic representation of that list. If a URL fails the filter, it is not in Google's list of known dangerous URLs. If it passes the filter, the browser verifies it with Google against their full database to eliminate false positives.</p>
<h2>The Mental Model</h2>
<p>Let's start by imagining your data. Let's let the following shapes represent the individual items in our set.</p>
<div style="display: flex; justify-content: space-between">
    <img style="max-width: min(30%,200px)" src="https://donatstudios.com/assets/89/diamond.svg" alt="A diamond">
    <img style="max-width: min(30%,200px)" src="https://donatstudios.com/assets/89/x.svg" alt="An X">
    <img style="max-width: min(30%,200px)" src="https://donatstudios.com/assets/89/meeple.svg" alt="A cartoon person">
</div>
<p>And let's let this &quot;wall&quot; represent our filter.</p>
<img style="max-width: min(90%,300px); display: block; margin: 0 auto;" src="https://donatstudios.com/assets/89/wall.svg" alt="A flat wall">
<p>Now imagine forcing our data shapes through the wall and the impression they would make. This can be imagined as adding items to our filter. There is now a hole in the wall in the shape of our data. As we continue pushing all our items through, we are additively making the hole larger.</p>
<img style="max-width: min(90%,300px); display: block; margin: 0 auto;" src="https://donatstudios.com/assets/89/filter-ran.svg" alt="A flat wall that's been road runnered through by our shapes">
<p>Imagine the wall is now locked from further changes. It becomes a filter that only certain shapes may pass through.</p>
<p>One can see how all three of our shapes in our set would successfully &quot;pass&quot; the filter, while <em>some</em> shapes not in our set would fail to pass the filter.</p>
<div style="display: flex; justify-content: space-between">
<img style="max-width: min(40%,300px); display: block; margin: 0 auto;" src="https://donatstudios.com/assets/89/pass.svg" alt="A shape from our set that fits successfully">

<img style="max-width: min(40%,300px); display: block; margin: 0 auto;" src="https://donatstudios.com/assets/89/fail.svg" alt="A shape not from our set that fails to pass">
</div>
<p>One can also imagine shapes we did not define that would pass. This is how false positives occur when using filters like these.</p>
<img style="max-width: min(90%,300px); display: block; margin: 0 auto;" src="https://donatstudios.com/assets/89/false-positive.svg" alt="A strange shape fitting through as a false positive">
<h2>Digitization</h2>
<p>So that's all fine and good for a wall in the physical world, but now we want to digitize this. Let's imagine our wall as a bitmap - a literal map of bits. A zero where our wall has been untouched, and a one wherever it was pierced.</p>
<img style="max-width: min(90%,300px); display: block; margin: 0 auto;" src="https://donatstudios.com/assets/89/digital.svg" alt="A matrix with our hole now represented digitally">
<p>To check if an object passes our filter, we simply check if its pattern fits into our filter's matrix.</p>
<p>Here we can see if we digitize our diamond from earlier, its enabled bits fit cleanly into the marked bits of our filter, and thus it passes the filter.</p>
<p>In many real implementations the &quot;shape&quot; is produced by hashing the item several times, which selects positions in the bitmap to set. The bitmap would also be magnitudes larger than the one in our illustration.</p>
<img style="max-width: min(90%,300px); display: block; margin: 0 auto;" src="https://donatstudios.com/assets/89/digital-diamond-2.svg" alt="the matrix with our hole shape, covered by a diamond and it's digital shape">
<p>One can easily imagine how this could save a lot of memory. Instead of passing around an entire set with potentially millions or billions of members, you only need to pass the serialized bits of the filter. </p>
<p>As I said at the beginning, this is not exactly how they work. This is a drastic oversimplification, and the exact inner workings vary by filter type and configuration. This is just a mental model that can be helpful when thinking about them.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/psr16-iterable-issue</guid>
      <title>Fully Implementing PSR-16 Simple Cache is Less Than Simple</title>
      <pubDate>Mon, 13 Oct 2025 04:42:59 -0500</pubDate>
      <link>https://donatstudios.com/psr16-iterable-issue</link>
      <description><![CDATA[<p><strong>Foreword</strong>: Please note this falls firmly into &quot;nitpick&quot; territory rather than shining light onto any sort of major issue. I just thought it was an interesting gotcha and wanted to share.</p>
<p><strong>TL;DR</strong>: To fully implement the PSR-16 specification as defined, an implementation MUST be contravariant of the defined interface in its accepted parameters when implementing v2 or later.</p>
<p>I work on an application with a proprietary caching layer. Recently I wanted to build a PSR-16 &quot;Simple Cache&quot; adapter for it, and started going through <a href="https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md">the spec</a> and <a href="https://github.com/php-fig/simple-cache">provided interface</a> in detail. I wanted my implementation to be as correct as possible.</p>
<p>While working my way through <code>getMultiple</code>/<code>setMultiple</code>/<code>deleteMultiple</code> I noted something that seemed like an oversight.</p>
<pre><code class="language-php">/**
 * Obtains multiple cache items by their unique keys.
 *
 * @param iterable&lt;string&gt; $keys    A list of keys that can be obtained in a single operation.
 * @param mixed            $default Default value to return for keys that do not exist.
 *
 * @return iterable&lt;string, mixed&gt; A list of key =&gt; value pairs. Cache keys that do not exist or are stale will have $default as value.
 *
 * @throws \Psr\SimpleCache\InvalidArgumentException
 *   MUST be thrown if $keys is neither an array nor a Traversable,
 *   or if any of the $keys are not a legal value.
 */
public function getMultiple(iterable $keys, mixed $default = null): iterable;</code></pre>
<p>Take a moment to read this carefully</p>
<pre><code>@throws \Psr\SimpleCache\InvalidArgumentException
  <strong>MUST be thrown if $keys is neither an array nor a Traversable</strong>
  or if any of the $keys are not a legal value.</code></pre>
<p>Then, inspect the definition</p>
<pre><code class="language-php">public function getMultiple(<strong>iterable $keys</strong>, mixed $default = null): iterable;</code></pre>
<p>The takeaway here should be:</p>
<blockquote>
<p><code>iterable $keys</code></p>
</blockquote>
<p>To implement <code>getMultiple</code>, we <strong>MUST</strong> throw a <code>\Psr\SimpleCache\InvalidArgumentException</code> when <code>$keys</code> is invalid, yet <code>$keys</code> is defined as <code>iterable</code> which makes it impossible to handle <em>if an implementation matches this signature</em>. </p>
<p>Passing anything non-iterable will throw a <code>TypeError</code> before the code even runs. This would make it impossible to throw an InvalidArgumentException.</p>
<p>Parameter types were added to the interface with the release of <code>v2</code>. My suspicion was that the <code>@throws</code> had simply not been updated, because if the parameter type is explicitly defined as iterable, the function cannot throw a custom exception.</p>
<p>I originally suspected this would make it impossible to fulfill the spec. However, I had not considered contravariance. The implementing methods accepted parameter types may be wider than the interface it is fulfilling. In this instance, <code>$keys</code> does not need to be defined as <code>iterable</code> in the implementation.</p>
<p>This means a technically complete implementation must omit <code>iterable</code> and look something like</p>
<pre><code class="language-php">public function getMultiple($keys, mixed $default = null): iterable {
    if(!is_array($keys) &amp;&amp; !$var instanceof Traversable) {
        // Must implement \Psr\SimpleCache\InvalidArgumentException;
        throw new ExampleException;
    }
}</code></pre>
<p><small>Note <code>\Psr\SimpleCache\InvalidArgumentException</code> is an interface. An implementation must define a concrete exception class that implements it before it can be thrown</small></p>
<p>So while it's not <em>impossible</em> to implement, it certainly is strange to implement. One can only correctly implement the spec by manually checking <code>$keys</code> type like this.</p>
<p>Doing a little investigation, it seems like most major implementations still target <code>v1 || v2 || v3</code> and as <code>v1</code> did not have typed parameters, neither do the implementations iterable parameters. This means most major implementations are implemented correctly.</p>
<p>However, there are at least a couple of implementations that get this wrong.</p>
<ul>
<li><a href="https://github.com/laminas/laminas-cache/blob/969a664de32938c08a2c7a0181f47c6eef38155c/src/Psr/SimpleCache/SimpleCacheDecorator.php#L172">laminas/laminas-cache</a></li>
<li><a href="https://github.com/yiisoft/cache/blob/20d3671d9f419116400a885f826180d66573941c/src/ArrayCache.php#L77">yiisoft/cache</a></li>
<li><a href="https://github.com/PHPSocialNetwork/phpfastcache/blob/974f404318fab53dfc41cc48aa0eca47c046fcba/lib/Phpfastcache/Helper/Psr16Adapter.php#L142">phpfastcache/phpfastcache</a></li>
</ul>
<p>I'm sure there are others, but I found these three in my quick search. It is a very easy mistake to make when not implementing <code>v1</code>.</p>
<p>This is not Java. There are no checked exceptions forcing anyone's hand. In practice, this is a minor quirk of the spec. If a <code>TypeError</code> gets thrown instead of an <code>\Psr\SimpleCache\InvalidArgumentException</code>, almost certainly nothing meaningful breaks. I doubt anything depends on this behavior. It’s just an inconsistency between what the spec says and what implementations are doing. It is nothing to lose sleep over, but it might be worth fixing for consistency's sake.</p>
<p>In summary, to fully implement the <code>getMultiple</code>/<code>setMultiple</code>/<code>deleteMultiple</code> methods of the PSR-16 spec, one cannot simply copy their definitions directly from the CacheInterface. The implementation must check the validity of their iterable parameters and not have them defined as an <code>iterable</code>.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/mac-terminal-run-unsigned-binaries</guid>
      <title>Best Way to Run Unsigned Binaries in Terminal on macOS</title>
      <pubDate>Wed, 28 May 2025 19:00:17 -0500</pubDate>
      <link>https://donatstudios.com/mac-terminal-run-unsigned-binaries</link>
      <description><![CDATA[<p>With the introduction of Apple Silicon, macOS became much stricter about running unsigned binaries. It even refuses to execute them in the Terminal. This can be frustrating as many precompiled tools, once easily downloadable from sources like GitHub, may no longer work as expected.</p>
<p>By default, if you try to run an unsigned binary in Terminal, you will see an error like:</p>
<picture>
  <source srcset="/assets/86/warning.avif" type="image/avif">
  <img src="https://donatstudios.com/assets/86/warning.png" 
title="Apple could not verify [binary] is free of malware that may harm your Mac or compromise your privacy" 
alt="Message: Apple could not verify [binary] is free of malware that may harm your Mac or compromise your privacy" 
style="max-width: min(100%, 638px); display: block; margin: 0 auto;">
</picture>
<blockquote>
<p>Apple could not verify &quot;[binary]&quot; is free of malware that may harm your Mac or compromise your privacy</p>
</blockquote>
<p>Instead of offering any help, macOS simply offers to move the binary to the trash. Very frustrating.</p>
<p>You find a lot of complicated solutions online, such as manually unquarantining and self-signing the individual binaries with:</p>
<pre><code class="language-console">$ xattr -dr com.apple.quarantine ./[binary]
$ codesign -s - --deep --force ./[binary]</code></pre>
<p>Fortunately, there's a simple solution: add Terminal as a system <strong>Developer Tool</strong> in <em>System Settings</em>. </p>
<p>This will just allow it to run unsigned binaries. No fuss, no muss.</p>
<p>To do this:</p>
<ol>
<li>Open <strong>System Settings</strong></li>
<li>Search for &quot;developer&quot;</li>
<li>Click <strong>Allow applications to use developer tools</strong> in the sidebar.</li>
</ol>
<p>If Terminal is not already listed, click the <code>+</code> and search for it:</p>
<picture>
  <source srcset="/assets/86/add-terminal.avif" type="image/avif">
  <img src="https://donatstudios.com/assets/86/add-terminal.png"
title="System Settings: Allow applications to use developer tools - Add App"
alt="Shows 'System Settings' app with 'Allow applications to use developer tools' selected and an arrow pointing to the plus option"
style="max-width: min(100%, 827px);  display: block; margin: 0 auto;">
</picture>
<p>Search for <code>Terminal</code> in the file dialog that appears and select it.</p>
<picture>
  <source srcset="/assets/86/search.avif" type="image/avif">
  <img src="https://donatstudios.com/assets/86/search.png"
title="Search for 'Terminal'"
alt="File Chooser searching spotlight for Terminal"
style="max-width: min(100%, 912px);  display: block; margin: 0 auto;">
</picture>
<p>Once added, ensure the toggle next to Terminal is <strong>enabled</strong>.</p>
<picture>
  <source srcset="/assets/86/enable-terminal.avif" type="image/avif">
  <img src="https://donatstudios.com/assets/86/enable-terminal.png"
title="Make sure the slider is in the enabled position"
alt="Arrow points to the slider knob to enable Terminal in System Settings" 
style="max-width: min(100%, 827px);  display: block; margin: 0 auto;">
</picture>
<p>Finally, <strong>restart Terminal</strong> and everything should now work:</p>
<picture>
  <source srcset="/assets/86/success.avif" type="image/avif">
  <img src="https://donatstudios.com/assets/86/success.png"
title="Success!"
alt="Terminal window showing the previous app that died in error is now a success" 
style="max-width: min(100%, 682px); display: block; margin: 0 auto;">
</picture>
<p>There you have it. You may now run any unsigned binary from Terminal without issues.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Go-Iterators-Smell-Funny</guid>
      <title>Go&#x2019;s New Iterators Smell a Little Funny, but They&#x2019;re Still Good</title>
      <pubDate>Thu, 05 Sep 2024 18:28:12 -0500</pubDate>
      <link>https://donatstudios.com/Go-Iterators-Smell-Funny</link>
      <description><![CDATA[<p>Go's new Iterators were <a href="https://tip.golang.org/doc/go1.23">released on August 13th with Go 1.23</a>. They're pretty useful.</p>
<p>They've had just a minute to marinate. In some parts of the community they've been  an unpopular addition, however I don't particularly mind them. The old <a href="https://go.dev/tour/methods/21">alternatives</a> seem a little more &quot;Go&quot; to me, but the new iterators are fine.</p>
<p>One thing however I am mildly disappointed about is that there isn't just a <em>generic</em> <code>iterable</code> type similar to the generic <code>comparable</code>. It would be nice to have an imaginary <code>iterable</code> type that could accept maps, slices, arrays, and iterators. As I understand it, iterators combined with <a href="https://pkg.go.dev/slices@master#All"><code>slices.All</code></a> and <a href="https://pkg.go.dev/maps@master#All"><code>maps.All</code></a> are the blessed alternative to that. It works, I'm using it, but I don't love it.</p>
<p>All that's neither here nor there, however, and not what I'm here to talk about.</p>
<p>The part that smells funny to me is that <code>range</code> now accepts any already existing function that happens to fulfill <a href="https://pkg.go.dev/iter#Seq">either of the following interfaces</a>.</p>
<pre><code class="language-go">func(func(any) bool)
func(func(any,any) bool)</code></pre>
<p>It would be less weird if that wasn't also a little troublesome.</p>
<h2>Example time!</h2>
<p>The first interface, known as <a href="https://pkg.go.dev/iter@master#Seq"><code>Seq</code></a> takes a <code>yield</code> method as an argument that itself accepts any single parameter. The second interface, known as <a href="https://pkg.go.dev/iter@master#Seq2"><code>Seq2</code></a> similarly takes a <code>yield</code> method that accepts any two parameters as essentially a key/value tuple. </p>
<p>Note that <code>yield</code> is not a magic keyword as in many languages, but a passed-in callable. This is very akin to Go allowing you to name receivers rather than magically defining <code>this</code>.</p>
<p>At first blush to the uninitiated, this iterator looks fine. It seemingly <a href="https://go.dev/play/p/xL4fNb0CLDF">runs fine</a> as well.</p>
<pre><code class="language-go">package main

import "fmt"

func mightBeAnIterator(f func(string) bool) {
    f("foo")
    f("bar")
    f("baz")
}

func main() {
    for x := range mightBeAnIterator {
        fmt.Println(x)
    }
}
</code></pre>
<pre><code class="language-console">foo
bar
baz</code></pre>
<h2>What's the problem then?</h2>
<p>The problem is iterators MUST handle early exit of the loop manually. With the current interface fulfilling &quot;iterator&quot;, if we just add what seems a harmless <code>break</code> to our loop as follows, suddenly the code working moments ago panics.</p>
<pre><code class="language-go">    for x := range mightBeAnIterator {
        fmt.Println(x)
        break
    }</code></pre>
<pre><code class="language-console">foo
panic: runtime error: range function continued iteration after function for loop body returned false</code></pre>
<p>Somewhat surprisingly, as Go exits the loop, it allows the iterator to continue executing. In doing so, however, it signals the iterator through the result of the <code>yield</code>ing method that it should return. This is to allow for teardown. For example, imagine an iterator that opens files or a socket; continuing execution allows it to close them before returning.</p>
<p>However, Go will panic if the signal to return is not honored and the <code>yield</code> function is then invoked again by the iterator.</p>
<p>To prevent our example iterator from panic-ing, we simply have to listen for the signal to exit, as shown below.</p>
<pre><code class="language-go">func mightBeAnIterator(f func(string) bool) {
    if !f("foo") {
        return
    }
    if !f("bar") {
        return
    }
    if !f("baz") {
        return
    }
}</code></pre>
<p>And <a href="https://go.dev/play/p/53k6sUUCyqK">when run</a>, now we receive simply</p>
<pre><code class="language-console">foo</code></pre>
<p>I would imagine the reason for the panic is to prevent iterators from accidentally running wild. </p>
<p>To my personal taste, I would have strongly preferred honoring the exit signal to be <strong>optional</strong>, and following superfluous <code>yield</code> calls being quietly ignored and returning false. </p>
<p>I can certainly imagine cases where an iterator would want to finish regardless of early exits by its consumer. While still possible, it's awkward to implement.</p>
<p>We are certainly in a place where, should they decide to go back on the panic behavior, it would not break any existing code. That's not a terrible place to be.</p>
<p>All this is to say it smells a little funny that such a simple and likely somewhat common interface has this magic range logic available to it.</p>
<p>Expecting the implementers to implement it correctly and <code>panic</code>-ing if they don't smells kind of <a href="https://www.dreamsongs.com/RiseOfWorseIsBetter.html">Worse is Better</a> to me. While one could complicate Go's iterator interface to enforce stricter guarantees around safe iteration, doing so would add unnecessary complexity and detract from Go's philosophy of simplicity. </p>
<p>It's one of the rare occasions where I wish interfaces had to be intentionally met. It's strange to me that anything that smells like an iterator can be passed to <code>range</code>, even if it doesn't implement the intended handshake.</p>
<p>It smells funny, but that's probably OK. It almost certainly falls into the case of &quot;things that will never be a real problem in the real world&quot;. I look forward to the oncoming brave new world of libraries expecting iterators.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/debugging</guid>
      <title>Three-Step Debugging: A&#xA0;Methodical Approach</title>
      <pubDate>Wed, 20 Mar 2024 18:07:18 -0500</pubDate>
      <link>https://donatstudios.com/debugging</link>
      <description><![CDATA[<p>I've been working as a developer for almost twenty years now. In that time I have had the pleasure of helping many developers, and helping them debug code. Some were new to the field, had vastly more experience than me.</p>
<p>While some are naturally somewhat gifted at debugging, it's important to understand that the skill can be learned. In fact, even very experienced developers can struggle with it</p>
<p>I've known developers who  stare at their code trying to reason their way through it. I've known developers who will change things at random until the problem goes away.  I've known developers whose technique I describe as &quot;I've tried nothing and I'm all out of ideas&quot;.</p>
<p>I take a very structured approach, and this is something I've meant to write down for years now. Solving 90% of the of the problems you will face comes down to three simple steps.</p>
<ol>
<li>Identify Outputs</li>
<li>Identify Inputs</li>
<li>Repeat</li>
</ol>
<h2>Identify Outputs</h2>
<p>There is a bug in your code; that much we know. It's causing something to not work as expected. The first step in debugging is to identify what that something is.</p>
<p>Determine what your expected output is, and what the actual output is.</p>
<p>Walk through the call stack to find where the output diverged from expected.</p>
<h2>Identify Inputs</h2>
<p>This step is crucial and often the most challenging.</p>
<p>In an ideal world we'd only be debug pure functional code. The only inputs would be parameters, and all calls within are deterministic.</p>
<p>We don't live in an ideal world.</p>
<p>We often find ourselves debugging code that inherits values and state from elsewhere. This can be self-fulfilling, such code is usually hardest to test and most prone to errors.</p>
<p>Sources of state include, but are not limited to:</p>
<ul>
<li>Members of the containing class or its parents</li>
<li>Non-deterministic values and methods
<ul>
<li>The current time</li>
<li>Random number generators</li>
<li>Potentially flakey network services</li>
<li>Databases</li>
<li>File systems</li>
<li>User input</li>
</ul></li>
<li>Global variables</li>
<li>Environment variables</li>
<li>Configuration files</li>
</ul>
<p>This list is far from exhaustive and meant to give you an idea of the kinds of things you might need to consider.</p>
<p>While debugging, it's important to identify and note all these inputs. We then need to understand their state at the time of the error. This is often where I see developers struggle. Some part of their code can vary and they have failed to identify it.</p>
<p>Ways to identify their state logging, or by using a debugger to inspect the state of the system at the time of the error.</p>
<p>It comes down to a is a matter of personal preference as well as comfort with the tooling.</p>
<p>I know my way around my debugger, and yet I more often than not am inclined to just spit the state to standard out. I find this to be the most effective way of debugging. I know developers who are very good at using debuggers. I also know developers who spend a lot of time trying to make sense of their debugger.</p>
<p>I've never met a developer who is bad at using print statements.</p>
<blockquote>
<p>The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.</p>
</blockquote>
<p>– Brian Kernighan</p>
<h2>Repeat</h2>
<p>After identifying the code's outputs and inputs, use this information to track down the problem. You will need to repeat this process recursively through the call tree.</p>
<p>We work our way through the call stack, identifying the unexpected outputs and inputs as we go. Keep this up and we will locate the root cause.</p>
<h2>Other Thoughts</h2>
<p>Identifying outputs and inputs isn't limited to values of variables. It's about the state of the system as a whole - including where boundaries get crossed.</p>
<p>You need to understand how the system handles crossing boundaries and ensure that the handoff results as expected. Understanding how the system manages crossing these boundaries is crucial for identifying where errors might occur.</p>
<p>You also may need to understand the state of the larger system at the time of the error. &quot;The system&quot; is not limited to the code you're working on. It can also include the state of the database, the state of the file system, the state of the network, cookies, sessions, the end user's browser, etc.</p>
<p>When you've identified the issue, write a test that reproduces it. This confirms you've fixed it and understood it, as well as preventing its recurrence. If you don't already have a strong test suite, regression tests are a great place to start.</p>
<h2>Conclusion</h2>
<p>I hope this guide helps you improve your debugging skills. Debugging is a skill that you can learn and improve over time.</p>
<p>Debugging is not just about fixing errors. It's an opportunity to learn your codebase and how to enhance it.</p>
<p>And remember, careful thought, combined with well-placed print statements, remains the most effective debugging tool.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/ideas</guid>
      <title>Ideas</title>
      <pubDate>Mon, 26 Feb 2024 12:43:34 -0600</pubDate>
      <link>https://donatstudios.com/ideas</link>
      <description><![CDATA[<ul>
<li>A minimal functional language, maybe similar to Excel equations for sharing logic between unrelated languages. Share the same logic between a Go or PHP backend and a JavaScript front end.</li>
<li><code>mtpl</code> minimum viable template language - like mustache but with less gotchas</li>
<li>A pkg.go.dev style service for PHP / composer packages.</li>
<li>Materialized JSON Parser - generate parsers ahead of time for specific JSON structures to read much faster</li>
<li>Raspberry PI Thermostat</li>
</ul>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/License-Grumbles</guid>
      <title>Releasing my tools under the MIT License was probably a mistake</title>
      <pubDate>Fri, 11 Aug 2023 00:15:30 -0500</pubDate>
      <link>https://donatstudios.com/License-Grumbles</link>
      <description><![CDATA[<p>For basically as long as I can remember, I have been a staunch defender of permissive licenses over the more copyleft variety. I <em>want</em> you to use things I've written. On top of that I don't believe it's my place to force you to then open source things you have written that expand upon my source code. Hence my extensive use of the MIT License.</p>
<p>The MIT License is very simple. It basically says you can do whatever you want with the code, but you have to include the license and copyright when redistributing the code itself. It does not however require you to open source any modifications you make to the code, nor provide attribution to the non-code products of the work.</p>
<p>With my libraries, this has served me very well. It's great, people use and contribute fixes back, and often donate back to me. I love it. I want you to use my code in your proprietary software. I open sourced it so you could do that. I specifically build open source libraries because I solved a problem I thought other people might face and wanted to save them time. Lifting the sea for all boats and all that. I have zero problem continuing to use the MIT License on my libraries.</p>
<p>I also open sourced the tools I host on my website, including but not limited to <a href="https://donatstudios.com/PixelCircleGenerator">Pixel Circle / Oval Generator</a> and <a href="https://donatstudios.com/RewriteRule_Generator">Batch RewriteRule Generator</a>. The MIT License seemed like the natural choice because I've been using it for everything I've released since the early 2000's.</p>
<p>I'd considered these as just <em>part of my website</em> far more so than something I thought people would reuse. My intentions open sourcing these tools, unlike my libraries, was to promote the community helping me to improve them - and they certainly have done so. I have gotten so much great feedback and contributions to these tools, and I am very happy with that. Massive shout out to <a href="https://github.com/Tyson453">Tyson453</a> for his insanely awesome work improving the speed of the Pixel Circle / Oval Generator.</p>
<p>Against my well meaning intentions however, websites re-hosting my tools have been popping up like weeds. In some cases, they are even beating me in search results for my own tools. With noted exception, they don't credit me as the author or provide any sort of link back. Many of them have made minor or major modifications to the tools, and next to none provide the source to those modifications. A number of them even have the gall to post links advertising them in the comments of my own tools.</p>
<p>Most irksome of all, in a fair number of cases they sit centrally on pages covered in ads and SEO keywords. My tools are being associated with a genuinely bad user experience.</p>
<p>This is all perfectly legal and allowed under the MIT License. That's on me. It just is not in the spirit of what I intended. I didn't have the foresight to see this coming. I didn't think people so lacked in the spirit of open source. I wanted to promote community contributions, not to have them monetized by other people who <strong>don't even provide the source to their modifications</strong>. I wanted to grow the tools as a community, not have closed source forks of them overtake my own open source versions.</p>
<p>There's not a lot I can do. I could try to contact the owners of these sites and ask them to provide attribution, but there's no obligation for them to do so. I could try to contact the owners of these sites and ask them to provide the source to their modifications, but again there is no obligation for them to do so. I really wish to see the changes shared alike, and the vain part of me would like attribution as the original author. At the end of the day though I chose the MIT License and it is what it is. It simply does not require any of that.</p>
<hr />
<p>On top of everything else, until very recently and for completely unclear reasons, my website Donat Studios was completely blocked from Bing search results. This in turn meant I didn't show up in DuckDuckGo or Yahoo results either. This meant that for a time the only results when searching these sites for my tools were these re-hosted versions.</p>
<p>I actually managed to reach a human at Bing and get this resolved, but it took a lot of work and I'm still not sure why it happened in the first place.</p>
<hr />
<p>I am considering relicensing my tools under some sort of Attribution-ShareAlike license similar to the <a href="https://creativecommons.org/licenses/by-sa/3.0/">BY-SA</a> the content on this site is licensed under. It would not apply to current and past versions of the tools as that code is licensed MIT and you can't un-ring that bell. It would however apply to future versions. This would still promote community contributions, but would also require that any modifications be shared alike meaning that the modified source would have to be provided and I would have to be credited as the original author.</p>
<p>At the very least I will definitely think twice about the license I choose for future tools. I don't want to be a jerk about it, but I also don't want to see them monetized by others without even a link back to my website.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/TypeScriptTimeoutTrouble</guid>
      <title>Trouble with TypeScript Timeouts: How to Fix It</title>
      <pubDate>Wed, 28 Dec 2022 14:46:19 -0600</pubDate>
      <link>https://donatstudios.com/TypeScriptTimeoutTrouble</link>
      <description><![CDATA[<p>The <strong>tl;dr</strong> is this:</p>
<p>You can fix the problem by replacing <code>number</code> with <code>ReturnType&lt;typeof setTimeout&gt;</code> </p>
<p>Similarly you can replace <code>number</code> with <code>ReturnType&lt;typeof setInterval&gt;</code> if you are seeing the problem with <code>setInterval</code>.</p>
<p>If you want to know more about it, read on.</p>
<hr />
<p>I've run into this problem a number of times where I'm developing frontend TypeScript and after I import a package, I suddenly find throughout my project I'm receiving:</p>
<pre><code class="language-console">error TS2322: Type 'Timeout' is not assignable to type 'number'.</code></pre>
<p>If you are working on a Node project rather than a front end project this can also manifest itself as the opposite </p>
<pre><code class="language-console">error TS2322: Type 'number' is not assignable to type 'Timeout'</code></pre>
<p>Usually this happens because some dependency included <code>@types/node</code> under <code>dependencies</code> instead of <code>devDependencies</code>. When that happens, Node's global types leak into your project—even in frontend environments—causing <code>setTimeout</code> to return a <code>Timeout</code> object instead of a <code>number</code>.</p>
<p>The heart of the problem is that <code>setTimeout()</code> and <code>setInterval()</code> in Node API return a <code>Timeout</code> whereas in the HTML DOM Window API, they return an integer. </p>
<p>The ideal solution would be for the offending package to <em>fix the problem</em> themselves, and for you to update to the corrected version of the package. This is a hassle and might not be possible, with the issue going ignored for months if not years - as on Facebook's Jest package <sup><a href="https://github.com/facebook/jest/pull/12544">[1]</a></sup> <sup><a href="https://github.com/facebook/jest/pull/13374">[2]</a></sup>.</p>
<p>For example, consider the following:</p>
<pre><code class="language-ts">let x : number = setTimeout(() =&gt; console.log("foo"), 1000);</code></pre>
<p>Your immediate reaction might be to either change the definition to <code>let x : Timeout</code> or eliminate the explicit type definition all together à la</p>
<pre><code class="language-ts">let x = setTimeout(() =&gt; console.log("foo"), 1000);</code></pre>
<p>Both of these options work.</p>
<p>The first option of setting it to <code>Timeout</code> suffers from two issues though. Firstly it's fragile - presumably you're not explicitly including <code>@types/node</code> so the change to the environment is a side effect you're coding around. Should the specific packages you include change, or said package correct this it will break. Second, it's ambiguous. <code>Timeout</code> isn’t a global type—it's specific to Node's type system—so using it assumes a Node environment even if you're working in the browser. That can confuse collaborators or tooling down the line.</p>
<p>The second option of implicit typing will work in a lot of cases just fine. There are many cases where you'd want for instance to define the variable in a higher scope than you are setting its value - as a trivial example</p>
<pre><code class="language-ts">class Foo {
    private x? : number;

    start() {
        this.x = setTimeout(() =&gt; console.log("foo"), 1000);
    }

    stop() {
        clearTimeout(this.x);
    }
}</code></pre>
<p>The answer is frustratingly simple. We can just use the <code>ReturnType</code> utility type to set our type to the return type of setTimeout. We can do this using <code>ReturnType&lt;typeof setTimeout&gt;</code>.</p>
<p>Our examples from above become</p>
<pre><code class="language-ts">let x : ReturnType&lt;typeof setTimeout&gt; = setTimeout(() =&gt; console.log("foo"), 1000);</code></pre>
<p>and</p>
<pre><code class="language-ts">class Foo {
    private x?: ReturnType&lt;typeof setTimeout&gt;;

    start() {
        this.x = setTimeout(() =&gt; console.log("foo"), 1000);
    }

    stop() {
        clearTimeout(this.x);
    }
}</code></pre>
<p>Similarly, if you are using <code>setInterval</code> you can utilize type <code>ReturnType&lt;typeof setInterval&gt;</code> for similar purposes.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Read-User-Files-With-Go-WASM</guid>
      <title>Read Browser File Inputs with Go WebAssembly</title>
      <pubDate>Sun, 20 Mar 2022 22:11:42 -0500</pubDate>
      <link>https://donatstudios.com/Read-User-Files-With-Go-WASM</link>
      <description><![CDATA[<p>I have been <a href="https://github.com/donatj/gojs">playing with WebAssembly</a> since Go added support for it in 2017. </p>
<p>For a very long time now, I've been wanting to be able to accept files from an end user and process them with Go on their local machine. Basically I just want to read files from <em>file inputs</em>. I knew it had to be possible, but could not in all this time find a good walkthrough online. A couple nights ago I set my mind to it and figured it out.</p>
<p>Essentially we need to execute the following JavaScript, but in Go via the JS syscall API.</p>
 
<p>So after transliterating that JavaScript into Go <code>syscall/js</code> calls, we then using <code>js.CopyBytesToGo</code> to pass the given <code>Uint8Array</code> back to our WASM runtime where we can do with the bytes as we please.</p>
<p>Note, given the keyword <code>any</code> here, this example requires Go 1.18 - the <code>syscall/js</code> library is still considered experimental and expect it to break on major go releases.</p>
 
<p>This is as written for brevity and could be improved in code safety in a number of pretty obvious ways.</p>
<p>The <code>iframe</code> below runs the compiled WASM and spits out any file you upload to it, via Go.</p>
<fieldset>
<legend>Example</legend>
 
</fieldset>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/yagni-git-gc</guid>
      <title>git gc - You're not going to need it</title>
      <pubDate>Sat, 13 Nov 2021 04:43:37 -0600</pubDate>
      <link>https://donatstudios.com/yagni-git-gc</link>
      <description><![CDATA[<h2>tl;dr</h2>
<p>I recommend every developer turn off automatic garbage collection in git. It's one of the <a href="https://github.com/donatj/dotfiles/blob/20afd7528810bb7ed4046ca869b84391841dbcba/git/.gitconfig#L17-L18">first things I do</a> setting up a new machine - and you should do so too.</p>
<p>For a happier life, go to your terminal right now and run:</p>
<pre><code class="language-console">$ git config --global gc.auto 0</code></pre>
<p>I promise you won't regret it.</p>
<h2>The full story</h2>
<p>Turn off garbage collection? Surely, I must be crazy, right? Automatic garbage collection is important, right? They wouldn't turn it on by default if it wasn't!? Eh, not really. Not for you. I'm not being sarcastic. You just probably don't work at Meta or Microsoft scale.</p>
<p>Garbage collection for git in most every use case just isn't worth it. That is unless you're very regularly committing <strong>very large files</strong>, and then orphaning those commits. I don't see that happening in most uses cases; garbage just doesn't build up that quickly in normal usage.</p>
<h2>Don't believe me?</h2>
<img src="https://donatstudios.com/assets/75/git-info.png" style="float: right; width: 372px">
<p>I have been working with <em>the same</em> git repo every day for <em>ten solid years</em> with gc turned off. My <code>.git</code> folder is currently <code>554 MB</code> whereas a freshly cloned copy is <code>112 MB</code>. I could be saving a whole <code>442 MB</code> - whoop de doo.</p>
<p>I don't know about you, but I'll happily pay <code>442 MB</code> for 10 years of improved data safety.</p>
<h2>But why turn it off?</h2>
<p>Having it on means detached commits could disappear at any time. It's a roll of the dice.</p>
<p>Turning off automatic GC means that anything you once committed will remain restorable, forever. If a rebase goes badly, you can <strong>always</strong> safely get back to the previous state of a now-orphaned commit. There is zero chance of it ever disappearing.</p>
<p>Basically, turning automatic GC off means any amount of history rewriting is always undoable. </p>
<h3>Example</h3>
<p>A simple call to <code>git reflog</code> will show you all recent commits, including ones that are no longer on any branch.</p>
<pre><code class="language-console">$ git reflog
98a9b6b HEAD@{1}: commit (amend): Increase footer font-size and line-height
edc192c HEAD@{2}: commit: Increase footer line height
0360c78 HEAD@{3}: commit: Update footer color to quiet lighthouse
9231f91 HEAD@{4}: commit: Burn down a bunch of old css
…</code></pre>
<p>In the example above, commit <code>edc192c</code>  became <code>98a9b6b</code> when it was amended. </p>
<p>To undo that <em>amend</em>, I can reset the HEAD of my current branch back to  <code>edc192c</code>.  I have no fear of <code>edc192c</code> ever randomly disappearing before I get around to it.</p>
<hr />
<p>Turning off GC is a safety net that has saved me many times.</p>
<p>Turning off automatic GC means you could safely work on a detached HEAD if you're wild like that. You probably shouldn't do that unless you know exactly what you're doing, but you certainly could.</p>
<h2>And of course…</h2>
<p>Keep in mind, you can always just run GC manually if you feel like a lot of garbage has piled up.</p>
<pre><code class="language-console">$ git gc</code></pre>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Go-v2-Modules</guid>
      <title>Go Modules have a v2+ Problem</title>
      <pubDate>Thu, 10 Sep 2020 01:11:12 -0500</pubDate>
      <link>https://donatstudios.com/Go-v2-Modules</link>
      <description><![CDATA[<p>Go has a problem. Go modules place a strange naming requirement on modules version 2 or greater. Module names on modules v2+ must end in the major version ala <code>…/v2</code>, and communication of this rule has been weak. It's non-obvious, and the community at large does not understand it.</p>
<p>I have seen <em>many</em> very large projects including Google owned projects get it wrong. </p>
<p>I brought the issue up at my local Go meetup, and <em>no one</em> had ever heard about the rule. They were very skeptical the rule existed at all.</p>
<h2>A little history</h2>
<p>For a long time Go did not include a built in method for versioning dependencies. It did include the <a href="https://donatstudios.com/GithubsTotalSecurityFacepalm">sometimes controversial</a> <code>go get</code> method of fetching packages. The <code>go get</code> tool <a href="https://proxy.golang.org/">until recently</a> simply cloned the HEAD of a packages primary branch based on URL and placed it in your <code>$GOPATH</code>. </p>
<p>In what has been oft linked to Go's &quot;extremist position&quot; on backwards compatibility, the expectation was largely that if you published a library, you were expected to maintain compatibility when at all possible. </p>
<p>A plethora of tools popped up over the years to fill this void, <a href="https://labix.org/gopkg.in">gopkg.in</a>, <a href="https://github.com/Masterminds/glide">glide</a>, and even the <em>at one time</em> &quot;official experiment&quot; <a href="https://github.com/golang/dep">dep</a>.</p>
<p><code>dep</code> was the <em>heir apparent</em> to dependency management with backing from Google. </p>
<p>Seemingly suddenly however Russ &quot;rsc&quot; Cox sprung <code>vgo</code> (aka Go Modules) <a href="https://research.swtch.com/vgo-intro">onto the world</a> as the official Go solution. There was seemingly no input from the community in its creation, to the chagrin of many. </p>
<p>Whereas <code>dep</code> was a largely <em>standard</em> dependency manager in the vein of <code>npm</code> and the like, Go modules is a decidedly opinionated &quot;Go&quot; solution. It's handled as part of the language as a build step. When you <code>go build</code> if you are missing the versioned dependency, the build tool will fetch it.</p>
<p>The dependencies requirements are listed in <code>go.mod</code> and the exact versions expected  are in <code>go.sum</code>. Both files are in their own sort of domain specific language. The prior has a syntax reminiscent of Go itself. The latter is a space separated list of dependency versions largely not intended for human consumption.</p>
<p>The build step will add to these files as necessary, and running <code>go mod tidy</code> will clean them up.</p>
<h2>The v2+ problem in detail</h2>
<p>Go modules place a very strange requirement on package developers. When a module hits major version 2 or higher, the module name <strong>must</strong> end in the major version. The advantage to this is it creates a separate package. The result is that a project can now depend on multiple major versions of the same library.</p>
<p>There are two tactics to achieving <code>/v2</code> modules:</p>
<p><strong>The first method</strong> is to change the module name in your <code>go.mod</code></p>
<p>An example may seen
in the <code>go.mod</code> of <a href="https://github.com/google/go-github/blob/34cb1d623f03e277545da01608448d9fea80dc3b/go.mod#L1">github.com/google/go-github</a> seems simple enough, but requires you to also change any cross references within your package. While not the end of the world, it is error prone and <a href="https://github.com/google/go-github/blob/34cb1d623f03e277545da01608448d9fea80dc3b/scrape/apps.go#L20">even <em>Google</em> missed some</a>. </p>
<p>The biggest problem with this method is that it breaks subpackages in non-module-aware versions of Go. As time goes by this becomes less important, but in older versions of Go the package name had to exactly match it's location. <code>https://github.com/pkg/foo</code> could not be <code>github.com/pkg/foo/v2</code> when there is no v2 directory. The Go Team acknowledging this trouble and patched rudimentary module support into point releases 1.9.7 and 1.10.3 of the Go language to help ease the transition.</p>
<p><strong>The second method</strong> and the method the Go Team themselves promote is leaving your <code>v1</code> project <em>as is</em> in the root of your repo, and actually creating a <code>v2/</code> subfolder containing your <code>v2</code> library. </p>
<p>This feels weird, but the advantage is it works cleanly with older versions of Go that do not understand modules. Off the top of my head I only knew of a single project that went this route, and they appear to have gone back on it.</p>
<p>For what it's worth, the official Go Blog tried to clear the situation up some, and posted a write-up about it. </p>
<p><a href="https://blog.golang.org/v2-go-modules">blog.golang.org/v2-go-modules</a></p>
<p>Even as a seasoned developer, I found this write-up somewhat impenetrable. For how important and unusual of a requirement this is, the write-up is not accessible enough.</p>
<p>Many projects including Buildkite's <code>terminal-to-html</code> simply didn't know about the requirement until they <a href="https://github.com/buildkite/terminal-to-html/issues/74"><em>forget to handle it</em></a>, and end up publishing broken releases. A v2+ Go library with a <code>go.mod</code> file, but not respecting the module v2+ rules is a compile time error for dependants. This is worse for users than not being a Go module at all.</p>
<p>Some projects like GORM sidestep the issue entirely by <a href="https://gorm.io/docs/v2_release_note.html#Install:~:text=%2F%2F%20**NOTE**%20GORM%20%60v2.0.0%60%20released%20with%20git%20tag%20%60v1.20.0%60">tagging their 2.0 releases as far flung 1.x releases</a>. That something of a solution, but smells terrible.</p>
<h2>What's to be done?</h2>
<p>First, I think the Go Team needs to do a better job of shining a light on this rule. They need to scream it from the rooftops. They need at the very least a section of the documentation that lays it out <strong>clearly and in layman's terms</strong>.</p>
<p>Beyond this, there's room for warnings from the Go tooling. Subpackages importing older versions of the root package should throw a warning. It's way too easy an issue to miss.</p>
<p>There's room for similar notes on <a href="https://godoc.org/">godoc.org</a> / <a href="https://pkg.go.dev/">pkg.go.dev</a>, although the usefulness is questionable.</p>
<p>The best and least likely option would be for Go to drop <em>the requirement</em> and make it optional. I believe this is a workable solution. Packages who want to allow users to include multiple versions can follow the <code>v2</code> guidelines, and others don't need to.</p>
<p>An option I would personally promote, but is likely never going to happen, is to require <code>/v0</code> and <code>/v1</code> as well, such that you'd hit the issue right away rather than years into a project. Teach the convention early.</p>
<hr />
<p>Discussion at: <a href="https://news.ycombinator.com/item?id=24429045">news.ycombinator.com/item?id=24429045</a></p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/gostring</guid>
      <title>Text to "Go String" Tool</title>
      <pubDate>Sat, 08 Aug 2020 01:14:19 -0500</pubDate>
      <link>https://donatstudios.com/gostring</link>
      <description><![CDATA[<p>Go <a href="https://github.com/golang/go/issues/32590">currently lacks</a> any sort of heredoc or similar syntax, and it's backtick syntax cannot contain backticks. This can make correctly encoded creating go strings, particularly for large strings that contain many backticks, a bit of a challenge.</p>
<p>Here's a tool to safely encode any given input to Go string.  It's written in Go using Go's own string encoder and compiled with WASM.</p>
<p>The code as well as a CLI version of it are available on <a href="https://github.com/donatj/gostring">GitHub</a>.</p>
<fieldset>
 

<center><small><a href="https://github.com/donatj/gostring" target="_blank">Fork my source on GitHub!</a></small></center>
</fieldset>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/GifAsZoomBackground</guid>
      <title>Use a GIF as a Zoom Virtual Background</title>
      <pubDate>Tue, 12 May 2020 16:23:38 -0500</pubDate>
      <link>https://donatstudios.com/GifAsZoomBackground</link>
      <description><![CDATA[<p><strong>Update August 17 2021</strong>: I've got the tool working in Chrome and Firefox by launching it a standalone new window instead of loading in an iframe. It is still broken in Safari for unknown reasons. </p>
<p>It's not ideal, but until the whole <a href="https://developer.chrome.com/blog/enabling-shared-array-buffer/">SharedArrayBuffer debacle</a> settles down, it's probably the best I can do right now.</p>
<p><strong>Update October 17 2022</strong>: The mp4's should now always be compatible with QuickTime. While they were always compatible with Zoom, occasionally not being compatible with QuickTime was confusing some people.</p>
<hr />
<p>Stuck in boring Zoom meetings? Have the perfect gif you want to use as your &quot;Zoom Virtual Background&quot;, but Zoom won't let you use animated gifs?</p>
<p>Here's a tool to convert GIFs to MP4's that Zoom will accept!</p>
<!-- iframe frameborder="0" src="https://donatstudios.com/assets/71/" style="width:100%;height:100px" sandbox="allow-scripts"></iframe -->
<p><a style="text-align: center; display: block" href="https://donatstudios.com/assets/71/" target="_blank"><button>Launch Tool</button></a></p>
<hr />
<p>Previously, this page was just instructions for using <code>ffmpeg</code> yourself. The tool uses ffmpeg for you via WASM to make it easy.</p>
<blockquote>
<p>There's an easy solution - using <code>ffmpeg</code> to convert your GIF to an MP4.</p>
<p>You can install <code>ffmpeg</code> with <code>brew</code> or other package manager if you're on a UNIX-like system. Otherwise downloads are available <a href="https://www.ffmpeg.org/download.html">here</a>.</p>
<p>Once you have <code>ffmpeg</code> installed just run the following in your terminal, replacing <code>&lt;gif-file.gif&gt;</code> with the path to your gif.</p>
<pre><code class="language-console">ffmpeg -i &lt;gif-file.gif&gt; -vf scale=w=640:h=360:force_original_aspect_ratio=increase output.mp4</code></pre>
<p>The flags on the command specify a minimum size of 640 by 360, Zoom's minimum video size, while maintaining aspect ratio by increasing either dimension.</p>
</blockquote>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Golang-Binary-Sizes-Part-2</guid>
      <title>Go Binary Sizes Have Been Pretty Stable</title>
      <pubDate>Wed, 17 Jul 2019 19:55:21 -0500</pubDate>
      <link>https://donatstudios.com/Golang-Binary-Sizes-Part-2</link>
      <description><![CDATA[<p><strong>Update October 2nd 2024</strong>: This was updated to include Go versions all the way up to 1.23</p>
<p>In 2013 I wrote an unintentionally inflammatory post: <a href="https://donatstudios.com/Golang-Binary-Sizes">Go Binary Sizes Are Growing out of Control</a>. I had imagined no one would read it, but it took off. I wrote it because I noticed my binaries getting larger as I upgraded versions of Go and found it curious.</p>
<p>Much has changed since then; biggest of all there have a handful of major releases of Go in that time.</p>
<p>I wanted to find out how much this had changed since my post went up. I used Docker to automate the process of compiling against major releases of Go. The exact code I used is on <a href="https://github.com/donatj/gobinsize">GitHub</a>.</p>
<p>I hit a couple of hurdles. The oldest official version of Go on Docker Hub is 1.2 - which happens to be the version my previous post left off. Also, many of the programs from the previous comparison no longer compile in such old versions of Go.</p>
<p>Thus, consider this a standalone comparison rather than a <em>direct</em> continuation of the previous post.</p>
<p>I ended up going through most of my Go projects to find what would actually compile in ancient versions of Go.</p>
<p>The tools I compiled for the comparison are in order </p>
<ul>
<li><a href="https://github.com/donatj/gobinsize/tree/master/gopath/src/hello">hello world</a> using <code>fmt</code></li>
<li><a href="https://github.com/donatj/gobinsize/tree/master/gopath/src/hello-nofmt">hello world</a> using println</li>
<li><a href="https://github.com/donatj/hookah">Hookah</a> - a tool for GitHub automation</li>
<li><a href="https://github.com/donatj/imgavg">imgavg</a> - a tool for averaging a collection of images</li>
<li><a href="https://github.com/donatj/sqlread">sqlread</a> - a tool for querying MySQL dumps without loading them into a database.</li>
</ul>
<h2>The Results</h2>
<p>Without further ado, the raw data in megabytes.</p>
<div style="overflow:auto">

<table style="white-space: nowrap; text-align: right" cellpadding="5">
<thead>
<tr>
<th>binary</th>
<th>1.2</th>
<th>1.3</th>
<th>1.4</th>
<th>1.5</th>
<th>1.6</th>
<th>1.7</th>
<th>1.8</th>
<th>1.9</th>
<th>1.10</th>
<th>1.11</th>
<th>1.12</th>
<th>1.13</th>
<th>1.14</th>
<th>1.15</th>
<th>1.16</th>
<th>1.17</th>
<th>1.18</th>
<th>1.19</th>
<th>1.20</th>
<th>1.21</th>
<th>1.22</th>
<th>1.23</th>
</tr>
</thead>
<tbody>
<tr>
<td>hello-nofmt</td>
<td>0.577</td>
<td>0.475</td>
<td>0.635</td>
<td>1.076</td>
<td>1.101</td>
<td>1.003</td>
<td>0.960</td>
<td>1.035</td>
<td>1.086</td>
<td>1.068</td>
<td>1.127</td>
<td>1.149</td>
<td>1.234</td>
<td>1.224</td>
<td>1.216</td>
<td>1.159</td>
<td>1.155</td>
<td>1.203</td>
<td>1.236</td>
<td>1.285</td>
<td>1.377</td>
<td>1.519</td>
</tr>
<tr>
<td>hello</td>
<td>2.240</td>
<td>1.824</td>
<td>1.941</td>
<td>2.367</td>
<td>2.288</td>
<td>1.634</td>
<td>1.552</td>
<td>1.860</td>
<td>2.012</td>
<td>1.907</td>
<td>1.997</td>
<td>2.009</td>
<td>2.068</td>
<td>2.035</td>
<td>1.937</td>
<td>1.772</td>
<td>1.764</td>
<td>1.814</td>
<td>1.852</td>
<td>1.806</td>
<td>1.894</td>
<td>2.129</td>
</tr>
<tr>
<td>hookah</td>
<td>8.352</td>
<td>6.737</td>
<td>6.868</td>
<td>7.684</td>
<td>8.739</td>
<td>6.391</td>
<td>6.553</td>
<td>6.805</td>
<td>7.311</td>
<td>7.282</td>
<td>8.234</td>
<td>8.289</td>
<td>8.315</td>
<td>7.191</td>
<td>6.907</td>
<td>6.848</td>
<td>7.014</td>
<td>7.260</td>
<td>7.477</td>
<td>7.620</td>
<td>7.892</td>
<td>8.464</td>
</tr>
<tr>
<td>imgavg</td>
<td>3.793</td>
<td>3.040</td>
<td>3.089</td>
<td>3.607</td>
<td>3.560</td>
<td>2.736</td>
<td>2.529</td>
<td>2.656</td>
<td>2.854</td>
<td>2.766</td>
<td>2.891</td>
<td>2.920</td>
<td>2.978</td>
<td>2.887</td>
<td>2.802</td>
<td>2.569</td>
<td>2.558</td>
<td>2.629</td>
<td>2.676</td>
<td>2.619</td>
<td>2.748</td>
<td>2.970</td>
</tr>
<tr>
<td>sqlread</td>
<td>4.730</td>
<td>3.648</td>
<td>3.739</td>
<td>4.242</td>
<td>4.192</td>
<td>3.170</td>
<td>3.043</td>
<td>3.091</td>
<td>3.284</td>
<td>3.144</td>
<td>3.302</td>
<td>3.316</td>
<td>3.373</td>
<td>3.265</td>
<td>3.149</td>
<td>2.989</td>
<td>2.968</td>
<td>3.057</td>
<td>3.110</td>
<td>3.171</td>
<td>3.291</td>
<td>3.538</td>
</tr>
</tbody>
</table>

</div>
<p>&nbsp;</p>
<img src="https://donatstudios.com/assets/70/chart-2024.svg" alt="Chart of Go Binary File Sizes" style="width:100%; height: auto">
<h2>Summary</h2>
<p>The trendlines show the majority of the projects have a small downtick in size. The Go 1.5 and Go 1.6 releases caused a pretty universal uptick which 1.7 seems to have largely mitigated.</p>
<p>Hookah shows by far the most fluctuation in size by a pretty large margin. This is interesting because comparing dependencies the <strong>only</strong> builtin packages Hookah uses that sqlread does not are <code>regexp</code> and <code>regexp/syntax</code>. <code>sqlread</code> itself actually utilizes a pretty large number more. This could indicates the variation is deeper than the changes to the built in libraries, and potentially more to do with the compilation output and optimization.</p>
<p><em>Everything</em> except for the simplest &quot;Hello World&quot; <code>hello-nofmt</code> has shown an overall decrease.  <code>hello-nofmt</code> boils down to <code>println("Hello, 世界")</code> has shown an overall 658kb increase. This is likely the purest illustratration the growth in size of the runtime. </p>
<h2>Conclusion</h2>
<p>Go binaries are <strong>not</strong> &quot;growing out of control&quot;. They're pretty stable and for the most part have shown a decrease.</p>
<p>I'm still hopeful future optimizations will help improve sizes. Given WASM becoming a major compilation target, size matters more than ever as it directly affect load times.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Local-Certificate-On-macOS-Apache</guid>
      <title>Using mkcert to Set Up Local SSL on macOS's Built in Apache</title>
      <pubDate>Tue, 29 Jan 2019 18:16:47 -0600</pubDate>
      <link>https://donatstudios.com/Local-Certificate-On-macOS-Apache</link>
      <description><![CDATA[<p>A little under a year ago I wrote a walkthrough: <a href="https://donatstudios.com/Self-Signed-Certificate-On-macOS-Apache">Set up a Self Signed Certificate on macOS's Built in Apache</a>.</p>
<p>Recently a new tool showed up called <a href="https://blog.filippo.io/mkcert-valid-https-certificates-for-localhost/">mkcert</a> has come and made life way easier.</p>
<p>It's a wonderful tool that <strong><em>instead</em></strong> of building Self-Signed Certificates, sets your local machine up with a certificate signing authority and creates valid certificates. </p>
<p>You can read more about how it works at the link above. Needless to say, the process is much less painful than creating self signed certificates.</p>
<h2>Installing <code>mkcert</code></h2>
<p>The easiest way to install <code>mkcert</code> is via <a href="https://brew.sh/">Homebrew</a>.</p>
<p>Beyond <code>mkcert</code> we should also install <code>nss</code> which helps <code>mkcert</code> set up Firefox to respect its certificates.</p>
<pre><code class="language-console">$ brew install mkcert nss</code></pre>
<p>Once that's complete, we need <code>mkcert</code> to set itself up as a signing authority on our local machine.</p>
<pre><code class="language-console">$ mkcert -install</code></pre>
<h2>Creating the Certificates</h2>
<p>Once we have <code>mkcert</code> set up, the next step is to generate the certificates for the intended local domain(s). </p>
<p>You may include multiple domains or wildcard subdomains. You can find more information about <code>mkcert</code>'s capabilities <a href="https://blog.filippo.io/mkcert-valid-https-certificates-for-localhost/">here</a>.</p>
<pre><code class="language-console">$ mkdir /tmp/crt &amp;&amp; cd /tmp/crt
$ mkcert local.example.com</code></pre>
<p>You should get an output something like as follows</p>
<pre><code>Using the local CA at "/Users/user/Library/Application Support/mkcert" ✨

Created a new certificate valid for the following names ????
 - "local.example.com"

The certificate is at "./local.example.com.pem" and the key at "./local.example.com-key.pem" ✅</code></pre>
<p>Now that we have our certificate, we can move it into the apache /etc directory:</p>
<pre><code class="language-console">$ sudo mkdir /etc/apache2/ssl
$ sudo mv *.pem /etc/apache2/ssl</code></pre>
<h2>Apache Configuration</h2>
<p>Next, all we need to do is configure Apache. </p>
<p>Follow the <code>Apache Configuration</code> section's instructions, <strong>stopping</strong> and returning here before the &quot;Chrome / Safari Configuration&quot; section on <a href="https://donatstudios.com/Self-Signed-Certificate-On-macOS-Apache#apache-configuration">my previous post</a>.</p>
<p>When you reach the portion about configuring your VirtualHosts, the only difference is the filenames will look more akin to this pattern: </p>
<pre><code>  SSLEngine on
  SSLCertificateFile "/private/etc/apache2/ssl/local.example.com.pem"
  SSLCertificateKeyFile "/private/etc/apache2/ssl/local.example.com-key.pem"</code></pre>
<p>Unlike the prior walkthrough which restarted Apache with a reboot, we instead now only need to restart Apache.</p>
<pre><code class="language-console">$ sudo apachectl restart</code></pre>
<p>And we're done! Unlike setting up a self signed certificate, we do not need to add anything to our keychain or browsers. We are ready to go, your certificate should work as expected.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/MojaveMissingHeaderFiles</guid>
      <title>Fix Missing Headers Compiling on macOS Mojave</title>
      <pubDate>Tue, 02 Oct 2018 16:22:40 -0500</pubDate>
      <link>https://donatstudios.com/MojaveMissingHeaderFiles</link>
      <description><![CDATA[<fieldset>
<legend>Update</legend>

It would appear that the latest version of Xcode has removed the macOS_SDK_headers_for_macOS_10.14.pkg file. I am currently searching for alternative solutions.

</fieldset>
<p>While trying to compile PHP extensions after installing macOS Mojave, I was butting up against a ton of missing header file errors including <code>php.h</code> and zlib.</p>
<p>After several hours of beating my head against my desk and Googling/Stack Overflowing I found the solution.</p>
<p>Ends up you just need to force re-install the header files. Special thanks to <a href="https://github.com/Homebrew/homebrew-core/issues/29176#issuecomment-398656987">@sfdye</a> for the solution!</p>
<pre><code class="language-console">$ sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /</code></pre>
<p>If you get an error about that file not existing, you probably simply don't have the Xcode command line tools installed which can be remedied simply as follows:</p>
<pre><code class="language-console">$ xcode-select --install</code></pre>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Self-Signed-Certificate-On-macOS-Apache</guid>
      <title>Set up a Self Signed Certificate on macOS's Built in Apache</title>
      <pubDate>Tue, 15 May 2018 21:42:51 -0500</pubDate>
      <link>https://donatstudios.com/Self-Signed-Certificate-On-macOS-Apache</link>
      <description><![CDATA[<p><strong>Update 2019-01-29:</strong> A much simpler method using a tool called <code>mkcert</code> has come along and I have an <a href="https://donatstudios.com/Local-Certificate-On-macOS-Apache">updated walkthrough</a> you may want to use instead. I would recommend the <code>mkcert</code> method unless you have a reason you explicitly need a self signed certificate.</p>
<hr />
<p>I recently purchased a <code>.app</code> domain for a side project; <code>.app</code> domains are interesting as they <strong>require</strong> SSL at <em>all times</em>. </p>
<p>I like to set up my dev environments as subdomains of my actual domain, such as <code>local.donatstudios.com</code>. It makes working with CORS and cookies a lot simpler for applications with APIs on subdomains.</p>
<p>Doing this with <code>.app</code> however means having to get SSL working. I found a number of tutorials of varying quality online, but none of them did the full process from start to finish, particularly solving problems with Chrome 58.</p>
<p>This tutorial assumes you are using the built in version of Apache that comes with macOS, however you should be able to adjust paths for <code>brew</code> or otherwise installed versions.</p>
<h2>Creating the Certificates</h2>
<p>First off we'll make place to do our work. We need to do this outside of sudo requiring areas due to some bugs with the Apple version of openssl.</p>
<pre><code class="language-console">$ mkdir /tmp/crt &amp;&amp; cd /tmp/crt</code></pre>
<p>Next we will generate your key and certificate. Replace all four <code>local.example.com</code> entries with your full intended development subdomain. </p>
<p>The <code>subjectAltName=DNS</code> piece of this is required to get Chrome versions greater than 58 working.</p>
<pre><code class="language-console">$ openssl req -newkey rsa:2048 -x509 -nodes \
    -keyout local.example.com.key \
    -new \
    -out local.example.com.crt \
    -subj /CN=local.example.com \
    -reqexts SAN \
    -extensions SAN \
    -config &lt;(cat /System/Library/OpenSSL/openssl.cnf \
        &lt;(printf '[SAN]\nsubjectAltName=DNS:local.example.com')) \
    -sha256 \
    -days 3650</code></pre>
<p>Apache requires a <code>nopass</code> key, so to generate this we do the following, again adjusting paths as nessessary:</p>
<pre><code class="language-console">$ sudo openssl rsa -in local.example.com.key -out local.example.com.nopass.key</code></pre>
<p>Now that we have completed the key generation, we can move our certificates into the apache <code>/etc</code> directory:</p>
<pre><code class="language-console">$ sudo mkdir /etc/apache2/ssl
$ sudo mv *.key *.crt /etc/apache2/ssl</code></pre>
<h2 id="apache-configuration">Apache Configuration</h2>
<p>In your favorite editor open <code>/etc/apache2/httpd.conf</code></p>
<p>Uncomment the following lines (line numbers may vary):</p>
<p>Near line 89:</p>
<pre><code>LoadModule socache_shmcb_module libexec/apache2/mod_socache_shmcb.so</code></pre>
<p>Near line 144:</p>
<pre><code>LoadModule ssl_module libexec/apache2/mod_ssl.so</code></pre>
<p>Near line 528:</p>
<pre><code>Include /private/etc/apache2/extra/httpd-ssl.conf</code></pre>
<p>Next open <code>/etc/apache2/extra/httpd-ssl.conf</code>, find <code>&lt;VirtualHost _default_:443&gt;</code> around line 121.</p>
<p>Either <strong>comment out</strong> using a <code>#</code> at the start of each line, or <strong>delete</strong> <em>every single line</em> between the opening <code>&lt;VirtualHost _default_:443&gt;</code> and the closing <code>&lt;/VirtualHost&gt;</code> around line 290.</p>
<p>Presuming your sites VirtualHost is in <code>/etc/apache2/extra/httpd-vhosts.conf</code> either change <code>&lt;VirtualHost *:80&gt;</code> to <code>&lt;VirtualHost *:443&gt;</code>, or duplicate your VirtualHost entry using <code>&lt;VirtualHost *:443&gt;</code> if you'd like your VirtualHost accessible with and without SSL.</p>
<p>Next, inside your sites <code>&lt;VirtualHost *:443&gt;</code> block, append the following, adjusting the filenames accordingly.</p>
<pre><code>  SSLEngine on
  SSLCertificateFile "/private/etc/apache2/ssl/local.example.com.crt"
  SSLCertificateKeyFile "/private/etc/apache2/ssl/local.example.com.nopass.key"</code></pre>
<h2>Chrome / Safari Configuration</h2>
<p>Next we will open your Apache ssl directory and the &quot;Keychain Access&quot; application:</p>
<pre><code class="language-console">$ open /etc/apache2/ssl
$ open /Applications/Utilities/Keychain\ Access.app</code></pre>
<p>In the Keychain Access application, select the <code>login</code> <em>Keychain</em> and the <code>Certificates</code> <em>Category</em>.</p>
<p>Then from the open Finder window, drag and drop your <code>.crt</code> file.</p>
<img src="https://donatstudios.com/assets/65/keychain.png" alt="Keychain Access" style="max-width: 100%;">
<p>Double click, and change the settings for the selected certificate to <strong>&quot;Always Trust&quot;</strong>.</p>
<img src="https://donatstudios.com/assets/65/keychain_trust.png" alt="Keychain Access: Trust Level" style="max-width: 100%;">
<p>Lastly, repeat the process of dragging and giving access in the <code>System</code> <em>Keychain</em>.</p>
<p>You may need to reboot, but your certificate should now work in Chrome and Safari on your local machine.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/GithubsTotalSecurityFacepalm</guid>
      <title>GitHub Shouldn't Allow Username Reuse</title>
      <pubDate>Fri, 09 Feb 2018 21:19:29 -0600</pubDate>
      <link>https://donatstudios.com/GithubsTotalSecurityFacepalm</link>
      <description><![CDATA[<p><strong>Update 2018-04-19</strong> - GitHub has <a href="https://blog.github.com/2018-04-18-new-tools-for-open-source-maintainers/#popular-repository-namespace-retirement">implemented some rules</a> around retiring &quot;namespaces&quot;.</p>
<blockquote>
<p>To prevent developers from pulling down potentially unsafe packages, we now retire the namespace of any open source project that had more than 100 clones in the week leading up to the owner’s account being renamed or deleted. </p>
</blockquote>
<p>This is a decent half step but I'd still love to see either permalinks or <strong>every</strong> namespace of a user permanently retired once their account is deleted.</p>
<p><strong>Update 2018-02-10</strong> In discussing this with people online, I’ve come to the conclusion that the bigger, more important  issue is lack of <strong>permalinks</strong> to repository instances. Path reuse, rather than username reuse.</p>
<hr />
<p>There is a very popular tool for embedding data files into your Go binaries called <code>go-bindata</code>.  Several days ago however the user who ran it, &quot;Jim Teeuwen&quot; (<a href="https://web.archive.org/web/20150609210529/https://github.com/jteeuwen">wayback machine</a>), completely disappeared from the internet – deleting his GitHub account and personal domain in the process.</p>
<p>While this is within his rights to do, this broke a dependency many people had within their projects.</p>
<p>To fix this, some users of the project recreated the <strong>account</strong> and the repository based on a fork of the project. </p>
<p>They have an official announcement/disclosure here:</p>
<p><a href="https://github.com/jteeuwen/go-bindata/issues/5">https://github.com/jteeuwen/go-bindata/issues/5</a> </p>
<p>At the very least they are being honest about it.</p>
<p>The fact that they were <strong>allowed</strong> to do this however represents a fundamental flaw in GitHub's security model.</p>
<p>Usernames, once deleted, should <strong>never</strong> be allowed to be valid again. Many sites including Google do it this way. </p>
<p>Allowing username reuse completely breaks any trust that what I pull is what it claims to be.</p>
<p>What if this user had been malicious? It may have taken a while before someone actually noticed this wasn't the original user and the code was doing something more than it claimed to.</p>
<p>While Go's <code>go get</code> functionality is no doubt naive and just pulls the head of a repository, this is not exclusively Go's problem as this affects any <strong>package manager</strong> that runs on tags. Simply tag malicious changes beyond the current release and it would be deployed to many users likely with little actual review.</p>
<p>This should not be possible. This is scary and should be fixed.</p>
<p><strong>Update</strong></p>
<p>Many people are arguing that this is the developers fault or the package managers fault. I do not agree, but as far as I see it that doesn't matter anyway.  The simple fact of the matter is that it <em>is</em> being used like that, like it or not, and the simplest and I argue most correct fix is for GitHub to prevent the issue.</p>
<p>I think another good option would be GitHub offering permalinks to repos, such that if they were deleted and recreated the pathing would change.</p>
<p>It affects <strong>not only package managers</strong> and programs and software, but humans. Humans navigating GitHub. I have no way to tell while navigating the site if a project is the original or a charade.  That is a problem.</p>
<p>As for the title change, I agreed with Hacker News that the original was a bit hyperbolic. I have a tendency to resort to hyperbole to get my emotional point across - and it doesn't always read correctly on the internet.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/StaticAnalysis</guid>
      <title>Static Analysis: It's Simply Critical</title>
      <pubDate>Sat, 21 Oct 2017 02:59:24 -0500</pubDate>
      <link>https://donatstudios.com/StaticAnalysis</link>
      <description><![CDATA[<p>I am someone who spends large portions of their time working in weakly typed languages. Namely, PHP and JavaScript. I argue that static analysis is a <strong>must-have</strong> when working with any weakly typed language. It is nearly impossible to make reliable code without it. </p>
<p>Static analysis makes up for the missing type system and allows you to scale.</p>
<p>I recently got in an argument with another developer over static analysis. It's been an ongoing point of contention. He was angry that code that <em>works fine</em> but didn't document its undetectable return types was failing CI. He argued that if the code works, it should pass. He argued that if the code works, it is &quot;correct&quot;.</p>
<p>I put forward the exact opposite position. Code that <em>works</em> but doesn't <em>statically analyze</em> is incorrect. Code that is <strong>correct</strong> has no potential of runtime error. </p>
<p>My intentions are not only to sell you on the idea that you should be statically analyzing your code, but to convince you that it is fundamental to responsible reliable development.</p>
<p>Humans are fallible; code written by humans doubly so. By it's very nature that code is broken until <strong>proven otherwise.</strong> If it cannot be statically analyzed, it <strong>cannot be</strong> <strong>proven</strong> correct.</p>
<p>If code <strong>runs</strong> as expected, this only proves it correct in the <em>limited scope</em> it's been ran it within. Be that ones, tens or even hundreds of thousands of parameter combinations, it's always limited. There therefore <strong>must</strong> be zero expectation of it continuing to work.</p>
<p>To the contrary, if code statically analyzes unerring, it will run without runtime error in <strong>all</strong> <strong>cases</strong>, save a failure of the analyzer. This code <strong>is proven</strong>. This code is <strong>correct</strong>.</p>
<p>Code which &quot;works&quot; does nothing to prove that it's used as you expect. In particular elsewhere within the project. It does nothing to prove it will remain correctly used in all cases in the future.</p>
<p>I know what you are thinking &quot;But… but… my code runs correctly within my expected domain&quot;, and that's fine. That is what's expected of it. But throw exceptions if it happen it doesn't know how to handle, things that creep outside the expected domain. It lets the future developers, the analyzer, and the program itself know the intended domain and when it steps outside it. </p>
<p>No amount of fuzz testing can account for another developer doing something you never expected.</p>
<p>To give a trivial but near real life example, consider the following:</p>
<pre><code>function getUserById( userId ) {
    if(userId &gt; 0) {
        return new User($userId);
    }

    return null;
}

var user = getUserById( input.value );
console.log( "Hello userId: " + user.getId() );</code></pre>
<p>The above code may work throughout <strong>all</strong> your testing. Every place you use it, every time you try it, everything your application throws at it - success. </p>
<p>This code <strong>works</strong>, and yet this code <strong>is broken.</strong> This code does not handle the potential <strong>null,</strong> and has the potential to trigger a runtime error. This code has the capability of a runtime error, and is therefore <strong>incorrect</strong>.</p>
<p>Making this code <strong>correct</strong> would be as simple as:</p>
<pre><code>var user = getUserById( input.value );

if(!user instanceof User) {
    throw new Error("User not found");
}

console.log( "Hello userId: " + user.getId() );</code></pre>
<p>An Exception, as opposed to a runtime error, is something the code and coder anticipates. It's an easy way to show you've thought about the domain of your code. Even unhandled, it's clear the developer considered the possibility. It's easily recoverable. Throwing an exception here is <strong>correct</strong>.</p>
<p><em>Obviously you'd probably want to handle that Error in the example somewhere, but that's outside the scope of this writing.</em></p>
<p>The trivial case above is reasonably obvious and easy to spot. Let's instead imagine the function to get a user is deep in your codebase. While we are at it, let's also say that the logical <code>User|null</code> union we've created gets passed around. Passed several levels deep, rather than using it immediately where it's fetched. It rapidly becomes <em>much</em> less obvious, nigh impossible, to notice that there is an issue. A call 8 methods deep has no idea it might be receiving a null. This is <strong>exactly</strong> the kind of thing a static analysis is for.</p>
<p>A static analyzer knows the full scope and range of possible values. It is able to pinpoint problems a human fails to notice. Noting all function calls within the scope of a project and knowing all possible.</p>
<p>All this withstanding, static analysis cannot prove the logic is correct. That's not what static analysis is for. Unit tests and other testing prove that. Static analysis proves that the code itself executes free of unhandled <strong>runtime errors</strong>. It <strong>is</strong> the <strong>type checker your language forgot</strong>.</p>
<p>Arguable the better solution is to switch to a type safe language; one with compile time type checks and ideally union types. That's not always an option for most people though, consider the recent meteoric growth of JavaScript. </p>
<p><a href="https://www.typescriptlang.org/">TypeScript</a> is without a doubt the best option for JavaScript. I highly recommend it — consider it static analysis on steroids. It's JavaScript with the addition of strong typing. There's no reason to be writing straight JavaScript anymore. TypeScript is really a wonderful all in one solution.</p>
<p><a href="https://elm-lang.org/">Elm</a> is a neat alternative but a completely different paradigm. While it compiles to JavaScript, it's completely foreign and more of an all-in situation. It borrows much of its syntax from Haskell, and offers the aforementioned union types. </p>
<p>For PHP on the other hand I recommend a handful of tools. <a href="https://scrutinizer-ci.com/">Scrutinizer</a> is in my experience the most <em>feature complete</em> of the hosted CI services. I make extensive use of <a href="https://github.com/squizlabs/PHP_CodeSniffer">PHP CodeSniffer</a> which is an amazing tool based around a PHP tokenizer. It's a lot of fun writing your own sniffs in my experience. I have begun experimenting with <a href="https://github.com/phan/phan">Phan</a> - it's a little unforgiving, in a good way… And of course <a href="https://blog.jetbrains.com/phpstorm/">PHPStorm</a> has a pretty decent analyzer built in.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Falsehoods-Programmers-Believe-About-CSVs</guid>
      <title>Falsehoods Programmers Believe About CSVs</title>
      <pubDate>Tue, 27 Dec 2016 19:41:04 -0600</pubDate>
      <link>https://donatstudios.com/Falsehoods-Programmers-Believe-About-CSVs</link>
      <description><![CDATA[<p>Much of my professional work for the last 10+ years has revolved around handling, importing and exporting CSV files. CSV files are frustratingly misunderstood, abused, and most of all underspecified. While <a href="https://tools.ietf.org/html/rfc4180" title="RFC4180 Common Format and MIME Type for Comma-Separated Values (CSV) Files">RFC4180</a> exists, it is far from definitive and goes largely ignored.</p>
<p>Partially as a companion piece to my recent post about how CSV is an <a href="https://donatstudios.com/CSV-An-Encoding-Nightmare">encoding nightmare</a>, and partially an expression of frustration, I've decided to make a list of falsehoods programmers believe about CSVs. I recommend my previous post for a more in-depth coverage on the pains of CSVs encodings and how the default tooling (Excel) will ruin your day.</p>
<p><strong>Everything on this list is a false assumption</strong> that developers make.</p>
<ol>
<li>All CSVs are ASCII</li>
<li>All CSVs are Win1252</li>
<li>All CSVs are in 8-bit encodings</li>
<li>All CSVs are UTF-8</li>
<li>All CSVs are UTF-16</li>
<li>All CSVs contains a single consistent encoding</li>
<li>All records contain a single consistent encoding</li>
<li>All fields contain a single consistent encoding</li>
<li>All CSVs contain records</li>
<li>All records contain fields </li>
<li>Fields never contain record separators </li>
<li>Fields never contain delimiters </li>
<li>Fields never contain control characters</li>
<li>Delimiters are escaped with a <code>\</code></li>
<li>All fields are enclosed by double quotes</li>
<li>All records are a single line</li>
<li>All lines contain a single record</li>
<li>All records contain the same number of fields</li>
<li>All records contain the same number of fields as the header</li>
<li>All records contain the same number of fields or fewer than the header</li>
<li>All CSVs contain a header</li>
<li>All record separators are CRLF</li>
<li>All record separators are LF</li>
<li>All record separators are a single byte</li>
<li>All record separators are a single rune</li>
<li>All newlines are a single byte</li>
<li>All CSVs are delimited with a comma</li>
<li>All CSVs are delimited with a comma, tab or semicolon</li>
<li>TSV isn't CSV</li>
<li>All delimiters are a single byte</li>
<li>All commas are a single byte</li>
<li>All CSVs are readable with Excel</li>
<li>Excel is a good tool for working with CSVs</li>
<li>Excel is an OK tool for working with CSVs</li>
<li>Excel can losslessly save CSVs it opens</li>
<li>Using <code>="{value}"</code> is a <em>good</em> way to get around Excel auto-formatting</li>
<li>The first line will never be a poorly supported instruction header</li>
<li>Using <code>sep={char}</code> is a <em>good</em> way to get Excel to accept your delimiter</li>
<li>Prepending a BOM is a good way to get Excel to read your encoding</li>
<li>You can safely name your first column &quot;ID&quot;</li>
<li>All CSVs follow <a href="https://tools.ietf.org/html/rfc4180" title="RFC4180 Common Format and MIME Type for Comma-Separated Values (CSV) Files">RFC4180</a></li>
<li>Most CSVs follow <a href="https://tools.ietf.org/html/rfc4180" title="RFC4180 Common Format and MIME Type for Comma-Separated Values (CSV) Files">RFC4180</a></li>
<li>All CSVs follow the same defined standard</li>
<li>All CSVs follow a defined standard</li>
<li>All CSVs have a <code>.csv</code> extension</li>
<li>All CSV is human readable</li>
</ol>
<p>Please take these into consideration next time you find yourself working with CSV. If you can think of anything I may have missed I'd be happy to add it.</p>
<p>As a suggested further reading, &quot;The Art of Unix Programming&quot; <a href="https://www.catb.org/esr/writings/taoup/html/ch05s02.html#id2901882">http://www.catb.org/esr/writings/taoup/html/ch05s02.html#id2901882</a> section on DSV style which notably says &quot;CSV is a textbook example of how not to design a textual file format&quot;</p>
<h4>Updates:</h4>
<ul>
<li>Thanks to <a href="https://donatstudios.com/Falsehoods-Programmers-Believe-About-CSVs#Comment216545">Max</a> for pointing out the Excel supported <code>sep={value}</code> header I was strangely entirely unaware of.</li>
<li>Thanks to <a href="https://donatstudios.com/Falsehoods-Programmers-Believe-About-CSVs#Comment216547">Don Hopkins</a> for the note about not being able to start a header with ID</li>
</ul>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/CSV-An-Encoding-Nightmare</guid>
      <title>CSV: An Encoding Nightmare</title>
      <pubDate>Mon, 20 Jun 2016 19:17:57 -0500</pubDate>
      <link>https://donatstudios.com/CSV-An-Encoding-Nightmare</link>
      <description><![CDATA[<p><strong>UPDATE 2019-01-16</strong>: In the three years since this article was written, parts of the article, in particular talking about UTF-8 are thankfully no longer accurate.</p>
<p>It would appear in a recent update Microsoft has added support for safely reading and writing UTF-8 CSVs to Excel. There is a new format in the save dialog <code>CSV UTF-8 (Comma delimited)</code> which is distinct from <code>Comma Separated Values</code> which is also still in there. </p>
<p><del>In my testing it appears to safely both load and save. Best of all it appears if there is a BOM, it leaves it, if there isn't it doesn't add one. Very nice handling indeed.</del></p>
<p><strong>UPDATE 2022-07-22</strong>:</p>
<p>It would appear that the stricken above was incorrect. Current versions of Excel will only reliably load a UTF-8 CSV if a UTF-8 BOM is in place.</p>
<hr />
<p>The company I work for manages a self-service data import system, handling information from school districts “Student Information Systems”. There are hundreds if not thousands of SIS’s out there, but what the vast majority have in common is the ability to export CSV. It is the lingua franca, everyone can get us CSVs. </p>
<p>CSV to the casual observer seems a simple portable format, but its looks are deceiving. If the data is pure <a href="https://www.asciitable.com/"><strong>ASCII</strong></a> (bytes 0-127) you’ll be fine. However, if there is a need for any sort of non-ASCII character, there is some work ahead.</p>
<p>In our case, handling school data from around the world, correctly handling non-ASCII characters is of the utmost importance. </p>
<h2>Excel: A World of Hurt</h2>
<p>The biggest problem is not CSV itself, but that the primary tool used to interact with it is Excel. Excel handles CSV encodings badly.</p>
<p>Creating a new document in Excel and saving as ”Comma Separated Values (.csv)” it uses your locale’s Windows or Mac codepage. Win-1252 and MacRoman respectively in the United States. If your codepage doesn’t support a character in your document, it will be silently replaced with an underscore <code>_</code> character.</p>
<p>Because it uses codepages and not a Unicode encoding, it makes processing a painful chore. There is no way to tell the difference between different 8-bit codepages programatically. </p>
<p>One can use heuristics to sort them into an order of likelihood, but there is no way to ever know for sure. We chose to present the user with previews of the most likely codepages, and let them pick the correct one.</p>
<h3>Excel for Mac: Broken Beyond Belief</h3>
<p>The Macintosh version of Microsoft Excel is particularly harrowing. </p>
<p>As I mentioned above, Excel saves your CSV in your locales codepage. One should note that the Mac codepages fell out of use with OS 9, almost 15 years ago. Microsoft did not get that memo.</p>
<p>While that by definition makes the CSVs the Mac version of Excel exports unusable on Windows, the problem is more unfortunate than that. The Mac version can only <strong>read</strong> the locales <strong>Windows</strong> codepage. </p>
<p>That means the Mac version of Excel cannot read CSVs <strong>it</strong> wrote. That's pathetic.</p>
<p>If you had <strong>any extended characters</strong> when you saved, they are scrambled when you reopen it. This problem has persisted in every version of Mac Excel up to the current Excel 2016. </p>
<p>One simply <strong>cannot</strong> safely open a CSV created with the Mac version of Excel, on any platform, anywhere.</p>
<h2>UTF-8? What’s that?</h2>
<p>UTF-8 is the encoding of the 21st Century. It was quick in  <a href="https://en.wikipedia.org/wiki/File:UnicodeGrow2b.png">overtaking every other encoding</a>. <em>Almost</em> every modern application supports it. This will be easy and we’ll be fine? Not so fast. </p>
<p>If one attempts to open a CSV file encoded as UTF-8 <em>without</em> a Byte Order Mark (<a href="https://en.wikipedia.org/wiki/Byte_order_mark">BOM</a>) as <a href="https://utf8everywhere.org/#faq.boms">recommended</a>, any non-ASCII characters are again scrambled.</p>
<p>This is the CSV format Apple’s <em>Numbers</em> exports by default, UTF-8 sans BOM.</p>
<p>If we try it again <strong>with</strong> a UTF-8 BOM prepended to the file and Excel will read it. This is deceptive because once saved the text will remain correctly encoded UTF-8, but bizarrely the <strong>BOM will be stripped</strong> causing the file to no longer be correctly readable.</p>
<p>Many naïve application output UTF-8 + BOM CSVs, and they read correctly but do not write correctly. I’ve seen this cause all sorts of headaches because it appears to work but does not.</p>
<h2>The <strike>Right Way</strike> Functional Workaround</h2>
<p>As of this writing, there exists a <strong>single</strong> usable CSV format that Microsoft Excel can both <em>read</em> <strong>and</strong> <em>write</em> safely across platforms. Tab delimited UTF-16LE with leading Byte Order Mark. </p>
<p>Using Excel to output into this format to begin with, you have to use the Save As dialog and choose “UTF-16 Unicode Text (.txt)”. Frustratingly, it includes a <code>.txt</code> extension by default which can be confusing for non-technical users. However, if you provide clear instructions to Windows users to save their filename in quotes with a CSV extension such as <code>"example.csv"</code> it is workable.</p>
<p>If you operate an application with a CSV exporter <strong>please</strong> use this as your default export format. It will save us all many headaches in the long run.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/IAmDoneWithTheAppStore</guid>
      <title>I Am Done With the Mac App Store</title>
      <pubDate>Thu, 03 Mar 2016 23:44:46 -0600</pubDate>
      <link>https://donatstudios.com/IAmDoneWithTheAppStore</link>
      <description><![CDATA[<p><strong>tl;dr</strong> Do not depend on App Store Apps, Apple can and will pull the carpet out from under you.</p>
<p>Reading <a href="https://news.ycombinator.com/item?id=11219208">Hacker News</a> today there is an article about how <a href="https://tidbits.com/article/16302">OS X Installers</a> stopped working on February 14th, 2016. This is due to an expired certificate Apple used to sign the binaries with.  </p>
<p>In that same time range I had a handful of Apps <strong>purchased</strong> from the App store suddenly and <em>mysteriously</em> stop working. They just quit. No dialog, no error mesasage, nothing at all. Apps I <strong>paid for</strong> and <strong>depend on</strong> just stopped working.</p>
<p>The thing they all these Apps have in common is they are no longer available from the App store and have not been updated in several years. I am nearly certain that this is the same signing issue.</p>
<p>If this is the case, this means all Apps from the app store are signed <strong>built-in self destruct</strong> date. One which only Apple or the original author can fix.</p>
<p>I have been careful to keep backups of the <code>.app</code>’s.  Apple dropped the ability to re-download App's removed from the App store.</p>
<p>This gives way too much power over my work to third parties. Software I paid for should not stop working because someone else stopped supporting it.  How can someone build a reliable work-flow when any program stop working at any time? You can't, that’s simply insanity.</p>
<p>One of the apps I used daily, the author <a href="https://sophiestication.com/sevenyears/">took down</a> a few years ago because it “flopped and [was] plagued with technical issues”. I have encountered no technical issues with it until this point, and it an important part of my work-flow.</p>
<p>I, as well as others have asked the author to open source it on many occasions. <a href="https://twitter.com/donatj/status/459357498462326784">[1]</a>, <a href="https://twitter.com/donatj/status/560141498478383104">[2]</a>, <a href="https://twitter.com/donatj/status/565658296087248896">[3]</a>, <a href="https://twitter.com/donatj/status/613455514563801088">[4]</a> … <small>There’s more</small></p>
<p>Her only response to our cries has been a cringe-worthy <code>LOL</code>. I wish I were joking.</p>
<p>The moral of my story comes down to the following life lessons.</p>
<ul>
<li>Don’t depend on things that others can take from you. </li>
<li>Be able to scratch your own itch; seek open source when at all possible.</li>
</ul>
<p>I will no longer be purchasing things from the Mac App Store. The convenience is not worth the incredible down sides.</p>
<p>Richard Stallman has been warning us for years, I should have listened.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/iTunesPlaycountZeroFix</guid>
      <title>Fix Played Tracks with Empty Playcount in iTunes</title>
      <pubDate>Wed, 11 Nov 2015 21:50:58 -0600</pubDate>
      <link>https://donatstudios.com/iTunesPlaycountZeroFix</link>
      <description><![CDATA[<p>I have a quite large iTunes library, and I’m rather anal about keeping my metadata clean and up to date.  In an earlier release of iTunes 12 there was a bug. Every so often after a song played, the Play Count would fail to increment, but the Last Played would update.</p>
<p>Searching my library, I found 74 tracks that had this issue.</p>
<p>I found this irritating and inaccurate, so I set forth finding a way to correct it.</p>
<p>I wrote the following simple AppleScript to set the playcount of such tracks to 1.</p>
<p>All you need to do is either paste the following into the 'Script Editor' or <a href="https://cdn.rawgit.com/donatj/12b86fc65982e29f20a2/raw/3fd49e20267caf34d1ac60b62f2114a2ac57839c/FixITunesPlayedCount.applescript">download</a> it and run it.  See video below if you need help running it once it’s open.</p>
<video preload="auto" controls loop style="display: block; margin: 0 auto;">
      <source src="https://donatstudios.com/assets/60/fixitunes.mp4" type="video/mp4">
      <p>To view this video please enable JavaScript and upgrade to a web browser that <a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video>
 ]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Numberphilia</guid>
      <title>Numberphilia</title>
      <pubDate>Mon, 01 Jun 2015 22:13:00 -0500</pubDate>
      <link>https://donatstudios.com/Numberphilia</link>
      <description><![CDATA[<p>There's been a fair deal of hubbub in the community about version numbers, including what is and isn't <a href="https://semver.org/">Semantic Versioning</a>, what qualifies as a breaking change and how to use version numbers.</p>
<p><strong>Underscore.js</strong> released a second digit &quot;minor update&quot; that was actually a <a href="https://github.com/jashkenas/underscore/issues/1805">breaking update</a> for some people. They argue it was <em>minor</em> because not very many people would be affected. The community however was angry.</p>
<p>On the opposite side of things, <strong>PHP</strong> Internals announced they will be <a href="https://wiki.php.net/rfc/php6">skipping version 6</a> and numbering the next version of <strong>PHP 7</strong> to avoid confusion with the never released UTF-16 native version. Again the community was angry.</p>
<h2>Using Version Numbers Well</h2>
<p>If you are <em>semantically</em> versioning your library, the version represents a <strong>contract of trust</strong> between you and your developers regarding the public API. The contract declares that nothing you change in a 'less than major' release should break their code. Ever.</p>
<p>Break the contract, you can end up breaking their trust. Break their trust and they'll look elsewhere.</p>
<h3>Version Numbers <strong>SHOULD NEVER</strong> Be Branding</h3>
<p>If you're using your version numbers as <strong>branding</strong>, you are <em>doing it wrong</em>. Version numbers should show a clear history of the public facing interface, not what you sell or advertise it as.</p>
<p>Microsoft of all people <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832.aspx">understands this</a>.  While their version numbers aren't truly semantic, they are aimed at developers - and <strong>highly</strong> out of sync with the marketing designation because the version numbers denote a true relationship.</p>
<p>You can have both a release number and a sales version. This is the way to go if sales demands control of the version number, such as where I work.</p>
<h3>Increment Early, Increment Often</h3>
<p>Breaking changes <strong>always</strong> need to be a first digit release. <strong>Always</strong>.  It doesn't matter if it's not a common use case. It shouldn't matter if you're saving the number for a <em>&quot;woo big new release&quot;</em> version. A breaking change in a minor release violates trust and brings the quality of your software into question.</p>
<p><strong>WHENEVER</strong> a behavior or signature of <strong>ANY</strong> <em>explicitly public member</em> of your API has changed whereas the previous behavior was <strong>anything</strong> other than bold face wrong it is a first digit release. </p>
<h2>Numbers are Cheap</h2>
<p>Numbers are cheap. Numbers are infinite. <strong>Never</strong> reuse numbers, ever.  There is no reason.</p>
<p>PHP in my strong opinion did the <strong>right thing</strong> by skipping 6. They sidestepped ANY potential confusion, minor as it may have been. The argument that they caused confusion about &quot;Where did 6 go&quot; is childish. People will be investing time in the largest digit release, regardless of gaps. No one (save some overly cautious IT people) is going to be using PHP 5.6 and on seeing 7 released think &quot;I'll upgrade to 6&quot;.</p>
<p>Trying to not have gaps in your version numbers makes as much sense as if Git were to use ordered numbers instead of hashes. </p>
<p>Version numbers should be a <strong>universally unique identifier</strong> for a specific set of code. If there is ever any form of contention over what a version number represents, increment. Don't think about it - just do it.</p>
<h2>Conclusion</h2>
<p>Always avoid confusion; don't ever be afraid to increment your major release number. Only good things can come from it.</p>
<p>Trust is maintained.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/CSS-Alike-Color-Finder</guid>
      <title>Similar CSS Color Finder</title>
      <pubDate>Tue, 06 Jan 2015 03:41:33 -0600</pubDate>
      <link>https://donatstudios.com/CSS-Alike-Color-Finder</link>
      <description><![CDATA[<p><strong>Update</strong>: CIEDE2000 support added to the existing CIE94 support.</p>
<p>It would seem that <strong><em>very similar</em></strong> but <em>non</em>-identical colors seem to pop up often in CSS files of any reasonable age, and I became <strong>sick</strong> of them. This started as a little script to help me find them in a stylesheet, and grew into this full-fledged tool.</p>
<p>This tool will scan CSS and CSS-like files like Sass, Less, etc and find colors similar to each other within a set tolerance. </p>
<p>This handles <code>#hex</code>, <code>rgb</code>, <code>rgba</code>, <code>hsl</code>, and <code>hsla</code> colors.</p>
<p>There is a command line version of this with Continuous Integration support available on <a href="https://github.com/donatj/AlikeColorFinder/">GitHub</a>.</p>
<p></p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Install-PHP-Mcrypt-Extension-in-OS-X</guid>
      <title>Install PHP's Built In Extensions in macOS</title>
      <pubDate>Tue, 23 Dec 2014 17:40:16 -0600</pubDate>
      <link>https://donatstudios.com/Install-PHP-Mcrypt-Extension-in-OS-X</link>
      <description><![CDATA[<p>Should you run into errors related to missing <code>php.h</code> or other <code>.h</code> files, you should check out my post on <a href="https://donatstudios.com/MojaveMissingHeaderFiles">fixing missing headers on macOS Mojave</a>.</p>
<hr />
<p>These directions are for working with the <strong>native installation of PHP</strong>. Your results may vary if you are using a brew, MAMP or otherwise installed version of PHP - I do not recommend this for those cases.</p>
<p><strong>Important</strong>: As you are altering the built-in version of PHP, you will need to ensure you have disabled System Integrity Protection before you begin. You can find instructions on how to do this here: <a href="https://donatstudios.com/Disable-macOS-System-Integrity-Protection">How to: Disable macOS System Integrity Protection</a>.</p>
<p>This was previously dedicated to installing the Mcrypt extension specifically, but in reality can be used to install any of the following extensions distributed with the PHP source.</p>
<pre><code>bcmath       ftp          mcrypt       pdo_oci      simplexml    wddx
bz2          gd           mysqli       pdo_odbc     skeleton     xml
calendar     gettext      mysqlnd      pdo_pgsql    snmp         xmlreader
com_dotnet   gmp          oci8         pdo_sqlite   soap         xmlrpc
ctype        hash         odbc         pgsql        sockets      xmlwriter
curl         iconv        opcache      phar         spl          xsl
date         imap         openssl      posix        sqlite3      zip
dba          interbase    pcntl        pspell       standard     zlib
dom          intl         pcre         readline     sysvmsg
enchant      json         pdo          recode       sysvsem
exif         ldap         pdo_dblib    reflection   sysvshm
fileinfo     libxml       pdo_firebird session      tidy
filter       mbstring     pdo_mysql    shmop        tokenizer</code></pre>
<p>We need to install the required dependencies. If you are not already using <a href="https://brew.sh/">Homebrew</a> you will need it. </p>
<pre><code class="language-console">$ brew install autoconf pkg-config</code></pre>
<p>For certain extensions like mcrypt you may additionally need to install additional libraries such as:</p>
<pre><code class="language-console">$ brew install libmcrypt</code></pre>
<p>Next we will download the PHP source. Verify the exact version of PHP you are running. This can be retrieved as follows. The version is highlighted.</p>
<pre>
<code class="language-console">$ php --version
PHP <strong><em>7.1.19</em></strong> (cli) (built: Aug 17 2018 18:03:17) ( NTS )
Copyright (c) 1997-2018 The PHP Group</code>
</pre>
<p>Now we move into a working directory and download the source <strong>making sure</strong> to update the following for the version from above.  </p>
<pre>
<code class="language-console">$ cd /tmp
$ curl -L http://php.net/get/php-<strong><em>{{php-version}}</em></strong>.tar.bz2/from/this/mirror > php.tar.bz2
$ open php.tar.bz2</code>
</pre>
<p>Now we will compile and test the extension.</p>
<pre>
<code class="language-console">$ cd php-<strong><em>{{php-version}}</em></strong>/ext/<strong><em>{{extension}}</em></strong>
$ phpize
$ ./configure
$ make
$ make test
$ sudo make install</code>
</pre>
<p>If all that goes well, finally we'll need to add the following to our php.ini - I usually add at it at the end of the file.</p>
<pre>
<code>extension = <strong><em>{{extension}}</em></strong>.so</code>
</pre>
<p>You can verify your installation with the following:</p>
<pre>
<code class="language-console">$ php --info | grep <strong><em>{{extension}}</em></strong>\\.</code>
</pre>
<p>Lastly, depending on your setup now, you may want to restart Apache. </p>
<pre><code class="language-console">$ sudo apachectl restart</code></pre>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/PHP-in-OS-X-Yosemite</guid>
      <title>Configure Apache for PHP in OS X / macOS</title>
      <pubDate>Fri, 17 Oct 2014 02:27:59 -0500</pubDate>
      <link>https://donatstudios.com/PHP-in-OS-X-Yosemite</link>
      <description><![CDATA[<p>Every time I would upgrade OS X it took probably 45 minutes of fiddling with my Apache configuration to get it working properly. I'm writing this article in the hopes of saving myself and others some time.</p>
<h2>The Setup</h2>
<p>For the following examples, <code>{username}</code> represents <strong>your</strong> username.</p>
<p>Firstly, if you don't already have a Sites folder, create one in your home directory.</p>
<pre><code class="language-console">$ mkdir ~/Sites</code></pre>
<p>Create or update <code>/etc/apache2/users/{username}.conf</code> to look as follows:</p>
<pre><code>&lt;Directory "/Users/{username}/Sites/"&gt;
    Options Indexes MultiViews FollowSymLinks
    Require all granted
    AllowOverride All
    Order allow,deny
    Allow from all
&lt;/Directory&gt;</code></pre>
<p>Add your virtual hosts if you don't already have them to <code>/etc/apache2/extra/httpd-vhosts.conf</code></p>
<p>ala:</p>
<pre><code>&lt;VirtualHost *:80&gt;
  ServerName localhost
  DocumentRoot /Users/{username}/Sites/
&lt;/VirtualHost&gt;</code></pre>
<h2>Configuration</h2>
<h3>/etc/apache2/httpd.conf</h3>
<p>Search for and uncomment the following lines:</p>
<pre><code>LoadModule deflate_module libexec/apache2/mod_deflate.so
LoadModule userdir_module libexec/apache2/mod_userdir.so
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
LoadModule php5_module libexec/apache2/libphp5.so

Include /private/etc/apache2/extra/httpd-userdir.conf
Include /private/etc/apache2/extra/httpd-vhosts.conf</code></pre>
<p>Then around line 271 add <code>index.php</code> to the DirectoryIndex</p>
<pre><code>&lt;IfModule dir_module&gt;
    DirectoryIndex index.html index.php
&lt;/IfModule&gt;</code></pre>
<h3>/etc/apache2/extra/httpd-userdir.conf</h3>
<p>Uncomment the following line:</p>
<pre><code>Include /private/etc/apache2/users/*.conf</code></pre>
<h2>Almost Done</h2>
<p>Restart apache </p>
<pre><code class="language-console">$ sudo apachectl restart</code></pre>
<p>open a browser and test it out.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Require-a-Remote-Zip-File-with-Composer</guid>
      <title>Require a Remote Zip File with Composer</title>
      <pubDate>Fri, 10 Oct 2014 20:31:15 -0500</pubDate>
      <link>https://donatstudios.com/Require-a-Remote-Zip-File-with-Composer</link>
      <description><![CDATA[<p>I previously showed you how to <a href="https://donatstudios.com/Load-Gist-With-Composer">Load a Github Gist</a> with Composer, but sometimes you need to install code that isn't isn't even in a public facing VCS.</p>
<p>I for instance wanted to use a library only distributed by Zip.</p>
<p>It's actually fairly easy! In your <code>composer.json</code> file, you simply add a <code>repositories</code> section to the root. You will want to update the <code>url</code> section to point at the remote zip you intend to load, as well as updating the name and autoload sections.  More information on configuring composers autoloader can be found <a href="https://getcomposer.org/doc/04-schema.md#autoload">here</a>.</p>
<pre><code>"repositories": [
    {
        "type": "package",
        "package": {
            "name": "dr-que/x-y",
            "version": "master",
            "dist": {
                "type": "zip",
                "url": "http://xyplot.drque.net/Downloads/XY_Plot-1.4.zip",
                "reference": "master"
            },
            "autoload": {
                "classmap": ["."]
            }
        }
    }
]</code></pre>
<p>Then in your require section, add your selected name as follows.</p>
<pre><code>"require": {
    "dr-que/x-y": "dev-master"
}</code></pre>
<p>Then just run <code>composer update</code> and you should be ready to roll!</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/CsvToMarkdownTable</guid>
      <title>CSV To Markdown Table Generator</title>
      <pubDate>Sun, 14 Sep 2014 21:37:12 -0500</pubDate>
      <link>https://donatstudios.com/CsvToMarkdownTable</link>
      <description><![CDATA[<p><strong>Update July 8, 2019</strong>: Added more graceful pipe <code>|</code> and backslash <code>\</code> handling.</p>
<p>While doing code reviews on GitHub I find myself profiling a lot of SQL queries, getting <code>EXPLAIN</code> output into a markdown format for GitHub became very important, and was a huge pain to do by hand.</p>
<p>While there were tools out there, nothing I could easily copy and paste tab seperated query results into, so I created this. The markdown this outputs should work with most table supporting Markdowns flavors such as Markdown Extra and GitHub Flavored Markdown.</p>
<p>The default tab separated setting is very useful for pasting straight from Excel or other tabular data sources like SQL editors.</p>
<fieldset> <center><small><a href="https://github.com/donatj/CsvToMarkdownTable" target="_blank">Fork my source on GitHub!</a></small></center></fieldset>
<p>Caveat - while it does <em>essentially</em> parse CSV it is not quotation mark aware.  If need or demand arises I may look into it.</p>
<p>The script is available on <a href="https://github.com/donatj/CsvToMarkdownTable">GitHub</a> and <em>is</em> Node.js ready. More details can be found on GitHub. It is also available via <a href="https://www.npmjs.com/package/csv-to-markdown-table">npm</a>.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Bookmarklets</guid>
      <title>Bookmarklets</title>
      <pubDate>Wed, 10 Sep 2014 23:10:05 -0500</pubDate>
      <link>https://donatstudios.com/Bookmarklets</link>
      <description><![CDATA[<p>This is just a small collection of Bookmarklets I personally find useful.  To use them simply click and drag them into your Bookmarks bar.</p>
<p><a class="bookmarklet" href="https://donatstudios.com/javascript:(function(){var e=document.getElementsByTagName(&amp;quot;form&amp;quot;);for(var t=0;t&amp;lt;e.length;t++){e[t].setAttribute(&amp;quot;novalidate&amp;quot;,&amp;quot;novalidate&amp;quot;)}})()" onclick="event.preventDefault()">Disable HTML 5 Validation</a> </p>
<p>Clicking this will disable html5 validation for all form elements on the page.  Useful for testing server side validation while leaving HTML 5 validation in place.</p>
<p><a class="bookmarklet" href="https://donatstudios.com/javascript:(function(){alert(document.getElementsByTagName(&amp;quot;*&amp;quot;).length)})()" onclick="event.preventDefault()">Element Count</a></p>
<p>Clicking this will provide a count of the number of elements on the page in an alert box.</p>
 ]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/All-Glory-to-__invoke</guid>
      <title>All Glory to __invoke</title>
      <pubDate>Thu, 22 May 2014 20:29:47 -0500</pubDate>
      <link>https://donatstudios.com/All-Glory-to-__invoke</link>
      <description><![CDATA[<p>Lost in the shiny new features (see: <em>namespaces</em> and <em>closures</em>) PHP 5.3 also added the <code>__invoke</code> method. While not plainly apparent, it is secretly an amazingly useful 'magic method' . </p>
<p>If you're not taking advantage of <code>__invoke</code>, you <strong>should be</strong>. Why? It provides a uniform execution points for objects that have a <code>doPrimaryAction()</code> style method.</p>
<p>What do I mean by this? <strong>Many</strong> simple, single responsibility objects have a usage that goes something like:</p>
<ol>
<li>Instantiate</li>
<li>Set Options</li>
<li>Execute </li>
<li>[Optional] GOTO 2</li>
</ol>
<p>They are essentially glorified functions with manageable defaults.</p>
<p>So instead of </p>
<pre><code>&lt;?php

public function add($left = 0, $right = 0) {
    return $left + $right;
}

echo add(1, 2); // 3
echo add(3, 2); // 5</code></pre>
<p>We have something like</p>
<pre><code>&lt;?php

class Add {
    public $left;
    public $right;

    public function construct($left = 0, $right = 0) {
        $this-&gt;left  = $left;
        $this-&gt;right = $right;
    }

    public function doMath() {
        return $this-&gt;left + $this-&gt;right;
    }
}

$adder = new Add;

$adder-&gt;left  = 1;
$adder-&gt;right = 2;
echo $adder-&gt;doMath(); // 3

$adder-&gt;left  = 3;
echo $adder-&gt;doMath(); // 5</code></pre>
<p>The example is farcical and stands to illustrate my point. Everything except the <code>doMath()</code> method is setup <em>for</em> the <code>doMath</code> method. You are setting up the environment for <code>doMath</code> to execute.</p>
<p>This pattern of object with one primary action has a downside over a function. You need to know the <em>class name</em> and the invoking <em>method name</em>, which varies from object to object. This dear reader is where <code>__invoke</code> comes in. </p>
<p><code>__invoke</code> is a <a href="https://www.php.net/manual/en/language.oop5.magic.php"><em>magic method</em></a> allowing us to treat our object as a <em>function</em> or more correctly a <em>closure</em>. It will pass the <em>Callable</em> type hint when added, and makes the object directly invokable via <code>call_user_function</code>. </p>
<p>What this gives us most importantly though is a <strong>uniform</strong> way across <em>differently intentioned objects</em> to invoke its primary action.</p>
<p>If we update the previous example:</p>
<pre><code>&lt;?php

class Add {
    public $left;
    public $right;

    public function construct($left = 0, $right = 0) {
        $this-&gt;left  = $left;
        $this-&gt;right = $right;
    }

    public function __invoke() {
        return $this-&gt;left + $this-&gt;right;
    }
}

$adder = new Add;

$adder-&gt;left  = 1;
$adder-&gt;right = 2;
echo $adder(); // 3

$adder-&gt;left  = 3;
echo $adder(); // 5</code></pre>
<p>You can see the difference in use is <em>slight</em>, but the benefit is the lack of <code>-&gt;doMath</code>.  We know this object does <em>one thing</em>, now we have a uniform way to make our objects do that one thing.</p>
<p>I haven't seen a lot of traction with this in the community <em>yet</em> but I remain quite hopeful for this to pick up. I do know however that Aura appears to be a fan of the pattern, with Aura.Dispatcher using it beautifully.</p>
<ul>
<li><a href="https://github.com/auraphp/Aura.Dispatcher/blob/fec7c7ada167f9ae7ca9a1c26902021464c7deb8/src/Dispatcher.php#L90">Aura.Dispatcher</a> - Aura's <em>Dispatcher</em> has one primary purpose - to <strong><em>dispatch</em></strong>. As such, it is a perfect use case for being invokable.</li>
</ul>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/PHP-Has-Grown-Up</guid>
      <title>PHP Has Grown Up, You Should Too</title>
      <pubDate>Mon, 14 Apr 2014 20:35:01 -0500</pubDate>
      <link>https://donatstudios.com/PHP-Has-Grown-Up</link>
      <description><![CDATA[<p>Over the weekend I went to a talk on Scala. The speaker said variety of <strong>harsh</strong>, inflammatory, and mostly wrong things about PHP and the PHP community. </p>
<p>One such example:</p>
<blockquote>
<p>The PHP community doesn't care about things like lambdas, they just care about getting a site up as fast as possible. </p>
</blockquote>
<p>This is just rubbish. PHP got the lambda treatment in 2009, while Java received it only <em>months ago</em>. The speaker is a Java developer who walked away from PHP over five years ago. He had not seen it grow up. He doesn't know the current state of things, he is misrepresenting PHP.  </p>
<p>Later on, he asked what technology the audience used. I loudly proclaimed PHP, despite the speakers previous grumblings. An audience member in front of me turned around and scoffed, or at least so I thought. I felt at least a little like a martyr at this point and decided to take action. Justice was in order.</p>
<center>
<blockquote class="twitter-tweet" lang="en"><p>To the heavily bearded dude who turned around and scoffed when I mentioned PHP, die in a fire. <a href="https://twitter.com/search?q=%23minnebar&amp;amp;src=hash">#minnebar</a></p>&mdash; Jesse Donat (@donatj) <a href="https://twitter.com/donatj/statuses/455029421154381824">April 12, 2014</a></blockquote>
 
</center>
<p>After the talk, the &quot;bearded dude&quot; approached me. He had seen the tweet thanks to the hash tag. He apologized and said it was not a scoff, but rather an expression of excitement that someone would bring it up after the speakers treatment of the language. He works in the language some too and was glad to see someone stand up for it. I shook his hand and apologized for the tweet. </p>
<p>Later on he left the following reply:</p>
<center>
<blockquote class="twitter-tweet" data-conversation="none" lang="en"><p><a href="https://twitter.com/donatj">@donatj</a> not a scoff. admiration.</p>&mdash; Jeff Mattfield (@jeffmattfield) <a href="https://twitter.com/jeffmattfield/statuses/455045280019132417">April 12, 2014</a></blockquote>
 
</center>
<p>I had misjudged him, as the speaker had misjudged PHP. Alas the hypocrisy; I was no better than the speaker.</p>
<p>The whole experience got me thinking about why there is still so much FUD and ill will around PHP. After all, we've had many modern niceties for a good while now. The language has matured significantly in the last decade. It has become a modern language that's scalable, fast, and enjoyable, all while still easy to deploy. </p>
<p>Moreover, it has one of the best package managers I've ever used -  <a href="https://getcomposer.org/">Composer</a>, and a vibrant and helpful community. Seriously, why all the hate?</p>
<p>The moral to this story is think before you act. Make sure you have the whole picture. I didn't, the speaker didn't.  This has been my 2¢ for the day.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Beyond-Compare-Mac-Beta-with-Git-in-OSX</guid>
      <title>Using Beyond Compare 4 Mac Beta with Git</title>
      <pubDate>Thu, 26 Dec 2013 17:29:40 -0600</pubDate>
      <link>https://donatstudios.com/Beyond-Compare-Mac-Beta-with-Git-in-OSX</link>
      <description><![CDATA[<p>A little over a year ago I wrote a post about using <a href="https://donatstudios.com/Beyond-Compare-with-Git-in-OSX">Beyond Compare on Mac</a> via Wine.  A native version is now in beta and open to everyone! If you haven't tried Beyond Compare, I suggest you do. If you have you already know how awesome it is.</p>
<p>I've been testing the Alpha for many months now and thought I'd throw together some instructions for getting it going with git.</p>
<p>You can download the beta here: <a href="https://www.scootersoftware.com/beta.php?zz=beta4_dl">http://www.scootersoftware.com/beta.php</a></p>
<p>After you have the App installed, the next step is to install the command line tools. All other steps require this.</p>
<p>From the App, go to the &quot;Beyond Compare&quot; drop down menu and choose &quot;Install Command Line Tools...&quot;. It will then prompt you for your password. After this, you <code>bcomp file1.txt file2.txt</code> </p>
<h2>Using with Git CLI</h2>
<p>The next step is to set it up as a git diff tool and merge tool. Git already has native support for it built in thanks to the Linux client having existed for years now.</p>
<p>This can be achieved simply by running the following commands.</p>
<pre><code class="language-console">$ git config --global diff.tool bc3
$ git config --global difftool.prompt false
$ git config --global merge.tool bc3</code></pre>
<p>This simple setup will leave <code>git diff</code> and <code>git merge</code> functioning as usual, as well as allowing you to use Beyond Compare for the task by doing <code>git difftool</code> and <code>git mergetool</code>. </p>
<p>I <strong>highly</strong> recommend giving Beyond Compare a go as a <strong>merge tool</strong> next time you need to do a merge as it makes it wonderfully simple (when you understand whats going on with the 4 sections anyway).</p>
<h2>Using with Tower 1.x</h2>
<p><strong>Update:</strong> Tower 2.0.6+ include built in support for Beyond Compare which the following instructions will interfere with.</p>
<ol>
<li>Quit Tower</li>
<li>Navigate to <code>~/Library/Application\ Support/Tower</code> </li>
<li>Create a <code>CompareScripts</code> folder if one does not already exist.</li>
<li>Download <a href="https://jdon.at/3tWV">bcomp.sh</a> and place it in the new <code>CompareScripts</code> folder.</li>
<li>Make it executable via <code>chmod +x bcomp.sh</code></li>
<li>Download <a href="https://jdon.at/J4cR">CompareTools.plist</a> and place it in the <code>~/Library/Application\ Support/Tower</code> directory. Rename / move any old version that might exist.</li>
</ol>
<h2>Using with SourceTree</h2>
<ol>
<li>Open SourceTree</li>
<li>Open &quot;Preferences&quot;  from the SourceTree menu</li>
<li>Along the top bar, choose the &quot;Diff&quot; tab.</li>
<li>Under External Diff / Merge
<ol>
<li>for Visual Diff Tool choose <code>Other</code> then in the Diff Command enter <code>/usr/local/bin/bcomp</code> and for Arguments enter <code>$LOCAL $REMOTE</code></li>
<li>For Merge Tool choose <code>Other</code> and in Merge Command enter <code>/usr/local/bin/bcomp</code> and for Arguments enter <code>$LOCAL $REMOTE $BASE $MERGED</code></li>
</ol></li>
</ol>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Golang-Binary-Sizes</guid>
      <title>Go Binary Sizes Are Growing out of Control</title>
      <pubDate>Mon, 02 Dec 2013 17:25:43 -0600</pubDate>
      <link>https://donatstudios.com/Golang-Binary-Sizes</link>
      <description><![CDATA[<p><strong>Update 2019-07-17:</strong> I have released a follow up post <a href="https://donatstudios.com/Golang-Binary-Sizes-Part-2">Go Binary Sizes Are Relatively Stable</a> which speaks on what's happened in the time since this was published.</p>
<p><strong>Note:</strong> This is not intended as a &quot;Go Sucks&quot; post. I <strong><em>love</em></strong> Go. I <strong>am not</strong> saying the developers are <em>lazy</em> or <em>dumb</em> or any of the things Reddit has implied. I am not implying I could build a better compiler. Rob Pike and the Go team are geniuses whom I look up to. The tone of this was intended to be a light &quot;man I wish binaries were smaller&quot; but that is not how Reddit seemed to take it.  Also, the title is <a href="https://en.wikipedia.org/wiki/Hyperbole">hyperbole</a> and not meant to literally mean no one has control over it.</p>
<p>I started toying with Go about two years ago before the 1.0 release. I love the language, it meets me in a comfortable place between C and a scripting languages like PHP. I find it fun to write and an easy way to bust out performant code quickly. The binaries for a simple <code>Hello, 世界</code> were large  though, around 55KB.  I figured this was something the Go team would work out later; it is a lot of space just to send a string to standard out.</p>
<p>With the release of Go 1.0 though, I noticed the binary size had grown slightly to 242KB. Discouraging but livable. I was quite hopeful with the 1.1 release, but it had actually nearly doubled in size coming out to a whopping 405KB. Then this morning I compiled it again in the newly release 1.2 and to my horror the ever simple program came out to 557KB.</p>
<p>What on earth is going on in that binary? I am no expert on compilers but I would imagine the vast majority of what is in there is completely unnecessary and could be optimized out.  Changing the script to &quot;Hello, World&quot; rather than Go's flaunted unicode version has zero effect on binary size, as one would expect but I was hopeful.</p>
<p>For reference the complete source of my <code>Hello, 世界</code> follows. Using <code>fmt</code> comes out even larger.</p>
<pre><code>package main

func main() {
    println("Hello, 世界")
}</code></pre>
<p>Here is a collection of binaries compiled in different versions of Go.  Notice how imgavg grows from 1.9MB to 3.7MB. My applications are all small tools.  I can only imagine the effect this has an already large application.</p>
<img src="https://donatstudios.com/assets/48/golangBinarySizes.png" alt="Golang binary sizes grow with each release" style="display: block; margin: 0 auto; border: 1px solid #aaa">
<p>I really hope something is done about this. I would love to see tiny (&lt;32KB) binaries from Go but right now that doesn't seem to be a priority for them. The fact that <code>Hello, 世界</code> fills half a floppy means the language is not be useful for lighter or embedded environments.</p>
<p>As noted in the header there is an addendum to this:</p>
<ul>
<li><a href="https://donatstudios.com/Golang-Binary-Sizes-Part-2">Go Binary Sizes Are Relatively Stable</a></li>
</ul>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Load-Gist-With-Composer</guid>
      <title>Load a Github Gist with Composer</title>
      <pubDate>Wed, 06 Nov 2013 20:36:45 -0600</pubDate>
      <link>https://donatstudios.com/Load-Gist-With-Composer</link>
      <description><![CDATA[<p>Composer is amazing for pulling in packages, but what if you find a Gist that isn't Composer aware? Fear not, as Composer has the magical ability to pull in repositories not explicitly set up in Packagist by defining them in the &quot;repositories&quot; section. </p>
<p>But what if you wanted to load a single class from a git gist? </p>
<p>You can add the following <code>repositories</code> section to your <code>composer.json</code> file, adjusting <code>name</code> and <code>url</code> as necessary. The <code>name</code> should be a <code>vendor/package</code> (all lower) style name you make up for it, and <code>url</code> will be from the <em>Clone this gist</em> box on GitHub. </p>
<pre><code>"repositories": [
    {
        "type":"package",
        "package": {
            "name": "turin86/wssoapclient",
            "version": "master",
            "source": {
                "url": "https://gist.github.com/5569152.git",
                "type": "git",
                "reference":"master"
            },
            "autoload": {
                "classmap": ["."]
            }
        }
    }
]</code></pre>
<p>Then in your require section, add your selected <code>name</code> as follows.</p>
<pre><code>"require": {
    "turin86/wssoapclient": "dev-master"
}</code></pre>
<p>If the name of the class doesn't match the filename in a friendly autoload-able manner, you'll have to replace <code>"classmap": ["."]</code> with <code>"files": ["ClassToAutoload.php"]</code> replacing <code>ClassToAutoload.php</code> with the filename. More info on autoloading can be found <a href="https://getcomposer.org/doc/04-schema.md#autoload">here</a>. Note that if you are experimenting with autoload settings you may need to remove your vendor directory and <code>composer install</code>, as updates to the autoload were not reflected for me after a <code>composer update</code>.</p>
<p>After this, just run <code>composer update</code> and voilà, you are loading a gist with Composer.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/OS-X-Mavericks-Memcached-PHP-Extension-Installation</guid>
      <title>Installing the PHP "memcached" Extension</title>
      <pubDate>Wed, 23 Oct 2013 05:42:40 -0500</pubDate>
      <link>https://donatstudios.com/OS-X-Mavericks-Memcached-PHP-Extension-Installation</link>
      <description><![CDATA[<h3>Please Note</h3>
<p>This tutorial only covers the <strong>default PHP installation</strong> that ships with Mac OS X / macOS. If you have installed a new installation this does not cover you. </p>
<p>For those using brew, you can simply <code>brew install</code> the <a href="https://github.com/Homebrew/homebrew-php/blob/49cb525f2dd836ac1fe79905d9f094931d7b02ba/Formula/php55-memcached.rb">appropriate brew formula</a> for the extension.</p>
<p>These directions are verified to work with Mac OS X 10.9 Mavericks through macOS 10.14 Mojave.</p>
<fieldset><legend>System Integrity Protection</legend>

<p>If you are running OS X El Capitan or <strong>newer</strong> you will need to disable System Integrity Protection to modify system files and directories.</p>
<p>To disable System Integrity Protection, boot into recovery mode by restarting and then holding <code>⌘R</code> as you hear the startup chime. Then start the Terminal from the Utilities menu.</p>
<p>Run the following command</p>
<pre><code class="language-console">$ csrutil disable</code></pre>
<p>Then reboot. You are good to go.</p>
<p>If you wish to turn it back on, follow the instructions above but instead use:</p>
<pre><code class="language-console">$ csrutil enable</code></pre>

Click <a href="https://donatstudios.com/Disable-macOS-System-Integrity-Protection">here</a> for a full post on the topic.

</fieldset>
<p>The first step is to install the latest version of Xcode. On modern versions of MacOS this can be done from the App Store.</p>
<p>After this, install the Xcode developer tools. </p>
<pre><code class="language-console">$ xcode-select --install</code></pre>
<p><strong>Special note to Mojave users</strong> if the following steps fail, you may need to reinstall your Xcode header files. Instructions for that can be found <a href="https://donatstudios.com/MojaveMissingHeaderFiles">here</a>.</p>
<p>Next we will need to install the required dependencies before we can build the extension. If you are not already using <a href="https://brew.sh/">Homebrew</a> you should be. </p>
<pre><code class="language-console">$ brew install wget autoconf pkg-config libmemcached</code></pre>
<p>You will also want to make sure you have PEAR installed; full instructions can be found <a href="https://pear.php.net/manual/en/installation.getting.php">here</a> but can be summarized as follows:</p>
<pre><code class="language-console">$ wget http://pear.php.net/go-pear.phar
$ php go-pear.phar</code></pre>
<p>Then in the <code>/tmp</code> directory we will create a folder we can work in.</p>
<pre><code class="language-console">$ cd /tmp
$ mkdir memcached-work
$ cd memcached-work</code></pre>
<p>Next we will use pecl, part of pear, to fetch the current version of the extension.  </p>
<p>The version of the memcached extension will vary so you will need to update the paths accordingly.</p>
<pre><code class="language-console">$ pecl download memcached
$ open memcached-{{version}}.tgz
$ cd memcached-{{version}}/memcached-{{version}}
$ phpize
$ ./configure
$ make
$ sudo make install</code></pre>
<p>Finally you will need to add the following line to your php.ini</p>
<pre><code>extension = memcached.so</code></pre>
<p>You can verify your installation with the following:</p>
<pre><code class="language-console">$ php --info | grep memcached\\.</code></pre>
<p>Depending on your setup now you may want to restart apache. </p>
<pre><code class="language-console">$ sudo apachectl restart</code></pre>
<p>You should be all set to go!</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Disable-macOS-System-Integrity-Protection</guid>
      <title>How to: Disable macOS System Integrity Protection</title>
      <pubDate>Wed, 23 Oct 2013 05:42:40 -0500</pubDate>
      <link>https://donatstudios.com/Disable-macOS-System-Integrity-Protection</link>
      <description><![CDATA[<p>If you are running OS X El Capitan or <strong>newer</strong> you will need to disable System Integrity Protection to modify system files and directories.</p>
<p>To disable System Integrity Protection, boot into recovery mode by restarting and then holding <code>⌘R</code> as you hear the startup chime. Then start the Terminal from the Utilities menu.</p>
<p>Run the following command</p>
<pre><code class="language-console">$ csrutil disable</code></pre>
<p>Then reboot. You are good to go.</p>
<p>If you wish to turn it back on, follow the instructions above but instead use:</p>
<pre><code class="language-console">$ csrutil enable</code></pre>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/CoffeeScript-Madness</guid>
      <title>CoffeeScript's Scoping is Madness</title>
      <pubDate>Thu, 25 Jul 2013 17:19:44 -0500</pubDate>
      <link>https://donatstudios.com/CoffeeScript-Madness</link>
      <description><![CDATA[<p><a href="https://coffeescript.org/">CoffeeScript</a> is a programming language that compiles to JavaScript. Its mission is to &quot;expose the good parts of JavaScript in a simple way&quot;. I've  heard a lot of interest in it lately from various channels. </p>
<p>After a coworkers departure, I inherited maintenance on our deployment bot, and it was written in CoffeeScript. While trying to make some small modifications, I ended up getting unexpected issues I didn't understand. Things I did not touch were suddenly breaking. I did not understand until I investigated the generated JavaScript.</p>
<p>It ends up CoffeeScripts approach to scoping of variables is to not scope variables. There is no shadowing. There is no <code>var</code>, <code>let</code> nor <code>const</code> equivalent, there is simply no way to explicitly define scope. </p>
<p>Variables in CoffeeScript are scoped to their outermost definition, regardless of whether the name was already in use elsewhere. When you use a variable of a name, its scope now includes all inner scopes, trampling any existing definitions of the variable in those scopes.</p>
<p>Let's examine the following examples:</p>
<div style="display: grid; grid-auto-flow: column; grid-template-rows: auto 1fr; gap: 16px; grid-auto-columns: 1fr;">
<strong>CoffeeScript</strong>
<pre><code>test = (x) -&gt;<mark>
  y = 10
  x + y;</mark>

alert test 5</code></pre>
<strong>Generated JavaScript</strong>
<pre><code>var test;

test = function(x) {<mark>
  var y;
  y = 10;
  return x + y;</mark>
};

alert(test(5));</code></pre>
</div>
<p>Now let's make one small change and define a variable <code>y</code> in the outer scope, outside the function.</p>
<div style="display: grid; grid-auto-flow: column; grid-template-rows: auto 1fr; gap: 16px; grid-auto-columns: 1fr;">
<strong>CoffeeScript</strong>
<pre><code><mark>y = 0;
test = (x) -&gt;
  y = 10
  x + y;

alert test 5</mark></code></pre>
<strong>Generated JavaScript</strong>
<pre><code><mark>var test, y;

y = 0;

test = function(x) {
  y = 10;
  return x + y;
};

alert(test(5));</mark></code></pre>
</div>
<h2>The Problem</h2>
<p>Notice the highlighting indicating the scope of the <code>y</code> variable. By defining <code>y</code> in the global scope all subsequent <code>y</code>'s are also now that global. You may no longer have a variable named <code>y</code> in your code, at any deeper scope. The outer <code>y</code> is the only <code>y</code>.</p>
<p>Creating a global of a name given overrides all subsequent definitions.</p>
<p>In JavaScript <code>var</code>, <code>let</code>, and <code>const</code> keywords define the scope of a variable. Leave it out and it scopes downward until it hits a <code>var</code> definition of that variable <em>or</em> the global level. This means that inner scopes inherits from outer scopes <em>unless</em> it defines its own scope of the variable.  CoffeeScript on the other hand offers you no method to scope a variable. Scoping is entirely left to the lowest level use of a <em>variable name</em>.</p>
<p>This behavior is dangerous and makes code very difficult to maintain. The problem is twofold. Firstly, when writing a function, you need to be aware of all shallower scopes variable definitions, as not to trample them. Secondly, when altering code at any scope but the outermost, you need to be aware of all deeper scopes variables as to not trample any of them. </p>
<p>The only &quot;safe&quot; way to write CoffeeScript is to assume all variable names are global. In practice they are.</p>
<p>There have been proposed solutions, such as a Go-lang style <code>:=</code> for inner scoping, but these have not been accepted as of this writing. The creator of CoffeeScript, Jeremy Ashkenas, when questioned about it by Armin Ronacher on Twitter replied:</p>
<blockquote>
<p>Not gonna happen ;) Forbidding shadowing altogether is a huge win, and a huge conceptual simplification.</p>
</blockquote>
<p>This is not true for applications of any scale larger than toy. It makes development more complex, having to check every inner scope before using a new variable name. This is not simpler. The entire behaviour of all inner scopes can change with a single bad variable defintion.</p>
<h2>Finally</h2>
<p>CoffeeScripts goal of &quot;expos[ing] the good parts of JavaScript in a simple way&quot; was <strong>missed</strong>.  Instead of improving JavaScript's scoping which can be kind of daunting to learn, they actually made it worse. If you want to improve JavaScript make every variable <em>local</em> until explicitly defined global, as is the behavior in the vast majority of languages.</p>
<p>JavaScripts &quot;<em>global unless defined as local</em>&quot; behavior is weird, but manageable. CoffeeScript's &quot;<em>local until defined <strong>elsewhere</strong> globally</em>&quot; behavior on the other hand is simply difficult to predict and makes it easy to accidentally change the entire meaning of code even when you entirely understand the behavior.</p>
<p>It is a <strong>bad</strong> behavior, period.</p>
<h2>Further Reading</h2>
<p>I'd like to also suggest reading Armin Ronacher's <a href="https://lucumr.pocoo.org/2011/12/22/implicit-scoping-in-coffeescript/">The Problem with Implicit Scoping in CoffeeScript</a>.  It eloquently describes the problem from a slightly different angle.</p>
<h2>Update</h2>
<ul>
<li>I've a fight on Reddit <a href="https://www.reddit.com/r/programming/comments/1j1cw7/coffeescripts_scoping_is_madness/">here</a>.</li>
<li>I've irked Reginald Braithwaite, the author of <a href="https://leanpub.com/coffeescript-ristretto">CoffeeScript Ristretto</a>, so badly he's giving away <a href="https://twitter.com/raganwald/status/360501252162981888">100 copies of his book for free</a>!
<ul>
<li>The author has upgraded me to <em><a href="https://web.archive.org/web/20221122115820/https://twitter.com/raganwald/status/360514948956303361">Not Wrong</a></em>™!</li>
</ul></li>
</ul>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/UNIX-Keyboards</guid>
      <title>The Joys of UNIX Keyboards</title>
      <pubDate>Tue, 18 Jun 2013 18:10:51 -0500</pubDate>
      <link>https://donatstudios.com/UNIX-Keyboards</link>
      <description><![CDATA[<h2>I fell in love with a dead keyboard layout.</h2>
<p>A decade or so ago while helping a friends father clean out an old building, we came across an ancient Sun Microsystems server. We found it curious. <em>Everything</em> about it was different from what we were used to. The command line was black text on a white background, the connectors strange and foreign, and the keyboard layout was bizarre.</p>
<p>We never did much with it; turning it on made all the lights in his home dim, and our joint knowledge of UNIX was nonexistent. It sat in his bedroom for years supporting his television at the foot of his bed.</p>
<p>I never forgot that keyboard though. The thought that there was this alternative layout out there seemed intriguing to me.</p>
<h2>I am ruined for all other keyboards…</h2>
<p>Mac is the main platform at my new job. I found myself unhappy with Apple keyboards; they are flat, bland, and completely unsatisfying to type on. I dug out an old AppleDesign keyboard, and used that for several months but I still needed something more.</p>
<p>I read about Happy Hacking Keyboard, and after some deliberation decided to give it a try. I thought I could learn to accept most of the layout changes, but the lack of arrow keys on the &quot;Professional&quot; model made me squirm a little. Luckily there is a &quot;Lite&quot; version that includes arrow keys.</p>
<p>I purchased the <a href="https://amzn.com/B0000U1DJ2">Happy Hacking Keyboard Lite2</a>. </p>
<p>The Happy Hacking Keyboard much like the Sun Microsystem keyboard I found years earlier features a UNIX layout. They designed it with typing at a terminal in mind.</p>
<h2>A Better Layout</h2>
<p>For those unfamiliar with UNIX keyboards, there are a few distinctions.</p>
<img src="https://donatstudios.com/assets/43/hhkb.png" alt="Happy Hacking Keyboard" style=" display: block; max-width: 50%;float: right;margin-left: 22px;margin-bottom: 22px" /> 
<ul>
<li>
<p>The <strong>Control</strong> key moved to the <strong>Caps Lock</strong> keys usual position. This makes it much easier to hit common commands than its usual location, often without ever leaving home row. I can't imagine why anyone thought <strong>Caps Lock</strong> should have had this prominent of placement.</p>
</li>
<li>
<p>The <strong>Meta</strong> (<strong>◆ / ⌘ / Windows</strong>) key is in its rightful place next to the space bar, as on a Mac keyboard.</p>
</li>
<li>
<p>The <strong>Backspace</strong> key moved down a row to be above the <strong>Enter</strong> key. This change is my favorite change. Having a small reach, normally my hands have to leave home row to hit backspace. but on a UNIX board I have no issue hitting it with my pinky with the rest of my fingers remaining firmly on home row.</p>
</li>
<li>
<p><strong>Escape</strong> is also in reach of home row, placed where you would usually find <strong>~</strong>. I could have done without this change, but it is fine.</p>
</li>
<li>
<p>The <strong>~</strong> and <strong>\</strong> have moved to the usual location of the <strong>Backspace</strong>. Working exclusively on UNIX systems now, I don't mind the <strong>\</strong> being harder to reach. I could understand a Windows user finding this irritating.</p>
</li>
</ul>
<p>The HHKB is a minimal version of the UNIX keyboard. To this end the <strong>F Keys</strong> are mapped to <strong>Fn + 1, 2, 3, etc</strong>. The HHKB encourages you to stay on home row; you can hit any key on the keyboard without leaving it. I find my typing accuracy and speed to have improved thanks to its use.</p>
<p>The HHKB features one more change I don't care for. The <strong>Backspace</strong> key by <em>default</em> performs <strong>Forward Delete</strong>. Luckily changing this is easy by flipping a toggle switch on the back of the board.</p>
<h2>Closing</h2>
<p>I recommend this keyboard to <em>anyone</em> who spends a lot of time at the terminal. You are able to do more of what you need without ever leaving home row, every important key is within reach.</p>
<hr />
<h2>Elsewhere</h2>
<ul>
<li>Discussion on <a href="https://news.ycombinator.com/item?id=21912184">Hacker News</a></li>
<li>Mentioned on BSD Now Podcast: <a href="https://www.bsdnow.tv/333">Episode 333</a></li>
</ul>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Beyond-Compare-with-Git-in-OSX</guid>
      <title>Use Beyond Compare as a Git Diff/Merge Tool in OS X</title>
      <pubDate>Tue, 13 Nov 2012 18:07:03 -0600</pubDate>
      <link>https://donatstudios.com/Beyond-Compare-with-Git-in-OSX</link>
      <description><![CDATA[<p><strong>Update 12/26/2013:</strong> The Mac Beta is now available and there is no reason to use WineBottler anymore.  <a href="https://donatstudios.com/Beyond-Compare-Mac-Beta-with-Git-in-OSX">Instructions and an updated post are here</a>!</p>

<p>I switched from Windows to Mac for work a little over a year ago (I've been using Macs at home for years) and there is one program I still could not live without - Scooter Software's wonderful <a target="_blank" href="https://www.scootersoftware.com/">Beyond Compare</a>.  I've tried every single diff tool available and <strong>none</strong> have lived up to Beyond Compare. It truly lives up to its name.</p>

<p>I've had Beyond Compare running in <a target="_blank" href="https://winebottler.kronenberg.org/">WineBottler</a> for a while now, but was never able to get passing arguments to work correctly.  Today I had a revelation.  On creating the bottle it asks you for arguments to pass the executable, I had not provided any.  Ends up it then passes the first argument as an empty string, which prevented the first argument being a file! Huzzah, an easy fix, pass it an innocuous argument! So here are some directions on getting it set up!</p>

<h2>Beyond Compare Installation / Configuration</h2>

<p>The first step is to <a target="_blank" href="https://winebottler.kronenberg.org/">download</a> WineBottler if you don't already have it. Just drag the <em>WineBottler.app</em> and <em>Wine.app</em> into your <code>/Applications</code> directory. Then <a target="_blank" href="https://www.scootersoftware.com/download.php">download</a> the Windows version of the Beyond Compare Installer and hang onto it.</p>

<p>Launch <em>WineBottler.app</em>. In the left pane choose "Create Custom Prefixes", then for the <strong><em>Install File</em></strong>, choose the Beyond Compare Installer downloaded earlier.  To my knowledge no Winetricks are necessary.  Check the <strong><em>Self-contained</em></strong> checkbox to make a stand alone application.</p>

<p><strong>IMPORTANT</strong>: WineBottler passes a blank argument if you leave the <strong><em>Runtime Arguments</em></strong> blank, which Beyond Compare interprets as the first filename - an annoying bug that took me months to figure out. To get around this we can pass it an innocuous argument (<a href="https://www.scootersoftware.com/help/index.html?command_line_reference.html">see arguments</a>).  I chose <code>/nobackups</code> because I don't want backups and they are turned off by default anyway.</p>

<div style="max-width: 962px;"><img src="https://donatstudios.com/assets/42/winebottler_beyond_compare_settings.png" width="100%"></div>


<p>Click <strong>Install</strong>.  It will ask you to save your App, do so in <code>/Applications</code>. I named mine as <code>BeyondCompare.app</code> and paths later will reflect this.</p>

<p>Follow the installer. After the installer completes Beyond Compare may pop up if you've left the launch checkbox checked.  Just close the window. Choose <strong><code>BCompare.exe</code></strong> when it asks you for the default executable (<code>BComp.exe</code> is for batch scripting).</p>

<h2>Git Configuration</h2>

<p>The paths in the following presume you named your app <code>BeyondCompare.app</code> so these may be adjusted as necessary.</p>

<p>Add the following to your <code>~/.gitconfig</code> file. Remove anything existing that contradicts these settings. I've named the diff and merge tools <code>bc3wb</code> as git already has baked in support for the Windows version, which it calls <code>bc3</code>.</p>

<h3>Beyond Compare 3 Professional</h3>

<pre><code>[diff]
    tool = bc3wb
[difftool "bc3wb"]
    cmd = /Applications/BeyondCompare.app/Contents/MacOS/WineBottlerStarter \"$LOCAL\" \"$PWD/$REMOTE\"
[merge]
    tool = bc3wb
[mergetool "bc3wb"]
    cmd = /Applications/BeyondCompare.app/Contents/MacOS/WineBottlerStarter \"$PWD/$LOCAL\" \"$PWD/$REMOTE\" \"$PWD/$BASE\" \"$PWD/$MERGED\"
    keepBackup = false
    trustExitCode = false
</code></pre>

<h3>Beyond Compare 2 / 3 Standard</h3>

<pre><code>[diff]
    tool = bc3wb
[difftool "bc3wb"]
    cmd = /Applications/BeyondCompare.app/Contents/MacOS/WineBottlerStarter \"$LOCAL\" \"$PWD/$REMOTE\"
</code></pre>

<p><strong>Note</strong>: I am unable to get the mergetool portion working in Beyond Compare 2 or 3 Standard as WineBottler is interpreting <code>/savepath=</code> as a filepath and converting it to <code>z:\savepath=</code> rather than properly passing the arguments.  If anyone has any advice on how to fix this I would be ecstatic.</p>

<p>Voila, now you can <code>git mergetool</code> and <code>git difftool</code> it up!</p>

<div style="max-width: 1229px;"><img src="https://donatstudios.com/assets/42/winebottler_beyond_compare_3way_merge.png" width="100%"></div>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/PHP-Static-Nonsense</guid>
      <title>Static::? More like Lies::</title>
      <pubDate>Mon, 22 Oct 2012 18:28:30 -0500</pubDate>
      <link>https://donatstudios.com/PHP-Static-Nonsense</link>
      <description><![CDATA[<p>In my professional work, we have a system that utilizes __call and __callStatic for caching of certain method calls. We have been running into a problem where calling an undefined method via <code>::</code> within the class itself will trigger <code>__call</code> rather than the expected <code>__callStatic</code>.</p>
<h2>The Problem</h2>
<p>The following code example illustrates the issue.</p>
<pre><code>&lt;?php

class foo {
    function __call($name, $args){
        echo "__call " . $name . PHP_EOL;
    }

    public static function __callStatic($name, $args) {
        echo "__callStatic " . $name . PHP_EOL;
    }

    public function make_call_self() {
        self::ted();
    }

    public function make_call_static() {
        static::ted();
    }

    function make_call_classname() {
        foo::bob();
    }
}

$x = new foo();
$x-&gt;make_call_self();
$x-&gt;make_call_static();
$x-&gt;make_call_classname();
foo::sam();</code></pre>
<p>The above returns the following when executed in PHP 5.3+ (excluding PHP 5.3.3, we'll get to this later)</p>
<pre><code>__call ted
__call ted
__call bob
__callStatic sam</code></pre>
<p><a href="https://3v4l.org/gIP1H">See Example</a></p>
<p>As you can see, all of those ideally should have triggered <code>__callStatic</code> but all save the last <code>foo:::sam()</code> triggered call. If you click the link to the example execution above, it shows that this did in fact work in PHP 5.3.3 but has since been broken.</p>
<p>I was just about to post this as a bug on PHP.net, but my friend <a href="https://joelclermont.com/">Joel Clermont</a> brought to my attention several cases of this being submitted in the past. One example being fairly infamous. Remember how I mentioned it working in 5.3.3? It was actually <em>fixed</em> in that release in response to a bug report. The problem apparently is that it broke a lot of things to do with the at the time new late static binding, and was rolled back in the next release.</p>
<p>From the <a href="https://bugs.php.net/bug.php?id=45159">horses mouth</a>, via colder@php.net</p>
<blockquote>
<p>You're misunderstanding the meaning of static::foo();</p>
<p>it doesn't mean &quot;call foo statically&quot; but rather &quot;use runtime information to get the class currently called&quot;, and do a static(or not) call to the method of that class.</p>
</blockquote>
<p>So static doesn't mean static apparently.</p>
<h2>Understanding</h2>
<p>After quite a bit of reading and a fair deal of explination by my friend Joel, I came to understand that <code>::</code>, unlike C++, doesn't mean <em>static</em> at all. Rather it is a &quot;<a href="https://php.net/manual/en/language.oop5.paamayim-nekudotayim.php">Scope Resolution Operator</a>&quot; which determines scope from &quot;context&quot; meaning that when called from within an instance of an object, it makes instance calls if possible, rather than static calls. Infact, in 5.3+ you can even do <code>$this::</code>. The consequence of this is that <code>self::</code> nor <code>static::</code> are necessarily static calls.</p>
<p>This all rubs me wrong; I don't see the benefit to this behavior on the users end. Perhaps there is some backend optimization this allows. Even if that were the case I still feel like you could short circuit undefined self:: calls to __callStatic without breaking it.</p>
<h2>Solutions</h2>
<p>There were several &quot;solutions&quot; given, none of which are ideal.</p>
<ul>
<li>Call __callStatic directly.</li>
<li>Use <a href="https://us.php.net/manual/en/function.forward-static-call.php">forward_static_call</a>.</li>
<li>Call the class from within a lambda. (After some testing I see that this does not work in PHP 5.4+)</li>
</ul>
<p>None of these are particularly clean nor ideal. Instead I would like PHP to either add a new call like <code>stat::</code> or simply fix <code>self::</code> and <code>static::</code> to trigger the proper call. The latter of course would be ideal, especially as <code>static</code> has the word <em>static</em> in its name.</p>
<h2>See</h2>
<ul>
<li><a href="https://bugs.php.net/bug.php?id=62333">https://bugs.php.net/bug.php?id=62333</a></li>
<li><a href="https://bugs.php.net/bug.php?id=45159">https://bugs.php.net/bug.php?id=45159</a></li>
</ul>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Late-Definition-of-PHP-Class-Members</guid>
      <title>Late Definition of PHP Class Members</title>
      <pubDate>Mon, 20 Aug 2012 23:29:13 -0500</pubDate>
      <link>https://donatstudios.com/Late-Definition-of-PHP-Class-Members</link>
      <description><![CDATA[<p>Working on a class in our application, I discovered that it was being included before a number of constants it used for its members were even defined.  Puzzled on how this had never been a problem I started to experiment; echoing the member at the bottom of the class file after the class closed showed the expected <code>CONSTANT_NAME</code>.  I was very confused at runtime the value of the member in the constructor and elsewhere in the class appeared correct.</p>

<p>To test what all behaves in this manner I threw together this simple set of three tests including instance members, static members, and class constants.</p>

<pre><code>&lt;?php

class foo {
    public $aaa = TEST_CONSTANT;
}

class bar {
    static $bbb = TEST_CONSTANT;
}

class baz {
    const ccc = TEST_CONSTANT;
}

define('TEST_CONSTANT', "Weird Result\n");

$x = new foo();

echo $x-&gt;aaa;
echo bar::$bbb;
echo baz::ccc;
</code></pre>

<p>Returns the following:</p>

<pre><code>Weird Result
Weird Result
Weird Result
</code></pre>

<p>They all behave in the same manner, indicating that the members and class constants are not defined until the classes are invoked.</p>

<p>This is further demonstrated by the following example:</p>

<pre><code>&lt;?php

class foo {
    public $aaa = TEST_CONSTANT;
}

$x = new foo();
echo $x-&gt;aaa;

define('TEST_CONSTANT', "Weird Result. \n");

$y = new foo();
echo $y-&gt;aaa;
</code></pre>

<p>Which returns:</p>

<pre><code>TEST_CONSTANTTEST_CONSTANT
</code></pre>

<p>So by touching the foo class, its initial value for $aaa has been locked in at what <code>TEST_CONSTANT</code> is at that time.  Some testing indicates this also stands true for static methods and class constants as well.</p>

<p>This is a strange behavior I wanted to draw attention to.  I certainly wouldn&rsquo;t <strong>try</strong> to write something that was dependent on it, although our application currently seems to be.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Developer-Resum%C3%A9-Interview-Advice</guid>
      <title>Web Developer Resum&#xE9;/Interview Advice</title>
      <pubDate>Fri, 20 Jul 2012 02:20:37 -0500</pubDate>
      <link>https://donatstudios.com/Developer-Resum%C3%A9-Interview-Advice</link>
      <description><![CDATA[<p>As Lead Developer at my previous company I interviewed many Developers. I wanted to provide some advice to Developers on how to come off more positively.</p>
<p>For the purposes of this post <strong><em>I</em></strong>, <strong><em>me</em></strong> and all other pronouns referring to myself refer to a hiring professional.</p>
<h2>Some Basics Up Front</h2>
<p>Google yourself. Google your email address, I will! There will be lots of Googling before you're hired. I am well versed in the darker Google arts, and odds are I will find you. If I don't find you, that's almost as detrimental as finding something fairly negative; you are interviewing for a Web related job, but don't have a presence on the Web?</p>
<p>I will look at your Facebook / Twitter if they're open to the public.  This can actually be a very good thing if your posts are nerdy.</p>
<p>Try not to have the top result for your name be the police report for the night you spent in jail.  This is strangely common in my experience. Demonstrate some SEO skills, buy yourname.name! Do something to get that big red mark off the first page.</p>
<p>One of the most important things I can recommend is having a website.  Not only will it ideally outrank your jail time on Google presuming your name's all over it, but ideally it's a technology website where you talk about experiences and things you've learned you want to share with others. This helps support your resume and your interview.  This is very important, it brings substance to the game.  It shows your interest in technology goes beyond making money.  Also, lots of bonus points for not using Wordpress, I know it's tempting, but build something your self or use something more exotic.  Any slob with a 56k can throw together a Wordpress site.  Be sure to list it on your resume. I don't care how bad it is, fix it up, that's no excuse. If you have your name, or your email address or anything else I can surmise about you, I will find it. If I find it, I'll presume you were hiding it from me - and that doesn't come off well.</p>
<p><strong>Have extracurricular code samples.</strong>  Using <em>Github</em> or other similar service, and more importantly including a link on your resume not only shows a willingness to stand by your code, but show a versed knowledge of SCM. If you don't build things in your own time, you're not learning as much as you could be.</p>
<p>The best thing I can recommend is writing your own framework.  All the best developers I know have at one time or another created their own framework. It is an amazing learning experience for you, and moreover it gives a huge amount of insight into the way you <em>think</em>, and the way you <em>prefer</em> to code</p>
<h2>Resume Basics</h2>
<p>Your resume exists to spark a conversation; it shouldn't be huge and it doesn't need every minute detail about every project you've worked on. If it goes over four pages odds are I'm not reading it all.</p>
<p>Leaving pieces vague helps me ask questions, which can help spark a conversation.  Often the most important bits are in questions <em>I didn't ask</em>.</p>
<p>Be painfully consistent. Spelling/casing the same technology multiple ways reflects poorly on you. If you can't be consistent in your resume, what is to say you'd be consistent in your work?</p>
<p>Have the same name, address, email, and phone number on <strong>every</strong> document. Not only is inconsistency here really awkward when trying to contact you, but it helps me keep my notes on you organized. I may be interviewing several people that day and often these documents will be stacked on my desk between interviews.</p>
<p>Mention things you've built from the ground up. While framework knowledge is useful in places looking for an expert in a specific framework, ground up work demonstrates a broader depth of knowledge.</p>
<p>Don't send your resume as a .txt file.  It's not cool.</p>
<h2>Interview Basics</h2>
<p>Always be on time, especially for a phone interview. Gathering people to make a call just for you to not pick up is infuriating.</p>
<p>When you are asked a question, elaborate! Go off on small tangents about why some piece worked or didn't.  Smile devilishly and mention how something saved the day!  That said, don't ramble, there is a fine balance.  Don't spend minutes answering what should have been a several second answer.</p>
<p>Bring samples of work not included on your resumé. These are incredible talking points.</p>
<ul>
<li>Be ready to identify what specific pieces you worked on.</li>
<li>Don't include very similar examples.</li>
</ul>
<p>Ahead of time consider interesting challenges faced working on each, you will be asked. If the answer is none, it's probably not worth including.</p>
<h2>Other Important Bits</h2>
<p>Try to not be nervous. Easier said than done, but <em>you don't need this job</em>, or at least act like you don't. That's not to say <em>be cocky</em>  - just that showing neediness as with a personal relationship, can scare people away.</p>
<p>Experience is far more important than education. Talk less about what you've done in school and more about what you've done professionally or even better on your own.</p>
<p>Don't drone on about wanting to work from home or needing quiet.  Every developer is a bit of an introvert, but going on about it indicates problems working with others to come.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/PixelCircleGenerator</guid>
      <title>Pixel Circle / Oval Generator</title>
      <pubDate>Fri, 18 May 2012 04:09:59 -0500</pubDate>
      <link>https://donatstudios.com/PixelCircleGenerator</link>
      <description><![CDATA[

<fieldset style="float: right">
<legend>Donations</legend> 

    <p>If you appreciate this tool, please consider a donation. It truly helps me out.

    <div style="text-align: center">
	<div style="display: inline-block; vertical-align: top">
		<p><strong>PayPal</strong>:</p>

		<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
			<input type="hidden" name="cmd" value="_s-xclick" style>
			<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHLwYJKoZIhvcNAQcEoIIHIDCCBxwCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYAy4BMQR+gXgzT+VlMBgLYMmHmJUzkedAnz3pQKsB0ZJPBkPs8UGmCDeD3fwMMAEiMxqanNw+fX5XtRJp18ONZmM6NLXPy9MtWM5e4W4ZaaKwvYhg+BqjGec1IE0yuKHvwrb9uBIpYnYrhj9Ar4TKo+y3jDj0Jyeyb8F4MUu3X8BDELMAkGBSsOAwIaBQAwgawGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIRndc+ju3hn+AgYgXZRvvb1qCbLB5lLTjPDeue/6MBVENfs1z2jAUSFA69Ppvo3gkF9PKu0q+iwcIxSiKA3eJMVP6m4cayd1UFLSgN9rHbisgz3RRRqMMZXIoP7oZ45SXM+xl6TUKcGc7U14wRNuj5o1K8HqC60UZaHr37tiiubp/ngw9i5l73kHBoqajlS3yS0bioIIDhzCCA4MwggLsoAMCAQICAQAwDQYJKoZIhvcNAQEFBQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0MDIxMzEwMTMxNVoXDTM1MDIxMzEwMTMxNVowgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBR07d/ETMS1ycjtkpkvjXZe9k+6CieLuLsPumsJ7QC1odNz3sJiCbs2wC0nLE0uLGaEtXynIgRqIddYCHx88pb5HTXv4SZeuv0Rqq4+axW9PLAAATU8w04qqjaSXgbGLP3NmohqM6bV9kZZwZLR/klDaQGo1u9uDb9lr4Yn+rBQIDAQABo4HuMIHrMB0GA1UdDgQWBBSWn3y7xm8XvVk/UtcKG+wQ1mSUazCBuwYDVR0jBIGzMIGwgBSWn3y7xm8XvVk/UtcKG+wQ1mSUa6GBlKSBkTCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCBXzpWmoBa5e9fo6ujionW1hUhPkOBakTr3YCDjbYfvJEiv/2P+IobhOGJr85+XHhN0v4gUkEDI8r2/rNk1m0GA8HKddvTjyGw/XqXa+LSTlDYkqI8OwR8GEYj4efEtcRpRYBxV8KxAW93YDWzFGvruKnnLbDAF6VR5w/cCMn5hzGCAZowggGWAgEBMIGUMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTMwMjE1MDIwNjQzWjAjBgkqhkiG9w0BCQQxFgQU6pFsKsltm2oPpMCSwqPuFB2OShEwDQYJKoZIhvcNAQEBBQAEgYAXkXscShHz6Q+ckNrEnCQGB5z24Y9I2aSGHS9V3jHNbD+fHyUiGor0X2D9DmlJMi3ksBgJIVnTnv/+0ju2yN8WX3178bsoK+yliHYPRQpi2fANfVGLgmr2A33w06o2xi2DLxjL10JTWpxVSJRKQ64h/neP8tdwym8PumZwB0rB8w==-----END PKCS7-----
		">
			<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" style="border: none; padding: 0">
			<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
		</form>
	</div>
	<div style="display: inline-block; vertical-align: top">
		<p><strong>GitHub</strong>:</p>
		 
	</div>
	<div style="display: inline-block; vertical-align: top">
	<div style="float: left">
		<p><strong>Ko-Fi</strong>:</p>
		<a href='https://ko-fi.com/P5P1PFQ11' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
	</div>
</div>

</fieldset>

<p>Playing Minecraft, I like making circular things.  I used a chart while I was building, but wanted to be able to make variable size ovals which is something I couldn't find a decent chart of or generator capable of, so I created this!</p>

<br clear="all" />

<div style="max-width: 100vw; overflow: auto">
	<hr>

	 
	<ins class="adsbygoogle"
		 style="display:block; text-align:center;"
		 data-ad-layout="in-article"
		 data-ad-format="fluid"
		 data-ad-client="ca-pub-4216434847709731"
		 data-ad-slot="9361452374"></ins>
	 

	<hr>
	
</div>


<fieldset>
	<button id="go-fullscreen">Go Fullscreen</button>

	<button onclick="document.getElementById('circleGeneratorIframe').src='assets/38/Circle-Generator/'" style="font-size: .8em; padding: 5px">Show Old Generator</button>



	 
	<center><small><a href="https://github.com/donatj/Circle-Generator" target="_blank">Fork my source on Github!</a></small></center>
</fieldset>
]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Simple-Single-Pass-Doubly-Linked-Flat-Tree-Building-Algorithm</guid>
      <title>Simple Single Pass Doubly Linked Flat Tree Building Algorithm</title>
      <pubDate>Fri, 06 Jan 2012 21:55:47 -0600</pubDate>
      <link>https://donatstudios.com/Simple-Single-Pass-Doubly-Linked-Flat-Tree-Building-Algorithm</link>
      <description><![CDATA[<p>A hierarchy is a common structure, especially for structuring pages in web development. A problem with hierarchies is they often need to be flattened to be stored in a relational database, and then expanded again after being pulled out.</p>
<h4>An Example Hierarchy</h4>
<ul>
<li>Top level item
<ul>
<li>Sub Item
<ul>
<li>Sub Sub Item
<ul>
<li>Sub Sub Sub Item</li>
</ul></li>
</ul></li>
<li>Sibling of &quot;Sub Item&quot;</li>
</ul></li>
<li>Top level as well</li>
</ul>
<p>A common and simple way to store them is with a table structure similar to the following.</p>
<table>
<thead>
<tr>
<th>id</th>
<th>parent_id</th>
<th>title</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>0</td>
<td>Top level item</td>
</tr>
<tr>
<td>2</td>
<td>0</td>
<td>Top level as well</td>
</tr>
<tr>
<td>3</td>
<td>1</td>
<td>Sub Item</td>
</tr>
<tr>
<td>4</td>
<td>1</td>
<td>Sibling of &quot;Sub Item&quot;</td>
</tr>
<tr>
<td>5</td>
<td>3</td>
<td>Sub Sub Item</td>
</tr>
<tr>
<td>6</td>
<td>6</td>
<td>Sub Sub Sub Item</td>
</tr>
</tbody>
</table>
<p>Now there are several ways to process this into an array or other navigable structure. A painfully common and naïve way would be <em>recursive queries</em> ala:</p>
<pre><code>function fetch_items($parent = 0) {
    $data = array();
    $qry = mysql_query("select * from `tablename` where parent_id = " . (int)$parent );
    while( $row = mysql_fetch_array($qry) ) {
        $data = array('data' =&gt; $row, 'children' =&gt; fetch_items($row['id']));
    }
    return $data;
}</code></pre>
<p>The problem with this approach is the sheer number of queries this approach uses. Our simple example hierarchy would execute 6 queries, whereas more complicated hierarchies would execute many more.</p>
<p>A vastly superior approach is to pull all the data out with a single query, and then process it into the structure we need. There are several ways to do this, but I find the following DOM like structure this generates incredibly useful.</p>
<pre><code>function fetch_tree() {
    $data = array();
    $qrt = mysql_query("select * from `tablename` order by parent_id asc");
    while( $row = mysql_fetch_array($qry) ) {
        $data[ $row['id'] ]['data'] = $row;
        $data[ $row['parent_id'] ]['children'][ $row['id'] ] =&amp; $data[ $row['id'] ];
        $data[ $row['id'] ]['parent'] =&amp; $data[ $row['parent_id'] ];
    }
    return $data;
}</code></pre>
<p>Essentially, in a <strong>single pass</strong> through the data, this gets us an array of hierarchy elements, where rather than an element directly having its children, every entry has a pointer to its parent element as well as an array of pointers to its children. This is incredibly flexible, and as the tree is flattened it allows you to start at any point in the hierarchy by ID ala <code>$data[&lt;$id&gt;]</code>, and navigate your way up or down. Viewing any elements children is as simple as <code>$data[&lt;$id&gt;]['children']</code>, and its parent as simple as <code>$data[&lt;$id&gt;]['parent']</code>.</p>
<p>I use this basic methodology a ton in my work, personal and professional (the comments and sitemap of this site are powered by it!) but the concept seems foreign when I try to explain it to people. I hope this explanation sparks some ideas for people.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Damn-Simple-PHP-ASCII-Art-Generator</guid>
      <title>Simple PHP ASCII Art Generator</title>
      <pubDate>Thu, 10 Nov 2011 13:18:43 -0600</pubDate>
      <link>https://donatstudios.com/Damn-Simple-PHP-ASCII-Art-Generator</link>
      <description><![CDATA[<p>So yesterday I was bored and asked my friend what I should do.  She replied &quot;I don't know, draw ASCII art or something!&quot; and sitting there with the terminal open I kind of wondered what it would take to write one of those image to ASCII scripts.</p>
<p>20 minutes later I had the following PHP Shell Script! It works by simply sampling the saturation of every X<i>th</i> pixel and mapping that saturation to a character from an array. There is no nearest-neighboring or anything like that, just straight up sampling.</p>
 
<h2>Huzzah!</h2>
<p><a href="https://donatstudios.com/assets/36/AsciiJorie.png" rel="lightbox"><img src="https://donatstudios.com/assets/36/AsciiJorie.png" alt="ASCII JORIE is made of ASCII" style="margin: auto; display: block; width: 44%; float: right;" /></a></p>
<p>And there you have it! A whopping 35 lines of code. There is a ton of tweaking you can do like changing the array of characters, or adjusting the $scale variable which is simply how wide of a sample it makes.  Setting it to 1 for instance should sample every pixel on the Y and every other pixel on the X (so it scales properly). To the right is a sample output of the script.</p>
<h2>Try it for yourself!</h2>
<p>The script accepts local or remote image paths as the first argument.  The following is a simple usage example you can use to get you started.</p>
<pre><code class="language-console">./ascii.php "https://donatstudios.com/images/site/JesseDonat.jpg"</code></pre>
<p>It was a fun little project and I look forward to seeing if/what the internet comes up with for it. <a href="https://gist.github.com/1353237">Fork me on Github</a>, or post links in the comments!  Happy hacking!</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Lion-Photobooth-Repair</guid>
      <title>Photo Booth Plist Rebuild/Repair Script</title>
      <pubDate>Wed, 27 Jul 2011 05:13:20 -0500</pubDate>
      <link>https://donatstudios.com/Lion-Photobooth-Repair</link>
      <description><![CDATA[<fieldset><legend>Update 2023-10-17</legend>
The script has been verified to work in macOS Sonoma.
</fieldset>
<p>When I switched from an older MacBook Pro to a MacBook Air the backup from my old MacBook Pro was corrupted and unmountable. However, I had preserved a backup of my Photo Booth Photos and wanted the Photo Booth application to recognize them. This is just a matter of creating a <code>Recents.plist</code>, an ordered list of the image files.</p>
<p>While adding these photos to the <code>Recents.plist</code> file Photo Booth uses seems straightforward enough, arranging hundreds of photos from different naming schemes across various OS versions manually is pretty tedious.</p>
<p>To fix this, I wrote a script that automatically creates a new plist file with the photos in the correct order. As a safety measure, the script backs up the existing Recents.plist as Recents.plist.bk.[time], allowing easy restoration if needed.</p>
<h2>Usage</h2>
<h3>Please Note - <em>NEW</em></h3>
<p>This script is compatible with macOS versions up to macOS 14 (Sonoma). However, it relies on PHP. While PHP was bundled with macOS until macOS 12 (Monterey), subsequent versions require a separate PHP installation.</p>
<p>I recommend using Homebrew. You can find Homebrew installation instructions on their <a href="https://brew.sh/">official website</a>.</p>
<p>After setting up Homebrew, install PHP by entering the following command:</p>
<pre><code class="language-console">brew install php</code></pre>
<h3>To Begin With</h3>
<ul>
<li>Ensure all photos and movies you wanted loaded into Photo Booth are located in your Pictures/Photo Booth Library/Pictures/ folder.</li>
<li>Ensure the Photo Booth Application is <strong>closed</strong> to avoid overwriting our changes.</li>
</ul>
<h3>Simple Method</h3>
<p><small>Added September 10, 2012</small><br/>
Open Terminal and run the following command.</p>
<pre><code class="language-console">curl -Ls https://gist.githubusercontent.com/donatj/1108691/raw/Photobooth_rebuild.sh.php | php</code></pre>
<h3>Git Method</h3>
<p>Simply executing the following should both download it and set it executable.</p>
<pre><code class="language-console">git clone https://gist.github.com/1108691.git PhotoBoothRepair</code></pre>
<h3>Non-Git Method</h3>
<ul>
<li><a href="https://gist.githubusercontent.com/donatj/1108691/raw/Photobooth_rebuild.sh.php">Save the script</a> somewhere accessible to you via Terminal.</li>
<li>Navigate in Terminal to the directory where you saved the shell script</li>
<li>Set the file executable by running <pre><code class="terminal">chmod +x Photobooth_rebuild.sh.php</code></pre></li>
<li>Execute the shell script <pre><code class="terminal">./Photobooth_rebuild.sh.php</code></pre></li>
<li>All Done - Fire up Photo Booth to see our results!</li>
</ul>
<h3>Known Limitations</h3>
<ul>
<li>All Leopard format names will come first regardless of date simply for lack of anything to go on. They're just numbered and contain no date information.</li>
</ul>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/PhpED-Dark-Theme</guid>
      <title>Nusphere PhpED Dark Theme</title>
      <pubDate>Thu, 07 Jul 2011 22:34:57 -0500</pubDate>
      <link>https://donatstudios.com/PhpED-Dark-Theme</link>
      <description><![CDATA[<p>It would seem there is a fair deal of call for an updated version of my PhpED dark theme from colleagues as well as random folks on the internet who’ve seen screenshots of my PhpED installation.  I previously had my Dark Theme for 5 linked on my Review of PhpED, but I figured I could have a page dedicated to it.</p>

<fieldset><legend>Screenshots</legend>
<a href="https://donatstudios.com/assets/34/phped_php.png" rel="lightbox"><img src="https://donatstudios.com/assets/34/phped_php.png" alt="PhpED Dark Theme PHP" style="margin: auto; display: block; width: 30%; float: left;" /></a>
<a href="https://donatstudios.com/assets/34/phped_css.png" rel="lightbox"><img src="https://donatstudios.com/assets/34/phped_css.png" alt="PhpED Dark Theme CSS"  style="margin: auto; display: block; width: 30%; float: right;" /></a>
<a href="https://donatstudios.com/assets/34/phped_html.png" rel="lightbox"><img src="https://donatstudios.com/assets/34/phped_html.png" alt="PhpED Dark Theme HTML" style="margin: auto; display: block; width: 30%; margin: 0 auto;" /></a>
</fieldset>
<h2>Download and Installation</h2>
<p>The first step is to download the proper version.  <br />Click here to download the <a href="https://raw.github.com/gist/734872/57bea8d954110b7849bcea25851495d6a3f4ffa3/hl.cfg" target="_blank">PhpED 6 Version</a> or here to download the <a href="https://raw.github.com/gist/734872/69fc228a0c174f10a2aa571b0532113cdaa9bfeb/hl.cfg"  target="_blank">PhpED 5 Version</a>.</p>
<p>After you have downloaded the file you will need to replace your hl.cfg. </p><p>In Windows 7 or Vista this should be located in or around: <pre>C:\Users\<strong>YOUR_USER_NAME</strong>\AppData\[<em>Roaming</em> / <em>Local</em>]\NuSphere\PhpED\config</pre>
<br />Likewise in XP it should be located in something like: <pre>C:\Documents and Settings\<strong>YOUR_USER_NAME</strong>\Application Data\NuSphere\PhpED\config</pre>
</p><p>The final step is to simply change your background color to a dark color.  The background color is oddly saved in a different configuration file than the highlighting. This can be found in <strong>Tools</strong> &rArr; <strong>Settings</strong> &rArr; <strong>Editor</strong> &rArr; <strong>Appearance</strong></p>
<p>While you're in there you may want to tweak the settings some to your taste, but that is all there is to it.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/MPO-to-JPEG-Stereo</guid>
      <title>Convert MPO to Anaglyph or Stereo JPEG</title>
      <pubDate>Wed, 13 Apr 2011 23:41:33 -0500</pubDate>
      <link>https://donatstudios.com/MPO-to-JPEG-Stereo</link>
      <description><![CDATA[<fieldset style="float: left; width: 300px; margin-bottom: 22px; margin-right: 22px;"><legend>Convert MPO to JPEG</legend>
	 
</fieldset>

<p style="clear:right;">
This tool converts MPO files from devices like the Nintendo 3DS, Fujifilm FinePix Real 3D, or other 3D cameras that support MPO files, into more usable JPEGs (.jpg). It's useful for viewing and sharing 3D photos in a more commonly used format.
</p>

<p style="clear:right;">
Unlike many other online tools that simply strip one of the eyes from the result, presenting you with a single image, this tool incorporates both the left and right photos, preserving the depth and detail of the original 3D images.
</p>

<p style="clear:right;">
Additionally, it can convert MPO files into an anaglyph format, allowing the images to be viewed with standard bi-color 3D glasses.
</p>

<fieldset style="margin-bottom: 22px; clear: both;"><legend>Gallery</legend>

</fieldset>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/WPF-VBNet-JumpList-Example</guid>
      <title>Simple WPF VB.Net JumpList Example</title>
      <pubDate>Thu, 07 Apr 2011 05:00:31 -0500</pubDate>
      <link>https://donatstudios.com/WPF-VBNet-JumpList-Example</link>
      <description><![CDATA[<p>Today I decided to spruce up a VB.Net application I wrote in
	college and still use almost daily. In the process of converting it
	from .Net 2 to .Net 4 I wanted to add a splash of Windows 7 goodness; there were some
	very simple operations I wanted to add to JumpLists.  The problem I ran into with all the examples I
	could find is that they either called entirely different applications (the Microsoft
	examples all called <i>Notepad</i>) or
	the example was in C#.  The
	application is in VB.Net and I intend to keep it that way, as it is my .Net
	language of choice.</p>
<p>The interesting problem with JumpLists is they are just
	shortcuts.  They call an executable with
	the specified arguments. Microsoft’s reasoning for this is that they are still
	available while the program is no longer running. The initial <i>execution</i> of an application placed in
	the taskbar registers the JumpLists, after which point they are cached by the
	system and continue to be available even when the application is no longer
	running. This potentially limits the usefulness of JumpLists as they do start a
	new instance of your application.  If
	anyone knows how to catch the arguments on a single instance application, I
	would be <i>very</i> interested to hear
	it.  The best I’ve come up with is simply
	passing messages to my current instance from a new instance and then
	terminating the second instance, but this is awkward to my eye.</p>
<p>XAML can be used to create JumpLists in the Application.xaml
	file, which many other examples will demonstrate – but the issue here is there
	is no way to get the path to the current executable via XAML, so you can only
	call <i>other</i> executables whom you know
	the full path to.</p>
<p>The answer then is to create our JumpLists dynamically.  The best place to do this is in your
	Application.xaml.vb file.  The New method
	will be used to instantiate and register the JumpLists, whereas the Startup
	event will be used to handle arguments passed to the new instance of our
	application.</p>
<p>Below is a very simple example of JumpLists not too far off from what I ended up going with in my own application.  Feel free to leave me comments as to any improvements or changes you would make, or go ahead and fork the gist.</p>

 ]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/PHP-Parser-HTTP_USER_AGENT</guid>
      <title>PHP User Agent Parser</title>
      <pubDate>Tue, 29 Mar 2011 22:51:09 -0500</pubDate>
      <link>https://donatstudios.com/PHP-Parser-HTTP_USER_AGENT</link>
      <description><![CDATA[<fieldset><legend>Demo</legend>

</fieldset>
<p><br clear="all"/></p>
<h2>Requirements</h2>
<ul>
<li>PHP &gt;= 5.4.0</li>
</ul>
<h2>Download</h2>
<p>The script is available over on <a href="https://github.com/donatj/PhpUserAgent">GitHub</a> or via Composer:</p>
<pre><code class="language-console">$ composer require 'donatj/phpuseragentparser'</code></pre>
<h2>Usage</h2>
<p>The original procedural use is as simple as:</p>
<pre><code class="language-php">&lt;?php

// v0 style global function - @deprecated
$uaInfo = parse_user_agent();
// or
// modern namespaced function
$uaInfo = donatj\UserAgent\parse_user_agent();

echo $uaInfo[donatj\UserAgent\PLATFORM] . PHP_EOL;
echo $uaInfo[donatj\UserAgent\BROWSER] . PHP_EOL;
echo $uaInfo[donatj\UserAgent\BROWSER_VERSION] . PHP_EOL;
</code></pre>
<p>The new object oriented wrapper form:</p>
<pre><code class="language-php">&lt;?php

use donatj\UserAgent\UserAgentParser;

$parser = new UserAgentParser();

// object-oriented call
$ua = $parser-&gt;parse();
// or
// command style invocation
$ua = $parser();

echo $ua-&gt;platform() . PHP_EOL;
echo $ua-&gt;browser() . PHP_EOL;
echo $ua-&gt;browserVersion() . PHP_EOL;</code></pre>
<h2>Currently Detected Platforms</h2>
<p>see: <a href="https://github.com/donatj/PhpUserAgent#currently-detected-platforms">List of Detected Browsers</a></p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/RewriteRule_Generator</guid>
      <title>Batch RewriteRule Generator</title>
      <pubDate>Tue, 01 Mar 2011 01:58:44 -0600</pubDate>
      <link>https://donatstudios.com/RewriteRule_Generator</link>
      <description><![CDATA[<fieldset style="float: right; margin-left: 11px; text-align: center"><legend>Find this useful?</legend>Buy me a coffee! <hr /> <div style="text-align: center">
	<div style="display: inline-block; vertical-align: top">
		<p><strong>PayPal</strong>:</p>

		<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
			<input type="hidden" name="cmd" value="_s-xclick" style>
			<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHLwYJKoZIhvcNAQcEoIIHIDCCBxwCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYAy4BMQR+gXgzT+VlMBgLYMmHmJUzkedAnz3pQKsB0ZJPBkPs8UGmCDeD3fwMMAEiMxqanNw+fX5XtRJp18ONZmM6NLXPy9MtWM5e4W4ZaaKwvYhg+BqjGec1IE0yuKHvwrb9uBIpYnYrhj9Ar4TKo+y3jDj0Jyeyb8F4MUu3X8BDELMAkGBSsOAwIaBQAwgawGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIRndc+ju3hn+AgYgXZRvvb1qCbLB5lLTjPDeue/6MBVENfs1z2jAUSFA69Ppvo3gkF9PKu0q+iwcIxSiKA3eJMVP6m4cayd1UFLSgN9rHbisgz3RRRqMMZXIoP7oZ45SXM+xl6TUKcGc7U14wRNuj5o1K8HqC60UZaHr37tiiubp/ngw9i5l73kHBoqajlS3yS0bioIIDhzCCA4MwggLsoAMCAQICAQAwDQYJKoZIhvcNAQEFBQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0MDIxMzEwMTMxNVoXDTM1MDIxMzEwMTMxNVowgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBR07d/ETMS1ycjtkpkvjXZe9k+6CieLuLsPumsJ7QC1odNz3sJiCbs2wC0nLE0uLGaEtXynIgRqIddYCHx88pb5HTXv4SZeuv0Rqq4+axW9PLAAATU8w04qqjaSXgbGLP3NmohqM6bV9kZZwZLR/klDaQGo1u9uDb9lr4Yn+rBQIDAQABo4HuMIHrMB0GA1UdDgQWBBSWn3y7xm8XvVk/UtcKG+wQ1mSUazCBuwYDVR0jBIGzMIGwgBSWn3y7xm8XvVk/UtcKG+wQ1mSUa6GBlKSBkTCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCBXzpWmoBa5e9fo6ujionW1hUhPkOBakTr3YCDjbYfvJEiv/2P+IobhOGJr85+XHhN0v4gUkEDI8r2/rNk1m0GA8HKddvTjyGw/XqXa+LSTlDYkqI8OwR8GEYj4efEtcRpRYBxV8KxAW93YDWzFGvruKnnLbDAF6VR5w/cCMn5hzGCAZowggGWAgEBMIGUMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTMwMjE1MDIwNjQzWjAjBgkqhkiG9w0BCQQxFgQU6pFsKsltm2oPpMCSwqPuFB2OShEwDQYJKoZIhvcNAQEBBQAEgYAXkXscShHz6Q+ckNrEnCQGB5z24Y9I2aSGHS9V3jHNbD+fHyUiGor0X2D9DmlJMi3ksBgJIVnTnv/+0ju2yN8WX3178bsoK+yliHYPRQpi2fANfVGLgmr2A33w06o2xi2DLxjL10JTWpxVSJRKQ64h/neP8tdwym8PumZwB0rB8w==-----END PKCS7-----
		">
			<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" style="border: none; padding: 0">
			<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
		</form>
	</div>
	<div style="display: inline-block; vertical-align: top">
		<p><strong>GitHub</strong>:</p>
		 
	</div>
	<div style="display: inline-block; vertical-align: top">
	<div style="float: left">
		<p><strong>Ko-Fi</strong>:</p>
		<a href='https://ko-fi.com/P5P1PFQ11' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
	</div>
</div>
</fieldset>

<p>I find myself often needing to set up a large number of 301 redirects from an excel file of old to new url’s. Until this point, writing these had been a fairly exhausting process as you need to be certain to escape every character that could be picked up by the regular expression engine, or risk unintended consequences. On top of that, writing these rewrites to catch GET parameters in any order is complex to say the least.  While working through a large list of 301s for a very picky server where <em>Redirect 301</em> style redirects were not an option, I began setting up RewriteRule's by hand and decided there had to be a better way. Voila! My 301 RewriteRule Builder was born.
	</p><img src="https://donatstudios.com/assets/28/Old2New.png" alt="Excel Usage Example" style="float: right; padding-left: 11px;" />
<p>You can simply enter a list of tab or space delimited urls (copied from Excel works great) and it will spit out the generated RewriteRule’s for you. GET parameters on the old urls are broken up and built into multiple RewriteCond’s set up to work in any passed order. 	</p>
<p>I hope someone finds this useful.  Any comments are welcome below, of feel free to <a href="https://github.com/donatj/Mod-Rewrite-Rule-Generator" target="_blank">fork me on github</a>! </p>
<p>Please make sure you have <code>RewriteEngine On</code> somewhere near the top of your .htaccess, before you attempt any rewrites.</p>

<p><strong>Need more help?</strong> The tool can only do so much, and sometimes you need human help. I offer .htaccess help as a paid service and have 10+ years of Apache experience. Send me a note on my <a href="https://donatstudios.com/contact">contact page</a> and we can discuss your options.</p>

<fieldset> <center><small><a href="https://github.com/donatj/RewriteRule-Generator" target="_blank">Fork my source on Github!</a></small></center></fieldset>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Google_Killed_The_Internet</guid>
      <title>Opinion: Google Killed the Internet</title>
      <pubDate>Mon, 20 Dec 2010 04:35:10 -0600</pubDate>
      <link>https://donatstudios.com/Google_Killed_The_Internet</link>
      <description><![CDATA[<h2>Googles Page Speed &ldquo;Optimizations&rdquo; Make Learning Difficult</h2>
<p>If you, as I, grew up during the web bubble we call the 90&rsquo;s and were interested at all in web development my guess is that you probably didn&rsquo;t learn most of your skills from a book or a class.  You probably would run across a site that had something cool on it and ponder  &ldquo;Oh my, how does that work?&rdquo; right click, view source, and after some digging were enlightened.  I&rsquo;m certain this is how most of my generation learned their HTML/CSS/JavaScript skills, it is certainly how I did, and often how I continue though now I have tools to help me even further pick apart a page such as Firebug.</p>
<p>Where I take issue is CSS and more so JavaScript minifying. Google wants essentially all your CSS and JavaScript minified. This lessens the bandwidth <em>Google</em> has to use, but in the process makes the source complete illegible to a human being.</p>
<p>I personally, on <em>this site</em>, lose a fair deal of my Page Speed Score because &ldquo;Minifying <a href="https://donatstudios.com/js/general.js" target="_blank">http://donatstudios.com/js/general.js</a> could save 311B (23% reduction)&rdquo;.   My general.js file is at the moment 1.3 kilobytes; I am losing points off my Page Speed score over 311 bytes simply because I want my source code to remain legible.  Can we please get a sanity check on this, Google.  311 bytes is not going to kill you.</p>
<h2>SEO is Destroying the Spirit of the Internet</h2>
<p>Back in the hay day of Geocities if you did a search you were likely to get a few if not mostly amateur pages in your results.  They weren&rsquo;t usually <em>well designed</em> perse, but they were often <em>very</em> useful. I can recall of the top of my head a few instances where they saved the day.</p>
<p>In steps &ldquo;Search Engine Optimization&rdquo;. Corporations are gaga over paying people to dig through content, study their keywords, rewording things and saturating content with keywords (often at the cost of readability I might add).</p>
<p>Now let&rsquo;s compare that to an amateur, who&rsquo;s content is designed for <em>human consumption</em> rather than <em>googles</em> and now has no way to keep up in this arms race. There usually isn&rsquo;t the money nor the desire to pump into SEO and content development.  They just want to provide some useful information to the public.  These kinds of sites are becoming increasingly hard to find, with corporate sites taking the lion&rsquo;s share of the hits.</p>
<p>Frankly I believe a lot of great content is getting missed thanks to SEO.  The spirit of the internet years ago was driven around the fact that anyone could write something, and have it read by millions of people.  While this is still the case, I find it far less likely now than it has been in previous years.  I don&rsquo;t think there is a solution; I just find the corporatization of the web a little disheartening.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Grid_CSS_Versus_The_Web</guid>
      <title>Grid Style Sheets vs. The Semantic Web</title>
      <pubDate>Tue, 19 Oct 2010 23:43:21 -0500</pubDate>
      <link>https://donatstudios.com/Grid_CSS_Versus_The_Web</link>
      <description><![CDATA[<h2>Grid Style Sheets Must Die</h2>
<p>I have been aware of Grid Style Sheets for quite a while, but I long ago dismissed them as a fad like CSS Resets.  Recently though it came to my attention that OSCommerce 2.3 switched to the <a target="_blank" href="https://960.gs/">960 Grid Style</a> to replace its table based layout system.  I spend most of my development time working with OSCommerce and thought it would be good to understand this change so I could decide whether or not to integrate it into our fork.</p>
<p>My general impression playing with the latest nightly of OSCommerce 2.3 from Github though is that I don't like it – maybe I'm old school, but in my view Grid Style Sheets go against what we spent the previous decade fighting for, separation of our concerns&hellip; separating presentation and content.</p>
<h3><em>You're Doing Internet Wrong</em></h3>
<p>Let's talk about <a target="_blank" href="https://en.wikipedia.org/wiki/Separation_of_presentation_and_content">separation of presentation and content</a>.</p>
<p>Ideally, content and layout information should be entirely separate.  Class names should be used to <em>describe</em> the <em>data</em> they contain, and <em>nothing to do with</em> the style the class will contain.  You should be able to completely rearrange / restructure a page simply by switching out a stylesheet.  The HTML should strive to be as purely a data delivery mechanism as possible, similar to that of an XML request, and the CSS is there to make it something a human can digest.</p>
<p>Using a Grid stylesheet – your class names <em>purely</em> apply to your <em>specific</em> layout.  Class names like &quot;grid_1&quot; or &quot;container_12&quot; are not indicative of content in the slightest, but rather where they are placed on the grid that is your page. <s>Grid_1 will <em>always</em> be leftmost. Always. Might as well use <code>&lt;left&gt;</code></s> </p>
<p><strong>Correction</strong>: Grid_1 will always be one twelfth of your page wide (on a twelve Column Grid) , Grid_2 two twelfths, et cetera. Nothing to do with describing the content contained.</p>
<h3>Grid Style Sheets are <em>Extremely</em> Limiting</h3>
<p>With a well-structured CSS layout, deciding to move your navigation from the left to the right is as simple as flicking a switch.  Usually after changing one to a few lines of CSS, and its switched. It <em>cascades</em> across your entire site and everything floats and adjusts.  Same goes for altering the width, or even the entire flow of your document. You can quite easily decide your left navigation is now a top navigation with no alteration to the content.</p>
<p>Contrast this with a grid style sheet – to reposition <em>anything</em> you will have to <strong>edit the HTML</strong> – change the class you call.  Restructuring a site is a major task. They are essentially <strong><em>tables</em></strong> done in CSS with <em>all the <strong>drawbacks of tables</strong></em> with few of the benefits (<em>cough</em> valign <em>cough</em>).</p>
<h3>Why Does this Matter?</h3>
<p>Flexibility, sustainability, scalability – these are not just buzz words – these are important attributes of a well-developed site – these are things a grid layout does not deliver.</p>
<p>Let's look at this from a prebuilt application perspective. In OSCommerce 2.3 the left navigation is classed grid_4 which places it 4 gridlines out of 16, and the content area is push_4 which aptly pushes it over 4.  That defines not only where my navigation appears, but also its size.  Its inflexible without changing HTML, and quite honestly wouldn't work for the <strong>majority</strong> of carts I develop.</p>
<h3>Final Thoughts</h3>
<p>Grids, from a design perspective are not just <em>useful</em>, they're downright <em>ideal</em>.  Clear alignment of elements creating strong visual lines is something strived for.  <strong>Consistent</strong> padding and margins are all very important – and all of these are honorable virtues of a grid style sheet.</p>
<p>I definitely understand the draw and the time saved in the initial build of a site, but a meticulously well engineered style sheet supporting semantic CSS will in the long run pay back the initial investment many times over.</p>
<h3>Post Script</h3>
<p>While writing this, I've decided I really like the <strong>concept and aim</strong> of the grid style sheet – and would <em>love it</em> if it did not interfere with the semantic nature of CSS.  If I could simply do something like the following, I would be using it 10 minutes ago.</p>
<pre><code>#leftnav {  
    imports: .grid2;
}</code></pre>
<p>W3C – I'm looking your direction to see this happen.  Until then, I'm sticking with my old fashioned meticulously crafted CSS.</p>
<p><strong>Update February 3, 2014</strong>:</p>
<p>While my position on grid CSS has not changed, there are now <strong>many many</strong> <a href="https://sass-lang.com/">Sass</a>, Less, Stylus etc CSS pre-parser grid frameworks which allow your CSS to still be semantic, while utilizing grid layouts. This is <strong>wonderful</strong> and I wholeheartedly endorse their use.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/lorem_ipsum_generator</guid>
      <title>Statistics Based Lorem Ipsum Generator</title>
      <pubDate>Wed, 22 Sep 2010 21:02:25 -0500</pubDate>
      <link>https://donatstudios.com/lorem_ipsum_generator</link>
      <description><![CDATA[<fieldset>
<p>It strikes me now, many years later, that what I'd built was essentially a PHP markov chain</p>
</fieldset>

<p>Several months ago I was wondering to myself if you could detect patterns in a list of words and then use those patterns to generate a new list of fake &ldquo;words&rdquo;. A kind of a <em>flavored</em> Lorem Ipsum generator, where I could change the feel of the sentences by switching out the data set.  I&rsquo;m sure thousands of developers have done this before me – but it was a fun thought experiment.</p>
<p>I threw together a little script in PHP to test the idea, and the results were kind of interesting so I figured I'd throw it on my site - Anyway, here are some examples in action – you can hit reload for a random ipsum!  The code and a sample dataset are below.</p>

<fieldset style="clear:both"><legend>Faux English Demo</legend> </fieldset>
<fieldset><legend>Faux Latin-y English Demo</legend> </fieldset>
<fieldset><legend>Faux Japanese Demo</legend> </fieldset>
<br clear="all" />
<br />
You need to supply a set of words for it to base the pattern off - here is my <strong><a target="_blank" href="https://gist.github.com/raw/592419/2c7eb9757e578f743796699f10433c53dab28413/words_example.php">sample dataset</a></strong>.
<br />
 ]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/FastPatch</guid>
      <title>Fast Patches with Git</title>
      <pubDate>Fri, 27 Aug 2010 21:59:26 -0500</pubDate>
      <link>https://donatstudios.com/FastPatch</link>
      <description><![CDATA[<p>In my work, I deal with a lot of very similar codebases - and often if I fix something in one project I'll want to fix it in many others.  For a long time this has meant popping open Beyond Compare, which works, but isn't the simplest solution.</p>
<p>I use git on my projects - but they're not similar enough just to be branches. Recently I came up with a way to patch one or more commits from a project to another easily.  Here is a simple shell script I wrote to handle the task.</p>
<p>Simply put, you can use it either by </p>
<pre><code class="language-console">$ fast-patch.sh /z/my_project</code></pre>
<p>to patch a project with the latest commit from the project in the working directory or with an optional parameter, specify a number of commits, eg </p>
<pre><code class="language-console">$ fast-patch.sh /z/my_project 5</code></pre>
<p>Also, it will generate a patch log to <code>~/fastPatch.log</code> which I generally use directly for my commit message by way of </p>
<pre><code class="language-console">$ git commit -F ~/fastPatch.log</code></pre>
<p>Any comments on how I could improve it, or questions are quite welcome.</p>
 ]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Balanced_Take_on_Gzip</guid>
      <title>A Balanced Look at GZIP and the User Experience</title>
      <pubDate>Tue, 17 Aug 2010 00:30:31 -0500</pubDate>
      <link>https://donatstudios.com/Balanced_Take_on_Gzip</link>
      <description><![CDATA[<p>With Google recently starting to involve load time in search  rankings, there has been a lot of talk about GZIP.  Google Webmaster Tools exclaims "Compressing  the following resources with gzip could reduce their transfer size by <strong>X</strong>KB:". Many people listen to this, and go out looking for a quick fix without realizing that <strong>poor GZIP implementation has a strongly negative effect on  the overall user experience</strong>.</p>
<p>There are two main ways in PHP to go about implementing  GZIP. The first is <a href="https://php.net/manual/en/function.ob-gzhandler.php">ob_gzhandler</a>.   Basically all that is really involved in setting this up is adding <code>ob_start(&quot;ob_gzhandler&quot;);</code> anywhere  before headers are sent, and it will blindly handle this for you. </p>
<p>The second is <a href="https://www.php.net/manual/en/zlib.configuration.php#ini.zlib.output-compression" target="_blank">zlib.output_compression</a> which while entirely transparent and Zends states that <q cite="http://www.php.net/manual/en/function.ob-gzhandler.php">using zlib.output_compression is preferred over ob_gzhandler().</q></p>
<p>This method can have a huge downside for users.  It can for instance greatly exacerbate the sluggishness  of an already slow page or even site. Right now your probably saying to yourself "What? I  thought GZIP made everything faster. This guy is crazy.". Allow me to explain. </p>
<p>Let&rsquo;s say that you have a site with no all encompasing output  buffer.  As PHP works its way through  your script it occasionally flushes out its internal buffer to apache, which in turn flushes it out to the browser, allowing the browser to begin rendering the incomplete document. Also, let&rsquo;s say as an example you have a hold up slowing down your page in the footer of your site. The  user would already have the rest of the content and could  begin reading  and enjoying the site.</p>
<p>Now let&rsquo;s compare to a site GZIPed with an output buffer  callback, e.g. ob_gzhandler. After the output buffer is started, your code  begins to execute.  Everything echoed is  held in the buffer until the page is completely done and the output buffer closes, at which point it is  GZIPed and flushed out to the user.   The user recieves less bits, but that hold up in the footer we spoke of before halts up the entire  rendering process, and the user cannot see anything  until the footer completes.</p>
<p>For your viewing pleasure, a demonstration:</p>
<fieldset><legend>Side by Side Comparison</legend>
	<table>
		<tr>
			<th>Non-GZIPed</th>
			<th>GZIPed</th>
		</tr>
		<tr><td>
			 
		</td><td>
			 
		</td></tr>
	</table>
    <small>It has come to my attention that Webkit browsers wait for a full result in an iframe to begin rendering - so this example will not work in Safari/Chrome</small>
</fieldset>
<h2>Take Note</h2>
<p>I am not stating that all GZIP is bad. To the contrary, GZIP can be very beneficial and I absolutely sympathize with google wanting to cut down on their bandwidth. If you have Firebug or something similar you can note that the request for the Non-GZIPed example is a whopping 100 <strong>kilobytes</strong>, whereas the GZIPed examples request is a measily 323 <em><strong>bytes</strong></em>! The reason for this is I did not have enough content to get Apache to flush so to get around this I added the following line.</p>
<pre>echo str_repeat(' ', 100000); <em>//This creates 100kb worth of data, enough to trigger my copy of Apache to flush;</em></pre>
<p>Not only does this trick cause Apache to flush,  but it  exagerates the positive effect of GZIP. Simple patterns, or in this case large amounts of whitespace, will compress quite wondefully.</p>
<p>Uncompressed static files, JavaScript and CSS being two of the best examples compress quite well. The best way to do this is on an Apache server is with </p>
<pre>&lt;IfModule mod_deflate.c&gt;<br />
&lt;FilesMatch &quot;\.(js|css)$&quot;&gt;<br />
SetOutputFilter DEFLATE<br />
&lt;/FilesMatch&gt;<br />
&lt;/IfModule&gt;</pre>
<h2>Looking for a Solution</h2>
<p>I've posted a number of questions to Stack Overflow [<a href="https://stackoverflow.com/questions/2868167/any-way-to-chunk-gzip-with-apache-and-php">1</a>]  [<a href="https://stackoverflow.com/questions/2955180/mod-deflate-caching-question">2</a>]  researching this writing and actually found very limited help.</p>
<p>It appears murky whether or not the protocol even supports  the functionality of chunking GZIP results into separately compressed blocks,  as would be required to decompress portions before the page is done – you can  chunk a complete GZIP response, but the advantage of this is limited.</p>
<p> Lastly I have seen &ldquo;promises&rdquo; of ob_gzhandler caching the  results leading to a speed boost on a number of blogs – this is purely fiction and not to be trusted.</p>
<h2>Moral of the Story</h2>
<p>Understand what you&rsquo;re doing before you do it.  <em>Blindly</em> using output buffering for GZIP can have adverse effects on your site.  </p>
]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/SimpleCalendar</guid>
      <title>SimpleCalendar</title>
      <pubDate>Thu, 01 Jul 2010 01:17:11 -0500</pubDate>
      <link>https://donatstudios.com/SimpleCalendar</link>
      <description><![CDATA[<p>SimpleCalendar is a very simple PHP calendar class written to be easy to use and flexible.</p>
<p>It includes basic event logic and is made to be modified to your particular needs.</p>
<h2>Sample Usage</h2>
<pre><code>&lt;?php
require_once('SimpleCalendar.php');
$calendar = new donatj\SimpleCalendar();

$calendar-&gt;setStartOfWeek('Sunday');

$calendar-&gt;addDailyHtml( 'Sample Event', 'today', 'tomorrow' );
$calendar-&gt;show(true);</code></pre>
<h2>Example Calendar</h2>
<p><a href="https://github.com/donatj/SimpleCalendar">Click here for the Github repository</a>.</p>
<div class="GithubFeed"><h2>Recent Activity</h2><div><h3><time class="entryDate" title="2025-11-24T17:01:31-06:00" datetime="2025-11-24T17:01:31-06:00">Nov. 24, 2025</time></h3><h4>GitHub <small>(<a href="https://github.com/web-flow" target="_blank">web-flow</a>)</small></h4>Merge pull request #32 from donatj/donatj-patch-2<br />
<br />
Add PHP version 8.5 to CI workflow<hr /></div><div>Add PHP version 8.5 to CI workflow<hr /></div><div><h3><time class="entryDate" title="2025-11-21T00:39:19-06:00" datetime="2025-11-21T00:39:19-06:00">Nov. 21, 2025</time></h3>Bump actions/checkout from 5 to 6 (#31)<br />
<br />
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.<br />
- [Release notes](https://github.com/actions/checkout/releases)<br />
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)<br />
- [Commits](https://github.com/actions/checkout/compare/v5...v6)<br />
<br />
---<br />
updated-dependencies:<br />
- dependency-name: actions/checkout<br />
  dependency-version: '6'<br />
  dependency-type: direct:production<br />
  update-type: version-update:semver-major<br />
...<br />
<br />
Signed-off-by: dependabot[bot] &lt;support@github.com&gt;<br />
Co-authored-by: dependabot[bot] &lt;49699333+dependabot[bot]@users.noreply.github.com&gt;<hr /></div><div><h3><time class="entryDate" title="2025-08-12T08:39:12-05:00" datetime="2025-08-12T08:39:12-05:00">Aug. 12, 2025</time></h3>Bump actions/checkout from 4 to 5 (#30)<br />
<br />
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.<br />
- [Release notes](https://github.com/actions/checkout/releases)<br />
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)<br />
- [Commits](https://github.com/actions/checkout/compare/v4...v5)<br />
<br />
---<br />
updated-dependencies:<br />
- dependency-name: actions/checkout<br />
  dependency-version: '5'<br />
  dependency-type: direct:production<br />
  update-type: version-update:semver-major<br />
...<br />
<br />
Signed-off-by: dependabot[bot] &lt;support@github.com&gt;<br />
Co-authored-by: dependabot[bot] &lt;49699333+dependabot[bot]@users.noreply.github.com&gt;<hr /></div><div><h3><time class="entryDate" title="2024-11-24T06:22:14-06:00" datetime="2024-11-24T06:22:14-06:00">Nov. 24, 2024</time></h3>Merge pull request #29 from donatj/donatj-patch-2<br />
<br />
Test in PHP 8.4<hr /></div><div>Test in PHP 8.4<hr /></div><div><h3><time class="entryDate" title="2023-11-27T11:06:17-06:00" datetime="2023-11-27T11:06:17-06:00">Nov. 27, 2023</time></h3>Merge pull request #26 from donatj/php-version/8-3--users-jdonat-projects-donatj-simplecalendar--github-workflows-ci-yml<br />
<br />
Update PHP to 8.3<hr /></div><div><h4>Jesse Donat <small>(<a href="https://github.com/donatj" target="_blank">donatj</a>)</small></h4>Update PHP to 8.3<hr /></div><div><h3><time class="entryDate" title="2023-11-02T23:01:07-05:00" datetime="2023-11-02T23:01:07-05:00">Nov. 2, 2023</time></h3>Replace travis badge with GitHub Actions<hr /></div><div><h4>GitHub <small>(<a href="https://github.com/web-flow" target="_blank">web-flow</a>)</small></h4>Merge pull request #25 from donatj/phpstan<br />
<br />
Pretty major cleanup<hr /></div><div><h4>Jesse Donat <small>(<a href="https://github.com/donatj" target="_blank">donatj</a>)</small></h4>Use Makefile<hr /></div><div>Adds helpful Makefile<hr /></div><div>Update autoloader syntax<hr /></div><div>Use correctly compatible versions<hr /></div><div>Updates README<hr /></div><div>Minor fixup<hr /></div><div>Drop pre-7.2&rsquo;s<hr /></div><div>Run fixer<hr /></div><div>Add phpcs and php-cs-fixer<hr /></div><div>Fixup<hr /></div><div>Add phpstan.neon<hr /></div><div>Cleans up composer.json some<hr /></div><div>Correct backwards annotation<hr /></div><div>Remove unnecessary if<hr /></div><div>Min PHP 7.2<hr /></div><div>Fix http url<hr /></div><div><h3><time class="entryDate" title="2023-09-05T00:07:40-05:00" datetime="2023-09-05T00:07:40-05:00">Sep. 5, 2023</time></h3>Bump actions/checkout from 3 to 4<br />
<br />
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.<br />
- [Release notes](https://github.com/actions/checkout/releases)<br />
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)<br />
- [Commits](https://github.com/actions/checkout/compare/v3...v4)<br />
<br />
---<br />
updated-dependencies:<br />
- dependency-name: actions/checkout<br />
  dependency-type: direct:production<br />
  update-type: version-update:semver-major<br />
...<br />
<br />
Signed-off-by: dependabot[bot] &lt;support@github.com&gt;<hr /></div><div><h3><time class="entryDate" title="2023-01-11T12:36:38-06:00" datetime="2023-01-11T12:36:38-06:00">Jan. 11, 2023</time></h3>Updates docs<hr /></div><div><h4>GitHub <small>(<a href="https://github.com/web-flow" target="_blank">web-flow</a>)</small></h4>Merge pull request #22 from donatj/8_2<br />
<br />
Test in PHP 8.2<hr /></div><div><h4>Jesse Donat <small>(<a href="https://github.com/donatj" target="_blank">donatj</a>)</small></h4>Resolve PHP 8.1+ deprecation notice<hr /></div></div>   <table class="datatable" style="width: 100%" cellpadding="0" cellspacing="0">
        <tr><th align="left">Build</th><th align="left">Date</th><th align="left">Message</th></tr><tr class="odd"><td align="left">v0.6.0<br /><small><a href="https://github.com/donatj/SimpleCalendar/zipball/v0.6.0" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2014-04-16T13:58:55-05:00" datetime="2014-04-16T13:58:55-05:00">Apr. 16, 2014</time></td><td align="left"><small>Simplified shrimp2ts work</small></td></tr><tr class=""><td align="left">v0.6.1<br /><small><a href="https://github.com/donatj/SimpleCalendar/zipball/v0.6.1" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2014-04-16T14:42:21-05:00" datetime="2014-04-16T14:42:21-05:00">Apr. 16, 2014</time></td><td align="left"><small>gitignore updates</small></td></tr><tr class="odd"><td align="left">v0.6.2<br /><small><a href="https://github.com/donatj/SimpleCalendar/zipball/v0.6.2" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2017-06-07T09:57:30-05:00" datetime="2017-06-07T09:57:30-05:00">Jun. 7, 2017</time></td><td align="left"><small>Merge branch 'keesiemeijer-master'</small></td></tr><tr class=""><td align="left">v0.7.0<br /><small><a href="https://github.com/donatj/SimpleCalendar/zipball/v0.7.0" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2023-01-11T12:24:49-06:00" datetime="2023-01-11T12:24:49-06:00">Jan. 11, 2023</time></td><td align="left"><small>Updates docs</small></td></tr><tr class="odd"><td align="left">v0.8.0<br /><small><a href="https://github.com/donatj/SimpleCalendar/zipball/v0.8.0" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2023-11-02T23:01:07-05:00" datetime="2023-11-02T23:01:07-05:00">Nov. 2, 2023</time></td><td align="left"><small>Replace travis badge with GitHub Actions</small></td></tr>   </table>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/JDZoom</guid>
      <title>JDZoom</title>
      <pubDate>Tue, 08 Jun 2010 03:18:35 -0500</pubDate>
      <link>https://donatstudios.com/JDZoom</link>
      <description><![CDATA[<div class="GithubFeed"><h2>Recent Activity</h2><div><h3><time class="entryDate" title="2019-02-04T11:15:10-06:00" datetime="2019-02-04T11:15:10-06:00">Feb. 4, 2019</time></h3><h4>Jesse Donat <small>(<a href="https://github.com/donatj" target="_blank">donatj</a>)</small></h4>Readme/LICENSE fixes<hr /></div><div><h3><time class="entryDate" title="2014-01-22T14:30:20-06:00" datetime="2014-01-22T14:30:20-06:00">Jan. 22, 2014</time></h3>Merge pull request #1 from bitdeli-chef/master<br />
<br />
Add a Bitdeli Badge to README<hr /></div><div><h4>Bitdeli Chef <small>(<a href="https://github.com/bitdeli-chef" target="_blank">bitdeli-chef</a>)</small></h4>Add a Bitdeli badge to README<hr /></div><div><h3><time class="entryDate" title="2010-10-25T11:26:30-05:00" datetime="2010-10-25T11:26:30-05:00">Oct. 25, 2010</time></h3><h4>Jesse Donat <small>(<a href="https://github.com/donatj" target="_blank">donatj</a>)</small></h4>Fixed an issue with cross domain images<hr /></div><div><h3><time class="entryDate" title="2010-10-08T09:34:28-05:00" datetime="2010-10-08T09:34:28-05:00">Oct. 8, 2010</time></h3>Fixed a regression where selector would only accept strings<hr /></div><div><h3><time class="entryDate" title="2010-10-07T23:58:52-05:00" datetime="2010-10-07T23:58:52-05:00">Oct. 7, 2010</time></h3>Restructured Repo per MooTools Forge requirements<hr /></div><div>Updated README.md<hr /></div><div>Added Options:<br />
<br />
Magnified Position Option Added<br />
options: fixed/float<br />
<br />
Cancel Click Option Added<br />
boolean, prevents default on an images anchor<br />
<br />
Misc Code Cleanup...<br />
<br />
Fixed &quot;endless.com&quot; style added<br />
<br />
index.html demo's updated - shows many different<br />
options and settings in action.<hr /></div><div>Added escaping to css url() calls to prevent problems with images with<br />
spaces in file names.<hr /></div><div>Large Rework of Functionality<br />
- Chrome compatability added<br />
- Zoom box to magnified result math corrected<br />
- Image preloaded<br />
- Div now hovers over image rather than replacing<br />
- Selector option added allowing an element, array of elements,<br />
or a string selector to be passed.<br />
- Internet Explorer Compatability fixes.<hr /></div><div><h3><time class="entryDate" title="2010-06-07T11:22:08-05:00" datetime="2010-06-07T11:22:08-05:00">Jun. 7, 2010</time></h3>README.md and .gitignore added<hr /></div><div>Initial Commit<hr /></div></div>

<p>JDZoom is a MooTools powered hover zoom / magnifier similarly styled to the one featured on Endless.com.</p>
<p>It is supported by IE6+, Firefox 2.5+, Safari 2+ and Chrome</p>
<p>It does not interfere with click though, such it can be used in conjunction with almost any lightbox. In fact by altering the selector when you instantiate it, most lightboxes can be extended with JDZoom without touching the HTML at all. See the <a href="https://donatstudios.com/assets/18/Demo/" target="_blank">advanced demonstration</a> for more details.</p>
<p>JDZoom requires MooTools, and is Compatible with 1.2.5+ and 1.3+</p>
<p>It <strong>does not require</strong> any components of <i>MooTools More</i>.</p>

<p><a href="https://github.com/donatj/jdzoom" target="_blank">Click here for the Github repository.</a></p>

<fieldset style="width:300px"><legend>Demonstration</legend>
<a rel="jdzoom_fixed" href="https://donatstudios.com/assets/18/Demo/images/sakakibaraonsen.jpg"  title="Sakakibara Onsen, I've been naked here"><img src="https://donatstudios.com/assets/18/Demo/images/sakakibaraonsen.jpg" width="300" /></a>
<div style="text-align: center"><small><a href="https://donatstudios.com/assets/18/Demo/">View Advanced Demonstration</a></small></div>
</fieldset>
<h2>Example Source</h2>
<pre><code>&lt;html&gt;
&lt;head&gt;
	&lt;link href="https://donatstudios.com/&lt;a href="https://github.com/donatj/jdzoom/blob/master/Demo/css/jdzoom.css">css/jdzoom.css</a>" rel="stylesheet" type="text/css" /&gt;
	&lt;script src=&quot;<a href="https://mootools.net/download" target="_blank">js/mootools.js</a>&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
	&lt;script src=&quot;<a href="https://github.com/donatj/jdzoom/blob/master/Source/jdzoom.js" target="_blank">js/jdzoom.js</a>&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
	&lt;script type=&quot;text/javascript&quot;&gt;<br>	window.addEvent('load',function() { <br>		var jdz = new JDZoom();<br>	});<br>	&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
	&lt;a rel="jdzoom" href="https://donatstudios.com/&lt;a href="https://donatstudios.com/assets/18/Demo/images/venus.jpg" target="_blank">images/venus.jpg</a>"&gt;&lt;img src="https://donatstudios.com/&lt;a href="https://donatstudios.com/assets/18/Demo/images/venus_thumb.jpg" target="_blank">images/venus_thumb.jpg</a>" /&gt;&lt;/a&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>

	<table class="datatable" style="width: 100%" cellpadding="0" cellspacing="0">
		<tr><th align="left">Build</th><th align="left">Date</th><th align="left">Message</th></tr><tr class="odd"><td align="left">v0.1<br /><small><a href="https://github.com/donatj/jdzoom/zipball/v0.1" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2010-07-27T00:18:46-05:00" datetime="2010-07-27T00:18:46-05:00">Jul. 27, 2010</time></td><td align="left"><small>Initial Alpha<br />
</small></td></tr><tr class=""><td align="left">v0.9<br /><small><a href="https://github.com/donatj/jdzoom/zipball/v0.9" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2010-10-07T18:50:04-05:00" datetime="2010-10-07T18:50:04-05:00">Oct. 7, 2010</time></td><td align="left"><small>version 0.9 - nearing release<br />
</small></td></tr><tr class="odd"><td align="left">v0.9.1<br /><small><a href="https://github.com/donatj/jdzoom/zipball/v0.9.1" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2010-10-07T22:36:20-05:00" datetime="2010-10-07T22:36:20-05:00">Oct. 7, 2010</time></td><td align="left"><small>version 0.9.1<br />
</small></td></tr>	</table>
	
]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/XML_Excel_Exporter</guid>
      <title>XML Excel Exporter</title>
      <pubDate>Mon, 07 Jun 2010 22:54:58 -0500</pubDate>
      <link>https://donatstudios.com/XML_Excel_Exporter</link>
      <description><![CDATA[<p><strong>Update 2017</strong>: The functionality of &quot;XML Excel Exporter&quot; is now part of the <a href="https://github.com/QuorumCollection/Exporter">Quorum Exporter</a> project.</p>
<p><div class="GithubFeed"><h2>Recent Activity</h2><h3><time class="entryDate" title="2010-10-15T23:46:59-05:00" datetime="2010-10-15T23:46:59-05:00">Oct. 15, 2010</time></h3>XSpreadsheet dies after outputting the spreadsheet now, preventing corruption<hr /><h3><time class="entryDate" title="2010-02-18T13:05:58-06:00" datetime="2010-02-18T13:05:58-06:00">Feb. 18, 2010</time></h3>Repository Restructured<br />
-Working Corpus now in Source Folder<br />
-`Scripts` folder added, includes database dump batch, and README.md HTML viewer<br />
-`Local` folder added, for testing use with local database connections, etc<br />
<br />
Documentation<br />
-Finally! Created an initial README.md<br />
-TODO moved from includes folder into root<hr /></div></p>
<p>This simple class allows you via PHP to export an XML Spreadsheet compatible with all versions of Excel 2003 and up.</p>
<p>The files generated will work with versions of Office 2003 and up. It is considered production safe, and is currently in use in a fair number of production sites.<p>

<p>Unlike CSV or HTML Table exports, <strong>you can have multiple worksheets of data!</strong> A sheets data can be added to the spreadsheet either by a MySQL query resource or as a 2 dimensional array.</p>

<h3>Known bugs:</h3>
<ul>
    <li>Office 2007/2010 bark about XLS file extensions despite refusing to open XML file extensions downloaded from the internet. (Anyone know a solution?)</li>
    <li>DOMDocument incorrectly converts/removes non-printable/low range characters (eg: &amp;#10;).</li>
</ul>

<h3>Limitations:</h3>
<ul>
    <li>File Format is not supported by:
    <ul>
        <li>Open Office</li>
        <li>Google Documents</li>
        <li>Microsoft Office Live</li>
    </ul>
    </li>
</ul>

<h2>MySQL Example</h2>
<pre>require('includes/classes/XSpreadsheet.php');
$spread = new XSpreadsheet($fname)
$spread-&gt;AddWorksheet('Products', mysql_query(&quot;Select * From products&quot;))
    -&gt;AddWorksheet('Categories', mysql_query(&quot;Select * From categories&quot;))
    -&gt;Generate()-&gt;Send();
</pre>

<h2>Array Example</h2>
<pre>require('includes/classes/XSpreadsheet.php');
$data = array(
    array( 'Column 1', 'Column 2', 'Column 3' ),
    array( 1, 2, 3 ),
);

$spread = new XSpreadsheet($fname)
$spread-&gt;AddWorksheet('Awesome Sheet', $data )
    -&gt;Generate()-&gt;Send();
</pre>

<a title="Click Here to Download" href="https://donatstudios.com/Github/redirect?link=https%3A%2F%2Fgithub.com%2Fdonatj%2FCorpusPHP%2Fraw%2Fmaster%2FSource%2Fincludes%2Fclasses%2FXSpreadsheet.php" ><button type="button" >Click Here to Download</button></a>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/No_App_P1_PhpED</guid>
      <title>There Isn't an App for&#x2026; - Part 1: PhpED</title>
      <pubDate>Thu, 07 Jan 2010 20:53:36 -0600</pubDate>
      <link>https://donatstudios.com/No_App_P1_PhpED</link>
      <description><![CDATA[<h2>&ldquo;There Isn't an App For That&rdquo; - Windows Apps I haven't been able to replace in Mac OS X</h2>
<p>For a very long time I was a Windows guy, I <em>was once</em> a DOS guy. I switched nearly exclusively to Mac for my home use about 4 years ago. I&rsquo;ve been very happy with it. Its stable, well engineered, and - my favorite part of all - consistent. Yet, in this time there remain a few applications I cannot seem to replace.</p>
<h3><a href="https://www.nusphere.com/">Nusphere PhpED</a></h3>
<p> I am a PHP Developer by trade, and PhpED for my money is by far the best development environment for the serious PHP developer. When I do develop on the Mac platform it is usually split between two applications &ndash; Coda and TextMate. They are fantastic text editors, don&rsquo;t misunderstand, but when you are developing highly classed applications a strong IDE becomes highly desirable. PhpED&rsquo;s IntelliSense style PHP auto completion is <strong>on par</strong> with and in some ways <strong>surpasses</strong> Visual Studios.</p>
<p>Applications like Dreamweaver, Coda, TextMate and Espresso by comparison provide very basic auto completion. Zend Studio, Aptana Studio and other Eclipse based solutions have varying qualities of auto completion, but are awkward due to Eclipses generality and slow and visually out of water due to being built on Java. Komodo comes the closest, but can be very slow at times. Weighing in at $299 its pricetag is also heavy.</p>
<p>To give you in an example, in my CorpusPHP Framework the user class is built specifically to be extended for the sites particular needs. The login class therefore has a member instance of <em>whatever</em> type of object extending user you might have. This member is dynamically set as an instance and therefore type cannot be determined by <em>most</em> editors and auto completion will not work.&nbsp; PhpED on the other hand has a solution. This brings me to my favorite PhpED feature - deep phpDocumentor integration.</p>
<pre>class Login {<br />
&nbsp;&nbsp;&nbsp;&nbsp;/**
&nbsp;&nbsp;&nbsp;&nbsp;* Object that holds the user object, brought over from the session
&nbsp;&nbsp;&nbsp;&nbsp;* @var User
&nbsp;&nbsp;&nbsp;&nbsp;*/
&nbsp;&nbsp;&nbsp;&nbsp;public $user;

&nbsp;&nbsp;&nbsp;&nbsp;&hellip;&nbsp;&hellip;&nbsp;&hellip;<br />
}</pre>
<p>The above code snippet shows the user member of the login class. It can be any type of user, for instance admin or limited. It will though be inherently be something that extends the user object.&nbsp; Using phpDocumentor syntax here we imply that the type is a <em>user</em> and since PhpED is smart, it auto completes based on this.&nbsp; If you are unfamiliar with phpDocumentor syntax you should <a href="https://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_phpDocumentor.pkg.html" target="_blank">read up on it</a>, and begin marking your PHP up in this standardized syntax.</p>
<p> <img src="https://donatstudios.com/images/post/phpedAutocomplete.png" alt="Php Autocomplete" /></p>
<p> PhpED takes this integration further and will show descriptions of methods and functions as well as their parameters and return values based on their inline phpDocumentor documentation.</p>
<p> <img src="https://donatstudios.com/images/post/phpedCodeDescription.png" alt="PhpED phpDoc description" /></p>
<p> While auto complete is the PhpED killer feature in my book, it has many other excellent features.</p>
<p>&nbsp;Its internal debugger&nbsp; is second to none, and requires <em>no</em> setup unlike other debuggers. It installs its own copies of Apache and PHP 4 &amp; PHP 5.2 or 5.3 which it uses to debug. The debugger can output to an internal version of IE or Mozilla or any external browser. Its quite configurable, everything you would expect like ability to set $_GET, $_POST, etc variables, break points, runtime analysis.</p>
<p>Some smaller features I personally enjoy are for instance:</p>
<ul>
	<li>To-Do list shows you all the todo&rsquo;s throughout your project.</li>
	<li>Continually improving, high quality CSS/HTML auto completion.</li>
	<li>The ability to set a default character set on a per-project basis</li>
	<li>Non-Ascii range characters show up dark blue in variable and function names, making them obvious &ndash; which is particularly helpful when working on code from a Cyrillic coder whereas &Alpha; (Alpha) and A are <em>visually</em> identical but entirely different characters.</li>
	<li>Great support &ndash; Support will work with you to fix a problem.</li>
</ul>
<h2>Update 7/7/2011</h2>
<p>The dark PhpED Theme I use is now available <a href="https://donatstudios.com/PhpED-Dark-Theme">here for PhpED 5 and 6</a>.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/CorpusPHP</guid>
      <title>CorpusPHP</title>
      <pubDate>Wed, 02 Dec 2009 21:21:04 -0600</pubDate>
      <link>https://donatstudios.com/CorpusPHP</link>
      <description><![CDATA[<div class="GithubFeed"><h2>Recent Activity</h2><div><h3><time class="entryDate" title="2019-03-11T12:03:59-05:00" datetime="2019-03-11T12:03:59-05:00">Mar. 11, 2019</time></h3><h4>GitHub <small>(<a href="https://github.com/web-flow" target="_blank">web-flow</a>)</small></h4>Update README.md<hr /></div><div><h3><time class="entryDate" title="2015-04-14T15:32:16-05:00" datetime="2015-04-14T15:32:16-05:00">Apr. 14, 2015</time></h3><h4>Jesse Donat <small>(<a href="https://github.com/donatj" target="_blank">donatj</a>)</small></h4>Correct year and formatting<hr /></div><div>Create LICENSE.md<hr /></div><div><h3><time class="entryDate" title="2014-01-07T10:12:39-06:00" datetime="2014-01-07T10:12:39-06:00">Jan. 7, 2014</time></h3>Merge pull request #9 from bitdeli-chef/master<br />
<br />
Add a Bitdeli Badge to README<hr /></div><div><h4>Bitdeli Chef <small>(<a href="https://github.com/bitdeli-chef" target="_blank">bitdeli-chef</a>)</small></h4>Add a Bitdeli badge to README<hr /></div><div><h3><time class="entryDate" title="2013-07-15T11:45:29-05:00" datetime="2013-07-15T11:45:29-05:00">Jul. 15, 2013</time></h3><h4>Jesse Donat <small>(<a href="https://github.com/donatj" target="_blank">donatj</a>)</small></h4>Better constant<hr /></div><div><h3><time class="entryDate" title="2013-05-16T22:01:34-05:00" datetime="2013-05-16T22:01:34-05:00">May. 16, 2013</time></h3>Cache class made to serialize<hr /></div><div><h3><time class="entryDate" title="2013-04-26T11:41:20-05:00" datetime="2013-04-26T11:41:20-05:00">Apr. 26, 2013</time></h3>Corrections to the Github api modules pulled in<hr /></div><div><h3><time class="entryDate" title="2013-03-05T12:47:02-06:00" datetime="2013-03-05T12:47:02-06:00">Mar. 5, 2013</time></h3>See doesn't die.  Drop submits an error code.<hr /></div><div><h3><time class="entryDate" title="2012-10-15T10:33:25-05:00" datetime="2012-10-15T10:33:25-05:00">Oct. 15, 2012</time></h3>Update Source/corpus/modules/TwitterFeed.php<br />
<br />
Change in the twitter API url<hr /></div><div><h3><time class="entryDate" title="2012-09-05T15:49:26-05:00" datetime="2012-09-05T15:49:26-05:00">Sep. 5, 2012</time></h3>Update TODO<hr /></div><div><h3><time class="entryDate" title="2012-08-17T14:46:52-05:00" datetime="2012-08-17T14:46:52-05:00">Aug. 17, 2012</time></h3>Basic SASS/Compass support added<hr /></div><div><h3><time class="entryDate" title="2012-07-11T00:40:38-05:00" datetime="2012-07-11T00:40:38-05:00">Jul. 11, 2012</time></h3>Merge branch 'master' of github.com:donatj/CorpusPHP<br />
<br />
Conflicts:<br />
&nbsp;&nbsp;corpus.sql<hr /></div><div>Last.fm module allowed to float on its own<hr /></div><div>Comments upgraded to DonatStudios hierarchical system<hr /></div><div>Tag cloud improvements from Donat Studios<hr /></div><div>supplementary column added for supplementary JSON encoded data about a page<hr /></div><div>db_dump shell script added to scripts<hr /></div><div>SQL dump upgraded to 5.5 looking file<hr /></div><div><h3><time class="entryDate" title="2012-07-10T23:30:11-05:00" datetime="2012-07-10T23:30:11-05:00">Jul. 10, 2012</time></h3>Brfeadcrumb code given itemprops<hr /></div><div>Comment fix<hr /></div><div>More useful MySQL errors<hr /></div><div><h3><time class="entryDate" title="2012-07-09T11:24:16-05:00" datetime="2012-07-09T11:24:16-05:00">Jul. 9, 2012</time></h3>headers_list_assoc function allowing headers_list result a an associative array added<hr /></div><div><h3><time class="entryDate" title="2012-06-05T15:24:40-05:00" datetime="2012-06-05T15:24:40-05:00">Jun. 5, 2012</time></h3>Update master<hr /></div><div><h3><time class="entryDate" title="2012-06-01T09:41:13-05:00" datetime="2012-06-01T09:41:13-05:00">Jun. 1, 2012</time></h3>old template =&gt; layout correction<hr /></div><div><h3><time class="entryDate" title="2012-05-15T23:03:53-05:00" datetime="2012-05-15T23:03:53-05:00">May. 15, 2012</time></h3>HTML5 cleanup-y stuff<hr /></div><div>Mediabox advance removed<hr /></div><div>Basic namespace support roughed in to the autoloader<hr /></div><div>Made to throw errors by default in silly environments<hr /></div><div>Merge branch 'master' of github.com:donatj/CorpusPHP<hr /></div></div>
<p>CorpusPHP is setting out to be a world class PHP Framework. The aim is simple, to provide encapsulation, modularization, and helpful tools, all without changing the way you write PHP.</p>
<p><strong>Project Information, documentation, and a timeline for the first release available  availble soon</strong></p>
<p>Nightly's available at <a href="https://github.com/donatj/CorpusPHP" target="_new">GitHub</a>, or download the latest milestone below.
	<table class="datatable" style="width: 100%" cellpadding="0" cellspacing="0">
		<tr><th align="left">Build</th><th align="left">Date</th><th align="left">Message</th></tr><tr class="odd"><td align="left">v0.9<br /><small><a href="https://github.com/donatj/CorpusPHP/zipball/v0.9" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2010-02-18T13:32:38-06:00" datetime="2010-02-18T13:32:38-06:00">Feb. 18, 2010</time></td><td align="left"><small>Major steps made in maturity, approaching release candidate<br />
</small></td></tr><tr class=""><td align="left">v0.92<br /><small><a href="https://github.com/donatj/CorpusPHP/zipball/v0.92" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2010-05-14T12:55:38-05:00" datetime="2010-05-14T12:55:38-05:00">May. 14, 2010</time></td><td align="left"><small>Version 0.92<br />
</small></td></tr><tr class="odd"><td align="left">v0.98<br /><small><a href="https://github.com/donatj/CorpusPHP/zipball/v0.98" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2010-09-07T15:39:52-05:00" datetime="2010-09-07T15:39:52-05:00">Sep. 7, 2010</time></td><td align="left"><small>Version 0.98<br />
</small></td></tr><tr class=""><td align="left">v0.981<br /><small><a href="https://github.com/donatj/CorpusPHP/zipball/v0.981" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2010-12-21T00:18:32-06:00" datetime="2010-12-21T00:18:32-06:00">Dec. 21, 2010</time></td><td align="left"><small>Optional $seperator paramter added to the breadcrumb generator.</small></td></tr><tr class="odd"><td align="left">v0.99<br /><small><a href="https://github.com/donatj/CorpusPHP/zipball/v0.99" target="_blank">Download</a></small></td><td align="left"><time class="entryDate" title="2011-05-29T01:08:40-05:00" datetime="2011-05-29T01:08:40-05:00">May. 29, 2011</time></td><td align="left"><small>version 0.99<br />
</small></td></tr>	</table>
	]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Donat_Studios_Launches</guid>
      <title>Donat Studios Officially Launches</title>
      <pubDate>Tue, 27 Oct 2009 02:45:28 -0500</pubDate>
      <link>https://donatstudios.com/Donat_Studios_Launches</link>
      <description><![CDATA[<p>After much ado, Donat Studios is finally live!</p>
<p>There are a few things left to complete. Eventually setting up the documentation and public git repository for CorpusPHP will certainly take up a fair amount of time. I'm still not quite sure what to put in my portfolio, I was thinking of for the time being just throwing a bunch of my sketches in there.</p>
<p>My reasoning and goals behind this site are slightly different than my other sites. More than anything I wanted a place where I could rant about everything web related. I have strong opinions on many topics, and where Oasisband.net is good for movies and politics, and PHPStandards is pretty much just raw anger directed at Zend, and my live journal is for my personal life. Donat Studios on the other hand will allow me to express my thoughts on web development, databases, html, php, ajax, design, basically everything I do at work.</p>
<p>Where does the name &quot;Donat Studios&quot; come from? I was getting some photos printed by a great place in St. Paul, &quot;White House Custom Color&quot; and they needed to know the name of my studio. I didn't have a studio, so I quickly made up &quot;Donat Studios&quot;. Later, I realized I liked the name, and it stuck.</p>
<p>Here's wishing my newest domain luck!</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Blog</guid>
      <title>Blog</title>
      <pubDate>Mon, 12 Oct 2009 22:36:31 -0500</pubDate>
      <link>https://donatstudios.com/Blog</link>
      <description><![CDATA[]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Projects</guid>
      <title>Code</title>
      <pubDate>Mon, 12 Oct 2009 22:36:31 -0500</pubDate>
      <link>https://donatstudios.com/Projects</link>
      <description><![CDATA[]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/?id=5</guid>
      <title>Contact</title>
      <pubDate>Mon, 12 Oct 2009 22:36:31 -0500</pubDate>
      <link>https://donatstudios.com/?id=5</link>
      <description><![CDATA[]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/JSONP</guid>
      <title>JSONP: Take It With a Grain of Salt</title>
      <pubDate>Mon, 12 Oct 2009 22:36:31 -0500</pubDate>
      <link>https://donatstudios.com/JSONP</link>
      <description><![CDATA[<p>I don’t want anyone take this the wrong way. I <em>love</em> JSON. It is so much simpler to parse than XML, and is an all around exceptional way to represent data. I have one caveat though, and that is “JSON with Padding” or “JSONP” as it goes by.</p>
<p>Building <em>this site</em> I wanted to bring in images from my Flickr account as you may have noticed. Building the module in PHP, I basically ran <code>file_get_contents</code> on the feeds url and passed that directly into <code>json_decode</code>. It didn't work, the result was null. It had failed to decode. As a test I echoed the result of the <code>file_get_contents</code> to see what was up; it was succeeding at retrieving the feed. At a glance it looked like JSON, yet it was failing to parse. I knew something was up. </p>
<p>Here is an example result set:</p>
<pre><code>jsonFlickrFeed({
  "title":"Uploads from donatj",
  "link":"http://www.flickr.com/photos/donatj/",
  "description":"",
  "modified":"2009-05-31T08:24:46Z",
  "generator":"http://www.flickr.com/",
  "items":[
    {
 "title":"STP81353",
 "link":"http://www.flickr.com/photos/donatj/3581174864/",
 "media":{
   "m":"http://farm4.static.flickr.com/3568/3581174864_be1a44764e_m.jpg"
      },
      "date_taken":"2009-03-30T19:10:34-08:00",
      "description":"&lt;p&gt;&lt;a href=\"http://www.flickr.com/people/donatj/\"&gt;donatj&lt;\/a&gt; posted a photo:&lt;\/p&gt; &lt;p&gt;&lt;a href=\"http://www.flickr.com/
photos/donatj/3581174864/\" title=\"STP81353\"&gt;&lt;img src=\"http://farm4.static.flickr.com/3568/3581174864_be1a44764e_m.jpg\" width=\"180\" height=\"240\" alt=\"S
TP81353\" /&gt;&lt;\/a&gt;&lt;\/p&gt; ",
      "published":"2009-05-31T08:24:46Z",
      "author":"nobody@flickr.com (donatj)",
      "author_id":"30444376@N08",
      "tags":""
    }
  ]
})</code></pre>
<p>On further examination I noticed, as you should have noted, the data is wrapped in a function. My first thought was that this violates the <a href="https://www.ietf.org/rfc/rfc4627.txt?number=4627">JSON Specification</a>, so I immediately tweeted about how silly it was for Flickr to have an invalid JSON feed when their parent company Yahoo employs Douglas Crockford, the creator of JSON. I soon received a response from a colleague informing me that this was JSONP. He explained what it was, and how to use it in jQuery. I had not heard of JSONP.</p>
<p>It basically comes down to this: XMLHttpRequest will not, in order to protect against XSS, work across domains. Therefore, JSON cannot be directly retrieved nor parsed via JavaScript across domains.</p>
<p>This is where JSONP comes in. JSONP is essentially a regular JSON result set wrapped in a function. Rather than using XMLHttpRequest to retrieve the document, you dynamically attach a script tag to the page pointing the the JSONP document, which evokes a function call passing your JSON object to whatever function the JSONP is set to pass the data to, forgoing the need to eval anything.</p>
<p>I have a number of <em>criticisms</em> of this approach.</p>
<ul>
<li>First and foremost, JSONP is at its very heart remote code execution. You must have <em>absolute</em> faith not only in the intentions of the site you are bringing the feed in from, but the security of said site as well, because the code is in fact being <strong><em>executed</em></strong> by the browser with <em>no chance</em> for putting regular expressions or other such safety mechanisms in place. This makes your site and visitors <em>wide open</em> to <strong>any and all</strong> malicious code a hacker may plant in the feed.</li>
<li>JSONP is a language specific data type. What I mean by this is that JSONP is designed <em>for</em> <strong>JavaScript</strong>, and even then <strong>strictly in the context of a web browser</strong>. There is no <em>need, none,</em> for JSONP anywhere else or in any other language (PHP, ASP.Net, Ruby on Rails). Making a <em>feed</em> in a language specific format is inherently in <em>bad form</em>.</li>
<li>Adding to the last point, other languages currently cannot naturally parse JSONP. As I ran into in PHP, it is <em>not valid JSON</em> and <code>json_decode</code> will not handle it. My colleagues answer was to write a regular expression to remove the function, but this is at best a hack.</li>
<li>Mixing function and data goes in the face of <em>separation of concerns</em>. You’ve got logic in your data, you’ve got data in your logic. Bad form, good sir, bad form.</li>
</ul>
<p>JSONP is in many cases an unsafe practice, and more over its just <em>evil</em>. It is a necessary evil in some cases, but evil none the less.</p>
<p>There is at <em>least</em> one <strong>safe alternative</strong> to JSONP. The simplest solution provided you have access to server side code is to download, sanitize, optionally cache, and finally pass locally any remote JSON to the client rather than having the clients machine directly load the feed. Yes, this is added overhead on your side, bur for much added <strong>security</strong>.</p>
<p>If nothing else, it is up to you to weigh the risks and benefits before blindly using JSONP.</p>]]></description>
    </item>
    <item>
      <guid>https://donatstudios.com/Tools</guid>
      <title>Tools</title>
      <pubDate>Mon, 12 Oct 2009 22:36:31 -0500</pubDate>
      <link>https://donatstudios.com/Tools</link>
      <description><![CDATA[]]></description>
    </item>
  </channel>
</rss>

