<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Sam Rayner</title>
  <subtitle>iOS and web designer/developer from Sheffield, UK</subtitle>
  <id>http://www.samrayner.com/posts</id>
  <link href="http://www.samrayner.com/posts"/>
  <link href="http://www.samrayner.com/feed.xml" rel="self"/>
  <updated>2016-12-07T15:54:00+00:00</updated>
  <author>
    <name>Sam Rayner</name>
  </author>
  <entry>
    <title>Statto - The Ultimate stats tracker</title>
    <link rel="alternate" href="http://www.samrayner.com/posts/statto-the-ultimate-stat-tracker/"/>
    <id>http://www.samrayner.com/posts/statto-the-ultimate-stat-tracker/</id>
    <published>2016-12-07T15:54:00+00:00</published>
    <updated>2016-12-07T16:16:29+00:00</updated>
    <author>
      <name>Sam Rayner</name>
    </author>
    <content type="html">&lt;p&gt;I don’t really blog about Ultimate Frisbee but when I’m not looking at a screen it’s what I’ll be out doing or at least thinking about.&lt;/p&gt;

&lt;p&gt;For the past two years I’ve had the privilege of representing Great Britain, competing in the Mixed division at the European and World Championships. I also coach and captain the local club team, &lt;a href="http://www.sheffieldultimate.co.uk"&gt;Sheffield Steal&lt;/a&gt; and help coach Sheffield’s university teams.&lt;/p&gt;

&lt;p&gt;Ultimate is growing rapidly in popularity and recognition as a sport. With that growth has come an increasing desire for advanced statistical analysis to gain a better insight into player performance and team strategies.&lt;/p&gt;

&lt;p&gt;It will be many years before Ultimate has the funding for the kind of GPS player tracking of more mainstream sports. In the meantime, I’ve developed an app for the iPhone and iPad to allow coaches, broadcasters and fans to track and analyse Ultimate stats. It’s called &lt;a href="http://www.stattoapp.com"&gt;Statto&lt;/a&gt; and is &lt;a href="https://itunes.apple.com/us/app/statto-ultimate-frisbee-stats-tracker/id1130463861"&gt;available on the App Store&lt;/a&gt;.&lt;/p&gt;

&lt;iframe allowfullscreen="" frameborder="0" height="480" src="https://player.vimeo.com/video/183842052?title=0&amp;amp;byline=0&amp;amp;portrait=0" width="640"&gt;&lt;/iframe&gt;

&lt;p&gt;Try it out today for free, or for more information &lt;a href="http://www.stattoapp.com"&gt;visit the website&lt;/a&gt;.&lt;/p&gt;

</content>
  </entry>
  <entry>
    <title>Discographer</title>
    <link rel="alternate" href="http://www.samrayner.com/posts/discographer/"/>
    <id>http://www.samrayner.com/posts/discographer/</id>
    <published>2015-07-23T12:54:00+01:00</published>
    <updated>2015-07-23T12:53:29+01:00</updated>
    <author>
      <name>Sam Rayner</name>
    </author>
    <content type="html">&lt;p&gt;As a fun little project over the last few weeks, I’ve built a Web app that helps you discover more music by artists in your iTunes library. It’s called &lt;a href="http://www.samrayner.com/discographer/"&gt;Discographer&lt;/a&gt;, and uses the &lt;a href="https://developer.spotify.com/web-api/"&gt;Spotify API&lt;/a&gt; to fetch albums, ticking off the ones you own so you can see what you might have missed.&lt;/p&gt;

&lt;p&gt;&lt;img src="/posts/discographer/file.png" alt="iTunes Music Library.xml" class="right" /&gt;&lt;/p&gt;

&lt;p&gt;It was a nice opportunity to play with some new web APIs (&lt;a href="http://www.w3.org/TR/2010/WD-html5-20101019/dnd.htm"&gt;Drag and Drop&lt;/a&gt;, &lt;a href="http://www.w3.org/TR/FileAPI/"&gt;File&lt;/a&gt; and &lt;a href="http://www.w3.org/TR/css-flexbox-1/"&gt;Flexbox&lt;/a&gt;), the main benefit being your library is never uploaded but parsed locally in your browser. The only thing transmitted is the name of any artist you click.&lt;/p&gt;

