Thank you for pressing the self-destruct button. –Spaceballs

I’m placing this article in Dropbox/_articles_from_ipad/017-08ff/spaceballs/. With luck, er I mean due to our incredible skill, when we run our new MVP iPad transfer program, the article will appear on my web site, with all due indexes updated. When Tozier gets here, our mission will be to make that happen, with as little carnage as possible.

The first question in my mind, even before T gets here, is whether _articles_from_ipad is the right folder, or perhaps _articles, which is also present in Dropbox. Lesson to learn: name folders with more meaningful names. A check of the code may help me determine this. I find this test:

  def set_up_for_really_jekylling
    # root here is /Users/ron/programming/test-ftp
    new_files_folder = '/Users/ron/Dropbox/_articles_from_ipad'
    jekyll_folder = '/Users/ron/programming/rj-com'
    ftp_host = 'localhost'    # ftp.ronjeffries.com ?
    ftp_target_folder = '/Users/ron/programming/test-ftp/_scary_target'    # httpdocs
    password_prefix = 'TEST_' # PROD_
    return JekyllRunner.new(new_files_folder, jekyll_folder, 
      ftp_host, ftp_target_folder, password_prefix)
  end

This tells me that the intention we had, the last time we met, was to use the _articles_from_ipad folder as our source folder. Super.

This also gives me a frisson of fear, when I see the ftp_host and ftp_target_folder setting, because we have still not pointed this thing at the real site and let it do anything. Let’s explore that fear, hoping that it will suggest a test, and some code, to provide the necessary confidence to turn this thing loose.

Fear, still the mindkiller …

Clearly a rogue FTP writing my site could do very bad things. I could create a full backup, but there are a vast number of files. I do have the ability to regenerate the whole site in Jekyll, and if I were to do that and push it up with my normal FTP tool, Transmit, it should restore OK. That’s not a thing I want to do lightly, and certainly not one I want to do from the coffee shop. However, it does mean that we probably can’t do permanent harm.

In addition, my ISP allegedly does backups and could therefore allegedly restore the site. The reliability of that process has not been tested and while I like these people, I don’t trust the backup. I trust my own build more, since it is set up to enable me to move the whole site anywhere with nothing but FTP.

The only other thing I can think of that could go wrong would be if the transfer were to put files where they don’t belong. That should be mostly harmless. If they don’t overwrite anything that should be there, there will be extra files but no one will link to them. If they do overwrite something, restoring the site would put the right stuff back.

So, in principle, I should feel pretty good about this. And yet, I have fear. Fear is nature’s way of telling us to look the hell out, and it is sometimes appropriate and sometimes not. No one is likely to die from this thing going wrong, but I could be inconvenienced, which would be bad, and embarrassed, which would be worse, since I’d have to tell the world about it.

T arrives. We chat. We agree that the biggest question before us is the fact that my web site has data in the httpdocs folder under its root, so we have to get the folder settings just right.

Tozier asks whether we could mock up a set of files and a manifest that will move just one easily identified file to the right place (or a discernibly wrong place). We look at the code:

require "net/ftp"
require "./passwords"

class JekyllRunner
...
  def ftp_the_files
    move_the_categories
    move_feed_xml
    move_index_html
    move_the_articles
  end
...
  def move_feed_xml
    move_batch("#{@jekyll_folder}/_site/", '.', ['feed.xml'])
  end
...
  def run
    move_ipad_files
    run_jekyll
    ftp_the_files
  end
end

If we set up a JekyllRunner with the parameters pointing to the generated site on my Mac, and to the real FTP location, and then call move_feed_xml, we can check whether that file goes to the right place. If it does, we should feel much more confident. Let’s do that and see what happens.

We’ll copy the old file somewhere, create one we can recognize, run the app, and then check with Transmit to see what’s up there. Or maybe have our test re-download it (Tozier suggests). That’s better. Will we do it? Will we do it right? We don’t know yet, wait and see.

  # def test_in_live
  #   jr = set_up_for_really_jekylling
  #   jr.run
  # end

  def test_move_feed_to_site
    jr = set_up_for_really_jekylling
  end

