module ChefFS::CommandLine

Public Class Methods

diff(pattern, old_root, new_root, recurse_depth, get_content) click to toggle source
# File lib/chef_fs/command_line.rb, line 129
def self.diff(pattern, old_root, new_root, recurse_depth, get_content)
  ChefFS::Parallelizer.parallelize(ChefFS::FileSystem.list_pairs(pattern, old_root, new_root), :flatten => true) do |old_entry, new_entry|
    diff_entries(old_entry, new_entry, recurse_depth, get_content)
  end
end
diff_entries(old_entry, new_entry, recurse_depth, get_content) click to toggle source

Diff two known entries (could be files or dirs)

# File lib/chef_fs/command_line.rb, line 136
def self.diff_entries(old_entry, new_entry, recurse_depth, get_content)
  # If both are directories
  if old_entry.dir?
    if new_entry.dir?
      if recurse_depth == 0
        return [ [ :common_subdirectories, old_entry, new_entry ] ]
      else
        return ChefFS::Parallelizer.parallelize(ChefFS::FileSystem.child_pairs(old_entry, new_entry), :flatten => true) do |old_child, new_child|
          ChefFS::CommandLine.diff_entries(old_child, new_child, recurse_depth ? recurse_depth - 1 : nil, get_content)
        end
      end

    # If old is a directory and new is a file
    elsif new_entry.exists?
      return [ [ :directory_to_file, old_entry, new_entry ] ]

    # If old is a directory and new does not exist
    elsif new_entry.parent.can_have_child?(old_entry.name, old_entry.dir?)
      return [ [ :deleted, old_entry, new_entry ] ]

    # If the new entry does not and *cannot* exist, report that.
    else
      return [ [ :new_cannot_upload, old_entry, new_entry ] ]
    end

  # If new is a directory and old is a file
  elsif new_entry.dir?
    if old_entry.exists?
      return [ [ :file_to_directory, old_entry, new_entry ] ]

    # If new is a directory and old does not exist
    elsif old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?)
      return [ [ :added, old_entry, new_entry ] ]

    # If the new entry does not and *cannot* exist, report that.
    else
      return [ [ :old_cannot_upload, old_entry, new_entry ] ]
    end

  # Neither is a directory, so they are diffable with file diff
  else
    are_same, old_value, new_value = ChefFS::FileSystem.compare(old_entry, new_entry)
    if are_same
      if old_value == :none
        return [ [ :both_nonexistent, old_entry, new_entry ] ]
      else
        return [ [ :same, old_entry, new_entry ] ]
      end
    else
      if old_value == :none
        old_exists = false
      elsif old_value.nil?
        old_exists = old_entry.exists?
      else
        old_exists = true
      end

      if new_value == :none
        new_exists = false
      elsif new_value.nil?
        new_exists = new_entry.exists?
      else
        new_exists = true
      end

      # If one of the files doesn't exist, we only want to print the diff if the
      # other file *could be uploaded/downloaded*.
      if !old_exists && !old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?)
        return [ [ :old_cannot_upload, old_entry, new_entry ] ]
      end
      if !new_exists && !new_entry.parent.can_have_child?(old_entry.name, old_entry.dir?)
        return [ [ :new_cannot_upload, old_entry, new_entry ] ]
      end

      if get_content
        # If we haven't read the values yet, get them now so that they can be diffed
        begin
          old_value = old_entry.read if old_value.nil?
        rescue ChefFS::FileSystem::NotFoundError
          old_value = :none
        end
        begin
          new_value = new_entry.read if new_value.nil?
        rescue ChefFS::FileSystem::NotFoundError
          new_value = :none
        end
      end

      if old_value == :none || (old_value == nil && !old_entry.exists?)
        return [ [ :added, old_entry, new_entry, old_value, new_value ] ]
      elsif new_value == :none
        return [ [ :deleted, old_entry, new_entry, old_value, new_value ] ]
      else
        return [ [ :modified, old_entry, new_entry, old_value, new_value ] ]
      end
    end
  end
rescue ChefFS::FileSystem::FileSystemError => e
  return [ [ :error, old_entry, new_entry, nil, nil, e ] ]
