Class: CSD::Commands

Inherits:
Object show all
Includes:
Process
Defined in:
lib/csd/commands.rb

Overview

This class 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.

Defined Under Namespace

Classes: Replacer

Instance Method Summary collapse

Instance Method Details

#cd(target, params = {}) ⇒ Object

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.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/csd/commands.rb', line 68

def cd(target, params={})
  default_params = { :die_on_failure => true, :internal => false }
  params = default_params.merge(params)
  target = target.pathnamify
  result = CommandResult.new
  UI.info "cd #{target}".yellow unless params[:internal]
  if Options.reveal
    # We need to fake changing the directory in reveal mode.
    @pwd = target.to_s
    result.success = true
  else
    begin
      Dir.chdir(target)
      result.success = target.current_path?
    rescue Exception => e
      result.reason = "Cannot change to directory `#{target}´. Reason: #{e.message}"
      params[:die_on_failure] ? raise(CSD::Error::Command::CdFailed, result.reason) : UI.error(result.reason)
    end
  end
  result
end

#copy(src, dest, params = {}) ⇒ Object

Copies one or several files to the destination



117
118
119
# File 'lib/csd/commands.rb', line 117

def copy(src, dest, params={})
  transfer(:copy, src, dest, params)
end

#download(url, destination) ⇒ Object

This downloads a file. It could be replaced by Ruby internals in the future to make it work in Windows. The CommandResult of Cmd.run is returned, so result.success? will indicate whether the download was successful or not.



302
303
304
305
306
307
308
309
310
# File 'lib/csd/commands.rb', line 302

def download(url, destination)
  if Cmd.run('curl -h', :internal => true, :die_on_failure => false).success?
    # Darwin
    Cmd.run "curl #{url} --location -o #{destination}", :die_on_failure => false, :announce_pwd => false
  elsif Cmd.run('wget -h', :internal => true, :die_on_failure => false).success?
    # Linux
    Cmd.run "wget #{url} -O #{destination}", :die_on_failure => false, :announce_pwd => false
  end
end

#git_clone(name, repository, destination) ⇒ Object

Clones the master branch of a repository using git. name is a String used for UI outputs, repository is the URL/path to the repository, destination is an absolute or relative path to where the repository should be downloaded to.

This command will not do anything if the destination already exists.



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/csd/commands.rb', line 264

def git_clone(name, repository, destination)
  result = CommandResult.new
  destination = destination.pathnamify
  if destination.directory? and !Options.reveal
    UI.warn "Skipping #{name} download, because the directory already exists: #{destination.enquote}"
    result.already_exists = true
    return result
  end
  unless destination.parent.writable? or Options.reveal
    UI.error "Could not download #{name} (no permission): #{destination.enquote}"
    result.no_permission = true
    return result
  end
  UI.info "Downloading #{name} to #{destination.enquote}".green.bold
  if Options.github_tar and repository =~ /github.com/
    Cmd.mkdir destination
    Cmd.cd destination
    repository = repository.gsub('.git', '/tarball/master')
    Cmd.run "wget #{repository}", :announce_pwd => false
    if tar_file = Dir[File.join(destination, '*.tar*')].first or Options.reveal
      Cmd.run "tar -xzf #{tar_file || '[NAME OF THE TARFILE]'}"
      Cmd.run "rm #{tar_file || '[NAME OF THE TARFILE]'}"
    end
    if extracted_directory = Dir[File.join(destination, '*')].first or Options.reveal
      content = Options.reveal ? File.join('[EXTRACTED DIRECTORY]', '*') : File.join(extracted_directory, '*')
      Cmd.run "mv #{content} #{destination}", :announce_pwd => false
      Cmd.run "rm -r #{extracted_directory || '[EXTRACTED DIRECTORY]'}"
    end
  else
    # We will simply return the CommandResult of the run-method.
    Cmd.run("git clone #{repository} #{destination}", :announce_pwd => false)
  end
end

#mkdir(target, params = {}) ⇒ Object

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')    # => #<CommandResult...>
result.success?          # => true
result.already_existed?  # => false

puts "I created a directory" if mkdir('bar').success?

mkdir('i/can/create/directories/recursively')


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/csd/commands.rb', line 37

def mkdir(target, params={})
  default_params = { :die_on_failure => true, :internal => false }
  params = default_params.merge(params)
  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 unless params[:internal]
      # Try to create the directory
      target.mkpath unless Options.reveal
    rescue Errno::EACCES => e
      result.reason = "Cannot create directory (no permission): #{target}"
      params[:die_on_failure] ? raise(CSD::Error::Command::MkdirFailed, result.reason) : UI.error(result.reason)
    end
  end
  result.success  = (target.directory? or Options.reveal)
  result.writable = (target.writable? or Options.reveal)
  result