Building from a sketched test from last time, we write a test that doesn’t test anything yet. We update the set up method:

  def set_up_for_really_jekylling
    # root here is /Users/ron/programming/test-ftp
    new_files_folder = '/Users/ron/Dropbox/_articles_from_ipad'
    jekyll_folder = '/Users/ron/programming/rj-com'
    ftp_host = 'ftp.ronjeffries.com'
    ftp_target_folder = 'httpdocs'
    password_prefix = 'PROD_'
    return JekyllRunner.new(new_files_folder, jekyll_folder, 
      ftp_host, ftp_target_folder, password_prefix)
  end

My biggest concern is the ftp_target_folder. Should it be httpdocs, or perhaps /httpdocs. I guess we’ll find out. Tozier wants to run now. Basically he wants to test the initialize method, I guess. Never refuse to run the tests, so here goes:

They run and Tozier feels better. I guess I do, too, but I’m not going to admit it. Now we’ll back up our old feed.xml and enhance the test.

Tozier suggests that we make a backup of the feed, and write a test that moves a new fake feed, checks to see that it’s there, then puts the old one back. I demur. My concern is that if the move doesn’t work, the fix won’t work either. So I propose to let it do the move, then check manually first to see if it’s there. Then, we’ll talk about extending the test. So …

Air:rj-com ron$ cd _site
Air:_site ron$ cp feed.xml feed2.xml
Air:_site ron$ echo "hi there" >feed.xml
Air:_site ron$ cat feed.xml
hi there
Air:_site ron$
  def test_move_feed_to_site
    jr = set_up_for_really_jekylling
    jr.move_feed_xml
  end

Are we ready to set this loose? We decide, after a few other random things, to use Transmit to watch the site. We observe the feed.xml in its old state, and it is good. Now we run the tests. They hang. We instrument:

  def move_batch(local_site_folder, ftp_target_folder, folders_and_files )
    perform_in_folder(local_site_folder) do 
      puts "connecting"
      ftp_connection = ftp_connected_to_target
      puts "connected"
      ftp_connection.chdir(ftp_target_folder)
      puts "on folder"
      folders_and_files.each do |file|
        puts "moving a file"
        ftp_one_file(ftp_connection, file)
      end
      puts "closing"
      ftp_connection.close
      puts "closed"
    end
  end

We hang moving a file. (I kind of wish I’d said “done moving a file”.) With some added printing, we run again and it hangs. We chat, giving it time enough to time out, with this result from our tests:

Run options: --seed 54425

# Running:

.connecting
connected to .
on folder
moving a file: feed.xml
E
...
Finished in 72.522028s, 0.1241 runs/s, 0.1379 assertions/s.

  1) Error:
Test_JekyllRunner#test_move_feed_to_site:
Net::ReadTimeout: Net::ReadTimeout
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/protocol.rb:158:in `rescue in rbuf_fill'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/protocol.rb:152:in `rbuf_fill'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/protocol.rb:134:in `readuntil'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:1108:in `gets'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:1113:in `readline'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:290:in `getline'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:301:in `getmultiline'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:319:in `getresp'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:352:in `block in sendcmd'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/monitor.rb:211:in `mon_synchronize'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:350:in `sendcmd'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:430:in `transfercmd'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:547:in `block (2 levels) in storbinary'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:199:in `with_binary'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:546:in `block in storbinary'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/monitor.rb:211:in `mon_synchronize'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:545:in `storbinary'
    /Users/ron/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/net/ftp.rb:694:in `putbinaryfile'
    /Users/ron/programming/test-ftp/jekyllrunner.rb:104:in `ftp_one_file'
    /Users/ron/programming/test-ftp/jekyllrunner.rb:73:in `block (2 levels) in move_batch'
    /Users/ron/programming/test-ftp/jekyllrunner.rb:71:in `each'
    /Users/ron/programming/test-ftp/jekyllrunner.rb:71:in `block in move_batch'
    /Users/ron/programming/test-ftp/jekyllrunner.rb:44:in `call'
    /Users/ron/programming/test-ftp/jekyllrunner.rb:44:in `perform_in_folder'
    /Users/ron/programming/test-ftp/jekyllrunner.rb:65:in `move_batch'
    /Users/ron/programming/test-ftp/jekyllrunner.rb:93:in `move_feed_xml'
    /Users/ron/programming/test-ftp/test-jekyll-runner.rb:61:in `test_move_feed_to_site'

