conjure 0.2.10 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.md +8 -0
 - data/README.md +35 -76
 - data/lib/conjure.rb +1 -12
 - data/lib/conjure/delayed_job.rb +39 -0
 - data/lib/conjure/digital_ocean/droplet.rb +5 -2
 - data/lib/conjure/docker/host.rb +75 -0
 - data/lib/conjure/docker/template.rb +71 -0
 - data/lib/conjure/instance.rb +31 -76
 - data/lib/conjure/passenger.rb +123 -0
 - data/lib/conjure/postgres.rb +67 -0
 - data/lib/conjure/rails_application.rb +32 -0
 - data/lib/conjure/server.rb +41 -0
 - data/lib/conjure/swap.rb +28 -0
 - data/lib/conjure/{provision/templates → templates}/application-no-ssl.conf.erb +2 -1
 - data/lib/conjure/{provision/templates → templates}/application-ssl.conf.erb +2 -1
 - data/lib/conjure/version.rb +1 -1
 - metadata +12 -41
 - data/lib/conjure/application.rb +0 -35
 - data/lib/conjure/command.rb +0 -74
 - data/lib/conjure/command_target.rb +0 -25
 - data/lib/conjure/config.rb +0 -44
 - data/lib/conjure/data_set.rb +0 -7
 - data/lib/conjure/identity.rb +0 -25
 - data/lib/conjure/log.rb +0 -26
 - data/lib/conjure/provider.rb +0 -26
 - data/lib/conjure/provision.rb +0 -1
 - data/lib/conjure/provision/docker/host.rb +0 -32
 - data/lib/conjure/provision/docker/image.rb +0 -55
 - data/lib/conjure/provision/docker/template.rb +0 -55
 - data/lib/conjure/provision/instance.rb +0 -52
 - data/lib/conjure/provision/local_docker.rb +0 -16
 - data/lib/conjure/provision/passenger.rb +0 -111
 - data/lib/conjure/provision/postgres.rb +0 -70
 - data/lib/conjure/provision/server.rb +0 -78
 - data/lib/conjure/service/cloud_server.rb +0 -112
 - data/lib/conjure/service/database.rb +0 -25
 - data/lib/conjure/service/database/mysql.rb +0 -69
 - data/lib/conjure/service/database/postgres.rb +0 -77
 - data/lib/conjure/service/digital_ocean_account.rb +0 -31
 - data/lib/conjure/service/docker_host.rb +0 -259
 - data/lib/conjure/service/docker_shell.rb +0 -46
 - data/lib/conjure/service/forwarded_shell.rb +0 -25
 - data/lib/conjure/service/rails_codebase.rb +0 -67
 - data/lib/conjure/service/rails_console.rb +0 -10
 - data/lib/conjure/service/rails_log_view.rb +0 -14
 - data/lib/conjure/service/rails_server.rb +0 -91
 - data/lib/conjure/service/rake_task.rb +0 -11
 - data/lib/conjure/service/remote_file_set.rb +0 -24
 - data/lib/conjure/service/remote_shell.rb +0 -73
 - data/lib/conjure/service/repository_link.rb +0 -52
 - data/lib/conjure/service/volume.rb +0 -28
 - data/lib/conjure/target.rb +0 -19
 - data/lib/conjure/view/application_view.rb +0 -42
 - data/lib/conjure/view/table_view.rb +0 -38
 
| 
         @@ -1,24 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module Conjure
         
     | 
| 
       2 
     | 
    
         
            -
              module Service
         
     | 
| 
       3 
     | 
    
         
            -
                class RemoteFileSet
         
     | 
| 
       4 
     | 
    
         
            -
                  require "net/scp"
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
                  def initialize(options)
         
     | 
| 
       7 
     | 
    
         
            -
                    @shell = options[:shell]
         
     | 
| 
       8 
     | 
    
         
            -
                    @files = options[:files].to_a
         
     | 
| 
       9 
     | 
    
         
            -
                  end
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                  def upload
         
     | 
| 
       12 
     | 
    
         
            -
                    dir_names = @files.map{|local_path, remote_path| File.dirname remote_path}.uniq
         
     | 
| 
       13 
     | 
    
         
            -
                    @shell.run "mkdir -p #{dir_names.join ' '}" if dir_names.any?
         
     | 
| 
       14 
     | 
    
         
            -
                    @files.each do |local_path, remote_path|
         
     | 
| 
       15 
     | 
    
         
            -
                      @shell.session.scp.upload! local_path, remote_path
         
     | 