&lt;p&gt;It was &lt;a href="http://lifehacker.com/discographer-scans-your-itunes-library-for-artists-miss-1719274310"&gt;linked on Lifehacker&lt;/a&gt; a couple of days ago and I’ve since received some great feedback. Please give it a go and let me know what you think!&lt;/p&gt;

</content>
  </entry>
  <entry>
    <title>GitHub Spectacles</title>
    <link rel="alternate" href="http://www.samrayner.com/posts/github-spectacles/"/>
    <id>http://www.samrayner.com/posts/github-spectacles/</id>
    <published>2014-02-14T15:38:00+00:00</published>
    <updated>2016-01-15T16:05:42+00:00</updated>
    <author>
      <name>Sam Rayner</name>
    </author>
    <content type="html">&lt;p&gt;At &lt;a href="http://terracoding.com"&gt;Terracoding&lt;/a&gt; we regularly review pull requests from each other when working on a project. GitHub’s “Commits” and “Files Changed” tabs make it pretty painless but we wanted a way to easily view Ruby implementation files alongside their specs.&lt;/p&gt;

&lt;p&gt;&lt;img src="/posts/github-spectacles/octoclark.png" alt="Octoclark Kentocat" width="400" height="400" class="right" /&gt;&lt;/p&gt;

&lt;p&gt;I created a bookmarklet to arrange the file pairs 2-up for review. With the files side-by-side it’s easy to spot code that isn’t covered by tests and we get a better idea of what methods are meant to do. It also hides the code of deleted files which I never bother to read during review.&lt;/p&gt;

&lt;p&gt;To install, drag this button into your bookmarks bar:&lt;/p&gt;

&lt;p&gt;&lt;a id="bookmarklet" href="javascript:void%20function(){var%20e,t,r=function(e,t){return%20function(){return%20e.apply(t,arguments)}};e=function(){function%20e(){this.stylePairWrappers=r(this.stylePairWrappers,this),this.sorter=r(this.sorter,this);var%20e,t,i,s,a,n,l;for(this.pairClass=%22spectacles-pair%22,e=$(%22%23files%22),i=e.find(%22.file%22).sort(this.sorter),e.empty(),a=0,n=i.length;n%3Ea;a++)s=i[a],t=$(s),this.deletedFile(s)%26%26this.hideDeletedFile(s),e.append(s),l=t.prev(),l.length%26%26this.filePair(l,s)%26%26this.wrapPair(l,s);this.stylePairFiles(),this.stylePairWrappers(),$(window).resize(this.stylePairWrappers)}return%20e.prototype.deletedFile=function(e){var%20t;return%20t=$(e),t.find(%22.diff-deleted%22).length||t.find(%22.blob-code-hunk%22).text().indexOf(%22+0,0%20%40%40%22)%3E0},e.prototype.hideDeletedFile=function(e){var%20t,r;return%20r=$(e),t=r.find(%22.data%22),t.addClass(%22data%20empty%22).css(%22background-color%22,%22%23fdd%22).html(%22Deleted%20file%20not%20rendered%22)},e.prototype.sorter=function(e,t){return%20e=this.filePath(e),t=this.filePath(t),t%3Ee%3F-1:e%3Et%3F1:0},e.prototype.filePath=function(e){var%20t,r;return%20r=$(e).find(%22.file-header%22).data(%22path%22).toLowerCase(),t=r.split(/[\\\/]/),t[t.length-2]+%22/%22+t[t.length-1]},e.prototype.filePair=function(e,t){return%20e=this.filePath(e).replace(%22_spec%22,%22%22),t=this.filePath(t).replace(%22_spec%22,%22%22),e===t},e.prototype.wrapPair=function(e,t){var%20r;return%20r=$('%3Cdiv%20class=%22'+this.pairClass+'%22%20/%3E'),$(e).before(r),r.append($(e),$(t))},e.prototype.stylePairFiles=function(){return%20$(%22.%22+this.pairClass+%22%20.file%22).css({width:%2249%25%22,%22float%22:%22left%22,margin:%220%200.5%25%22}).find(%22.data%22).css({maxHeight:%22500px%22,overflow:%22auto%22})},e.prototype.stylePairWrappers=function(){var%20e;return%20e=$(%22.main-content%20.container%22).first().offset().left,$(%22.%22+this.pairClass).css({margin:%220%20-%22+e+%22px%2015px%20-%22+e+%22px%22,overflow:%22hidden%22})},e}(),t=new%20e}();"&gt;GitHub Spectacles&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try it out &lt;a href="https://github.com/samrayner/sheffield-ultimate/commit/92aae555fd52db20418e387f49dceca9ff6f4fd8"&gt;on this commit&lt;/a&gt;. Just click the bookmark when viewing the file changes.&lt;/p&gt;

