585 lines
14 KiB
Ruby
Executable File
585 lines
14 KiB
Ruby
Executable File
#
|
|
# Filename:: path.rb
|
|
# Description:: OS Path String Manipulation Functions
|
|
#
|
|
# Author:: David Muir <david.muir@rockstarnorth.com>
|
|
# Author:: Greg Smith <greg@rockstarnorth.com>
|
|
# Author:: Luke Openshaw <luke.openshaw@rockstarnorth.com>
|
|
# Author:: Marissa Warner-Wu <marissa.warner-wu@rockstarnorth.com>
|
|
#
|
|
#
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Uses
|
|
#-----------------------------------------------------------------------------
|
|
require 'pipeline/util/string'
|
|
require 'pipeline/win32/kernel32'
|
|
require 'platform'
|
|
|
|
module Pipeline
|
|
module OS
|
|
|
|
#
|
|
# == Description
|
|
#
|
|
# Contains class methods (static) that can manipulate path strings.
|
|
#
|
|
# Note: all paths returned from these functions are normalised.
|
|
#
|
|
class Path
|
|
|
|
#---------------------------------------------------------------------
|
|
# Constants
|
|
#---------------------------------------------------------------------
|
|
|
|
# Normalised path directory separator character.
|
|
DIRECTORY_SEPARATOR = '/'
|
|
# Normalised path filename and extension separator character.
|
|
EXTENSION_SEPARATOR = '.'
|
|
|
|
# Maximum path length on Windows.
|
|
MAX_PATH_LEN = 260
|
|
|
|
private
|
|
DOWNCASE_ON_NORMALISE_DEFAULT = true
|
|
@@downcase_on_normalise = DOWNCASE_ON_NORMALISE_DEFAULT
|
|
|
|
public
|
|
|
|
#---------------------------------------------------------------------
|
|
# Methods
|
|
#---------------------------------------------------------------------
|
|
|
|
# Override the 'downcase_on_normalise' feature.
|
|
def Path::set_downcase_on_normalise( state )
|
|
|
|
@@downcase_on_normalise = state
|
|
end
|
|
|
|
#
|
|
# This is the safest way to use 'downcase_on_normalise' because it
|
|
# resets it to default after the user-block is executed.
|
|
#
|
|
def Path::no_downcase_on_normalise( &block )
|
|
begin
|
|
Path::set_downcase_on_normalise( false )
|
|
yield if ( block_given? )
|
|
ensure
|
|
Path::set_downcase_on_normalise( DOWNCASE_ON_NORMALISE_DEFAULT )
|
|
end
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Gets the extension from a file path
|
|
#
|
|
# == Example Usage
|
|
# Path.get_extension( 'x:/test_log.txt' )
|
|
# => 'txt'
|
|
#
|
|
def Path::get_extension( path )
|
|
|
|
segs = path.split(EXTENSION_SEPARATOR)
|
|
return segs[segs.size - 1] if ( segs.size > 1 )
|
|
return '' unless ( segs.size > 1 )
|
|
end
|
|
|
|
|
|
#
|
|
# == Description
|
|
# Gets the extension from a file path
|
|
#
|
|
# == Example Usage
|
|
# Path.get_extension( 'x:/test_log.txt' )
|
|
# => 'txt'
|
|
#
|
|
def Path::get_multi_extension( path )
|
|
|
|
segs = path.split(EXTENSION_SEPARATOR)
|
|
return segs[1..(segs.length - 1)].join(EXTENSION_SEPARATOR) if ( segs.size > 1 )
|
|
return '' unless ( segs.size > 1 )
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Replace the path's extension with another, returning new path.
|
|
#
|
|
# == Example Usage
|
|
# Path.replace_ext( 'x:/test_log.txt', 'html' )
|
|
# => 'x:/test_log.html'
|
|
#
|
|
def Path::replace_ext( path, new_ext )
|
|
|
|
ext = Path.get_extension( path )
|
|
Path.normalise( path.gsub( /\.#{ext}/, ".#{new_ext}" ) )
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Remove the extension from a path.
|
|
#
|
|
# == Example Usage
|
|
# Path.remove_extension( 'x:/test_log.txt' )
|
|
# => 'x:/test_log'
|
|
#
|
|
def Path::remove_extension( path )
|
|
|
|
ext = Path::get_extension( path )
|
|
|
|
return ( path[0...(path.size - ext.size - 1)] ) if ( ext.size > 0 )
|
|
return ( path[0...(path.size - ext.size)] ) if ( 0 == ext.size )
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Get the filename from a path.
|
|
#
|
|
# == Example Usage
|
|
# Path.get_basename( 'x:/test_log.txt' )
|
|
# => 'test_log'
|
|
#
|
|
def Path::get_basename( path )
|
|
|
|
remove_extension( get_filename( path ) )
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Appends to a basename
|
|
#
|
|
# == Example Usage
|
|
# Path.append_basename( 'x:/test_log.txt', '_version1' )
|
|
# => 'x:/test_log.txt'
|
|
#
|
|
def Path::append_basename( path, append )
|
|
|
|
basename = get_basename(path)
|
|
extension = get_extension(path)
|
|
temp_filename = remove_filename(path)
|
|
temp_filename += "/" + basename + append + "." + extension
|
|
end
|
|
|
|
|
|
#
|
|
# == Description
|
|
# Get the full directory name from a path.
|
|
#
|
|
# == Example Usage
|
|
# Path.get_directory( 'x:/test/folder1/test_log.txt' )
|
|
# => 'x:/test/folder1/'
|
|
#
|
|
def Path::get_directory( path )
|
|
|
|
npath = Path.normalise( path )
|
|
ret = File.dirname( npath )
|
|
|
|
ret = "" if ret == "."
|
|
ret
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Get the filename from a path
|
|
#
|
|
# == Example Usage
|
|
# Path.get_filename( 'x:/test_log.txt' )
|
|
# => 'test_log.txt'
|
|
#
|
|
def Path::get_filename( path )
|
|
|
|
path = normalise(path)
|
|
segs = path.split(DIRECTORY_SEPARATOR)
|
|
segs[segs.length - 1]
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Get the drive from a path (rage image and pack mount point aware).
|
|
#
|
|
# == Example Usage
|
|
# Path.get_drive( 'x:/test_log.txt' )
|
|
# => 'x'
|
|
#
|
|
def Path::get_drive( path )
|
|
|
|
npath = Path.normalise( path )
|
|
npath.split( ':' )[0]
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Returns an array of all the separate elements in a path.
|
|
#
|
|
# == Example Usage
|
|
# Path.get_parts( 'x:/tools/test_log.txt' )
|
|
# => ['x', 'tools', 'test_log.txt']
|
|
#
|
|
def Path::get_parts( path )
|
|
|
|
npath = Path.normalise( path )
|
|
segs = npath.split(DIRECTORY_SEPARATOR)
|
|
|
|
segs[0] = segs[0].split( ':' )[0]
|
|
|
|
segs
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Returns an array of all the directories in a path.
|
|
#
|
|
# == Example Usage
|
|
# Path.get_directories( 'x:/tools/test/test_log.txt' )
|
|
# => ['tools', 'test']
|
|
#
|
|
def Path::get_directories( path )
|
|
|
|
npath = Path.normalise( path )
|
|
npath = Path.remove_filename( npath )
|
|
npath = Path.strip_drive( npath )
|
|
segs = npath.split(DIRECTORY_SEPARATOR)
|
|
|
|
segs
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Remove the filename from a path.
|
|
#
|
|
# == Example Usage
|
|
# Path.remove_filename( 'x:/folder1/test_log.txt' )
|
|
# => 'x:/folder1'
|
|
#
|
|
def Path::remove_filename( path )
|
|
path = normalise(path)
|
|
|
|
# Only remove the filename if it exists
|
|
if path.include? EXTENSION_SEPARATOR
|
|
segs = path.split(DIRECTORY_SEPARATOR)
|
|
segs.delete_at(segs.length - 1)
|
|
|
|
if segs.length > 0 then
|
|
|
|
path = segs[segs.length - 1]
|
|
segs.delete_at(segs.length - 1)
|
|
|
|
segs.reverse.each { |seg|
|
|
|
|
path = seg + DIRECTORY_SEPARATOR + path
|
|
}
|
|
end
|
|
end
|
|
|
|
path
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Normalises a path string, replacing "\" characters with "/"
|
|
# characters, returning the resulting path.
|
|
#
|
|
# == Example Usage
|
|
# Path.normalise( 'X:\\folder1/folder2\\test_file.txt' )
|
|
# => 'x:/folder1/folder2/test_file.txt'
|
|
#
|
|
def Path::normalise( path )
|
|
|
|
return "" if ( nil == path )
|
|
|
|
# Ensure path is lowercase
|
|
npath = path
|
|
npath = path.downcase() if @@downcase_on_normalise
|
|
|
|
# Replace backslash characters
|
|
npath = npath.gsub( '\\', DIRECTORY_SEPARATOR )
|
|
# Replace double-forward slash characters
|
|
npath = npath.gsub( '//', DIRECTORY_SEPARATOR )
|
|
|
|
# Reinstate UNC prefix (if available)
|
|
if ( path.starts_with( '\\\\' ) || path.starts_with( '//' ) )
|
|
npath = "#{DIRECTORY_SEPARATOR}#{npath}"
|
|
end
|
|
|
|
npath
|
|
end
|
|
|
|
# == Description
|
|
# Normalises a path string, replacing "/" characters with "\"
|
|
# characters, returning the resulting path.
|
|
#
|
|
# == Example Usage
|
|
# Path.normalise( 'X:\\folder1/folder2\\test_file.txt' )
|
|
# => 'x:\folder1\folder2\test_file.txt'
|
|
#
|
|
def Path::dos_format( path )
|
|
|
|
return "" if ( nil == path )
|
|
|
|
# Ensure path is lowercase
|
|
npath = path
|
|
npath = path.downcase() if @@downcase_on_normalise
|
|
|
|
# Replace backslash characters
|
|
npath.gsub!( DIRECTORY_SEPARATOR, '\\')
|
|
# Replace double-forward slash characters
|
|
npath.gsub!( '//', '\\' )
|
|
|
|
# Reinstate UNC prefix (if available)
|
|
if ( path.starts_with( '\\\\' ) || path.starts_with( '//' ) )
|
|
npath = "\\#{npath}"
|
|
end
|
|
|
|
npath
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Strips the prefix drive letter and colon from a path string (if it
|
|
# exists), returning the resulting path. Also accepts longer drive
|
|
# names and names which include underscores (e.g. "extract_pack:/").
|
|
#
|
|
# == Example Usage
|
|
# Path.strip_drive( 'x:/test_log.txt' )
|
|
# => 'test_log.txt'
|
|
#
|
|
def Path::strip_drive( path )
|
|
|
|
npath = Path.normalise( path )
|
|
if ( npath =~ /^([A-Za-z_]+:\/)/ ) then
|
|
|
|
npath.gsub!( $1, '' )
|
|
end
|
|
|
|
npath
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Combines two paths ensuring there is an appropriate path name
|
|
# separator character between the two parts.
|
|
#
|
|
# The path is normalised before being returned.
|
|
#
|
|
# == Example Usage
|
|
# Path.combine( "X:\\", "tools_release" ) => "x:/tools_release"
|
|
#
|
|
def Path::combine( *paths )
|
|
|
|
npaths = Array.new()
|
|
paths.each do |path|
|
|
|
|
if path != "" then
|
|
|
|
npaths << Path.normalise( path )
|
|
|
|
end
|
|
end
|
|
|
|
cpath = npaths.join( '/' )
|
|
Path.normalise( cpath )
|
|
end
|
|
|
|
|
|
#
|
|
# == Description
|
|
# Determines whether the specified path ends with the specified
|
|
# string.
|
|
#
|
|
# WARNING: paths are normalised before checking the end characters,
|
|
# please ensure that the end token is compatible with normalised
|
|
# paths.
|
|
#
|
|
# == Example Usage
|
|
# Path.ends_with( "x:/tools_release/file.txt", "file.txt" ) => true
|
|
# Path.ends_with( "x:\\tools_release\\", "\\" ) => false
|
|
# Path.ends_with( "x:\\tools_release\\", "/" ) => true
|
|
#
|
|
def Path::ends_with( path, ends )
|
|
|
|
npath = Path.normalise( path )
|
|
npath.ends_with( ends )
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Determines whether the specified path starts with the specified
|
|
# string.
|
|
#
|
|
# == Example Usage
|
|
# Path.starts_with( 'test_file.log', 'test' )
|
|
# => true
|
|
#
|
|
def Path::starts_with( path, start )
|
|
|
|
npath = Path.normalise( path )
|
|
npath.starts_with( start )
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Return a platform-native path String.
|
|
#
|
|
def Path::platform_native( path )
|
|
|
|
npath = Path::normalise( path )
|
|
case ::Platform::OS
|
|
when :win32
|
|
npath.gsub( '/', '\\' )
|
|
else
|
|
npath
|
|
end
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# works out the common path from an array of paths supplied
|
|
#
|
|
def Path::get_common( paths_in )
|
|
|
|
return "" if paths_in == nil
|
|
return "" if paths_in.size == 0
|
|
|
|
paths = Array.new()
|
|
|
|
paths_in.each { |path|
|
|
|
|
path = File.dirname(path) if File.directory?(path)
|
|
paths << Path::normalise(path).split("/")
|
|
}
|
|
|
|
common_denom = -1
|
|
max_size = paths[0].size
|
|
|
|
paths.each { |path|
|
|
|
|
max_size = path.size if path.size < max_size
|
|
}
|
|
|
|
paths.each { |path|
|
|
|
|
0.upto(max_size - 1) { |i|
|
|
|
|
if paths[0][i] != path[i] then
|
|
|
|
max_size = i
|
|
break
|
|
end
|
|
}
|
|
}
|
|
|
|
new_path = Array.new()
|
|
|
|
0.upto(max_size - 1) { |i|
|
|
|
|
new_path << paths[0][i]
|
|
}
|
|
|
|
ret = new_path.join("/")
|
|
ret = ret + "/" if ret.size > 0
|
|
|
|
ret
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Grab the trailing directory in the path
|
|
#
|
|
# == Example Usage
|
|
# Path.get_trailing_directory( 'X:/dir/subdir/subsubdir/file.txt' )
|
|
# => 'subsubdir'
|
|
#
|
|
def Path::get_trailing_directory(path)
|
|
path = Path::remove_filename( path )
|
|
segs = path.split(DIRECTORY_SEPARATOR)
|
|
|
|
if segs.length > 0 then
|
|
path = segs[segs.length - 1]
|
|
end
|
|
|
|
path
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Make an absolute path relative to another absolute path
|
|
#
|
|
# == Example Usage
|
|
# Path.make_relative( 'X:/dir/subdir/subsubdir/file.txt', 'X:/dir/subdir2/subsubdir/' )
|
|
# => '../../subdir/subsubdir'
|
|
#
|
|
def Path::make_relative( path, rel )
|
|
|
|
return path if (rel.index(":") == nil)
|
|
|
|
path = File.expand_path(path)
|
|
rel = File.expand_path(rel)
|
|
|
|
path_tokens = path.downcase.split("/")
|
|
relative_tokens = rel.downcase.split("/")
|
|
|
|
same = 0
|
|
|
|
0.upto(path_tokens.size - 1) { |i|
|
|
|
|
break if i >= relative_tokens.size
|
|
break if relative_tokens[i] != path_tokens[i]
|
|
same = same + 1
|
|
}
|
|
|
|
return path if same == 0
|
|
|
|
path_tokens = path.split("/")
|
|
relative_tokens = rel.split("/")
|
|
|
|
relative_tokens = relative_tokens[same..-1]
|
|
path_tokens = path_tokens[same..-1]
|
|
|
|
new_path = ("../" * relative_tokens.size) + path_tokens.join("/")
|
|
new_path
|
|
end
|
|
|
|
#
|
|
# Return the normalised Windows directory.
|
|
#
|
|
def Path::get_windows_directory( )
|
|
windir = ' '*(MAX_PATH_LEN+1)
|
|
Win32::Kernel32::GetSystemWindowsDirectory.call( windir, windir.size )
|
|
OS::Path.normalise( windir.strip.chomp.strip )
|
|
end
|
|
|
|
#
|
|
# Return the normalised temporary files directory.
|
|
#
|
|
def Path::get_temp_directory( )
|
|
tempdir = ' '*(MAX_PATH_LEN+1)
|
|
Win32::Kernel32::GetTempPath.call( tempdir.size, tempdir )
|
|
OS::Path.normalise( tempdir.strip.chomp.strip )
|
|
end
|
|
|
|
#
|
|
# Return the normalised Windows System directory.
|
|
#
|
|
def Path::get_windows_system_directory( )
|
|
sysdir = ' '*(MAX_PATH_LEN+1)
|
|
Win32::Kernel32::GetSystemDirectory.call( sysdir, sysdir.size )
|
|
OS::Path.normalise( sysdir.strip.chomp.strip )
|
|
end
|
|
end
|
|
|
|
def Path::get_corebasename( path )
|
|
|
|
basename = path
|
|
|
|
while basename.index('.') != nil
|
|
|
|
basename = remove_extension( get_filename( basename ) )
|
|
end
|
|
|
|
basename
|
|
end
|
|
|
|
end # OS module
|
|
end # Pipeline module
|
|
|
|
# End of path.rb
|