| 
       16 
     | 
    
         
            -
                    end
         
     | 
| 
       17 
     | 
    
         
            -
                  end
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                  def remove
         
     | 
| 
       20 
     | 
    
         
            -
                    @files.each{|local_path, remote_path| @shell.run "rm -f #{remote_path}"}
         
     | 
| 
       21 
     | 
    
         
            -
                  end
         
     | 
| 
       22 
     | 
    
         
            -
                end
         
     | 
| 
       23 
     | 
    
         
            -
              end
         
     | 
| 
       24 
     | 
    
         
            -
            end
         
     | 
| 
         @@ -1,73 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module Conjure
         
     | 
| 
       2 
     | 
    
         
            -
              module Service
         
     | 
| 
       3 
     | 
    
         
            -
                class RemoteShell
         
     | 
| 
       4 
     | 
    
         
            -
                  require "net/ssh"
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
                  class << self
         
     | 
| 
       7 
     | 
    
         
            -
                    attr_accessor :ssh_service
         
     | 
| 
       8 
     | 
    
         
            -
                  end
         
     | 
| 
       9 
     | 
    
         
            -
                  @ssh_service = Net::SSH
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                  def initialize(options = {})
         
     | 
| 
       12 
     | 
    
         
            -
                    @options = options
         
     | 
| 
       13 
     | 
    
         
            -
                  end
         
     | 
| 
       14 
     | 
    
         
            -
                
         
     | 
| 
       15 
     | 
    
         
            -
                  def run(command, options = {}, &block)
         
     | 
| 
       16 
     | 
    
         
            -
                    Log.debug "   [ssh] #{command}"
         
     | 
| 
       17 
     | 
    
         
            -
                    result = nil
         
     | 
| 
       18 
     | 
    
         
            -
                    session.open_channel do |channel|
         
     | 
| 
       19 
     | 
    
         
            -
                      channel.request_pty
         
     | 
| 
       20 
     | 
    
         
            -
                      channel.exec command do |c, success|
         
     | 
| 
       21 
     | 
    
         
            -
                        raise "Failed to execute command via SSH" unless success
         
     | 
| 
       22 
     | 
    
         
            -
                        result = Result.new(channel, &block)
         
     | 
| 
       23 
     | 
    
         
            -
                      end
         
     | 
| 
       24 
     | 
    
         
            -
                      if options[:stream_stdin]
         
     | 
| 
       25 
     | 
    
         
            -
                        channel.on_process do
         
     | 
| 
       26 
     | 
    
         
            -
                          poll_stream(STDIN) { |data| channel.send_data data }
         
     | 
| 
       27 
     | 
    
         
            -
                        end
         
     | 
| 
       28 
     | 
    
         
            -
                      end
         
     | 
| 
       29 
     | 
    
         
            -
                    end
         
     | 
| 
       30 
     | 
    
         
            -
                    if options[:stream_stdin]
         
     | 
| 
       31 
     | 
    
         
            -
                      with_raw_tty { session.loop 0.01 }
         
     | 
| 
       32 
     | 
    
         
            -
                    else
         
     | 
| 
       33 
     | 
    
         
            -
                      session.loop
         
     | 
| 
       34 
     | 
    
         
            -
                    end
         
     | 
| 
       35 
     | 
    
         
            -
                    result
         
     | 
| 
       36 
     | 
    
         
            -
                  end
         
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
                  def session
         
     | 
| 
       39 
     | 
    
         
            -
                    session_options = {
         
     | 
| 
       40 
     | 
    
         
            -
                      :auth_methods => ["publickey"],
         
     | 
| 
       41 
     | 
    
         
            -
                      :paranoid => false,
         
     | 
| 
       42 
     | 
    
         
            -
                      :forward_agent => true,
         
     | 
| 
       43 
     | 
    
         
            -
                    }
         
     | 
| 
       44 
     | 
    
         
            -
                    @session ||= self.class.ssh_service.start @options[:ip_address], @options[:username], session_options
         
     | 
| 
       45 
     | 
    
         
            -
                  end
         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
                  def poll_stream(stream, &block)
         
     | 
| 
       48 
     | 
    
         
            -
                    yield stream.sysread(1) if IO.select([stream], nil, nil, 0.01)
         
     | 
| 
       49 
     | 
    
         
            -
                  end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                  def with_raw_tty
         
     | 
| 
       52 
     | 
    
         
            -
                    system "stty raw -echo"
         
     | 
