How to automatically build a hash

Sorry if the title misled you, but I couldn’t come up with anything better and equally short. A YAML configuration file can be loaded into a hash, and this works pretty much out of the box. Sometimes, however, I need to also add configuration on the fly to the same hash. Then it becomes a little tedious to manually build missing keys, so I created my own Ando::Hash, which is a thin wrapper around Ruby’s Hash.

module Ando

  class Hash

    def initialize(hash=nil)
      hash ||= {}
      raise ArgumentError, "Expected a Hash object. (#{hash.class})" unless hash.respond_to?(:has_key?)
      @hash = hash
    end

    def [](*args)
      get_last_value(args)
    end

    def []=(*args)
      value = args.pop
      if args.first
        set_last_value(args, value)
      else
        initialize(value)
      end
      value # this is to preserve assignment semantics
    end

    protected

    # hook into @hash
    def get_previous_hash(path)
      current = @hash
      path[0..-2].each do |key|
        current = current[key] || current[key.is_a?(Symbol) ? key.to_s : key.to_sym]
      end
      current
    end

    # initialize intermediate keys to a hash
    def init_last(path, current = @hash)
      key = path.first
      return unless key
      raise ArgumentError, "Expected a Hash object. (#{current.class})" unless current.respond_to?(:has_key?)
      (current[key] = {}) unless (current.has_key?(key) && current[key].respond_to?(:has_key?))
      init_last(path[1..-1], current[key])
    end

    # set the given value to the last key
    def set_last_value(path, value)
      init_last(path)
      previous            = get_previous_hash(path)
      previous[path.last] = value
    end

    # get the value of the last key
    def get_last_value(path)
      previous = get_previous_hash(path)
      if previous.respond_to?(:has_key?) && path.last
        key = path.last
        return previous[key] || previous[key.is_a?(Symbol) ? key.to_s : key.to_sym]
      end
      previous
    end

  end

end

And here is a very short usage example.

>> h = Ando::Hash.new({:a => 1, :b => {:c => 3}})
=> #<Ando::Hash:0x007fd96a87d1b0 @hash={:a=>1, :b=>{:c=>3}}>
>> h[:b]
=> {:c=>3}
>> h[:b, :c]
=> 3
>> h[:a, :b, :c, :d, :e] = 5
=> 5
>> h[:a, :b, :c, :d, :e]
=> 5
>> h[:b]
=> {:c=>3}
>> h[:a]
=> {:b=>{:c=>{:d=>{:e=>5}}}}
>> h
=> #<Ando::Hash:0x007fd96a87d1b0 @hash={:a=>{:b=>{:c=>{:d=>{:e=>5}}}}, :b=>{:c=>3}}>

How to run Rake tasks programmatically

Some time ago I was told to reimplement some Rake tasks such that they would be available from a simple admin interface. Instead of that, I came up with a web interface for Rake tasks, such that exactly the same code can be run from the web and from a console.

At the core there are a couple of global methods: runnable_tasks and run_task.

#see http://stackoverflow.com/questions/4459330/how-do-i-temporarily-redirect-stderr-in-ruby -----------------------
require "stringio"

def capture_stderr
  previous, $stderr = $stderr, StringIO.new
  yield
  $stderr.string
ensure
  $stderr = previous
end

def capture_stdout
  previous, $stdout = $stdout, StringIO.new
  yield
  $stdout.string
ensure
  $stdout = previous
end
#-------------------------------------------------------------------------------------------------------------------

# requires rake such that descriptions are collected too
def require_rake
  return if defined? Rake
  require 'rake'
  Rake::TaskManager.record_task_metadata = true
  require 'rake/testtask'
  require 'rdoc/task'
  require 'tasks/rails'
end

# returns the stdout generated by an execution of the given task
def run_task(task_name)
  require_rake
  capture_stdout { Rake.application[task_name].invoke } # or Rake::Task[task_name].invoke
end