end
diff_print(pattern, a_root, b_root, recurse_depth, output_mode, format_path = nil, diff_filter = nil, ui = nil) { |"Common subdirectories: #{new_path}\n"| ... } click to toggle source
# File lib/chef_fs/command_line.rb, line 26
def self.diff_print(pattern, a_root, b_root, recurse_depth, output_mode, format_path = nil, diff_filter = nil, ui = nil)
  if format_path.nil?
    format_path = proc { |entry| entry.path_for_printing }
  end

  get_content = (output_mode != :name_only && output_mode != :name_status)
  found_match = false
  diff(pattern, a_root, b_root, recurse_depth, get_content).each do |type, old_entry, new_entry, old_value, new_value, error|
    found_match = true unless type == :both_nonexistent
    old_path = format_path.call(old_entry)
    new_path = format_path.call(new_entry)

    case type
    when :common_subdirectories
      if output_mode != :name_only && output_mode != :name_status
        yield "Common subdirectories: #{new_path}\n"
      end

    when :directory_to_file
      next if diff_filter && diff_filter !~ /T/
      if output_mode == :name_only
        yield "#{new_path}\n"
      elsif output_mode == :name_status
        yield "T\t#{new_path}\n"
      else
        yield "File #{old_path} is a directory while file #{new_path} is a regular file\n"
      end

    when :file_to_directory
      next if diff_filter && diff_filter !~ /T/
      if output_mode == :name_only
        yield "#{new_path}\n"
      elsif output_mode == :name_status
        yield "T\t#{new_path}\n"
      else
        yield "File #{old_path} is a regular file while file #{new_path} is a directory\n"
      end

    when :deleted
      next if diff_filter && diff_filter !~ /D/
      if output_mode == :name_only
        yield "#{new_path}\n"
      elsif output_mode == :name_status
        yield "D\t#{new_path}\n"
      elsif old_value
        result = "diff --knife #{old_path} #{new_path}\n"
        result << "deleted file\n"
        result << diff_text(old_path, '/dev/null', old_value, '')
        yield result
      else
        yield "Only in #{format_path.call(old_entry.parent)}: #{old_entry.name}\n"
      end

    when :added
      next if diff_filter && diff_filter !~ /A/
      if output_mode == :name_only
        yield "#{new_path}\n"
      elsif output_mode == :name_status
        yield "A\t#{new_path}\n"
      elsif new_value
        result = "diff --knife #{old_path} #{new_path}\n"
        result << "new file\n"
        result << diff_text('/dev/null', new_path, '', new_value)
        yield result
      else
        yield "Only in #{format_path.call(new_entry.parent)}: #{new_entry.name}\n"
      end

    when :modified
      next if diff_filter && diff_filter !~ /M/
      if output_mode == :name_only
        yield "#{new_path}\n"
      elsif output_mode == :name_status
        yield "M\t#{new_path}\n"
      else
        result = "diff --knife #{old_path} #{new_path}\n"
        result << diff_text(old_path, new_path, old_value, new_value)
        yield result
      end

    when :both_nonexistent
    when :added_cannot_upload
    when :deleted_cannot_download
    when :same
      # Skip these silently
    when :error
      if error.is_a?(ChefFS::FileSystem::OperationFailedError)
        ui.error "#{format_path.call(error.entry)} failed to #{error.operation}: #{error.message}" if ui
        error = true
      elsif error.is_a?(ChefFS::FileSystem::OperationNotAllowedError)
        ui.error "#{format_path.call(error.entry)} #{error.reason}." if ui
      else
        raise error
      end
    end
  end
  if !found_match
    ui.error "#{pattern}: No such file or directory on remote or local" if ui
    error = true
  end
  error
end

Private Class Methods

canonicalize_json(json_text) click to toggle source
# File lib/chef_fs/command_line.rb, line 251
def self.canonicalize_json(json_text)
  parsed_json = JSON.parse(json_text, :create_additions => false)
  sorted_json = sort_keys(parsed_json)
  JSON.pretty_generate(sorted_json)
end
diff_text(old_path, new_path, old_value, new_value) click to toggle source
# File lib/chef_fs/command_line.rb, line 257
def self.diff_text(old_path, new_path, old_value, new_value)
  # Copy to tempfiles before diffing
  # TODO don't copy things that are already in files!  Or find an in-memory diff algorithm
  begin
    new_tempfile = Tempfile.new("new")
    new_tempfile.write(new_value)
    new_tempfile.close

    begin
      old_tempfile = Tempfile.new("old")
      old_tempfile.write(old_value)
      old_tempfile.close

      result = `diff -u #{old_tempfile.path} #{new_tempfile.path}`
      result = result.gsub(/^--- #{old_tempfile.path}/, "--- #{old_path}")
      result = result.gsub(/^\+\+\+ #{new_tempfile.path}/, "+++ #{new_path}")
      result
    ensure
      old_tempfile.close!
    end
  ensure
    new_tempfile.close!
  end
end
sort_keys(json_object) click to toggle source
# File lib/chef_fs/command_line.rb, line 239
def self.sort_keys(json_object)
  if json_object.is_a?(Array)
    json_object.map { |o| sort_keys(o) }
  elsif json_object.is_a?(Hash)
    new_hash = {}
    json_object.keys.sort.each { |key| new_hash[key] = sort_keys(json_object[key]) }
    new_hash
  else
    json_object
  end
end