| 
       53 
     | 
    
         
            -
                    yield
         
     | 
| 
       54 
     | 
    
         
            -
                  ensure
         
     | 
| 
       55 
     | 
    
         
            -
                    system "stty -raw echo"
         
     | 
| 
       56 
     | 
    
         
            -
                  end
         
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
                  class Result
         
     | 
| 
       59 
     | 
    
         
            -
                    attr_accessor :stdout, :stderr, :status
         
     | 
| 
       60 
     | 
    
         
            -
                    def initialize(channel)
         
     | 
| 
       61 
     | 
    
         
            -
                      @stdout, @stderr = "", ""
         
     | 
| 
       62 
     | 
    
         
            -
                      channel.on_data do |c, data|
         
     | 
| 
       63 
     | 
    
         
            -
                        yield data if block_given?
         
     | 
| 
       64 
     | 
    
         
            -
                        @stdout << data
         
     | 
| 
       65 
     | 
    
         
            -
                      end
         
     | 
| 
       66 
     | 
    
         
            -
                      channel.on_extended_data { |c, type, data| @stderr << data }
         
     | 
| 
       67 
     | 
    
         
            -
                      channel.on_request("exit-status") { |c, data| @status = data.read_long }
         
     | 
| 
       68 
     | 
    
         
            -
                    end
         
     | 
| 
       69 
     | 
    
         
            -
                  end
         
     | 
| 
       70 
     | 
    
         
            -
                end
         
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
              end
         
     | 
| 
       73 
     | 
    
         
            -
            end
         
     | 
| 
         @@ -1,52 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module Conjure
         
     | 
| 
       2 
     | 
    
         
            -
              module Service
         
     | 
| 
       3 
     | 
    
         
            -
                class RepositoryLink
         
     | 
| 
       4 
     | 
    
         
            -
                  def initialize(options)
         
     | 
| 
       5 
     | 
    
         
            -
                    @volume = options[:volume]
         
     | 
| 
       6 
     | 
    
         
            -
                    @branch = options[:branch]
         
     | 
| 
       7 
     | 
    
         
            -
                    @origin_url = options[:origin_url]
         
     | 
| 
       8 
     | 
    
         
            -
                    @public_key = options[:public_key]
         
     | 
| 
       9 
     | 
    
         
            -
                  end
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                  def update
         
     | 
| 
       12 
     | 
    
         
            -
                    code_checked_out ? fetch_code_updates : checkout_code
         
     | 
| 
       13 
     | 
    
         
            -
                  end
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
                  def branch
         
     | 
| 
       16 
     | 
    
         
            -
                    @branch ||= git_shell.command("cd #{code_path}; git rev-parse --abbrev-ref HEAD").strip
         
     | 
| 
       17 
     | 
    
         
            -
                  end
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                  private
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                  def code_checked_out
         
     | 
| 
       22 
     | 
    
         
            -
                    git_shell.command("[ -d #{code_path}/.git ] && echo yes; true").strip == "yes"
         
     | 
| 
       23 
     | 
    
         
            -
                  end
         
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
                  def checkout_code
         
     | 
| 
       26 
     | 
    
         
            -
                    Log.info "[  repo] Checking out code from git"
         
     | 
| 
       27 
     | 
    
         
            -
                    output = git_shell.command "git clone -b #{@branch} #{@origin_url} #{code_path}"
         
     | 
| 
       28 
     | 
    
         
            -
                    raise "Access denied to git repo" if output.include? "Permission denied"
         
     | 
| 
       29 
     | 
    
         
            -
                  end
         
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
                  def fetch_code_updates
         
     | 
| 
       32 
     | 
    
         
            -
                    Log.info "[  repo] Fetching code updates from git"
         
     | 
| 
       33 
     | 
    
         
            -
                    git_shell.command "cd #{code_path}; git reset --hard; git checkout #{@branch}; git pull"
         
     | 
| 
       34 
     | 
    
         
            -
                  end
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                  def code_path
         
     | 
| 
       37 
     | 
    
         
            -
                    @volume.container_path
         
     | 
| 
       38 
     | 
    
         
            -
                  end
         
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
                  def git_shell
         
     | 