# returns a hash (each key is a task name and each value is a task description)
def runnable_tasks(include_tasks = /.^/, exclude_tasks = /.^/) # include (and exclude) nothing for safety
  require_rake
  list = []
  tasks = Rake.application.tasks
  tasks.each do |task|
    if task.name.match(include_tasks) && ! task.name.match(exclude_tasks)
      list.push(task.name)
    end
  end
  list.sort
  result = {}
  list.each do |item|
    result[item] = Rake::Task[item].comment
  end
  result
end

And here is how that it’s supposed to be used:

result = run_task(task_name) if runnable_tasks.has_key?(task_name)

URI encoding and decoding for Ruby

These are some global methods for dealing with URI encoding and decoding in Ruby, such that it behaves exactly like JavaScript.

      # printable ASCII chars (between 32 and 126) without 0..9, A..Z, a..z
      def symbols
        ' !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~' # symbols.length === 33
      end


      def gsub(input, replace)
        search = Regexp.new(replace.keys.map{|x| "(?:#{Regexp.quote(x)})"}.join('|'))
        input.gsub(search, replace)
      end

      # same as the JavaScript encodeURI
      def encodeURI(value)
        # encodeURI(symbols)  === "%20   ! %22   #   $ %25   &   '   (   )   *   +   , - .   /   :   ; %3C   = %3E   ?   @ %5B %5C %5D %5E _ %60 %7B %7C %7D   ~"
        # CGI.escape(symbols) === "  + %21 %22 %23 %24 %25 %26 %27 %28 %29 %2A %2B %2C - . %2F %3A %3B %3C %3D %3E %3F %40 %5B %5C %5D %5E _ %60 %7B %7C %7D %7E"
        gsub(CGI.escape(value.to_s),
            '+'   => '%20',  '%21' => '!',  '%23' => '#',  '%24' => '$',  '%26' => '&',  '%27' => "'",
            '%28' => '(',    '%29' => ')',  '%2A' => '*',  '%2B' => '+',  '%2C' => ',',  '%2F' => '/',
            '%3A' => ':',    '%3B' => ';',  '%3D' => '=',  '%3F' => '?',  '%40' => '@',  '%7E' => '~'
        )
      end

      # same as the JavaScript encodeURIComponent, also for UTF8 multibyte chars (including gclef)
      def encodeURIComponent(value)
        # encodeURIComponent(symbols) === "%20   ! %22 %23 %24 %25 %26   '   (   )   * %2B %2C - . %2F %3A %3B %3C %3D %3E %3F %40 %5B %5C %5D %5E _ %60 %7B %7C %7D   ~"
        # CGI.escape(symbols)         === "  + %21 %22 %23 %24 %25 %26 %27 %28 %29 %2A %2B %2C - . %2F %3A %3B %3C %3D %3E %3F %40 %5B %5C %5D %5E _ %60 %7B %7C %7D %7E"
        gsub(CGI.escape(value.to_s),
            '+'   => '%20',  '%21' => '!',  '%27' => "'",  '%28' => '(',  '%29' => ')',  '%2A' => '*',
            '%7E' => '~'
        )
      end

      # same as the JavaScript decodeURI
      def decodeURI(value)
        # decodeURI(encodeURI(symbols))     === symbols
        # CGI.unescape(CGI.escape(symbols)) === symbols
        CGI.unescape(gsub(value.to_s,
            '%20' => '+',    '!' => '%21',  '#' => '%23',  '$' => '%24',  '&' => '%26',  "'" => '%27',
            '('   => '%28',  ')' => '%29',  '*' => '%2A',  '+' => '%2B',  ',' => '%2C',  '/' => '%2F',
            ':'   => '%3A',  ';' => '%3B',  '=' => '%3D',  '?' => '%3F',  '@' => '%40',  '~' => '%7E'
        ))
      end

      # same as the JavaScript decodeURIComponent
      def decodeURIComponent(value)
        # decodeURIComponent(encodeURIComponent(symbols)) === symbols
        # CGI.unescape(CGI.escape(symbols))               === symbols
        CGI.unescape(gsub(value.to_s,
            '%20' => '+',    '!' => '%21',  "'" => '%27',  '(' => '%28',  ')' => '%29',  '*' => '%2A',
            '~'   => '%7E'
        ))
      end