Dynamic Web URLs with ExpanDrive

Often when I work with ExpanDrive the files I am working on correspond to some website that I own. When I’m mounted with ExpanDrive each file is accessible via “my hard drive” in the mounted volume and, more importantly, from a web URL! I found myself repeatedly opening up my browser and manually typing the URL for files that I just uploaded or edited. This was error prone, especially if some of the characters needed encoding. So, I spent some time to write up a Ruby script that can read ExpanDrive’s preferences, build the file’s “web URL,” and open it in your default browser.

expanurl usage

Simple to Use

I followed along with ExpanDrive’s previous command line tool named expan and named my script expanurl. Given no arguments it will open the current directory via its web URL, or you can give a list of files and each will be opened at their web URLs. Its usage is pretty straightforward but there is a single catch: the server setting in an ExpanDrive Drive may not be a true one-to-one mapping with the web server’s address.

For example: I provide holly.cs.rit.edu as the server value for one of my personal ExpanDrive drives. However, when I view files on that server (inside the public_html directory) they have a much different looking URL: http://www.cs.rit.edu/~jjp1820/. The result? The script simply keeps its own mapping of ExpanDrive server values to their associated web page prefixes. When you use expanurl on a Drive you have never used it on before the script will prompt you for that mapping, store the value, and never ask again.

Here it is in action. I have removed all stored mappings so I can demonstrate what it would be like using expanurl for the first time. Here I use it on my BogoJoker ExpanDrive drive:

expanurl first usage

Notice that in the prompt it tells you:

  • the server that the ExpanDrive volume is linked to and the one you will be providing a web url prefix for
  • an example of a web url prefix (useful)
  • where the mappings are stored in case you need to edit them later

The script is available on GitHub, so feel free to contribute and improve. Here is a link to the always current version, and here is a snapshot of the current version at the time of writing:

 

#!/usr/bin/env ruby
# Author: Joseph Pecoraro
# Date: Saturday December 13, 2008
# Description: When I'm using ExpanDrive and I'm
# remotely logged into a server, I can use this
# script to "open filename" and it will open using
# the server's associated URL.

# For URL Escaping and Stored Mappings
require 'uri'
require 'yaml'

# Exit with a msg
def err(msg)
  puts msg
  exit 1
end

# Simple Class to handle the mappings
class UrlMap
  
  MAP_PATH = File.expand_path('~/.expanurl')
  
  def initialize
    @hash = load
  end
  
  def load
    if File.exists?(MAP_PATH)
      YAML::load_file( File.expand_path(MAP_PATH) )
    else
      Hash.new
    end
  end
  
  def add_mapping(server, mapto)
    @hash[server] = mapto
    File.open(MAP_PATH, 'w') do |file|
      file.write(@hash.to_yaml)
    end
  end
  
  def is_mapping?(server)
    @hash.has_key?(server)
  end
  
  def get_mapping(server)
    @hash[server]
  end
  
  def path
    MAP_PATH
  end
  
end


# Local Variables
mapping = UrlMap.new
url_prefix = nil
server = nil
volume = nil

# Check the if the current directory is an
# ExpanDrive Volume and a public_html folder
pwd = `pwd`
match = pwd.match(/^\/Volumes\/([^\/]+)/)
if match.nil?
  err("Not inside an ExpanDrive Volume")
elsif !pwd.match(/\/public_html\/?/)
  err("Not inside a public_html directory.")