| 
       41 
     | 
    
         
            -
                    @git_shell ||= ForwardedShell.new(:shell => @volume.shell.prepare({
         
     | 
| 
       42 
     | 
    
         
            -
                      label: "git",
         
     | 
| 
       43 
     | 
    
         
            -
                      setup_commands: [
         
     | 
| 
       44 
     | 
    
         
            -
                        "apt-get install -y git",
         
     | 
| 
       45 
     | 
    
         
            -
                        "mkdir -p /root/.ssh",
         
     | 
| 
       46 
     | 
    
         
            -
                        "echo 'Host github.com\\n\\tStrictHostKeyChecking no\\n' >> /root/.ssh/config",
         
     | 
| 
       47 
     | 
    
         
            -
                      ],
         
     | 
| 
       48 
     | 
    
         
            -
                    }), :public_key => @public_key)
         
     | 
| 
       49 
     | 
    
         
            -
                  end
         
     | 
| 
       50 
     | 
    
         
            -
                end
         
     | 
| 
       51 
     | 
    
         
            -
              end
         
     | 
| 
       52 
     | 
    
         
            -
            end
         
     | 
| 
         @@ -1,28 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module Conjure
         
     | 
| 
       2 
     | 
    
         
            -
              module Service
         
     | 
| 
       3 
     | 
    
         
            -
                class Volume
         
     | 
| 
       4 
     | 
    
         
            -
                  attr_reader :target, :container_path
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
                  def initialize(options)
         
     | 
| 
       7 
     | 
    
         
            -
                    @target = options[:target]
         
     | 
| 
       8 
     | 
    
         
            -
                    @host_path = options[:host_path]
         
     | 
| 
       9 
     | 
    
         
            -
                    @container_path = options[:container_path]
         
     | 
| 
       10 
     | 
    
         
            -
                  end
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
                  def read(filename)
         
     | 
| 
       13 
     | 
    
         
            -
                    shell.command "cat #{@container_path}/#{filename}"
         
     | 
| 
       14 
     | 
    
         
            -
                  end
         
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                  def write(filename, data)
         
     | 
| 
       17 
     | 
    
         
            -
                    shell.command "echo '#{data}' >#{@container_path}/#{filename}"
         
     | 
| 
       18 
     | 
    
         
            -
                  end
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
                  def shell
         
     | 
| 
       21 
     | 
    
         
            -
                    @shell ||= @target.shell.prepare(
         
     | 
| 
       22 
     | 
    
         
            -
                      :label => "volume",
         
     | 
| 
       23 
     | 
    
         
            -
                      :host_volumes => {@host_path => @container_path},
         
     | 
| 
       24 
     | 
    
         
            -
                    )
         
     | 
| 
       25 
     | 
    
         
            -
                  end
         
     | 
| 
       26 
     | 
    
         
            -
                end
         
     | 
| 
       27 
     | 
    
         
            -
              end
         
     | 
| 
       28 
     | 
    
         
            -
            end
         
     | 
    
        data/lib/conjure/target.rb
    DELETED
    
    | 
         @@ -1,19 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module Conjure
         
     | 
| 
       2 
     | 
    
         
            -
              class Target
         
     | 
| 
       3 
     | 
    
         
            -
                def initialize(options)
         
     | 
| 
       4 
     | 
    
         
            -
                  @machine_name = options[:machine_name]
         
     | 
| 
       5 
     | 
    
         
            -
                end
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
                def shell
         
     | 
| 
       8 
     | 
    
         
            -
                  docker_host.shell
         
     | 
| 
       9 
     | 
    
         
            -
                end
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                def ip_address
         
     | 
| 
       12 
     | 
    
         
            -
                  docker_host.ip_address
         
     | 
| 
       13 
     | 
    
         
            -
                end
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
                def docker_host
         
     | 
| 
       16 
     | 
    
         
            -
                  @docker_host ||= Service::DockerHost.new @machine_name
         
     | 
| 
       17 
     | 
    
         
            -
                end
         
     | 
| 
       18 
     | 
    
         
            -
              end
         
     | 
| 
       19 
     | 
    
         
            -
            end
         
     | 
| 
         @@ -1,42 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require "conjure"
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            module Conjure
         
     | 
| 
       4 
     | 
    
         
            -
              module View
         
     | 
| 
       5 
     | 
    
         
            -
                class ApplicationView
         
     | 
| 
       6 
     | 
    
         
            -
                  def initialize(application)
         
     | 
| 
       7 
     | 
    
         
            -
                    @application = application
         
     | 
| 
       8 
     | 
    
         
            -
                    @instances = @application.instances
         
     | 
| 
       9 
     | 
    
         
            -
                  end
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                  def render
         
     | 
| 
       12 
     | 
    
         
            -
                    [application_content, instances_content].join "\n\n"
         
     | 
