# # Filename:: path.rb # Description:: OS Path String Manipulation Functions # # Author:: David Muir # Author:: Greg Smith # Author:: Luke Openshaw # Author:: Marissa Warner-Wu # # #----------------------------------------------------------------------------- # 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