Err the Blog Atom Feed Icon
Err the Blog
Rubyisms and Railities
  • “Cappin' that Stat”
    – PJ on June 19, 2007

    vJot’s a public code base, so I saw it unbecoming of an open-source guy to add my stats html to the repo. No worries, I’ll just add it with a little help via the wonder library, Capistrano:

    task :after_symlink, :roles => :app do
      stats = <<-JS
        <script src="http://www.google-analytics.com/urchin.js">
        </script>
        <script type="text/javascript">
        _uacct = "UA-104904-8";
        urchinTracker();
        </script>
      JS
      layout = "#{current_path}/app/views/layouts/application.html.erb"
      run "sed -i 's?</body>?#{stats}</body>?' #{layout}"
    end
    

    The code appends my Analytics’ javascript to the layout file and voilà, instant stats!

    The secret sauce is that if you’ve defined an after_symlink task, Capistrano will run it during your regularly scheduled cap deploy. As a matter of fact, it has before_ and after_ hooks for all its tasks, including any tasks that you’ve written. Schweet.

    Update: Thanks goes out to the unix gurus in the audience for shortening up my sed code.

  • evan, about 3 hours later:

    Maybe you need to double-escape your sed backslashes?

  • PJ, about 4 hours later:

    I’m rocking the ? instead of / to avoid escaping woes, but maybe I’m missing something obvious.

  • Sam Pohlenz, about 4 hours later:

    sed’s in-place option (-i) is what you’re after.

    run “sed -i’’ ’s??#{stats}?’ #{layout}”

    should do the trick.

  • Gabe da Silveira, about 4 hours later:

    What might not be immediately obvious is that you can chain these together. That might seem useless, but consider that the RailsMachine gem already has an :after_symlink task. Rather than muck around the gem just define :after_after_symlink. Booya! (Thanks Bradley Taylor for that one).

  • Iain, about 8 hours later:

    The reason `cat x| foo > x` doesn’t work is that x is zeroed by the redirect before cat has a chance to read anything from it.

  • noob, about 9 hours later:

    Well its because the sed’s output is writen simultaneusly with sed’s data processing. So bascicly your data gets trunclated first processed then :)

  • Todd, about 10 hours later:

    Could be because sed is a line editor and your tag is on it’s own line?

  • Shot, about 13 hours later:

    As noob pointed out, you’re most probably truncating the layout file with sed before cat can cat the second line from it.

    Also, sed can get the input file as one of its parameters, so it’s a useless cat there.

    Either sed -i, or use sponge.

  • Vrensk, about 14 hours later:

    Noob and Shot get it almost right. The reason you can’t do it that way is that the redirection is handled by the shell before the command is run. It goes something like this:

    1. Parse the command line into three chunks:
      1. cat /whatever/app/views/layouts/application.html.erb
      2. sed '...'
      3. /whatever/app/views/layouts/application.html.erb
    2. Create a pipe.
    3. Open
      /whatever/app/views/layouts/application.html.erb
      for writing, thereby truncating the file.
    4. Fork, and replace the child (exec) with
      cat /whatever/app/views/layouts/application.html.erb
      where STDOUT is one end of the previously created pipe.
    5. Fork again, and replace the new child with
      sed '...'
      where STDIN is one end of the pipe and STDOUT is the filehandle created to the file.

    I suppose the reason it’s done this way is that it is fail-early. If you redirect to a file in a non-existing directory, the shell will notice and will never run your (potentially) destructive commands.

  • Dylan, about 15 hours later:

    Instead of

    layout = ”#{current_path}/app/views/layouts/application.html.erb” run “cat #{layout} | sed ’s??#{stats}?’ > ~/app.tmp” run “mv ~/app.tmp #{layout}”

    Just use

    run “sed -i ’’ -e ’s??#{stats}?’ #{current_path}/app/views/layouts/application.html.erb”

  • PJ, about 17 hours later:

    So many unix badasses reading err. I’ll update the post shortly with my new found knowledge. Thanks guys.

  • Stephen Touset, about 18 hours later:

    Others have already chimed in with the correct answer. POSIX shells run all the steps of a pipe chain in parallel. This includes the redirection at the end, which truncates your original file. Net result is that the file is truncated before it has a chance to be cat’d.

    A few solutions for this exist. Instead of cat’ing the file to sed, use sed’s -i (in-place) flag as indicated earlier. The other option is to install the Debian “moreutils” package, which has a utility called sponge. “cat foo | sponge foo” will cause sponge to “soak up” standard in completely, and flush to the named file once it hits the EOF.

  • Dr J, about 20 hours later:

    Just wondering why you are doing this with capistrano insead of just putting the code into your layout file? For instance, I just have <%= GOOGLE_ANALYTICS %> in all my layout files and then in my development.rb and production.rb config files I set the GOOGLE_ANALYTICS variable.

  • Chris, about 20 hours later:

    Wow, I love you guys.

  • Simon, 1 day later:

    Ahh, thanks for a great idea. Gave me another idea how to use capistrano.

  • Fifteen people have commented.
    Chime in.
    Sorry, no more comments :(
This is Err, the weblog of PJ Hyett and Chris Wanstrath.
All original content copyright ©2006-2008 the aforementioned.