| 
       13 
     | 
    
         
            -
                  end
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
                  private
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                  def application_content
         
     | 
| 
       18 
     | 
    
         
            -
                    content = ["Showing application status (Conjure v#{Conjure::VERSION})"]
         
     | 
| 
       19 
     | 
    
         
            -
                    content << "Origin  #{@application.origin}"
         
     | 
| 
       20 
     | 
    
         
            -
                    content.join "\n"
         
     | 
| 
       21 
     | 
    
         
            -
                  end
         
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
                  def instances_content
         
     | 
| 
       24 
     | 
    
         
            -
                    content = ["Deployed Instances:"]
         
     | 
| 
       25 
     | 
    
         
            -
                    content << instances_table
         
     | 
| 
       26 
     | 
    
         
            -
                    content << "(none)" unless @instances.any?
         
     | 
| 
       27 
     | 
    
         
            -
                    content.join "\n"
         
     | 
| 
       28 
     | 
    
         
            -
                  end
         
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                  def instances_table
         
     | 
| 
       31 
     | 
    
         
            -
                    data = @instances.map do |instance|
         
     | 
| 
       32 
     | 
    
         
            -
                      {
         
     | 
| 
       33 
     | 
    
         
            -
                        "Name" => instance.name,
         
     | 
| 
       34 
     | 
    
         
            -
                        "Status" => instance.status,
         
     | 
| 
       35 
     | 
    
         
            -
                        "Address" => instance.ip_address,
         
     | 
| 
       36 
     | 
    
         
            -
                      }
         
     | 
| 
       37 
     | 
    
         
            -
                    end
         
     | 
| 
       38 
     | 
    
         
            -
                    TableView.new(data).render
         
     | 
| 
       39 
     | 
    
         
            -
                  end
         
     | 
| 
       40 
     | 
    
         
            -
                end
         
     | 
| 
       41 
     | 
    
         
            -
              end
         
     | 
| 
       42 
     | 
    
         
            -
            end
         
     | 
| 
         @@ -1,38 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module Conjure
         
     | 
| 
       2 
     | 
    
         
            -
              module View
         
     | 
| 
       3 
     | 
    
         
            -
                class TableView
         
     | 
| 
       4 
     | 
    
         
            -
                  def initialize(data)
         
     | 
| 
       5 
     | 
    
         
            -
                    @data = data
         
     | 
| 
       6 
     | 
    
         
            -
                    calculate_widths
         
     | 
| 
       7 
     | 
    
         
            -
                  end
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
                  def render
         
     | 
| 
       10 
     | 
    
         
            -
                    rows = [@width.map{|col, width| pad_to_width(col, width)}.join(column_separator)]
         
     | 
| 
       11 
     | 
    
         
            -
                    rows += @data.map do |row|
         
     | 
| 
       12 
     | 
    
         
            -
                      @width.map{|col, width| pad_to_width(row[col], width)}.join(column_separator)
         
     | 
| 
       13 
     | 
    
         
            -
                    end
         
     | 
| 
       14 
     | 
    
         
            -
                    rows.join("\n")
         
     | 
| 
       15 
     | 
    
         
            -
                  end
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                  private
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                  def column_separator
         
     | 
| 
       20 
     | 
    
         
            -
                    "  "
         
     | 
| 
       21 
     | 
    
         
            -
                  end
         
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
                  def pad_to_width(string, width)
         
     | 
| 
       24 
     | 
    
         
            -
                    string.to_s + " "*(width - string.to_s.length)
         
     | 
| 
       25 
     | 
    
         
            -
                  end
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                  def calculate_widths
         
     | 
| 
       28 
     | 
    
         
            -
                    @width = {}
         
     | 
| 
       29 
     | 
    
         
            -
                    @data.each do |row|
         
     | 
| 
       30 
     | 
    
         
            -
                      row.each_pair do |key, value|
         
     | 
| 
       31 
     | 
    
         
            -
                        @width[key] ||= key.to_s.length
         
     | 
| 
       32 
     | 
    
         
            -
                        @width[key] = [@width[key], value.to_s.length].max
         
     | 
| 
       33 
     | 
    
         
            -
                      end
         
     | 
| 
       34 
     | 
    
         
            -
                    end
         
     | 
| 
       35 
     | 
    
         
            -
                  end
         
     | 
| 
       36 
     | 
    
         
            -
                end
         
     | 
| 
       37 
     | 
    
         
            -
              end
         
     | 
| 
       38 
     | 
    
         
            -
            end
         
     |