Skip to content
Draft
2 changes: 2 additions & 0 deletions classes/local/cli/application.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use local_devkit\local\cli\commands\env\env_show;
use local_devkit\local\cli\commands\format;
use local_devkit\local\cli\commands\lint\handler as lint_handler;
use local_devkit\local\cli\commands\make;
use local_devkit\local\cli\commands\mcp\mcp_serve;
use local_devkit\local\cli\commands\plugins\plugins_list;
use local_devkit\local\cli\commands\purge\handler as purge_handler;
Expand All @@ -46,6 +47,7 @@ public function __construct() {
$this->addCommand(new database_table());
$this->addCommand(new env_show());
$this->addCommand(new format());
$this->addCommand(new make());
$this->addCommand(new mcp_serve());
lint_handler::register($this);
purge_handler::register($this);
Expand Down
77 changes: 77 additions & 0 deletions classes/local/cli/commands/make.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.

namespace local_devkit\local\cli\commands;

use local_devkit\local\format\base as format_base;
use local_devkit\local\format\phpcbf;
use local_devkit\local\format\pint;
use local_devkit\local\generators\snippets\base as snippet_base;
use local_devkit\local\generators\snippets\php_class;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
* Command to generate boilerplates.
*
* @package local_devkit
* @copyright 2026 Felix
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[AsCommand(
name: 'make',
description: 'Make boilerplate.',
)]
class make extends Command {
/**
* Invoke.
*/
public function __invoke(
#[Argument('Path to new file.')]
string $path,
SymfonyStyle $io,
InputInterface $input,
OutputInterface $output,
): int {
[$generatorclass, $formatterclassnames] = $this->get_generator_and_formatter_classnames();

// The generators need the file to actually exist.
@mkdir(dirname($path));
file_put_contents($path, '');

$generator = new $generatorclass($path);
$contents = $generator->generate();
file_put_contents($path, $contents);

foreach ($formatterclassnames as $formatterclassname) {
\core\di::get($formatterclassname)::format($path);
}

return Command::SUCCESS;
}

/**
* Summary of get_generator_and_formatters
* @return array{class-string<snippet_base>, class-string<format_base>[]}
*/
private function get_generator_and_formatter_classnames() {
return [php_class::class, [pint::class, phpcbf::class]];
}
}
22 changes: 20 additions & 2 deletions classes/local/generators/boilerplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ public static function get_boilerplate(bool $usehttps): string {
}

/**
* Get the canonical GPL boilerplate with JS line comments.
* Get the canonical GPL boilerplate with '//' comments.
* @param bool $usehttps
* @return string
*/
public static function generate_for_javascript(bool $usehttps): string {
public static function generate_with_slash_comments(bool $usehttps): string {
$raw = self::get_boilerplate($usehttps);

$lines = explode("\n", rtrim($raw));
Expand All @@ -69,6 +69,24 @@ public static function generate_for_javascript(bool $usehttps): string {
return implode("\n", $commented) . "\n";
}

/**
* Get the canonical GPL boilerplate with JS line comments.
* @param bool $usehttps
* @return string
*/
public static function generate_for_javascript(bool $usehttps): string {
return self::generate_with_slash_comments($usehttps);
}

/**
* Get the canonical GPL boilerplate with JS line comments.
* @param bool $usehttps
* @return string
*/
public static function generate_for_php(bool $usehttps): string {
return self::generate_with_slash_comments($usehttps);
}

/**
* Get the canonical GPL boilerplate wrapped as a Mustache comment block.
* @param bool $usehttps
Expand Down
154 changes: 154 additions & 0 deletions classes/local/generators/snippets/base.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.

namespace local_devkit\local\generators\snippets;

use local_devkit\local\component;
use local_devkit\local\generators\boilerplate;
use local_devkit\local\utils;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpFile;
use Nette\PhpGenerator\PhpNamespace;
use Nette\PhpGenerator\Printer;

/**
* Class base
*
* @package local_devkit
* @copyright 2026 Felix
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base {
/** @var string */
protected readonly string $filepath;
/** @var string */
protected readonly string $component;
/** @var string */
protected readonly string $copyright;
/** @var string|null */
protected ?string $category = null;

/**
* Constructor.
*/
public function __construct(string $filepath) {
$this->filepath = utils::get_path_relative_to_moodle_root($filepath);
$component = component::resolve_component_from_path($this->filepath);
if ($component === null) {
throw new \InvalidArgumentException("Could not resolve component for path: $this->filepath");
}
$this->component = $component;

$year = date("Y");
$this->copyright = "$year Your name";
}

/**
* Generates the snippet.
*/
abstract public function generate(): string;

/**
* Standard PHP File.
*/
protected function php_file(): PhpFile {
return new PhpFile();
}

/**
* PHP Printer.
*/
protected function php_printer(): Printer {
$printer = new Printer();
$printer->indentation = ' ';
$printer->bracesOnNextLine = false;
$printer->linesBetweenMethods = 1;
return $printer;
}

/**
* Prints a php file, injects license info at top of file.
*/
protected function print_php_file(PhpFile $file): string {
$printer = $this->php_printer();

$phptag = '<?php';
$license = boilerplate::generate_for_php(true);
$content = $printer->printFile($file);
return str_replace($phptag, "$phptag\n$license", $content);
}

/**
* Standard PHP File with class.
* @return array{PhpFile, PhpNamespace, ClassType}
*/
protected function php_file_with_namespaced_class(): array {
$file = $this->php_file();
[$namespacename, $classname] = $this->php_class_info();
$namespace = $file->addNamespace($namespacename);
$class = $namespace->addClass($classname);
$this->php_class_add_docblock_tags($class, $this->category);
return [$file, $namespace, $class];
}

/**
* Adds docblock to class.
*/
protected function php_class_add_docblock_tags(
ClassType $class,
?string $category = null,
): ClassType {
$license = 'https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later';

if (!$class->getComment()) {
$class->addComment("Class {$class->getName()}.");
}

$class->addComment('');
$class->addComment("@package $this->component");

if ($category) {
$class->addComment("@category $category");
}

$class->addComment("@copyright $this->copyright");
$class->addComment("@license $license");
return $class;
}

/**
* Deduces the namespace and classname for a given path.
* @return array{string, string}
*/
protected function php_class_info(): array {
$dirpath = dirname($this->filepath);
$classname = basename($this->filepath, '.php');
$componentpath = utils::get_path_relative_to_moodle_root(
\core_component::get_component_directory($this->component),
);

$classesdir = "$componentpath/classes";
if (str_starts_with($dirpath, $classesdir)) {
$namespace = substr($dirpath, strlen($classesdir) + 1);
} else {
$namespace = '';
}
$namespace = str_replace('/', '\\', $namespace);
$namespace = rtrim("$this->component\\$namespace", '\\');

return [$namespace, $classname];
}
}
32 changes: 32 additions & 0 deletions classes/local/generators/snippets/php_class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.

namespace local_devkit\local\generators\snippets;

/**
* Snippet for a standard PHP class file.
*
* @package local_devkit
* @copyright 2026 Felix
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class php_class extends base {
#[\Override]
public function generate(): string {
[$file] = $this->php_file_with_namespaced_class();
return $this->print_php_file($file);
}
}
71 changes: 71 additions & 0 deletions classes/local/generators/snippets/task.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.

namespace local_devkit\local\generators\snippets;

use Nette\PhpGenerator\ClassManipulator;

use function in_array;

/**
* Class task
*
* @package local_devkit
* @copyright 2026 Felix
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class task extends php_class {
/**
* Constructor.
*/
public function __construct(
string $filepath,
/** @var 'scheduled'|'adhoc' $tasktype */
private readonly string $tasktype = 'scheduled',
) {
if (!in_array($tasktype, ['scheduled', 'adhoc'])) {
throw new \InvalidArgumentException('Invalid task type');
}
parent::__construct($filepath);
}

#[\Override]
public function generate(): string {
$this->category = 'task';
[$file, $namespace, $class] = $this->php_file_with_namespaced_class();

$baseclass = match ($this->tasktype) {
'scheduled' => \core\task\scheduled_task::class,
'adhoc' => \core\task\adhoc_task::class,
};
$namespace->addUse($baseclass);

$class->setExtends($baseclass);
$manipulator = new ClassManipulator($class);

$getname = $manipulator->inheritMethod('get_name');
$getname->removeComment();
$getname->addAttribute('Override');
$getname->addBody('return get_string(?, ?);', ["task:{$class->getName()}", $this->component]);

$execute = $manipulator->inheritMethod('execute');
$execute->removeComment();
$execute->addAttribute('Override');
Comment thread
coderabbitai[bot] marked this conversation as resolved.
$class->setMethods([$getname, $execute]);

return $this->print_php_file($file);
}
}
Loading