&lt;p&gt;The CoffeeScript for the bookmarklet is &lt;a href="https://github.com/samrayner/spectacles"&gt;opensourced on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;div class="footnotes"&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href="http://octodex.github.com/octoclarkkentocat/"&gt;Octoclark Kentocat&lt;/a&gt; by &lt;a href="https://github.com/cameronmcefee"&gt;Cameron McEfee&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;

</content>
  </entry>
  <entry>
    <title>Retiring Lando</title>
    <link rel="alternate" href="http://www.samrayner.com/posts/retiring-lando/"/>
    <id>http://www.samrayner.com/posts/retiring-lando/</id>
    <published>2013-11-12T12:24:00+00:00</published>
    <updated>2013-11-12T15:09:26+00:00</updated>
    <author>
      <name>Sam Rayner</name>
    </author>
    <content type="html">&lt;p&gt;Yesterday I finished porting this site to &lt;a href="http://middlemanapp.com/"&gt;Middleman&lt;/a&gt;, a static blog generator written in Ruby. Since &lt;a href="/posts/lando/"&gt;releasing Lando&lt;/a&gt;, I’ve barely written a line of PHP and, short of rewriting it in Ruby (which I unfortunately don’t have time to do), I’ve been put off working on Lando because of that.&lt;/p&gt;

&lt;p&gt;I am very proud of how Lando turned out and have genuinely enjoyed using it to power my own site but it felt like it was time to switch to something written in the language I use day-to-day. If you use Lando, I am sorry I won’t be continuing its development. Rest assured it will stay &lt;a href="http://github.com/samrayner/Lando"&gt;open-sourced on GitHub&lt;/a&gt; and if anyone wants to take over managing it please &lt;a href="&amp;#109;&amp;#097;&amp;#105;&amp;#108;&amp;#116;&amp;#111;:&amp;#038;&amp;#035;&amp;#049;&amp;#049;&amp;#053;&amp;#059;&amp;#038;&amp;#035;&amp;#057;&amp;#055;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#048;&amp;#057;&amp;#059;&amp;#038;&amp;#035;&amp;#054;&amp;#052;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#049;&amp;#053;&amp;#059;&amp;#038;&amp;#035;&amp;#057;&amp;#055;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#048;&amp;#057;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#049;&amp;#052;&amp;#059;&amp;#038;&amp;#035;&amp;#057;&amp;#055;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#050;&amp;#049;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#049;&amp;#048;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#048;&amp;#049;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#049;&amp;#052;&amp;#059;&amp;#038;&amp;#035;&amp;#052;&amp;#054;&amp;#059;&amp;#038;&amp;#035;&amp;#057;&amp;#057;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#049;&amp;#049;&amp;#059;&amp;#038;&amp;#035;&amp;#049;&amp;#048;&amp;#057;&amp;#059;"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re looking for a replacement, translating my Lando templates to Middleman was remarkably simple, with the majority of helper functions and content properties matching across the two systems. I recommend it over &lt;a href="http://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;/&lt;a href="http://octopress.org/"&gt;Octopress&lt;/a&gt; which I found to be less flexible and hampered by their &lt;a href="http://liquidmarkup.org/"&gt;Liquid&lt;/a&gt; templates.&lt;/p&gt;

&lt;p&gt;&lt;img src="/posts/retiring-lando/lando-and-han.jpg" alt="Lando and Han" class="shadow" /&gt;&lt;/p&gt;

&lt;p&gt;Thanks to everyone who tried Lando out and gave feedback. I learned a lot building it.&lt;/p&gt;

