class Environment

Constants

DEFAULT_IO
RASH_LOCAL_FILE

Attributes

aliasing_disabled[R]
prompt[R]
umask[R]

Public Class Methods

new() click to toggle source
# File lib/rash.rb, line 6
def initialize
  common_init
end

Public Instance Methods

add_path(path) click to toggle source
# File lib/rash.rb, line 33
def add_path(path) 
  ENV["PATH"] += File::PATH_SEPARATOR + (path.respond_to?(:path) ? path.path : path.to_s)
end
alias?(f) click to toggle source
# File lib/rash/aliasing.rb, line 10
def alias?(f)
  @aliases.key?(f.to_sym)
end
aliases() click to toggle source
# File lib/rash/aliasing.rb, line 14
def aliases
  @aliases.dup
end
as_pipe_command(&block) click to toggle source
# File lib/rash/pipeline.rb, line 33
def as_pipe_command(&block)
  raise IOError.new("pipelining not enabled") unless @in_pipeline
  return as_sync_pipe_command(&block) if @synchronous_pipeline
  
  input = (@active_pipelines.empty? ? $stdin : @active_pipelines.last.reader)
  @active_pipelines << Pipeline.new
  output = @active_pipelines.last.writer
  error = ($stderr == $stdout ? output : $stderr)

  pid = fork do
    @in_pipeline = false
    $stdin = input
    $stdout = output
    $stderr = error
    block.call
    output.close
    exit!(true)
  end
  output.close

  @active_pipelines.last.link_process(pid)
  nil
end
as_superuser(&block) click to toggle source
# File lib/rash.rb, line 52
def as_superuser(&block)
  @superuser_mode = true
  begin
    block.call
  ensure
    @superuser_mode = false
  end
end
as_sync_pipe_command(&block) click to toggle source
# File lib/rash/pipeline.rb, line 57
def as_sync_pipe_command(&block)
  raise IOError.new("pipelining not enabled") unless @in_pipeline
  raise IOError.new("pipeline is not synchronous") unless @synchronous_pipeline
 
  @next_pipe.close
  @next_pipe = Pipeline.new # flush the output pipe
  @prev_pipe.writer.close

  input = (@first_sync_command ? $stdin : @prev_pipe.reader)
  @first_sync_command = false
  output = @next_pipe.writer
  error = ($stderr == $stdout ? @next_pipe.writer : $stdin)

  pid = fork do
    @in_pipeline = false
    @synchronous_pipeline = false
    $stdin = input
    $stdout = output
    $stderr = error
    block.call
    exit!(true)
  end

  Process.wait(pid)
  @prev_pipe, @next_pipe = @next_pipe, @prev_pipe
  nil
end
async(&block) click to toggle source
# File lib/rash/jobcontrol.rb, line 6
def async(&block)
  pid = fork {
    block.call
    exit!(true)
  }
  @active_jobs << pid
  Process.detach(pid)
  pid
end
capture_block(&block) click to toggle source
# File lib/rash/capturing.rb, line 2
def capture_block(&block)
  raise ArgumentError.new("no block provided") unless block_given?
  result = nil
  old_pipeline = @in_pipeline
  begin
    reader, writer = IO.pipe
    self.stdout = writer
    @in_pipeline = false
    block.call
  ensure
    @in_pipeline = old_pipeline
    reset_stdout
    writer.close
    result = reader.read
    reader.close
  end
  result
end
capture_command(m, *args) click to toggle source
# File lib/rash/capturing.rb, line 21
def capture_command(m, *args)
  raise NameError.new("no such command", m) unless which(m) || ($env.alias?(m) && !$env.aliasing_disabled)
  result = nil
  begin 
    reader, writer = IO.pipe
    system_command(m, *args, out: writer)
  ensure
    writer.close
    result = reader.read
    reader.close
  end
  result
end
chdir(dir = nil) click to toggle source
# File lib/rash.rb, line 10
def chdir(dir = nil)
  old = @working_directory
  Dir.chdir(dir.nil? ? "~" : dir.to_s)
  @working_directory = Dir.pwd
  ENV["OLDPWD"] = old.to_s
  ENV["PWD"] = Dir.pwd
  Dir.pwd
end
clear_alias(func) click to toggle source
# File lib/rash/aliasing.rb, line 6
def clear_alias(func) 
  @aliases.delete(func.to_sym)