end

#move(src, dest, params = {}) ⇒ Object

Moves one or several files to the destination



123
124
125
# File 'lib/csd/commands.rb', line 123

def move(src, dest, params={})
  transfer(:move, src, dest, params)
end

#pwdObject

This returns the current pwd. However, it will return a fake result if we are in reveal-commands-mode.



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/csd/commands.rb', line 129

def pwd
  begin
    if Options.reveal
      @pwd ||= Dir.pwd
    else
      Dir.pwd
    end
  rescue Errno::ENOENT => e
    UI.debug "The current pwd could not be located. Was the directory removed?"
    nil
  end
end

#replace(filepath, pattern = '', substitution = '', params = {}, &block) ⇒ Object

Replaces all occurences of a pattern in a file. In a block, it yields the Replacer class. Otherwise it calls the replace function in the Replacer class.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/csd/commands.rb', line 146

def replace(filepath, pattern='', substitution='', params={}, &block)
  # In case of a block, the second argument might be the params
  params = pattern if pattern.is_a?(Hash)
  # Setting default parameters
  default_params = { :die_on_failure => true }
  params = default_params.merge(params)
  UI.info "Modifying contents of `#{filepath}´ as follows:".cyan
  Replacer.filepath = filepath
  if block_given?
    yield Replacer
  else
    Replacer.replace(pattern, substitution, params)
  end
end

#run(cmd, params = {}) ⇒ Object

Runs a command on the system.

Options

The following options can be passed as a hash.

:die_on_failure

If the status code of the command was not 0, raise an Command::RunFailed exception (default: true).

:announce_pwd

Before running the command, announce in which path the command will be executed (default: true).

:verbose

Instead of printing just one `.´ per command output line, print the full command output lines (default: false).

:internal

If this parameter is true, there will be no output what-so-ever for running this command. (default: false).

:force_in_reveal

If this parameter is true, the command will be executed, even in --reveal mode (default: false).

Returns

This method returns a CommandResult object with the following values:

output

The command's output as a String (with newline delimiters). Note that the status code can be accessed via the global variable $?.

status?

Contains $? and all the methods Ruby provides for it.

success?

true if the command was successful, nil if not (internally $?.success? is called).



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/csd/commands.rb', line 226

def run(cmd, params={})
  default_params = { :die_on_failure => true, :announce_pwd => true, :verbose => Options.verbose, :internal => false, :force_in_reveal => false }
  params = default_params.merge(params)
  result = CommandResult.new
  cmd = cmd.to_s
  UI.info "Running command in #{pwd}".yellow if params[:announce_pwd] and !params[:internal]
  UI.info cmd.cyan unless params[:internal]
  if Options.reveal and !params[:force_in_reveal]
    result.success = true
    return result
  end
  result.output = ''
  STDOUT.sync = true # Because we redirect stderr into stdout to get the output and it needs to be flushing automatically
  IO.popen("#{cmd} 2>&1") do |pipe| # IO redirection is performed using operators
    pipe.sync = true
    while line = pipe.gets
      unless params[:internal] # No output needed for the unit-tests and AI-internal use
        params[:verbose] ? UI.info("       #{line}") : UI.indicate_activity
      end
      result.output << line
    end
  end
  UI.separator unless params[:verbose] or params[:internal] # i.e. if dots are concatenated in the same line, we should create a new line after them
  result.status = $?
  result.success = $?.success?
  if params[:die_on_failure] and !result.success
    UI.info result.output unless params[:verbose] # In verbose mode, we don't need to repeat the unsuccessful command's output
    raise CSD::Error::Command::RunFailed, "The last command was unsuccessful." 
  end
  result
end

#touch_and_replace_content(target, content = '', params = {}) ⇒ Object

Creates a new file and writes content to it. Truncates the file if it already had content.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/csd/commands.rb', line 92

def touch_and_replace_content(target, content='', params={})
  default_params = { :die_on_failure => true, :internal => false }
  params = default_params.merge(params)
  target = target.pathnamify
  result = CommandResult.new
  unless params[:internal]
    UI.info "Writing content into file #{target} as follows:".cyan 
    UI.info "#{content}"
  end
  if Options.reveal
    result.success = true
  else
    begin
      File.open(target, 'w') { |f| f << content }
      result.success = target.file? # TODO: Maybe check for the actual content of the created file here
    rescue Exception => e
      result.reason = "Cannot write to file `#{target}´. Reason: #{e.message}"
      params[:die_on_failure] ? raise(CSD::Error::Command::TouchAndReplaceContentFailed, result.reason) : UI.error(result.reason)
    end
  end
  result
end