</content>
  </entry>
  <entry>
    <title>Alfred Lyrics Search Workflow</title>
    <link rel="alternate" href="http://www.samrayner.com/posts/alfred-lyrics/"/>
    <id>http://www.samrayner.com/posts/alfred-lyrics/</id>
    <published>2013-08-04T20:05:00+01:00</published>
    <updated>2013-11-05T16:18:15+00:00</updated>
    <author>
      <name>Sam Rayner</name>
    </author>
    <content type="html">&lt;p&gt;Ever had the lyrics of a song stuck in your head but can’t for the life of you remember the artist or title? I get it all the time so wrote this Alfred workflow to search my iTunes library and start playing the first match it finds.&lt;/p&gt;

&lt;iframe src="http://player.vimeo.com/video/71681950?portrait=0&amp;amp;byline=0&amp;amp;title=0" width="1280" height="720" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;a href="https://dl.dropbox.com/s/k0jglbcxiwp6q59/Lyrics%20Search.alfredworkflow"&gt;Download Lyrics Search&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img class="right" src="/posts/alfred-lyrics/get-lyrical.png" alt="Get Lyrical" width="236" height="147" /&gt;&lt;/p&gt;

&lt;p&gt;To get it working with your music collection you’ll need to make sure all of your tracks have lyrics downloaded for them. Don’t panic though, a great little Mac app called &lt;a href="http://shullian.com/get_lyrical.php"&gt;Get Lyrical&lt;/a&gt; can automate the process, tagging songs you select or tagging in the background as you play them.&lt;/p&gt;

&lt;p&gt;Once your library is tagged with lyrics, install the workflow and type &lt;strong&gt;sing&lt;/strong&gt; followed by the lyrics.&lt;/p&gt;

&lt;h2 id="under-the-hood"&gt;Under The Hood&lt;/h2&gt;
&lt;p&gt;For those interested in the technical side, the Workflow is just an Applescript:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;on normalize(theString)
  --trim everything but letters and numbers
  return do shell script "echo " &amp;amp; quoted form of theString &amp;amp; " | tr '\r' ' ' | sed 's/[^[:space:][:alnum:]]//g'"
end normalize

tell application "iTunes"
  set theQuery to my normalize("{query}")
  --playlist 1 should be your whole music library
  set theTracks to tracks of library playlist 1
  set match to ""
  
  repeat with i from 1 to number of items in theTracks
    set theTrack to item i of theTracks
    set theLyrics to my normalize(lyrics of theTrack)
    if theQuery is in theLyrics then
      set match to (artist of theTrack &amp;amp; " - " &amp;amp; name of theTrack)
      exit repeat
    end if
  end repeat
  
  if match is not "" then
    play theTrack
    get match
  else
    get "No match found"
  end if
end tell
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You’ll notice that it runs through your entire library in alphabetical order. Unfortunately, if the song you’re looking for is by ZZ Top the search is going to be a hell of a lot slower than if it were by ABBA.&lt;/p&gt;

&lt;p&gt;Also, lyrical clichés like “oh baby” or “tell me why” are likely to produce a match earlier than you expect so try to search for longer or less common phrases.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;If anyone has suggestions for improvements please let me know. I like the simplicity of the script so don’t plan to produce a playlist of search results or anything like that but feel free to use the Applescript as a starting point for your own script!&lt;/p&gt;

&lt;div class="footnotes"&gt;
  &lt;ol&gt;
    &lt;li id="fn:1"&gt;
      &lt;p&gt;It’s actually pretty fun to guess at lyrics and see what songs they appear in. If a search for “hands in the air” or “in da club” return in less than 10 seconds your iTunes may be due a clear-out. &lt;a href="#fnref:1" class="reversefootnote"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>Dynamic Regular Expressions</title>
    <link rel="alternate" href="http://www.samrayner.com/posts/dynamic-regex/"/>
    <id>http://www.samrayner.com/posts/dynamic-regex/</id>
    <published>2013-05-03T19:47:00+01:00</published>
    <updated>2013-11-05T16:09:00+00:00</updated>
    <author>
      <name>Sam Rayner</name>
    </author>
    <content type="html">&lt;p&gt;I faced an interesting challenge while developing &lt;a href="{{site_root}}/posts/alfred-sprintly"&gt;Sprint.ly for Alfred&lt;/a&gt; recently; how to provide a preview of a story title as the user typed it. A &lt;a href="http://en.wikipedia.org/wiki/User_story"&gt;story&lt;/a&gt; is a concept from &lt;a href="http://en.wikipedia.org/wiki/Agile_software_development"&gt;Agile software development&lt;/a&gt; that encapsulates the &lt;em&gt;who&lt;/em&gt;, &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt; of a feature.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;As a &lt;strong&gt;site owner&lt;/strong&gt;, I want &lt;strong&gt;a contact form&lt;/strong&gt; so that &lt;strong&gt;visitors can get in touch&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First off, I created a &lt;code&gt;Story&lt;/code&gt; class to hold the components of a story and a &lt;code&gt;title&lt;/code&gt; method to get them out as a formatted string:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Story
  attr_accessor :who, :what, :why

  def title
    #declare our separators
    prefixes = {
      who: "As a",
      what: "I want",
      why: "so that"
    }

    #go all grammar nazi
    prefixes[:who] &amp;lt;&amp;lt; "n" if @who &amp;amp;&amp;amp; ["a","e","i","o"].include?(@who[0,1])

    #format the story into a string
    "#{prefixes[:who]} #{@who}, #{prefixes[:what]} #{@what} #{prefixes[:why]} #{@why}"
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The tricky bit would be taking a string as input and parsing it into those attributes. I needed the title broken up to submit the parts separately to the &lt;a href="https://github.com/sprintly/sprint.ly-docs"&gt;Sprint.ly API&lt;/a&gt; as well as generate the preview on the fly.&lt;/p&gt;

