Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Allows administrators to write small scripts which users can run through the fil
<command>OCA\FilesScripts\Command\ListScripts</command>
<command>OCA\FilesScripts\Command\ImportScripts</command>
<command>OCA\FilesScripts\Command\ExportScripts</command>
<command>OCA\FilesScripts\Command\Interactive</command>
</commands>
<settings>
<admin>OCA\FilesScripts\Settings\AdminSettings</admin>
Expand Down
13 changes: 13 additions & 0 deletions docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,16 @@ To get the `<userid>` value, you can use the user-list command:
```sh
occ user:list
```

## Interactive (REPL) shell
You can start an interactive shell with the `files_scripts:interactive` command
```sh
occ files_scripts:interactive
```

Multi-line commands can be given by ending the line with a backslash character:
```
> print("hello \
> world")
```
You can type the `exit` command to exit the shell.
90 changes: 90 additions & 0 deletions lib/Command/Interactive.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php
namespace OCA\FilesScripts\Command;

use OC\Core\Command\Base;
use OCA\FilesScripts\Db\Script;
use OCA\FilesScripts\Interpreter\Context;
use OCA\FilesScripts\Interpreter\Interpreter;
use OCA\FilesScripts\Interpreter\Lua\LuaProvider;
use OCP\Files\IRootFolder;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Interactive extends Base {
private LuaProvider $luaProvider;
private IRootFolder $rootFolder;
private Interpreter $interpreter;

public function __construct(
IRootFolder $rootFolder,
LuaProvider $luaProvider,
Interpreter $interpreter
) {
parent::__construct('files_scripts:interactive');
$this->rootFolder = $rootFolder;
$this->luaProvider = $luaProvider;
$this->interpreter = $interpreter;
}
protected function configure(): void {
$this->setDescription('Starts an interactive Lua shell where you can interact with the server using the scripting API')
->addOption('user', 'u', InputOption::VALUE_OPTIONAL, 'User as which the action should be run')
->addOption('file', 'f', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'File path or id of a file given to the action as input file');

parent::configure();
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$userId = $input->getOption('user');
$rootFolder = $this->rootFolder;

try {
if ($userId) {
$rootFolder = $this->rootFolder->getUserFolder($userId);
}
$files = RunScript::getFilesForCommand($input, $output, $rootFolder);
} catch (\Throwable $e) {
$output->writeln('<error>' . $e->getMessage() .'</error>');
return 1;
}

$output->writeln('<info>Lua files_scripts interpreter started...</info>');
$output->writeln('<info>To stop type "exit"</info>');

$context = new Context($this->luaProvider->createLua(), $rootFolder, [], $files);
$f = fopen( 'php://stdin', 'r' );
$command = "";
while (true) {
echo "> ";
$line = fgets( $f );

// Handle exit clause
if (trim($line) == "exit") {
fclose($f);
break;
}

$replacements = 0;
$line = preg_replace('/(.*)\\\\(\s*)$/i', '$1 $2', $line, 1, $replacements);
$command .= $line;

// if line does not end with `\` backslash we execute the command
if ($replacements == 0) {
$this->executeCommand($command, $context, $output);
$command = "";
}
}

return 0;
}

private function executeCommand(string $command, Context $context, OutputInterface $output): void {
$script = new Script();
$script->setProgram($command);
try {
$this->interpreter->execute($script, $context);
} catch (\Throwable $e) {
$output->writeln('<error>' . $e->getMessage() . '</error>');
}
}
}
69 changes: 42 additions & 27 deletions lib/Command/RunScript.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<?php
namespace OCA\FilesScripts\Command;

use Error;
use OC\Core\Command\Base;
use OCA\FilesScripts\Db\ScriptInputMapper;
use OCA\FilesScripts\Db\ScriptMapper;
use OCA\FilesScripts\Interpreter\Context;
use OCA\FilesScripts\Interpreter\Lua\LuaProvider;
use OCA\FilesScripts\Service\ScriptService;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -44,29 +46,8 @@ protected function configure(): void {
parent::configure();
}

protected function execute(InputInterface $input, OutputInterface $output) {
$scriptId = $input->getArgument('id');
$userId = $input->getOption('user');
$scriptInputsJson = $input->getOption('inputs') ?? '{}';
public static function getFilesForCommand(InputInterface $input, OutputInterface $output, Folder $rootFolder) {
$fileInputs = $input->getOption('file') ?? [];

try {
$scriptInputsData = json_decode($scriptInputsJson, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $err) {
$output->writeln('<error>Could not parse the inputs JSON</error>');
return 1;
}

$script = $this->scriptMapper->find($scriptId);
$output->writeln('<info>Executing file action: ' . $script->getTitle() . '</info>');

$scriptInputs = $this->scriptInputMapper->findAllByScriptId($scriptId);
foreach ($scriptInputs as $scriptInput) {
$value = $scriptInputsData[$scriptInput->getName()] ?? null;
$scriptInput->setValue($value);
}

$rootFolder = $this->rootFolder->getUserFolder($userId);

$files = [];
$n = 1;
Expand All @@ -77,22 +58,56 @@ protected function execute(InputInterface $input, OutputInterface $output) {
if (ctype_digit(strval($fileInput))) {
$nodes = $rootFolder->getById(intval($fileInput));
if (!isset($nodes[0])) {
$output->writeln('<error>Could not find input file ' . $fileInput . ' belonging in root folder ' . $rootFolder->getPath() . ' for file action</error>');
return 1;
throw new Error('Could not find input file ' . $fileInput . ' belonging in root folder ' . $rootFolder->getPath() . ' for file action');
}
$file = $nodes[0];
unset($nodes);
} else {
$file = $rootFolder->get($fileInput);
}
} catch (\Exception $e) {
$output->writeln('<error>Could not find input file ' . $fileInput . ' belonging in root folder ' . $rootFolder->getPath() . ' for file action</error>');
return 1;
} catch (\Throwable $e) {
throw $e;
}
$files[$n++] = $file;
}
}

return $files;
}

protected function execute(InputInterface $input, OutputInterface $output) {
$scriptId = $input->getArgument('id');
$userId = $input->getOption('user');
$scriptInputsJson = $input->getOption('inputs') ?? '{}';

try {
$scriptInputsData = json_decode($scriptInputsJson, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $err) {
$output->writeln('<error>Could not parse the inputs JSON</error>');
return 1;
}

$script = $this->scriptMapper->find($scriptId);
$output->writeln('<info>Executing file action: ' . $script->getTitle() . '</info>');

$scriptInputs = $this->scriptInputMapper->findAllByScriptId($scriptId) ?? [];
foreach ($scriptInputs as $scriptInput) {
$value = $scriptInputsData[$scriptInput->getName()] ?? null;
$scriptInput->setValue($value);
}

$rootFolder = $this->rootFolder;
try {
if ($userId) {
$rootFolder = $this->rootFolder->getUserFolder($userId);
}
$files = self::getFilesForCommand($input, $output, $rootFolder);
} catch (\Throwable $e) {
$output->writeln('<error>' . $e->getMessage() .'</error>');
return 1;
}

print(gettype($scriptInputs));
$context = new Context(
$this->luaProvider->createLua(),
$rootFolder,
Expand Down
2 changes: 1 addition & 1 deletion lib/Interpreter/RegistrableFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ final protected function getHomeFolder(): Folder {
return $folder;
}

final protected function getPath(array $data): string {
final protected function getPath(?array $data): string {
return ($data['path'] ?? '<no-path>') . '/' . ($data['name'] ?? '<no-name>');
}

Expand Down