Created
October 11, 2019 09:03
-
-
Save flangofas/407aec84124f5e1225932269adb8e578 to your computer and use it in GitHub Desktop.
Write a function that provides change directory (cd) function for an abstract file system.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Write a function that provides change directory (cd) function for an abstract file system. | |
* Notes: | |
* root path is '/'. | |
* path separator is '/'. | |
* parent directory is addressable as '..'. | |
* directory names consist only of English alphabet letters (A-Z and a-z). | |
* the function will not be passed any invalid paths. | |
* do not use built-in path-related functions. | |
* | |
* Solution explained: | |
* The filesystem takes the cd request and breaks down into smaller tokens/patterns and then it processes one by one. | |
* The purpose of DirectoryPattern is to | |
* 1. Recognize the token | |
* 2. Execute it | |
* | |
* We can scale this solution by adding new Directory Pattern to the constructor of the FileSystem, for example, imagine we need | |
* implement the MoveBackDirectoryPattern which implies to "cd -". | |
* | |
* First we need to create the pattern and then assign it to the constructor. That's it! Of course, we need to create a new varialble | |
* oldPath in filesystem where the value of the old path can be held. | |
* | |
* Regarding, the invalid paths, a RuntimeException is being thrown when a token is not recognized. | |
* | |
* @author Antonis Flangofas | |
*/ | |
declare(strict_types=1); | |
class FileSystem | |
{ | |
private $patterns = []; | |
private $path; | |
public function __construct(string $path) | |
{ | |
$this->currentPath = $path; | |
$this->patterns = [ | |
new ChangeDirectoryPattern, | |
new MoveUpDirectoryPattern | |
]; | |
} | |
/** | |
* Changes directory of the current path | |
* | |
* @param string $newDir | |
*/ | |
public function cd(string $newDir) :void | |
{ | |
$this->process($newDir); | |
} | |
/** | |
* Returns current path | |
* | |
* @return string | |
*/ | |
public function path() :string | |
{ | |
return $this->currentPath; | |
} | |
/** | |
* Take the cd request and break it down in smaller request and process one by one. | |
* | |
* When a request is ../../foobar, it will be tokenized into an array of | |
* ['..', '..', 'directoryName']. Then, this array is being looped to match | |
* each token with the applicable cd pattern. | |
* | |
* @param string $pattern | |
*/ | |
private function process(string $pattern) :void | |
{ | |
//Break down pattern into tokens and process one by one | |
$tokenized = array_filter(explode(DIRECTORY_SEPARATOR, $pattern)); | |
//Root directory pattern | |
//Append the directory separator at start | |
$strpos = strpos($pattern, DIRECTORY_SEPARATOR); | |
if ($strpos === 0) { | |
array_unshift($tokenized, DIRECTORY_SEPARATOR); | |
} | |
//Current path change directory pattern | |
//Remove the dot | |
$strpos = strpos($pattern, './'); | |
if ($strpos === 0) { | |
array_shift($tokenized); | |
} | |
foreach ($tokenized as $token) { | |
$this->matchTokenToPattern($token); | |
} | |
} | |
/** | |
* Use the assigned patterns to find which applies per tokeninzed cd request. | |
* | |
* @param string $token | |
*/ | |
private function matchTokenToPattern(string $token) :void | |
{ | |
$patternFound = false; | |
foreach ($this->patterns as $patternObj) { | |
if ($patternObj->isApplicable($token)) { | |
$patternFound = true; | |
$this->currentPath = $patternObj->execute($this->currentPath, $token); | |
break; | |
} | |
} | |
if (!$patternFound) { | |
throw new \RuntimeException('This pattern: ' . $token . ' cannot be recognized'); | |
} | |
} | |
} | |
interface ChangeDirectoryPatternInterface | |
{ | |
/** | |
* Check whether the given pattern is recognized. | |
* | |
* @param string $pattern | |
* @return bool | |
*/ | |
public function isApplicable(string $pattern) :bool; | |
/** | |
* Execute and apply the defined change of the pattern. | |
* | |
* @param string $pattern | |
* @return bool | |
*/ | |
public function execute(string $pattern, string $path) : string; | |
} | |
class MoveUpDirectoryPattern implements ChangeDirectoryPatternInterface | |
{ | |
/** | |
* @inheritdoc | |
*/ | |
public function isApplicable(string $pattern) :bool | |
{ | |
return $pattern === '..'; | |
} | |
/** | |
* @inheritdoc | |
*/ | |
public function execute(string $path, string $dir) :string | |
{ | |
$tokenized = explode(DIRECTORY_SEPARATOR, $path); | |
array_pop($tokenized); | |
return implode($tokenized, DIRECTORY_SEPARATOR); | |
} | |
} | |
class ChangeDirectoryPattern implements ChangeDirectoryPatternInterface | |
{ | |
/** | |
* @inheritdoc | |
*/ | |
public function isApplicable(string $pattern) :bool | |
{ | |
//Single Word | |
preg_match('#^[a-z]+.*$#i', $pattern, $matches); | |
if (!empty($matches)) { | |
return true; | |
} | |
//Root directory request | |
if ($this->isRoot($pattern)) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* @inheritdoc | |
*/ | |
public function execute(string $path, string $dir) :string | |
{ | |
$result = ""; | |
if ($this->isRoot($dir)) { | |
$result = $dir; | |
} else if ($path === DIRECTORY_SEPARATOR) { | |
$result = $path . $dir; | |
} else { | |
$result = $path . DIRECTORY_SEPARATOR . $dir; | |
} | |
return $result; | |
} | |
/** | |
* Validate if it is a root dir | |
* | |
* @param string $dir | |
* @return bool | |
*/ | |
private function isRoot(string $dir) :bool | |
{ | |
return $dir === DIRECTORY_SEPARATOR; | |
} | |
} | |
/** | |
* Testing cases | |
*/ | |
$fs = new FileSystem('/a/b/c/d/e'); | |
echo $fs->path() . PHP_EOL; | |
echo "cd foo-bar" . PHP_EOL; | |
$fs->cd('foo-bar'); | |
echo $fs->path() . PHP_EOL; | |
//Prints /a/b/c/d/e/foobar | |
echo "cd ../../subfolder" . PHP_EOL; | |
$fs->cd('../../subfolder'); | |
echo $fs->path() . PHP_EOL; | |
//Prints /a/b/c/d/subfolder | |
echo "cd ./newfolder" . PHP_EOL; | |
$fs->cd('./newfolder'); | |
echo $fs->path() . PHP_EOL; | |
//Prints /a/b/c/d/subfolder/newfolder | |
echo "cd /home/username" . PHP_EOL; | |
$fs->cd('/home/username'); | |
echo $fs->path() . PHP_EOL; | |
//Prints /home/username | |
echo "cd ../documents" . PHP_EOL; | |
$fs->cd('../documents'); | |
echo $fs->path() . PHP_EOL; | |
//Prints /home/documents | |
echo "cd ../-/&&/" . PHP_EOL; | |
$fs->cd('../-/&&/"'); | |
//Throws Exception |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment