Watcher plugin fixes
* com.noccy.watcher: Initial inotify support * ScriptRunner now accepts an array of local vars for expansion when evaluating scripts
This commit is contained in:
parent
30dfd4889b
commit
1125ccb82d
@ -17,10 +17,12 @@ class FileWatcher {
|
|||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
if (extension_loaded('inotify')) {
|
if (extension_loaded('inotify') && !getenv("SPARK_NO_INOTIFY")) {
|
||||||
$this->monitor = new MtimeMonitor();
|
//$this->monitor = new MtimeMonitor();
|
||||||
//$this->monitor = new InotifyMonitor();
|
printf("Enabling inotify support, watching directories\n");
|
||||||
|
$this->monitor = new InotifyMonitor();
|
||||||
} else {
|
} else {
|
||||||
|
printf("No inotify support, watching file mtimes\n");
|
||||||
$this->monitor = new MtimeMonitor();
|
$this->monitor = new MtimeMonitor();
|
||||||
}
|
}
|
||||||
$this->scriptRunner = get_environment()->getScriptRunner();
|
$this->scriptRunner = get_environment()->getScriptRunner();
|
||||||
@ -38,7 +40,11 @@ class FileWatcher {
|
|||||||
private function triggerRule(Rule $rule)
|
private function triggerRule(Rule $rule)
|
||||||
{
|
{
|
||||||
$actions = $rule->getActions();
|
$actions = $rule->getActions();
|
||||||
$this->scriptRunner->evaluate($actions);
|
$locals = [
|
||||||
|
'WATCHER_RULE' => $rule->getName(),
|
||||||
|
'WATCHER_FILES' => join(" ",$rule->getWatchedFiles()),
|
||||||
|
];
|
||||||
|
$this->scriptRunner->evaluate($actions, $locals);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loop()
|
public function loop()
|
||||||
|
97
plugins/com.noccy.watcher/Monitor/InotifyMonitor.php
Normal file
97
plugins/com.noccy.watcher/Monitor/InotifyMonitor.php
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SparkPlug\Com\Noccy\Watcher\Monitor;
|
||||||
|
|
||||||
|
use SparkPlug\Com\Noccy\Watcher\Rule;
|
||||||
|
|
||||||
|
class InotifyMonitor implements MonitorInterface
|
||||||
|
{
|
||||||
|
private array $rules = [];
|
||||||
|
|
||||||
|
private array $watched = [];
|
||||||
|
|
||||||
|
private array $modified = [];
|
||||||
|
|
||||||
|
private $fd;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->fd = \inotify_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (is_resource($this->fd)) fclose($this->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function add(Rule $rule)
|
||||||
|
{
|
||||||
|
$this->rules[] = $rule;
|
||||||
|
|
||||||
|
$paths = $rule->getWatchedFiles();
|
||||||
|
$check = [];
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
if (str_contains($path, '*')) {
|
||||||
|
$check = array_merge($check, glob($path));
|
||||||
|
} else {
|
||||||
|
$check[] = $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$dirs = [];
|
||||||
|
foreach ($check as $path) {
|
||||||
|
$dir = is_dir($path) ? $path : dirname($path);
|
||||||
|
if (!array_key_exists($dir, $dirs)) {
|
||||||
|
$dirs[$dir] = $rule;
|
||||||
|
\inotify_add_watch($this->fd, $dir, \IN_ATTRIB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->watched = array_merge($this->watched, $dirs);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getModified(): array
|
||||||
|
{
|
||||||
|
$mod = $this->modified;
|
||||||
|
$this->modified = [];
|
||||||
|
return $mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getWatched(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loop()
|
||||||
|
{
|
||||||
|
$read = [ $this->fd ];
|
||||||
|
$write = null;
|
||||||
|
$except = null;
|
||||||
|
$changed = [];
|
||||||
|
while (stream_select($read,$write,$except,0)) {
|
||||||
|
$events = \inotify_read($this->fd);
|
||||||
|
foreach ($events as $event) {
|
||||||
|
$changed[] = $event['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($changed as $file) {
|
||||||
|
foreach ($this->watched as $dir=>$rule) {
|
||||||
|
if (file_exists($dir."/".$file)) {
|
||||||
|
if (!in_array($rule, $this->modified)) {
|
||||||
|
printf("~ modified: %s (%s)\n", $dir."/".$file, $rule->getName());
|
||||||
|
$this->modified[] = $rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -60,12 +60,13 @@ class MtimeMonitor implements MonitorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($check as $path) {
|
foreach ($check as $path) {
|
||||||
|
if (!file_exists($path)) continue;
|
||||||
if (empty($this->watched[$path])) {
|
if (empty($this->watched[$path])) {
|
||||||
$this->watched[$path] = filemtime($path);
|
$this->watched[$path] = filemtime($path);
|
||||||
} else {
|
} else {
|
||||||
$mtime = filemtime($path);
|
$mtime = filemtime($path);
|
||||||
if ($mtime > $this->watched[$path]) {
|
if ($mtime > $this->watched[$path]) {
|
||||||
printf("* modified: %s (%s)\n", $path, $rule->getName());
|
printf("~ modified: %s (%s)\n", $path, $rule->getName());
|
||||||
$this->watched[$path] = $mtime;
|
$this->watched[$path] = $mtime;
|
||||||
if (!in_array($rule, $this->modified)) {
|
if (!in_array($rule, $this->modified)) {
|
||||||
$this->modified[] = $rule;
|
$this->modified[] = $rule;
|
||||||
|
@ -34,4 +34,21 @@ issues.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `initial-trigger` key controls whether the rule is triggered on startup.
|
- `name` contains an optional name of the rule.
|
||||||
|
- `initial-trigger` controls whether the rule is triggered on startup. If false
|
||||||
|
or not specified, the actions will be triggered when the file is modified
|
||||||
|
after startup.
|
||||||
|
- `watch` is an array of files or wildcards to watch.
|
||||||
|
- `actions` are script actions to invoke.
|
||||||
|
|
||||||
|
The executed action will have access to the `WATCHER_RULE` and `WATCHER_FILES`
|
||||||
|
variables for expansion.
|
||||||
|
|
||||||
|
## Known issues
|
||||||
|
|
||||||
|
- The *inotify* monitor will trigger whenever a file is changed in a watched
|
||||||
|
directory, even if an explicit file is set. For example, changing the file
|
||||||
|
`./templates/index.html` will trigger the rule for `./templates/style.scss`.
|
||||||
|
If you can't live with that, export `SPARK_NO_INOTIFY=1` before invoking!
|
||||||
|
- The *mtime* monitor will poll the mtime of each watched file every check.
|
||||||
|
This works great if you are explicit in what you are watching.
|
||||||
|
@ -26,16 +26,16 @@ class ScriptRunner
|
|||||||
$this->evaluate($script);
|
$this->evaluate($script);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function evaluate(string|array $script)
|
public function evaluate(string|array $script, array $locals=[])
|
||||||
{
|
{
|
||||||
if (is_array($script)) {
|
if (is_array($script)) {
|
||||||
foreach ($script as $step) {
|
foreach ($script as $step) {
|
||||||
$this->evaluate($step);
|
$this->evaluate($step, $locals);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$script = $this->expandString($script);
|
$script = $this->expandString($script, $locals);
|
||||||
|
|
||||||
// Determine what to do
|
// Determine what to do
|
||||||
if (str_starts_with($script, '@')) {
|
if (str_starts_with($script, '@')) {
|
||||||
@ -81,10 +81,12 @@ class ScriptRunner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function expandString(string $input)
|
public function expandString(string $input, array $locals=[])
|
||||||
{
|
{
|
||||||
return preg_replace_callback('/(\$\{(.+?)\})/', function ($match) {
|
return preg_replace_callback('/(\$\{(.+?)\})/', function ($match) use ($locals) {
|
||||||
|
if (array_key_exists($match[2], $locals)) {
|
||||||
|
return $locals[$match[2]];
|
||||||
|
}
|
||||||
return ($_ENV[$match[2]]??getenv($match[2]))??null;
|
return ($_ENV[$match[2]]??getenv($match[2]))??null;
|
||||||
}, $input);
|
}, $input);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user