296 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php # -*- coding: utf-8 mode: php -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
 | 
						|
namespace nur;
 | 
						|
 | 
						|
use nur\b\io\IOException;
 | 
						|
 | 
						|
/**
 | 
						|
 * Class path: manipulation de chemins du système de fichiers
 | 
						|
 */
 | 
						|
class path {
 | 
						|
  /**
 | 
						|
   * Normaliser le chemin spécifié:
 | 
						|
   * - supprimer si possible les ocurrences de ./ et ../
 | 
						|
   * - supprimer les slash multiples, sauf s'il y en a exactement 2 au début de
 | 
						|
   * la chaine
 | 
						|
   * - "~/path" est transformé en "$HOME/path"
 | 
						|
   *
 | 
						|
   * cas particuliers:
 | 
						|
   * - retourner la valeur null inchangée
 | 
						|
   * - retourner '.' pour un chemin vide
 | 
						|
   */
 | 
						|
  static final function normalize(?string $path): ?string {
 | 
						|
    if ($path === null) return null;
 | 
						|
    if ($path !== "") {
 | 
						|
      if (substr($path, 0, 2) == "~/") {
 | 
						|
        $path = os::homedir().substr($path, 1);
 | 
						|
      }
 | 
						|
 | 
						|
      $initial_slashes = strpos($path, "/") === 0;
 | 
						|
      if ($initial_slashes &&
 | 
						|
        strpos($path, "//") === 0 &&
 | 
						|
        strpos($path, "///") === false) {
 | 
						|
        $initial_slashes = 2;
 | 
						|
      }
 | 
						|
      $initial_slashes = intval($initial_slashes);
 | 
						|
 | 
						|
      $comps = explode("/", $path);
 | 
						|
      $new_comps = array();
 | 
						|
      foreach ($comps as $comp) {
 | 
						|
        if ($comp === "" || $comp === ".") continue;
 | 
						|
        if ($comp != ".." ||
 | 
						|
          (!$initial_slashes && !$new_comps) ||
 | 
						|
          ($new_comps && end($new_comps) == "..")) {
 | 
						|
          array_push($new_comps, $comp);
 | 
						|
        } elseif ($new_comps) {
 | 
						|
          array_pop($new_comps);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      $comps = $new_comps;
 | 
						|
 | 
						|
      $path = implode("/", $comps);
 | 
						|
      if ($initial_slashes) $path = str_repeat("/", $initial_slashes) . $path;
 | 
						|
    }
 | 
						|
    return $path !== ""? $path: ".";
 | 
						|
  }
 | 
						|
 | 
						|
  /** Comme normalize() mais retourner inchangée la valeur false */
 | 
						|
  static final function with($path) {
 | 
						|
    if ($path === null || $path === false) return $path;
 | 
						|
    else return self::normalize(strval($path));
 | 
						|
  }
 | 
						|
 | 
						|
  /** obtenir le chemin absolu normalisé correspondant à $path */
 | 
						|
  static final function abspath($path, ?string $cwd=null) {
 | 
						|
    if ($path === null || $path === false) return $path;
 | 
						|
    $path = strval($path);
 | 
						|
    if (substr($path, 0, 1) !== "/") {
 | 
						|
      if ($cwd === null) $cwd = getcwd();
 | 
						|
      $path = "$cwd/$path";
 | 
						|
    }
 | 
						|
    return self::normalize($path);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * obtenir le chemin $path exprimé relativement à $basedir.
 | 
						|
   * si $basedir est relatif, il est exprimé par rapport à $cwd qui vaut par
 | 
						|
   * défaut le chemin courant.
 | 
						|
   */
 | 
						|
  static final function relpath($path, string $basedir="", ?string $cwd=null): string {
 | 
						|
    $path = self::abspath($path, $cwd);
 | 
						|
    $basedir = self::abspath($basedir, $cwd);
 | 
						|
    if ($path === $basedir) return "";
 | 
						|
 | 
						|
    $initial_slashes = strpos($path, "//") === 0? 2: 1;
 | 
						|
    $path = substr($path, $initial_slashes);
 | 
						|
    $initial_slashes = strpos($basedir, "//") === 0? 2: 1;
 | 
						|
    $basedir = substr($basedir, $initial_slashes);
 | 
						|
    if ($basedir === "") return $path;
 | 
						|
 | 
						|
    $pparts = $path? explode("/", $path): [];
 | 
						|
    $pcount = count($pparts);
 | 
						|
    $bparts = explode("/", $basedir);
 | 
						|
    $bcount = count($bparts);
 | 
						|
 | 
						|
    $i = 0;
 | 
						|
    while ($i < $pcount && $i < $bcount && $pparts[$i] === $bparts[$i]) {
 | 
						|
      $i++;
 | 
						|
    }
 | 
						|
    $ups = array_fill(0, $bcount - $i, "..");
 | 
						|
    $relparts = array_merge($ups, array_slice($pparts, $i));
 | 
						|
    return implode("/", $relparts);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * obtenir un chemin visuellement agréable:
 | 
						|
   * - $HOME est remplacé par "~"
 | 
						|
   * - $CWD/ est remplacé par ""
 | 
						|
   */
 | 
						|
  static final function ppath($path, ?string $cwd=null): string {
 | 
						|
    $path = self::abspath($path, $cwd);
 | 
						|
    $homedir = os::homedir();
 | 
						|
    $cwd = getcwd()."/";
 | 
						|
    if (str::del_prefix($path, $cwd)) {
 | 
						|
      return $path;
 | 
						|
    }
 | 
						|
    if (str::_starts_with($homedir, $path)) {
 | 
						|
      str::del_prefix($path, $homedir);
 | 
						|
      $path ="~$path";
 | 
						|
    }
 | 
						|
    return $path;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * obtenir le chemin absolu canonique correspondant à $path
 | 
						|
   *
 | 
						|
   * @throws IOException si le chemin n'existe pas ou si une autre erreur se
 | 
						|
   * produit
 | 
						|
   */
 | 
						|
  static final function realpath($path) {
 | 
						|
    if ($path === null || $path === false) return $path;
 | 
						|
    $path = strval($path);
 | 
						|
    $realpath = realpath($path);
 | 
						|
    if ($realpath === false) throw IOException::last_error();
 | 
						|
    return $realpath;
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * obtenir le chemin parent de $path
 | 
						|
   *
 | 
						|
   * cas particuliers:
 | 
						|
   * dirname("/") === "/";
 | 
						|
   * dirname("filename") === ".";
 | 
						|
   */
 | 
						|
  static final function dirname($path): ?string {
 | 
						|
    if ($path === null || $path === false) return $path;
 | 
						|
    else return dirname(strval($path));
 | 
						|
  }
 | 
						|
 | 
						|
  /** obtenir le nom du fichier sans son chemin */
 | 
						|
  static final function filename($path): ?string {
 | 
						|
    if ($path === null || $path === false) return $path;
 | 
						|
    $index = strrpos($path, "/");
 | 
						|
    if ($index !== false) $path = substr($path, $index + 1);
 | 
						|
    return $path;
 | 
						|
  }
 | 
						|
 | 
						|
  /** obtenir le nom de base du fichier (sans son chemin et sans l'extension) */
 | 
						|
  static final function basename($path): ?string {
 | 
						|
    if ($path === null || $path === false) return $path;
 | 
						|
    $basename = self::filename($path);
 | 
						|
    $index = strrpos($basename, ".");
 | 
						|
    if ($index !== false) $basename = substr($basename, 0, $index);
 | 
						|
    return $basename;
 | 
						|
  }
 | 
						|
 | 
						|
  /** obtenir l'extension du fichier. l'extension est retournée avec le '.' */
 | 
						|
  static final function ext($path): ?string {
 | 
						|
    if ($path === null || $path === false) return $path;
 | 
						|
    $ext = self::filename($path);
 | 
						|
    $index = strrpos($ext, ".");
 | 
						|
    if ($index === false) $ext = "";
 | 
						|
    else $ext = substr($ext, $index);
 | 
						|
    return $ext;
 | 
						|
  }
 | 
						|
 | 
						|
  /** découper le chemin entre sa partie "répertoire" et "fichier" */
 | 
						|
  static final function split($path): array {
 | 
						|
    if ($path === null || $path === false) return [$path, $path];
 | 
						|
    elseif ($path === "") return ["", ""];
 | 
						|
    $index = strrpos($path, "/");
 | 
						|
    if ($index !== false) {
 | 
						|
      if ($index == 0) $dir = "/";
 | 
						|
      else $dir = substr($path, 0, $index);
 | 
						|
      $file = substr($path, $index + 1);
 | 
						|
    } else {
 | 
						|
      $dir = "";
 | 
						|
      $file = $path;
 | 
						|
    }
 | 
						|
    return [$dir, $file];
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * joindre les composantes pour faire un seul chemin normalisé. les composantes
 | 
						|
   * sont joints inconditionnellements
 | 
						|
   */
 | 
						|
  static final function join(...$parts): ?string {
 | 
						|
    $path = null;
 | 
						|
    foreach ($parts as $part) {
 | 
						|
      if ($part === null || $part === false) continue;
 | 
						|
      if ($path && substr($path, -1) !== "/"
 | 
						|
        && substr($part, 0, 1) !== "/") $path .= "/";
 | 
						|
      if ($path === null) $path = "";
 | 
						|
      $path .= $part;
 | 
						|
    }
 | 
						|
    return self::normalize($path);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * joindre les composantes pour faire un seul chemin normalisé. chaque
 | 
						|
   * composante relative s'ajoute au chemin. chaque composante absolue relance
 | 
						|
   * le calcul de puis le début
 | 
						|
   * e.g reljoin("a", "b", "../c", "d/e") retourne "a/c/d/e"
 | 
						|
   * alors que reljoin("a", "b", "/c", "d/e") retourne "/c/d/e"
 | 
						|
   */
 | 
						|
  static final function reljoin(...$parts): ?string {
 | 
						|
    $path = null;
 | 
						|
    foreach ($parts as $part) {
 | 
						|
      if ($part === null || $part === false) continue;
 | 
						|
      if (substr($part, 0, 1) == "/") {
 | 
						|
        $path = $part;
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      if ($path && substr($path, -1) !== "/") $path .= "/";
 | 
						|
      elseif ($path === null) $path = "";
 | 
						|
      $path .= $part;
 | 
						|
    }
 | 
						|
    return self::normalize($path);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * tester si $dir est situé dans $basedir.
 | 
						|
   *
 | 
						|
   * par défaut, on considère qu'un chemin $dir est situé dans lui-même sauf si
 | 
						|
   * $strict == true, auquel cas "$dir est dans $basedir" implique $dir !== $basedir
 | 
						|
   */
 | 
						|
  static final function within(string $dir, string $basedir, bool $strict=false): bool {
 | 
						|
    $dir = self::abspath($dir);
 | 
						|
    $basedir = self::abspath($basedir);
 | 
						|
    if ($dir === $basedir) return !$strict;
 | 
						|
    $prefix = $basedir;
 | 
						|
    if ($prefix !== "/") $prefix .= "/";
 | 
						|
    return substr($dir, 0, strlen($prefix)) === $prefix;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * tester si $file a un répertoire, c'est à dire s'il contient le caractère '/'
 | 
						|
   * e.g have_dir('dir/name') est vrai alors que have_dir('name.ext') est faux
 | 
						|
   */
 | 
						|
  static final function have_dir(string $file): bool {
 | 
						|
    return strpos($file, "/") !== false;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * tester si le chemin est qualifié, c'est à dire s'il est absolu ou s'il est
 | 
						|
   * relatif à '.' ou '..'
 | 
						|
   * e.g is_qualified('./a/b') est vrai alors que is_qualified('a/b') est faux
 | 
						|
   */
 | 
						|
  static final function is_qualified(string $file): bool {
 | 
						|
    if (strpos($file, "/") === false) return false;
 | 
						|
    if (substr($file, 0, 1) == "/") return true;
 | 
						|
    if (substr($file, 0, 2) == "./") return true;
 | 
						|
    if (substr($file, 0, 3) == "../") return true;
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  /** tester si $file a une extension */
 | 
						|
  static final function have_ext(string $file): bool {
 | 
						|
    $pos = strrpos($file, "/");
 | 
						|
    if ($pos === false) $pos = 0;
 | 
						|
    return strpos($file, ".", $pos) !== false;
 | 
						|
  }
 | 
						|
 | 
						|
  static final function ensure_ext(string $path, string $new_ext, ?string $replace_ext=null): string {
 | 
						|
    [$dir, $filename] = self::split($path);
 | 
						|
    if (self::ext($filename) === $replace_ext) {
 | 
						|
      $filename = self::basename($filename);
 | 
						|
    }
 | 
						|
    $filename .= $new_ext;
 | 
						|
    return self::join($dir, $filename);
 | 
						|
  }
 | 
						|
 | 
						|
  /** tester si le fichier, le répertoire ou le lien symbolique existe */
 | 
						|
  static final function exists(string $file): bool {
 | 
						|
    return is_link($file) || file_exists($file);
 | 
						|
  }
 | 
						|
 | 
						|
  /** tester si $dir est un répertoire (mais pas un lien symbolique) */
 | 
						|
  static final function is_dir(string $dir, bool $allow_link=false): bool {
 | 
						|
    return is_dir($dir) && ($allow_link || !is_link($dir));
 | 
						|
  }
 | 
						|
 | 
						|
  /** tester si $dir est un fichier (mais pas un lien symbolique) */
 | 
						|
  static final function is_file(string $file, bool $allow_link=false): bool {
 | 
						|
    return is_file($file) && ($allow_link || !is_link($file));
 | 
						|
  }
 | 
						|
}
 |