module FileUtils

Monkey patch FileUtils with some useful methods

Public Class Methods

digests_changed?(key, digestfile, files) click to toggle source

Check if any digests have changed based on the given files @param key [yaml] yalm section heading to give digests @param digestfile [string] digest file to check against @param files [array] files to get digests for @returns (newfiles, modifiedfiles, deletedfiles)

# File lib/nub/fileutils.rb, line 36
def self.digests_changed?(key, digestfile, files)
  files = [files] if files.is_a?(String)
  newfiles, modifiedfiles, deletedfiles = [], [], []

  # Since layers are stacked and the digest file may be from a lower
  # layer this should be ignored unless the layer matches
  if (digests = File.exist?(digestfile) ? YAML.load_file(digestfile)[key] : nil)

    # New files: in files but not yaml
    newfiles = files.select{|x| not digests[x]}

    # Delete files: in yaml but not files
    deletedfiles = digests.map{|k,v| k}.select{|x| not files.include?(x)}

    # Modified files: in both but digest is different
    modifiedfiles = files.select{|x| digests[x] and
      Digest::MD5.hexdigest(File.binread(x)) != digests[x]}
  else
    newfiles = files
  end

  return newfiles, modifiedfiles, deletedfiles
end
exec?(name, path:nil) click to toggle source

Check PATH for executable @param name [String] name of executable to find @param path [Array] path to search @return path of executable or nil

# File lib/nub/fileutils.rb, line 65
def self.exec?(name, path:nil)
  (path || ENV['PATH'].split(File::PATH_SEPARATOR).uniq).each{|dir|
    target = File.join(dir, name)
    if stat = File.stat(target) rescue nil
      return target if stat.file? && stat.executable?
    end
  }
  return nil
end
inc_version(path, regex, major:false, minor:false, rev:true, override:nil) click to toggle source

Increment SemVer formatted versions @param path [String] file to increment version for @param regex [Regex] capture pattern for version match @param major [Bool] true then increment the major @param minor [Bool] true then increment the minor @param rev [Bool] true then increment the revision @param override [String] value to use for version if set @return new version

# File lib/nub/fileutils.rb, line 83
def self.inc_version(path, regex, major:false, minor:false, rev:true, override:nil)
  version = nil
  self.update(path){|data|

    # Increment version
    line = data.split("\n").find{|x| x[regex, 1]}
    if ver = line[regex, 1]
      new_ver = nil
      if not override
        new_ver = ver.strip.split('.')
        new_ver[0] = new_ver[0].to_i + 1 if major
        new_ver[1] = new_ver[1].to_i + 1 if minor
        new_ver[2] = new_ver[2].to_i + 1 if rev
        new_ver = new_ver * '.'
      else
        new_ver = override
      end

      # Modify the original line
      version = new_ver
      newline = line.gsub(ver, new_ver)
      data.gsub!(line, newline)
    end
  }

  return version
end
insert(file, values, regex:nil, offset:1) click to toggle source

Insert into a file Location of insert is determined by the given regex and offset. Append is used if no regex is given. @param file [string] path of file to modify @param values [array] string or list of string values to insert @param regex [string] regular expression for location, not used if offset is nil @param offset [int] offset insert location by this amount for regexs @returns true if a change was made to the file

# File lib/nub/fileutils.rb, line 119
def self.insert(file, values, regex:nil, offset:1)
  return false if not values or values.empty?

  values = [values] if values.is_a?(String)
  FileUtils.touch(file) if not File.exist?(file)

  changed = self.update(file){|data|
    lines = data.split("\n")
    lastline = lines[-1].dup

    # Match regex for insert location
    regex = Regexp.new(regex) if regex.is_a?(String)
    if i = regex ? lines.index{|x| x =~ regex} : lines.size
      i += offset if regex and offset

      # Insert at offset
      values.each{|x| lines.insert(i, x) and i += 1}

      # Change data inline
      newdata = lines * "\n"
      newdata += "\n" if lines[-1] != lastline
      data.gsub!(data, newdata)
    end
  }

  return changed
end
replace(file, regex, value) click to toggle source

Replace in file @param file [string] file to modify @param regex [string] regular expression match on @param value [string] regular expression string replacement @returns true on change

# File lib/nub/fileutils.rb, line 153
def self.replace(file, regex, value)
  changed = self.update(file){|data|
    lines = data.split("\n")

    # Search replace
    regex = Regexp.new(regex) if regex.is_a?(String)
    lines.each{|x| x.gsub!(regex, value)}

    # Change data inline
    newdata = lines * "\n"
    newdata += "\n" if data[-1] == "\n"
    data.gsub!(data, newdata)
  }

  return changed
end
resolve(file, vars) click to toggle source

Resolve template @param file [string] file to resolve templates for @param vars [hash/ostruct] templating variables to use while resolving @returns true on change

# File lib/nub/fileutils.rb, line 174
def self.resolve(file, vars)
  return self.update(file){|data| data.erb!(vars)}
end
update(filename) click to toggle source

Update the file using a block, revert on failure. Use this for file edits. Block is passed in the file data @param filename [String] name of the file to change @return true on change

# File lib/nub/fileutils.rb, line 182
def self.update(filename)
  changed = false
  block = Proc.new # Most efficient way to get block

  begin
    data = nil
    File.open(filename, 'r+'){|f|
      data = f.read
      data_orig = data.dup

      # Call block
      block.call(data)
      changed |= data_orig != data

      # Save the changes back to the file
      if changed
        f.seek(0)
        f.truncate(0)
        f << data
      end
    }
  rescue Exception => e
    # Revert back to the original incase of failure
    File.open(filename, 'w'){|f| f << data} if data
    raise
  end

  return changed
end
update_digests(key, digestfile, files) click to toggle source

Generate digests for array of files given and save them @param key [yaml] yalm section heading to give digests @param digestfile [string] digest file to update @param files [array] files to get digests for

# File lib/nub/fileutils.rb, line 244
def self.update_digests(key, digestfile, files)
  files = [files] if files.is_a?(String)

  # Build up digests structure
  digests = {}
  files.each{|x| digests[x] = Digest::MD5.hexdigest(File.binread(x)) if File.exist?(x)}

  # Write out digests structure as yaml with named header
  File.open(digestfile, 'w'){|f| f.puts({key => digests}.to_yaml)}
end
version(path, regex) click to toggle source

Get SemVer formatted versions @param path [String] file to increment version for @param regex [Regex] capture pattern for version match @return version

# File lib/nub/fileutils.rb, line 259
def self.version(path, regex)
  if File.file?(path)
    File.readlines(path).each{|line|
      if ver = line[regex, 1]
        return ver
      end
    }
  end
  return nil
end