As we read this stack we see that the FTP is trying to

We clean up the local test so the setup format matches our newer one:

  def set_up_for_jekyll_testing
    FileUtils.rm_rf("./_target")
    FileUtils.mkdir("./_target")
    FileUtils.mkdir("./_target/articles")
    FileUtils.mkdir("./_target/categories")
    FileUtils.rm_rf("./test_jekyll_site/articles")
    ipad_folder = '/Users/ron/Dropbox/_source'
    jekyll_folder = './test_jekyll_site'
    ftp_host = 'localhost'
    ftp_target_folder = 'programming/test-ftp/_target/'
    password_prefix = 'TEST_'
    return JekyllRunner.new(ipad_folder, jekyll_folder, 
      ftp_host, ftp_target_folder, password_prefix)
  end

This lets us better inspect the two, and Tozier notices that the target folder ends in a slash in the local test, which works, and not in the remote, which doesn’t. In for a penny, let’s try that:

  def set_up_for_really_jekylling
    # root here is /Users/ron/programming/test-ftp
    new_files_folder = '/Users/ron/Dropbox/_articles_from_ipad'
    jekyll_folder = '/Users/ron/programming/rj-com'
    ftp_host = 'ftp.ronjeffries.com'
    ftp_target_folder = 'httpdocs/'
    password_prefix = 'PROD_'
    return JekyllRunner.new(new_files_folder, jekyll_folder, 
      ftp_host, ftp_target_folder, password_prefix)
  end

Still hangs, same way we think. We think we’ll try FTP’s debug_mode. It seems to me that we should turn off all the tests but our favorite, as well.

  def move_batch(local_site_folder, ftp_target_folder, folders_and_files )
    perform_in_folder(local_site_folder) do 
      puts "connecting"
      ftp_connection = ftp_connected_to_target
      ftp_connection.debug_mode = true
      puts "connected to #{ftp_target_folder}"
      ftp_connection.chdir(ftp_target_folder)
      puts "on folder"
      folders_and_files.each do |file|
        puts "moving a file: #{file}"
        ftp_one_file(ftp_connection, file)
        puts "done moving #{file}"
      end
      puts "closing"
      ftp_connection.close
      puts "closed"
    end
  end

We run some debug mode and learn very little. The file’s just not going, though the connection seems to be working. The FTP object says it is connected to /httpdocs. Tozier is concerned about the slash. Interestingly there is no slash in our input, so where did it come from?

As we flail, we check Transmit with a restart and it shows feed.xml with zero length. This tells us, we think, that our test has actually managed to touch and empty the file, but not put data into it.

We realize we’ve never directly tested using FTP to put a file on my site, back in our other earlier tests. We’d better do that: there’s apparently something about how we’re using FTP. We move into our old test of ftp, and produce this:

require "minitest/autorun"
require "net/ftp"
require "./passwords"

class Test_FTP  < Minitest::Test

  # utility functions
...
  def ftp_to_site
    ftp = Net::FTP.new('ftp.ronjeffries.com')
    host = Object.const_get('Passwords::' + 'PROD_' + 'USER')
    pass = Object.const_get('Passwords::' + 'PROD_' + 'PASSWORD')
    ftp.passive = true
    ftp.login(host,pass)
    puts "#{ftp.pwd}"
    puts "#{ftp.status}"
    # puts "top list #{ftp.list.inspect}"
    ftp.chdir('./httpdocs')
    ftp
  end
