Skip to content

Commit

Permalink
Merge branch 'release/0.1.6' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
olinox14 committed Apr 17, 2024
2 parents e61dc37 + 2a4a5ba commit b22f02d
Show file tree
Hide file tree
Showing 4 changed files with 387 additions and 134 deletions.
55 changes: 22 additions & 33 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,43 @@
[![CI](https://github.com/olinox14/path-php/actions/workflows/php.yml/badge.svg)](https://github.com/olinox14/path-php/actions/workflows/php.yml)
[![Coverage Status](https://coveralls.io/repos/github/olinox14/path-php/badge.svg?branch=master)](https://coveralls.io/github/olinox14/path-php?branch=master)
[![Version](http://poser.pugx.org/olinox14/path-php/version)](https://packagist.org/packages/olinox14/path-php)
[![License](http://poser.pugx.org/olinox14/path-php/license)](https://packagist.org/packages/olinox14/path-php)
[![PHP Version Require](http://poser.pugx.org/olinox14/path-php/require/php)](https://packagist.org/packages/olinox14/path-php)

# Path-php

> This library is still under development, DO NOT USE IN PRODUCTION. Contributions, however, are welcome.
> This library is still under development, **USE WITH CAUTION**.
An intuitive and object-oriented library for file and path operations, inspired by the path.py python library.
An **intuitive**, **standalone**, and **object-oriented** library for file and path operations,
inspired by the ['path' python library](https://path.readthedocs.io/en/latest/api.html#path.Path.parts).

<?php

use Path\Path;

// Get the parent directory of the current script file
$dir = (new Path(__file__))->parent();

// Display the liste of the subdirectories of this directory
var_dump(
$dir->dirs()
);
// Get the parent directory of the current script file and list its subdirs
$script = new Path(__file__);
$dir = $script->parent();
var_dump($dir->dirs());

// Get the path of the working directory

// Get the path of the working directory, iterate over its files and change their permissions
$path = new Path('.');

// Iterate over the files in this directory and change the permissions of these files to 755
foreach($path->files() as $file) {
$file->chmod(755);
}

// Create a new path by adding a file's name to the previous path
$newPath = $path->append('readme.md');

// Put content into a file
$path = (new Path('.'))->append('readme.md');

// Display the absolute path of this file
var_dump($newPath->absPath());
$path->putContent('new readme content');

// And many more...


Full documentation : [API Documentation](https://olinox14.github.io/path-php/classes/Path-Path.html)


## Requirement

Expand Down Expand Up @@ -68,10 +74,6 @@ your current script lies into .md files :
}
}

## Documentation

> [API Documentation](https://olinox14.github.io/path-php/classes/Path-Path.html)
## Contribute

### Git branching
Expand Down Expand Up @@ -155,16 +157,3 @@ And then run phpdoc with :
## Licence

Path-php is under the [MIT](http://opensource.org/licenses/MIT) licence.

## Roadmap

0.1.6 :

* [ ] multi os compat (windows)
* [ ] handle protocols (ftp, sftp, file, smb, http, ...etc)
* [ ] handle unc paths (windows)
* [ ] improve error management and tracebacks
* [ ] add 'ignore' and 'errorOnExistingDestination' to the copyTree method
* [ ] review copyTree performances
* [ ] study the interest of implementing a 'mergeTree' method
* [ ] study the interest of mimic the perms of the source when using the copy, copyTree and move methods
9 changes: 9 additions & 0 deletions src/BuiltinProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
*/
class BuiltinProxy
{
public static string $DIRECTORY_SEPARATOR = DIRECTORY_SEPARATOR;

public function getHome(): string
{
return PHP_OS_FAMILY == 'Windows' ?
$_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH'] :
$_SERVER['HOME'];
}

public function date(string $format, int $time): string
{
return date($format, $time);
Expand Down
121 changes: 88 additions & 33 deletions src/Path.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,61 @@ public static function join(string|self $path, string|self ...$parts): self
$parts = array_map(fn ($p) => (string)$p, $parts);

foreach ($parts as $part) {
if (str_starts_with($part, DIRECTORY_SEPARATOR)) {
if (str_starts_with($part, BuiltinProxy::$DIRECTORY_SEPARATOR)) {
$path = $part;
} elseif (!$path || str_ends_with($path, DIRECTORY_SEPARATOR)) {
} elseif (!$path || str_ends_with($path, BuiltinProxy::$DIRECTORY_SEPARATOR)) {
$path .= $part;
} else {
$path .= DIRECTORY_SEPARATOR . $part;
$path .= BuiltinProxy::$DIRECTORY_SEPARATOR . $part;
}
}
return new self($path);
}

/**
* Split the pathname path into a pair (drive, tail) where drive is either a mount point or the empty string.
* On systems which do not use drive specifications, drive will always be the empty string. In all cases,
* drive + tail will be the same as path.
*
* On Windows, splits a pathname into drive/UNC sharepoint and relative path.
*
* If the path contains a drive letter, drive will contain everything up to and including the colon:
*
* Path::splitDrive("c:/dir")
* >>> ["c:", "/dir"]
*
* If the path contains a UNC path, drive will contain the host name and share:
*
* Path::splitDrive("//host/computer/dir")
* >>> ["//host/computer", "/dir"]
*
* @param string|self $path The path with the drive.
* @return array<string> An array containing the drive and the path.
*/
public static function splitDrive(string|self $path): array
{
$path = (string)$path;

$matches = [];

preg_match('/(^[a-zA-Z]:)(.*)/', $path, $matches);
if ($matches) {
return array_slice($matches, -2);
}

$rx =
BuiltinProxy::$DIRECTORY_SEPARATOR === '/' ?
'/(^\/\/[\w\-\s]{2,15}\/[\w\-\s]+)(.*)/' :
'/(^\\\\\\\\[\w\-\s]{2,15}\\\[\w\-\s]+)(.*)/';

preg_match($rx, $path, $matches);
if ($matches) {
return array_slice($matches, -2);
}

return ['', $path];
}

public function __construct(string|self $path)
{
$this->builtin = new BuiltinProxy();
Expand Down Expand Up @@ -300,17 +344,16 @@ public function name(): string
/**
* Normalize the case of a pathname.
*
* On Windows, convert all characters in the pathname to lowercase, and also convert
* forward slashes to backward slashes. On other operating systems,
* return the path unchanged.
* Convert all characters in the pathname to lowercase, and also convert
* forward slashes to backward slashes.
*
* @return self The instance of the current object.
*/
public function normCase(): self
{
return $this->cast(
strtolower(
str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $this->path())
str_replace(['/', '\\'], BuiltinProxy::$DIRECTORY_SEPARATOR, $this->path())
)
);
}
Expand All @@ -322,33 +365,26 @@ public function normCase(): self
* and A/foo/../B all become A/B. This string manipulation may change the meaning of a path that contains
* symbolic links. On Windows, it converts forward slashes to backward slashes. To normalize case, use normcase().
*
* // TODO: becare of the normcase when we're getting to the windows compat
*
* @return self A new instance of the class with the normalized path.
*/
public function normPath(): self
{
$path = $this->normCase()->path();
$path = str_replace(['/', '\\'], BuiltinProxy::$DIRECTORY_SEPARATOR, $this->path());

// TODO: handle case where path start with //
if (empty($path)) {
return $this->cast('.');
}

// Also tests some special cases we can't really do anything with
if (!str_contains($path, '/') || $path === '/' || '.' === $path || '..' === $path) {
if (!str_contains($path, BuiltinProxy::$DIRECTORY_SEPARATOR) || $path === '/' || '.' === $path || '..' === $path) {
return $this->cast($path);
}

$path = rtrim($path, '/');
$path = rtrim($path, BuiltinProxy::$DIRECTORY_SEPARATOR);

// Extract the scheme if any
$scheme = null;
if (strpos($path, '://')) {
list($scheme, $path) = explode('://', $path, 2);
}
[$prefix, $path] = self::splitDrive($path);

$parts = explode('/', $path);
$parts = explode(BuiltinProxy::$DIRECTORY_SEPARATOR, $path);
$newParts = [];

foreach ($parts as $part) {
Expand Down Expand Up @@ -376,13 +412,13 @@ public function normPath(): self
}

// Rebuild path
$newPath = implode('/', $newParts);

// Add scheme if any
if ($scheme !== null) {
$newPath = $scheme . '://' . $newPath;
if ($prefix) {
array_shift($newParts); // Get rid of the leading empty string resulting from slitDrive result
array_unshift($newParts, rtrim($prefix, BuiltinProxy::$DIRECTORY_SEPARATOR));
}

$newPath = implode(BuiltinProxy::$DIRECTORY_SEPARATOR, $newParts);

return $this->cast($newPath);
}

Expand Down Expand Up @@ -1019,14 +1055,20 @@ public function expand(): self
* Expands the user directory in the file path.
*
* @return self The modified instance with the expanded user path.
* @throws IOException
*/
public function expandUser(): self
{
if (!str_starts_with($this->path(), '~/')) {
return $this;
}
$home = $this->builtin->getHome();

$home = $this->cast($_SERVER['HOME']);
if (!$home) {
throw new IOException("Error while getting home directory");
}

$home = $this->cast($home);
return $home->append(substr($this->path(), 2));
}

Expand Down Expand Up @@ -1180,6 +1222,7 @@ public function rmdir(bool $recursive = false, bool $permissive = false): void
*
* @throws FileNotFoundException
* @throws IOException
* @throws FileExistsException
*/
public function rename(string|self $newPath): self
{
Expand Down Expand Up @@ -1328,7 +1371,8 @@ public function chunks(int $chunk_size = 8192): Generator
*/
public function isAbs(): bool
{
return str_starts_with($this->path, '/');
[$drive, $path] = Path::splitDrive($this->path());
return !empty($drive) || str_starts_with($path, '/');
}

/**
Expand Down Expand Up @@ -1591,16 +1635,27 @@ public function symlink(string | self $newLink): self
*/
public function parts(): array
{
[$prefix, $path] = self::splitDrive($this->path());
$parts = [];
if (str_starts_with($this->path, DIRECTORY_SEPARATOR)) {
$parts[] = DIRECTORY_SEPARATOR;

if ($prefix) {
$path = ltrim($path, BuiltinProxy::$DIRECTORY_SEPARATOR);
} elseif (str_starts_with($path, BuiltinProxy::$DIRECTORY_SEPARATOR)) {
$parts[] = BuiltinProxy::$DIRECTORY_SEPARATOR;
}

$parts += explode(BuiltinProxy::$DIRECTORY_SEPARATOR, $path);

if ($prefix) {
array_unshift($parts, $prefix);
}
$parts += explode(DIRECTORY_SEPARATOR, $this->path);
return $parts;
}

/**
* Compute a version of this path that is relative to another path.
* This method relies on the php `realpath` method and then requires the path to refer to
* an existing file.
*
* @param string|Path $basePath
* @return self
Expand All @@ -1621,8 +1676,8 @@ public function getRelativePath(string|self $basePath): self
throw new FileNotFoundException("$basePath does not exist or unable to get a real path");
}

$pathParts = explode(DIRECTORY_SEPARATOR, $path);
$baseParts = explode(DIRECTORY_SEPARATOR, $realBasePath);
$pathParts = explode(BuiltinProxy::$DIRECTORY_SEPARATOR, $path);
$baseParts = explode(BuiltinProxy::$DIRECTORY_SEPARATOR, $realBasePath);

while (count($pathParts) && count($baseParts) && ($pathParts[0] == $baseParts[0])) {
array_shift($pathParts);
Expand All @@ -1631,10 +1686,10 @@ public function getRelativePath(string|self $basePath): self

return $this->cast(
str_repeat(
'..' . DIRECTORY_SEPARATOR,
'..' . BuiltinProxy::$DIRECTORY_SEPARATOR,
count($baseParts)
) . implode(
DIRECTORY_SEPARATOR,
BuiltinProxy::$DIRECTORY_SEPARATOR,
$pathParts
)
);
Expand Down
Loading

0 comments on commit b22f02d

Please sign in to comment.