From 471cc3f2aa6adcb32dd69778db425a495eec001d Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:20:07 +0100 Subject: [PATCH 01/10] feat: add php file generator --- classes/local/generators/snippets/base.php | 120 +++++++++++++ .../generators/snippets/scheduled_task.php | 49 +++++ composer.json | 3 +- composer.lock | 167 +++++++++++++++++- 4 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 classes/local/generators/snippets/base.php create mode 100644 classes/local/generators/snippets/scheduled_task.php diff --git a/classes/local/generators/snippets/base.php b/classes/local/generators/snippets/base.php new file mode 100644 index 0000000..d02ba92 --- /dev/null +++ b/classes/local/generators/snippets/base.php @@ -0,0 +1,120 @@ +. + +namespace local_devkit\local\generators\snippets; + +use local_devkit\local\component; +use local_devkit\local\utils; +use Nette\PhpGenerator\ClassType; +use Nette\PhpGenerator\PhpFile; + +/** + * 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); + $this->component = component::resolve_component_from_path($this->filepath); + + $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(); + } + + /** + * Standard PHP File with class. + * @return array{PhpFile, ClassType} + */ + protected function php_file_with_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, $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/"; + $namespace = str_replace($classesdir, '', $dirpath); + $namespace = str_replace('/', '\\', $namespace); + $namespace = "$this->component\\$namespace"; + + return [$namespace, $classname]; + } +} diff --git a/classes/local/generators/snippets/scheduled_task.php b/classes/local/generators/snippets/scheduled_task.php new file mode 100644 index 0000000..4b5c9cd --- /dev/null +++ b/classes/local/generators/snippets/scheduled_task.php @@ -0,0 +1,49 @@ +. + +namespace local_devkit\local\generators\snippets; + +use Nette\PhpGenerator\ClassManipulator; + +/** + * Class scheduled_task + * + * @package local_devkit + * @copyright 2026 Felix + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class scheduled_task extends base { + #[\Override] + public function generate(): string { + $this->category = 'task'; + [$file, $class] = self::php_file_with_class(); + + $class->setExtends(\core\task\scheduled_task::class); + $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'); + $class->setMethods([$getname, $execute]); + + return $file; + } +} diff --git a/composer.json b/composer.json index dc5da1c..49117f6 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "mcp/sdk": "^0.6.0", "symfony/process": "^7.4", "symfony/yaml": "^7.4", - "symfony/finder": "^7.4" + "symfony/finder": "^7.4", + "nette/php-generator": "^4.2" }, "require-dev": { "phpstan/phpstan": "^2.1", diff --git a/composer.lock b/composer.lock index dd4e801..8733823 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b2035bacecbe1dbf7448b4f6616ac055", + "content-hash": "1cf1c27472a8f35a45205154ef5b6fcb", "packages": [ { "name": "doctrine/deprecations", @@ -138,6 +138,171 @@ }, "time": "2026-06-02T15:47:04+00:00" }, + { + "name": "nette/php-generator", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "0d7060926f5c3e8c488b9b9ced42d857f12a34b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/0d7060926f5c3e8c488b9b9ced42d857f12a34b5", + "reference": "0d7060926f5c3e8c488b9b9ced42d857f12a34b5", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0.6", + "php": "8.1 - 8.5" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "nikic/php-parser": "^5.0", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.40@stable", + "tracy/tracy": "^2.8" + }, + "suggest": { + "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.5 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "support": { + "issues": "https://github.com/nette/php-generator/issues", + "source": "https://github.com/nette/php-generator/tree/v4.2.2" + }, + "time": "2026-02-26T00:58:33+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.5", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::chr(), ord() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.5" + }, + "time": "2026-07-17T23:02:45+00:00" + }, { "name": "opis/json-schema", "version": "2.6.0", From e851113fc1aae0e0ea7f7f16cef3ff712582a3c8 Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:25:30 +0100 Subject: [PATCH 02/10] feat: alias base class with use keyword --- classes/local/generators/snippets/base.php | 7 ++++--- classes/local/generators/snippets/scheduled_task.php | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/classes/local/generators/snippets/base.php b/classes/local/generators/snippets/base.php index d02ba92..566c64b 100644 --- a/classes/local/generators/snippets/base.php +++ b/classes/local/generators/snippets/base.php @@ -20,6 +20,7 @@ use local_devkit\local\utils; use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\PhpFile; +use Nette\PhpGenerator\PhpNamespace; /** * Class base @@ -63,15 +64,15 @@ protected function php_file(): PhpFile { /** * Standard PHP File with class. - * @return array{PhpFile, ClassType} + * @return array{PhpFile, PhpNamespace, ClassType} */ - protected function php_file_with_class(): array { + 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, $class]; + return [$file, $namespace, $class]; } /** diff --git a/classes/local/generators/snippets/scheduled_task.php b/classes/local/generators/snippets/scheduled_task.php index 4b5c9cd..61fc1e3 100644 --- a/classes/local/generators/snippets/scheduled_task.php +++ b/classes/local/generators/snippets/scheduled_task.php @@ -29,7 +29,8 @@ class scheduled_task extends base { #[\Override] public function generate(): string { $this->category = 'task'; - [$file, $class] = self::php_file_with_class(); + [$file, $namespace, $class] = self::php_file_with_namespaced_class(); + $namespace->addUse(\core\task\scheduled_task::class); $class->setExtends(\core\task\scheduled_task::class); $manipulator = new ClassManipulator($class); From 3189e6231fa184dace1beee2f59cdb545ae6c85c Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:31:47 +0100 Subject: [PATCH 03/10] feat: make task generator generic to support both scheduled and adhoc tasks --- .../snippets/{scheduled_task.php => task.php} | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) rename classes/local/generators/snippets/{scheduled_task.php => task.php} (72%) diff --git a/classes/local/generators/snippets/scheduled_task.php b/classes/local/generators/snippets/task.php similarity index 72% rename from classes/local/generators/snippets/scheduled_task.php rename to classes/local/generators/snippets/task.php index 61fc1e3..524ed06 100644 --- a/classes/local/generators/snippets/scheduled_task.php +++ b/classes/local/generators/snippets/task.php @@ -19,20 +19,37 @@ use Nette\PhpGenerator\ClassManipulator; /** - * Class scheduled_task + * Class task * * @package local_devkit * @copyright 2026 Felix * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class scheduled_task extends base { +class task extends base { + /** + * Constructor. + */ + public function __construct( + string $filepath, + /** @var 'scheduled'|'adhoc' $tasktype */ + private readonly string $tasktype = 'scheduled', + ) { + parent::__construct($filepath); + } + #[\Override] public function generate(): string { $this->category = 'task'; [$file, $namespace, $class] = self::php_file_with_namespaced_class(); - $namespace->addUse(\core\task\scheduled_task::class); - $class->setExtends(\core\task\scheduled_task::class); + $baseclass = match ($this->tasktype) { + 'scheduled' => \core\task\scheduled_task::class, + 'adhoc' => \core\task\adhoc_task::class, + default => throw new \Exception('Invalid task type'), + }; + $namespace->addUse($baseclass); + + $class->setExtends($baseclass); $manipulator = new ClassManipulator($class); $getname = $manipulator->inheritMethod('get_name'); From c3b7bca020d1bc8b5031a2117bb7192bc584a9be Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:31:58 +0100 Subject: [PATCH 04/10] refactor: move argument validation to constructor --- classes/local/generators/snippets/task.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/classes/local/generators/snippets/task.php b/classes/local/generators/snippets/task.php index 524ed06..e8600b1 100644 --- a/classes/local/generators/snippets/task.php +++ b/classes/local/generators/snippets/task.php @@ -18,6 +18,8 @@ use Nette\PhpGenerator\ClassManipulator; +use function in_array; + /** * Class task * @@ -32,8 +34,11 @@ class task extends base { public function __construct( string $filepath, /** @var 'scheduled'|'adhoc' $tasktype */ - private readonly string $tasktype = 'scheduled', + private readonly string $tasktype, ) { + if (!in_array($tasktype, ['scheduled', 'adhoc'])) { + throw new \InvalidArgumentException('Invalid task type'); + } parent::__construct($filepath); } @@ -45,7 +50,6 @@ public function generate(): string { $baseclass = match ($this->tasktype) { 'scheduled' => \core\task\scheduled_task::class, 'adhoc' => \core\task\adhoc_task::class, - default => throw new \Exception('Invalid task type'), }; $namespace->addUse($baseclass); From 1e6016ab2eb60e7aed58eb2f2b3c6bc047604feb Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sun, 19 Jul 2026 01:44:18 +0100 Subject: [PATCH 05/10] feat: add cli command `make` --- classes/local/cli/application.php | 2 + classes/local/cli/commands/make.php | 77 ++++++++++++++++++++++ classes/local/generators/snippets/task.php | 2 +- 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 classes/local/cli/commands/make.php diff --git a/classes/local/cli/application.php b/classes/local/cli/application.php index 53a7157..d3ecdd9 100644 --- a/classes/local/cli/application.php +++ b/classes/local/cli/application.php @@ -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; @@ -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); diff --git a/classes/local/cli/commands/make.php b/classes/local/cli/commands/make.php new file mode 100644 index 0000000..a070ced --- /dev/null +++ b/classes/local/cli/commands/make.php @@ -0,0 +1,77 @@ +. + +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\task; +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, class-string[]} + */ + private function get_generator_and_formatter_classnames() { + return [task::class, [pint::class, phpcbf::class]]; + } +} diff --git a/classes/local/generators/snippets/task.php b/classes/local/generators/snippets/task.php index e8600b1..13fd2b3 100644 --- a/classes/local/generators/snippets/task.php +++ b/classes/local/generators/snippets/task.php @@ -34,7 +34,7 @@ class task extends base { public function __construct( string $filepath, /** @var 'scheduled'|'adhoc' $tasktype */ - private readonly string $tasktype, + private readonly string $tasktype = 'scheduled', ) { if (!in_array($tasktype, ['scheduled', 'adhoc'])) { throw new \InvalidArgumentException('Invalid task type'); From f94dd46ff921bfdb72a7cebb8a5b7fed886c5026 Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:19:21 +0100 Subject: [PATCH 06/10] fix: call method on instance instead of statically --- classes/local/generators/snippets/task.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/local/generators/snippets/task.php b/classes/local/generators/snippets/task.php index 13fd2b3..bf1c200 100644 --- a/classes/local/generators/snippets/task.php +++ b/classes/local/generators/snippets/task.php @@ -45,7 +45,7 @@ public function __construct( #[\Override] public function generate(): string { $this->category = 'task'; - [$file, $namespace, $class] = self::php_file_with_namespaced_class(); + [$file, $namespace, $class] = $this->php_file_with_namespaced_class(); $baseclass = match ($this->tasktype) { 'scheduled' => \core\task\scheduled_task::class, From c060eb56281eadfb0faf1f3ea333664136fcc381 Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:22:30 +0100 Subject: [PATCH 07/10] fix: php_class_info handles files directly in classes/ Use str_starts_with + substr instead of str_replace so files like classes/test.php are handled correctly alongside classes/test/test.php. --- classes/local/generators/snippets/base.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/classes/local/generators/snippets/base.php b/classes/local/generators/snippets/base.php index 566c64b..64d8ef9 100644 --- a/classes/local/generators/snippets/base.php +++ b/classes/local/generators/snippets/base.php @@ -111,10 +111,14 @@ protected function php_class_info(): array { \core_component::get_component_directory($this->component), ); - $classesdir = "$componentpath/classes/"; - $namespace = str_replace($classesdir, '', $dirpath); + $classesdir = "$componentpath/classes"; + if (str_starts_with($dirpath, $classesdir)) { + $namespace = substr($dirpath, strlen($classesdir) + 1); + } else { + $namespace = ''; + } $namespace = str_replace('/', '\\', $namespace); - $namespace = "$this->component\\$namespace"; + $namespace = rtrim("$this->component\\$namespace", '\\'); return [$namespace, $classname]; } From ac37f3062d3f0248253c1e605fa2dc15b9fc2b12 Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:28:46 +0100 Subject: [PATCH 08/10] fix: handle null component resolution in constructor --- classes/local/generators/snippets/base.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/classes/local/generators/snippets/base.php b/classes/local/generators/snippets/base.php index 64d8ef9..f3ae74f 100644 --- a/classes/local/generators/snippets/base.php +++ b/classes/local/generators/snippets/base.php @@ -44,7 +44,11 @@ abstract class base { */ public function __construct(string $filepath) { $this->filepath = utils::get_path_relative_to_moodle_root($filepath); - $this->component = component::resolve_component_from_path($this->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"; From 01ce6110deb04b85b3a70a5bc6b2646ae55e4fb4 Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:48:29 +0100 Subject: [PATCH 09/10] feat: injects license at top of file --- classes/local/generators/boilerplate.php | 22 +++++++++++++++++-- classes/local/generators/snippets/base.php | 25 ++++++++++++++++++++++ classes/local/generators/snippets/task.php | 2 +- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/classes/local/generators/boilerplate.php b/classes/local/generators/boilerplate.php index 4712d04..37b6f6e 100644 --- a/classes/local/generators/boilerplate.php +++ b/classes/local/generators/boilerplate.php @@ -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)); @@ -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 diff --git a/classes/local/generators/snippets/base.php b/classes/local/generators/snippets/base.php index f3ae74f..179a769 100644 --- a/classes/local/generators/snippets/base.php +++ b/classes/local/generators/snippets/base.php @@ -17,10 +17,12 @@ 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 @@ -66,6 +68,29 @@ 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 = 'printFile($file); + return str_replace($phptag, "$phptag\n$license", $content); + } + /** * Standard PHP File with class. * @return array{PhpFile, PhpNamespace, ClassType} diff --git a/classes/local/generators/snippets/task.php b/classes/local/generators/snippets/task.php index bf1c200..fcc9b65 100644 --- a/classes/local/generators/snippets/task.php +++ b/classes/local/generators/snippets/task.php @@ -66,6 +66,6 @@ public function generate(): string { $execute->addAttribute('Override'); $class->setMethods([$getname, $execute]); - return $file; + return $this->print_php_file($file); } } From 02af41606dfa1f1efc530231af974a3bff2994cc Mon Sep 17 00:00:00 2001 From: Felix Yeung <19355619+imfelixyeung@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:53:01 +0100 Subject: [PATCH 10/10] feat: add standard class file generator --- classes/local/cli/commands/make.php | 4 +-- .../local/generators/snippets/php_class.php | 32 +++++++++++++++++++ classes/local/generators/snippets/task.php | 2 +- 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 classes/local/generators/snippets/php_class.php diff --git a/classes/local/cli/commands/make.php b/classes/local/cli/commands/make.php index a070ced..795bee8 100644 --- a/classes/local/cli/commands/make.php +++ b/classes/local/cli/commands/make.php @@ -20,7 +20,7 @@ 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\task; +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; @@ -72,6 +72,6 @@ public function __invoke( * @return array{class-string, class-string[]} */ private function get_generator_and_formatter_classnames() { - return [task::class, [pint::class, phpcbf::class]]; + return [php_class::class, [pint::class, phpcbf::class]]; } } diff --git a/classes/local/generators/snippets/php_class.php b/classes/local/generators/snippets/php_class.php new file mode 100644 index 0000000..20f7466 --- /dev/null +++ b/classes/local/generators/snippets/php_class.php @@ -0,0 +1,32 @@ +. + +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); + } +} diff --git a/classes/local/generators/snippets/task.php b/classes/local/generators/snippets/task.php index fcc9b65..623543b 100644 --- a/classes/local/generators/snippets/task.php +++ b/classes/local/generators/snippets/task.php @@ -27,7 +27,7 @@ * @copyright 2026 Felix * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class task extends base { +class task extends php_class { /** * Constructor. */