...
  # actual tests

  def setup
    @folder = Dir.pwd
    FileUtils.rm_rf("./_target")
    FileUtils.mkdir("./_target")
  end

  def teardown
    Dir.chdir(@folder)
  end
...
  def test_read_site
    puts "test_read_site"
    ftp = ftp_to_site
    ftp.debug_mode = true
    puts "#{ftp.pwd}"
    # puts "file: #{ftp.gettextfile('feed.xml').inspect}"
    puts "list #{ftp.nlst}"
    # puts "feed: #{ftp.gettextfile('feed.xml')}"
    puts "end test_read_site"
    ftp.putbinaryfile('to_move.txt')
  end
end

From this test, which we checked manually for now, we learned that we needed passive mode. When we set passive in the ftp_to_site method, our test began to produce the right results, so far only shown in our console. (We promise ourselves that we’ll improve this test.) But wait! We had already set passive in the failing test:

  def move_batch(local_site_folder, ftp_target_folder, folders_and_files )
    perform_in_folder(local_site_folder) do 
      puts "connecting"
      ftp_connection = ftp_connected_to_target
      ftp_connection.debug_mode = true
      ftp_connected_to_target.passive = true
      puts "connected to ftp, chdir #{ftp_target_folder}"
      ftp_connection.chdir(ftp_target_folder)
      puts "on folder #{ftp_connection.pwd}"
      folders_and_files.each do |file|
        puts "moving a file: #{file}"
        ftp_one_file(ftp_connection, file)
        puts "done moving #{file}"
      end
      puts "closing"
      ftp_connection.close
      puts "closed"
    end
  end

See? There it is right there, passive=true. Oh. Look again. We just hammered a convenient new member variable into whatever ftp_connected_to_target returns, namely a new, unused, instance of FTP. Our setting of passive has no effect. It is null, and void. It is a dead parrot.

Excuse me while I curse a bit. We fix the problem:

  def move_batch(local_site_folder, ftp_target_folder, folders_and_files )
    perform_in_folder(local_site_folder) do 
      puts "connecting"
      ftp_connection = ftp_connected_to_target
      ftp_connection.debug_mode = true
      ftp_connection.passive = true
      puts "connected to ftp, chdir #{ftp_target_folder}"
      ftp_connection.chdir(ftp_target_folder)
      puts "on folder #{ftp_connection.pwd}"
      folders_and_files.each do |file|
        puts "moving a file: #{file}"
        ftp_one_file(ftp_connection, file)
        puts "done moving #{file}"
      end
      puts "closing"
      ftp_connection.close
      puts "closed"
    end
  end

And all the tests run, just as we expected three hours ago. Wow. Now of course we have debug statements all over. We’ll clean those up Thursday. For now, let’s think about what we could have done to avoid this long and deep rat hole.

Retrospective …

We were fairly confused because it was timing out, and Tozier remembered the passive mode from of old. We decided to put it in. We put it in the wrong place, the tests didn’t run. We decided we’d better write tests in our earlier test_ftp.rb, which had never actually written to the real site.

We messed with that rather too long, because we had, we thought, eliminated passive as something that mattered. So we walked through to the same place as in our original test. We googled again why it’s not returning anything, and so we again put passive in. We continued to run our little test and it seemed at first to return no data from get. We finally noticed that, despite confusing Ruby documentation, that it just creates a file in the current folder with the same name as the file you get. This caused a great insight: “IT’S BEEN WORKING ALL ALONG”.

That being the case, we looked back at the test_jekyll_runner test, because it “should” have been working too. Tozier spotted the typo (which was an auto-complete error). Had we typed out the variable name originally, I believe, we’d likely not have made the error. We were just unlucky that the auto-completed name also accepted passive=true.

Except that I don’t like answers like “we were just unlucky”. What might we have done better? What’s to learn other than “pay better attention”?

