# encoding: utf-8 require 'pathname' require 'ostruct' module CSD # This module contains wrapper methods for standard file system operations. They are meant to be # a little bit more robust (e.g. raising no exceptions) and return elaborate feedback on their operation. # All of these methods, except for the +run+ method, are platform independent. # module Commands # The Process module is a collection of methods used to manipulate processes. # We use it to check whether we run as sudo or not by evaluating the uid of ths user (if it's 0 it's root) include Process # Objects of this class can be returned by Commands. Since it is an OpenStruct object, # it can contain an arbritary number of values. # class CommandResult < OpenStruct # This creates an convenient, read-only accessor for the OpenStruct object values. # It simply maps methods that end with a ? to the same method without ?. # # ==== Examples # # command_result.something? # => command_result.something # def method_missing(meth, *args, &block) meth.to_s.ends_with?('?') ? self.send(meth.to_s.chop.to_sym, *args, &block) : super end end # Creates a directory recursively. # # ==== Returns # # This method returns a CommandResult object with the following values: # # [+success?+] +true+ if the directory exists after the operation, +nil+ if not. # [+already_existed?+] +true+ if the directory existed before the operation, +nil+ if not. # [+writable?+] +true+ if the directory is writable, +false+ if not, +nil+ if the directory doesn't exist. # # ==== Examples # # result = mkdir('foo') # => # # result.success? # => true # result.already_existed? # => false # # puts "I created a directory" if mkdir('bar').success? # # mkdir('i/can/create/directories/recursively') # def mkdir(target) target = target.pathnamify result = CommandResult.new if target.directory? # Don't do anything if the directory already exists result.already_existed = true else begin UI.info "Creating directory: #{target}".cyan # Try to create the directory target.mkpath unless (Options.dry or Options.reveal) rescue Errno::EACCES => e UI.error "Cannot create directory (no permission): #{target}" return result end end result.success = (target.directory? or Options.reveal) result.writable = (target.writable? or Options.reveal) result end # Changes the current directory. # # ==== Returns # # This method returns a CommandResult object with the following values: # # [+success?+] +true+ if pwd is where it was requested to be after the operation, +nil+ if not. # def cd(target) target = target.pathnamify result = CommandResult.new if target.directory? or Options.reveal UI.info "cd #{target}".yellow if Options.reveal @pwd = target.to_s else Dir.chdir(target) end elsif target.exist? UI.error "Cannot change to directory because it exists but is not a directory: #{target}".red end result.success = (target.current_path? or Options.reveal) result end # Copies one or several files to the destination # def copy(src, dest) FileUtils.cp(src, dest) unless (Options.dry or Options.reveal) end # This returns the current pwd. However, it will return a fake result if we are in reveal-commands-mode. # def pwd if Options.reveal @pwd ||= Dir.pwd else Dir.pwd end end # Replaces all occurences of a pattern in a file # # ==== Returns # # This method returns a CommandResult object with the following values: # # [+success?+] +true+ if the replacement was successful, +nil+ if not. # def replace(filepath, pattern, substitution) result = CommandResult.new begin UI.info "Modifying contents of `#{filepath}´ as follows:".blue UI.info " (Replacing all occurences of `#{pattern}´ with `#{substitution}´)".blue new_file_content = File.read(filepath).gsub(pattern, substitution) File.open(filepath, 'w+') { |file| file << new_file_content } unless (Options.dry or Options.reveal) rescue Errno::ENOENT => e result.success = false end result.success = true if Options.reveal result end # Runs a command on the system. # # ==== Returns # # The command's output as a +String+ (with newline delimiters). Note that the exit code can be accessed via the global variable $? # # ==== Options # # The following options can be passed as a hash. # # [+:exit_on_failure+] If the exit code of the command was not 0, exit the CSD application. # def run(cmd, params={}) cmd = cmd.to_s default_params = { :die_on_failure => true, :silent => false } params = default_params.merge(params) unless params[:silent] UI.info "Running command in #{pwd}".yellow UI.info cmd.cyan end return '' if Options.reveal or Options.dry ret = '' IO.popen(cmd) do |stdout| stdout.each do |line| UI.info " #{line}" unless params[:silent] ret << line end end die_if_last_command_had_errors if params[:exit_on_failure] ret end def die_if_last_command_had_errors UI.die "The last command was unsuccessful." unless $?.try(:success?) end end # Having a class to include all command modules # class CommandsInstance include Commands end # Wrapping the CommandsInstance class # class Cmd COMMANDS = %w{ mkdir cd run replace copy } def self.instance @@instance ||= CommandsInstance.new end def self.method_missing(meth, *args, &block) COMMANDS.include?(meth.to_s) ? instance.send(meth, *args, &block) : super end end end