<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~files/feed.xsl"?>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:feedpress="https://feed.press/xmlns" xmlns:media="http://search.yahoo.com/mrss/" xmlns:podcast="https://podcastindex.org/namespace/1.0" version="2.0">
  <channel>
    <feedpress:locale>en</feedpress:locale>
    <atom:link rel="via" href="http://ryanmo.co/feeds/tech.rss.xml"/>
    <atom:link rel="self" href="https://feedpress.me/ryanmoco"/>
    <atom:link rel="hub" href="http://feedpress.superfeedr.com/"/>
    <title>ryanmo.co - Tech</title>
    <link>https://ryanmo.co/</link>
    <description/>
    <lastBuildDate>Sat, 31 Dec 2022 00:00:00 -0800</lastBuildDate>
    <item>
      <title>Hello, again?</title>
      <link>https://ryanmo.co/2022/12/31/hello-again</link>
      <description><![CDATA[<p>It's been a long while since I've posted anything on here. I'm currently working on cleaning up the code and then I might start posting again. </p>
<p>The focus will continue to be on scripting and automations, but a lot has changed since the last post and so I might find new things to post about. There will be RSS feeds for each category and tag so if some things don't interest you, just subscribe to the things that you like.</p>
<p>Happy New Year!</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 31 Dec 2022 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2022-12-31:/2022/12/31/hello-again</guid>
      <category>Tech</category>
    </item>
    <item>
      <title>Create Multiple Tasks in Omnifocus on iOS</title>
      <link>https://ryanmo.co/2017/08/30/create-multiple-tasks-in-omnifocus-on-ios</link>
      <description><![CDATA[<p>Throughout the day I think of things that I need to do after work or this coming weekend. The best way for me to not forget these is to quickly get them into Omnifocus. I've been using Drafts on my iPhone and Alfred on my Mac to quickly create tasks with the appropriate deferral and due dates. This has worked really well except for when I want to add more than one task at a time. </p>
<p>Not too long ago, Omnifocus added the ability to import tasks in Taskpaper format. The import initially seemed very basic and only allowed you to dump them into Omnifocus' Inbox for processing. Digging into the documentation shows that you can be more specific about what project you want those tasks to go into as well as context, defer and due dates.</p>
<p>Up until now, if I wanted to create multiple tasks for tonight, I would have to add each one individually in Drafts and use my "Due Tonight" action, return to Drafts and repeat. Using the Taskpaper syntax import, I can now add multiple lines as tasks in Drafts and import. Each line would look something like this:</p>
<div class="codehilite"><pre><span></span><code><span class="err">- Take out the garbage @flagged(true) @context(Admin &amp; Routines) @defer(6pm) @due(8pm)</span>
<span class="err">- Finish blog post @flagged(true) @context(Admin &amp; Routines) @defer(6pm) @due(8pm)</span>
</code></pre></div>

<p>Since Drafts supports JavaScript, I can create a single script to support my tonight, weekend or today tasks.</p>
<div class="codehilite"><pre><span></span><code><span class="nx">targetTask</span> <span class="o">=</span> <span class="s1">&#39;Todo&#39;</span><span class="p">;</span>
<span class="nx">defaultTaskString</span> <span class="o">=</span> <span class="s1">&#39;@flagged(true) @context(Admin &amp; Routines)&#39;</span><span class="p">;</span>

<span class="nx">dueDateStrings</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">today</span><span class="o">:</span> <span class="s1">&#39;@defer(now) @due(5pm)&#39;</span><span class="p">,</span>
    <span class="nx">tonight</span><span class="o">:</span> <span class="s1">&#39;@defer(6pm) @due(8pm)&#39;</span><span class="p">,</span>
    <span class="nx">weekend</span><span class="o">:</span> <span class="s1">&#39;@defer(next saturday @ 10am) @due(next sunday @ 5pm)&#39;</span>
<span class="p">};</span>

<span class="kd">function</span> <span class="nx">generateOmnifocusUrl</span><span class="p">(</span><span class="nx">params</span> <span class="o">=</span> <span class="p">{}){</span>
    <span class="kd">let</span> <span class="p">{</span><span class="nx">contentLines</span> <span class="o">=</span> <span class="p">[],</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">targetTask</span><span class="p">,</span> <span class="nx">dueTime</span> <span class="o">=</span> <span class="nx">dueDateStrings</span><span class="p">.</span><span class="nx">today</span><span class="p">}</span> <span class="o">=</span> <span class="nx">params</span><span class="p">;</span>

    <span class="nx">contentLines</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">line</span><span class="p">,</span> <span class="nx">index</span><span class="p">,</span> <span class="nx">theArray</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="nx">theArray</span><span class="p">[</span><span class="nx">index</span><span class="p">]</span> <span class="o">=</span> <span class="sb">`- </span><span class="si">${</span><span class="nx">line</span><span class="si">}</span><span class="sb"> </span><span class="si">${</span><span class="nx">dueTime</span><span class="si">}</span><span class="sb"> </span><span class="si">${</span><span class="nx">defaultTaskString</span><span class="si">}</span><span class="sb">`</span>
    <span class="p">);</span>
    <span class="nx">content</span> <span class="o">=</span> <span class="nb">encodeURIComponent</span><span class="p">(</span><span class="nx">contentLines</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s1">&#39;\n&#39;</span><span class="p">));</span>
    <span class="k">return</span> <span class="sb">`omnifocus:///paste?target=/task/</span><span class="si">${</span><span class="nx">target</span><span class="si">}</span><span class="sb">&amp;content=</span><span class="si">${</span><span class="nx">content</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">params</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">contentLines</span><span class="o">:</span> <span class="nx">draft</span><span class="p">.</span><span class="nx">content</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s1">&#39;\n&#39;</span><span class="p">),</span>
    <span class="nx">dueTime</span><span class="o">:</span> <span class="nx">dueDateStrings</span><span class="p">[</span><span class="nx">draft</span><span class="p">.</span><span class="nx">getTag</span><span class="p">(</span><span class="s1">&#39;taskWhen&#39;</span><span class="p">)],</span>
<span class="p">}</span>

<span class="nx">draft</span><span class="p">.</span><span class="nx">defineTag</span><span class="p">(</span><span class="s2">&quot;OFTasks&quot;</span><span class="p">,</span> <span class="nx">generateOmnifocusUrl</span><span class="p">(</span><span class="nx">params</span><span class="p">));</span>
</code></pre></div>

<p>The action will first prompt me asking whether these lines are for tonight, this weekend or today and will then create the TaskPaper lines and open Omnifocus.</p>
<p><img alt="drafts-prompt" src="https://ryanmo.co/posts/Tech/2017-08-30/drafts-prompt.png" /></p>
<p>You can download the action for Drafts <a href="https://drafts4-actions.agiletortoise.com/a/2Ex">here</a>.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Wed, 30 Aug 2017 11:22:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2017-08-30:/2017/08/30/create-multiple-tasks-in-omnifocus-on-ios</guid>
      <category>Tech</category>
      <category>applescript</category>
      <category>efficiency</category>
      <category>ios</category>
      <category>javascript</category>
      <category>omnifocus</category>
    </item>
    <item>
      <title>JSON Feed in Pelican</title>
      <link>https://ryanmo.co/2017/05/18/json-feed-in-pelican</link>
      <description><![CDATA[<p>Brent Simmons and Manton Reece recently <a href="https://jsonfeed.org/2017/05/17/announcing_json_feed">announced</a> an alternative to RSS and Atom using JSON. The format is straight forward and seemed like a great fit to implement in Pelican.</p>
<p>I've been spending a considerable amount of my time lately writing Apex code (Salesforce's proprietary language similar to Java and C#) and have come to appreciate it's ability to serialize different objects. Python isn't particularly good at this, and so I initially struggled with coming up with a clean way of implementing the generator. The new JSON feed spec has many nested objects and so representing these as separate classes made sense. Let's look at an author</p>
<div class="codehilite"><pre><span></span><code><span class="k">class</span> <span class="nc">Author</span><span class="p">(</span><span class="n">Object</span><span class="p">):</span>
    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">avatar</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="n">url</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">avatar</span> <span class="o">=</span> <span class="n">avatar</span>
</code></pre></div>

<p>This is a basic representation of an author based on JSON feed's <a href="https://jsonfeed.org/version/1">spec</a>. If we were to simply try to serialize this in Python using the <code>json</code> library, we'd come across this exception</p>
<div class="codehilite"><pre><span></span><code><span class="ne">TypeError</span><span class="p">:</span> <span class="o">&lt;</span><span class="n">__main__</span><span class="o">.</span><span class="n">Author</span> <span class="nb">object</span> <span class="n">at</span> <span class="mh">0x107d23e10</span><span class="o">&gt;</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">JSON</span> <span class="n">serializable</span>
</code></pre></div>

<p>The <code>json</code> library allows you to pass in your own custom parser, and so I created a base class for all my my objects that would contain one method that the parser would look for as a way to tell it how to serialize each class.</p>
<div class="codehilite"><pre><span></span><code><span class="kn">import</span> <span class="nn">json</span>

<span class="k">class</span> <span class="nc">JSONEncoder</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">JSONEncoder</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">default</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">):</span>
        <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;as_json&#39;</span><span class="p">):</span>
            <span class="k">return</span> <span class="n">obj</span><span class="o">.</span><span class="n">as_json</span><span class="p">()</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONEncoder</span><span class="o">.</span><span class="n">default</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">)</span>

<span class="k">class</span> <span class="nc">Base</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">as_json</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__dict__</span>

<span class="k">class</span> <span class="nc">Author</span><span class="p">(</span><span class="n">Base</span><span class="p">):</span>
    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">avatar</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="n">url</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">avatar</span> <span class="o">=</span> <span class="n">avatar</span>
</code></pre></div>

<p>Now we can call the same method, while passing in our custom JSON encoder to serialize our class</p>
<div class="codehilite"><pre><span></span><code><span class="n">a</span> <span class="o">=</span> <span class="n">Author</span><span class="p">(</span><span class="s1">&#39;Ryan M&#39;</span><span class="p">)</span>
<span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="bp">cls</span><span class="o">=</span><span class="n">JSONEncoder</span><span class="p">)</span>
<span class="c1"># &#39;{&quot;url&quot;: null, &quot;name&quot;: &quot;Ryan M&quot;, &quot;avatar&quot;: null}&#39;</span>
</code></pre></div>

<p>Now it's just a matter of building a class for each object in the JSON feed top-level object</p>
<div class="codehilite"><pre><span></span><code><span class="k">class</span> <span class="nc">Item</span><span class="p">(</span><span class="n">Base</span><span class="p">):</span>
    <span class="k">pass</span>

<span class="c1"># a list of Item classes, since there are many</span>
<span class="k">class</span> <span class="nc">Items</span><span class="p">(</span><span class="nb">list</span><span class="p">):</span>
      <span class="c1"># list type doesn&#39;t have a __dict__ accessor, so we just return the list to be serialized</span>
    <span class="k">def</span> <span class="nf">as_json</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span>

<span class="c1"># The top-level JSON feed object containing all child objects</span>
<span class="k">class</span> <span class="nc">JsonFeed</span><span class="p">(</span><span class="n">Base</span><span class="p">):</span>
    <span class="k">pass</span>
</code></pre></div>

<p>I've left out the implementation details for generating each of these objects for brevity, but the idea is all there. Each class now knows how to tell the <code>json</code> encoder how to be serialized, so it's just a matter of implementing the Pelican plugin and writing the output.</p>
<div class="codehilite"><pre><span></span><code><span class="k">class</span> <span class="nc">JsonFeedGenerator</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">article_generator</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">articles</span> <span class="o">=</span> <span class="n">article_generator</span><span class="o">.</span><span class="n">articles</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">settings</span> <span class="o">=</span> <span class="n">article_generator</span><span class="o">.</span><span class="n">settings</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="n">article_generator</span><span class="o">.</span><span class="n">context</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">generator</span> <span class="o">=</span> <span class="n">article_generator</span>

        <span class="bp">self</span><span class="o">.</span><span class="n">path</span> <span class="o">=</span> <span class="s1">&#39;feed.json&#39;</span>

        <span class="bp">self</span><span class="o">.</span><span class="n">site_url</span> <span class="o">=</span> <span class="n">article_generator</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;SITEURL&#39;</span><span class="p">,</span>
                                                      <span class="n">path_to_url</span><span class="p">(</span><span class="n">get_relative_path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">)))</span>

        <span class="bp">self</span><span class="o">.</span><span class="n">feed_domain</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;FEED_DOMAIN&#39;</span><span class="p">)</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">feed_url</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="si">{}</span><span class="s1">/</span><span class="si">{}</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">feed_domain</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">write_feed</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">complete_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">generator</span><span class="o">.</span><span class="n">output_path</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">)</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">complete_path</span><span class="p">))</span>
        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
            <span class="k">pass</span>

        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">complete_path</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
            <span class="n">json</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">JsonFeed</span><span class="o">.</span><span class="n">from_generator</span><span class="p">(</span><span class="bp">self</span><span class="p">),</span> <span class="n">f</span><span class="p">,</span> <span class="bp">cls</span><span class="o">=</span><span class="n">JSONEncoder</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">get_generators</span><span class="p">(</span><span class="n">article_generator</span><span class="p">):</span>
    <span class="n">json_feed_generator</span> <span class="o">=</span> <span class="n">JsonFeedGenerator</span><span class="p">(</span><span class="n">article_generator</span><span class="p">)</span>
    <span class="n">json_feed_generator</span><span class="o">.</span><span class="n">write_feed</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">register</span><span class="p">():</span>
    <span class="n">signals</span><span class="o">.</span><span class="n">article_generator_finalized</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">get_generators</span><span class="p">)</span>
</code></pre></div>

<p>You can see the feed for this blog <a href="https://ryanmo.co/feed.json">here</a>. The source for the entire plugin can be found on Github <a href="https://github.com/rjames86/myblog/tree/master/pelican_site/plugins/json_feed">here</a>. The plugin should work for all sites right now. I chose not to implement multiple languages into the feed since it doesn't seem like the spec supports this. Hopefully they consider this as they improve the format.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Thu, 18 May 2017 07:28:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2017-05-18:/2017/05/18/json-feed-in-pelican</guid>
      <category>Tech</category>
      <category>pelican</category>
      <category>python</category>
    </item>
    <item>
      <title>Create Dropbox Links from Alfred</title>
      <link>https://ryanmo.co/2017/05/16/create-dropbox-links-from-alfred</link>
      <description><![CDATA[<p>I've always treated Alfred as a Finder replacement. The speed at which I can find and take action on files is faster than Finder.app or Spotlight will ever be able to do. Because of this, I want to have a quick and easy way to share Dropbox files from within Alfred.</p>


<p>Over the years, I've built version of a workflow that lets me share files with Dropbox. They've always been very specific to me and never reliable or secure enough to share with others. The Dropbox API has come a long ways and now gives me the ability to share this workflow without exposing my app secret.</p>
<p><img alt="File Search" src="https://ryanmo.co/posts/Tech/2017-05-16/file_search.png" /></p>
<p>This is my primary way of searching for files. I have Alfred configured so that I can hit the right arrow to take me to the actions menu.</p>
<p><img alt="File Action" src="https://ryanmo.co/posts/Tech/2017-05-16/file_action.png" /></p>
<p>You'll see that I have two actions set up. One for simply creating a link and another for creating a link that expires in a week. The latter is only possible if you have a Dropbox Pro account.</p>
<p><img alt="Notification" src="https://ryanmo.co/posts/Tech/2017-05-16/notification.png" /></p>
<p>Once you've selected one of the options, a notification will appear telling you that the link was created and the link will then be in your clipboard.</p>
<p>You can download the workflow by clicking the Alfred icon below. Instructions for setting up the workflow can be found by clicking on the [x] in the top-right of the workflow once it's installed. Alfred doesn't do a great job making it easy to find the set-up instructions.</p>
<h3 id="features">Features</h3>
<ul>
<li>Supports multiple accounts if you have a personal and business account</li>
<li>You can create any number of expiring links by creating another action and modifying the Alfred Workflow JSON <code>expires</code> key to a number of days</li>
<li>Do to all of the different permissions that Dropbox offers for their business product, if a link already exists for the file you're trying to share with more restrictive permissions, a link won't be created. It's too difficult to expose what permissions that exist in a notification bubble. </li>
</ul>
<p><a href="https://ryanmo.co/downloads/2017-05-16/DropboxSharedLinks.alfredworkflow"><img alt="image" src="https://ryanmo.co/images/alfred_extension.jpg" /></a>  </p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Tue, 16 May 2017 07:30:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2017-05-16:/2017/05/16/create-dropbox-links-from-alfred</guid>
      <category>Tech</category>
      <category>alfred</category>
      <category>dropbox</category>
      <category>efficiency</category>
      <category>python</category>
    </item>
    <item>
      <title>Syncing Photos from Dropbox to the Photos App on iPhone</title>
      <link>https://ryanmo.co/2017/01/23/syncing-photos-from-dropbox-to-the-photos-app-on-iphone</link>
      <description><![CDATA[<p>I've chosen to not use iCloud Photo Library for a few reasons. I have a decent system set up for storing my photos in Dropbox, but I've always wanted to have my photos local on my iPhone. Now that iOS 10 has some cool features like memories and image search, I've been looking for a way to have my photos on my iPhone and also in Dropbox.</p>


<p>The easy solution is to simply point iTunes to your photo library in Dropbox and sync your photos over. This has a few downsides:</p>
<ol>
<li>If your photo library is large, you can't sync everything over</li>
<li>iTunes doesn't let you sync multiple folders, so its all or nothing</li>
<li>iTunes creates a thumbnail cache in the folder of photos, which means you have a large folder constantly syncing to Dropbox, which isn't ideal</li>
</ol>
<p>I already use Hazel to sort and organize my photos, so I figured adding another workflow would be fairly easy. On the computer where I sync my iPhone to iTunes, I set up a workflow that looks like this</p>
<p><img alt="1" src="https://ryanmo.co/posts/Tech/2017-01-23/1.png" /></p>
<p>Since my photo folder structure looks like [year]/[month]/[event], I need to look at each of the photos and then continue the workflow if it matches a matches a shell command</p>
<p><img alt="2" src="https://ryanmo.co/posts/Tech/2017-01-23/2.png" /></p>
<p>As long as what Hazel processes is a folder, and matches this regular expression, we can continue on to process the photos. This regular expression looks for a folder path that contains the the numbers 2015 through 2019<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>. You can change the 5 to be whatever range you need, but I didn't need to go back to photos older than that.</p>
<p>The next step is creating a hard link to the photo to a new folder I keep in <code>~/Pictures</code> called "Photos for iPhone."</p>
<p><img alt="3" src="https://ryanmo.co/posts/Tech/2017-01-23/3.png" /></p>
<p>A hard link is nice here since it simply references the original file and doesn't take up space on your hard drive.</p>
<p>Now I can point iTunes to my newly created folder. Each time a new photo gets added to my Dropbox photos folder, a new hard link is created and then synced to my iPhone the next time I plug it in.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>This will stop working after 2019, but by that point, I'd hope that syncing and viewing photos will be in a better place.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Mon, 23 Jan 2017 07:16:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2017-01-23:/2017/01/23/syncing-photos-from-dropbox-to-the-photos-app-on-iphone</guid>
      <category>Tech</category>
      <category>applescript</category>
      <category>javascript</category>
      <category>scripting</category>
      <category>hazel</category>
    </item>
    <item>
      <title>Setting Keyboard Shortcuts from Terminal in macOS</title>
      <link>https://ryanmo.co/2017/01/05/setting-keyboard-shortcuts-from-terminal-in-macos</link>
      <description><![CDATA[<p>It's been a few months since my last post. I've spent a lot of time working on my blog, but all things behind the scenes that most people wouldn't even notice. </p>
<p>Setting keyboard shortcuts on Mac is actually fairly easy, but it requires a lot of clicking around. Fortunately there's a way to do this from the terminal that's faster and easier.</p>


<p>The <code>defaults</code> command in MacOS is nothing short of a mystery. It does some powerful things, but the documentation is sparse and half of the time I don't know what I'm doing. That being said, I've had a script written for a long time called <code>new_computer.sh</code> where I set all of my favorite global and application-specific shortcuts when getting a new computer.</p>
<p>Let's take an example of a shortcut everyone should have: Print As PDF from within a print dialog. I've always set it to ⌘ ⇧ P. To do this within System Preferences, the steps are:</p>
<ol>
<li>Open the Keyboard preference Pane</li>
<li>Click the Shortcuts tab</li>
<li>Click App Shortcuts</li>
<li>Click the + symbol</li>
<li>Fill out the prompts<ul>
<li>Leave All Applications Selected</li>
<li>Menu Title is "Save as PDF…" (it's an elipsis, not three periods. Type <code>option ;</code> to get the symbol)</li>
<li>Choose your shortcut</li>
</ul>
</li>
</ol>
<p>It's almost too many steps for one shortcut, let alone multiple. Let's try this in Terminal:</p>
<div class="codehilite"><pre><span></span><code>defaults write -globalDomain NSUserKeyEquivalents  -dict-add <span class="s2">&quot;Save as PDF\\U2026&quot;</span> <span class="s2">&quot;@\$p&quot;</span><span class="p">;</span>
</code></pre></div>

<p>Easy, right? Sort of. The syntax for writing global shortcuts is fairly straight forward. If you're not creating a shortcut for a specific application, you can use the command above and simply change the title and shortcut. Here are how to represent all of the modifier keys:</p>
<ul>
<li>@ is command</li>
<li>^ is control</li>
<li>~ is option</li>
<li>$ is shift</li>
</ul>
<p>So command-shift p becomes <code>"@\$p"</code>.</p>
<p>The reason this came up was that Omnifocus recently added tabs. This is great except that there's no shortcut for cycling through the tabs. This makes the feature almost pointless for me. So to add shortcuts, I ended up using the command above, but I need to target Omnifocus only.</p>
<div class="codehilite"><pre><span></span><code>defaults write com.omnigroup.OmniFocus2 NSUserKeyEquivalents -dict-add <span class="s2">&quot;Show Next Tab&quot;</span> <span class="s2">&quot;^\\U005D&quot;</span>
defaults write com.omnigroup.OmniFocus2 NSUserKeyEquivalents -dict-add <span class="s2">&quot;Show Previous Tab&quot;</span> <span class="s2">&quot;^\\U005B&quot;</span>
</code></pre></div>

<p>Here I'm setting show next/previous tab to control [ and control ]. Once you've set your keyboard shortcuts, you'll need to quit and re-launch the application in order for the new preferences to be read.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Thu, 05 Jan 2017 08:54:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2017-01-05:/2017/01/05/setting-keyboard-shortcuts-from-terminal-in-macos</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>bash</category>
    </item>
    <item>
      <title>Publishing from Day One to Pelican with Hazel and Dropbox</title>
      <link>https://ryanmo.co/2016/08/19/publishing-from-day-one-to-pelican-with-hazel-and-dropbox</link>
      <description><![CDATA[<p>I'll be soon embarking on a long bike tour and was searching for a way to keep a journal of my trip but also post updates to a website. Day One was an obvious journaling choice, but with version 2, publishing isn't yet available. With a little poking around, it turned out to be fairly easy to export Day One entries and publish to Pelican (my static blog generator of choice).</p>


<p>I've not been a heavy user of Day One, and with the new version, I've stopped entirely until they provide end-to-end encryption with their proprietary sync service. Journaling my bike trip isn't anything I'm worried about being out in the open, and so I'll use it to keep a log of my days on the trip. At the same time, I want to keep my friends and family up-to-date on my trip. Since I use Pelican for this site, it seemed like a reasonable choice to use it for this trip and use Github Pages as an easy, free place to host it.</p>
<p>The first step was getting the Pelican site set up. I used the basic quickstart and put in a custom theme that I found online. The only modifications I made was using the <a href="https://github.com/getpelican/pelican-plugins/tree/master/photos">photos plugin</a> to make it easier to add galleries if I want in the future. Publishing to Github Pages is trivial. You can follow the steps <a href="http://docs.getpelican.com/en/3.6.3/tips.html#publishing-to-github">here</a>.</p>
<p>Now the fun part. Day One lets you export a journal entry as Markdown. When exported, it's compressed into a zip file which includes a folder of photos if you've included any in the journal entry. For each post, I use the export action and then upload to a folder I've created in Dropbox. I have Hazel watching this folder which will do the following:</p>
<ol>
<li>Unarchive any file that appears</li>
<li>Move the unarchived contents into a new folder I unoriginally name "decompressed"</li>
</ol>
<p><img alt="unarchive" src="https://ryanmo.co/posts/Tech/2016-08-19/unarchive.png" /></p>
<p>I then have a separate rule watching "decompressed" which will</p>
<ol>
<li>Move any image file type into my blog's images folder</li>
<li>Move any text file into the content folder</li>
</ol>
<p><img alt="move_text" src="https://ryanmo.co/posts/Tech/2016-08-19/move_text.png" /></p>
<p>Step 2 here requires a little bit of extra work. Day One has some weird formatting issues and I also need to update the image urls in the entry to match what Pelican expects. The script isn't my finest, but it takes care of everything</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39</pre></div></td><td class="code"><div class="codehilite"><pre><span></span><code><span class="ch">#!/usr/bin/python</span>

<span class="kn">import</span> <span class="nn">codecs</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>

<span class="n">input_file</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>

<span class="n">f</span> <span class="o">=</span> <span class="n">codecs</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">input_file</span><span class="p">,</span>
                <span class="n">mode</span><span class="o">=</span><span class="s1">&#39;r&#39;</span><span class="p">,</span>
                <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>

<span class="c1"># Get rid of the tabs that DayOne inserts</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="sa">u</span><span class="s1">&#39;</span><span class="se">\t</span><span class="s1">Date:&#39;</span><span class="p">,</span> <span class="s1">&#39;Date:&#39;</span><span class="p">)</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="sa">u</span><span class="s1">&#39;</span><span class="se">\t</span><span class="s1">Weather:&#39;</span><span class="p">,</span> <span class="s1">&#39;Weather:&#39;</span><span class="p">)</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="sa">u</span><span class="s1">&#39;</span><span class="se">\t</span><span class="s1">Location:&#39;</span><span class="p">,</span> <span class="s1">&#39;Location:&#39;</span><span class="p">)</span>


<span class="c1"># Replace default Markdown image syntax with Pelican&#39;s syntax + photos plugin</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;![](photos/&#39;</span><span class="p">,</span> <span class="s1">&#39;![](</span><span class="si">{photo}</span><span class="s1">/&#39;</span><span class="p">)</span>


<span class="n">title_re</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\n\n#\s+(.*)\n&#39;</span><span class="p">)</span>
<span class="n">title_search</span> <span class="o">=</span> <span class="n">title_re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="n">now_datestring</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s1">&#39;%B </span><span class="si">%d</span><span class="s1">, %Y at %H:%M:%S %Z&#39;</span><span class="p">)</span>

<span class="c1"># We need a title: header for Pelican</span>
<span class="k">if</span> <span class="n">title_search</span><span class="p">:</span>
    <span class="n">f</span> <span class="o">=</span> <span class="s1">&#39;Title: </span><span class="si">%s</span><span class="se">\n</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">title_search</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">f</span>
    <span class="n">f</span> <span class="o">=</span> <span class="n">title_re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span>
<span class="k">else</span><span class="p">:</span>
    <span class="n">f</span> <span class="o">=</span> <span class="s1">&#39;Title: Update </span><span class="si">%s</span><span class="se">\n</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">now_datestring</span> <span class="o">+</span> <span class="n">f</span>

<span class="k">if</span> <span class="s2">&quot;Date:&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">f</span> <span class="o">=</span> <span class="s2">&quot;Date: </span><span class="si">%s</span><span class="se">\n</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">now_datestring</span> <span class="o">+</span> <span class="n">f</span>

<span class="k">with</span> <span class="n">codecs</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">input_file</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s1">&#39;w&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">new_file</span><span class="p">:</span>
    <span class="n">new_file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</code></pre></div>
</td></tr></table>
<p>Now the file is cleaned up and in the right place. We can now publish and push to Github.</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13</pre></div></td><td class="code"><div class="codehilite"><pre><span></span><code><span class="ch">#!/bin/bash</span>

<span class="nb">cd</span> ~/Dropbox/blogs/biketour/pelican_site

make publish

git add ..

git commit -am <span class="s1">&#39;update blog&#39;</span>

/Users/rjames/dev/pelican/bin/ghp-import output

git push git@github.com:rjames86/rjames86.github.io.git gh-pages:master
</code></pre></div>
</td></tr></table>
<p>That's it! You can see the posts and follow my bike tour at <a href="http://rjames86.github.io">http://rjames86.github.io</a></p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Fri, 19 Aug 2016 11:02:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2016-08-19:/2016/08/19/publishing-from-day-one-to-pelican-with-hazel-and-dropbox</guid>
      <category>Tech</category>
      <category>hazel</category>
      <category>pelican</category>
      <category>scripting</category>
      <category>dropbox</category>
    </item>
    <item>
      <title>Clearing Multiple Notifications in Mac OS X</title>
      <link>https://ryanmo.co/2016/04/18/clearing-multiple-notifications-in-mac-os-x</link>
      <description><![CDATA[<p>If I haven't used my computer for a while, I'll end up with multiple calendar notifications that I have to painfully close one by one. I went searching for something that would let me close them faster, but nothing I could find did quite what I wanted.</p>
<p>Nearly every day I come home from work to a slew of notifications from my day.</p>
<p><img alt="notifications" src="https://ryanmo.co/posts/Tech/2016-04-18/notifications.png" /></p>
<p>One way to close all these is to open up the Notification Center panel and click all of the X's, which will also clear out your notifications. That's still janky and I'd rather have this done without having to click a bunch of times.</p>
<p>I first wrote the script using Applescript. I ran into some annoying issues when trying to ignore certain notification windows, such as System Updates. I wanted to ignore that window entirely, and since Applescript doesn't have a <code>continue</code> in for-loops I opted to use Javascript which ended up being easier.</p>
<div class="codehilite"><pre><span></span><code><span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">Application</span><span class="p">(</span><span class="s2">&quot;System Events&quot;</span><span class="p">)</span>

<span class="nx">notificationCenter</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">processes</span><span class="p">.</span><span class="nx">byName</span><span class="p">(</span><span class="s1">&#39;NotificationCenter&#39;</span><span class="p">)</span>

<span class="kd">function</span> <span class="nx">closeWindow</span><span class="p">(</span><span class="nb">window</span><span class="p">){</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">buttons</span><span class="p">.</span><span class="nx">whose</span><span class="p">({</span>
        <span class="nx">_or</span><span class="o">:</span> <span class="p">[</span>
            <span class="p">{</span><span class="nx">name</span><span class="o">:</span> <span class="s2">&quot;Close&quot;</span><span class="p">},</span>
            <span class="p">{</span><span class="nx">name</span><span class="o">:</span> <span class="s2">&quot;OK&quot;</span><span class="p">}</span>
        <span class="p">]</span>
    <span class="p">})().</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">button</span><span class="p">){</span><span class="nx">button</span><span class="p">.</span><span class="nx">click</span><span class="p">()})</span>
    <span class="nx">delay</span><span class="p">(</span><span class="mf">0.1</span><span class="p">);</span> <span class="c1">// The UI can&#39;t always keep up, so we introduce a short delay</span>
<span class="p">}</span>

<span class="nx">notificationCenter</span><span class="p">.</span><span class="nx">windows</span><span class="p">().</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">closeWindow</span><span class="p">)</span>
</code></pre></div>

<p>I'm currently running this in Keyboard Maestro, but it could just as easily be run with Alfred. I've made very basic versions using both for download:</p>
<ul>
<li><a href="https://ryanmo.co/downloads/ClearNotificationsAlfred.zip">Alfred</a></li>
<li><a href="https://ryanmo.co/downloads/ClearNotificationCenterKM.zip">Keyboard Maestro</a></li>
</ul>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Mon, 18 Apr 2016 20:04:59 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2016-04-18:/2016/04/18/clearing-multiple-notifications-in-mac-os-x</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>keyboardmaestro</category>
      <category>alfred</category>
      <category>efficiency</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Moving TextExpander Snippets to Keyboard Maestro</title>
      <link>https://ryanmo.co/2016/04/10/moving-textexpander-snippets-to-keyboard-maestro</link>
      <description><![CDATA[<p>I've been a long time TextExpander user. I use it every day for simple things like pasting my contact info or shortening urls using bit.ly. There are plenty of articles out there arguing for and against TextExpander's new subscription model. I support their decision but I can't justify $50 a year's worth of value and so I'm moving all of my snippets to Keyboard Maestro.</p>


<p>I had been thinking this weekend whether it would be worth the time to try to migrate all my snippets to Keyboard Maestro. Browsing my Twitter feed, it looked as though <a href="http://leancrew.com/all-this/2016/04/importing-textexpander-snippets-to-keyboard-maestro/">Dr. Drang</a> had beat me to it. Unfortunately he didn't do the work I was hoping I wouldn't have to do, and so I sat down to see how hard it would be to convert snippets to macros. Turns out...not that hard.</p>
<p>Here are the requirements for running this script:</p>
<ul>
<li>Python 2.7 (I didn't test Python 3.x). If you're running an older/newer version of Python, you should be able to replace <code>python</code> with <code>/usr/bin/python2.7</code> when running the script.</li>
<li>TextExpander 5.x. If you are running version 4, your settings will be named Settings.textexpander instead of Settings.textexpandersettings.</li>
</ul>
<p>I've made some decisions as to how I want the snippets to work. Notably</p>
<ul>
<li>Pasting instead of typing. Typing is too slow.</li>
<li>Delete the last clipboard item, since it was the text that was just pasted. </li>
<li>Groups remain the same. If you used groups in TextExpander, they show up as "Snippets - <group name>" in Keyboard Maestro. </li>
</ul>
<p>There are a few things that I haven't yet solved. Some I might in the future, others maybe not:</p>
<ul>
<li>I didn't test Applescript since I didn't have any. Please let me know if that one breaks.</li>
<li>Placeholders and variables from TextExpander won't work. This means if you had a "today's date" snippet, you'll need to rewrite that one<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>.</li>
<li>Custom delimiters. I haven't really figured out if there's a way to do this. I've tried changing Keyboard Maestro to only fire on delimiters, but it doesn't seem to work. If anyone figured this out, please let me know.</li>
</ul>
<p>Before running, be sure to update the variable <code>TEXTEXPANDER_PATH</code> to wherever your TextExpander settings file lives. To run, it's as simple as navigating to the location where the script lives in Terminal.app and entering</p>
<div class="codehilite"><pre><span></span><code><span class="err">python TE.py</span>
</code></pre></div>

<p>You'll now have a folder on your Desktop named 'TextExpander_to_KeyboardMaestro' with all of your groups.</p>
<hr />
<p><em>Update 2016-04-12</em></p>
<p>Big thanks to NW in the comments for helping me debug a few things.</p>
<p>I've removed the import of <code>enum</code>. I had forgotten that wasn't a standard library in Python 2.7. I also added a list of requirements above for running the script.</p>
<p><em>Update 2016-04-14</em></p>
<p>Also thanks to Dr Drang for posting about the script! I've made some updates and also put up a repo for those who would like to make edits and pull requests.</p>
<p>Updates are</p>
<ul>
<li>Optional prefix if you want to change that up when moving to Keyboard Maestro</li>
<li>Insert text by typing OR pasting</li>
<li>Added some instruction on how to edit the variables to have the script do what you want</li>
</ul>
<p>The repo is now hosted at <a href="https://github.com/rjames86/textexpander_to_keyboardmaestro">https://github.com/rjames86/textexpander_to_keyboardmaestro</a></p>
<hr />
<p>You can download the script on Github <a href="https://raw.githubusercontent.com/rjames86/textexpander_to_keyboardmaestro/master/TE.py">here</a></p>
<div class="codehilite"><pre><span></span><code><span class="kn">import</span> <span class="nn">plistlib</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">glob</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">This script will parse through all group_*.xml files within your TextExpander folder.</span>
<span class="sd">Anything marked as Plain Text, Shell Script or JavaScript should be converted into</span>
<span class="sd">Keyboard Maestro groups with the same title and abbreviation.</span>

<span class="sd">All new KM Macro files will be saved to the Desktop.</span>

<span class="sd">&#39;&#39;&#39;</span>

<span class="c1"># Modify this area to customize how the script will run</span>

<span class="c1"># Change this path to where ever your TextExander Settings live</span>
<span class="n">HOME</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">expanduser</span><span class="p">(</span><span class="s1">&#39;~&#39;</span><span class="p">)</span>
<span class="n">TEXTEXPANDER_PATH</span> <span class="o">=</span> <span class="n">HOME</span> <span class="o">+</span> <span class="s1">&#39;/Dropbox/TextExpander/Settings.textexpandersettings&#39;</span>
<span class="n">SAVE_PATH</span> <span class="o">=</span> <span class="n">HOME</span> <span class="o">+</span> <span class="s1">&#39;/Desktop/TextExpander_to_KeyboardMaestro&#39;</span>

<span class="c1"># Change this if you&#39;d like to change your snippets when importing to Keyboard Maestro</span>
<span class="c1"># If your snippet is ttest, you can make it ;;ttest by changing the variable to &#39;;;&#39;</span>
<span class="n">OPTIONAL_NEW_PREFIX</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>

<span class="c1"># Change this if you want the snippet to inserted by typing or pasting</span>
<span class="c1"># Remember it MUST be &#39;paste&#39; or &#39;type&#39; or the script will fail</span>
<span class="n">PASTE_OR_TYPE</span> <span class="o">=</span> <span class="s1">&#39;paste&#39;</span> <span class="c1"># &#39;type&#39;</span>




<span class="c1">############</span>

<span class="c1"># Edit below at your own risk</span>

<span class="c1">############</span>

<span class="n">snippet_types</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">&#39;plaintext&#39;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="s1">&#39;applescript&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="s1">&#39;shell&#39;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
    <span class="s1">&#39;javascript&#39;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="p">}</span>

<span class="n">snippet_types_to_values</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">((</span><span class="n">value</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span> <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">snippet_types</span><span class="o">.</span><span class="n">iteritems</span><span class="p">())</span>


<span class="k">class</span> <span class="nc">KeyboardMaestroMacros</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="nd">@classmethod</span>
    <span class="k">def</span> <span class="nf">macro_by_name</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">macro_name</span><span class="p">,</span> <span class="n">group_name</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">abbreviation</span><span class="p">):</span>
        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">macro_name</span><span class="p">)(</span><span class="n">group_name</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">abbreviation</span><span class="p">)</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">javascript</span><span class="p">(</span><span class="n">group_name</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">abbreviation</span><span class="p">):</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="s1">&#39;Activate&#39;</span><span class="p">:</span> <span class="s1">&#39;Normal&#39;</span><span class="p">,</span>
            <span class="s1">&#39;CreationDate&#39;</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
            <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
            <span class="s1">&#39;Macros&#39;</span><span class="p">:</span> <span class="p">[</span>
                <span class="p">{</span><span class="s1">&#39;Actions&#39;</span><span class="p">:</span> <span class="p">[</span>
                    <span class="p">{</span><span class="s1">&#39;DisplayKind&#39;</span><span class="p">:</span> <span class="n">KeyboardMaestroMacros</span><span class="o">.</span><span class="n">_paste_or_type</span><span class="p">(),</span>
                     <span class="s1">&#39;IncludeStdErr&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;IsDisclosed&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;MacroActionType&#39;</span><span class="p">:</span> <span class="s1">&#39;ExecuteJavaScriptForAutomation&#39;</span><span class="p">,</span>
                     <span class="s1">&#39;Path&#39;</span><span class="p">:</span> <span class="s1">&#39;&#39;</span><span class="p">,</span>
                     <span class="s1">&#39;Text&#39;</span><span class="p">:</span> <span class="n">text</span><span class="p">,</span>
                     <span class="s1">&#39;TimeOutAbortsMacro&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;TrimResults&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;TrimResultsNew&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;UseText&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">},</span> <span class="p">{</span>
                        <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                        <span class="s1">&#39;IsDisclosed&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                        <span class="s1">&#39;MacroActionType&#39;</span><span class="p">:</span> <span class="s1">&#39;DeletePastClipboard&#39;</span><span class="p">,</span>
                        <span class="s1">&#39;PastExpression&#39;</span><span class="p">:</span> <span class="s1">&#39;0&#39;</span><span class="p">}</span>
                    <span class="p">],</span>
                 <span class="s1">&#39;CreationDate&#39;</span><span class="p">:</span> <span class="mf">482018934.65354</span><span class="p">,</span>
                 <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                 <span class="s1">&#39;ModificationDate&#39;</span><span class="p">:</span> <span class="mf">482018953.856014</span><span class="p">,</span>
                 <span class="s1">&#39;Name&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
                 <span class="s1">&#39;Triggers&#39;</span><span class="p">:</span> <span class="p">[{</span>
                    <span class="s1">&#39;Case&#39;</span><span class="p">:</span> <span class="s1">&#39;Exact&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;DiacriticalsMatter&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;MacroTriggerType&#39;</span><span class="p">:</span> <span class="s1">&#39;TypedString&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;OnlyAfterWordBreak&#39;</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span>
                    <span class="s1">&#39;SimulateDeletes&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;TypedString&#39;</span><span class="p">:</span> <span class="n">KeyboardMaestroMacros</span><span class="o">.</span><span class="n">_abbreviation</span><span class="p">(</span><span class="n">abbreviation</span><span class="p">)}]}</span>
            <span class="p">],</span>
            <span class="s1">&#39;Name&#39;</span><span class="p">:</span> <span class="s1">&#39;Snippet - </span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">group_name</span><span class="p">,</span>
        <span class="p">}</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">applescript</span><span class="p">(</span><span class="n">group_name</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">abbreviation</span><span class="p">):</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="s1">&#39;Activate&#39;</span><span class="p">:</span> <span class="s1">&#39;Normal&#39;</span><span class="p">,</span>
            <span class="s1">&#39;CreationDate&#39;</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
            <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
            <span class="s1">&#39;Macros&#39;</span><span class="p">:</span> <span class="p">[</span>
                <span class="p">{</span><span class="s1">&#39;Actions&#39;</span><span class="p">:</span> <span class="p">[</span>
                    <span class="p">{</span><span class="s1">&#39;DisplayKind&#39;</span><span class="p">:</span> <span class="n">KeyboardMaestroMacros</span><span class="o">.</span><span class="n">_paste_or_type</span><span class="p">(),</span>
                     <span class="s1">&#39;IncludeStdErr&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;IsDisclosed&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;MacroActionType&#39;</span><span class="p">:</span> <span class="s1">&#39;ExecuteAppleScript&#39;</span><span class="p">,</span>
                     <span class="s1">&#39;Path&#39;</span><span class="p">:</span> <span class="s1">&#39;&#39;</span><span class="p">,</span>
                     <span class="s1">&#39;Text&#39;</span><span class="p">:</span> <span class="n">text</span><span class="p">,</span>
                     <span class="s1">&#39;TimeOutAbortsMacro&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;TrimResults&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;TrimResultsNew&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                     <span class="s1">&#39;UseText&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">},</span> <span class="p">{</span>
                        <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                        <span class="s1">&#39;IsDisclosed&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                        <span class="s1">&#39;MacroActionType&#39;</span><span class="p">:</span> <span class="s1">&#39;DeletePastClipboard&#39;</span><span class="p">,</span>
                        <span class="s1">&#39;PastExpression&#39;</span><span class="p">:</span> <span class="s1">&#39;0&#39;</span><span class="p">}</span>
                    <span class="p">],</span>
                 <span class="s1">&#39;CreationDate&#39;</span><span class="p">:</span> <span class="mf">482018934.65354</span><span class="p">,</span>
                 <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                 <span class="s1">&#39;ModificationDate&#39;</span><span class="p">:</span> <span class="mf">482018953.856014</span><span class="p">,</span>
                 <span class="s1">&#39;Name&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
                 <span class="s1">&#39;Triggers&#39;</span><span class="p">:</span> <span class="p">[{</span>
                    <span class="s1">&#39;Case&#39;</span><span class="p">:</span> <span class="s1">&#39;Exact&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;DiacriticalsMatter&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;MacroTriggerType&#39;</span><span class="p">:</span> <span class="s1">&#39;TypedString&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;OnlyAfterWordBreak&#39;</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span>
                    <span class="s1">&#39;SimulateDeletes&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;TypedString&#39;</span><span class="p">:</span> <span class="n">KeyboardMaestroMacros</span><span class="o">.</span><span class="n">_abbreviation</span><span class="p">(</span><span class="n">abbreviation</span><span class="p">)}]}</span>
            <span class="p">],</span>
            <span class="s1">&#39;Name&#39;</span><span class="p">:</span> <span class="s1">&#39;Snippet - </span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">group_name</span><span class="p">,</span>
        <span class="p">}</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">plaintext</span><span class="p">(</span><span class="n">group_name</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">abbreviation</span><span class="p">):</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="s1">&#39;Activate&#39;</span><span class="p">:</span> <span class="s1">&#39;Normal&#39;</span><span class="p">,</span>
            <span class="s1">&#39;CreationDate&#39;</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
            <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
            <span class="s1">&#39;Macros&#39;</span><span class="p">:</span> <span class="p">[{</span><span class="s1">&#39;Actions&#39;</span><span class="p">:</span> <span class="p">[</span>
                <span class="p">{</span>
                    <span class="s1">&#39;Action&#39;</span><span class="p">:</span> <span class="n">KeyboardMaestroMacros</span><span class="o">.</span><span class="n">_paste_or_type</span><span class="p">(</span><span class="s1">&#39;plaintext&#39;</span><span class="p">),</span>
                    <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;IsDisclosed&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;MacroActionType&#39;</span><span class="p">:</span> <span class="s1">&#39;InsertText&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;Paste&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;Text&#39;</span><span class="p">:</span> <span class="n">text</span><span class="p">},</span> <span class="p">{</span>
                        <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                        <span class="s1">&#39;IsDisclosed&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                        <span class="s1">&#39;MacroActionType&#39;</span><span class="p">:</span> <span class="s1">&#39;DeletePastClipboard&#39;</span><span class="p">,</span>
                        <span class="s1">&#39;PastExpression&#39;</span><span class="p">:</span> <span class="s1">&#39;0&#39;</span>
                    <span class="p">}],</span>
                <span class="s1">&#39;CreationDate&#39;</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
                <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                <span class="s1">&#39;ModificationDate&#39;</span><span class="p">:</span> <span class="mf">482031702.132113</span><span class="p">,</span>
                <span class="s1">&#39;Name&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
                <span class="s1">&#39;Triggers&#39;</span><span class="p">:</span> <span class="p">[{</span>
                    <span class="s1">&#39;Case&#39;</span><span class="p">:</span> <span class="s1">&#39;Exact&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;DiacriticalsMatter&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;MacroTriggerType&#39;</span><span class="p">:</span> <span class="s1">&#39;TypedString&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;OnlyAfterWordBreak&#39;</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span>
                    <span class="s1">&#39;SimulateDeletes&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;TypedString&#39;</span><span class="p">:</span> <span class="n">KeyboardMaestroMacros</span><span class="o">.</span><span class="n">_abbreviation</span><span class="p">(</span><span class="n">abbreviation</span><span class="p">)}],</span>
            <span class="p">}],</span>
            <span class="s1">&#39;Name&#39;</span><span class="p">:</span> <span class="s1">&#39;Snippet - </span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">group_name</span><span class="p">,</span>
        <span class="p">}</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">shell</span><span class="p">(</span><span class="n">group_name</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">abbreviation</span><span class="p">):</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="s1">&#39;Activate&#39;</span><span class="p">:</span> <span class="s1">&#39;Normal&#39;</span><span class="p">,</span>
            <span class="s1">&#39;CreationDate&#39;</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
            <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
            <span class="s1">&#39;Macros&#39;</span><span class="p">:</span> <span class="p">[{</span>
                <span class="s1">&#39;Actions&#39;</span><span class="p">:</span> <span class="p">[{</span>
                    <span class="s1">&#39;DisplayKind&#39;</span><span class="p">:</span> <span class="n">KeyboardMaestroMacros</span><span class="o">.</span><span class="n">_paste_or_type</span><span class="p">(),</span>
                    <span class="s1">&#39;IncludeStdErr&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;IsDisclosed&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;MacroActionType&#39;</span><span class="p">:</span> <span class="s1">&#39;ExecuteShellScript&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;Path&#39;</span><span class="p">:</span> <span class="s1">&#39;&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;Text&#39;</span><span class="p">:</span> <span class="n">text</span><span class="p">,</span>
                    <span class="s1">&#39;TimeOutAbortsMacro&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;TrimResults&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;TrimResultsNew&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;UseText&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">},</span>
                 <span class="p">{</span><span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                  <span class="s1">&#39;IsDisclosed&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                  <span class="s1">&#39;MacroActionType&#39;</span><span class="p">:</span> <span class="s1">&#39;DeletePastClipboard&#39;</span><span class="p">,</span>
                  <span class="s1">&#39;PastExpression&#39;</span><span class="p">:</span> <span class="s1">&#39;0&#39;</span><span class="p">}],</span>
                <span class="s1">&#39;CreationDate&#39;</span><span class="p">:</span> <span class="mf">482018896.698121</span><span class="p">,</span>
                <span class="s1">&#39;IsActive&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                <span class="s1">&#39;ModificationDate&#39;</span><span class="p">:</span> <span class="mf">482020783.300151</span><span class="p">,</span>
                <span class="s1">&#39;Name&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
                <span class="s1">&#39;Triggers&#39;</span><span class="p">:</span> <span class="p">[{</span>
                    <span class="s1">&#39;Case&#39;</span><span class="p">:</span> <span class="s1">&#39;Exact&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;DiacriticalsMatter&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;MacroTriggerType&#39;</span><span class="p">:</span> <span class="s1">&#39;TypedString&#39;</span><span class="p">,</span>
                    <span class="s1">&#39;OnlyAfterWordBreak&#39;</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span>
                    <span class="s1">&#39;SimulateDeletes&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
                    <span class="s1">&#39;TypedString&#39;</span><span class="p">:</span> <span class="n">KeyboardMaestroMacros</span><span class="o">.</span><span class="n">_abbreviation</span><span class="p">(</span><span class="n">abbreviation</span><span class="p">)}],</span>
                <span class="p">}],</span>
            <span class="s1">&#39;Name&#39;</span><span class="p">:</span> <span class="s1">&#39;Snippet - </span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">group_name</span><span class="p">,</span>
        <span class="p">}</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">_abbreviation</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">OPTIONAL_NEW_PREFIX</span> <span class="o">+</span> <span class="n">name</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">_paste_or_type</span><span class="p">(</span><span class="n">snippet_type</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="n">value</span> <span class="o">=</span> <span class="p">{</span>
            <span class="s1">&#39;paste&#39;</span><span class="p">:</span> <span class="s2">&quot;Pasting&quot;</span><span class="p">,</span>
            <span class="s1">&#39;type&#39;</span><span class="p">:</span> <span class="s2">&quot;Typing&quot;</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="n">snippet_type</span> <span class="o">==</span> <span class="s1">&#39;plaintext&#39;</span><span class="p">:</span>
            <span class="k">return</span> <span class="s2">&quot;By</span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">value</span><span class="p">[</span><span class="n">PASTE_OR_TYPE</span><span class="p">]</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">value</span><span class="p">[</span><span class="n">PASTE_OR_TYPE</span><span class="p">]</span>


<span class="k">def</span> <span class="nf">parse_textexpander</span><span class="p">():</span>
    <span class="sd">&#39;&#39;&#39;</span>
<span class="sd">    Each TextExpander group is its own file starting with the file name &#39;group_&#39;.</span>

<span class="sd">    Example snippet dictionary</span>
<span class="sd">    {</span>
<span class="sd">        &#39;abbreviation&#39;: &#39;.bimg&#39;,</span>
<span class="sd">        &#39;abbreviationMode&#39;: 0,</span>
<span class="sd">        &#39;creationDate&#39;: datetime.datetime(2013, 5, 19, 19, 42, 16),</span>
<span class="sd">        &#39;label&#39;: &#39;&#39;,</span>
<span class="sd">        &#39;modificationDate&#39;: datetime.datetime(2015, 1, 10, 20, 19, 59),</span>
<span class="sd">        &#39;plainText&#39;: &#39;some text,</span>
<span class="sd">        &#39;snippetType&#39;: 3,</span>
<span class="sd">        &#39;uuidString&#39;: &#39;100F8D1F-A2D1-4313-8B55-EFD504AE7894&#39;</span>
<span class="sd">    }</span>

<span class="sd">    Return a list of dictionaries where the keys are the name of the group</span>
<span class="sd">    &#39;&#39;&#39;</span>
    <span class="n">to_ret</span> <span class="o">=</span> <span class="p">{}</span>

    <span class="c1"># Let&#39;s get all the xml group files in the directory</span>
    <span class="n">xml_files</span> <span class="o">=</span> <span class="p">[</span><span class="n">f</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">TEXTEXPANDER_PATH</span> <span class="o">+</span> <span class="s2">&quot;/*.xml&quot;</span><span class="p">)</span>
                 <span class="k">if</span> <span class="n">f</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">TEXTEXPANDER_PATH</span> <span class="o">+</span> <span class="s2">&quot;/group_&quot;</span><span class="p">)]</span>

    <span class="k">for</span> <span class="n">xml_file</span> <span class="ow">in</span> <span class="n">xml_files</span><span class="p">:</span>
        <span class="n">pl</span> <span class="o">=</span> <span class="n">plistlib</span><span class="o">.</span><span class="n">readPlist</span><span class="p">(</span><span class="n">xml_file</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">pl</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">to_ret</span><span class="p">:</span>
            <span class="n">to_ret</span><span class="p">[</span><span class="n">pl</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="k">for</span> <span class="n">snippet</span> <span class="ow">in</span> <span class="n">pl</span><span class="p">[</span><span class="s1">&#39;snippetPlists&#39;</span><span class="p">]:</span>
            <span class="k">if</span> <span class="n">snippet</span><span class="p">[</span><span class="s1">&#39;snippetType&#39;</span><span class="p">]</span> <span class="ow">in</span> <span class="n">snippet_types</span><span class="o">.</span><span class="n">values</span><span class="p">():</span>
                <span class="n">to_ret</span><span class="p">[</span><span class="n">pl</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">snippet</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">to_ret</span>


<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">text_expanders</span> <span class="o">=</span> <span class="n">parse_textexpander</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">group</span><span class="p">,</span> <span class="n">text_expander</span> <span class="ow">in</span> <span class="n">text_expanders</span><span class="o">.</span><span class="n">iteritems</span><span class="p">():</span>
        <span class="n">macros_to_create</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="k">for</span> <span class="n">snippet</span> <span class="ow">in</span> <span class="n">text_expander</span><span class="p">:</span>
            <span class="n">macros_to_create</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
                <span class="n">KeyboardMaestroMacros</span><span class="o">.</span><span class="n">macro_by_name</span><span class="p">(</span><span class="n">snippet_types_to_values</span><span class="p">[</span><span class="n">snippet</span><span class="p">[</span><span class="s1">&#39;snippetType&#39;</span><span class="p">]],</span>
                                                    <span class="n">group</span><span class="p">,</span>
                                                    <span class="n">snippet</span><span class="p">[</span><span class="s1">&#39;label&#39;</span><span class="p">],</span>
                                                    <span class="n">snippet</span><span class="p">[</span><span class="s1">&#39;plainText&#39;</span><span class="p">],</span>
                                                    <span class="n">snippet</span><span class="p">[</span><span class="s1">&#39;abbreviation&#39;</span><span class="p">])</span>
                <span class="p">)</span>

        <span class="c1"># Create a new folder on the desktop to put the macros</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">SAVE_PATH</span><span class="p">):</span>
            <span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">SAVE_PATH</span><span class="p">)</span>
        <span class="c1"># Save the macros</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">SAVE_PATH</span> <span class="o">+</span> <span class="s1">&#39;/</span><span class="si">%s</span><span class="s1">.kmmacros&#39;</span> <span class="o">%</span> <span class="n">group</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
            <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">plistlib</span><span class="o">.</span><span class="n">writePlistToString</span><span class="p">(</span><span class="n">macros_to_create</span><span class="p">))</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">main</span><span class="p">()</span>
</code></pre></div>

<p>Feedback and pull requests welcome. I'll continue to update the script and this post if I make any substantial iterations.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>Hint. If you had your date look like 2016-04-10, the Keyboard Maestro equivalent is <code>%ICUDateTime%yyyy-MM-dd%</code>&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 10 Apr 2016 17:26:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2016-04-10:/2016/04/10/moving-textexpander-snippets-to-keyboard-maestro</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>textexpander</category>
      <category>python</category>
      <category>keyboardmaestro</category>
    </item>
    <item>
      <title>Searching Todo’s in Code</title>
      <link>https://ryanmo.co/2016/01/11/searching-todos-in-code</link>
      <description><![CDATA[<p>Happy 2016! It's been a while since I've gotten something up here.</p>

<p>Last week at work I was working on a fairly large refactor of our front-end. Large pieces of code were being moved around and others re-written to be cleaner and more understandable. Throughout this process, I was leaving myself todo's so that I'd remember to fix something later.  Problem is, I would rarely ever go back to them. That was until someone on my team shared some bash functions they had written to make following up on those todo's much easier</p>


<p>It's fairly common practice to leave yourself todo's as comments in code such as</p>
<div class="codehilite"><pre><span></span><code><span class="err"># TODO(ryan) fix this later.</span>
</code></pre></div>

<p>That way if someone comes across it in the future, they'll know that whatever is below may not be perfect and that I plan on fixing it at some point. Finding all your todo's later is a different story. That's where some fancy bash functions come in handy.</p>
<div class="codehilite"><pre><span></span><code><span class="k">function</span> ga_code_search<span class="o">()</span> <span class="o">{</span>
    <span class="c1"># alias todo=&#39;ga_code_search &quot;TODO\(`whoami`\)&quot;&#39;</span>
    <span class="nv">SCREEN_WIDTH</span><span class="o">=</span><span class="sb">`</span>stty size <span class="p">|</span> awk <span class="s1">&#39;{print $2}&#39;</span><span class="sb">`</span>
    <span class="nv">SCREEN_WIDTH</span><span class="o">=</span><span class="k">$((</span>SCREEN_WIDTH-4<span class="k">))</span>
    <span class="c1"># Given a spooky name so you can alias to whatever you want. </span>
    <span class="c1"># (cs for codesearch)</span>
    <span class="c1"># AG is WAY faster but requires a binary </span>
    <span class="c1"># (try brew install the_silver_searcher)</span>
    <span class="nv">AG_SEARCH</span><span class="o">=</span><span class="s1">&#39;ag &quot;$1&quot; | sort -k1 | cat -n | cut -c 1-$SCREEN_WIDTH&#39;</span>

    <span class="c1"># egrep is installed everywhere and is the default.</span>
    <span class="nv">GREP_SEARCH</span><span class="o">=</span><span class="s1">&#39;egrep -nR &quot;$1&quot; * | sort -k1 | cat -n | cut -c 1-$SCREEN_WIDTH&#39;</span>

    <span class="nv">SEARCH</span><span class="o">=</span><span class="nv">$AG_SEARCH</span>

    <span class="k">if</span> <span class="o">[</span> <span class="nv">$#</span> -eq <span class="m">0</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>

        <span class="nb">echo</span> <span class="s2">&quot;Usage: ga_code_search &lt;search&gt; &lt;index_to_edit&gt;&quot;</span>
        <span class="nb">echo</span> <span class="s2">&quot;&quot;</span>
        <span class="nb">echo</span> <span class="s2">&quot;Examples:&quot;</span>
        <span class="nb">echo</span> <span class="s2">&quot;    ga_code_search TODO&quot;</span>
        <span class="nb">echo</span> <span class="s2">&quot;    ga_code_search TODO 1&quot;</span>
        <span class="nb">echo</span> <span class="s2">&quot;    ga_code_search \&quot;TODO\\(graham\\)\&quot;&quot;</span>
        <span class="nb">echo</span> <span class="s2">&quot;    ga_code_search \&quot;TODO\\(graham\\)\&quot; 4&quot;</span>
        <span class="nb">echo</span> <span class="s2">&quot;&quot;</span>        
        <span class="k">return</span>
    <span class="k">fi</span>

    <span class="k">if</span> <span class="o">[</span> <span class="nv">$#</span> -eq <span class="m">1</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
        <span class="c1"># There are no command line argumnets.</span>
        <span class="nb">eval</span> <span class="nv">$SEARCH</span>
    <span class="k">else</span>
        <span class="c1"># arg one should be a line from the output of above.</span>
        <span class="nv">LINE</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$SEARCH</span><span class="s2"> | sed &#39;</span><span class="nv">$2</span><span class="s2">q;d&#39; | awk -F&#39;:&#39; &#39;{print +\$2 \&quot; \&quot; \$1}&#39; | awk -F&#39; &#39; &#39;{print \$1 \&quot; \&quot; \$3}&#39;&quot;</span>
        <span class="c1"># Modify with your editor here.</span>
        emacs <span class="se">\+</span><span class="sb">`</span><span class="nb">eval</span> <span class="nv">$LINE</span><span class="sb">`</span>
    <span class="k">fi</span>    
<span class="o">}</span>
</code></pre></div>

<p>If you read through the comments, <code>the_silver_searcher</code> is far faster than <code>grep</code> for searching contents of files. If you don't have it already, I'd highly suggest installing it with <code>brew install the_silver_searcher</code>. If you don't want to, be sure to change <code>SEARCH=$AG_SEARCH</code> to <code>SEARCH=$GREP_SEARCH</code>.</p>
<p>The function itself isn't that interesting. It's when you assign aliases to use this function that things become interesting. Here are the three that were given to me:</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># Find todo items that are assigned to me. TODO(ryan)</span>
<span class="c1"># You can change `whoami` to whatever you want.</span>
<span class="nb">alias</span> <span class="nv">todo</span><span class="o">=</span><span class="s1">&#39;ga_code_search &quot;TODO\(`whoami`\)&quot;&#39;</span>

<span class="c1"># Find merge conflicts that need to be resolved.</span>
<span class="nb">alias</span> <span class="nv">conflicts</span><span class="o">=</span><span class="s1">&#39;ga_code_search &quot;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&quot;&#39;</span>

<span class="c1"># Find anything below your CWD.</span>
<span class="c1"># You can now type `cs some_piece_of_code`</span>
<span class="nb">alias</span> <span class="nv">cs</span><span class="o">=</span><span class="s1">&#39;ga_code_search&#39;</span>
</code></pre></div>

<p>My favorite by far is the first alias <code>todo</code>. Here is some example output when running this command:</p>
<div class="codehilite"><pre><span></span><code>&gt; my_project <span class="o">(</span>master<span class="o">)</span>: todo
 <span class="m">1</span>  app/models/strava.py:102: <span class="c1"># TODO(ryan) probably should memoize this at some point so its faster.</span>
 <span class="m">2</span>  app/models/strava.py:148: <span class="c1"># TODO(ryan) make this line prettier</span>
 <span class="m">3</span>  app/templates/strava/index.html:50: &lt;!-- TODO<span class="o">(</span>ryan<span class="o">)</span> move this into its own template file at some point --&gt;
</code></pre></div>

<p>Notice how there are numbers next to each result? That's because you can also open the file right to that todo item by typing <code>todo 1</code>! As the function is written, it will open in emacs. If that's your editor of choice, you'll be set. I'm personally a fan of Sublime Text. There's a way to also open a file in Sublime Text to a specific line number. Simply change the text in red with that in green:</p>
<div class="codehilite"><pre><span></span><code><span class="gd">- LINE=&quot;$SEARCH | sed &#39;$2q;d&#39; | awk -F&#39;:&#39; &#39;{print +\$2 \&quot; \&quot; \$1}&#39; | awk -F&#39; &#39; &#39;{print \$1 \&quot; \&quot; \$3}&#39;&quot;</span>
<span class="gi">+ LINE=&quot;$SEARCH | sed &#39;$2q;d&#39; | awk -F&#39;:&#39; &#39;{print +\$2 \&quot; \&quot; \$1}&#39; | awk -F&#39; &#39; &#39;{print \$3 \&quot;:\&quot; \$1}&#39;&quot;</span>

<span class="gd">- emacs \+`eval $LINE`</span>
<span class="gi">+ subl `eval $LINE`</span>
</code></pre></div>

<p>I've only used the functions for a few days now, but it's greatly improved my workflow for getting old todo's done in code. If you'd like to download these scripts, <a href="https://gist.github.com/2819e0576e9280a985ae">here</a> is the Sublime Text version and the <a href="https://gist.github.com/1351952bdc55d206d939">emacs</a> version.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Mon, 11 Jan 2016 08:55:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2016-01-11:/2016/01/11/searching-todos-in-code</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>efficiency</category>
      <category>bash</category>
      <category>sublimetext</category>
    </item>
    <item>
      <title>List Server Favorites in OS X 10.11 El Capitan</title>
      <link>https://ryanmo.co/2015/10/31/list-server-favorites-in-os-x-1011-el-capitan</link>
      <description><![CDATA[<p>I'm using Alfred a lot less these days. Many of my workflows have been easier to build in Keyboard Maestro. The remaining few that are left in Alfred are ones that I heavily depend on, one of which is accessing my Server Favorites in OS X. </p>
<p>Up until OS X 10.11 El Capitan, Server Favorites were stored in a plist file called <code>com.apple.sidebarlists.plist</code>. I finally got around to upgrading my computers at home only to realize that my "server" workflow stopped working. After inspecting the plist file, I found that those favorites were gone and were hiding elsewhere. After a bunch of searching, and the help of Houdaspot, I found them in <code>~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.FavoriteServers.sfl</code>. What is this <code>sfl</code> extension? Still not really sure, but after some poking around, <a href="https://gist.github.com/pudquick/4776b4b2075bf9b7e512">this</a> was the only resource I could find that helped me get started. </p>
<p>I don't necessarily like the CoreFoundation stuff in Python, and since I'm on a OS X JavaScript automation roll right now, I decided to give it a try. Turns out, it's really easy.</p>
<div class="codehilite"><pre><span></span><code><span class="nx">items</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">NSKeyedUnarchiver</span><span class="p">.</span><span class="nx">unarchiveObjectWithFile</span><span class="p">(</span><span class="s1">&#39;/Users/username/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.FavoriteServers.sfl&#39;</span><span class="p">)</span>

<span class="nx">items</span> <span class="o">=</span> <span class="nx">items</span><span class="p">.</span><span class="nx">objectForKey</span><span class="p">(</span><span class="s1">&#39;items&#39;</span><span class="p">)</span>
<span class="nx">itemsCount</span> <span class="o">=</span> <span class="nx">items</span><span class="p">.</span><span class="nx">count</span>

<span class="nx">to_ret</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">itemsCount</span><span class="o">--</span><span class="p">){</span>
      <span class="nx">item</span> <span class="o">=</span> <span class="nx">items</span><span class="p">.</span><span class="nx">objectAtIndex</span><span class="p">(</span><span class="nx">itemsCount</span><span class="p">)</span>
    <span class="nx">to_ret</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span>
        <span class="p">{</span>
            <span class="nx">name</span><span class="o">:</span> <span class="nx">item</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> 
            <span class="nx">url</span><span class="o">:</span> <span class="nx">item</span><span class="p">.</span><span class="nx">URL</span><span class="p">.</span><span class="nx">absoluteString</span>
        <span class="p">}</span>
    <span class="p">)</span>
<span class="p">}</span>

<span class="nx">to_ret</span>
</code></pre></div>

<p>This returns a nice object of the name and url for the servers in your favorites.</p>
<p>You can download the workflow here.</p>
<p><a href="https://ryanmo.co/downloads/VNC_Favorites.alfredworkflow"><img alt="image" src="https://ryanmo.co/images/alfred_extension.jpg" /></a>    </p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 31 Oct 2015 02:11:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-10-31:/2015/10/31/list-server-favorites-in-os-x-1011-el-capitan</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>efficiency</category>
      <category>javascript</category>
      <category>objectivec</category>
    </item>
    <item>
      <title>Using Contacts.app with TextExpander v2: Objective-C and JavaScript</title>
      <link>https://ryanmo.co/2015/10/24/using-contactsapp-with-textexpander-v2-objective-c-and-javascript</link>
      <description><![CDATA[
<p>I was generally happy with how I was using <a href="https://ryanmo.co{static}../2015-08-23/2015-08-23_Using-Contacts.app-with-TextExpander.md">Contacts.app with TextExpander</a> to create snippets for my emails, phone numbers and addresses. However, as I eventually realized, I have to have Contacts.app running for it to work. When AppleScript and JavaScript talk to applications in OS X, they have to be running. That isn't the case for C and Objective-C libraries, so I decided to see how hard it was to use the Objective-C bindings for Javascript.</p>


<p>The documentation is just as sparse in <a href="https://developer.apple.com/library/mac/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/OSX10-10.html#//apple_ref/doc/uid/TP40014508-CH109-SW1">the developer documentation</a>, however <a href="http://tylergaw.com/articles/building-osx-apps-with-js">this article</a> by Tyler Gaw helped get me started in understanding how to represent Objective-C methods in Javascript. It's probably easiest to just show the script and explain what's going on.</p>
<div class="codehilite"><pre><span></span><code><span class="nx">ObjC</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="s2">&quot;AddressBook&quot;</span><span class="p">);</span>
<span class="nx">sAB</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">ABAddressBook</span><span class="p">.</span><span class="nx">sharedAddressBook</span>
<span class="nx">meRecord</span> <span class="o">=</span> <span class="nx">sAB</span><span class="p">.</span><span class="nx">me</span>

<span class="kd">var</span> <span class="nx">propertyToObjCType</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">&#39;email&#39;</span><span class="o">:</span> <span class="nx">$</span><span class="p">.</span><span class="nx">kABEmailProperty</span><span class="p">,</span>
    <span class="s1">&#39;address&#39;</span><span class="o">:</span> <span class="nx">$</span><span class="p">.</span><span class="nx">kABAddressProperty</span><span class="p">,</span>
    <span class="s1">&#39;phone&#39;</span><span class="o">:</span> <span class="nx">$</span><span class="p">.</span><span class="nx">kABPhoneProperty</span>
<span class="p">}</span>

<span class="kd">var</span> <span class="nx">labelToObjCType</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">&#39;work&#39;</span><span class="o">:</span> <span class="nx">$</span><span class="p">.</span><span class="nx">kABWorkLabel</span><span class="p">,</span>
    <span class="s1">&#39;home&#39;</span><span class="o">:</span> <span class="nx">$</span><span class="p">.</span><span class="nx">kABHomeLabel</span><span class="p">,</span>
    <span class="s1">&#39;iPhone&#39;</span><span class="o">:</span> <span class="nx">$</span><span class="p">.</span><span class="nx">kABPhoneiPhoneLabel</span><span class="p">,</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">valueForProperty</span><span class="p">(</span><span class="nx">property</span><span class="p">){</span>
    <span class="k">return</span> <span class="nx">meRecord</span><span class="p">.</span><span class="nx">valueForProperty</span><span class="p">(</span><span class="nx">propertyToObjCType</span><span class="p">[</span><span class="nx">property</span><span class="p">])</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">getEmailByLabel</span><span class="p">(</span><span class="nx">inputLabel</span><span class="p">){</span>
    <span class="nx">emails</span> <span class="o">=</span> <span class="nx">valueForProperty</span><span class="p">(</span><span class="s1">&#39;email&#39;</span><span class="p">)</span>
    <span class="nx">label</span> <span class="o">=</span> <span class="nx">labelToObjCType</span><span class="p">[</span><span class="nx">inputLabel</span><span class="p">]</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mf">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">emails</span><span class="p">.</span><span class="nx">count</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">){</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">$</span><span class="p">.</span><span class="nx">CFEqual</span><span class="p">(</span><span class="nx">emails</span><span class="p">.</span><span class="nx">labelAtIndex</span><span class="p">(</span><span class="nx">i</span><span class="p">),</span> <span class="nx">label</span><span class="p">)){</span>
            <span class="k">return</span> <span class="nx">emails</span><span class="p">.</span><span class="nx">valueAtIndex</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

<span class="p">}</span>

<span class="kd">function</span> <span class="nx">getAddressByLabel</span><span class="p">(</span><span class="nx">inputLabel</span><span class="p">){</span>
    <span class="nx">addresses</span> <span class="o">=</span> <span class="nx">valueForProperty</span><span class="p">(</span><span class="s1">&#39;address&#39;</span><span class="p">)</span>
    <span class="nx">label</span> <span class="o">=</span> <span class="nx">labelToObjCType</span><span class="p">[</span><span class="nx">inputLabel</span><span class="p">]</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mf">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">addresses</span><span class="p">.</span><span class="nx">count</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">){</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">$</span><span class="p">.</span><span class="nx">CFEqual</span><span class="p">(</span><span class="nx">addresses</span><span class="p">.</span><span class="nx">labelAtIndex</span><span class="p">(</span><span class="nx">i</span><span class="p">),</span> <span class="nx">label</span><span class="p">)){</span>
            <span class="k">return</span> <span class="nx">sAB</span><span class="p">.</span><span class="nx">formattedAddressFromDictionary</span><span class="p">(</span><span class="nx">addresses</span><span class="p">.</span><span class="nx">valueAtIndex</span><span class="p">(</span><span class="nx">i</span><span class="p">)).</span><span class="nx">string</span>
        <span class="p">}</span>
    <span class="p">}</span>

<span class="p">}</span>

<span class="kd">function</span> <span class="nx">getPhoneByLabel</span><span class="p">(</span><span class="nx">inputLabel</span><span class="p">){</span>
    <span class="nx">phone</span> <span class="o">=</span> <span class="nx">valueForProperty</span><span class="p">(</span><span class="s1">&#39;phone&#39;</span><span class="p">)</span>
    <span class="nx">label</span> <span class="o">=</span> <span class="nx">labelToObjCType</span><span class="p">[</span><span class="nx">inputLabel</span><span class="p">]</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mf">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">phone</span><span class="p">.</span><span class="nx">count</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">){</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">$</span><span class="p">.</span><span class="nx">CFEqual</span><span class="p">(</span><span class="nx">phone</span><span class="p">.</span><span class="nx">labelAtIndex</span><span class="p">(</span><span class="nx">i</span><span class="p">),</span> <span class="nx">label</span><span class="p">)){</span>
            <span class="k">return</span> <span class="nx">phone</span><span class="p">.</span><span class="nx">valueAtIndex</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

<span class="p">}</span>
</code></pre></div>

<p>The biggest thing to point out is that if you have a method called in Objective-C like <code>[ABAddressBook sharedAddressBook];</code>, this gets converted to dot notation <code>$.ABAddressBook.sharedAddressBook</code>. The Obj-C bridge is always called with either <code>ObjC.</code> or <code>$.</code> followed by the method.</p>
<p>You can find a nice list of different properties and values for the address book <a href="http://www.macdevcenter.com/pub/a/mac/2002/08/27/cocoa.html?page=2">here</a>. For labels, the most common will be <code>$.kABHomeLabel</code> and <code>$.kABWorkLabel</code> for home and work respectively. If you've created a custom label (let's call it XXX), you can reference it by calling <code>$.kABXXXLabel</code>.</p>
<p>As with any other JavaScript snippet in TextExpander, you can call any of these functions to expand the contact information that you'd like.</p>
<div class="codehilite"><pre><span></span><code><span class="nx">getEmailByLabel</span><span class="p">(</span><span class="s1">&#39;home&#39;</span><span class="p">)</span>  <span class="c1">// returns your home phone number</span>

<span class="nx">getAddressByLabel</span><span class="p">(</span><span class="s1">&#39;work&#39;</span><span class="p">)</span>  <span class="c1">// returns your work address</span>

<span class="nx">getAddressByLabel</span><span class="p">(</span><span class="s1">&#39;iPhone&#39;</span><span class="p">)</span>  <span class="c1">// returns your phone number labeled iPhone</span>
</code></pre></div>

<p>For those of you who don't like copy/pasting the same code over and over, there's a nice little hack that you can do in TextExpander.</p>
<p>First, create a new Plain Text snippet with the code from the top of the post. I called the snippet "getInfoFromContacts". Once that's done, you can create new snippets that take advantage of this code by creating new JavaScript snippets with the following:</p>
<div class="codehilite"><pre><span></span><code><span class="o">%</span><span class="nx">snippet</span><span class="o">:</span><span class="nx">getInfoFromContacts</span><span class="o">%</span>
<span class="nx">getEmailByLabel</span><span class="p">(</span><span class="s1">&#39;home&#39;</span><span class="p">)</span>
</code></pre></div>

<p>This way, if you update something from the main part of the code, you don't have to update all of your TextExpanders.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 24 Oct 2015 02:12:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-10-24:/2015/10/24/using-contactsapp-with-textexpander-v2-objective-c-and-javascript</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>textexpander</category>
      <category>efficiency</category>
      <category>javascript</category>
      <category>objectivec</category>
    </item>
    <item>
      <title>Adding Critical CSS in Pelican</title>
      <link>https://ryanmo.co/2015/09/14/adding-critical-css-in-pelican</link>
      <description><![CDATA[<p>As it turns out, adding <a href="http://www.smashingmagazine.com/2015/08/understanding-critical-css/">critical css</a> wasn't trivial, but didn't take as much effort as I had originally thought. My site's layout doesn't contain <em>that</em> much styling, and so I simply added all of my CSS as an inline <code>style</code> tag. The tricky part, was getting Jinja to play nicely.</p>
<p>The first step was to generate a separate css file that only contained what was needed when you first load and see the page. I use Less as my pre-processor, and created a very small Less file that looked like this:</p>
<div class="codehilite"><pre><span></span><code><span class="p">@</span><span class="k">import</span> <span class="o">(</span><span class="nt">inline</span><span class="o">)</span> <span class="s1">&#39;../tipuesearch/tipuesearch.css&#39;</span><span class="p">;</span>

<span class="p">@</span><span class="k">import</span> <span class="s1">&#39;default_mobile.less&#39;</span><span class="p">;</span>
<span class="p">@</span><span class="k">import</span> <span class="s1">&#39;largescreens.less&#39;</span><span class="p">;</span>
</code></pre></div>

<p>Once compiled and minimized<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>, I needed to add it to my <code>base.html</code> template.</p>
<div class="codehilite"><pre><span></span><code><span class="x">&lt;style type=&quot;text/css&quot;&gt;</span>
<span class="cp">{%</span> <span class="k">include</span> <span class="s1">&#39;critical.css&#39;</span> <span class="cp">%}</span><span class="x"></span>
<span class="x">&lt;/style&gt;</span>
</code></pre></div>

<p>Here is where the problem when generating my site.</p>
<div class="codehilite"><pre><span></span><code>WARNING: Caught exception <span class="s2">&quot;TemplateSyntaxError: Missing end of comment tag&quot;</span>. Reloading.
</code></pre></div>

<p>Since my minimized CSS contained <code>'{#'</code>, Jinja was interpreting this as a comment and raised an exception. While this is an easy fix by changing the Jinja environment variables within Pelican's generators.py, I didn't want to go this route since I would need to update this<sup id="fnref:2"><a class="footnote-ref" href="https://ryanmo.co#fn:2">2</a></sup> every time there was an update to Pelican. Instead, I wrote a Jinja extension which Pelican supports natively. </p>
<div class="codehilite"><pre><span></span><code><span class="c1"># in pelicanconf.py</span>
<span class="kn">from</span> <span class="nn">jinja2.ext</span> <span class="kn">import</span> <span class="n">Extension</span>

<span class="k">class</span> <span class="nc">CustomCommentStrings</span><span class="p">(</span><span class="n">Extension</span><span class="p">):</span>
    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">environment</span><span class="p">):</span>
        <span class="nb">super</span><span class="p">(</span><span class="n">CustomCommentStrings</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">environment</span><span class="p">)</span>

        <span class="n">environment</span><span class="o">.</span><span class="n">comment_start_string</span> <span class="o">=</span> <span class="s1">&#39;###&#39;</span>
        <span class="n">environment</span><span class="o">.</span><span class="n">comment_end_string</span> <span class="o">=</span> <span class="s1">&#39;/###&#39;</span>

<span class="n">JINJA_EXTENSIONS</span> <span class="o">=</span> <span class="p">[</span><span class="n">CustomCommentStrings</span><span class="p">]</span>
</code></pre></div>

<hr />
<p><em>Update 2017-01-05</em></p>
<p>If you're using Pelican version 3.7+, you don't have to write the custom extension shown above, you can simply update the <code>JINJA_ENVIRONMENT</code> settings variable:</p>
<div class="codehilite"><pre><span></span><code><span class="n">JINJA_ENVIRONMENT</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">&#39;comment_start_string&#39;</span><span class="p">:</span> <span class="s1">&#39;###&#39;</span><span class="p">,</span> 
    <span class="s1">&#39;comment_end_string&#39;</span><span class="p">:</span> <span class="s1">&#39;/###&#39;</span>
<span class="p">}</span>
</code></pre></div>

<hr />
<p>One thing to note here is that if you are using <code>{# ... #}</code> as comment strings in Jinja, you'll need to update them to whatever new start and end strings you define.</p>
<p>And success! The <code>critical.css</code> file was successfully imported and I now my critical CSS is included on page load. With this, Google now gives me a 100/100 speed score for mobile and 98/100 on desktop.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>Google suggests that you minimize critical css to reduce your file size.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
<li id="fn:2">
<p>I plan on submitting a pull-request to allow manually setting Jinja environment variables.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:2" title="Jump back to footnote 2 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Mon, 14 Sep 2015 07:41:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-09-14:/2015/09/14/adding-critical-css-in-pelican</guid>
      <category>Tech</category>
      <category>pelican</category>
      <category>scripting</category>
      <category>python</category>
    </item>
    <item>
      <title>Improving Your Site's Load Times</title>
      <link>https://ryanmo.co/2015/09/12/improving-your-sites-load-times</link>
      <description><![CDATA[<p>While reading through my RSS feeds the other night, I came across <a href="https://onetapless.com/whats-new-one-tap-less">this</a> article from One Tap Less about what he did to improve load times on his site. My first thought was,  "I use a static site, I don't need to worry about this" and dismissed it. Then I figured, why not just try out my site on Google's <a href="https://developers.google.com/speed/pagespeed/insights/">PageSpeed Insights</a>. Turns out, I had some work to do.</p>


<p>When I initially ran the test, this site came back with a score of around 41/100 for both desktop and mobile. I would have been fine leaving it, but that was pretty bad. Google does a great job telling you what things need to be improved, even down to the specific files causing problems.</p>
<p><img alt="file_specific" src="https://ryanmo.co/posts/Tech/2015-09-12/file_specific.png" /></p>
<p>My first task was "eliminating render-blocking JavaScript and CSS." I was lazily loading all of my JavaScript in the <code>&lt;head&gt;</code> tag, so this was as simple as moving that to the bottom of the page. Google also suggested using the <code>async</code> attribute.</p>
<div class="codehilite"><pre><span></span><code><span class="c">&lt;!-- Initial setup --&gt;</span>
<span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">&quot;{{ DEFAULT_LANG }}&quot;</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">async</span> <span class="na">src</span><span class="o">=</span><span class="s">&quot;{{ SITEURL }}/theme/js/main.js&quot;</span> <span class="na">type</span><span class="o">=</span><span class="s">&quot;text/javascript&quot;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
  ...
<span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>

<span class="c">&lt;!-- New setup --&gt;</span>
...
    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">async</span> <span class="na">src</span><span class="o">=</span><span class="s">&quot;{{ SITEURL }}/theme/js/main.js&quot;</span> <span class="na">type</span><span class="o">=</span><span class="s">&quot;text/javascript&quot;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div>

<p>Eliminating render-blocking CSS was a little more tricky. Google suggests inlining <a href="http://www.smashingmagazine.com/2015/08/understanding-critical-css/">critical CSS</a>. I haven't taken the time to figure out which CSS that would require and how this would change my workflow. For now, I've taken their suggestion and load my CSS at the bottom of the page using a <code>&lt;script&gt;</code> tag.</p>
<p><em>[Update 2015-09-14]</em>: I figured it out. You can read how I added the critical css <a href="https://ryanmo.co{static}../2015-09-14/2015-09-14-critical_css.md">here</a></p>
<div class="codehilite"><pre><span></span><code><span class="o">&lt;</span><span class="nx">script</span><span class="o">&gt;</span>
  <span class="kd">var</span> <span class="nx">cb</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">l</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;link&#39;</span><span class="p">);</span> <span class="nx">l</span><span class="p">.</span><span class="nx">rel</span> <span class="o">=</span> <span class="s1">&#39;stylesheet&#39;</span><span class="p">;</span>
    <span class="nx">l</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="s2">&quot;{{ SITEURL }}/theme/css/style.css&quot;</span><span class="p">;</span>
    <span class="kd">var</span> <span class="nx">h</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">&#39;head&#39;</span><span class="p">)[</span><span class="mf">0</span><span class="p">];</span> <span class="nx">h</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">insertBefore</span><span class="p">(</span><span class="nx">l</span><span class="p">,</span> <span class="nx">h</span><span class="p">);</span>
  <span class="p">};</span>
  <span class="kd">var</span> <span class="nx">raf</span> <span class="o">=</span> <span class="nx">requestAnimationFrame</span> <span class="o">||</span> <span class="nx">mozRequestAnimationFrame</span> <span class="o">||</span>
      <span class="nx">webkitRequestAnimationFrame</span> <span class="o">||</span> <span class="nx">msRequestAnimationFrame</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">raf</span><span class="p">)</span> <span class="nx">raf</span><span class="p">(</span><span class="nx">cb</span><span class="p">);</span>
  <span class="k">else</span> <span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;load&#39;</span><span class="p">,</span> <span class="nx">cb</span><span class="p">);</span>
<span class="o">&lt;</span><span class="err">/script&gt;</span>
</code></pre></div>

<p>Ok. Easy stuff done. This put my site up into the 50's range. Good, but still not great. Let's tackle the one that's lowering my score the most: gzip compression and caching.</p>
<p>I host my blog at the wonderful <a href="http://macminicolo.net">macminicolo.net</a>, which means I control the server and have to do my own optimizations. Turns out, this really wasn't that hard to do. Here's how to easily enable compression in Apache.</p>
<div class="codehilite"><pre><span></span><code><span class="err">#</span><span class="w"> </span><span class="n">Always</span><span class="w"> </span><span class="n">back</span><span class="w"> </span><span class="n">up</span><span class="w"> </span><span class="n">your</span><span class="w"> </span><span class="n">config</span><span class="w"> </span><span class="k">file</span><span class="w"> </span><span class="k">before</span><span class="w"> </span><span class="n">changing</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">bunch</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nf">stuff</span><span class="w"></span>
<span class="n">sudo</span><span class="w"> </span><span class="n">emacs</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">apache2</span><span class="o">/</span><span class="n">httpd</span><span class="p">.</span><span class="n">conf</span><span class="w"></span>

<span class="err">#</span><span class="w"> </span><span class="n">Make</span><span class="w"> </span><span class="n">sure</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">enabled</span><span class="w"></span>
<span class="n">LoadModule</span><span class="w"> </span><span class="n">deflate_module</span><span class="w"> </span><span class="n">libexec</span><span class="o">/</span><span class="n">apache2</span><span class="o">/</span><span class="n">mod_deflate</span><span class="p">.</span><span class="n">so</span><span class="w"></span>

<span class="o">&lt;</span><span class="n">IfModule</span><span class="w"> </span><span class="n">mod_deflate</span><span class="p">.</span><span class="n">c</span><span class="o">&gt;</span><span class="w"></span>

<span class="w">    </span><span class="err">#</span><span class="w"> </span><span class="k">Restrict</span><span class="w"> </span><span class="n">compression</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">these</span><span class="w"> </span><span class="n">MIME</span><span class="w"> </span><span class="n">types</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="nc">text</span><span class="o">/</span><span class="n">plain</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="nc">text</span><span class="o">/</span><span class="n">html</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="n">xhtml</span><span class="o">+</span><span class="nc">xml</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="nc">text</span><span class="o">/</span><span class="nc">xml</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="nc">xml</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="n">x</span><span class="o">-</span><span class="n">javascript</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="nc">text</span><span class="o">/</span><span class="n">javascript</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="n">javascript</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="n">json</span><span class="w"></span>
<span class="w">    </span><span class="n">AddOutputFilterByType</span><span class="w"> </span><span class="n">DEFLATE</span><span class="w"> </span><span class="nc">text</span><span class="o">/</span><span class="n">css</span><span class="w"></span>

<span class="w">    </span><span class="err">#</span><span class="w"> </span><span class="k">Level</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">compression</span><span class="w"> </span><span class="p">(</span><span class="n">Highest</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">Lowest</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="n">DeflateCompressionLevel</span><span class="w"> </span><span class="mi">9</span><span class="w"></span>

<span class="w">    </span><span class="err">#</span><span class="w"> </span><span class="n">Netscape</span><span class="w"> </span><span class="mf">4.</span><span class="n">x</span><span class="w"> </span><span class="n">has</span><span class="w"> </span><span class="ow">some</span><span class="w"> </span><span class="n">problems</span><span class="p">.</span><span class="w"></span>
<span class="w">    </span><span class="n">BrowserMatch</span><span class="w"> </span><span class="o">^</span><span class="n">Mozilla</span><span class="o">/</span><span class="mi">4</span><span class="w"> </span><span class="n">gzip</span><span class="o">-</span><span class="k">only</span><span class="o">-</span><span class="nc">text</span><span class="o">/</span><span class="n">html</span><span class="w"></span>

<span class="w">    </span><span class="err">#</span><span class="w"> </span><span class="n">Netscape</span><span class="w"> </span><span class="mf">4.06</span><span class="o">-</span><span class="mf">4.08</span><span class="w"> </span><span class="n">have</span><span class="w"> </span><span class="ow">some</span><span class="w"> </span><span class="n">more</span><span class="w"> </span><span class="n">problems</span><span class="w"></span>
<span class="w">    </span><span class="n">BrowserMatch</span><span class="w"> </span><span class="o">^</span><span class="n">Mozilla</span><span class="o">/</span><span class="mi">4</span><span class="err">\</span><span class="mf">.0</span><span class="o">[</span><span class="n">678</span><span class="o">]</span><span class="w"> </span><span class="k">no</span><span class="o">-</span><span class="n">gzip</span><span class="w"></span>

<span class="w">    </span><span class="err">#</span><span class="w"> </span><span class="n">MSIE</span><span class="w"> </span><span class="n">masquerades</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">Netscape</span><span class="p">,</span><span class="w"> </span><span class="n">but</span><span class="w"> </span><span class="n">it</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">fine</span><span class="w"></span>
<span class="w">    </span><span class="n">BrowserMatch</span><span class="w"> </span><span class="err">\</span><span class="n">bMSI</span><span class="o">[</span><span class="n">E</span><span class="o">]</span><span class="w"> </span><span class="err">!</span><span class="k">no</span><span class="o">-</span><span class="n">gzip</span><span class="w"> </span><span class="err">!</span><span class="n">gzip</span><span class="o">-</span><span class="k">only</span><span class="o">-</span><span class="nc">text</span><span class="o">/</span><span class="n">html</span><span class="w"></span>

<span class="w">    </span><span class="o">&lt;</span><span class="n">IfModule</span><span class="w"> </span><span class="n">mod_headers</span><span class="p">.</span><span class="n">c</span><span class="o">&gt;</span><span class="w"></span>
<span class="w">        </span><span class="err">#</span><span class="w"> </span><span class="n">Make</span><span class="w"> </span><span class="n">sure</span><span class="w"> </span><span class="n">proxies</span><span class="w"> </span><span class="n">don</span><span class="err">&#39;</span><span class="n">t</span><span class="w"> </span><span class="n">deliver</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">wrong</span><span class="w"> </span><span class="n">content</span><span class="w"></span>
<span class="w">        </span><span class="n">Header</span><span class="w"> </span><span class="n">append</span><span class="w"> </span><span class="n">Vary</span><span class="w"> </span><span class="k">User</span><span class="o">-</span><span class="n">Agent</span><span class="w"> </span><span class="n">env</span><span class="o">=</span><span class="err">!</span><span class="n">dont</span><span class="o">-</span><span class="n">vary</span><span class="w"></span>
<span class="w">    </span><span class="o">&lt;/</span><span class="n">IfModule</span><span class="o">&gt;</span><span class="w"></span>
<span class="o">&lt;/</span><span class="n">IfModule</span><span class="o">&gt;</span><span class="w"></span>
</code></pre></div>

<p>Save and restart Apache</p>
<div class="codehilite"><pre><span></span><code><span class="err">sudo /usr/sbin/apachectl restart</span>
</code></pre></div>

<p>You can test to be sure it's working by using <code>curl</code> on a file that matches any of the above content types. You should see <code>Content-Encoding: gzip</code> in the response headers.</p>
<div class="codehilite"><pre><span></span><code>curl -I -H <span class="s1">&#39;Accept-Encoding: gzip,deflate&#39;</span> https://ryanmo.co/theme/js/main.js


HTTP/1.1 <span class="m">200</span> OK
Date: Sat, <span class="m">12</span> Sep <span class="m">2015</span> <span class="m">20</span>:38:24 GMT
Server: Apache/2.4.10 <span class="o">(</span>Unix<span class="o">)</span> PHP/5.5.20 OpenSSL/0.9.8zd
Last-Modified: Sat, <span class="m">12</span> Sep <span class="m">2015</span> <span class="m">02</span>:47:24 GMT
ETag: <span class="s2">&quot;38169-51f83da1c0700-gzip&quot;</span>
Accept-Ranges: bytes
Vary: Accept-Encoding,User-Agent
Content-Encoding: gzip
Cache-Control: max-age<span class="o">=</span><span class="m">2592000</span>
Expires: Mon, <span class="m">12</span> Oct <span class="m">2015</span> <span class="m">20</span>:38:24 GMT
Content-Type: application/javascript
</code></pre></div>

<p>Next is content caching. This is also something to edit in the httpd.conf file for Apache. Google suggests at <em>least</em> 7 days for default caching, and up to a year for content that doesn't change often.</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># Make sure this is uncommented</span>
LoadModule expires_module libexec/apache2/mod_expires.so

<span class="c1">## EXPIRES CACHING ##</span>
&lt;IfModule mod_expires.c&gt;
    ExpiresActive On
    ExpiresByType image/jpg <span class="s2">&quot;access plus 1 year&quot;</span>
    ExpiresByType image/jpeg <span class="s2">&quot;access plus 1 year&quot;</span>
    ExpiresByType image/gif <span class="s2">&quot;access plus 1 year&quot;</span>
    ExpiresByType image/png <span class="s2">&quot;access plus 1 year&quot;</span>
    ExpiresByType application/x-font-ttf <span class="s2">&quot;access plus 1 year&quot;</span>
    ExpiresByType text/css <span class="s2">&quot;access plus 1 month&quot;</span>
    ExpiresByType application/javascript <span class="s2">&quot;access plus 1 month&quot;</span>
    ExpiresByType image/x-icon <span class="s2">&quot;access plus 1 year&quot;</span>
    ExpiresDefault <span class="s2">&quot;access plus 7 days&quot;</span>
&lt;/IfModule&gt;
<span class="c1">## EXPIRES CACHING ##</span>
</code></pre></div>

<p>At this point, I was getting into the mid to high 80s for my score. Awesome. At this point, I could probably stop and be satisfied with the results. The remaining suggestions were easy, so I kept going. The first was to minimize all of my CSS and JavaScript. Easy. I just hit that checkbox in CodeKit and moved along. I did take one additional step here and took advantage of CodeKit's ability to combine JavaScript files into a single file by adding a header to my main JavaScript file.</p>
<div class="codehilite"><pre><span></span><code><span class="err"># @codekit-prepend &quot;../js/jquery-1.10.1.min.js&quot;, &quot;../js/bigfoot.min.js&quot;;</span>
</code></pre></div>

<p>Lastly, Google suggested that I compress my images. They were even nice enough to provide a zipped file of all your CSS, JavaScript and images compressed for you. If I don't have to do the work, then I won't. I downloaded the file and replaced all my images with the ones they gave me. In the future, I'll be using <a href="https://imageoptim.com">ImageOptim</a> to optimize the images on my site.</p>
<p>My final score check: 91/100 on mobile and 97/100 on desktop! I think I can call that a success. Honestly, when trying to load my site on different devices, I didn't notice a significant increase. That being said, at least I'll be in Google's good graces for being a good web citizen, and I'll avoid any risk of them down-ranking my site for doing things incorrectly. I still want to take advantage of the critical CSS at some point, but I can leave that for another day.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 12 Sep 2015 01:15:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-09-12:/2015/09/12/improving-your-sites-load-times</guid>
      <category>Tech</category>
      <category>pelican</category>
      <category>javascript</category>
      <category>scripting</category>
    </item>
    <item>
      <title>Backup Your Email with Getmail</title>
      <link>https://ryanmo.co/2015/09/06/backup-your-email-with-getmail</link>
      <description><![CDATA[<p>It's always a good idea to keep backups of data you can't replace, including email. For the last few years, I've had a script that automatically backs up my Gmail account. Since switching to Fastmail, I figured I should continue doing the same thing. It turned out, it was pretty easy to set up another account.</p>


<p>I've been meaning to write about backing up email for a few months now. With Dr. Drang's <a href="http://leancrew.com/all-this/2015/09/archiving-old-mail-with-formail/">recent post</a> about archiving email using <code>formail</code>, I figured this a good enough time as ever to post my solution.</p>
<p>To install <code>getmail</code>, you can use Homebrew, or <a href="http://pyropus.ca/software/getmail/documentation.html#installing">manually</a> if you're in to that sort of thing.</p>
<div class="codehilite"><pre><span></span><code><span class="err">brew install getmail</span>
</code></pre></div>

<p>For the configuration files, I first created a directory in my Home folder</p>
<div class="codehilite"><pre><span></span><code><span class="err">mkdir .getmail</span>
</code></pre></div>

<p>The config files for both Fastmail and Gmail are similar, but I'll include both for completeness</p>
<h4 id="gmail">Gmail</h4>
<div class="codehilite"><pre><span></span><code><span class="k">[retriever]</span>
<span class="na">type</span> <span class="o">=</span> <span class="s">SimpleIMAPSSLRetriever</span>
<span class="na">server</span> <span class="o">=</span> <span class="s">imap.gmail.com</span>
<span class="na">username</span> <span class="o">=</span> <span class="s">&lt;username&gt;@gmail.com</span>
<span class="na">mailboxes</span> <span class="o">=</span> <span class="s">(&quot;[Gmail]/All Mail&quot;,) # To pull all emails</span>
<span class="na">port</span> <span class="o">=</span> <span class="s">993</span>

<span class="k">[destination]</span>
<span class="na">type</span> <span class="o">=</span> <span class="s">Maildir</span>
<span class="na">path</span> <span class="o">=</span> <span class="s">~/gmail-archive/</span>

<span class="k">[options]</span>
<span class="c1"># print messages about each action (verbose = 2)</span>
<span class="c1"># Other options:</span>
<span class="c1"># 0 prints only warnings and errors</span>
<span class="c1"># 1 prints messages about retrieving and deleting messages only</span>
<span class="na">verbose</span> <span class="o">=</span> <span class="s">2</span>
<span class="na">message_log</span> <span class="o">=</span> <span class="s">~/Dropbox/Scripts/logs/gmail.log</span>
<span class="na">receieved</span> <span class="o">=</span> <span class="s">false</span>
<span class="na">delivered_to</span> <span class="o">=</span> <span class="s">false</span>
<span class="c1"># dont re-read messages its already pulled down</span>
<span class="na">read_all</span> <span class="o">=</span> <span class="s">false</span>
</code></pre></div>

<h4 id="fastmail">Fastmail</h4>
<div class="codehilite"><pre><span></span><code><span class="k">[retriever]</span>
<span class="na">type</span> <span class="o">=</span> <span class="s">SimpleIMAPSSLRetriever</span>
<span class="na">server</span> <span class="o">=</span> <span class="s">mail.messagingengine.com</span>
<span class="na">username</span> <span class="o">=</span> <span class="s">&lt;username&gt;@fastmail.com</span>
<span class="na">mailboxes</span> <span class="o">=</span> <span class="s">(&quot;INBOX.Archive&quot;,)</span>

<span class="k">[destination]</span>
<span class="na">type</span> <span class="o">=</span> <span class="s">Maildir</span>
<span class="na">path</span> <span class="o">=</span> <span class="s">~/fastmail-archive/</span>

<span class="k">[options]</span>
<span class="na">verbose</span> <span class="o">=</span> <span class="s">2</span>
<span class="na">read_all</span> <span class="o">=</span> <span class="s">false</span>
<span class="na">message_log</span> <span class="o">=</span> <span class="s">~/Dropbox/Scripts/logs/fastmail.log</span>
<span class="na">delivered_to</span> <span class="o">=</span> <span class="s">false</span>
<span class="na">received</span> <span class="o">=</span> <span class="s">false</span>
</code></pre></div>

<p>You'll notice that I don't include the password in either config. If password is omitted from a config on Mac OS, the first time you run <code>getmail</code>, you'll be prompted to enter your password and it will then be stored securely in KeyChain. Since I'm using Maildir as my type, you'll need to create special folders, with 3 specific subfolders</p>
<div class="codehilite"><pre><span></span><code>mkdir ~/fastmail-archive
mkdir !$/<span class="o">{</span>cur,tmp,new<span class="o">}</span> <span class="c1"># creates 3 new folders in ~/fastmail-archive</span>
</code></pre></div>

<p>The same will need to be done for ~/gmail-archive.</p>
<p>Once this is set up, you'll need to run the script once to enter in your password</p>
<div class="codehilite"><pre><span></span><code><span class="err">/usr/local/bin/getmail -q -r ~/.getmail/getmail.gmail</span>
</code></pre></div>

<p>If I ever need to view the emails, I do the same as Dr. Drang and use <code>mutt</code>, which can also be installed with Homebrew.</p>
<div class="codehilite"><pre><span></span><code><span class="err">mutt -R -f ~/gmail-archive</span>
</code></pre></div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 06 Sep 2015 06:45:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-09-06:/2015/09/06/backup-your-email-with-getmail</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>email</category>
      <category>backup</category>
    </item>
    <item>
      <title>Using Contacts.app with TextExpander</title>
      <link>https://ryanmo.co/2015/08/23/using-contactsapp-with-textexpander</link>
      <description><![CDATA[<p>Changing your email or phone number isn't fun. You have to tell everyone, update all of your online accounts, and make sure your TextExpanders are up-to-date with the right information. One of the places I will always update is my contact information in Contacts.app, so why not just use that as the source of truth for TextExpander snippets?</p>


<p>In TextExpander 5, you can now use Javascript in snippets. Since I had <a href="https://ryanmo.co{static}../2014-12-14/2014-12-14_backup-your-contacts-v2.md">already created some scripts</a> using Contacts, I re-used some of the bits to create TextExpander snippets to expand information directly from Contacts.app.</p>
<div class="codehilite"><pre><span></span><code><span class="nx">Contacts</span> <span class="o">=</span> <span class="nx">Application</span><span class="p">(</span><span class="s1">&#39;/Applications/Contacts.app&#39;</span><span class="p">)</span>

<span class="c1">// ::attr is the attribute you want pull (emails, phones, etc.)</span>
<span class="c1">// ::accountType is what you see to the left of the info in Contacts.app</span>
<span class="kd">function</span> <span class="nx">getMyInfo</span><span class="p">(</span><span class="nx">attr</span><span class="p">,</span> <span class="nx">accountType</span><span class="p">){</span>
    <span class="nx">method</span> <span class="o">=</span> <span class="nx">attr</span> <span class="o">==</span> <span class="s1">&#39;addresses&#39;</span> <span class="o">?</span> <span class="s1">&#39;formattedAddress&#39;</span> <span class="o">:</span> <span class="s1">&#39;value&#39;</span>
    <span class="nx">search</span> <span class="o">=</span> <span class="nx">Contacts</span><span class="p">.</span><span class="nx">myCard</span><span class="p">()[</span><span class="nx">attr</span><span class="p">].</span><span class="nx">where</span><span class="p">({</span><span class="nx">label</span><span class="o">:</span> <span class="nx">accountType</span><span class="p">})</span>
    <span class="nx">results</span> <span class="o">=</span> <span class="nx">search</span><span class="p">.</span><span class="nx">length</span> <span class="o">?</span> <span class="nx">search</span><span class="p">().</span><span class="nx">map</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">a</span><span class="p">){</span><span class="k">return</span> <span class="nx">a</span><span class="p">[</span><span class="nx">method</span><span class="p">]()})</span> <span class="o">:</span> <span class="p">[]</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span> <span class="o">==</span> <span class="mf">1</span><span class="p">){</span>
        <span class="k">return</span> <span class="nx">results</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mf">1</span><span class="p">){</span>
        <span class="k">return</span> <span class="nx">results</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s2">&quot;, &quot;</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">return</span> <span class="s2">&quot;&quot;</span>

<span class="p">}</span>

<span class="c1">// Grab my &#39;blog&#39; email address </span>
<span class="nx">getMyInfo</span><span class="p">(</span><span class="s1">&#39;emails&#39;</span><span class="p">,</span> <span class="s1">&#39;blog&#39;</span><span class="p">)</span> <span class="c1">// returns email address</span>
</code></pre></div>

<p>The <code>getMyInfo</code> function is fairly generalized to allow you to get any basic information like emails or phone numbers from your contacts. The first argument is the type<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>, and the second is the label you want, such as Home, Work, etc. To get a phone number, just change the function called to <code>getMyInfo('phones', 'iPhone)</code>. To get an address, change to <code>getMyInfo('addresses', 'home')</code>, which will return a formatted address.</p>
<p><em>Update 2015-08-23</em>: Updated the <code>getMyInfo</code> function to support addresses.  </p>
<p><em>Update 2015-09-20</em>: For a while, I couldn't figure out why occasionally expanding things from Contacts would take a little while. Then I realized, for AppleScript to work, the application you're calling needs to be open. If you do want to use this as a way to expand your information from Contacts, just know that you'll need to be running the application for the TextExpander snippets to work.</p>
<p><em>Update 2015-09-20</em>: I've figured out a way to do this without Contacts.app running! Check out my post <a href="https://ryanmo.co{static}../2015-10-24/2015-10-24-Using_Contacts_TextExpander_v2.md">here</a> for more info.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>You can find all the attributes in the Contacts.app scripts library in Address Book Script Suite → Application → myCard&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 23 Aug 2015 11:41:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-08-23:/2015/08/23/using-contactsapp-with-textexpander</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>textexpander</category>
      <category>efficiency</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Filling Forms with PDFPen and Javascript</title>
      <link>https://ryanmo.co/2015/06/06/filling-forms-with-pdfpen-and-javascript</link>
      <description><![CDATA[<p>My adventure with Mac Javascript Automation continues. Things still aren't easy and the documentation is poor, but I'm finding that it's still easier to write automation scripts in Javascript than it ever was with Applescript.</p>

<p>Every month I need to fill out four receipts in a PDF form that I made with PDFPenPro. I never liked doing it and I felt like there had to be a way to do this better. I did some searching, and of course came across an <a href="http://leancrew.com/all-this/2012/02/automatic-w-9s-with-pdfpen">old post</a> by Dr. Drang where he was adding rich text to PDFs. This was close to what I wanted, but since I had taken the time to create the form fields myself, I figured I should take advantage of them if I could.</p>
<p>After some painful reading of the PDFPen Javascript library and playing around, I found that I was able to set the values of form fields if they had assigned names. I first went through the process of naming all of my form fields.</p>
<p><img alt="formnames" src="https://ryanmo.co/posts/Tech/2015-06-06/formnames.png" /></p>
<p>First we start with creating our app variable for PDFPen</p>
<div class="codehilite"><pre><span></span><code><span class="nx">app</span> <span class="o">=</span> <span class="nx">Application</span><span class="p">(</span><span class="s2">&quot;PdfPenPro&quot;</span><span class="p">)</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">includeStandardAdditions</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</code></pre></div>

<p>After that, I created a few functions to easily set the values for text fields and buttons</p>
<div class="codehilite"><pre><span></span><code><span class="kd">function</span> <span class="nx">getFormField</span><span class="p">(</span><span class="nx">doc</span><span class="p">,</span> <span class="nx">field</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">formField</span> <span class="o">=</span> <span class="nx">doc</span><span class="p">.</span><span class="nx">pages</span><span class="p">()[</span><span class="mf">0</span><span class="p">].</span><span class="nx">imprints</span><span class="p">.</span><span class="nx">whose</span><span class="p">({</span><span class="nx">fieldName</span><span class="o">:</span> <span class="nx">field</span><span class="p">})()</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">formField</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">formField</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span>
    <span class="p">}</span>
    <span class="k">return</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">setFormField</span><span class="p">(</span><span class="nx">doc</span><span class="p">,</span> <span class="nx">field</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">theField</span> <span class="o">=</span> <span class="nx">getFormField</span><span class="p">(</span><span class="nx">doc</span><span class="p">,</span> <span class="nx">field</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">theField</span><span class="p">.</span><span class="kd">class</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;button&quot;</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">theField</span><span class="p">.</span><span class="nx">checked</span> <span class="o">=</span> <span class="nx">value</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">theField</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<p>I've found that with PDFPen (or maybe more generally), keeping track of windows in applications with Javascript can get tricky. The order in which they appear can change, and so I've written myself a few helper functions to keep track of documents as I work through scripts.</p>
<div class="codehilite"><pre><span></span><code><span class="kd">var</span> <span class="nx">getByName</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">fileName</span><span class="p">){</span>
    <span class="k">return</span> <span class="nx">app</span><span class="p">.</span><span class="nx">documents</span><span class="p">.</span><span class="nx">byName</span><span class="p">(</span><span class="nx">fileName</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">getDocPage</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">fileName</span><span class="p">,</span> <span class="nx">pageNum</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">getByName</span><span class="p">(</span><span class="nx">fileName</span><span class="p">).</span><span class="nx">pages</span><span class="p">[</span><span class="nx">pageNum</span><span class="o">-</span><span class="mf">1</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div>

<p>These are helpful when I want to duplicate my template receipt that I've created and not run the risk of overwriting the original.</p>
<div class="codehilite"><pre><span></span><code><span class="kd">function</span> <span class="nx">createNewReceipt</span><span class="p">()</span> <span class="p">{</span> 
    <span class="cm">/*</span>
<span class="cm">        Opens the Receipt template</span>
<span class="cm">        Creates a new document and duplicates</span>
<span class="cm">        the template into the new doc</span>
<span class="cm">    */</span>
    <span class="nx">app</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="nx">templatePath</span><span class="p">)</span>
    <span class="kd">var</span> <span class="nx">currentDoc</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">windows</span><span class="p">[</span><span class="mf">0</span><span class="p">].</span><span class="nb">document</span>
    <span class="kd">var</span> <span class="nx">currentDocName</span> <span class="o">=</span> <span class="nx">currentDoc</span><span class="p">.</span><span class="nx">name</span><span class="p">()</span>
    <span class="nx">doc</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">Document</span><span class="p">().</span><span class="nx">make</span><span class="p">()</span>
    <span class="nx">app</span><span class="p">.</span><span class="nx">duplicate</span><span class="p">(</span><span class="nx">getDocPage</span><span class="p">(</span><span class="nx">currentDocName</span><span class="p">,</span> <span class="mf">1</span><span class="p">),</span> <span class="p">{</span><span class="nx">to</span><span class="o">:</span><span class="nx">doc</span><span class="p">})</span>
    <span class="k">return</span> <span class="nx">doc</span>
<span class="p">}</span>
</code></pre></div>

<p>At this point, it's simply a matter of filling in the fields however<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup> I like. I chose to have a few input fields and optional lists. Here are examples of both of those and how they can be implemented in Javascript.</p>
<div class="codehilite"><pre><span></span><code><span class="cm">/*</span>
<span class="cm">    Creates a list to choose from where the options</span>
<span class="cm">    are an array called dateOptions.</span>
<span class="cm">    I always take the 0 element since I don&#39;t allow</span>
<span class="cm">    empty selections and multiples aren&#39;t allowed</span>
<span class="cm">*/</span>
<span class="nx">dateChoice</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">chooseFromList</span><span class="p">(</span>
                        <span class="nx">dateOptions</span><span class="p">,</span>
                        <span class="p">{</span><span class="nx">withTitle</span><span class="o">:</span> <span class="s2">&quot;Start Date&quot;</span><span class="p">,</span>
                        <span class="nx">withPrompt</span><span class="o">:</span> <span class="s2">&quot;Choose Start Date&quot;</span><span class="p">,</span> 
                        <span class="nx">defaultItems</span><span class="o">:</span> <span class="nx">dateOptions</span><span class="p">[</span><span class="mf">1</span><span class="p">],</span>
                        <span class="nx">multipleSelectionsAllowed</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
                        <span class="nx">emptySelectionAllowed</span><span class="o">:</span> <span class="kc">false</span><span class="p">}</span>
            <span class="p">)[</span><span class="mf">0</span><span class="p">]</span>

<span class="cm">/* </span>
<span class="cm">    Basic dialog box. </span>
<span class="cm">    defaultAnswer has to exist, otherwise there</span>
<span class="cm">    won&#39;t be a text box to type into</span>
<span class="cm">*/</span>
<span class="nx">receivedFrom</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">displayDialog</span><span class="p">(</span><span class="s2">&quot;Who sent the check?&quot;</span><span class="p">,</span> <span class="p">{</span><span class="nx">defaultAnswer</span><span class="o">:</span> <span class="s2">&quot;&quot;</span><span class="p">})</span>
</code></pre></div>

<p>I then created an array of all the fields that I wanted to be filled.</p>
<div class="codehilite"><pre><span></span><code><span class="nx">fieldsToFill</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span> <span class="nx">value</span><span class="o">:</span> <span class="nx">receivedFrom</span><span class="p">.</span><span class="nx">textReturned</span><span class="p">,</span> <span class="nx">fieldName</span><span class="o">:</span> <span class="s2">&quot;ReceivedFrom&quot;</span> <span class="p">},</span>
    <span class="p">...</span>
<span class="p">{</span> <span class="nx">value</span><span class="o">:</span> <span class="nx">paymentType</span> <span class="o">==</span> <span class="s2">&quot;Check&quot;</span> <span class="o">?</span> <span class="kc">true</span> <span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">fieldName</span><span class="o">:</span> <span class="s2">&quot;CheckCheckBox&quot;</span> <span class="p">}</span>
<span class="p">]</span>
</code></pre></div>

<p>The very last lines are where the magic happens and all the fields are filled out. It was pretty fun to watch the form fill almost instantly and then let me save it.</p>
<div class="codehilite"><pre><span></span><code><span class="nx">newDoc</span> <span class="o">=</span> <span class="nx">createNewReceipt</span><span class="p">()</span>
<span class="nx">fieldsToFill</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">obj</span><span class="p">){</span><span class="nx">setFormField</span><span class="p">(</span><span class="nx">newDoc</span><span class="p">,</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">fieldName</span><span class="p">,</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">value</span><span class="p">)})</span>

<span class="nx">saveLocation</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">chooseFileName</span><span class="p">({</span><span class="nx">defaultName</span><span class="o">:</span> <span class="nx">rentReceiptName</span><span class="p">(</span><span class="nx">dateChoice</span><span class="p">),</span> <span class="nx">defaultLocation</span><span class="o">:</span> <span class="nx">savePath</span><span class="p">})</span>
<span class="nx">newDoc</span><span class="p">.</span><span class="nx">save</span><span class="p">({</span><span class="k">in</span><span class="o">:</span> <span class="nx">saveLocation</span><span class="p">})</span>
</code></pre></div>

<p>That's it! This will take a lot of the slow process out of filling out my receipts each month. The next step will probably be generating the email and attaching the PDF. PDFPen already includes a script to do this very thing, so no more work needed for me. You can see the entire script <a href="https://gist.github.com/rjames86/8ee61652087a2c44802f">here</a></p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>I found <a href="https://github.com/dtinth/JXA-Cookbook/wiki/User-Interactions">this resource</a> helpful to get me started on user input interactions&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 06 Jun 2015 04:15:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-06-06:/2015/06/06/filling-forms-with-pdfpen-and-javascript</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>efficiency</category>
      <category>javascript</category>
      <category>applescript</category>
    </item>
    <item>
      <title>Automatically Attach tmux in SSH Session</title>
      <link>https://ryanmo.co/2015/05/09/automatically-attach-tmux-in-ssh-session</link>
      <description><![CDATA[<p>I frequently work in ssh sessions and have found terminal multiplexers like <code>tmux</code> to be invaluable. The problem I was constantly facing was having to re-attach or create a new  session each time I would ssh into a machine. Sometimes I would accidentally create a new session when one already existed and would then have to find where I had been working previously.</p>


<p>After searching around, I found a nice way to automatically create a  session each time I ssh into a machine, or re-attach if it already exists.</p>
<div class="codehilite"><pre><span></span><code><span class="k">if</span> <span class="o">[[</span> <span class="s2">&quot;</span><span class="nv">$TMUX</span><span class="s2">&quot;</span> <span class="o">==</span> <span class="s2">&quot;&quot;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span>
        <span class="o">[[</span> <span class="s2">&quot;</span><span class="nv">$SSH_CONNECTION</span><span class="s2">&quot;</span> !<span class="o">=</span> <span class="s2">&quot;&quot;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
    <span class="c1"># Attempt to discover a detached session and attach</span>
    <span class="c1"># it, else create a new session</span>
    <span class="nv">WHOAMI</span><span class="o">=</span><span class="k">$(</span>whoami<span class="k">)</span>
    <span class="k">if</span> tmux has-session -t <span class="nv">$WHOAMI</span> <span class="m">2</span>&gt;/dev/null<span class="p">;</span> <span class="k">then</span>
    tmux -2 attach-session -t <span class="nv">$WHOAMI</span>
    <span class="k">else</span>
        tmux -2 new-session -s <span class="nv">$WHOAMI</span>
    <span class="k">fi</span>
<span class="k">fi</span>
</code></pre></div>

<p>I first check to be sure I'm not in a <code>screen</code> session and also that I'm using ssh and not local to my machine. After that, it's a simple check to see if a  session exists. If so, re-attach it, otherwise create a new one. This can be simple added to the bottom of your ~/.bashrc file. Now every time I ssh in to any machine, my previous session is sitting there waiting for me.</p>
<p><img alt="tmux" src="https://ryanmo.co/posts/Tech/2015-05-09/tmux.gif" /></p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 09 May 2015 09:47:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-05-09:/2015/05/09/automatically-attach-tmux-in-ssh-session</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>efficiency</category>
      <category>bash</category>
    </item>
    <item>
      <title>Apple Watch for Cycling</title>
      <link>https://ryanmo.co/2015/05/03/apple-watch-for-cycling</link>
      <description><![CDATA[<p>I ride with a <a href="https://buy.garmin.com/en-US/US/into-sports/cycling/edge-810/prod112912.html">Garmin 810</a> with a cadence/speed sensor<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>. I wanted to compare the Garmin to the Apple Watch for a bike ride.</p>

<h2 id="first-impressions">First Impressions</h2>
<p>Starting the workout on the watch is nice. Two-three taps and you're starting a workout. What I didn't find ideal is that there wasn't a screen that gave you an overview of elapsed time, speed and distance all in one. There were individual pages for each section that you'd have to swipe through, which isn't very safe while riding a bike. I ended up leaving it on the heart rate monitor since the one I usually wear isn't working.</p>
<h2 id="speed">Speed</h2>
<p>Speed was within 0.3 - 0.5 mph of the Garmin the entire time</p>
<p><img alt="Apple_watch_cycling_00001" src="https://ryanmo.co/posts/Tech/2015-05-03/Apple_watch_cycling_00001.jpg" /></p>
<p>(I know, I know. This was not safe to be taking this photo while moving.)</p>
<h2 id="distance">Distance</h2>
<p>Distance was nearly spot on. I accidentally forgot to start my Garmin, so the watch was ahead by about 0.5 miles. By the end, it was still within the same range</p>
<p><img alt="Apple_watch_cycling_00003" src="https://ryanmo.co/posts/Tech/2015-05-03/Apple_watch_cycling_00003.jpg" /></p>
<p><img alt="Apple_watch_cycling_00004" src="https://ryanmo.co/posts/Tech/2015-05-03/Apple_watch_cycling_00004.jpg" /></p>
<h2 id="time">Time</h2>
<p>This is the one area that I wasn't happy with on the watch. The watch does let you pause the ride, but it won't ever pause it automatically. If you stop for lunch, which I did, I had to manually pause the ride. I forgot to when I stopped to get an espresso along the way.</p>
<p>My elapsed time (top-right on the Garmin) was the true amount of time I spend on the ride and time (top-left) is the time on the bike. With the watch, it all depends on whether you pause your ride or not</p>
<p><img alt="Apple_watch_cycling_00005" src="https://ryanmo.co/posts/Tech/2015-05-03/Apple_watch_cycling_00005.jpg" /></p>
<h2 id="battery">Battery</h2>
<p>Workouts destroy Watch's battery. By the end, Apple Watch was at 29%, while the Garmin was at 79% after around 3.5 hours.</p>
<h2 id="location">Location</h2>
<p>I assume that Apple Watch is using GPS to calculate distance and speed. However, no where does it show you a map of where you went. Not a deal breaker, but it's fun to look back on your previous route, or be able to ride the same route again to improve your times.</p>
<h2 id="siri">Siri</h2>
<p>I tried using Siri while riding. I don't know if I wasn't being loud enough or the road noise was making it difficult to hear me, but I couldn't get it to ever work. Plus needing your other hand for some actions makes using Apple Watch while riding nearly impossible (yes, I tried nose touching, but it wasn't great either).</p>
<h2 id="the-finish">The Finish</h2>
<p>The ride summary on Apple Watch is very nice. It gives you a summary before you save the workout</p>
<p><img alt="Apple_watch_cycling_00006" src="https://ryanmo.co/posts/Tech/2015-05-03/Apple_watch_cycling_00006.png" /></p>
<p><img alt="Apple_watch_cycling_00007" src="https://ryanmo.co/posts/Tech/2015-05-03/Apple_watch_cycling_00007.png" /></p>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>If you don't want to invest in a dedicated cycling GPS, Apple Watch is a decent option, granted you don't go on rides over 5-6 hours. You won't get all the cool features, but if distance, speed and calories is all you care about, it's a pretty good option. I'll be sticking to my Garmin 810 for rides.</p>
<p>Next will be to try out Strava, which will use the iPhone for the heavy lifting rather than the watch. </p>
<p>[Update 2015-05-03 15:45 ] As <a href="https://twitter.com/ismh/status/594993860390051840">@ismh points on out</a> Twitter, another good test would be the Garmin vs. the Apple Watch without the iPhone since I was basically comparing my iPhone GPS to Garmin's.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>The speed sensor uses a magnet to measure speed based on wheel size rather than relying on GPS, which can be inaccurate.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 03 May 2015 01:40:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-05-03:/2015/05/03/apple-watch-for-cycling</guid>
      <category>Tech</category>
      <category>cycling</category>
      <category>applewatch</category>
    </item>
    <item>
      <title>Download Paychecks from ADP with Python</title>
      <link>https://ryanmo.co/2015/04/05/download-paychecks-from-adp-with-python</link>
      <description><![CDATA[<p>If your employer uses ADP, you'll know how terrible their website is.  I always dread having to go to the website, but I like to download my paychecks every two weeks. </p>

<p>I started playing around with writing a script to download them using Python, but decided I should check Github to see if anyone had already done this. Sure enough, the <a href="https://github.com/rayhe/adp">first result</a> was a script that someone had written that would download all your paychecks. </p>
<p>The script worked perfectly, but there were a few small changes I wanted to make. First, I added a new method that let me easily set the destination path.  I also didn't want to have to remember to run this every two weeks, nor did I want a cron job running this script with my password in plain text. I forked the repo, and started incorporating OS X's Keychain to let me store my password securely and running the script once a day to pull down any new paychecks. I borrowed the Keychain library from the <a href="https://github.com/deanishe/alfred-workflow">alfred-workflow</a>. This lets me easily save and retrieve passwords from Keychain in python. To set my for ADP in Keychain, I used the quick script (you could also do this manually in Keychain.app):</p>
<div class="codehilite"><pre><span></span><code><span class="kn">from</span> <span class="nn">keychain</span> <span class="kn">import</span> <span class="n">KeyChain</span>
<span class="kn">import</span> <span class="nn">getpass</span>

<span class="n">my_password</span> <span class="o">=</span> <span class="n">getpass</span><span class="o">.</span><span class="n">getpass</span><span class="p">(</span><span class="s2">&quot;Enter pw: &quot;</span><span class="p">)</span>

<span class="n">KeyChain</span><span class="p">()</span><span class="o">.</span><span class="n">save_password</span><span class="p">(</span><span class="s2">&quot;my_username&quot;</span><span class="p">,</span> <span class="n">my_password</span><span class="p">,</span> <span class="s1">&#39;adp&#39;</span><span class="p">)</span>
</code></pre></div>

<p>The script is smart enough to not re-download paychecks that you've already saved, so now it was as simple as adding a new line to my daily cron jobs</p>
<div class="codehilite"><pre><span></span><code><span class="n">python</span> <span class="n">adp</span><span class="o">.</span><span class="n">py</span> <span class="s2">&quot;my_username&quot;</span>
</code></pre></div>

<p>Lastly, just so that I know when a new paycheck has been added, I set up a small Hazel rule to send me a notification through Pushover whenever a new file is added</p>
<p><img alt="adp_hazel" src="https://ryanmo.co/posts/Tech/2015-04-05/adp_hazel.png" /></p>
<p>You can see my fork of ADP-paychecks <a href="https://github.com/rjames86/adp/tree/keychain">here</a>. You can download a zip of the project <a href="https://github.com/rjames86/adp/archive/keychain.zip">here</a>.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 05 Apr 2015 03:44:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-04-05:/2015/04/05/download-paychecks-from-adp-with-python</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>python</category>
      <category>efficiency</category>
    </item>
    <item>
      <title>How I use my Mac Mini Server on Macminicolo</title>
      <link>https://ryanmo.co/2015/02/09/how-i-use-my-mac-mini-server-on-macminicolo</link>
      <description><![CDATA[<p>I frequently get asked why I use <a href="http://www.macminicolo.net">Macminicolo</a> and if it's worth it. It's a relatively expensive hobby, but it gives me so much benefit that at this point I couldn't live without it. Having an always-on Mac opens up a lot of opportunity and I'm always finding new things to use it for. </p>


<p>If you haven't already read it, Macminicolo has already posted their own [50 ways to use your server][50ways]. I thought it would be worth sharing some of the ways that I use my Mac Mini. Some of these things I've already shared in the past and I'll be sure to post more details on any of the other things in the future. </p>
<h2 id="50ways-httpblogmacminicolonetpost4703882550250-ways-to-use-your-server">[50ways]: http://blog.macminicolo.net/post/47038825502/50-ways-to-use-your-server</h2>
<div class="toc">
<ul>
<li><a href="https://ryanmo.co#50ways-httpblogmacminicolonetpost4703882550250-ways-to-use-your-server">[50ways]: http://blog.macminicolo.net/post/47038825502/50-ways-to-use-your-server</a></li>
<li><a href="https://ryanmo.co#syncing-and-backup">Syncing and Backup</a></li>
<li><a href="https://ryanmo.co#hazel">Hazel</a></li>
<li><a href="https://ryanmo.co#web-server">Web Server</a><ul>
<li><a href="https://ryanmo.co#blogs">Blogs</a></li>
<li><a href="https://ryanmo.co#site-analytics">Site Analytics</a></li>
<li><a href="https://ryanmo.co#url-shortening">URL Shortening</a></li>
<li><a href="https://ryanmo.co#vpn">VPN</a></li>
<li><a href="https://ryanmo.co#tapiriik">Tapiriik</a></li>
<li><a href="https://ryanmo.co#webdav">WebDAV</a></li>
</ul>
</li>
<li><a href="https://ryanmo.co#automation-and-scripts">Automation and Scripts</a></li>
<li><a href="https://ryanmo.co#media">Media</a></li>
</ul>
</div>
<h2 id="syncing-and-backup">Syncing and Backup</h2>
<p><strong>Dropbox</strong></p>
<p>All of my Dropbox files are synced to this computer. My MacBook Air doesn't have enough space to store all my files and so the Mac Mini is the place where I store all my Dropbox files locally so that I can run workflows and have a local backup. </p>
<p><strong>Off-site Backup</strong></p>
<p>Since I have so much space, I use it as an offsite backup for my laptop using Arq over sftp. Nothing too fancy or special here, but it's a nice alternative to Time Machine as an offsite backup. </p>
<h2 id="hazel">Hazel</h2>
<p><a href="http://www.noodlesoft.com/hazel.php">Hazel</a> may be my favorite reason for having an always-on Mac. Hazel watches multiple folders in my Dropbox folder and keeps my Dropbox much more organized than I ever would manually. Some of my favorites are</p>
<p><strong>Organizing my photos</strong></p>
<p>I've talked about this one a bit in the past. I love that Carousel will automatically upload my photos to Dropbox, but the Camera Uploads folder becomes a wasteland of files if you don't organize them on a regular basis. I move all of my photos into a photos folder organized by year. I've written about this in more detail <a href="http://ryanmo.co/2014/01/11/my-photo-workflow/">here</a>. If you use Carousel and have ever saved photos that someone else shared with you, you'll know that a completely different folder is created in Dropbox called Carousel. In this folder, more folders are created with the email address of the person who shared the photos with you. I want these photos in my normal photos folder and so I run the same set of rules as my Camera Uploads to reorganize these photos. The only exception is that I add a "carousel" tag to these photos so that I know they were added from Carousel. </p>
<p><img alt="organize_photos" src="https://ryanmo.co/posts/Tech/2015-02-09/organize_photos.png" /></p>
<p>I take a selfie every day (620 days and counting) and am far too lazy to move that photo to its own special photo every day. I've made sure to always use Camera+ to take these photos. Hazel looks at the metadata of the photos in Camera Uploads and if the photo was taken by the front camera and the app used to create the file was Camera+, it's moved to its own special folder and renamed to just YYYY-MM-DD. </p>
<p><strong>Publishing my blog</strong></p>
<p>I use a static blog generator, <a href="http://blog.getpelican.com">Pelican</a>,  which means that I can store the entire project, including the Python code in my Dropbox account. While I'm on my Mac, it's easy to run a shell script to publish my blog. On iOS, it's not quite as easy and so I use Hazel to watch my blog folder for a file called 'publish.blog'. If that file exists, the shell script is run and the file is then deleted. Since my girlfriend runs her blog over at <a href="http://www.keepitlit.co">keepitlit.co</a> with the same static blog generator, it's much simpler for her to create a file just like this when she wants to publish her blog. </p>
<p><strong>IFTTT → Dropbox → Flickr → AppleTV</strong></p>
<p>I have a rule set up in IFTTT that will append to a text file each time my girlfriend or I post a photo to Instagram. Each time this file runs, I have a script that uploads the photo to a private Flickr album. My AppleTV is then set to that album so that we have an updated list of photos as a screen saver. I realize I could do this directly in IFTTT, but I don't like that you can't make the album private. </p>
<p>Download the script <a href="https://ryanmo.co/posts/Tech/2015-02-09/ifttt_to_flickr.py">here</a></p>
<p><strong>Time sensitive Dropbox shared links</strong></p>
<p>If you have a Dropbox Pro account,  this is now a feature built right in.  I have two folders named "One day" and "One week". Files that I want to share temporarily are copied to that folder. After the set amount of time, the files will be deleted and I'm sent a push notification. For the one week folder, I also get a notification the day before to remind me that it'll get deleted.</p>
<p>You can download the 1 Day rule <a href="https://ryanmo.co/posts/Tech/2015-02-09/1 day.hazelrules">here</a>. Be sure to add your own Pushover key and secret, or remove it if you don't need notifications.</p>
<p><img alt="1%20day" src="https://ryanmo.co/posts/Tech/2015-02-09/1 day.png" /></p>
<p><strong>Scanned files</strong></p>
<p>This folder is for files added from my Fujitsu ScanSnap or Scanbot for iOS. If the file hasn't been OCR'd already, a script will run to launch PDFPenPro and OCR the file. I then have a series of rules set up to move the files based on their names.</p>
<p>Work Receipts is my favorite. When I scan a receipt in Scanbot, I have a snippet "wwr" that expands to "Work receipt". Hazel watches for any new PDFs with that string in the filename. Files are then moved to my expenses folder, organized by date. It then creates a new task in Due.app with a due date of one week in the future so that I'll remember to do my expenses for the file. I no longer have to keep all my receipts and I'll never forget to actually do the expenses<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>.</p>
<p>Business cards obviously moves any business cards to a special folder. Hazel watches for a string match in the filename to know to file these as well. Finally, personal receipts moves the files to my own receipts folder for archiving.</p>
<h2 id="web-server">Web Server</h2>
<p>I use this Mac Mini as a web server since it has more than enough bandwidth and speed. I had never set up an server before, and so this was a fun learning experience to do it all myself. I run a very basic Apache, MySQL, PHP stack for my web server. </p>
<h3 id="blogs">Blogs</h3>
<p>I host this blog from my Mac Mini as well as a couple of others, most notably my girlfriend's. </p>
<h3 id="site-analytics">Site Analytics</h3>
<p>I don't want to use Google Analytics. They know enough about me already and so I use an open source version called Piwik. I've been fairly happy with it so far. </p>
<h3 id="url-shortening">URL Shortening</h3>
<p>I don't like long urls and will shorten them whenever I can. When I publish my blog, I always shorten the URL. I like having full control over that and so I'm using <a href="http://yourls.org">yourls</a> to shorten and track URLs. </p>
<h3 id="vpn">VPN</h3>
<p>I was running OS X Server and used the Mac Mini as a VPN server. Since upgrading to Yosemite, I haven't gotten around to upgrading server, but it's on the todo list. Check out Macminicolo's blog for some great instructions on setting up a VPN <a href="http://blog.macminicolo.net/post/102283942903/setup-a-vpn-with-yosemite-server-10-10">here</a>.</p>
<h3 id="tapiriik">Tapiriik</h3>
<p>When I'm out cycling, I use a Garmin GPS. Most of my friends use RunKeeper, and I prefer Strava over all of the services. <a href="https://tapiriik.com">Tapiriik</a> is a great service that lets you keep your fitness services all in sync, including syncing TCX files to your Dropbox account. It's open source, so you can run a local version on your own computer.</p>
<h3 id="webdav">WebDAV</h3>
<p>When I was using Omnifocus, I didn't want to sync my database through their servers. I could be wrong, but I don't believe it's encrypted on their servers. I feel much better knowing that it's on my machine and I have completely control of it. I have set up my own WebDAV server so that I can sync my database. It's been extremely fast and reliable. </p>
<h2 id="automation-and-scripts">Automation and Scripts</h2>
<p>I have crons running on an hourly, daily and weekly bases. I don't want to bore you with all of them, but here are a few of the better ones. </p>
<p><strong>getmail</strong></p>
<p>I use getmail  for archiving my Gmail daily (they've been known to lose data once in a while). I've never needed to use it, but if I ever decide to change providers or Gmail just hits the delete key someday, I'll have a complete backup of my email. A great introduction to getmail can be found <a href="http://www.makethenmakeinstall.com/2013/02/script-gmail-backup-with-getmail-on-linux/">here</a></p>
<p><strong>Slogger</strong></p>
<p>I love Slogger and Day One. I've customized a lot of the current plugins and even wrote my own for Instagram. You can read more about it <a href="http://ryanmo.co/2014/09/04/instagram-slogger/">here</a></p>
<p><strong>Download Pinboard</strong></p>
<p>My updated version of Brett Terpstra's pinboard → webloc file script to have tagged webloc files locally. You can read more about this project <a href="https://ryanmo.co{static}../2014-12-23/2014-12-23_Download-Pinboard-as-Webloc-Files.md">here</a>. </p>
<p><strong>Face detection → Finder tags</strong></p>
<p>I don't want to use iPhoto, Aperture or Picasa as a photo management application. Instead, I use Picasa to harvest the facial recognition data, and then have a script that applies Finder tags of the person's name to the photo. I haven't shared this one, because it's not done yet, but it's functional. It's a lot of fun to be able to get all the photos of a person with a simple Spotlight search. Hopefully I can share this in the near future.</p>
<p><strong>Dropbox Deletions</strong></p>
<p>I like to keep tabs on my shared folders and any scripts that might be running in my Dropbox account. I parse my Dropbox RSS feeds for deletions of more than 50 files and send myself a push notification with Pushover.</p>
<p>You can download the script <a href="https://ryanmo.co/posts/Tech/2015-02-09/dropbox_events.py">here</a></p>
<p><strong>Dropbox inbox</strong></p>
<p>Throughout the week, I'll add files to an "Inbox" in my Dropbox folder. On the weekends, I send myself a push notification if there have been any files added so that I can deal with them.</p>
<h2 id="media">Media</h2>
<p>I don't have a lot of media. I've never been attached to the idea of owning my music or video and stream whatever I can. For any content that I've ripped over the years, I have <a href="https://plex.tv">Plex</a> running on my Mac Mini. Again, since the connection is so fast, there's little to no lag when streaming something from home or on my phone. </p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>I've recently switched from Omnifocus to Due2. It took me a bit to figure out how to programmatically create task items, especially if I want emojis in it. See <a href="http://unduressing.com/post/108269360039/how-i-use-due-2-come-with-me-if-you-want-to-live">this</a> post for a good reason to use emojis for tasks in Due. If you're interested, <a href="https://ryanmo.co/posts/Tech/2015-02-09/due_hazel.scpt">here</a> is the script I wrote to create tasks in Due for Mac within Hazel.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Mon, 09 Feb 2015 22:04:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-02-09:/2015/02/09/how-i-use-my-mac-mini-server-on-macminicolo</guid>
      <category>Tech</category>
      <category>Dropbox</category>
      <category>efficiency</category>
      <category>hazel</category>
      <category>scripting</category>
    </item>
    <item>
      <title>Save First Page of PDF for Expenses with Hazel</title>
      <link>https://ryanmo.co/2015/01/25/save-first-page-of-pdf-for-expenses-with-hazel</link>
      <description><![CDATA[<p>Once a month I have to submit my Verizon bill as an expense. The process of getting the PDF of the bill and then modifying it turned out to be a big pain by first reminding my mom to send the bill<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>, saving the first page and then submitting it for reimbursement. Turns out that Hazel can take care of everything beyond the actual submission.</p>

<p>I'm fine with reminding my mom to put the PDF in Dropbox, but I then have to check back every-so-often to see if she's actually done it. I've created a rule now that will check for any files in our shared Verizon Bill folder and if Hazel hasn't seen it before, it will send me a push notification with Pushover.</p>
<p><img alt="pushover_hazel_verizon" src="https://ryanmo.co/posts/Tech/2015-01-25/pushover_hazel_verizon.png" /></p>
<p>I then wrote a handy little Applescript based on PDFPenPro's default script called Split PDFs that will take the first page of a PDF and save it to a new file. I differentiate the files by just adding "SINGLE PAGE" to the filename</p>
<div class="codehilite"><pre><span></span><code><span class="k">set</span> <span class="nv">basePath</span> <span class="k">to</span> <span class="s2">&quot;/path/to/verizon/folder&quot;</span>

<span class="k">tell</span> <span class="nb">application</span> <span class="s2">&quot;PDFpenPro&quot;</span>
    <span class="nb">open</span> <span class="nv">theFile</span> <span class="k">as</span> <span class="nv">alias</span>
    <span class="k">set</span> <span class="nv">originalDoc</span> <span class="k">to</span> <span class="na">document</span> <span class="mi">1</span>
    <span class="k">set</span> <span class="nv">docName</span> <span class="k">to</span> <span class="na">name</span> <span class="k">of</span> <span class="nv">originalDoc</span>

    <span class="k">if</span> <span class="nv">docName</span> <span class="ow">ends with</span> <span class="s2">&quot;.pdf&quot;</span> <span class="k">then</span>
        <span class="k">set</span> <span class="nv">docNameLength</span> <span class="k">to</span> <span class="p">(</span><span class="nv">length</span> <span class="k">of</span> <span class="nv">docName</span><span class="p">)</span>
        <span class="k">set</span> <span class="nv">docName</span> <span class="k">to</span> <span class="p">(</span><span class="nb">characters</span> <span class="mi">1</span> <span class="nb">thru</span> <span class="p">(</span><span class="nv">docNameLength</span> <span class="o">-</span> <span class="mi">4</span><span class="p">)</span> <span class="k">of</span> <span class="nv">docName</span> <span class="k">as </span><span class="nc">string</span><span class="p">)</span>
    <span class="k">end</span> <span class="k">if</span>


    <span class="k">set</span> <span class="nv">newDoc</span> <span class="k">to</span> <span class="nb">make</span> <span class="nb">new</span> <span class="na">document</span>
    <span class="k">set</span> <span class="nv">savePath</span> <span class="k">to</span> <span class="p">((</span><span class="nv">basePath</span> <span class="k">as</span> <span class="na">rich text</span><span class="p">)</span> <span class="o">&amp;</span> <span class="nv">docName</span> <span class="o">&amp;</span> <span class="s2">&quot; SINGLE PAGE&quot;</span> <span class="o">&amp;</span> <span class="s2">&quot;.pdf&quot;</span><span class="p">)</span>

    <span class="nb">copy</span> <span class="nv">page</span> <span class="mi">1</span> <span class="k">of</span> <span class="nv">originalDoc</span> <span class="k">to</span> <span class="k">end</span> <span class="k">of</span> <span class="nv">pages</span> <span class="k">of</span> <span class="nv">newDoc</span>

    <span class="nv">save</span> <span class="nv">newDoc</span> <span class="k">in</span> <span class="nv">POSIX</span> <span class="nv">file</span> <span class="nv">savePath</span>

    <span class="nb">quit</span>
<span class="k">end</span> <span class="k">tell</span>
</code></pre></div>

<p>Finally, so that I don't forget to submit the expense, I have one final Applescript that creates a todo item in Omnifocus based on David Spark's post <a href="http://macsparky.com/blog/2012/8/applescript-omnifocus-tasks">here</a></p>
<div class="codehilite"><pre><span></span><code><span class="k">set</span> <span class="nv">theDate</span> <span class="k">to</span> <span class="nb">current date</span>
<span class="k">set</span> <span class="nv">deferDate</span> <span class="k">to</span> <span class="p">(</span><span class="nb">current date</span><span class="p">)</span>
<span class="k">set</span> <span class="nv">dueDate</span> <span class="k">to</span> <span class="p">(</span><span class="nb">current date</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="mi">1</span> <span class="o">*</span> <span class="nb">days</span><span class="p">)</span>
<span class="k">set</span> <span class="nv">theTask</span> <span class="k">to</span> <span class="s2">&quot;Expense Verizon Bill&quot;</span>
<span class="k">set</span> <span class="nv">theNote</span> <span class="k">to</span> <span class="nv">theFile</span>

<span class="k">tell</span> <span class="nb">application</span> <span class="s2">&quot;OmniFocus&quot;</span>
    <span class="k">tell</span> <span class="nb">front</span> <span class="na">document</span>
        <span class="k">set</span> <span class="nv">theContext</span> <span class="k">to</span> <span class="nb">first</span> <span class="nv">flattened</span> <span class="na">context</span> <span class="nb">where</span> <span class="k">its</span> <span class="na">name</span> <span class="o">=</span> <span class="s2">&quot;A Context&quot;</span>
        <span class="k">set</span> <span class="nv">theProject</span> <span class="k">to</span> <span class="nb">first</span> <span class="nv">flattened</span> <span class="nv">project</span> <span class="nb">where</span> <span class="k">its</span> <span class="na">name</span> <span class="o">=</span> <span class="s2">&quot;Expenses&quot;</span>
        <span class="k">tell</span> <span class="nv">theProject</span> <span class="k">to</span> <span class="nb">make</span> <span class="nb">new</span> <span class="nv">task</span> <span class="nv">with</span> <span class="na">properties</span> <span class="p">{</span><span class="na">name</span><span class="p">:</span><span class="nv">theTask</span><span class="p">,</span> <span class="nv">note</span><span class="p">:</span><span class="nv">theNote</span><span class="p">,</span> <span class="na">context</span><span class="p">:</span><span class="nv">theContext</span><span class="p">,</span> <span class="nv">defer</span> <span class="nv">date</span><span class="p">:</span><span class="nv">deferDate</span><span class="p">,</span> <span class="nv">due</span> <span class="nv">date</span><span class="p">:</span><span class="nv">dueDate</span><span class="p">}</span>
    <span class="k">end</span> <span class="k">tell</span>
<span class="k">end</span> <span class="k">tell</span>
</code></pre></div>

<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>We're on a family plan&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 25 Jan 2015 12:46:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-01-25:/2015/01/25/save-first-page-of-pdf-for-expenses-with-hazel</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>hazel</category>
      <category>applescript</category>
      <category>efficiency</category>
    </item>
    <item>
      <title>View Image Links from Pelican in Marked 2</title>
      <link>https://ryanmo.co/2015/01/10/view-image-links-from-pelican-in-marked-2</link>
      <description><![CDATA[<p>I really enjoy writing in MultiMarkdown Composer and having Marked display a rendered version. When writing blog posts like this, images would never appear since Pelican's syntax for displaying images is <code>{static}/path/to/image</code>. I looked into Marked's preprocessor abilities and figured out a nice, clean way to display images when writing blog posts.</p>

<p>In Marked's preferences under Advanced, there is an option to add your own preprocessor. This gives you the ability to format the text in the file before Marked renders the markdown.</p>
<p><img alt="marked_preferences" src="https://ryanmo.co/posts/Tech/2015-01-10/marked_preferences.png" /></p>
<p>The script simply looks for any occurrence of the <code>{static}</code> and replaces it with the path to my content folder in Pelican.</p>
<div class="codehilite"><pre><span></span><code><span class="ch">#!/usr/bin/python</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="n">home</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">expanduser</span><span class="p">(</span><span class="s1">&#39;~&#39;</span><span class="p">)</span>


<span class="k">class</span> <span class="nc">PelicanFormat</span><span class="p">:</span>
    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">blog_path</span> <span class="o">=</span> <span class="n">home</span> <span class="o">+</span> <span class="s1">&#39;/Dropbox/blog/content&#39;</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>

    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="fm">__str__</span><span class="p">())</span>

    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">content</span>

    <span class="k">def</span> <span class="nf">replace_filenames</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;</span><span class="si">{static}</span><span class="s1">&#39;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">blog_path</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">change_codeblocks</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="sd">&quot;&quot;&quot;</span>
<span class="sd">        TODO Pelican uses &#39;:::language&#39; to override syntax highlighting.</span>
<span class="sd">        &quot;&quot;&quot;</span>
        <span class="k">pass</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">p</span> <span class="o">=</span> <span class="n">PelicanFormat</span><span class="p">()</span>
    <span class="n">p</span><span class="o">.</span><span class="n">replace_filenames</span><span class="p">()</span>
    <span class="nb">print</span> <span class="n">p</span>
</code></pre></div>

<p>Now I can preview images for my blog posts instead of broken images.</p>
<hr />
<p><em>Bonus!</em></p>
<p>This is a Text Expander snippet I use to create image urls for Pelican. It looks for the last file that was added to my images folder and then creates the url</p>
<div class="codehilite"><pre><span></span><code><span class="ch">#!/bin/bash</span>

<span class="nv">DROPBOX_PERSONAL</span><span class="o">=</span><span class="k">$(</span>python -c <span class="s2">&quot;import json;f=open(&#39;</span><span class="nv">$HOME</span><span class="s2">/.dropbox/info.json&#39;, &#39;r&#39;).read();data=json.loads(f);print data.get(&#39;personal&#39;, {}).get(&#39;path&#39;, &#39;&#39;)&quot;</span><span class="k">)</span>

<span class="nv">BASE_PATH</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$DROPBOX_PERSONAL</span><span class="s2">/blog/content&quot;</span>
<span class="nv">IMAGE_PATH</span><span class="o">=</span><span class="s2">&quot;images&quot;</span>
<span class="nv">SEARCH_PATH</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$BASE_PATH</span><span class="s2">/</span><span class="nv">$IMAGE_PATH</span><span class="s2">&quot;</span>

<span class="nv">LAST_ADDED</span><span class="o">=</span><span class="k">$(</span>mdfind <span class="se">\</span>
    -onlyin <span class="s2">&quot;</span><span class="nv">$SEARCH_PATH</span><span class="s2">&quot;</span> <span class="se">\</span>
    <span class="s1">&#39;kMDItemDateAdded &gt;= $time.today(-1)&#39;</span> <span class="se">\</span>
    -attr <span class="s1">&#39;kMDItemDateAdded&#39;</span> <span class="p">|</span> <span class="se">\</span>
awk -F<span class="s2">&quot;kMDItemDateAdded =&quot;</span> <span class="s1">&#39;{print $2 &quot;|&quot; $1}&#39;</span> <span class="p">|</span>
sort -r <span class="p">|</span> <span class="se">\</span>
cut -d<span class="s1">&#39;|&#39;</span> -f2 <span class="p">|</span> <span class="se">\</span>
head -n1 <span class="p">|</span> <span class="se">\</span>
sed -e <span class="s1">&#39;s/^ *//&#39;</span> -e <span class="s1">&#39;s/ *$//&#39;</span> -e <span class="s2">&quot;s:</span><span class="nv">$BASE_PATH</span><span class="s2">::&quot;</span><span class="k">)</span>

<span class="nb">echo</span> -n <span class="s2">&quot;![]({static}</span><span class="nv">$LAST_ADDED</span><span class="s2">)&quot;</span>
</code></pre></div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 10 Jan 2015 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2015-01-10:/2015/01/10/view-image-links-from-pelican-in-marked-2</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>python</category>
      <category>pelican</category>
    </item>
    <item>
      <title>Download Pinboard Bookmarks with OS X Tags</title>
      <link>https://ryanmo.co/2014/12/23/pinboard-downloader</link>
      <description><![CDATA[<p>For the last few years, I've been using Brett Terpstra's <a href="http://brettterpstra.com/2011/04/02/mirror-your-pinboard-bookmarks-with-openmeta-tags/">Pinboard to Openmeta</a> to save my Pinboard bookmarks locally. In the last few months, I've been spending more and more time trying to fix issues to get it to run reliably. Since this is something that I use often, I figured it was worth just re-writing it myself.</p>

<p>The script is a slightly simpler version of the original, but the core functionality is the same. Each bookmark is saved as a webloc file and apply any OS X tags to the file. This can be paired with an <a href="https://ryanmo.co/downloads/pinboard-downloader/Bookmarks.alfredworkflow">Alfred workflow</a> to easily search by title or tag.</p>
<p>You can download the download-pinboard project <a href="https://github.com/rjames86/download_pinboard/archive/master.zip">here</a>. Feel free to check out the Github project <a href="https://github.com/rjames86/download_pinboard">here</a>.</p>
<h3 id="setup">Setup</h3>
<p>Create a settings file</p>
<div class="codehilite"><pre><span></span><code>cp settings.py<span class="o">{</span>.example,<span class="o">}</span>
</code></pre></div>

<p>with the following information</p>
<div class="codehilite"><pre><span></span><code><span class="n">_PINBOARD_TOKEN</span> <span class="o">=</span> <span class="s1">&#39;YOUR TOKEN HERE&#39;</span>
<span class="n">_SAVE_PATH</span> <span class="o">=</span> <span class="n">HOME</span> <span class="o">+</span> <span class="s1">&#39;/Bookmarks/&#39;</span>
</code></pre></div>

<p>In settings.py set your Pinboard token and the path where you want your bookmarks to be saved. Your token can be found at <a href="https://pinboard.in/settings/password">https://pinboard.in/settings/password</a>. The path must exist where you save your bookmarks and must end with a trailing /.</p>
<h3 id="running-the-script">Running the Script</h3>
<p>To start the script, you can simply do</p>
<div class="codehilite"><pre><span></span><code><span class="err">python main.py</span>
</code></pre></div>

<h4 id="optional-arguments">Optional arguments</h4>
<p><code>-v, --verbose</code> Shows output as stdout<br />
<code>-t</code> Filters the bookmarks you want to download by tag. You can pass multiple -t tags, but no more than 3. Multiple tags are AND not OR<br />
<code>--reset [optional num of days]</code> Resets your last updated time. If you don't specifiy a number, it will reset to 999 days.<br />
<code>--skip-update</code> Lets you bypass the last downloaded time. Nice for redownloading everything.  </p>
<h3 id="notes-and-todo">Notes and Todo</h3>
<p>I have a very small number of bookmarks (~150) and so I don't know if there will be any issues with a really large library. If you have one, and run into problems, please let me know and I'll happily look into it.</p>
<hr />]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Tue, 23 Dec 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-12-23:/2014/12/23/pinboard-downloader</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>python</category>
      <category>projects</category>
    </item>
    <item>
      <title>Backup Your Contacts v2 : Yosemite’s Javascript Automation</title>
      <link>https://ryanmo.co/2014/12/14/backup-your-contacts-v2-yosemites-javascript-automation</link>
      <description><![CDATA[<p>I recently read MacStories' <a href="http://www.macstories.net/tutorials/getting-started-with-javascript-for-automation-on-yosemite/">article</a> and was curious how easy this was to learn. Applescript never made sense to me and I spent more time trying to piece together examples than actually writing anything meaningful. I don't trust iCloud to keep my contacts safe, and I'm still using <a href="https://ryanmo.co{static}../2014-09-28/2014-09-25_backup_contacts_with_pythonista.md">my previous workflow</a> with <a href="http://contrast.co/launch-center-pro/">Launch Center Pro</a> and <a href="http://omz-software.com/pythonista/">Pythonista</a> to back up my contacts.</p>

<p>My first attempt at the new JSX Automation was a script to back up my contacts, which would allow me to run this automatically on my Mac Mini server. Here is what the script looks like</p>
<div class="codehilite"><pre><span></span><code><span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">Application</span><span class="p">.</span><span class="nx">currentApplication</span><span class="p">()</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">includeStandardAdditions</span> <span class="o">=</span> <span class="kc">true</span>

<span class="nx">now</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
<span class="nx">nowString</span> <span class="o">=</span> <span class="nx">now</span><span class="p">.</span><span class="nx">getFullYear</span><span class="p">()</span> <span class="o">+</span> <span class="s2">&quot;-&quot;</span> <span class="o">+</span> <span class="p">(</span><span class="nx">now</span><span class="p">.</span><span class="nx">getMonth</span><span class="p">()</span> <span class="o">+</span> <span class="mf">1</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&quot;-&quot;</span> <span class="o">+</span> <span class="nx">now</span><span class="p">.</span><span class="nx">getDate</span><span class="p">()</span>

<span class="c1">// Replace outputFile with this if you want to automatically set the path</span>
<span class="c1">// var outputFile = Path(&#39;pick your path&#39;)  </span>
<span class="kd">var</span> <span class="nx">outputFile</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">chooseFileName</span><span class="p">({</span>
    <span class="nx">withPrompt</span><span class="o">:</span> <span class="s2">&quot;Pick where to save your vCard backup.&quot;</span><span class="p">,</span>
    <span class="nx">defaultName</span><span class="o">:</span> <span class="nx">nowString</span> <span class="o">+</span> <span class="s2">&quot;_backup.vcf&quot;</span>
<span class="p">})</span>

<span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">openForAccess</span><span class="p">(</span><span class="nx">outputFile</span><span class="p">,</span> <span class="p">{</span><span class="nx">writePermission</span><span class="o">:</span> <span class="kc">true</span><span class="p">})</span>
<span class="nx">Contacts</span> <span class="o">=</span> <span class="nx">Application</span><span class="p">(</span><span class="s1">&#39;/Applications/Contacts.app&#39;</span><span class="p">)</span>
<span class="nx">contacts</span> <span class="o">=</span> <span class="nx">Contacts</span><span class="p">.</span><span class="nx">people</span><span class="p">()</span>
<span class="nx">outputString</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>

<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mf">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">contacts</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">){</span>
  <span class="nx">outputString</span> <span class="o">+=</span> <span class="nx">contacts</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">vcard</span><span class="p">()</span>
<span class="p">}</span>

<span class="nx">app</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">outputString</span><span class="p">,{</span>
    <span class="nx">to</span><span class="o">:</span> <span class="nx">a</span><span class="p">,</span>
    <span class="nx">startingAt</span><span class="o">:</span> <span class="mf">0</span><span class="p">,</span>
    <span class="nx">as</span><span class="o">:</span><span class="s1">&#39;text&#39;</span>
<span class="p">})</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">closeAccess</span><span class="p">(</span><span class="nx">outputFile</span><span class="p">)</span>

<span class="nx">app</span><span class="p">.</span><span class="nx">displayNotification</span><span class="p">(</span><span class="s2">&quot;Backup finished!&quot;</span><span class="p">,{</span>
    <span class="nx">withTitle</span><span class="o">:</span> <span class="s2">&quot;Backup Contacts&quot;</span><span class="p">,</span>
    <span class="nx">subtitle</span><span class="o">:</span> <span class="nx">contacts</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="s2">&quot; contacts backed up.&quot;</span>
<span class="p">})</span>
</code></pre></div>

<p>The current script lets you choose the path to save the file. You can change this to have it be the same path every time if you want (see the instructions in the comments above).</p>
<p>You can download the script <a href="https://ryanmo.co/2014/12/14/backup-your-contacts-v2-yosemites-javascript-automation/BackupContacts.zip">here</a> to try it for yourself.</p>
<p>Big thank you to Alex Guyot at MacStories for his introduction to Javascript Automation.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 14 Dec 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-12-14:/2014/12/14/backup-your-contacts-v2-yosemites-javascript-automation</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>applescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Show Last File Added to Dropbox with Alfred</title>
      <link>https://ryanmo.co/2014/11/16/show-last-file-added-to-dropbox-with-alfred</link>
      <description><![CDATA[<p><em>Update 2014-12-28</em></p>
<p>I realize now that simply revealing the file isn't as useful as performing actions on the file. There are also occasions when I want to see the last 5 files added, not just the most recent. I've converted the workfow to now be a script filter.</p>
<p><img alt="Show Recently Added in Dropbox" src="https://ryanmo.co/2014/11/16/show-last-file-added-to-dropbox-with-alfred/recentlyadded.png" /></p>
<p>I've updated the download link below to be the latest Alfred workflow. The old version is still available with an empty keyword. </p>
<p>Enjoy!</p>
<hr />
<p>To continue on <a href="https://ryanmo.co{static}../2014-11-15/2014-11-15_A_better_downloads_folder.md">yesterday's post</a>, revealing files in the Finder can be very useful. One thing that I find myself doing daily is moving into a particular folder in my Dropbox account once I've used the Alfred "move" action or when a new file has been added to my account.</p>
<p>How many times have you see this notification and wondered what file it was and where on earth it is in your Dropbox account?</p>
<p><img alt="dropbox_notification" src="https://ryanmo.co/2014/11/16/show-last-file-added-to-dropbox-with-alfred/dropbox_notification.png" /></p>
<p>Similar to revealing the last file added to my Dropbox folder, I can show the file last added in my Dropbox account. The only difference here is that my two Dropbox folders combined (work and personal) amount to about 150,000 files. Listing off all those files and sorting them by Date Added would take far too long. Instead, I can take advantage of <code>mdfind</code>, which is the command line version of Spotlight.</p>
<div class="codehilite"><pre><span></span><code><span class="nv">DROPBOX_WORK</span><span class="o">=</span><span class="k">$(</span>python -c <span class="s2">&quot;import json;f=open(&#39;</span><span class="nv">$HOME</span><span class="s2">/.dropbox/info.json&#39;, &#39;r&#39;).read();data=json.loads(f);print data.get(&#39;business&#39;, {}).get(&#39;path&#39;, &#39;&#39;)&quot;</span><span class="k">)</span>
<span class="nv">DROPBOX_PERSONAL</span><span class="o">=</span><span class="k">$(</span>python -c <span class="s2">&quot;import json;f=open(&#39;</span><span class="nv">$HOME</span><span class="s2">/.dropbox/info.json&#39;, &#39;r&#39;).read();data=json.loads(f);print data.get(&#39;personal&#39;, {}).get(&#39;path&#39;, &#39;&#39;)&quot;</span><span class="k">)</span>
<span class="nv">DROPBOX</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$HOME</span><span class="s2">/Dropbox&quot;</span>

<span class="nv">LAST_ADDED</span><span class="o">=</span><span class="k">$(</span>mdfind <span class="se">\</span>
    -onlyin <span class="s2">&quot;</span><span class="nv">$DROPBOX_PERSONAL</span><span class="s2">&quot;</span> <span class="se">\</span>
    -onlyin <span class="s2">&quot;</span><span class="nv">$DROPBOX_WORK</span><span class="s2">&quot;</span> <span class="se">\</span>
    -onlyin <span class="s2">&quot;</span><span class="nv">$DROPBOX</span><span class="s2">&quot;</span> <span class="se">\</span>
    <span class="s1">&#39;kMDItemDateAdded &gt;= $time.today(-1)&#39;</span> <span class="se">\</span>
    -attr <span class="s1">&#39;kMDItemDateAdded&#39;</span> <span class="p">|</span> <span class="se">\</span>
awk -F<span class="s2">&quot;kMDItemDateAdded =&quot;</span> <span class="s1">&#39;{print $2 &quot;|&quot; $1}&#39;</span> <span class="p">|</span>
sort -r <span class="p">|</span> <span class="se">\</span>
cut -d<span class="s1">&#39;|&#39;</span> -f2 <span class="p">|</span> <span class="se">\</span>
head -n1 <span class="p">|</span> <span class="se">\</span>
sed -e <span class="s1">&#39;s/^ *//&#39;</span> -e <span class="s1">&#39;s/ *$//&#39;</span><span class="k">)</span>

<span class="k">if</span> <span class="o">[</span> ! -z <span class="s2">&quot;</span><span class="nv">$LAST_ADDED</span><span class="s2">&quot;</span> <span class="o">]</span>
<span class="k">then</span>
  <span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$LAST_ADDED</span><span class="s2">&quot;</span>
  open -R <span class="s2">&quot;</span><span class="nv">$LAST_ADDED</span><span class="s2">&quot;</span>
<span class="k">fi</span>
</code></pre></div>

<p>I include the paths to both the work and personal Dropbox folders if you have them (it doesn't matter if you don't) as well as the regularly named "Dropbox" folder. From there, it's a matter of getting the name and date last added for files that have been added within the last day. The result shows up almost instantly with having over 150,000 files in my Dropbox folders. </p>
<p>You can download the workflow below. </p>
<p><a href="https://ryanmo.co/2014/11/16/show-last-file-added-to-dropbox-with-alfred/Reveal last added in Dropbox.alfredworkflow"><img alt="image" src="https://ryanmo.co/images/alfred_extension.jpg" /></a>  </p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 16 Nov 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-11-16:/2014/11/16/show-last-file-added-to-dropbox-with-alfred</guid>
      <category>Tech</category>
      <category>alfred</category>
      <category>scripting</category>
    </item>
    <item>
      <title>A Better “Show Downloads Folder" with Alfred</title>
      <link>https://ryanmo.co/2014/11/15/a-better-show-downloads-folder-with-alfred</link>
      <description><![CDATA[<p>I've always used Alfred as a way to reveal my Downloads folder with the keyboard shortcut ⌘ ⌥ L, but that only gets me part of the way. I'm usually opening the downloads folder for a reason and so it would be handy if the file last added was already highlighted for me.</p>
<p>My original workflow simply looked like this</p>
<p><img alt="Show Downloads Folder in Alfred" src="https://ryanmo.co/2014/11/15/a-better-show-downloads-folder-with-alfred/show_downloads.png" title="Show Downloads Folder in Alfred" /></p>
<p>Unfortunately, listing files or using the <code>find</code> command doesn't give you the file last added. You can get away with using ctime, but not in every case. Turns out Date Added is an attribute that Mac OS X adds to every file, which meant that I could use <code>mdfind</code> to get the file that was last added. All that's left to do is print out a list of file name and date last added, sort them, and get the most recently added file from the Downloads folder. From there, its just a matter of using the <code>open -R</code> command to reveal the file</p>
<div class="codehilite"><pre><span></span><code><span class="nv">DOWNLOADS</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$HOME</span><span class="s2">/Downloads&quot;</span>

<span class="nv">RECENT</span><span class="o">=</span><span class="k">$(</span>mdls -name kMDItemFSName -name kMDItemDateAdded <span class="nv">$DOWNLOADS</span>/* <span class="p">|</span> <span class="se">\</span>
sed <span class="s1">&#39;N;s/\n//&#39;</span> <span class="p">|</span> <span class="se">\</span>
awk <span class="s1">&#39;{print $3 &quot; &quot; $4 &quot; &quot; substr($0,index($0,$7))}&#39;</span> <span class="p">|</span> <span class="se">\</span>
sort -r <span class="p">|</span> <span class="se">\</span>
cut -d<span class="s1">&#39;&quot;&#39;</span> -f2 <span class="p">|</span> <span class="se">\</span>
head -n1<span class="k">)</span>

open -R <span class="s2">&quot;</span><span class="nv">$DOWNLOADS</span><span class="s2">/</span><span class="nv">$RECENT</span><span class="s2">&quot;</span>
</code></pre></div>

<p><code>mdls -name kMDItemFSName -name kMDItemDateAdded ~/Downloads/*</code></p>
<p>Lists the name and date added for all the files in the Downloads folder</p>
<p><code>sed 'N;s/\n//'</code></p>
<p>Looks at the next line and removes any newlines, which puts the name and date added all on one line<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup></p>
<p><code>awk '{print $3 " " $4 " " substr($0,index($0,$7))}'</code></p>
<p>Returns the name and date added in a nice format like "2014-11-15 19:36:28 "Arq.zip""</p>
<p><code>sort -r</code></p>
<p>Sorts the lines</p>
<p><code>cut -d'"' -f2</code></p>
<p>Splits the lines on a quotation mark and returns the second result (the filename)</p>
<p><code>head -n1</code></p>
<p>Gives the top item in the list, which is the most recently added file</p>
<p><code>open -R</code></p>
<p>Reveals the file instead of opening it in OS X.</p>
<p>You can download this workflow to reveal the last added file in your Downloads folder below.</p>
<p><a href="https://ryanmo.co/downloads/OpenDownloadsFolder.alfredworkflow"><img alt="image" src="https://ryanmo.co/images/alfred_extension.jpg" /></a></p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>You can read a nice explanation of how the N command works in sed <a href="http://stackoverflow.com/questions/6255796/how-the-n-command-works-in-sed">here</a>&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 15 Nov 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-11-15:/2014/11/15/a-better-show-downloads-folder-with-alfred</guid>
      <category>Tech</category>
      <category>alfred</category>
      <category>scripting</category>
    </item>
    <item>
      <title>My Exiftool Cheatsheet</title>
      <link>https://ryanmo.co/2014/09/28/exiftool-cheatsheet</link>
      <description><![CDATA[<p>I've spent a lot of time organizing and <a href="https://ryanmo.co{static}../2014-03-01/2014-03-01%20Digitizing%20the%20Family%20Photos.md">digitizing</a> old photos. Exiftool has been a great tool, but the learning curve is fairly steep and you can end up making a lot of bad mistakes<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>. Here is my ongoing cheat sheet of exiftool commands.</p>


<script src="https://gist.github.com/rjames86/33b9af12548adf091a26.js"></script>

<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>I still make a backup copy of my photos before ever making changes.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 28 Sep 2014 12:22:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-09-28:/2014/09/28/exiftool-cheatsheet</guid>
      <category>Tech</category>
      <category>exiftool</category>
      <category>photos</category>
    </item>
    <item>
      <title>Back up Your Contacts with Pythonista</title>
      <link>https://ryanmo.co/2014/09/28/back-up-your-contacts-with-pythonista</link>
      <description><![CDATA[<p>While it hasn't happened in a while, I have lost or had issues with contacts in iCloud. I haven't found a reliable way to automatically back up my contacts on my Mac, but Pythonista offers a simple way to back them up.</p>


<p>Pythonista offers a great library which gives you access to your contacts on iOS. With a short script, I can back up my contacts to a folder in my Dropbox account. This will add a vCard file to my Dropbox account with the date the script was run.</p>
<p><em>Note: You'll need the Dropbox login script for this to work. Visit <a href="https://gist.github.com/omz/4034526">this</a> link to get it set up. I keep mine in a folder called "lib" in Pythonista.</em></p>
<p>You can download my Contacts Back up script <a href="https://gist.github.com/rjames86/79f857f427599f6e145c">here</a>.</p>
<div class="codehilite"><pre><span></span><code><span class="kn">import</span> <span class="nn">contacts</span>
<span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">console</span>
<span class="n">sys</span><span class="o">.</span><span class="n">path</span> <span class="o">+=</span> <span class="p">[</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)),</span> <span class="s1">&#39;lib&#39;</span><span class="p">)]</span>
<span class="kn">from</span> <span class="nn">dropboxlogin</span> <span class="kn">import</span> <span class="n">get_client</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>

<span class="c1"># Update this path here for the backup</span>
<span class="c1"># location in your Dropbox account.</span>
<span class="n">BACKUP_PATH</span> <span class="o">=</span> <span class="s1">&#39;/Backups/Contacts&#39;</span>

<span class="n">TODAY</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">today</span><span class="p">()</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s1">&#39;%Y-%m-</span><span class="si">%d</span><span class="s1">&#39;</span><span class="p">)</span>

<span class="n">dropbox_client</span> <span class="o">=</span> <span class="n">get_client</span><span class="p">()</span>

<span class="n">VCARD</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">person</span><span class="o">.</span><span class="n">vcard</span> <span class="k">for</span> <span class="n">person</span> <span class="ow">in</span> <span class="n">contacts</span><span class="o">.</span><span class="n">get_all_people</span><span class="p">())</span>

<span class="n">console</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">dropbox_client</span><span class="o">.</span><span class="n">put_file</span><span class="p">(</span><span class="n">BACKUP_PATH</span> <span class="o">+</span> <span class="s1">&#39;/contacts </span><span class="si">{}</span><span class="s1">.vcf&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">TODAY</span><span class="p">),</span> <span class="n">VCARD</span><span class="p">)</span>
<span class="nb">print</span> <span class="s1">&#39;Backup complete!&#39;</span>
</code></pre></div>

<p>If you're like me, you're going to forget to do this on a regular basis. I hadn't yet found a reason to use the IFTTT Launch Center Pro triggers, but this turned about to be a great reason to use it. I have a trigger that goes off on the first of every month that will launch the back up script.</p>
<p>If you want to get reminders to back up your contacts using IFTTT, you can use the recipe below.</p>
<p><a href="https://ifttt.com/view_embed_recipe/206885-backup-contacts-with-lcp" target = "_blank" class="embed_recipe embed_recipe-l_24" id= "embed_recipe-206885"><img src= 'https://ifttt.com/recipe_embed_img/206885' alt="IFTTT Recipe: Backup Contacts with LCP connects date-time to launch-center" width="370px" style="max-width:100%"/></a><script async type="text/javascript" src= "https://ryanmo.co//ifttt.com/assets/embed_recipe.js"></script></p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 28 Sep 2014 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-09-28:/2014/09/28/back-up-your-contacts-with-pythonista</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>Dropbox</category>
      <category>ifttt</category>
      <category>launchcenterpro</category>
      <category>ios</category>
      <category>pythonista</category>
    </item>
    <item>
      <title>My Podcast List</title>
      <link>https://ryanmo.co/2014/09/14/my-podcast-list</link>
      <description><![CDATA[<p>I commute about 30 minutes each way to and from work. Since I can listen to music while I work and spoken word is distracting, I listen to podcasts. Here's a list of the podcasts I'm currently listening to.</p>


<p><strong>Current Podcasting App</strong>: <a href="http://overcast.fm">Overcast</a></p>
<ul>
<li><a href="http://99percentinvisible.org">99% Invisible</a></li>
<li><a href="http://atp.fm">Accidental Tech Podcast</a></li>
<li><a href="https://ryanmo.co">The Black Tapes</a></li>
<li><a href="http://relay.fm/connected">Connected</a></li>
<li><a href="https://ryanmo.co">Criminal</a></li>
<li><a href="https://ryanmo.co">Hello Internet</a></li>
<li><a href="https://ryanmo.co">Lifeoff</a></li>
<li><a href="http://www.npr.org/programs/ted-radio-hour/">NPR TED Radio Hour</a></li>
<li><a href="http://www.radiolab.org/series/podcasts/">Radiolab</a></li>
<li><a href="https://ryanmo.co">Small Town Horror</a></li>
<li><a href="https://ryanmo.co">Invisibilia</a></li>
<li><a href="https://ryanmo.co">Under the Radar</a></li>
<li><a href="https://ryanmo.co">Ungeniused</a></li>
<li><a href="https://ryanmo.co">Upgrade</a></li>
</ul>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 14 Sep 2014 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-09-14:/2014/09/14/my-podcast-list</guid>
      <category>Tech</category>
      <category>podcasts</category>
    </item>
    <item>
      <title>Instagram for Slogger</title>
      <link>https://ryanmo.co/2014/09/04/instagram-slogger</link>
      <description><![CDATA[<p>In early 2013, I discovered Slogger and loved the idea of journalling about more than just what I had to say. What I was listening to at a given time is just as important as what I was thinking. However, there wasn't an ideal way to log Instagram posts without other dependencies, and so I took a stab at writing my first plugin.</p>


<hr />
<p><em>Update</em></p>
<p>As of June 2016, Instagram has changed their API and no longer allows this script to work. Sorry :(</p>
<hr />
<p>I didn't know ruby and quickly learned how bad some API documentation can be, but I wanted this plugin more than all the others. After fiddling with it for an evening, I was able to log Instagram posts with more than just a photo, including:</p>
<ul>
<li>number of likes</li>
<li>comments</li>
<li>date of post, not the date Slogger ran</li>
<li>location data (including place name if you used Foursquare checkin)</li>
</ul>
<p>The last point is by far my favorite. I can look at a map over the last year and see all the Instagram photos I've taken and where I took them</p>
<p><img alt="Instagram map" src="https://ryanmo.co/2014/09/04/instagram-slogger/dayonemap.png" /></p>
<p>I also wanted to import photos that I had already taken. The plugin now will let you set <code>backdate: true</code> and will log the last 20 photos that you had posted on Instagram. Once it's finished, it'll automatically set itself to false to prevent duplicate posts<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>.</p>
<p>Setup is fairly straight forward. I create a local server, which runs you through the Instagram OAuth flow. After you've finished, you simply paste in the access token, and it'll run from there</p>
<div class="codehilite"><pre><span></span><code>&gt; ./slogger -o instagram
Initializing Slogger v2 <span class="o">(</span><span class="m">2</span>.1.14<span class="o">)</span>...
<span class="m">08</span>:01:18      InstagramLogger: Instagram requires configuration, please run from the <span class="nb">command</span> line and follow the prompts

------------- Instagram Configuration --------------

Slogger will now open an authorization page <span class="k">in</span> your default web browser. Copy the code you receive and <span class="k">return</span> here.

Press Enter to <span class="k">continue</span>...
</code></pre></div>

<p>Last night I finally did a pull request and it went live this morning. You can check out and download the latest Slogger on <a href="https://github.com/ttscoff/Slogger">Github</a>. If you find any issues or bugs, please send them my way. Enjoy!</p>
<p><img alt="Instagram map" src="https://ryanmo.co/2014/09/04/instagram-slogger/dayone.png" /></p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>It looks like in the newest version of Slogger, you can find and delete duplicate posts.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Thu, 04 Sep 2014 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-09-04:/2014/09/04/instagram-slogger</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>efficiency</category>
      <category>slogger</category>
      <category>dayone</category>
      <category>photos</category>
    </item>
    <item>
      <title>Global Shell Variables for Dropbox Paths</title>
      <link>https://ryanmo.co/2014/08/31/global-shell-variables-for-dropbox-paths</link>
      <description><![CDATA[<p>I have multiple computers running Dropbox, all of which have different folder paths to where the Dropbox folder is located. I wanted to have a universal way to find and navigate to the folders regardless of what computer I was on.</p>


<p>In most cases, setting a variable to your Dropbox path is relatively easy. You could set your .bashrc to look something like this</p>
<div class="codehilite"><pre><span></span><code><span class="nv">DROPBOX_PERSONAL</span><span class="o">=</span><span class="nv">$HOME</span>/Dropbox
</code></pre></div>

<p>But this fails in a few situations, all of which apply to me on one or more of my computers</p>
<ul>
<li>Multiple Dropbox accounts on one computer (Personal and Business accounts)</li>
<li>Dropbox isn't located in my home folder</li>
</ul>
<p>If you're running Dropbox version 2.8 or higher (you should be anyways), there's a json file that tells you where your Dropbox folders are located. The json looks like this:</p>
<div class="codehilite"><pre><span></span><code><span class="o">{</span>
    <span class="s2">&quot;personal&quot;</span>: <span class="o">{</span>
        <span class="s2">&quot;path&quot;</span>: <span class="s2">&quot;/Users/username/Dropbox (Personal)&quot;</span>,
        <span class="s2">&quot;host&quot;</span>: <span class="m">1234</span>
    <span class="o">}</span>,
    <span class="s2">&quot;business&quot;</span>: <span class="o">{</span>
        <span class="s2">&quot;path&quot;</span>: <span class="s2">&quot;/Users/username/Dropbox (Business)&quot;</span>, 
        <span class="s2">&quot;host&quot;</span>: <span class="m">5678</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div>

<p>What this means is that you can set global variables using this information in your .bashrc or .bash_profile so that you always know where your Dropbox folder is</p>
<div class="codehilite"><pre><span></span><code><span class="nv">DROPBOX_WORK</span><span class="o">=</span><span class="k">$(</span>python -c <span class="s2">&quot;import json;f=open(&#39;</span><span class="nv">$HOME</span><span class="s2">/.dropbox/info.json&#39;, &#39;r&#39;).read();data=json.loads(f);print data.get(&#39;business&#39;, {}).get(&#39;path&#39;, &#39;&#39;)&quot;</span><span class="k">)</span>
<span class="nv">DROPBOX_PERSONAL</span><span class="o">=</span><span class="k">$(</span>python -c <span class="s2">&quot;import json;f=open(&#39;</span><span class="nv">$HOME</span><span class="s2">/.dropbox/info.json&#39;, &#39;r&#39;).read();data=json.loads(f);print data.get(&#39;personal&#39;, {}).get(&#39;path&#39;, &#39;&#39;)&quot;</span><span class="k">)</span>
</code></pre></div>

<p>Now all you have to do is reference your Dropbox folders with <code>$DROPBOX_PERSONAL</code> or <code>$DROPBOX_WORK</code>.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 31 Aug 2014 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-08-31:/2014/08/31/global-shell-variables-for-dropbox-paths</guid>
      <category>Tech</category>
      <category>bash</category>
      <category>scripting</category>
      <category>Dropbox</category>
    </item>
    <item>
      <title>[Updated] Comcast is just Awful</title>
      <link>https://ryanmo.co/2014/08/29/updated-comcast-is-just-awful</link>
      <description><![CDATA[<p>This story probably won't be surprising to anyone, and it's one of many about how awful Comcast's service is. Maybe I thought I would get lucky and maybe I was assuming that I would have a similar experience to the last time I used them, which was over a year ago. My first experience was impersonal, but it wasn't horrible. This time was very different.</p>


<p>On Monday, 2014-08-25, I signed up for Comcast online. There was a relatively nice deal where I would get up to 50mbps download for $34.99/mo for the first 12 months. Seemed pretty reasonable and I so I chose the plan and started through the sign-up process. Towards the end, they gave me some good news. My apartment was already ready for Comcast and I could order the self-install kit (SIK) and not worry about having someone come out to the house. I wanted my internet NOW, so I chose the overnight option, which was an extra $30. </p>
<p>The next day, the modem shows up at around 8:00am and so I quickly pull it out of the box and hook it up. It doesn't work. I immediately call Comcast and tell them that my modem isn't getting a connection and I believe that my apartment doesn't have whatever hookups it needs (even though the last tenant had Comcast too). I had to go through the basic "unplug it, wait, plug it back in" b.s. and then they told me I would need to have a tech come out and there may be additional charges. A few hours later, I get a confirmation email that the tech would be coming between 7:30-8:00am on Friday. Perfect, since now I don't have to miss work. But now there's a new problem. The email has an order summary, saying that my monthly bill will be $80 + $30 for the overnight shipping + a new $50 for a failed SIK install. Again, I call Comcast, and this is where the real annoyance begins.</p>
<p>The first person I talked to was pleasant enough, but told me that they couldn't see online deals and so they couldn't and wouldn't honor the deal I claimed to have signed up for (you don't have a browser to look??). They basically made me swear on my life that I signed up online and not in a store before they
 would let me go any further. I was then transferred to someone else, only to re-confirm my name, phone number, address and last 4 of my social and then told the exact same thing. Repeat these steps 3 more times before getting disconnected during the hold process. I call back and immediately tell the person I've been transferred 5 times and disconnected once. I need someone to help me or I would like to talk to someone about getting a full refund for my service. I was immediately transferred (again) to a customer retention specialist. She was nice, and at least seemed to have a soul. She understood my situation and told me that she will get me to someone who can honor the deal I signed up for. She was nice enough to also do a live call transfer in which she explained to the other rep while I was on the line what my situation was. She, also, was very nice, and was very explicit about what she was doing and that she left detailed notes about everything that happened. She waved my overnight fee since a tech was coming out and that if I got charged for anything else, call back, give her agent ID and tell the rep to look at the ticket notes. At this point, I was happy and assumed it was smooth sailing from there. Nope.</p>
<p>It's now Friday morning, I get up early and eagerly sit and wait for the Comcast tech to arrive and give me my internets. Comcast let me down...again... Here's the timeline so far (remember, the scheduled appointment was 7:30-8:00am):</p>
<p><strong>07:30am</strong> - No show. I'll give him a few more minutes.<br />
<strong>8:20am</strong> - Called Comcast to see if someone was coming out. Their automated system tells me the tech is late (as if I didn't know) and that he'll be arriving between 7:55-8:25am. I can press 4 to talk to someone. Pressing this button then says "this entry is invalid" and immediately disconnects<br />
<strong>08:23am</strong> - I call again, but this time it says that my tech missed the appointment and immediately tries to transfer me, and then disconnects due to an invalid entry. I'm now stuck and cannot talk to anyone at Comcast.<br />
<strong>08:36am</strong> - I call one last time, claim to not have an account and finally get to someone. He's actually friendly and helpful. He gives me the $20 credit I'm due for the tech not being on time, but promises that dispatch would be calling me within 15 minutes to let me know what's going on.<br />
<strong>09:04am</strong> -  Sitting and waiting for either the tech to show up, or Comcast to call me back. So much for that 15 minutes.<br />
<strong>09:22am</strong> - Still no call or tech. I think I'm going to just go to work at this point. I really don't want to go the whole weekend without internet.<br />
<strong>09:33am</strong> - Decided to call one more time. They told me the tech just straight up isn't coming now. The next appointment is Sunday from 12:00-2:00pm. Guess we'll see what happens then.  </p>
<p>[Updated 2014-08-31]</p>
<p><strong>Saturday 2014-08-30</strong></p>
<p><strong>8:00pm</strong> - I receive my Comcast bill, once again saying I owe $110, of which I'll be paying $80/mo instead of $39.99. Comcast's site says they're available 24/7 on the phone, but when I call, it says they're closed and won't be open again until Tuesday since it's a holiday. Luckily their chat was available. I explained in detail my situation, the agent who left notes for my account, and that my bill is incorrect. They verified that the bill was in fact correct and that a new one will be issued in a few days. The person was very nice and answered my question in detail.</p>
<p><strong>Sunday 2014-08-31</strong></p>
<p><strong>12:45pm</strong> - The tech arrived (on time). He was a nice guy and got everything set up. He was overly chatty and it ended up taking almost an hour and a half to get everything set up. He was nice enough to say that my modem was "bad" which waves the $50 service fee since there was a problem with my service.</p>
<p>All in all, my experience was pretty poor with Comcast. My last few interactions were plesant, but I fear they weren't the common experience for most people. I hope that I don't have to contact them in the future, but I'm sure I will to resolve my bill.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Fri, 29 Aug 2014 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-08-29:/2014/08/29/updated-comcast-is-just-awful</guid>
      <category>Tech</category>
      <category>customerservice</category>
    </item>
    <item>
      <title>Find images with No Exif Dates</title>
      <link>https://ryanmo.co/2014/08/01/find-images-with-no-exif-dates</link>
      <description><![CDATA[<p>My Dropbox folder is full of images claiming to be "missing dates." <sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup> Some of these photos were thumbnails or images from DayOne that didn't necessarily need dates, but others were real photos that for whatever reason didn't have dates that Dropbox recognized.</p>


<p><img alt="Carousel Missing Photos" src="https://ryanmo.co/2014/08/01/find-images-with-no-exif-dates/carousel_missing.png" /></p>
<p>I did some poking around, and found that there were a couple of different reasons why my photos in Dropbox weren't displaying dates:</p>
<ul>
<li>The DateTimeOriginal exif tag was missing entirely</li>
<li>The DateTimeOriginal was set to 0000:00:00 00:00:00</li>
</ul>
<p>With the magic of <a href="http://www.sno.phy.queensu.ca/~phil/exiftool/">exiftool</a>, I found a way to find all the photos in my Dropbox folder that were missing dates and output the results to a CSV.</p>
<div class="codehilite"><pre><span></span><code>exiftool -filename -r -if <span class="s1">&#39;(not $datetimeoriginal or ($datetimeoriginal eq &quot;0000:00:00 00:00:00&quot;)) and ($filetype eq &quot;JPEG&quot;)&#39;</span> -common -csv &gt; ~/Dropbox/nodates.csv
</code></pre></div>

<p>This will give you a CSV with all of the common file information for the images. </p>
<p><img alt="CSV of Missing Photos" src="https://ryanmo.co/2014/08/01/find-images-with-no-exif-dates/csv.png" /></p>
<p>At this point, you'll need to decide how you'll want to fix these photos. From what I have seen so far, the best exif tag to go on is <code>-filemodifydate</code>, but you'll probably need to figure that out on your own. If you want to fix any photo that matches the above criteria, you can do something like this</p>
<div class="codehilite"><pre><span></span><code>exiftool <span class="sb">`</span>-datetimeoriginal&lt;filemodifydate<span class="sb">`</span> -r -if <span class="s1">&#39;(not $datetimeoriginal or ($datetimeoriginal eq &quot;0000:00:00 00:00:00&quot;)) and ($filetype eq &quot;JPEG&quot;)&#39;</span> ~/Dropbox
</code></pre></div>

<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>2965 photos to be exact.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Fri, 01 Aug 2014 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-08-01:/2014/08/01/find-images-with-no-exif-dates</guid>
      <category>Tech</category>
      <category>bash</category>
      <category>scripting</category>
      <category>exiftool</category>
      <category>photos</category>
    </item>
    <item>
      <title>Log Foursquare Locations in Markdown</title>
      <link>https://ryanmo.co/2014/07/06/log-foursquare-locations-in-markdown</link>
      <description><![CDATA[<p>I've always used Foursquare as a way to remember the places I had visited while traveling. Foursquare isn't really meant to be used in this way, and as a result, they don't make it easy to answer the question, "what was that restaurant I went to last time I was here?" I'm now using IFTTT to log all my checkins to a text file in my Dropbox account.</p>


<p>I like MultiMarkdown tables. So that my Foursquare checkins looked nice, I first created a file in my Dropbox account with a heading</p>
<div class="codehilite"><pre><span></span><code><span class="p">|</span> <span class="nv">Date</span> <span class="p">|</span>  <span class="nv">VenueName</span>  <span class="p">|</span> <span class="nv">VenueUrl</span> <span class="p">|</span> <span class="nv">Shout</span> <span class="p">|</span> <span class="nv">MapURL</span> <span class="p">|</span>  <span class="nv">City</span> <span class="p">|</span> <span class="nv">State</span> <span class="p">|</span> <span class="nv">Country</span> <span class="p">|</span>
<span class="p">|</span> <span class="p">:-</span><span class="s s-Atom">--:</span> <span class="p">|</span> <span class="p">:-</span><span class="s s-Atom">--:</span> <span class="p">|</span> <span class="p">:-</span><span class="s s-Atom">--:</span> <span class="p">|</span> <span class="p">:-</span><span class="s s-Atom">--:</span> <span class="p">|</span> <span class="p">:-</span><span class="s s-Atom">--:</span> <span class="p">|</span> <span class="p">:-</span><span class="s s-Atom">--:</span> <span class="p">|</span>
</code></pre></div>

<p>In IFTTT, I then created a recipe which matches my table headers</p>
<p><center><a href="https://ifttt.com/view_embed_recipe/187719-share-foursquare-checkins-in-mamarkdown-table" target = "_blank" class="embed_recipe embed_recipe-l_45" id= "embed_recipe-187719"><img src= 'https://ifttt.com/recipe_embed_img/187719' alt="IFTTT Recipe: Share Foursquare checkins in mamarkdown table connects foursquare to dropbox" width="370px" style="max-width:100%"/></a><script async type="text/javascript" src= "https://ryanmo.co//ifttt.com/assets/embed_recipe.js"></script></center></p>
<p><img alt="IFTTT Content" src="https://ryanmo.co/2014/07/06/log-foursquare-locations-in-markdown/content.png" /></p>
<p>You may have noticed that I added an additional "Address" column that isn't getting filled out. IFTTT doesn't explicitly give the address of the venue you visited. However, the link to the Google Maps image contains GPS coordinates that I can use. Dr. Drang's <a href="http://www.leancrew.com/all-this/2014/07/extracting-coordinates-from-apple-maps/" title="Extracting coordinates from Apple Maps - All this">post</a> gave me the idea to parse out the coordinates and then use them how I'd like. This script, which I'm using with Hazel each time the file is updated, reverse geolocates the coordinates and returns the full address using OpenStreetMap. After that, it appends that address to each line in the markdown file.</p>
<div class="codehilite"><pre><span></span><code><span class="ch">#!/bin/bash</span>

<span class="nv">FILE</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$HOME</span><span class="s2">/Dropbox/IFTTT/foursquare/foursquare.txt&quot;</span>

<span class="nv">START</span><span class="o">=</span><span class="m">1</span>
<span class="nv">index</span><span class="o">=</span><span class="nv">$START</span>
<span class="nv">IFS</span><span class="o">=</span><span class="s1">$&#39;\n&#39;</span>     <span class="c1"># new field separator, the end of line</span>
<span class="k">for</span> line <span class="k">in</span> <span class="k">$(</span>cat <span class="nv">$FILE</span><span class="k">)</span>
<span class="k">do</span>
    <span class="nv">mapsurl</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$line</span> <span class="p">|</span> sed -n <span class="s1">&#39;s/.*(\(http.*\)).*/\1/p&#39;</span><span class="k">)</span><span class="p">;</span>

    <span class="nv">existingaddress</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$line</span> <span class="p">|</span> grep -E <span class="s1">&#39;^.*\(http.*\)(.*\|){2,}$&#39;</span><span class="k">)</span><span class="p">;</span>

    <span class="k">if</span> <span class="o">[[</span> ! <span class="nv">$mapsurl</span> <span class="o">||</span> <span class="nv">$existingaddress</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
        <span class="o">((</span> <span class="nv">index</span> <span class="o">=</span> index + <span class="m">1</span> <span class="o">))</span>
        <span class="k">continue</span>
    <span class="k">fi</span>

    <span class="nv">coords</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$mapsurl</span> <span class="p">|</span> sed -E <span class="s1">&#39;s/^.+\?center=([0-9.,-]+).+/\1/&#39;</span><span class="k">)</span><span class="p">;</span>
    <span class="nv">lat</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$coords</span> <span class="p">|</span> cut -f1 -d,<span class="k">)</span>
    <span class="nv">long</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$coords</span> <span class="p">|</span> cut -f2 -d,<span class="k">)</span>

    <span class="nv">address</span><span class="o">=</span><span class="k">$(</span>curl -s <span class="s2">&quot;http://nominatim.openstreetmap.org/reverse?format=json&amp;lat=</span><span class="si">${</span><span class="nv">lat</span><span class="si">}</span><span class="s2">&amp;lon=</span><span class="si">${</span><span class="nv">long</span><span class="si">}</span><span class="s2">&amp;zoom=18&amp;addressdetails=1&quot;</span><span class="k">)</span>

    <span class="nv">country</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$address</span> <span class="p">|</span> sed -e <span class="s1">&#39;s/.*\(&quot;country&quot;:&quot;.*&quot;\),.*/\1/&#39;</span> <span class="p">|</span> awk -F<span class="s1">&#39;&quot;&#39;</span> <span class="s1">&#39;/country/ {print $4}&#39;</span><span class="k">)</span>
    <span class="nv">city</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$address</span> <span class="p">|</span> sed -e <span class="s1">&#39;s/.*\(&quot;city&quot;:&quot;.*&quot;\),.*/\1/&#39;</span> <span class="p">|</span> awk -F<span class="s1">&#39;&quot;&#39;</span> <span class="s1">&#39;/city/ {print $4}&#39;</span><span class="k">)</span>
    <span class="nv">state</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$address</span> <span class="p">|</span> grep <span class="s2">&quot;\bstate\b&quot;</span> <span class="p">|</span> sed -e <span class="s1">&#39;s/.*\(&quot;state&quot;:&quot;.*&quot;\),.*/\1/&#39;</span> <span class="p">|</span> awk -F<span class="s1">&#39;&quot;&#39;</span> <span class="s1">&#39;/state/ {print $4}&#39;</span><span class="k">)</span>

    <span class="c1"># update the line of text</span>
    sed -i <span class="s1">&#39;&#39;</span> -e <span class="s2">&quot;</span><span class="si">${</span><span class="nv">index</span><span class="si">}</span><span class="s2">s/\(.*\)/\1 </span><span class="nv">$city</span><span class="s2"> | </span><span class="nv">$state</span><span class="s2"> | </span><span class="nv">$country</span><span class="s2"> |/&quot;</span> <span class="s2">&quot;</span><span class="nv">$FILE</span><span class="s2">&quot;</span><span class="p">;</span>
    <span class="o">((</span> <span class="nv">index</span> <span class="o">=</span> index + <span class="m">1</span> <span class="o">))</span>
<span class="k">done</span>
</code></pre></div>

<p>In the end, the table then ends up looking something like this:</p>
<table>
<thead>
<tr>
<th align="center">Date</th>
<th align="center">VenueName</th>
<th align="center">VenueUrl</th>
<th align="center">Shout</th>
<th align="center">MapURL</th>
<th align="center">City</th>
<th align="center">State</th>
<th align="center">Country</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">July 06, 2014 at 07:07PM</td>
<td align="center">Third Floor Espresso (3FE)</td>
<td align="center">http://4sq.com/rtEJWP</td>
<td align="center"></td>
<td align="center"><a href="http://maps.google.com/maps/api/staticmap?center=53.33998,-6.242084&amp;zoom=16&amp;size=710x440&amp;maptype=roadmap&amp;sensor=false&amp;markers=color:red%7C53.33998,-6.242084">Map Link</a></td>
<td align="center">Dublin</td>
<td align="center"></td>
<td align="center">Republic of Ireland</td>
</tr>
<tr>
<td align="center">July 06, 2013 at 10:00AM</td>
<td align="center">Wooly Pig Cafe</td>
<td align="center">http://4sq.com/1n5Scct</td>
<td align="center"></td>
<td align="center"><a href="http://maps.google.com/maps/api/staticmap?center=37.76522,-122.460266&amp;zoom=16&amp;size=710x440&amp;maptype=roadmap&amp;sensor=false&amp;markers=color:red%7C53.33842395309077,-6.234097712535167">Map Link</a></td>
<td align="center">San Francisco</td>
<td align="center">California</td>
<td align="center">United States of America</td>
</tr>
</tbody>
</table>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 06 Jul 2014 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-07-06:/2014/07/06/log-foursquare-locations-in-markdown</guid>
      <category>Tech</category>
      <category>markdown</category>
      <category>scripting</category>
      <category>bash</category>
      <category>ifttt</category>
    </item>
    <item>
      <title>Show Time in Multiple Time Zones with TextExpander</title>
      <link>https://ryanmo.co/2014/05/10/show-time-in-multiple-time-zones-with-textexpander</link>
      <description><![CDATA[<p>I'm really bad at converting a time to other timezones. Now that the company I work for has offices in multiple countries, scheduling has become much more difficult. In an effort to eliminate the need for people to convert times themselves, I wrote a TextExpander snippet to take care of it for me.</p>


<p>There are tons of tools out there that show you what time it is in other parts of the world. One thing that isn't as readily available is a quick way to tell me what time it would be in California if it's 3:00pm in Dublin. I decided to write a quick TextExpander snippet that would let me pick the time and then it would output the time in all of my chosen time zones. </p>
<p>The first step is to choose the time zones that you want to appear. In my case, I chose the following since we have offices in these locations:</p>
<ul>
<li>Europe/Dublin</li>
<li>America/Los_Angeles</li>
<li>America/Chicago</li>
</ul>
<p>Now I need to convert a chosen time to all of these time zones. This can be done using the <code>date</code> command in bash. Here's a quick example to try in the Terminal:</p>
<div class="codehilite"><pre><span></span><code><span class="err">TZ=Europe/Dublin date -jf &quot;%H:%M %z&quot; &quot;$(date &quot;+%H:%M %z&quot;)&quot; &quot;+%H:%M %Z&quot;</span>
</code></pre></div>

<ul>
<li>TZ lets you choose the time zone for the <code>date</code> command</li>
<li>-f tells <code>date</code> the format to expect for the input</li>
<li>-j tells <code>date</code> to not change the date allowing the -f flag to convert a time</li>
<li>"$(date "+%H:%M %z")" just gives the current date that looks like HH:MM +0100</li>
<li>"+%H:%M %Z" is the output format</li>
</ul>
<p>This gives you the following result:</p>
<p>04:52 IST</p>
<p>Now to do this for multiple time zones:</p>
<div class="codehilite"><pre><span></span><code><span class="nv">timezones</span><span class="o">=(</span> <span class="s2">&quot;America/Los_Angeles&quot;</span> <span class="s2">&quot;America/Chicago&quot;</span> <span class="s2">&quot;Europe/Dublin&quot;</span><span class="o">)</span>

<span class="k">for</span> zone <span class="k">in</span> <span class="si">${</span><span class="nv">timezones</span><span class="p">[@]</span><span class="si">}</span>
<span class="k">do</span>
    <span class="nv">TZ</span><span class="o">=</span><span class="nv">$zone</span> date -jf <span class="s2">&quot;%H:%M %z&quot;</span> <span class="s2">&quot;</span><span class="k">$(</span>date <span class="s2">&quot;+%H:%M %z&quot;</span><span class="k">)</span><span class="s2">&quot;</span> <span class="s2">&quot;+%H:%M %Z&quot;</span><span class="p">;</span>
<span class="k">done</span>
</code></pre></div>

<p>Which gives:</p>
<p>08:55 PDT<br />
10:55 CDT<br />
16:55 IST  </p>
<p>Lastly, let's add in some TextExpander input methods, and we have a way to use this with whatever time we want:</p>
<div class="codehilite"><pre><span></span><code><span class="ch">#! /bin/bash</span>

/* 
Enter a <span class="nb">time</span> using 24H. <span class="m">1</span>:30pm is <span class="m">13</span>:30
*/
<span class="nv">ENTERTIME</span><span class="o">=</span><span class="s2">&quot;%filltext:name=Hour:width=2%:%filltext:name=Minute:width=2%&quot;</span>

<span class="nv">timezones</span><span class="o">=(</span> <span class="s2">&quot;America/Los_Angeles&quot;</span> <span class="s2">&quot;America/Chicago&quot;</span> <span class="s2">&quot;Europe/Dublin&quot;</span> <span class="o">)</span>

<span class="k">for</span> zone <span class="k">in</span> <span class="si">${</span><span class="nv">timezones</span><span class="p">[@]</span><span class="si">}</span>
<span class="k">do</span>
        <span class="nv">TZ</span><span class="o">=</span><span class="nv">$zone</span> date -jf <span class="s2">&quot;%H:%M %z&quot;</span> <span class="s2">&quot;</span><span class="nv">$ENTERTIME</span><span class="s2"> </span><span class="k">$(</span>date <span class="s2">&quot;+%z&quot;</span><span class="k">)</span><span class="s2">&quot;</span> <span class="s2">&quot;+%H:%M %Z&quot;</span><span class="p">;</span>
<span class="k">done</span>
</code></pre></div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 10 May 2014 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-05-10:/2014/05/10/show-time-in-multiple-time-zones-with-textexpander</guid>
      <category>Tech</category>
      <category>bash</category>
      <category>efficiency</category>
      <category>scripting</category>
      <category>textexpander</category>
    </item>
    <item>
      <title>100 Happy Days</title>
      <link>https://ryanmo.co/2014/04/06/100-happy-days</link>
      <description><![CDATA[<p>A few people from work convinced me to participate in <a href="http://100happydays.com">100 Happy Days</a>. Since I'm already doing a "selfie a day" so I figured adding one more photo a day wouldn't hurt. What I didn't want to do is post to the various social media sites every single day and spam all my followers. Hazel and my blog helped me solve this problem.</p>


<h3 id="hazel">Hazel</h3>
<p>Similar to my <a href="https://ryanmo.co{static}../2014-01-05/2014-01-05_Organizing-Special-Photo-with-Hazel.md">previous post</a>, I'm using Hazel to detect special types of photos. I decided for 100 Happy Days I would always take the photos using the default Camera in square mode.</p>
<p><img alt="1 Happy Day of Coffee" src="https://ryanmo.co/posts/Tech/2014-04-06/3.jpg" /></p>
<p>Hazel makes this really simple. Each time a photo that matches the criteria comes into my Dropbox Camera Uploads folder, it gets resorted and renamed to YYYY-mm-dd.jpg.</p>
<p><img alt="Hazel Rule for Photos" src="https://ryanmo.co/posts/Tech/2014-04-06/hazel1.png" /></p>
<p>This simply takes care of the photos themselves. But now I want them to also appear on my blog. I have a separate rule that watches this new folder of photos and moves them into my Pelican project folder.</p>
<p><img alt="Hazel Rule for Pelican" src="https://ryanmo.co/posts/Tech/2014-04-06/hazel2.png" /></p>
<p>The key to this one is that I name them with sequential numbers, starting with 1.jpg. This will be useful later for my blog.</p>
<h3 id="pelican-blog">Pelican Blog</h3>
<p>I decided to set up a hidden page on my blog to host these images. I created a custom template since it's fairly unique and different from the rest of my blog. The meat of the template is just this:</p>
<div class="codehilite"><pre><span></span><code><span class="p">&lt;</span><span class="nt">article</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">h3</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&quot;{{ SITEURL }}/{{ page.url }}&quot;</span><span class="p">&gt;</span>{{ page.title }}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&quot;two-columns&quot;</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;grid-container&quot;</span> <span class="na">style</span><span class="o">=</span><span class="s">&quot;display:block;&quot;</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;rig columns-2&quot;</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">article</span><span class="p">&gt;</span>
</code></pre></div>

<p>I'm using the CSS for the gallery from <a href="http://alijafarian.com/responsive-image-grids-using-css/">this</a> post by Ali Jafarian.</p>
<p>This is where my Hazel photo naming comes in handy. I'm using a simply JavaScript function to embed these images on page load.</p>
<div class="codehilite"><pre><span></span><code><span class="kd">function</span> <span class="nx">createImages</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">start_date</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="mf">2014</span><span class="p">,</span> <span class="mo">03</span><span class="p">,</span> <span class="mo">03</span><span class="p">)</span> <span class="c1">// April 3, 2014</span>
    <span class="nx">days_passed</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">((</span><span class="k">new</span> <span class="nb">Date</span><span class="p">()</span> <span class="o">-</span> <span class="nx">start_date</span><span class="p">)</span> <span class="o">/</span> <span class="mf">1000</span> <span class="o">/</span> <span class="mf">86400</span><span class="p">);</span>
    <span class="nx">extension</span> <span class="o">=</span> <span class="s1">&#39;.jpg&#39;</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mf">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;=</span> <span class="nx">days_passed</span> <span class="o">+</span> <span class="mf">1</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">var</span> <span class="nx">li</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;li&#39;</span><span class="p">);</span>
        <span class="kd">var</span> <span class="nx">img</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;img&#39;</span><span class="p">);</span>
        <span class="nx">img</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="s1">&#39;/images/100daysofhappiness/&#39;</span> <span class="o">+</span> <span class="nx">i</span> <span class="o">+</span> <span class="nx">extension</span><span class="p">;</span>
        <span class="nx">img</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s2">&quot;onError&quot;</span><span class="p">,</span> <span class="s2">&quot;this.onerror=null;this.src=&#39;/images/imagenotfound.jpg&#39;&quot;</span><span class="p">);</span>
        <span class="kd">var</span> <span class="nx">h3</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;h3&#39;</span><span class="p">);</span>
        <span class="nx">h3</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="s2">&quot;Day &quot;</span> <span class="o">+</span> <span class="nx">i</span><span class="p">;</span>
        <span class="nx">li</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
        <span class="nx">li</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">h3</span><span class="p">);</span>
        <span class="nx">jQuery</span><span class="p">(</span><span class="s1">&#39;.rig&#39;</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="nx">li</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nx">jQuery</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">ready</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">createImages</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div>

<p>I can easily compute the number of days that have passed and safely assume that an image exists for each of those days. I learned today that if you add the attribute <code>onError</code> to an image, you can create a fallback image in case the real image source doesn't exist.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 06 Apr 2014 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-04-06:/2014/04/06/100-happy-days</guid>
      <category>Tech</category>
      <category>hazel</category>
      <category>pelican</category>
      <category>photos</category>
    </item>
    <item>
      <title>Quick Sharing with Launch Center Pro and Dropbox</title>
      <link>https://ryanmo.co/2014/03/04/quick-sharing-with-launch-center-pro-and-dropbox</link>
      <description><![CDATA[<p>I've been finding more and more reasons to use Launch Center Pro recently. With the fairly recent addition of Dropbox actions, I've been finding new ways to share links quickly. </p>


<p><img alt="Launch Center Pro and Dropbox" src="https://ryanmo.co/2014/03/04/quick-sharing-with-launch-center-pro-and-dropbox/lcp_dropbox.png" /></p>
<p>I take a lot of quick photos that I never plan to keep around. In most cases, it's just to send to someone quickly. iMessage is easy, but the images aren't compressed nearly enough and can take a while to upload. I've now started uploading the images to Dropbox and sharing the link. The upload speed is reduced since Launch Center Pro will take care of reducing the quality before uploading. The message sends almost instantly because there isn't an attachment. Here are a few of workflows I use with Dropbox:</p>
<h3 id="upload-last-photo-taken-and-get-the-link">Upload last photo taken and get the link</h3>
<p><em>This is if I simply need a quick link to share anywhere. The image uploads at 50% quality. I have a folder called Temp/_Destrctable Folder where I keep all my throwaway images. I'm using the TextExpander snippet ..ttimestamp to name the files like 14-03-08-19.42.45.jpg</em></p>
<div class="codehilite"><pre><span></span><code><span class="c">launchpro-dropbox://addlastphoto?path=/Temp/_Destructable Folder&amp;name=&lt;..ttimestamp&gt;.jpg&amp;quality=50&amp;getlink=1</span>
</code></pre></div>

<h3 id="upload-last-photo-and-put-the-link-in-an-in-app-message-body">Upload last photo and put the link in an in-app message body</h3>
<p><em>Quick sharing with iMessage. Settings are the same as above.</em> </p>
<div class="codehilite"><pre><span></span><code><span class="c">launch://x-callback-url/dropbox/addphoto?attach=photo&amp;path=/Temp/_Destructable Folder&amp;name=&lt;..ttimestamp&gt;.jpg&amp;quality=50&amp;getlink=1&amp;x-success=launch%3A//messaging%3Fbody%3D%5Bclipboard%5D</span>
</code></pre></div>

<h3 id="upload-from-any-source-to-dropbox">Upload from any source to Dropbox</h3>
<p><em>Nice if you haven't taken the photo yet</em></p>
<div class="codehilite"><pre><span></span><code><span class="c">launch://dropbox/addphoto?attach=photo&amp;path=&amp;name=&amp;quality=&amp;getlink=1</span>
</code></pre></div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Tue, 04 Mar 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-03-04:/2014/03/04/quick-sharing-with-launch-center-pro-and-dropbox</guid>
      <category>Tech</category>
      <category>ios</category>
      <category>efficiency</category>
      <category>Dropbox</category>
      <category>launchcenterpro</category>
      <category>photos</category>
    </item>
    <item>
      <title>Digitizing the Family Photos</title>
      <link>https://ryanmo.co/2014/03/01/digitizing-the-family-photos</link>
      <description><![CDATA[<p>I had this ongoing fear that all of our family photos would get lost or destroyed. I've always wanted to have a central place for all of my photos, both past and present. In early 2012, my mom and I started on a project to scan, crop and organize all of our old photos from negatives.</p>


<h1 id="going-digital">Going Digital</h1>
<p>I was fortunate that when I decided to take on this project, my mom already had two large Epson flatbed scanners with transparency adapters. What was even better was that my mom was highly organized over the years and archived all of the negatives of every photo she had ever taken. This only left us with one thing to do: scan the photos.  Since I was living in California and my mom in Montana, the work of pulling the negatives and scanning them was going to be done by my mom. We also needed to have a way to transfer the files from her computer to mine. Dropbox was an obvious choice in this case, but there was one problem that would complicate everything: hard drive space. </p>
<p>My mom was still using her PowerMac G5 at the time and hard drive space was pretty limited. It wouldn't have taken long before she wouldn't have enough space to even scan the photos. As a simple solution, once the scans were uploaded and synced to my computer, I could have manually removed the files and place them on my own computer, but I was lazy and didn't want to check constantly whether new files had been added. At the time, I was just learning how to code and thought this would be good practice <sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>. I ended up writing a script that would mirror the folder structure for the scans in our Dropbox shared folder on my local Desktop and then remove the original file in Dropbox. The old folder structure was maintained so that if any new files were added to the same folder, my mom wouldn't have to recreate them. I then set this up as a cron job to run once a day and then send me an email digest of all the files that were transferred.</p>
<p>After a couple of weeks, I had nearly 15 years of photos in folders organized by year totaling around 85GB. Each Photoshop file was around 1.GB each and the photos were scanned at 300dpi. Now the hardest and longest part of the project was about to begin.</p>
<p><img alt="Folder Structure" src="https://ryanmo.co/2014/03/01/digitizing-the-family-photos/folders.png" /></p>
<h1 id="cropping-resizing-and-renaming">Cropping, Resizing, and Renaming</h1>
<p>I wasn't entirely sure how I was going to do this part efficiently. My mom hadn't laid out the photos in a symmetric grid and there wasn't a reliable way to detect photo borders. I also decided beforehand that I wanted to preserve the original files and so I would save an individual Photoshop file for each photo that was cropped. I then wanted to have a separate folder that was simply for viewing the files.</p>
<p>Starting off, I wanted to try manually doing everything and automate things over time. Cropping the files using the marquee tool was always going to be manual. I would select the file, copy it, create a new file with the dimensions from the clipboard and then paste the photo into the new file. After I had gone through the entire file, I would save all the files at once with random names (you'll see later why the naming here didn't matter). This part immediately became tedious. I did some research on how I could make this easier or faster and discovered Photoshop actions<sup id="fnref:2"><a class="footnote-ref" href="https://ryanmo.co#fn:2">2</a></sup>. What was great about this was I was able to record every step I was taking into one single keyboard shortcut. This broke down the process to simply selecting the photo and hitting shift-F1. This one keyboard shortcut took care of copying the file, creating a new file with the dimensions of the clipboard, pasting and then finally selecting the previous file. That last step was key. Instead of a final control-tab to move back to the original file, the action took care of it for me. You can download the Photoshop action <a href="https://ryanmo.co/2014/03/01/digitizing-the-family-photos/Scans.atn">here</a>.</p>
<p><img alt="Actions Screenshot" src="https://ryanmo.co/2014/03/01/digitizing-the-family-photos/actions_screenshot.png" /></p>
<p>At this point, I had an original Photoshop file and a folder called Cropped where all the new photos lived. I now needed a way to rename these files to something meaningful. Automator and Alfred made this simple. After I finished cropping, I would select all of the newly created files, run my Alfred extension "Rename Scans" which would trigger an automator script, prompt me to name the files, and then each file would be renamed from something like Untitled1.psd to November 1987_1.psd.</p>
<p>At this point, the final step for each of these files was to create a viewable JPG for every photo. Turns out, Photoshop has a great feature called Image Processor. After the files were neatly renamed, I would open up the Image Processor, select the folder, and hit go. My settings were always saved so there wasn't much else to be done each time I ran this. I would take the Photoshop files, create a new JPG at 5 quality in a new folder called Low Res Images with the same naming convention. </p>
<p><img alt="Image Processor" src="https://ryanmo.co/2014/03/01/digitizing-the-family-photos/image_processor.png" /></p>
<p>I was then able to share this folder back with my mom and the rest of my family. They enjoyed watching the photos get added over the last year or so as I casually worked on the files.</p>
<p>Once I had done all of the steps for each file, I would move the folder of PSD files into a folder called Done. This simply gave me a better idea of how many folders I had left to work on.</p>
<h1 id="viewing">Viewing</h1>
<p>In late January 2014, I finally finished cropping all of the photos. I never intended on it taking quite this long, but it was never something that needed to have a deadline. It felt great to know that I was finally done and could just sit back and look at all of the old photos from what I was little. I was using <a href="http://www.lynapp.com">Lyn</a> to view all the photos and realized that something was a little off. All of the photos were out of order. All of the folders had been named as Month Year, and even if I was viewing all of the photos at the same time, they were sorted in the order that I had created the files, not the time they were actually taken. I couldn't sort them in a photo viewer, Dropbox's photo tab would sort them by file creation and not EXIF date taken, and using Spotlight search was more-or-less pointless. There was no way  that I was going to manually date 3,300 photos by hand. I had used the command line tool <a href="http://www.sno.phy.queensu.ca/~phil/exiftool/">exiftools</a> a few times, and I started looking into whether this would be a possibility for dating the files. It turned out that the command was really straightforward for naming a folder</p>
<div class="codehilite"><pre><span></span><code><span class="n">exiftool</span> <span class="s2">&quot;-AllDates=1999:12:31 12:00:00&quot;</span> <span class="n">foldername</span><span class="o">/</span>
</code></pre></div>

<p>Even though I could have done it by hand, I didn't really want to have to type this in for 80 or so folders of photos. I quickly wrote up a Python script that would parse out the date from the folder names and prompt me to confirm whether this was correct or not. I was fine hitting Enter 80 times. </p>
<p>Some of the folders were called things like January-March 1995. For these cases, I would just assume the first month for the date. I wasn't going for perfection, but rather a good estimate for the time the photos were taken.</p>
<p>You can take a look at the script <a href="https://ryanmo.co/2014/03/01/digitizing-the-family-photos/convert_exif_dates.py">here</a>. Do note that the script is really specific to my folder structure so it might not work perfectly for you, but it'll be a good start if you need to do something like this.</p>
<p><img alt="Lyn App" src="https://ryanmo.co/2014/03/01/digitizing-the-family-photos/lynapp.png" /></p>
<h1 id="what-i-learned">What I Learned</h1>
<p>Epson now makes a <a href="http://www.epson.com/cgi-bin/Store/jsp/Product.do?BV_UseBVCookie=yes&amp;sku=B11B178061">scanner</a> that eliminates a lot of the hard work around cropping the photos. It's expensive, but it would have saved me a lot of work.</p>
<p>I've made this comment before, but I still would love to have a way to embed facial recognition into the metadata of photos. My perfect world would be having the ability to do something like search for all of the photos of my brother before 1995.</p>
<p>Ultimately, this was a long, but satisfying project. I sleep better at night knowing that all of our family photos are backed up and not be lost forever if there were ever to be a disaster.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>For those who want to see the script, <a href="https://ryanmo.co/2014/03/01/digitizing-the-family-photos/movescans.py">here</a> it is. Please don't judge me. This was actually one of my first real scripts I had ever written. I know there are better ways to do a lot of it.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
<li id="fn:2">
<p>I'm a total Photoshop newb&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:2" title="Jump back to footnote 2 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 01 Mar 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-03-01:/2014/03/01/digitizing-the-family-photos</guid>
      <category>Tech</category>
      <category>python</category>
      <category>alfred</category>
      <category>hazel</category>
      <category>dropbox</category>
      <category>bash</category>
      <category>photos</category>
    </item>
    <item>
      <title>Airport Codes with Alfred</title>
      <link>https://ryanmo.co/2014/01/18/airport-codes-with-alfred</link>
      <description><![CDATA[<p>Here's a quick Alfred workflow to get the airport code for a given city or the city based on an airport code.</p>


<p><img alt="Alfred Search" src="https://ryanmo.co/2014/01/18/airport-codes-with-alfred/dub.png" /></p>
<p>You can search either by the 3-character airport code or by the city name. You can make your search specific enough to return one result, such as "dublin, ireland"</p>
<p><img alt="Growl" src="https://ryanmo.co/2014/01/18/airport-codes-with-alfred/growl_dublin.png" /></p>
<p>or simple so that you can see multiple results, like "ireland"</p>
<p><img alt="Growl" src="https://ryanmo.co/2014/01/18/airport-codes-with-alfred/growl_ireland.png" /></p>
<p>You can download the Airport Codes workflow here:</p>
<p><a href="https://ryanmo.co/2014/01/18/airport-codes-with-alfred/Airport_Codes.alfredworkflow"><img alt="image" src="https://ryanmo.co/images/alfred_extension.jpg" /></a>  </p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 18 Jan 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-01-18:/2014/01/18/airport-codes-with-alfred</guid>
      <category>Tech</category>
      <category>alfred</category>
      <category>scripting</category>
    </item>
    <item>
      <title>Travel Notifications with Launch Center Pro and Pythonista</title>
      <link>https://ryanmo.co/2014/01/14/travel-notifications</link>
      <description><![CDATA[<p>I've been doing a lot more traveling in the last year. Each time I take off or land, I found myself sending nearly the same text message to multiple people. After a while, it began to feel more like a chore than the kind gesture of letting others know I made it safely. For the repetitive messages, I found  way of automating nearly the entire process.</p>


<p>Now that Launch Center Pro has the <code>launchpro-messaging://</code> action with x-callback-url support, I can chain SMS messages together. This is something I've been wanting for a long time for this specific use case. When I began writing the action, I found one hiccup in which is I would need to write the message to each individual person unless I wanted to first copy it to my clipboard before running the action. I didn't have luck with the <code>launchpro-clipboard://</code> action while calling the clipboard from the same action even though it is supposed to work in theory<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>.</p>
<p>I decided to venture out and use Pythonista to generate the url scheme for me and then pass it back into Launch Center Pro's in-app messaging. The nice thing here is that I can cleanly list out all of the contacts to whom I'd like to send the message from with in the script and change the action much more quickly.</p>
<div class="codehilite"><pre><span></span><code><span class="n">contacts</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s1">&#39;friend1@dropbox.com&#39;</span><span class="p">,</span>
    <span class="s1">&#39;mom@gmail.com&#39;</span><span class="p">,</span>
    <span class="s1">&#39;+1-555-867-5309&#39;</span>
<span class="p">]</span>
</code></pre></div>

<p>The script is written in such a way that I can put as many contacts in as I want and the url scheme will still get generated correctly with the url-escaping. </p>
<p>Now, I have a nice list of message options in Launch Center Pro that will then send the same message to all of my contacts:</p>
<p><img alt="Launch Center Pro notifications" src="https://ryanmo.co/2014/01/14/travel-notifications/lcp_notifications.png" /></p>
<p>If the "Just Landed" option is chosen, a new prompt will be given to type in the place where I landed. Once I'm brought back into the app, I just need to hit send for each message.</p>
<p><span style="text-align: center; display: block;"></p>
<iframe src="https://ryanmo.co//player.vimeo.com/video/84171813?title=0&amp;byline=0&amp;portrait=0" width="361" height="642" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
<p></span></p>
<p>You can download the Python script <a href="https://ryanmo.co/2014/01/14/travel-notifications/Travel_Notices.py">here</a> and the Launch Center Pro action can be found <a href="http://launchcenterpro.com/k0p747">here</a></p>
<p>[Update] 2014-02-15  </p>
<p>I made some updates to the script so that you can send custom notifications for each individual person. It also uses Pythonista's location services to automatically put in the city name to make it easier.</p>
<div class="codehilite"><pre><span></span><code><span class="n">contacts</span> <span class="o">=</span> <span class="p">[</span>
     <span class="p">{</span>
        <span class="s1">&#39;address&#39;</span><span class="p">:</span> <span class="s1">&#39;person1@gmail.com&#39;</span><span class="p">,</span>
        <span class="s1">&#39;landed&#39;</span><span class="p">:</span> <span class="s1">&#39;Hi, Mom. Just landed in &#39;</span><span class="p">,</span>
        <span class="s1">&#39;boarding&#39;</span><span class="p">:</span> <span class="s1">&#39;Boarding now!&#39;</span><span class="p">,</span>
        <span class="s1">&#39;shuttingdown&#39;</span><span class="p">:</span> <span class="s1">&#39;Shutting down. I</span><span class="se">\&#39;</span><span class="s1">ll text when I land.&#39;</span>
     <span class="p">},</span>
     <span class="p">{</span>
        <span class="s1">&#39;address&#39;</span><span class="p">:</span> <span class="s1">&#39;+1 555 867 5309&#39;</span><span class="p">,</span>
        <span class="s1">&#39;landed&#39;</span><span class="p">:</span> <span class="s1">&#39;Hi, John. Just landed in &#39;</span><span class="p">,</span>
        <span class="s1">&#39;boarding&#39;</span><span class="p">:</span> <span class="s1">&#39;Boarding now.&#39;</span><span class="p">,</span>
        <span class="s1">&#39;shuttingdown&#39;</span><span class="p">:</span> <span class="s1">&#39;Shutting down. See you soon!&#39;</span>
     <span class="p">},</span>
<span class="p">]</span>
</code></pre></div>

<p><a href="https://ryanmo.co/2014/01/14/travel-notifications/Travel_Notices2.py">Here's</a> a link to the updated version.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>The developers mention a workaround <a href="http://help.contrast.co/hc/en-us/articles/200611883-x-callback-url-Support">here</a>, but I wasn't able to get it to work.&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Tue, 14 Jan 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-01-14:/2014/01/14/travel-notifications</guid>
      <category>Tech</category>
      <category>efficiency</category>
      <category>ios</category>
      <category>scripting</category>
      <category>pythonista</category>
    </item>
    <item>
      <title>My Photo Workflow</title>
      <link>https://ryanmo.co/2014/01/11/my-photo-workflow</link>
      <description><![CDATA[<p>After listening to the Mac Power Users <a href="http://www.macpowerusers.com/2014/01/05/mac-power-users-171-photo-management/">episode</a> on photo management and reading the slew of follow up blog posts on other photo management workflows, I thought I would share mine as well.  While my workflow will be fairly similar to <a href="http://www.macstories.net/tutorials/my-photo-management-workflow-early-2014/">Federico Viticci's</a> with a few exceptions, I thought I would share the way that I take, organize, view and share my photos.</p>


<h2 id="taking-photos">Taking Photos</h2>
<p>My iPhone is one of the main ways that I take photos. Since it's always in my pocket and takes great quality photos, it's by far the easiest way to take photos no matter where I am.  I've had a lot of fun with the iPhone 5S and the burst and slo-mo modes.</p>
<p>I've never considered myself a photographer. For a long time, I had my mom's hand-me-down Olympus E-500. It was a great camera, but I had no idea how to use it and it was bigger than I preferred. Before moving to Ireland, I decided that I wanted to learn the basics of photography and have a camera that would grow with me as I learned more. The Olympus PEN E-P5 had just started pre-order and I decided that this would be my first "real" camera. I only had a few requirements, and it fortunately satisfied both of them: GPS tagging and small/lightweight. I've now had this the E-P5 for a little over 6 months and couldn't be happier. </p>
<p><img alt="Olympus PEN E-P5" src="https://ryanmo.co/2014/01/11/my-photo-workflow/ep5.jpg" /></p>
<h2 id="importing">Importing</h2>
<p>I only have one main way to upload my photos - Dropbox Camera Uploads. Whether I use the Dropbox app for iOS or the desktop application, my photos end up in the same place to get processed (more on that later in <a href="https://ryanmo.co#organization">Organization</a>). </p>
<p>Any photos that are taken on my iPhone are quickly uploaded via the Dropbox app. When I use my E-P5, I will first turn on the built-in Wifi to sync GPS data from my phone to the camera<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>. Once that is all taken care of, I plug the camera into my laptop and Dropbox grabs the new photos and imports them.</p>
<h2 id="organization">Organization</h2>
<p>I'm still pretty new to Hazel, but dealing with my photos was the reason I decided to bite the bullet and buy it. My Dropbox Camera Uploads folder was nearing 900 photos and I hadn't taken the time to organize them in over a year. </p>
<p>Before Camera Uploads, I was suffering through iPhoto. It always bothered me that my photos were obfuscated from view. I always found myself wasting time trying to find the original or using the export option. When Camera Uploads was released, I searched for a way to cleanly export my photos into a Year-Month-Event folder structure. I discovered <a href="https://github.com/BMorearty/exportiphoto">this script</a> that gave me more than what I wanted and solved my problem perfectly. For anyone who wants to use this, the command I used was</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># -x deconflict export directories of same name</span>
<span class="c1"># -d stop use date prefix in folder name</span>
<span class="c1"># -y add year directory to output</span>
python exporti_photo.py -x -d -y
</code></pre></div>

<p>I've been using the Year-Month-Event structure for a few years now and have starting running into a slight annoyance. I find myself constantly flipping between months trying to remember when a certain event happened. I finally came to the conclusion that the month directory was pretty unnecessary. What I decided on was the folder structure Year-MM.YY Event Name. </p>
<p><img alt="New Photo Structure" src="https://ryanmo.co/2014/01/11/my-photo-workflow/photo_list.png" /></p>
<p>This gives me a much easier way to visualize my photos by event name rather than poking through folders by month.</p>
<p>My Hazel workflow is fairly simple, but takes care of everything in one rule. I've set up a few exceptions for photo types that don't need to be sorted, such as screenshots or other PNG files. I also have rules set up for fun projects like my <a href="https://ryanmo.co/2014/01/05/organizing-special-photos-with-hazel">"photo a day"</a>.</p>
<p><img alt="Hazel Rule" src="https://ryanmo.co/2014/01/11/my-photo-workflow/photos_hazel.png" /></p>
<p>Finally once photos are sorted, I will manually go in and individually name all of the events that were created. This makes it much easier to search for events in the future. The next step in my process here is to tag photos. The one feature I do miss about iPhoto was the facial recognition. Since I haven't found a way to do facial recognition outside of Aperture or iPhoto, I will manually go in and tag photos with the names of those in the photos. This has been very useful when I want to find photos of people in certain contexts. For example,  the tags <code>me</code>, <code>office</code>, <code>dublin</code> will give me photos of myself in the Dublin office, but not San Francisco.</p>
<h2 id="consumption-and-sharing">Consumption and Sharing</h2>
<p>In Mac OS X, I have three ways that I view my photos. The first, and most basic is Finder.  The Cover Flow view in Finder is actually a great way to quickly go through photos and get the ones that you want. When I'm wanting to share my photos with others, I use the <a href="https://www.dropbox.com/photos">Dropbox Photos</a> page. As a quick way to share a select number of photos quickly, I've still found this to be the best way. For general viewing and pruning of photos I don't want, I've been using a not so well known app called <a href="http://www.lynapp.com">Lyn</a>. It has some nice features for sharing to multiple services, but what I really like about it is that it'll just watch a folder and display the photos in that folder. Lyn will also let me see all of the metadata about the photos, including a map if there is GPS information. Lastly, on the rare occasion that I want to edit my photo, I will import the photo into Aperture. For the same reasons I dislike iPhoto, I dislike Aperture. I will typically import the photo, edit it, and then export back into Dropbox.</p>
<p><img alt="Lyn.app" src="https://ryanmo.co/2014/01/11/my-photo-workflow/lyn.jpg" /></p>
<p>On iOS, I have two primary apps that I use to view my photos. The first, unsurprisingly, is the Dropbox app. For quick viewing and sharing, I will use Dropbox since that's where all of my photos live. As a Photos app replacement, I use <a href="http://unboundapp.com">Unbound</a>. What's great about Unbound is that it treats folders in your Dropbox account like albums. Since my photos are organized this way anyways, I get perfectly created albums that I can view and even cache to my phone for offline viewing.</p>
<p><img alt="Dropbox and Unbound" src="https://ryanmo.co/2014/01/11/my-photo-workflow/unbound_dropbox.jpg" /></p>
<h2 id="the-future">The Future</h2>
<p>Dropbox has been doing a great job improving the photo experience. Photo organization is a very personal thing and trying to solve this for the majority is not an easy task. Many companies are trying to do this, and so far there has been no clear winner. As much as I love my folder organization, I would really like to get to a point where I don't even have to worry about where my photos are. The metadata of the photos should be enough for an application or website to organize the photos for me.</p>
<p>I mentioned this earlier, but one other thing I would really like to see is a 3rd-party app that does facial recognition and applies tags or some other bit of metadata to the file. Tagging my photos with peoples' names is by far the most manual part of my photos workflow, but also one of the most important to me.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>The GPS data is stored on the SD card, but I haven't taken the time to see if I can add this metadata after importing from Dropbox&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 11 Jan 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-01-11:/2014/01/11/my-photo-workflow</guid>
      <category>Tech</category>
      <category>hazel</category>
      <category>efficiency</category>
      <category>dropbox</category>
      <category>ios</category>
      <category>photos</category>
    </item>
    <item>
      <title>Organizing Special Photos with Hazel</title>
      <link>https://ryanmo.co/2014/01/05/organizing-special-photos-with-hazel</link>
      <description><![CDATA[<p>Nearly all of my photos are sorted based on year, month and day. Hazel easily takes care of of this for me, but occasionally I will have projects where photos need to be excluded or organized in a different way. With Hazel, I can still account for these special cases with extra bits of metadata.</p>


<p>This may not come in handy to anyone, but I thought it would be worth showing some of the creative ways that Hazel can be used to organize your files based on more than just creation/modification time or file type.</p>
<p>In late May 2013, I decided I wanted to do one of those time lapse videos where you take a picture of yourself in front of the camera every day. At first, the hardest part was just remembering to take the picture each day. Once I was in the routine, I started to find the task monotonous to pull the photo from my Dropbox folder, rename it to YYYY-MM-DD.jpg and then move it into a special folder I had creatively named "Picture a Day." Hazel was already taking care of my general photo organization, but I wanted to ensure that these photos got organized specifically so I started digging into the special traits of these photos. I quickly found a few default options in Hazel that would help me do this:</p>
<ul>
<li>Device make</li>
<li>Pixel width/height</li>
<li>Content creator</li>
</ul>
<p>I was always using Camera+ for these photos because of the grid and level features. It allowed me to align my face in the same place in the photos. Since I always used the front camera, the dimensions of the photos remained the same. After playing around, here is the Hazel rule I came up with</p>
<p><img alt="Hazel Picture a Day" src="https://ryanmo.co/2014/01/05/organizing-special-photos-with-hazel/hazel_picture_a_day.png" /></p>
<p>Another key piece here is the datestamp token. The rule watches for Dropbox's Camera Uploads filename format YYYY-MM-DD HH.MM.SS.jpg. This wouldn't be necessary except for that this token then becomes useful in the actions portion. I can take that token and rename the file based on the token to simply YYYY-MM-DD.jpg since I don't care about the hour the photo was taken. What's great about the token is that this will prevent accidental naming of the file if I happen to upload it the next day or I'm flying between Ireland and the US and date times get messed up.</p>
<p>While this rule is fairly specific, it's saved me a lot of time having to organize the photos manually. </p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 05 Jan 2014 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2014-01-05:/2014/01/05/organizing-special-photos-with-hazel</guid>
      <category>Tech</category>
      <category>hazel</category>
      <category>efficiency</category>
      <category>photos</category>
    </item>
    <item>
      <title>Exploring Pelican: Automation Part 1</title>
      <link>https://ryanmo.co/2013/12/29/exploring-pelican-automation</link>
      <description><![CDATA[<p>It's been a few months now since I switched from <a href="http://mynt.mirroredwhite.com">Mynt</a> to <a href="http://blog.getpelican.com">Pelican</a> as my static blog generator and so far I've been very happy with the switch. It's been a learning process along the way, but I've come to the point where I'm comfortable enough with it and want to start customizing and automating.</p>
<h2 id="customization">Customization</h2>
<p>I haven't done much yet in terms of customization quite yet, but I'm adding little bits every day.  </p>
<h3 id="original-files">Original Files</h3>
<p>I recently updated Pelican to the newest version 3.3. The part that was new to me here was that you have the option to keep the original file in your output directory.</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the specified OUTPUT_PATH.</span>
<span class="n">OUTPUT_SOURCES</span> <span class="o">=</span> <span class="kc">True</span>
</code></pre></div>

<p><strong>Update 2014-02-25:</strong> Turns out this was a bug. It's been since fixed. See the thread <a href="https://github.com/getpelican/pelican/pull/1183">here</a> on github. </p>
<p>I'm not entirely sure if it's a bug or something I was doing wrong, but I noticed that instead of creating an index.txt for every index.md file, it would create a directory called index.txt and then place the original markdown file within it. I did some poking around in the source code and found a slight issue with the <code>copy</code> function within the util.py file. It was checking if any destination existed, and if not, it would create a new directory.</p>
<div class="codehilite"><pre><span></span><code><span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">destination_</span><span class="p">):</span>
    <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">destination_</span><span class="p">)</span>
</code></pre></div>

<p>I made a couple of changes to prevent this from happening. The first was that I added an additional argument to the function called <code>is_file</code> and then added this to the destination check</p>
<div class="codehilite"><pre><span></span><code><span class="k">def</span> <span class="nf">copy</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">source</span><span class="p">,</span> <span class="n">destination</span><span class="p">,</span> <span class="n">destination_path</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">is_file</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">destination_</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">is_file</span><span class="p">:</span>
    <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">destination_</span><span class="p">)</span>
</code></pre></div>

<p>Finally, in generators.py, I added the argument where the copy function is called in <code>_create_source</code> in the <code>SourceFileGenerator</code> class.</p>
<div class="codehilite"><pre><span></span><code><span class="n">copy</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">obj</span><span class="o">.</span><span class="n">source_path</span><span class="p">,</span> <span class="n">dest</span><span class="p">,</span> <span class="n">is_file</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</code></pre></div>

<p>Now that the files are being generated correctly, I used the tip by Gabe Weatherhead over at <a href="http://www.macdrifter.com/tag/pelican.html">Macdrifter</a> to add a link to the original file for every post. You can see an example of this post at the bottom of the page.</p>
<h3 id="automatic-posting-to-appnet">Automatic Posting to App.net</h3>
<p>App.net's new Broadcast platform is pretty cool. I've subscribed to a few people already and I like the idea of having a way to broadcast each post that's made. Pelican doesn't have a great way to detect new posts, so I'm playing with my own solution by keeping track of every post and comparing.</p>
<p>In my Fabric file, I created a function to check for new posts and then use the App.net Broadcast API to make a post</p>
<div class="codehilite"><pre><span></span><code><span class="k">def</span> <span class="nf">adn</span><span class="p">():</span>
    <span class="n">current_posts</span> <span class="o">=</span> <span class="n">util</span><span class="o">.</span><span class="n">current_posts</span><span class="p">()</span>
    <span class="n">post_history</span> <span class="o">=</span> <span class="n">pickler</span><span class="o">.</span><span class="n">load_old_results</span><span class="p">(</span><span class="s1">&#39;lib/posts.pkl&#39;</span><span class="p">)</span>
    <span class="n">new_posts</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">current_posts</span><span class="p">)</span> <span class="o">-</span> <span class="nb">set</span><span class="p">(</span><span class="n">post_history</span><span class="p">))</span>

<span class="k">if</span> <span class="n">new_posts</span><span class="p">:</span>
    <span class="k">for</span> <span class="n">post</span> <span class="ow">in</span> <span class="n">new_posts</span><span class="p">:</span>
        <span class="n">get_adn</span> <span class="o">=</span> <span class="n">util</span><span class="o">.</span><span class="n">ADN</span><span class="p">(</span><span class="n">POST_PATH</span> <span class="o">+</span> <span class="n">post</span><span class="p">)</span>
        <span class="n">get_adn</span><span class="o">.</span><span class="n">post</span><span class="p">()</span>
    <span class="n">pickler</span><span class="o">.</span><span class="n">store_results</span><span class="p">(</span><span class="s1">&#39;lib/posts.pkl&#39;</span><span class="p">,</span> <span class="n">current_posts</span><span class="p">)</span>
</code></pre></div>

<p>I get the current posts by simply listing the contents of the posts directory and then compare to what was previously stored the last time a new post was made. I keep this is a file called lib/util.py, which explains why I have to call <code>os.path.dirname</code> twice.</p>
<div class="codehilite"><pre><span></span><code><span class="k">def</span> <span class="nf">current_posts</span><span class="p">():</span>
    <span class="n">post_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))),</span> <span class="s1">&#39;content&#39;</span><span class="p">,</span> <span class="s1">&#39;posts&#39;</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">f</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">post_path</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">f</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;.&#39;</span><span class="p">)]</span>
</code></pre></div>

<p>This seems to be the most reliable solution since it won't send broadcasts if I edit a file. Finally, when the publish function is called from my fabfile, I call <code>adn()</code>.</p>
<h2 id="automation">Automation</h2>
<p>I'm traveling a lot these days, which means that sometimes I only have my iPad or iPhone with me. I'd still like to easily create posts without having to write up the post, log in via Prompt, commit and push. I went with a setup fairly similar to <a href="http://www.evanlovely.com/notes/about-this-jekyll-site/">Evan Lovely</a> and use Hazel to watch for new posts within a directory. </p>
<p>My Hazel workflow relies on an additional piece of metadata in my posts instead of just the file itself. This prevents any accidental posts and also lets me put whatever file I want in the folder. The file needs to pass the following script:</p>
<div class="codehilite"><pre><span></span><code><span class="kn">import</span> <span class="nn">markdown</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">codecs</span>

<span class="n">f</span> <span class="o">=</span> <span class="n">codecs</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">mode</span><span class="o">=</span><span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">md</span> <span class="o">=</span> <span class="n">markdown</span><span class="o">.</span><span class="n">Markdown</span><span class="p">(</span><span class="n">extensions</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;meta&#39;</span><span class="p">])</span>
<span class="n">md</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>

<span class="k">if</span> <span class="n">md</span><span class="o">.</span><span class="n">Meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;hazel&#39;</span><span class="p">):</span>
    <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div>

<p>As long as the piece of metadata "hazel" exists in any of my files, Hazel  moves the file into my Pelican project folder and my publish script takes over.</p>
<p>That's it for now! I'll keep iterating on the process and make things better.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 29 Dec 2013 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-12-29:/2013/12/29/exploring-pelican-automation</guid>
      <category>Tech</category>
      <category>scripting</category>
      <category>efficiency</category>
      <category>pelican</category>
    </item>
    <item>
      <title>Create a Scratchpad with Alfred</title>
      <link>https://ryanmo.co/2013/12/28/create-a-scratchpad-with-alfred</link>
      <description><![CDATA[<p>I always have an empty doc open on my computer as a place to quickly paste in some text. It's never something I need to save and I'll never miss it if I happen to lose it. The only problem is that it's slow recreating this scratchpad file or find it each time I need it. </p>
<p>I created a very simple workflow that takes the contents of my clipboard and opens a file in a text editor of my choice. The Alfred workflow can be triggered in one of two ways. The first is with the keyboard shortcut "hyper"-s<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>. The second way is simply typing "scratchpad" into Alfred. </p>
<p><img alt="scratchpad_alfred" src="https://ryanmo.co/2013/12/28/create-a-scratchpad-with-alfred/scratchpad_alfred.jpg" /></p>
<p>I have it set to save a file in my Home folder called ".scratchpad" and then open the file in Sublime Text. Right now, the workflow will check for Sublime Text version 3, then version 2 and if neither exist, it will open the scratchpad in Text Edit. </p>
<p><img alt="scratchpad_example" src="https://ryanmo.co/2013/12/28/create-a-scratchpad-with-alfred/scratchpad_example.jpg" /></p>
<p>You'll need the Alfred Powerpack to use this workflow. If you already have it, you can download it with the link below. </p>
<p><a href="https://ryanmo.co/2013/12/28/create-a-scratchpad-with-alfred/Scratchpad.alfredworkflow"><img alt="image" src="https://ryanmo.co/images/alfred_extension.jpg" /></a>  </p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>command-option-control-shift mapped to my caps lock key. You can read more <a href="http://brettterpstra.com/2012/12/08/a-useful-caps-lock-key/">here</a> on how to make a hyper key&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sat, 28 Dec 2013 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-12-28:/2013/12/28/create-a-scratchpad-with-alfred</guid>
      <category>Tech</category>
      <category>alfred</category>
      <category>scripting</category>
      <category>efficiency</category>
    </item>
    <item>
      <title>Quick Conversions with Launch Center Pro and Soulver</title>
      <link>https://ryanmo.co/2013/12/03/quick-conversions-with-launch-center-pro-and-soulver</link>
      <description><![CDATA[<p>There are some great tools out there to convert things like currency, distances and measurements. Even Siri can do this fairly well, but the one thing I always find frustrating is that the process of doing this can be fairly slow and in a lot of cases requires a data connection. Growing up in the United States, I was unfortunately never exposed to the metric system or Celsius. Since I've moved to Dublin, I'm find myself doing a lot of conversions from one unit to another.</p>


<p>I was poking around in <a href="http://contrast.co/launch-center-pro/">Launch Center Pro</a> for iOS the other day to just see what kinds of things I could do, and I noticed one of the options was <a href="http://www.acqualia.com/soulver/iphone/">Soulver</a>. My main use case for Soulver has always been one-off conversions or keeping score while playing Farkle with my girlfriend. It occurred to me that this would make a really nice way to do quick conversions. </p>
<p>I didn't want to have to create a mess of different actions for each unit. The way around this was to create a variable within Soulver and always reference back to it with multiple conversions. I started out simple with a quick US Dollar to Euro and Pounds and vice-versa. </p>
<p><img alt="Currency Conversion" src="https://ryanmo.co/2013/12/03/quick-conversions-with-launch-center-pro-and-soulver/lcp_currency.jpg" /></p>
<p>To achieve this, Launch Center Pro uses x-callback-urls which allows apps to send data to another app and perform actions. The following url requests a number and then sends over this number as a variable to Soulver</p>
<div class="codehilite"><pre><span></span><code>soulver://new?text=x%20%3D%20[prompt-num:Text]%0Ax%20euro%20to%20usd%0Ax%20gbp%20to%20usd%0A----------------%0Ax%20usd%20to%20euro%0Ax%20usd%20to%20gbp%0A----------------<span class="err">&amp;</span>title=Currency%20Conversion
</code></pre></div>

<p>This worked perfectly and solved the two biggest requirements that I had: to be quick and to not rely on a data connection. I then wanted to go a little further and so I did some very basic unit conversions.</p>
<p><img alt="Unit Conversions" src="https://ryanmo.co/2013/12/03/quick-conversions-with-launch-center-pro-and-soulver/lcp_temp.jpg" />  </p>
<p>The url for the action is very similar, but using different conversions</p>
<div class="codehilite"><pre><span></span><code>soulver://new?text=x%20%3D%20[prompt-num:Text]%0AFahrenheit%3A%20x%20C%20to%20F%0ACelsius%3A%20x%20F%20to%20C%0A-%20-%20-%20-%20-%20-%20-%20-%20-%20-%20-%20-%20%0AMiles%3A%20x%20km%20to%20mi%0AKilometers%3A%20x%20mi%20to%20km%0A-%20-%20-%20-%20-%20-%20-%20-%20-%20-%20-%20-%20%0AFeet%3A%20x%20m%20to%20feet%0AMeters%3A%20x%20feet%20to%20m%0A----------------<span class="err">&amp;</span>title=Temperature
</code></pre></div>

<p>If you have both Launch Center Pro and Soulver, you can download both of these actions here:</p>
<p><a href="http://launchcenterpro.com/2xv247">Currency Conversion</a><br />
<a href="http://launchcenterpro.com/ylljt1">Unit Conversions</a>  </p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Tue, 03 Dec 2013 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-12-03:/2013/12/03/quick-conversions-with-launch-center-pro-and-soulver</guid>
      <category>Tech</category>
      <category>ios</category>
      <category>scripting</category>
      <category>efficiency</category>
    </item>
    <item>
      <title>Pebble's New Notifications</title>
      <link>https://ryanmo.co/2013/11/12/pebble_notifications</link>
      <description><![CDATA[<p>It's been over a year ago now that I backed the Pebble Kickstart project. I had really high hopes for this watch and was excited to see what it could do. Pebble recently released a big new update to the watch's firmware which takes advantage of iOS 7's notifications and allows you to receive any notification quickly and easily.</p>
<p><img alt="Pebble watch" src="https://ryanmo.co/2013/11/12/pebble_notifications/pebble.jpg" /></p>
<p>When I first received the watch, I found the overall design and feel of the watch to be great. 
Putting aside the fact that the watch is meant to be an extension of your phone, it was a really nice watch to wear. It felt nice, it wasn't super flashy and I wasn't constantly worried that I was going to scratch or break it. What I found disappointing about the watch was that the notifications were inconsistent and took a lot of work to function at all. I ended up getting frustrated and put the watch in a drawer for nearly 4 months. </p>
<p>With the most recent update to Pebble's firmware, it's now really simple to get all notifications on the watch. Previously you couldn't only receive SMS , phone calls and email notifications. Now, any notification that is set to "banner" will show up. </p>
<p><img alt="Tweetbot notification" src="https://ryanmo.co/2013/11/12/pebble_notifications/pebble2.jpg" /></p>
<p>The only downside I've found so far is that even if you have your notifications set to not appear in the lock screen, you still receive the notification on the watch. Since I receive so many emails, I like to only get the notification on my phone when I unlock the screen. Now every time I get an email, my Pebble watch is buzzing on my wrist. It creates a bit of unnecessary anxiety, so I'll have to see what I want to do with that.</p>
<p>Overall, it's a great update. I do find myself wearing the watch more often and am excited to see what other new stuff they have in store in the future.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Tue, 12 Nov 2013 00:00:00 -0800</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-11-12:/2013/11/12/pebble_notifications</guid>
      <category>Tech</category>
      <category>pebble</category>
    </item>
    <item>
      <title>Using Dropbox to Host Images on your Website</title>
      <link>https://ryanmo.co/2013/11/03/dropboxsharedlinks</link>
      <description><![CDATA[<p>I notice a lot of people asking about why they can't get images to display on their website when using <a href="https://www.dropbox.com/help/167">Dropbox shared links</a>. Dropbox is a great way to post an image quickly on a forum or as free hosting for your low traffic website, but there are a few things to know.</p>


<p>In the early days, Dropbox offered a Public folder where you could easily serve webpages, images or anything else you want to share to the world. The risk there is that the links to the files were formulaic and anyone could crawl your Public folder looking for things they maybe shouldn't have. This formula looked like this:</p>
<p>www.dropbox.com/u/&lt;number>/&lt;name of file></p>
<p>To add a level of security to the shared links, Dropbox now has a hashed value so that someone would then need to know the unique hash as well as the file name. The chances that someone is able to guess both of these within the next 10,000 years is pretty low. The second thing that was added was a preview to your shared links. If you have images, you see a nice gallery in your links and Office documents now have a preview. The downside here is that simple file hosting doesn't work by pasting in the link.</p>
<p>To solve this, you just need to change the actual shared link with the link to the file itself. To do this, you just need to replace <code>www.dropbox.com</code> with <code>dl.dropboxusercontent.com</code>. This will serve the true file instead of the file wrapped in a preview. For those of you using snippet software like TextExpander, you can make this a lot faster by making a shell script snippet with the following:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5</pre></div></td><td class="code"><div class="codehilite"><pre><span></span><code><span class="ch">#!/bin/bash</span>

<span class="nv">url</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> %clipboard <span class="p">|</span> sed <span class="s1">&#39;s/www.dropbox.com/dl.dropboxusercontent.com/g&#39;</span> <span class="p">|</span> tr -d <span class="s1">&#39;\n&#39;</span><span class="k">)</span>

<span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$url</span><span class="s2">&quot;</span>
</code></pre></div>
</td></tr></table>
<p>Now an image like</p>
<p>https://www.dropbox.com/s/kyjm1pr79g2irfj/Guinness%20Storehouse%20top.jpg</p>
<p>turns into this:</p>
<p><img alt="https://dl.dropboxusercontent.com/s/kyjm1pr79g2irfj/Guinness%20Storehouse%20top.jpg" src="https://dl.dropboxusercontent.com/s/kyjm1pr79g2irfj/Guinness%20Storehouse%20top.jpg" /></p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 03 Nov 2013 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-11-03:/2013/11/03/dropboxsharedlinks</guid>
      <category>Tech</category>
      <category>dropbox</category>
      <category>scripting</category>
    </item>
    <item>
      <title>My New Pencil. The rOtring 600</title>
      <link>https://ryanmo.co/2013/10/02/rotring_pencil</link>
      <description><![CDATA[<p>Being a math major in school, I've always been partial towards pencils. I always loved using the Dixon Ticonderoga pencils, but I'd never have a sharpener nearby and nothing is worse than trying to write a formula than with dull lead. For most of college, I used a <a href="http://www.pentel.com/store/twist-erase-iii-mechanical-pencil">Pentel QE517</a> which I still carry with me in my bag.</p>


<p>I very rarely write in a physical notebook. All my notes are written in either Byword, MultiMarkdown Composer or NVAlt and synced with Dropbox. Plain text allows me to have my notes everywhere, whether it's online, on my phone or my computer. I've had this recent desire to start writing things down in a notebook. I'm traveling a lot more lately and sometimes want to conserve phone battery or just entirely too lazy to pull out my phone.</p>
<p>To go all out, I did some research on what was considered the "best mechanical pencil." For the most part, rOtring pencils kept coming up as the best pencils out there. I decided to give it a try and go with the <a href="http://www.jetpens.com/Rotring-600-Drafting-Pencil-0.5-mm-Black-Body/pd/6435">rOtring 600 0.5 mm</a> mechanical pencil.</p>
<p><img alt="Pencil and Notebote" src="https://ryanmo.co/2013/10/02/rotring_pencil/rotring_fieldnotes.jpg" /></p>
<p>This is an entirely new world for me and I don't claim to be expert when it comes to pencils, but what I can say is that I love this pencil. The first thing that I noticed was the weight. It's definitely heavier than the average pencil (or pen for that matter). No part of the pen is made of plastic, which is also a huge plus and obviously contributes to the weight. So far it's been great to write with. The only complaint I could come up with is that I wish it were a little thicker. Sometimes I feel like I'm stressing my hand to hold on to it.</p>
<p>If anyone has any pencils or pens that they can't live without, I'd love to hear about them. My next test will be on my new Field Notes Expedition series, which are water, tear, burn proof.</p>
<p>I put together a really short video to show the pencil in use. </p>
<iframe src="http://player.vimeo.com/video/76011517?portrait=0" width="500" height="375" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Wed, 02 Oct 2013 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-10-02:/2013/10/02/rotring_pencil</guid>
      <category>Tech</category>
      <category>misc</category>
    </item>
    <item>
      <title>Instascriptogram. Post Instagram pics to Scriptogr.am</title>
      <link>https://ryanmo.co/2013/09/05/instascriptogram</link>
      <description><![CDATA[<p><strong>[Update 2014-11-19]</strong>
I've since moved off of scriptogr.am. The service wasn't working for a long time and doesn't seem to be in active developement. I ended up moving that blog over to a static blog with Pelican similar to this one.</p>
<hr />

<p>Since moving to Dublin, my girlfriend and I have wanted to keep our friends and family up-to-date on everything we've been doing. I recently bought the new Olympus E-P5 and have been taking a lot of pictures. So that everyone knows what we're doing, we decided to share a <a href="http://www.scriptogr.am">Scriptogr.am</a> blog and post pictures of our adventures. </p>


<p>Sometimes it's quick and easy to snap a picture on Instagram and share with all your friends, but my parents and family aren't on Instagram, but they know to follow my blog for updates. Instead of having to manually pull the pics down, write up a post and publish it, I used a combination of IFTTT, Dropbox and my server at Macminicolo.net to do all the work for me.</p>
<p>The magic starts at IFTTT. I have a <a href="https://ifttt.com/recipes/115652">recipe</a> that watched for a particular tag when I post to Instagram. If that tag exists, a text file is saved to my Dropbox account. I have a cron running once an hour<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup> to run the script and check for any new files.</p>
<p>One of the only complaints about Scriptogr.am has been that I have to manually hit a publish button before posts will go live. But with their API, the posts are immediate<sup id="fnref:2"><a class="footnote-ref" href="https://ryanmo.co#fn:2">2</a></sup>. Now, all of my Instagram adventures (and my girlfriend's) can be posted to our blog for friends and family to follow. Once the post is made, I get a Pushover notification letting me know that a post was made by either me or my girlfriend.</p>
<p><img alt="pushover_instascriptogram" src="https://ryanmo.co/2013/09/05/instascriptogram/pushover_instascriptogram.jpg" /></p>
<p>If you're interested in the script, it can be found on Github <a href="https://github.com/rjames86/instascriptogram">here</a>. An example of the posts being made can be found at our travel blog <a href="http://keephouseadventures.com/posts/2013/Sep/03_12_44/instagram-pic-for-tuesday-sep-03/">keephouseadventures.com</a></p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>I tried using Hazel for this, but I kept getting errors since I wasn't actually processing the file. Any suggestions on this, please let me know!&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
<li id="fn:2">
<p>Like what you get from apps like Byword&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:2" title="Jump back to footnote 2 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Thu, 05 Sep 2013 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-09-05:/2013/09/05/instascriptogram</guid>
      <category>Tech</category>
      <category>dropbox</category>
      <category>scripting</category>
      <category>ifttt</category>
      <category>photos</category>
    </item>
    <item>
      <title>Internationalizing Your Contacts</title>
      <link>https://ryanmo.co/2013/07/07/i18n_contacts</link>
      <description><![CDATA[<p>Living in the U.S. we rarely call people outside of the country. Whenever we create new contacts in our address book, they'll typically start with the state's area code and omit the country code. </p>


<p>Since moving to Ireland, my contacts wouldn't show up correctly since I hadn't prepended all of contacts with '+1'. I wasn't about to manually change all 700 contacts in my phone and fortunately came across a <a href="http://en.katzueno.com/2011/06/08/adding-1-to-us-tel-numbers-in-address-book-mac-os-x/">nice post</a> that had the following AppleScript:</p>
<div class="codehilite"><pre><span></span><code><span class="k">tell</span> <span class="nb">application</span> <span class="s2">&quot;Address Book&quot;</span>
    <span class="k">repeat</span> <span class="nv">with</span> <span class="nv">eachPerson</span> <span class="k">in</span> <span class="nv">people</span>
        <span class="k">repeat</span> <span class="nv">with</span> <span class="nv">eachNumber</span> <span class="k">in</span> <span class="nv">phones</span> <span class="k">of</span> <span class="nv">eachPerson</span>
            <span class="k">set</span> <span class="nv">theNum</span> <span class="k">to</span> <span class="p">(</span><span class="k">get</span> <span class="nv">value</span> <span class="k">of</span> <span class="nv">eachNumber</span><span class="p">)</span>
            <span class="k">if</span> <span class="p">(</span><span class="nv">theNum</span> <span class="ow">does</span> <span class="ow">not</span> <span class="ow">start with</span> <span class="s2">&quot;+&quot;</span> <span class="ow">and</span> <span class="nv">theNum</span> <span class="ow">does</span> <span class="ow">not</span> <span class="ow">start with</span> <span class="s2">&quot;1&quot;</span> <span class="ow">and</span> <span class="nv">theNum</span> <span class="ow">does</span> <span class="ow">not</span> <span class="ow">start with</span> <span class="s2">&quot;0&quot;</span><span class="p">)</span> <span class="k">then</span>
                <span class="k">set</span> <span class="nv">value</span> <span class="k">of</span> <span class="nv">eachNumber</span> <span class="k">to</span> <span class="s2">&quot;+1&quot;</span> <span class="o">&amp;</span> <span class="nv">theNum</span>
            <span class="k">end</span> <span class="k">if</span>
        <span class="k">end</span> <span class="k">repeat</span>
    <span class="k">end</span> <span class="k">repeat</span>
    <span class="nv">save</span>
<span class="k">end</span> <span class="k">tell</span>
</code></pre></div>

<p>Before running this, I highly recommend backing up your contacts. This can be run easily by just launching AppleScript Editor and pasting in the code above. Enjoy!</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 07 Jul 2013 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-07-07:/2013/07/07/i18n_contacts</guid>
      <category>Tech</category>
      <category>efficiency</category>
      <category>scripting</category>
      <category>applescript</category>
    </item>
    <item>
      <title>My First Official Bike Ride</title>
      <link>https://ryanmo.co/2013/05/19/davis_double</link>
      <description><![CDATA[<p>I've never done an actual bike ride before. A friend and I decided that we would try our luck at a double century<sup id="fnref:1"><a class="footnote-ref" href="https://ryanmo.co#fn:1">1</a></sup>. We ended up finishing in around 16 hours. The ride went really well up until the 185 mile mark where my tire decided to explode. </p>


<p><img alt="tire_blowout" src="https://ryanmo.co/2013/05/19/davis_double/tire_blowout.jpg" /></p>
<p>They first told me that I was just going to have to be driven to the finish line. Luckily someone decided to get creative and boot the tire. We placed a piece of cut tire unbetween the tube and the tire. Once the tire part was fixed, we wrapped the tire in duct tape to keep it from possibly blowing again.</p>
<p><img alt="tire_fixed" src="https://ryanmo.co/2013/05/19/davis_double/tire_fixed.jpg" /></p>
<p>All in all it was a great day. </p>
<div align="center"><iframe width='465' height='548' frameborder='0' style="display:block;" src='http://connect.garmin.com:80/activity/embed/315135686'></iframe></div>

<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>200 miles&#160;<a class="footnote-backref" href="https://ryanmo.co#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 19 May 2013 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-05-19:/2013/05/19/davis_double</guid>
      <category>Tech</category>
      <category>cycling</category>
    </item>
    <item>
      <title>Writing Notes with Alfred 2</title>
      <link>https://ryanmo.co/2013/05/14/notes_and_alfred</link>
      <description><![CDATA[<p>I started coding about two years ago and only recently discovered the wonders of Markdown.  Every time I'd learn something new, I would keep it in a text file with TextEdit. This was good and fine until a coworker introduced me to Notational Velocity. This completely changed the way I managed my notes but I always felt like I was missing something. That's when I discovered NVAlt. It let me keep the simplicity of plain text but format the note with the wonders of Markdown. </p>


<p>Now I was left with another problem. I was annoyed having to command-tab over to NVAlt, command-D to go to the search field and type just to find the note. With Alfred 2's new File Filter, I can now search specifically for my notes within Alfred. I now just launch Alfred, type 'note' and any keywords I want and am immediately taken to my note in NVAlt. </p>
<p><img alt="alfred_note" src="https://ryanmo.co/2013/05/14/notes_and_alfred/alfred_note.png" /></p>
<p>This last week I've spent a lot of time writing up plans and documents. I wouldn't put NVAlt in the category of great text editors and so I've been using MultiMarkdown Composer, but I'm still saving to the same notes folder. It seemed only obvious to add more functionality to my Alfred script. Now I can optionally hold down command or control to open my note in MultiMarkdown Composer or Byword respectively. </p>
<p><img alt="open_in_mmc" src="https://ryanmo.co/2013/05/14/notes_and_alfred/open_in_mmc.png" /></p>
<p>You can download the Alfred Extension here:</p>
<p><a href="https://ryanmo.co/2013/05/14/notes_and_alfred/Notes.alfredworkflow"><img alt="image" src="https://ryanmo.co/images/alfred_extension.jpg" /></a>  </p>
<p>Be sure to set your path to your notes folder in the File Filter under Search Scope. If you're using Notational Velocity, you can change which application is opened in the action script. </p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Tue, 14 May 2013 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-05-14:/2013/05/14/notes_and_alfred</guid>
      <category>Tech</category>
      <category>alfred</category>
      <category>efficiency</category>
    </item>
    <item>
      <title>[Updated] Log Your Instagram Posts with Slogger</title>
      <link>https://ryanmo.co/2013/05/08/instagram_and_slogger</link>
      <description><![CDATA[<hr />
<p><strong>Update 2016-06-23</strong></p>
<p>As of June 2016, Instagram has changed their API and no longer allows this script to work. Sorry :(</p>
<p><em>Update 2014-09-04</em></p>
<p>I recently submitted a new plugin that now comes with Slogger which uses the Instagram API. You can check out my post with more information <a href="https://ryanmo.co{static}../2014-09-04/2014-09-04-instagram-slogger.md">here</a>.</p>
<hr />

<p>I've received a few questions about <a href="https://ifttt.com/recipes/62754">this</a> IFTTT recipe which logs my Instagram posts to Day One. There are a few others floating out there, but there are a couple of things that I wanted to have:</p>


<ul>
<li>The Day One entry date is the date the picture was taken</li>
<li>The caption is saved in the journal entry</li>
<li>Ignore duplicate posts if I also posted to Twitter</li>
</ul>
<p>The last point assumes that I'm also using the default Twitter logger. If you want to ignore all of your Instagram tweets, add the following to be on line 112 in the twitterlogger plugin:</p>
<div class="codehilite"><pre><span></span><code><span class="k">break</span> <span class="k">if</span> <span class="n">tweet_text</span><span class="o">.</span><span class="n">include?</span> <span class="s1">&#39;instagram&#39;</span>
</code></pre></div>

<p>You can download the Instagram IFTTT Slogger extension <a href="https://ryanmo.co/2013/05/08/instagram_and_slogger/instagram_ifttt.rb">here</a>. Simply add it to your plugins directory and run the following once to set up the slogger_config file:</p>
<div class="codehilite"><pre><span></span><code>./slogger -o instagram_ifttt
</code></pre></div>

<p>You'll need to set the location of your IFTTT slogger directory. The plugin will check for any text files and then automatically move them into a "logged" folder once they've been added to Day One. </p>
<p><img alt="image" src="https://ryanmo.co/2013/05/08/instagram_and_slogger/dayone_instagram.jpg" /></p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Wed, 08 May 2013 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-05-08:/2013/05/08/instagram_and_slogger</guid>
      <category>Tech</category>
      <category>slogger</category>
      <category>dropbox</category>
      <category>dayone</category>
      <category>photos</category>
    </item>
    <item>
      <title>Browse Files on Dropbox.com with Alfred 2</title>
      <link>https://ryanmo.co/2013/04/14/browse_on_dropbox</link>
      <description><![CDATA[<p>On a rare occasion, I need to view files in my Dropbox folder on my computer on the website. The most common use case is I want to see the entire structure of a directory. I <a href="https://www.dropbox.com/help/175/">selectively unsync</a> a lot of large directories since my MacBook Air has limited hard drive space. I want to quickly go to the Dropbox website and view this particular folder without having to re-navigate to it's location.</p>


<p>To use, navigate to any file/folder from within Alfred and trigger the Actions panel. I have a file filter set up to search specifically my Dropbox folder</p>
<p><img alt="image" src="https://ryanmo.co/2013/04/14/browse_on_dropbox/db_filter.jpg" /> </p>
<p>and trigger Actions to browse on Dropbox</p>
<p><img alt="image" src="https://ryanmo.co/2013/04/14/browse_on_dropbox/browse_action.jpg" /></p>
<p>Remember, you’ll need the <a href="http://www.alfredapp.com/">Alfred</a> <a href="http://www.alfredapp.com/powerpack/">Powerpack</a> for these extensions to work. Click the following icon to download the Alfred script to your computer:</p>
<p><a href="https://dl.dropbox.com/s/lh85xmki4lgeawk/Browse%20on%20Dropbox.alfredworkflow"><img alt="image" src="https://ryanmo.co/images/alfred_extension.jpg" /></a>  </p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 14 Apr 2013 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-04-14:/2013/04/14/browse_on_dropbox</guid>
      <category>Tech</category>
      <category>dropbox</category>
      <category>alfred</category>
    </item>
    <item>
      <title>Your .bash_profile everywhere</title>
      <link>https://ryanmo.co/2013/03/31/bashprofile</link>
      <description><![CDATA[<p>I have two computers, one for work and one for personal. I keep mostly everything separate, but one thing I want to always have with me is my terminal environment and aliases. With Dropbox, I can not only access, but edit my .bash_profile from anywhere <em>without</em> using symlinks.</p>


<p>The first thing to do is figure out where you want to keep your .bash_profile in your Dropbox account. I keep mine in a folder called Sync that's shared between my work and personal Dropbox accounts. To move your .bash_profile, use the following command in Terminal:</p>
<div class="codehilite"><pre><span></span><code><span class="err">mv ~/.bash_profile ~/path/to/Dropbox/.bash_profile</span>
</code></pre></div>

<p>Once you've moved it here, create a new .bash_profile in your home directory and add the single line:</p>
<div class="codehilite"><pre><span></span><code><span class="err">source ~/that/path/to/.bash_profile</span>
</code></pre></div>

<p>Thats it! From now on, just point the local .bash_profile to the one location in your Dropbox folder.</p>]]></description>
      <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Ryan M</dc:creator>
      <pubDate>Sun, 31 Mar 2013 00:00:00 -0700</pubDate>
      <guid isPermaLink="false">tag:ryanmo.co,2013-03-31:/2013/03/31/bashprofile</guid>
      <category>Tech</category>
      <category>dropbox</category>
      <category>efficiency</category>
      <category>bash</category>
    </item>
  </channel>
</rss>