We should probably have turned sooner to our simpler test. FTP wasn’t working, and we have a test file for FTP. The failure was in our JekyllRunner tests, which obfuscated things. We “should” have looked at our older tests, noticed that we didn’t ever connect to a real FTP and started digging in sooner.

Note that we had not run those tests for a long time, and they didn’t work at all, because of our new passwords.rb format. Something something about a suite of tests instead of just running the ones we care about. Wouldn’t really have made a difference here but it would be better practice.

Had we turned to the simpler test sooner, we might have had fresher mind when we rediscovered the need for passive. We might have said wait what we did that and looked at the bad line sooner. Maybe. Anyway, there’s a lesson here about turning quickly to the more basic tests, and a lesson about running all the tests all the time.

Beyond turning to our simpler tests sooner, what might have helped?

Tozier notes that we have a lot of variables and methods whose names start with ftp_. Is this a problem? Yes, it is. Why, Ron, you ask.

Well, grasshopper, what those names tell us is that in our program there is an idea, the “ftp thing” that is trying to be born. Our JekyllRunner does a lot of FTP stuff, and a lot of non-FTP stuff, and they’re only broken out by a weak1 naming convention. So there’s an abstraction missing. The code does not “express all the programmer’s ideas”, as we were taught long ago.

We give ourselves a bit of a back-pat for having made some headway, with our connection object, but obviously we’ve not done enough.

Ron recalls another place where there seems to be ideas missing. We have vast tracts of arrays of strings that we pass around. Object-oriented programming 101 tells us that strings are a weak (q.v.) abstraction and that they are always calling for a smarter object. Bottom line, this code, despite all the cleaning we have done, is messy enough to confuse us.

Think about this! We’ve got less than 400 lines of code and tests, and that’s enough to leave us confused. If we press forward with this kind of thing on a real project, we’ll have 4,000 lines, 40,000 lines, a half a million lines of code that we struggle to understand. This tiny program is a microcosm that tells us how clean we need to be.

Honestly, we were feeling pretty good about the code. We were planning, this morning, to ship our MVP. And we still may do that. But the code is not good enough to be maintainable.

Next Thursday

We talk about what we might do next time. T suggests that we need to remove all our debugging instruments. I ask “was there a moment where that many put statement should have triggered a reset?” T nods. Anyway we’ll clean those out.

Then we face a decision. Make it run (which I now am rather confident it does), or make it better? We’re under strong pressure from our Product Owner to get this article out, and there’s no way to do it until this program works.

We’re not confident enough to do it today, so the soonest is Thursday.

What else might we do? We might build an object of some kind, whatever the code is asking for, make that work (heck, maybe even test it), and then think about deploying. What we’ll do, for sure, is reflect again, with fresh minds and eyes, and decide then.

Tozier thinks he’d like to finish what we set out to do today, namely a single JekyllRunner level test that moves a single test file. I think I agree. That test needs elaboration and we now probably know enough FTP that we can put a file up and then read it back, and perhaps even delete it.

NARRATOR: They don’t know enough. They still don’t know how to get the text out of an FTP’d file.

We have more to do, such as the cron job, and we can manually run the thing to put up our current article. We need to build the cron, deciding issues like how often we’ll really run this. We think we need to clean up the paths that fly all around this thing. That may be a call for another object or two. Additionally, I’ve been thinking about materializing a Manifest object that represents the information about what’s in the iPad folder and therefore what needs to be moved. I’ve been thinking that if we had a Manifest, we could put dates in it and at some future time have incremental build.

Tozier wants more confidence around moving binary and text files. We’ve been thinking we can use binary for everything, because we really don’t want to work out what types files actually are, but we don’t know for sure how well that’ll work.

At that, our brains are full.

See you Thursday …

Thursday!

We mill around a bit, refreshing our minds. We remove a bunch of debugging puts from our test-ftp and it all runs. Skim what follows and we’ll talk about the important bits just below:

require "minitest/autorun"
require "net/ftp"
require "./passwords"