end
continued_prompt=(prompt) click to toggle source
# File lib/rash/prompt/irb.rb, line 41
def continued_prompt=(prompt)
  @prompt[:prompt_c] = case prompt
                       when Proc
                         prompt
                       else
                         err_msg = "expecting stringable type or method that resolves to string"
                         raise ArgumentError.new(err_msg) unless prompt.respond_to?(:to_s)
                         lambda {prompt.to_s}
                       end

  @prompt[:PROMPT_C] = "".tap {|s| def s.dup; $env.prompt[:prompt_c].call; end}
end
dirs() click to toggle source
# File lib/rash.rb, line 29
def dirs
  @directory_stack
end
dispatch(m, *args) click to toggle source
# File lib/rash.rb, line 74
def dispatch(m, *args)
  if @in_pipeline
    add_pipeline(m, *args)
  else
    system_command(m, *args)
  end
end
indent_prompt=(prompt) click to toggle source
# File lib/rash/prompt/irb.rb, line 15
def indent_prompt=(prompt)
  @prompt[:prompt_n] = case prompt
                       when Proc
                         prompt
                       else
                         err_msg = "expecting stringable type or method that resolves to string"
                         raise ArgumentError.new(err_msg) unless prompt.respond_to?(:to_s)
                         lambda {prompt.to_s}
                       end

  @prompt[:PROMPT_N] = "".tap {|s| def s.dup; $env.prompt[:prompt_n].call; end}
end
jobs() click to toggle source
# File lib/rash/jobcontrol.rb, line 2
def jobs
  @active_jobs.keep_if{|pid| Process.kill(0, pid) rescue false}.dup
end
local_call(name, *args, &block) click to toggle source

This ultimately behaves similarly to a lambda in function if that ever changes, then the proc needs to be converted to a lambda.

# File lib/rash/ext/filesystem.rb, line 39
def local_call(name, *args, &block)
  @working_directory.local_method(name).call(*args, &block)
end
local_def(name, locked: false, &block) click to toggle source
# File lib/rash/ext/filesystem.rb, line 19
def local_def(name, locked: false, &block)
  @working_directory.add_local_method(name, &block)
  @working_directory.lock_method(name) if locked
  name.to_sym
end
local_method?(name) click to toggle source
# File lib/rash/ext/filesystem.rb, line 29
def local_method?(name)
  @working_directory.local_method?(name)
end
local_methods() click to toggle source
# File lib/rash/ext/filesystem.rb, line 33
def local_methods
  @working_directory.local_methods
end
local_undef(name) click to toggle source
# File lib/rash/ext/filesystem.rb, line 25
def local_undef(name)
  @working_directory.clear_local_method(name)
end
local_var(name, v = nil, locked: false) click to toggle source
# File lib/rash/ext/filesystem.rb, line 43
def local_var(name, v = nil, locked: false)
  res = nil
  if v.nil?
    res = @working_directory.local_variable(name)
  else
    @working_directory.set_local_variable(name, v)
    res = v
  end
  @working_directory.lock_variable(name) if locked
  res
end
local_var?(name) click to toggle source
# File lib/rash/ext/filesystem.rb, line 55
def local_var?(name)
  @working_directory.local_variable?(name)
end
local_vars() click to toggle source
# File lib/rash/ext/filesystem.rb, line 59
def local_vars
  @working_directory.local_variables
end
make_alias(new_func, old_func) click to toggle source
# File lib/rash/aliasing.rb, line 2
def make_alias(new_func, old_func)
  @aliases[new_func.to_sym] = old_func.to_s.split(" ")
end
make_pipeline(&block) click to toggle source
# File lib/rash/pipeline.rb, line 11
def make_pipeline(&block) 
  raise IOError.new("pipelining already enabled") if @in_pipeline
  start_pipeline
  begin
    block.call
  ensure
    end_pipeline
  end
  nil
end
make_sync_pipeline(&block) click to toggle source
# File lib/rash/pipeline.rb, line 22
def make_sync_pipeline(&block)
  raise IOError.new("pipelining already enabled") if @in_pipeline
  start_sync_pipeline
  begin
    block.call
  ensure
    end_sync_pipeline
  end
  nil
