Last active
July 26, 2018 06:58
-
-
Save ifcanduela/70212d833b82a040e54afe5eeaeae28c to your computer and use it in GitHub Desktop.
Dead-simple PHP file watcher and task runner.
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
assets/css/*.less | |
assets/css/*/*.less | |
lessc assets/css/app.less app.css |
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 | |
# Firewatch | |
# ========= | |
# | |
# Dead-simple PHP task runner for watching files and folders and reacting to their changes. | |
# | |
# This script will read a file called `.watch` with this format: | |
# | |
# ``````````````````````````````````````````````` | |
# assets/css/*.less | |
# assets/css/*/*.less | |
# lessc assets/css/app.less app.compiled.less | |
# ``````````````````````````````````````````````` | |
# | |
# This definition will make the script watch for changes to .less files in assets/css and | |
# its subfolders and run `lessc` if changes are found. | |
# | |
/** | |
* Class task. | |
* | |
* Contains information about a single task: which files to check and | |
* which command to run. | |
*/ | |
class Task | |
{ | |
/** @var string */ | |
protected $command; | |
/** @var array */ | |
protected $globs; | |
/** @var string[] */ | |
protected $fileMap = []; | |
/** | |
* Create a new watching unit. | |
* | |
* @param string $command | |
* @param string[] $globs | |
*/ | |
public function __construct($command, $globs) | |
{ | |
$this->command = $command; | |
$this->globs = $globs; | |
} | |
/** | |
* Run the task. | |
* | |
* @return null | |
*/ | |
public function run() | |
{ | |
echo "Running task `{$this->command}`" . PHP_EOL; | |
shell_exec($this->command); | |
echo "- - - - - - -" . PHP_EOL; | |
} | |
/** | |
* Check for changes and run the task | |
* @return bool | |
*/ | |
public function check() | |
{ | |
// clear cached timestamps | |
clearstatcache(); | |
$changed = false; | |
$fileMap = $this->buildFileMap(); | |
if (count($fileMap) !== count($this->fileMap)) { | |
// file counts are different | |
$changed = true; | |
} elseif (array_diff_key($fileMap, $this->fileMap)) { | |
// file names are different | |
$changed = true; | |
} else { | |
// file dates are different | |
$changed = $this->compareFileMap($fileMap); | |
} | |
if ($changed) { | |
// update the file map to the latest version | |
$this->fileMap = $fileMap; | |
} | |
return $changed; | |
} | |
private function compareFileMap(array $fileMap) | |
{ | |
foreach ($fileMap as $fileName => $newTimestamp) { | |
$oldTimestamp = $this->fileMap[$fileName]; | |
if ($newTimestamp !== $oldTimestamp) { | |
return true; | |
} | |
} | |
} | |
private function buildFileMap() | |
{ | |
$files = []; | |
foreach ($this->globs as $glob) { | |
$files += glob($glob); | |
} | |
$fileMap = []; | |
foreach ($files as $filename) { | |
$fileMap[$filename] = filemtime($filename); | |
} | |
return $fileMap; | |
} | |
} | |
/** | |
* Class Watcher | |
* | |
* Keeps track of defined tasks and runs them when necessary. | |
*/ | |
class Watcher | |
{ | |
protected $tasks = []; | |
/** | |
* Create a file watcher. | |
*/ | |
public function __construct(string $watchFileName = null) | |
{ | |
if ($watchFileName) { | |
$this->buildTasks($watchFileName); | |
} | |
} | |
/** | |
* Start watching for changes. | |
* | |
* @param string $watchFileName | |
* @return null | |
*/ | |
public function watch(string $watchFileName = null) | |
{ | |
if ($watchFileName) { | |
$this->buildTasks($watchFileName); | |
} | |
$this->runTasks(); | |
} | |
private function buildTasks(string $watchFileName) | |
{ | |
if (!file_exists($watchFileName)) { | |
throw new \RuntimeException("Watch definition file not found: `{$watchFileName}`"); | |
} | |
$globs = []; | |
$f = fopen($watchFileName, "r"); | |
while ($line = fgets($f)) { | |
$line = rtrim($line); | |
if ($line && !in_array($line[0], ["#", " ", "\t"])) { | |
$globs[] = $line; | |
} | |
if ($line[0] === " ") { | |
$this->tasks[] = new Task(trim($line), $globs); | |
$globs = []; | |
} | |
} | |
} | |
private function runTasks() | |
{ | |
do { | |
foreach ($this->tasks as $task) { | |
if ($task->check()) { | |
$task->run(); | |
} | |
} | |
usleep(50000); | |
} while (true); | |
} | |
} | |
$dotWatchFile = realpath(__DIR__ . DIRECTORY_SEPARATOR . ".watch"); | |
$watcher = new Watcher(); | |
$watcher->watch($dotWatchFile); | |
// or (new Watcher())->watch(".watch"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment