Recently I’ve been working on a little project for Testing with Frank. In using it, I found very early on that the wait_for_nothing_to_be_animating function has some issues. I detailed it a little in my post about Frank’s Ruby API, but as a refresher, frank mistakenly thinks that the keyboard is always animating. This is a problem that I assume other people have either not tried to deal with, or have dealt with via sleep. To me, that is just not an option, so I modified the Shelly Source.
The modified *.a files, and the fact that I’m used to using package managers and git made me very unhappy the first time I set up a project to use frank.
For anyone that doesn’t know, to set up a project with frank, all you have to do is run frank setup and then when prompted, tell it which target to frankify. When you do this, it creates a Frank folder with all of the libraries frank needs to link with, an extra folder for any plugins you want, some *.xcconfig files, and a folder where you are expected to put all of your Cucumber tests. You are expected to put this whole folder, with the exception of the folder generated for frank build artifacts, into your source control system.
I guess this is sort of ok if you’re using SVN, but in Git, if you put a *.a file into your repo, it, and any other versions are there forever, and I really don’t like this. Additionally, if you have multiple targets, it quite possible that you will have developers that never actually need the extra files. I know this can seem like I’m living high and mighty up on my little soap box, but I don’t care.
The solution I came up with was to create my own little pack management tool for the frank dependencies. For the sake of sticking on topic, I’ll go over the theories in here, and then I’ll post my source at the end. If you’re interested, you can make it an exercise of your own to figure out how it works.
Executing Code From a File
I know this probably seems odd, but hear me out. If you’ve ever looked at a Gemspec, you’ll know that they usually look something like this (example from RubyGems Documentation)
1 2 3 4 5 6 7 8 9 10 11 |
Gem::Specification.new do |s| s.name = 'example' s.version = '0.1.0' s.licenses = ['MIT'] s.summary = "This is an example!" s.description = "Much longer explanation of the example!" s.authors = ["Ruby Coder"] s.email = 'rubycoder@example.com' s.files = ["lib/example.rb"] s.homepage = 'https://rubygems.org/gems/example' end |
Have you ever wondered how it is that you can write a Gemspec, in real Ruby code, and somehow it gets used? Normally when you do a require, a file will be read and executed, but in the case of a Gemspec, how do you get the return value? require will only return true or false, and in the Gemspec you aren’t setting any global variables.
The magic happens in Ruby’s eval method. Eval will evaluate a string as Ruby code and return the value. For example:
1 2 3 4 5 |
def double(x) return x * 2 end eval 'double 10' #=> 20 |
This may seem a little useless at first because couldn’t we just call double directly? Well remember, the intention is to have some code in 1 file, and evaluate it in another so that we don’t have to rely on any more convention that needed to have inter file communication.
Using eval, we can read in a whole file, then execute the code in it to save off our variable
1 |
object_from_file = eval(File.read('some_file')) |
C# like Object Initializer Syntax
Several times I’ve seen this fancy syntax while looking at Ruby code
1 2 3 |
SomeObject.new do |instance| instance.blah = foo end |
It’s very similar to C#’s object initializer syntax, but unfortunately, you don’t get this for free in Ruby. It’s pretty easy to get though. All you have to do is add this to your initialize method
1 |
yield self if block_given? |
There’s more information for yield and instance_eval over here
My Frank Downloader
Without further tangents, this is what I came up with for downloading the Frank linked libraries. I know that it’s not pretty, but it also only took me about 2 hours to do from start to finish.
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#! /usr/bin/env ruby require 'fileutils' class FrankSpec attr_accessor :version, :repo def initialize yield self if block_given? end end def eval_file(file) begin lines = File.readlines(file) rescue puts "Could not read [#{file}]" end begin return eval(lines.join(' ')) rescue puts "[#{file}] could not be evaluated" end end def download_frank(spec, frank) frankzip = frank + '.zip' puts "downloading version #{spec.version} from #{spec.repo}" FileUtils.rm_rf frankzip if File.exists? frankzip FileUtils.rm_rf frank if File.directory? frank `git archive --remote=#{spec.repo} --output=#{frankzip} #{spec.version}` FileUtils.mkdir_p frank unless File.directory? frank `tar -xf #{frankzip} --directory #{frank}` FileUtils.rm_rf frankzip if File.exists? frankzip end root = Dir.pwd + '/' frank = root + 'Frank' frank_version = frank + '/version' spec = eval_file(root + 'frank.spec') if File.exists? frank_version version = eval_file(frank_version) if(Gem::Version.new(version) < Gem::Version.new(spec.version)) puts "updating from #{version} to #{spec.version}" FileUtils.rm_rf(frank) download_frank(spec, frank) else puts "verison #{version} is up to date" end else download_frank(spec, frank) end |