class Test_FTP  < Minitest::Test

  # utility functions

  def ftp_connected_to_target
    ftp = Net::FTP.new('localhost')
    ftp.passive = true
    host = Object.const_get('Passwords::' + 'TEST_' + 'USER')
    pass = Object.const_get('Passwords::' + 'TEST_' + 'PASSWORD')
    ftp.login(host,pass)
    ftp.chdir('programming/test-ftp/_target')
    ftp
  end

  def ftp_to_site
    ftp = Net::FTP.new('ftp.ronjeffries.com')
    host = Object.const_get('Passwords::' + 'PROD_' + 'USER')
    pass = Object.const_get('Passwords::' + 'PROD_' + 'PASSWORD')
    ftp.passive = true
    ftp.login(host,pass)
    ftp.chdir('./httpdocs')
    ftp
  end

  def ftp_folder_exists?(ftp, folder_string)
    split = folder_string.split("/")
    proposed_folder = split.last
    ppl = split[0...-1]
    proposed_prefix = "./" + ppl.join("/")
    ftp.list(proposed_prefix).any? { |name| name.match(proposed_folder) }
  end

  def ftp_mkdir_safely(ftp, folder_string)
    ftp.mkdir(folder_string) unless ftp_folder_exists?(ftp, folder_string)
  end

  # actual tests

  def setup
    @folder = Dir.pwd
    FileUtils.rm_rf("./_target")
    FileUtils.mkdir("./_target")
  end

  def teardown
    Dir.chdir(@folder)
  end

  def test_read_site
    ftp = ftp_to_site
    ftp.putbinaryfile('to_move.txt')
  end

  def test_target_is_empty
    target = Dir.glob('_target/*') 
    assert_equal(0,target.length)
  end

  def test_connect_finds_no_files
    ftp = ftp_connected_to_target
    list = ftp.list('*')
    ftp.close
    assert_equal(0,list.length)
  end

  def test_overwrite_works
    ftp = ftp_connected_to_target
    oldDir = Dir.pwd
    Dir.chdir("/Users/ron/Dropbox/_source")
    ftp.putbinaryfile("a.txt", "overwritten.txt")
    ftp.putbinaryfile("b.txt", "overwritten.txt")
    expected = File.read('b.txt')
    ftp.close
    Dir.chdir(oldDir)
    stuff = File.read('_target/overwritten.txt')
    assert_equal(expected, stuff)
  end

  def test_upload_files
    ftp = ftp_connected_to_target
    oldDir = Dir.pwd
    Dir.chdir("/Users/ron/Dropbox/_source")
    source = Dir.glob('**/*').sort
    source.each do |file_name|
      if File::directory? file_name
        ftp_mkdir_safely(ftp, file_name)
      else
        ftp.putbinaryfile(file_name,file_name)
        ftp.putbinaryfile(file_name,file_name)
      end
    end
    ftp.close
    Dir.chdir(oldDir)
    Dir.chdir("_target")
    target = Dir.glob('**/*').sort
    assert_equal(source, target)
    Dir.chdir(oldDir)
  end

  def test_make_subfolders_safely
    ftp = ftp_connected_to_target
    ftp_mkdir_safely(ftp, "subfolder")
    ftp_mkdir_safely(ftp, "subfolder")
    ftp_mkdir_safely(ftp, "otherfolder")
    ftp_mkdir_safely(ftp, "otherfolder")
    ftp_mkdir_safely(ftp, "otherfolder/subsubfolder")
    ftp_mkdir_safely(ftp, "otherfolder/subsubfolder")
    ftp.putbinaryfile("test-ftp.rb","otherfolder/subsubfolder/RUBY.rb")
    ftp.close
    exists = Dir.exist?("_target/subfolder")
    assert(exists,"folder not made")
    exists = Dir.exist?("_target/otherfolder/subsubfolder")
    assert(exists,"subfolder not made")
  end

  def test_jpg_cant_be_puttexted
    ftp = ftp_connected_to_target
    Dir.chdir("/Users/ron/Dropbox/_source")
    ftp.putbinaryfile('pic.JPG','../poc.JPG')
    ftp.putbinaryfile('a.txt', '../a.txt')
    ## we inspect these manually. so sue me.
  end