end
method_missing(m, *args, &block) click to toggle source
Calls superclass method Object::method_missing
# File lib/rash.rb, line 37
def method_missing(m, *args, &block) 
  if args.length == 0 && !block_given?
    ENV[m.to_s.upcase]
  elsif m.to_s[-1] == "=" && args.length == 1  && !block_given?
    ENV[m.to_s.upcase.delete_suffix("=")] = args[0].to_s
  else
    super
  end
end
name?(v) click to toggle source
# File lib/rash.rb, line 82
def name?(v)
  v.kind_of?(String) || v.kind_of?(Symbol)
end
pipelined?() click to toggle source
# File lib/rash/pipeline.rb, line 3
def pipelined?
  @in_pipeline
end
pop_dir() click to toggle source
# File lib/rash.rb, line 25
def pop_dir
  self.chdir(@directory_stack.pop) if @directory_stack.size > 0
end
push_dir(dir = nil) click to toggle source

Note that this works regardless of which version of chdir is used.

# File lib/rash.rb, line 20
def push_dir(dir = nil)
  @directory_stack.push(Dir.pwd)
  self.chdir(dir)
end
reset_io() click to toggle source
# File lib/rash/redirection.rb, line 3
def reset_io
  reset_stdout
  reset_stderr
  reset_stdin
end
reset_stderr() click to toggle source
# File lib/rash/redirection.rb, line 49
def reset_stderr
  $stderr.flush
  $stderr.close unless standard_stream?($stderr)
  $stderr = DEFAULT_IO[:err]
end
reset_stdin() click to toggle source
# File lib/rash/redirection.rb, line 69
def reset_stdin
  $stdin.close unless standard_stream?($stdin)
  $stdin = DEFAULT_IO[:in]
end
reset_stdout() click to toggle source
# File lib/rash/redirection.rb, line 26
def reset_stdout
  $stdout.flush
  $stdout.close unless standard_stream?($stdout)
  $stdout = DEFAULT_IO[:out]
end
return_value_header=(prompt) click to toggle source

This method can only be run from .rashrc. Anywhere else and it will simply do nothing

# File lib/rash/prompt/irb.rb, line 55
def return_value_header=(prompt)
  @prompt[:RETURN] = prompt
end
standard_prompt=(prompt) click to toggle source
# File lib/rash/prompt/irb.rb, line 3
def standard_prompt=(prompt)
  @prompt[:prompt_i] = case prompt
                       when Proc
                         prompt
                       else
                         err_msg = "expecting stringable type or method that resolves to string"
                         raise ArgumentError.new(err_msg) unless prompt.respond_to?(:to_s)
                         lambda {prompt.to_s}
                       end
  @prompt[:PROMPT_I] = "".tap {|s| def s.dup; $env.prompt[:prompt_i].call; end}
end
stderr=(file) click to toggle source
# File lib/rash/redirection.rb, line 32
def stderr=(file)
  $stderr.flush
  old_stderr = $stderr
  case file
  when String
    $stderr = File.new(file, "w")
  when :out
    $stderr = STDOUT
  when :err
    $stderr = STDERR
  else
    raise ArgumentError.new("not an output stream - #{file}") unless file.is_a?(IO)
    $stderr = file
  end
  old_stderr.close unless standard_stream?(old_stderr)
end
stdin=(file) click to toggle source
# File lib/rash/redirection.rb, line 55
def stdin=(file)
  old_stdin = $stdin
  case file
  when String
    $stdin = File.new(file, "r")
  when :in
    $stdin = STDIN
  else
    raise ArgumentError.new("not an input stream - #{file}") unless file.is_a?(IO)
    $stdin = file
  end
  old_stdin.close unless standard_stream?(old_stdin)
end
stdout=(file) click to toggle source
# File lib/rash/redirection.rb, line 9
def stdout=(file)
  $stdout.flush
  old_stdout = $stdout
  case file
  when String
    $stdout = File.new(file, "w")
  when :out
    $stdout = STDOUT
  when :err
    $stdout = STDERR
  else
    raise ArgumentError.new("not an output stream - #{file}") unless file.is_a?(IO)
    $stdout = file
  end
  old_stdout.close unless standard_stream?(old_stdout)
end
string_prompt=(prompt) click to toggle source
# File lib/rash/prompt/irb.rb, line 28
def string_prompt=(prompt)
  @prompt[:prompt_s] = case prompt
                       when Proc
                         prompt
                       else
                         err_msg = "expecting stringable type or method that resolves to string"
                         raise ArgumentError.new(err_msg) unless prompt.respond_to?(:to_s)
                         lambda {prompt.to_s}
                       end

  @prompt[:PROMPT_S] = "".tap {|s| def s.dup; $env.prompt[:prompt_s].call; end}
end
synced_pipeline?() click to toggle source
# File lib/rash/pipeline.rb, line 7
def synced_pipeline?
  @in_pipeline && @synchronous_pipeline
end
umask=(mask) click to toggle source
# File lib/rash.rb, line 47
def umask=(mask)
  File.umask(mask)
  @umask = mask
end
use_irb_prompt() click to toggle source
# File lib/rash/prompt/irb.rb, line 59
def use_irb_prompt
  if $0 == "irb"
    IRB.conf[:PROMPT][:RASH] = @prompt
    IRB.conf[:PROMPT_MODE] = :RASH
  end
end
with_aliasing() { || ... } click to toggle source
# File lib/rash/aliasing.rb, line 30
def with_aliasing
  old_aliasing = @aliasing_disabled
  @aliasing_disabled = false
  if block_given?
    begin
      yield
    ensure
      @aliasing_disabled = old_aliasing
    end
  end
end
with_limits(limits, &block) click to toggle source
# File lib/rash.rb, line 61
def with_limits(limits, &block)
  if block_given?
    pid = fork do
      limits.each {|resource, limit| Process.setrlimit(resource, *limit)}
      block.call
      exit!(true)
    end
    Process.wait(pid)
  else
    limits.each {|resource, limit| Process.setrlimit(resource, *limit)}
  end
end
without_aliasing() { || ... } click to toggle source
# File lib/rash/aliasing.rb, line 18
def without_aliasing
  old_aliasing = @aliasing_disabled
  @aliasing_disabled = true
  if block_given?
    begin
      yield
    ensure
      @aliasing_disabled = old_aliasing
    end
  end
end

Private Instance Methods

add_pipeline(m, *args) click to toggle source

special method to be referenced from Environment#dispatch. Do not use directly

# File lib/rash/pipeline.rb, line 128
def add_pipeline(m, *args)
  raise IOError.new("pipelining not enabled") unless @in_pipeline
  return add_sync_pipeline(m, *args) if @synchronous_pipeline

  input = (@active_pipelines.empty? ? $stdin : @active_pipelines.last.reader)
  @active_pipelines << Pipeline.new
  output = @active_pipelines.last.writer
  error = ($stderr == $stdout ? output : $stderr)
  pid = fork do # might not be necessary, spawn might cover it. Not risking it before testing
    system_command(m, *args, out: output, input: input, err: error, except: true)
    output.close
    exit!(true)
  end
  output.close
  @active_pipelines.last.link_process(pid)
end
add_sync_pipeline(m, *args) click to toggle source
# File lib/rash/pipeline.rb, line 145
def add_sync_pipeline(m, *args)
  raise IOError.new("pipelining not enabled") unless @in_pipeline
  raise IOError.new("pipeline is not synchronous") unless @synchronous_pipeline

  # Ensure pipe is empty for writing
  @next_pipe.close
  @next_pipe = Pipeline.new
  @prev_pipe.writer.close

  input = (@first_sync_command ? $stdin : @prev_pipe.reader)
  @first_sync_command = false
  error = ($stderr == $stdout ? @next_pipe.writer : $stdin)
  system_command(m, *args, out: @next_pipe.writer, input: input, err: error, except: true)
  @prev_pipe, @next_pipe = @next_pipe, @prev_pipe
  nil
end
common_init() click to toggle source
# File lib/rash.rb, line 88
def common_init
  @working_directory = Dir.pwd
  @umask = File.umask

  @aliases = {}
  @aliasing_disabled = false
  @active_jobs = []

  @active_pipelines = []

  @directory_stack = []

  @prompt = {
    AUTO_INDENT: true,
    RETURN: ""
  }
end
end_pipeline() click to toggle source
# File lib/rash/pipeline.rb, line 99
def end_pipeline
  raise IOError.new("pipelining not enabled") unless @in_pipeline
  @in_pipeline = false
  if @active_pipelines.size > 0
    begin
      Process.wait(@active_pipelines.last.pid)
      @active_pipelines.last.writer.close # probably redundant, but leaving it for now
      IO.copy_stream(@active_pipelines.last.reader, $stdout)
      @active_pipelines.pop.close
      @active_pipelines.reverse_each {|pipe| pipe.terminate}
    ensure
      @active_pipelines.clear
    end
  end