else
  volume = match[1]
  defaults = `defaults read com.magnetk.ExpanDrive Drives`
  defaults.gsub!(/\n/, '')
  props = defaults.match(/\{[^\}]+driveName\s+=\s+#{volume}[^\}]+server\s+=\s+"([^"]+)"[^\}]+\}/)
  if props
    server = props[1]
  else
    err("This Volume (#{volume}) is not an ExpanDrive Volume")
  end
end

# Check if a mapping exists
# Otherwise create and store one
if mapping.is_mapping?(server)
  url_prefix = mapping.get_mapping(server)
else
  
  # Prompt
  puts
  puts "This is the first time you've used expanurl for #{volume}"
  puts "Please Provide us with a mapping for #{server}"
  puts "Mappings are stored in #{mapping.path}"
  puts "Example: http://bogojoker.com/"
  print ">> "
  
  # Store user input and proceed
  url_prefix = gets.chomp
  url_prefix += '/' unless url_prefix.match(/\/$/)
  mapping.add_mapping(server, url_prefix)
  
  # Terminal Output
  puts
  puts "Server: #{server}"
  puts "Maps to: #{url_prefix}"
  puts

end

# Build the URL
subpath = pwd.match(/public_html\/?(.*)/)[1]
subpath += '/' unless subpath.length.zero? || subpath.match(/\/$/)
url_prefix += subpath

# If No Files, open the directory
# Otherwise,   open each provided file
if ARGV.size == 0
  `open #{url_prefix}`
else
  ARGV.each do |filename|
    `open #{url_prefix}#{URI.escape(filename)}`
  end
end

 

How it Works

The Ruby Script grabs the current working directory using `pwd` and checks to make sure you’re in an ExpanDrive volume. ExpanDrive volume’s are dynamically generated by parsing the ExpanDrive preferences thanks to their foresight to make them accessible via the `defaults` command. So if you’re in an ExpanDrive volume and inside a public_html directory expanurl will then use its mapping to open a uri encoded web url in your default browser with the `open` command.

The mappings are stored in a hidden YAML file in your home directory (~/.expanurl). This style of storing preferences is just like dozens of other command line applications and scripts. YAML is just a lightweight textual data format popular with Ruby, similar to JSON and XML. Its so simple that you could edit the file yourself if you wanted/needed to. For instance here is what is in mine, just two simple key/value pairs:

~/.expanurl yaml mapping

The Future

Its just that simple. Being a Ruby Script you can call this from GUI applications, anything with built-in shell access, etc. It should play friendly with your usual Unix tools. I will likely make this script more and more robust if others find it useful, so I’d be happy to hear some feedback.

Cheers.

Mac OS X What is my IP Address?!

I’ve asked this question a lot. What is my IP address? This can be for any number of reasons. There are a number of ways that this can be done, but I was looking for a way to do this on the command line. Well… it didn’t turn out to be as simple as I had hoped.

ipaddr

The primary tool is ifconfig. However, that spits out way more information then I wanted. So a little bit of reading, some regular expressions, and I created the following script. Note that en1 is my wireless port because I am always on my wireless:

#!/bin/sh
ifconfig en1 | awk '$2~/[0-9]+./{print$2}'

Update: I found a better solution:

#!/bin/sh
# Update: Even Better!
ipconfig getifaddr en1

Here is some usage and me checking that it actually makes sense:

ipaddr usage

ipaddr2

I’m paranoid… so let me double check at whatismyip.com. “What!?” That didn’t make sense. Or did it? A few minutes later, after running a traceroute and following the hops I noticed that sure enough the Frontier Wireless provider in this public CafĂ© was using that IP address. So I wrote a quick curl script to pull whatismyip’s opinion and quite craftily they must have planned for this:

whatismyip suggestion

Cool. That makes my job even simpler. The response is simply my ip address string. Add an echo to provide a newline and I’ve got a useful script.

#!/bin/sh
curl --silent www.whatismyip.com/automation/n09230945.asp
echo

Hopefully these two scripts can help save someone a few minutes. ipaddr to see what my machine’s ip address is and ipaddr2 to see through what ip address my computer is reaching the outer world. Cheers.

Ruby Process Controller – psgrep

Every once in a while a process will freeze and will be too stubborn to die when I try to “Quit” it. For those stubborn processes I tend to use the terminal to `kill` it. For a while I had been using a simple Perl script for searching through processes. The script would find me the processids and I could then kill it, using whatever power I need.

I found that it was taking far too long for me to do the search, and then carefully type out the process id, and hope I got the right one. I discovered killall, but the problem is that sometimes I don’t want to kill “all” of the processes with that name. So, I gave in and wrote up a Ruby script that did what I wanted. Here is psgrep: (Download)

#!/usr/bin/env ruby
# Start Date: Saturday December 6, 2008
# Current Version: 0.9
# Author: Joseph Pecoraro
# Contact: joepeck02@gmail.com
# Decription: Quicker handling of process searching
# and killing.  Never type in a PID again, just regexes!

# -----------
#   Globals
# -----------
kill_mode = false
icase_mode = false
pattern = nil
targets = []
pids = []

# ---------
#   Usage
# ---------
def usage
  puts "usage: #{$0.split(/\//).last} [options] pattern"
  puts "  -k  or --kill   kills all processes"
  puts "  -k# or --kill#  kills the [#] process"
  exit 0
end

# -----------
#   Options
# -----------
if ARGV.size > 1
  ARGV.each do |arg|
    if arg.match(/^-(k|-kill)(\d*)$/)
      kill_mode = true
      targets << $2.to_i unless $2.empty?
    elsif arg.match(/^-(i|-ignore)$/)
      icase_mode = true
    end
  end
  ARGV.delete_if { |e| e.match(/^-/) }
end

# -------------------
#   Remaining Args
# -------------------
if ARGV.size != 1
  usage
end

if icase_mode
  pattern = Regexp.new( ARGV[0], Regexp::IGNORECASE )
else
  pattern = Regexp.new( ARGV[0] )
end

# ----------------------
#   Actual `ps` Output
# ----------------------
lines = %x{ ps -Au#{ENV['USER']} }.split(/\n/)
header = lines.shift

# ----------
#   psgrep
# ----------
puts
puts "     #{header}"
count = 0
lines.each do |line|
  unless line =~ /psgrep/
    if line.match(pattern)
      count += 1
      puts "[#{count}]: #{line}"
      if targets.empty? || targets.member?(count)
        pids << line.strip.split[1]
      end
    end
  end
end

# -------------
#   Kill Mode
# -------------
if kill_mode
  puts
  puts "Killing Processes"
  puts "-----------------"
  pids.each_with_index do |pid, i|
    print targets.empty? ? "[#{i}]:" :  "[#{targets[i]}]:"
    print " Killing #{pid}... "
    STDOUT.flush
    res = %x{ kill #{pid} }
    puts "Dead" if $?.exitstatus.zero?
  end
end

# Always
puts

So there it is. Less then 100 lines of ruby to get a pretty straightforward psgrep/kill program. Here is an example where I have three perl processes running on my machine. One of the is running as root (the userid is 0). I just type “!! –kill” or “[up-arrow] –kill” and it tries to kill them all. Note that the root perl process doesn’t terminate and there is an error message but psgrep continues as best as it can, and kills the two normal perl processes: [Note: I could have done `sudo psgrep perl -k` to kill the root process]

psgrep usage

Here is another good example. I have two python instances that I want to kill but there is another python instance running ExpanDrive in the background. I just ran psgrep and found the two I want to kill are [2] and [3]. Therefore, I can send -k2 and -k3 (or –kill2 and –kill3) to kill only those processes. Here is the result:

psgrep target

Note also that by default psgrep is case-sensitive. To ignore case just add the -i or –ignore switch. So there you have it. Usage is straightforward. Switches can go anywhere on the command line makes it easier to just use your history and tack a switch on the end of your previous command.

Feel free to improve it, it is on GitHub!

The ARGFy Experiment

I wrote an earlier article that talked about Ruby’s global ARGF variable. I mentioned that I took that a step further, to experiment and learn a number of aspects about Ruby development. Those included:

  1. RDoc – Ruby Autogenerated Documentation
  2. RSpec – Ruby Test Framework aiding Behavior Driven Development
  3. General Familiarity with Ruby Classes
  4. General Familiarity with GitHub

I have to say that I was really impressed with how strikingly natural, easy, and fun it was to work with these tools. I already wrote about RDoc, hopefully to cover a “void” that I saw in the online documentation for it. I may look into writing about RSpec, however the current RSpec documentation was quite good so I may focus elsewhere. Finally GitHub and Ruby are mostly things that you have to personally practice with to get good at, and there are already plenty of great resources for them. The Ruby community has done a very good job!

The ARGFy Results

So, here are the results of my experiment:

The GitHub README is very similar to the RDoc, but it goes in more depth by showing the output of the sample.rb script included. Its not too exciting, but here is what ARGFy does.

What ARGFy Does

ARGFy is a class. In the constructor it takes an Array of filenames. It then treats those files as one continuous stream of lines. If no filenames are provided, or if “-” is provided as a filename, that input is treated as STDIN. Everything so far makes ARGFy look and act just like ARGF except you can specify your own files instead of only relying on the command line arguments.

Using ARGFy is mostly like ARGF. If you call the ARGFy#each method (note that this allows for any Enumerable method!) it will exhaust all the lines of input from all the files as a single stream. At each line you can check the states of the ARGFy object itself. The states include filename and lineno like the normal ARGF, but they also include filelineno. Because there is a filelineno there is a guaranteed way to know if under-the-hood the stream is now processing a different file. Since this might be a common thing to check there is a ARGFy#new_file? helper method that does just that.

Finally, because its an object you can add a file to the list at any time. Although removing didn’t seem to make much sense considering what its purpose was. Just make use of ARGFy#add_file to add a file to the end of the sequence of input files to the stream.

In the background ARGFy is really just reading and buffering the files one at a time and returning the lines. Its nothing too exciting, just a little fun working with Ruby. The example nicely displays how ARGFy works:

# sample.rb
require 'ARGFy'

argf = ARGFy.new(ARGV)
argf.each do |line|

  # Per File Header
  if argf.new_file?
    filename = argf.filename
    filename = "STDIN" if filename == "-"
    puts '', filename, "-"*filename.length
  end

  # Print out the line with line numbers
  puts "%3d: %s" % [argf.lineno, line]

end
puts

Calling sample.rb with a few small input files creates some nicely formatted output:

shell> ruby sample.rb in1.txt in2.txt 

in1.txt
-------
  1: one
  2: two

in2.txt
-------
  1: alpha, beta, gamma
  2: 0987654321
  3: 
  4: NOT BLANK!

Nothing complex. It works like you would expect it too. For more sample usage you can scan the RSpec test cases in the GitHub repository.

search