end

Our newest test is the only one we care about:

  def ftp_to_site
    ftp = Net::FTP.new('ftp.ronjeffries.com')
    host = Object.const_get('Passwords::' + 'PROD_' + 'USER')
    pass = Object.const_get('Passwords::' + 'PROD_' + 'PASSWORD')
    ftp.passive = true
    ftp.login(host,pass)
    ftp.chdir('./httpdocs')
    ftp
  end

  def test_move_file_to_site # checked manually
    ftp = ftp_to_site
    ftp.putbinaryfile('to_move.txt')
  end

Note that we just moved the file and looked. We didn’t automate reading it back. This may come back to haunt us: I promise to confess if it does.

These tests gave us the confidence to add a file-moving test to our JekyllRunner tests, which we ran once and also commented out:

  # following test we checked manually and now trust it
  # cf. hold my beer

  # def test_move_test_file_to_site
  #   jr = set_up_for_really_jekylling
  #   testfile = "#{jr.jekyll_folder}/_site/test-file-do-not-read.txt"
  #   FileUtils.touch(testfile)
  #   jr.move_test_file
  # end

  def move_test_file
    move_batch("#{@jekyll_folder}/_site/", '.', ['test-file-do-not-read.txt'])
  end

Note that we just put a test method into JekyllRunner. Tozier points out that we could reopen JR in our test and inject a method into it, thus not contaminating JR’s source with test methods. We don’t remember the syntax, though we do have the Internet right here. In addition this seems to me to be pretty deep in the bag of tricks.

Chet wonders if it would be better to subclass a TestJekyllRunner and put the method there. I’m feeling like “meh” on both these ideas. T’s concern is that when we tried calling the inside method move_batch from the test, it didn’t hook up correctly, and creating the move_test_file method in JekyllRunner did work. I’m disinclined to chase code that was invading the class under test in any case.

We have checked that a file moves to the correct place on my web site, using the JekyllRunner as set up in our tests. This gives me good but not great confidence that a correctly-configured JekyllRunner, running directly from command line, will correctly copy down iPad articles, run Jekyll, and put the results on my site.

Now I have to ask myself Do you feel lucky, punk? Am I willing, finally, to set up a main program running JR, and set it loose manually? I guess after I push everything here to GitHub, I am.

OK. What do we have to do to run a JekyllRunner by hand? Tozier suggests:

  1. Make sure this article is saved in the iPad input folder.
  2. Construct a new JekyllRunner with the arguments as in our tests.
  3. Run it
  4. Look and see what happened.

I guess we can do the second item2 with a main in the jekyllrunner.rb file. Well, no! That would mean that if we accidentally run it here in our editor, it would do its thing. We’ll create a new file, jr.rb just for this purpose.

Tozier says that at this point one would normally create a Ruby Gem, install it, and then it’d be available with a suitable require. This is a yak I didn’t see coming and frankly I’m trying to scare it away. I want to know why we don’t just copy the JR file into my site, along with the passwords file.

We may want to package this up later, figure out a release build and such but for now I’m inclined to copy the two files into my Jekyll source folder. OK, that’s done, now to make jr.rb.

require "./jekyllrunner"

new_files_folder = '/Users/ron/Dropbox/_articles_from_ipad'
jekyll_folder = '/Users/ron/programming/rj-com'
ftp_host = 'ftp.ronjeffries.com'
ftp_target_folder = 'httpdocs/'
password_prefix = 'PROD_'
jr = JekyllRunner.new(new_files_folder, jekyll_folder, 
  ftp_host, ftp_target_folder, password_prefix)
jr.run

We run it, and the fact that you’re reading this shows that it worked.



  1. Tozier suggests “half-assed”.

  2. Pardon the circumlocution, I just couldn’t bring myself to say “Number 2” in a serious article like this one.