&lt;p&gt;I wanted the preview to start out as &lt;em&gt;“As a __, I want __ so that __“&lt;/em&gt;, with the blanks being filled in as the user typed. I also wanted to give the option of a &lt;em&gt;aa iw st&lt;/em&gt; shorthand to speed up entry, and to allow the blanks to be filled in any order.&lt;/p&gt;

&lt;p&gt;Unable to write a standard regex that would cut it, I ended up with this system of appending to a regex as matches were found:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def parse_title(title)
  #default values
  @who = "__"
  @what = "__"
  @why = "__"

  #define our capture triggers
  captures = {
    "who" =&amp;gt; "as an?|aa",
    "what" =&amp;gt; " i want| iw",
    "why" =&amp;gt; " so that| st"
  }

  #get things started
  regex_str = '^\s*'

  #if the prefix is present, append a named capture group to the regex
  captures.each do |key,val|
    prefix = '(?:'+val+')\s+'
    if title.match(Regexp.new(prefix, true))
      regex_str &amp;lt;&amp;lt; prefix+'(?&amp;lt;'+key+'&amp;gt;.+)'
    end
  end

  #apply the final regex built from the string
  matches = title.match(Regexp.new(regex_str, true))

  if matches
    matches.names.each do |name| 
      value = matches[name].strip
      #strip out any trailing comma from the who
      value.sub!(/,$/, "") if name == "who"
      #store each component in the instance variables
      self.send(name+"=", value)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The solution greedily matches everything after a trigger phrase until another trigger is encountered and then breaks the string down.&lt;/p&gt;

&lt;p&gt;With that done, the last thing was to trigger parsing by calling a setter method on every keystroke:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def title=(value)
  self.parse_title(value)
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And it works pretty well!&lt;/p&gt;

&lt;iframe src="http://player.vimeo.com/video/65418688?portrait=0&amp;amp;byline=0&amp;amp;title=0" width="762" height="534" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""&gt;&lt;/iframe&gt;

&lt;p&gt;Download &lt;a href="{{site_root}}/posts/alfred-sprintly"&gt;Sprint.ly for Alfred&lt;/a&gt; to give it a spin. If you know a smarter way of doing this, I’d be really interested to hear it &lt;a href="http://twitter.com/samrayner"&gt;on Twitter&lt;/a&gt; or &lt;a href="&amp;amp;#109;&amp;amp;#97;&amp;amp;#105;&amp;amp;#x6c;&amp;amp;#x74;&amp;amp;#x6f;&amp;amp;#58;&amp;amp;#x73;&amp;amp;#97;&amp;amp;#109;&amp;amp;#64;&amp;amp;#115;&amp;amp;#x61;&amp;amp;#x6d;&amp;amp;#114;&amp;amp;#97;&amp;amp;#x79;&amp;amp;#110;&amp;amp;#101;&amp;amp;#x72;&amp;amp;#46;&amp;amp;#99;&amp;amp;#111;&amp;amp;#109;"&gt;via email&lt;/a&gt;.&lt;/p&gt;

</content>
  </entry>
</feed>