end
end_sync_pipeline() click to toggle source
# File lib/rash/pipeline.rb, line 115
def end_sync_pipeline
  raise IOError.new("pipelining not enabled") unless @in_pipeline
  raise IOError.new("pipeline is not synchronous") unless @synchronous_pipeline
  @next_pipe.close
  @prev_pipe.writer.close
  IO.copy_stream(@prev_pipe.reader, $stdout)
  @prev_pipe.close

  @next_pipe = @prev_pipe = @first_sync_command = nil
  @synchronous_pipeline = @in_pipeline = false
end
resolve_alias(f) click to toggle source

Unless given a compelling reason, this doesn't need to be public. For most purposes, some combination of `alias?` and `aliases` should be sufficient.

# File lib/rash/aliasing.rb, line 46
def resolve_alias(f)
  result = [f.to_s]
  aliases = @aliases.dup
  found_alias = true
  while found_alias
    found_alias = false
    if aliases.has_key?(result[0].to_sym)
      found_alias = true
      match = result[0].to_sym
      result[0] = aliases[match]
      aliases.delete(match)
      result.flatten! 
    end
  end
  result
end
resolve_command(m, *args, literal: false) click to toggle source
# File lib/rash.rb, line 106
def resolve_command(m, *args, literal: false) 
  (literal ? [m.to_s] : resolve_alias(m)) + args.flatten.map{|a| a.to_s} 
end
standard_stream?(f) click to toggle source
# File lib/rash/redirection.rb, line 76
def standard_stream?(f)
  DEFAULT_IO.values.include?(f)
end
start_pipeline() click to toggle source
# File lib/rash/pipeline.rb, line 87
def start_pipeline
  @in_pipeline = true
end
start_sync_pipeline() click to toggle source
# File lib/rash/pipeline.rb, line 91
def start_sync_pipeline
  @in_pipeline = true
  @synchronous_pipeline = true
  @first_sync_command = true
  @prev_pipe = Pipeline.new
  @next_pipe = Pipeline.new
end
system_command(m, *args, except: false, literal: false, out: nil, input: nil, err: nil) click to toggle source
# File lib/rash.rb, line 110
def system_command(m, *args, except: false, literal: false, out: nil, input: nil, err: nil)
  command = resolve_command(m, *args, literal: literal)
  command.unshift("sudo") if @superuser_mode
  opts = {out: out || $stdout, 
          err: err || $stderr, 
          in: input || $stdin, 
          exception: except || @superuser_mode,
          umask: @umask}

  system(*command, opts)
end
traverse_filetree(from, to) click to toggle source

from and to are strings

# File lib/rash/ext/filesystem.rb, line 66
def traverse_filetree(from, to)
  abs_from = File.expand_path(from)
  abs_to = File.expand_path(to)
  raise SystemCallError.new(from, Errno::ENOENT::Errno) unless Dir.exists?(abs_from) 
  raise SystemCallError.new(to, Errno::ENOENT::Errno) unless Dir.exists?(abs_to)
  
  from_parts = (abs_from == "/" ? [""] : abs_from.split(File::SEPARATOR))
  to_parts = (abs_to == "/" ? [""] : abs_to.split(File::SEPARATOR))
  common_path = from_parts.filter.with_index {|p, i| p == to_parts[i]}
  
  from_parts = from_parts.drop(common_path.size)
  to_parts = to_parts.drop(common_path.size)

  from_parts.each do |p|
    @working_directory.add_parent(File.expand_path("..")) if @working_directory.root?
    @working_directory = @working_directory.parent
    Dir.chdir(@working_directory.to_s)
  end

  to_parts.each do |p|
    @working_directory = @working_directory.child(File.expand_path(p, @working_directory.to_s))
    Dir.chdir(@working_directory.to_s)
    # rashrc_local = @working_directory.to_s + File::SEPARATOR + RASH_LOCAL_FILE
    load RASH_LOCAL_FILE if File.exists?(RASH_LOCAL_FILE) && !File.directory?(RASH_LOCAL_FILE)
  end

  Dir.pwd
end