diff --git a/.idea/ncc.iml b/.idea/ncc.iml index f554332..63b2d12 100644 --- a/.idea/ncc.iml +++ b/.idea/ncc.iml @@ -5,6 +5,7 @@ + diff --git a/CHANGELOG.md b/CHANGELOG.md index 547e46a..0e1968c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ features and reduced the number of exceptions down to 15 exceptions. - Added a new interface class `ValidatableObjectInterface` to implement validatable objects, this method will throw a `ConfigurationException` if the object is not valid or a `NotSupportedException` if the object contains methods that are not supported by the current version of ncc or project. + - Added a new interface class `TemplateInterface` to implement template classes + - Added new template PhpCliTemplate `phpcli` + - Added new template PhpLibraryTemplate `phplib` ### Fixed - Fixed MITM attack vector in `\ncc\Classes > HttpClient > prepareCurl()` @@ -221,6 +224,7 @@ features and reduced the number of exceptions down to 15 exceptions. - Also updated a bunch of objects in a similar fashion to the ones above, (BuildConfiguration, Execute, ExitHandle, ExitHandler, Repository, Assembly, Build, Dependency, ExecutionPolicy, Installer, Project, UpdateSource) I'm not going to list them all here, but you can find them in the commit history. + - Implemented a template engine and refactored the CLI menu for the Project Manager and added a new `template` command ### Removed diff --git a/src/ncc/CLI/Main.php b/src/ncc/CLI/Main.php index eead148..8d2d4f1 100644 --- a/src/ncc/CLI/Main.php +++ b/src/ncc/CLI/Main.php @@ -190,7 +190,7 @@ */ private static function displayVersion(): void { - Console::out(sprintf('ncc version %s (%s)', ncc_VERSION_NUMBER, ncc_VERSION_BRANCH)); + Console::out(sprintf('ncc version %s (%s)', NCC_VERSION_NUMBER, NCC_VERSION_BRANCH)); } /** diff --git a/src/ncc/CLI/Management/ProjectMenu.php b/src/ncc/CLI/Management/ProjectMenu.php index 31e0982..3b75e2d 100644 --- a/src/ncc/CLI/Management/ProjectMenu.php +++ b/src/ncc/CLI/Management/ProjectMenu.php @@ -23,15 +23,10 @@ namespace ncc\CLI\Management; use Exception; - use ncc\Enums\CompilerExtensionDefaultVersions; - use ncc\Enums\CompilerExtensions; - use ncc\Enums\CompilerExtensionSupportedVersions; - use ncc\Exceptions\ConfigurationException; - use ncc\Exceptions\IOException; - use ncc\Exceptions\PathNotFoundException; + use ncc\Enums\ProjectTemplates; use ncc\Managers\ProjectManager; use ncc\Objects\CliHelpSection; - use ncc\Objects\ProjectConfiguration\Compiler; + use ncc\Objects\ProjectConfiguration; use ncc\Utilities\Console; use ncc\Utilities\Functions; @@ -42,185 +37,136 @@ * * @param $args * @return void - * @throws ConfigurationException - * @throws IOException - * @throws PathNotFoundException */ - public static function start($args): void + public static function start(array $args): void { if(isset($args['create'])) { - self::createProject($args); + self::initializeProject($args); + return; + } + + if(isset($args['template'])) + { + self::applyTemplate($args); + return; } self::displayOptions(); } /** - * Creates a new project + * Initializes a new project * * @param $args * @return void - * @throws ConfigurationException - * @throws IOException - * @throws PathNotFoundException */ - public static function createProject($args): void + private static function initializeProject(array $args): void { - // First determine the source directory of the project - $current_directory = getcwd(); - if(isset($args['src'])) + if(isset($args['path']) || isset($args['p'])) { - // Make sure directory separators are corrected - $args['src'] = str_ireplace('/', DIRECTORY_SEPARATOR, $args['src']); - $args['src'] = str_ireplace('\\', DIRECTORY_SEPARATOR, $args['src']); - - // Remove the trailing slash - if(substr($args['src'], -1) === DIRECTORY_SEPARATOR) - { - $args['src'] = substr($args['src'], 0, -1); - } - - $full_path = getcwd() . DIRECTORY_SEPARATOR . $args['src']; - - if(file_exists($full_path) && is_dir($full_path)) - { - $real_src = getcwd() . DIRECTORY_SEPARATOR . $args['src']; - } - else - { - Console::outError('The selected source directory \'' . $full_path . '\' was not found or is not a directory', true, 1); - return; - } + $project_path = $args['path'] ?? $args['p']; } else { - $real_src = getcwd() . DIRECTORY_SEPARATOR . 'src'; + Console::outError('Missing required option: --path|-p, please specify the path to the project', true, 1); + return; } - // Remove basename from real_src - $real_src = Functions::removeBasename($real_src, $current_directory); - - // Fetch the rest of the information needed for the project - //$compiler_extension = Console::getOptionInput($args, 'ce', 'Compiler Extension (php, java): '); - $package_name = Console::getOptionInput($args, 'package', 'Package Name (com.example.foo): '); - $project_name = Console::getOptionInput($args, 'name', 'Project Name (Foo Bar Library): '); - $Compiler = new Compiler(); - - // Detect the specified compiler extension - if(isset($args['ext']) || isset($args['extension'])) + if(isset($args['name']) || isset($args['n'])) { - $compiler_extension = strtolower(($args['extension'] ?? $args['ext'])); - - if(in_array($compiler_extension, CompilerExtensions::ALL)) - { - $Compiler->setExtension($compiler_extension); - } - else - { - Console::outError('Unsupported extension: ' . $compiler_extension, true, 1); - return; - } + $project_name = $args['name'] ?? $args['n']; } else { - // Default PHP Extension - $Compiler->setExtension(CompilerExtensions::PHP); + Console::outError('Missing required option: --name|-n, please specify the name of the project', true, 1); + return; } - // If a minimum and maximum version is specified - if( - (isset($args['max-version']) || isset($args['max-ver'])) && - (isset($args['min-version']) || isset($args['min-ver'])) - ) + if(isset($args['package']) || isset($args['pkg'])) { - $max_version = strtolower($args['max-version'] ?? $args['max-ver']); - $min_version = strtolower($args['min-version'] ?? $args['min-ver']); - - switch($Compiler->getExtension()) - { - case CompilerExtensions::PHP: - - if(!in_array($max_version, CompilerExtensionSupportedVersions::PHP)) - { - Console::outError('The extension \'' . $Compiler->getExtension() . '\' does not support version ' . $max_version, true, 1); - return; - } - if(!in_array($min_version, CompilerExtensionSupportedVersions::PHP)) - { - Console::outError('The extension \'' . $Compiler->getExtension() . '\' does not support version ' . $min_version, true, 1); - return; - } - - $Compiler->setMaximumVersion($max_version); - $Compiler->setMinimumVersion($min_version); - - break; - - default: - Console::outError('Unsupported extension: ' . $Compiler->getExtension(), true, 1); - return; - } + $package_name = $args['package'] ?? $args['pkg']; } - // If a single version is specified - elseif(isset($args['version']) || isset($args['ver'])) - { - $version = strtolower($args['version'] ?? $args['ver']); - switch($Compiler->getExtension()) - { - case CompilerExtensions::PHP: - if(!in_array($version, CompilerExtensionSupportedVersions::PHP)) - { - Console::outError('The extension \'' . $Compiler->getExtension() . '\' does not support version ' . $version, true, 1); - return; - } - - $Compiler->setMaximumVersion($version); - $Compiler->setMinimumVersion($version); - - break; - - default: - Console::outError('Unsupported extension: ' . $Compiler->getExtension(), true, 1); - return; - } - } - // If no version is specified, use the default version else { - switch($Compiler->getExtension()) - { - case CompilerExtensions::PHP: - $Compiler->setMinimumVersion(CompilerExtensionDefaultVersions::PHP[0]); - $Compiler->setMaximumVersion(CompilerExtensionDefaultVersions::PHP[1]); - break; - - default: - Console::outError('Unsupported extension: ' . $Compiler->getExtension(), true, 1); - return; - } + Console::outError('Missing required option: --package|--pkg, please specify the package name of the project', true, 1); + return; } - // Now create the project - $ProjectManager = new ProjectManager($current_directory); + if(isset($args['ext'])) + { + $compiler_extension = $args['ext']; + } + else + { + Console::outError('Missing required option: --ext, please specify the compiler extension of the project', true, 1); + return; + } try { - $ProjectManager->initializeProject($Compiler, $project_name, $package_name, $real_src); - } - catch (ConfigurationException $e) - { - Console::outException(sprintf('The project configuration is invalid: %s', $e->getMessage()), $e, 1); - return; + $project_manager = ProjectManager::initializeProject($project_path, $project_name, $package_name, $compiler_extension); } catch(Exception $e) { - Console::outException('There was an unexpected error while trying to initialize the project', $e, 1); + Console::outException('There was an error while trying to initialize the project', $e, 1); return; } - Console::out('Project successfully created'); - exit(0); + Console::out(sprintf('Project successfully created in \'%s\'', $project_manager->getProjectPath())); + Console::out(sprintf('Modify the project configuration in \'%s\'', $project_manager->getProjectPath() . DIRECTORY_SEPARATOR . 'project.json')); + } + + private static function applyTemplate(array $args): void + { + if(isset($args['path']) || isset($args['p'])) + { + $project_path = $args['path'] ?? $args['p']; + } + else + { + if(is_file(getcwd() . DIRECTORY_SEPARATOR . 'project.json')) + { + $project_path = getcwd(); + } + else + { + Console::outError('Missing option: --path|-p, please specify the path to the project', true, 1); + return; + } + } + + if(isset($args['name']) || isset($args['n'])) + { + $template_name = $args['name'] ?? $args['n']; + } + else + { + Console::outError('Missing required option: --name|-n, please specify the name of the template', true, 1); + return; + } + + try + { + $project_manager = new ProjectManager($project_path); + } + catch(Exception $e) + { + Console::outException('There was an error while trying to load the project', $e, 1); + return; + } + + try + { + $project_manager->applyTemplate($template_name); + } + catch(Exception $e) + { + Console::outException('There was an error while trying to apply the template', $e, 1); + return; + } + + Console::out(sprintf('Template successfully applied to project in \'%s\'', $project_manager->getProjectPath())); } /** @@ -232,19 +178,23 @@ { $options = [ new CliHelpSection(['help'], 'Displays this help menu about the value command'), - new CliHelpSection(['create', '--src', '--package', '--name'], 'Creates a new NCC project'), - new CliHelpSection(['create', '--ext'], 'Specifies the compiler extension'), - new CliHelpSection(['create', '--min-version', '--min-ver', '--maximum-ver', '-max-ver'], 'Specifies the compiler extension version'), - new CliHelpSection(['create-makefile'], 'Generates a Makefile for the project'), + new CliHelpSection(['create', '--path|-p', '--name|-n', '--package|--pkg', '--ext'], 'Creates a new ncc project'), + new CliHelpSection(['template', '--path|-p', '--name|-n'], 'Applies a template to the project'), ]; $options_padding = Functions::detectParametersPadding($options) + 4; Console::out('Usage: ncc project {command} [options]'); - Console::out('Options:' . PHP_EOL); + Console::out('Options:'); foreach($options as $option) { Console::out(' ' . $option->toString($options_padding)); } + + Console::out(PHP_EOL . 'Available Templates:'); + foreach(ProjectTemplates::ALL as $template) + { + Console::out(' ' . $template); + } } } \ No newline at end of file diff --git a/src/ncc/Classes/NccExtension/ConstantCompiler.php b/src/ncc/Classes/NccExtension/ConstantCompiler.php index 42576ab..6307c2f 100644 --- a/src/ncc/Classes/NccExtension/ConstantCompiler.php +++ b/src/ncc/Classes/NccExtension/ConstantCompiler.php @@ -1,37 +1,56 @@ getAssembly()); + $input = self::compileBuildConstants($input); + $input = self::compileDateTimeConstants($input, time()); + $input = self::compileRuntimeConstants($input); + + return $input; + } + /** * Compiles assembly constants about the project (Usually used during compiling time) * @@ -46,15 +65,19 @@ namespace ncc\Classes\NccExtension; return null; } - if($assembly->getName() !== null) - { - $input = str_replace(AssemblyConstants::ASSEMBLY_NAME, $assembly->getName(), $input); - } - - if($assembly->getPackage() !== null) - { - $input = str_replace(AssemblyConstants::ASSEMBLY_PACKAGE, $assembly->getPackage(), $input); - } + $input = str_replace( + [ + AssemblyConstants::ASSEMBLY_NAME, + AssemblyConstants::ASSEMBLY_PACKAGE, + AssemblyConstants::ASSEMBLY_VERSION, + AssemblyConstants::ASSEMBLY_UID + ], + [ + $assembly->getName(), + $assembly->getPackage(), + $assembly->getVersion(), + $assembly->getUuid() + ], $input); if($assembly->getDescription() !== null) { @@ -80,17 +103,6 @@ namespace ncc\Classes\NccExtension; { $input =str_replace(AssemblyConstants::ASSEMBLY_TRADEMARK, $assembly->getTrademark(), $input); } - - if($assembly->getVersion() !== null) - { - $input = str_replace(AssemblyConstants::ASSEMBLY_VERSION, $assembly->getVersion(), $input); - } - - if($assembly->getUuid() !== null) - { - $input = str_replace(AssemblyConstants::ASSEMBLY_UID, $assembly->getUuid(), $input); - } - return $input; } @@ -126,10 +138,10 @@ namespace ncc\Classes\NccExtension; * Compiles installation constants (Usually used during compiling time) * * @param string|null $input - * @param InstallationPaths $installationPaths + * @param InstallationPaths $installation_paths * @return string|null */ - public static function compileInstallConstants(?string $input, InstallationPaths $installationPaths): ?string + public static function compileInstallConstants(?string $input, InstallationPaths $installation_paths): ?string { if($input === null) { @@ -144,10 +156,10 @@ namespace ncc\Classes\NccExtension; InstallConstants::INSTALL_PATH_DATA ], [ - $installationPaths->getInstallationpath(), - $installationPaths->getBinPath(), - $installationPaths->getSourcePath(), - $installationPaths->getDataPath() + $installation_paths->getInstallationpath(), + $installation_paths->getBinPath(), + $installation_paths->getSourcePath(), + $installation_paths->getDataPath() ], $input); } diff --git a/src/ncc/Classes/PhpExtension/PhpCliTemplate.php b/src/ncc/Classes/PhpExtension/PhpCliTemplate.php new file mode 100644 index 0000000..8a5eeae --- /dev/null +++ b/src/ncc/Classes/PhpExtension/PhpCliTemplate.php @@ -0,0 +1,114 @@ +getProjectConfiguration()->addExecutionPolicy( + new ExecutionPolicy('main_policy', Runners::PHP, new ExecutionPolicy\Execute('main')) + ); + + $project_manager->getProjectConfiguration()->getBuild()->setMain('main_policy'); + $project_manager->getProjectConfiguration()->getProject()->addOption(ProjectOptions::CREATE_SYMLINK, true); + + self::writeProgramTemplate($project_manager); + self::writeMainEntryTemplate($project_manager); + self::writeMakefileTemplate($project_manager); + + $project_manager->save(); + } + + /** + * Writes the Program.php file to the project source directory + * + * @param ProjectManager $project_manager + * @return void + * @throws IOException + * @throws PathNotFoundException + */ + private static function writeProgramTemplate(ProjectManager $project_manager): void + { + IO::fwrite( + $project_manager->getProjectSourcePath() . DIRECTORY_SEPARATOR . 'Program.php', + ConstantCompiler::compileConstants($project_manager->getProjectConfiguration(), + IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'TemplateFiles' . DIRECTORY_SEPARATOR . 'Program.php.tpl') + ) + ); + } + + /** + * Writes the main.php file to the project directory + * + * @param ProjectManager $project_manager + * @return void + * @throws IOException + * @throws PathNotFoundException + */ + private static function writeMainEntryTemplate(ProjectManager $project_manager): void + { + IO::fwrite( + $project_manager->getProjectPath() . DIRECTORY_SEPARATOR . 'main', + ConstantCompiler::compileConstants($project_manager->getProjectConfiguration(), + IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'TemplateFiles' . DIRECTORY_SEPARATOR . 'main.php.tpl') + ) + ); + } + + /** + * Writes the Makefile to the project directory + * + * @param ProjectManager $project_manager + * @return void + * @throws IOException + * @throws PathNotFoundException + */ + private static function writeMakefileTemplate(ProjectManager $project_manager): void + { + IO::fwrite( + $project_manager->getProjectPath() . DIRECTORY_SEPARATOR . 'Makefile', + ConstantCompiler::compileConstants($project_manager->getProjectConfiguration(), + IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'TemplateFiles' . DIRECTORY_SEPARATOR . 'Makefile.tpl') + ) + ); + } + } \ No newline at end of file diff --git a/src/ncc/Classes/PhpExtension/PhpLibraryTemplate.php b/src/ncc/Classes/PhpExtension/PhpLibraryTemplate.php new file mode 100644 index 0000000..51bb968 --- /dev/null +++ b/src/ncc/Classes/PhpExtension/PhpLibraryTemplate.php @@ -0,0 +1,76 @@ +getProjectSourcePath() . DIRECTORY_SEPARATOR . $project_manager->getProjectConfiguration()->getAssembly()->getName() . '.php', + ConstantCompiler::compileConstants($project_manager->getProjectConfiguration(), + IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'TemplateFiles' . DIRECTORY_SEPARATOR . 'class.php.tpl') + ) + ); + } + + /** + * Writes the Makefile to the project directory + * + * @param ProjectManager $project_manager + * @return void + * @throws IOException + * @throws PathNotFoundException + */ + private static function writeMakefileTemplate(ProjectManager $project_manager): void + { + IO::fwrite( + $project_manager->getProjectPath() . DIRECTORY_SEPARATOR . 'Makefile', + ConstantCompiler::compileConstants($project_manager->getProjectConfiguration(), + IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'TemplateFiles' . DIRECTORY_SEPARATOR . 'Makefile.tpl') + ) + ); + } + } \ No newline at end of file diff --git a/src/ncc/Classes/PhpExtension/TemplateFiles/Makefile.tpl b/src/ncc/Classes/PhpExtension/TemplateFiles/Makefile.tpl new file mode 100644 index 0000000..404177e --- /dev/null +++ b/src/ncc/Classes/PhpExtension/TemplateFiles/Makefile.tpl @@ -0,0 +1,20 @@ +# Variables +CONFIG ?= release +LOG_LEVEL = debug +OUTDIR = build/$(CONFIG) +PACKAGE = $(OUTDIR)/%ASSEMBLY.PACKAGE%.ncc + +# Default Target +all: build + +# Build Steps +build: + ncc build --config=$(CONFIG) --log-level $(LOG_LEVEL) + +install: + ncc package install --package=$(PACKAGE) --skip-dependencies --reinstall -y --log-level $(LOG_LEVEL) + +clean: + rm -rf build + +.PHONY: all build install clean \ No newline at end of file diff --git a/src/ncc/Classes/PhpExtension/TemplateFiles/Program.php.tpl b/src/ncc/Classes/PhpExtension/TemplateFiles/Program.php.tpl new file mode 100644 index 0000000..6538024 --- /dev/null +++ b/src/ncc/Classes/PhpExtension/TemplateFiles/Program.php.tpl @@ -0,0 +1,17 @@ +handleExit($package, $version, $unit->getExecutionPolicy()->getExitHandlers()->getSuccess(), $process); - $this->handleExit($package, $version, $unit->getExecutionPolicy()->getExitHandlers()->getWarning(), $process); - $this->handleExit($package, $version, $unit->executigetExecutionPolicy()on_policy->getExitHandlers()->getError(), $process); + $this->handleExit($package, $version, $unit->getExecutionPolicy()->getExitHandlers()?->getSuccess(), $process); + $this->handleExit($package, $version, $unit->getExecutionPolicy()->getExitHandlers()?->getWarning(), $process); + $this->handleExit($package, $version, $unit->getExecutionPolicy()->getExitHandlers()?->getError(), $process); } } diff --git a/src/ncc/Managers/ProjectManager.php b/src/ncc/Managers/ProjectManager.php index 2b425ec..d229177 100644 --- a/src/ncc/Managers/ProjectManager.php +++ b/src/ncc/Managers/ProjectManager.php @@ -25,9 +25,13 @@ namespace ncc\Managers; + use JetBrains\PhpStorm\NoReturn; + use ncc\Classes\PhpExtension\PhpCliTemplate; + use ncc\Classes\PhpExtension\PhpLibraryTemplate; use ncc\Enums\Options\BuildConfigurationValues; use ncc\Enums\Options\InitializeProjectOptions; use ncc\Classes\NccExtension\PackageCompiler; + use ncc\Enums\ProjectTemplates; use ncc\Exceptions\BuildException; use ncc\Exceptions\ConfigurationException; use ncc\Exceptions\IOException; @@ -35,9 +39,8 @@ use ncc\Exceptions\PathNotFoundException; use ncc\Objects\ProjectConfiguration; use ncc\Objects\ProjectConfiguration\Compiler; - use ncc\ThirdParty\Symfony\Uid\Uuid; + use ncc\Utilities\Console; use ncc\Utilities\Validate; - use RuntimeException; class ProjectManager { @@ -58,7 +61,7 @@ /** * The loaded project configuration, null if no project file is loaded * - * @var ProjectConfiguration|null + * @var ProjectConfiguration */ private $project_configuration; @@ -69,13 +72,14 @@ * @throws ConfigurationException * @throws IOException * @throws PathNotFoundException + * @throws NotSupportedException */ public function __construct(string $path) { // Auto-resolve the trailing slash - if(!str_ends_with($path, '/')) + if(str_ends_with($path, '/')) { - $path .= DIRECTORY_SEPARATOR; + $path = substr($path, 0, -1); } // Detect if the folder exists or not @@ -85,139 +89,7 @@ } $this->project_path = $path; - $this->project_file_path = $path . 'project.json'; - - if(file_exists($this->project_file_path)) - { - $this->load(); - } - } - - /** - * Initializes the project structure - * - * @param Compiler $compiler - * @param string $name - * @param string $package - * @param string|null $src - * @param array $options - * @throws ConfigurationException - * @throws IOException - */ - public function initializeProject(Compiler $compiler, string $name, string $package, ?string $src=null, array $options=[]): void - { - // Validate the project information first - if(!Validate::packageName($package)) - { - throw new ConfigurationException('The given package name \'' . $package . '\' is not a valid package name'); - } - - if(!Validate::projectName($name)) - { - throw new ConfigurationException('The given project name \'' . $name . '\' is not valid'); - } - - if(file_exists($this->project_path . DIRECTORY_SEPARATOR . 'project.json')) - { - throw new IOException('A project has already been initialized in \'' . $this->project_path . DIRECTORY_SEPARATOR . 'project.json' . '\''); - } - - $this->project_configuration = new ProjectConfiguration(); - - // Set the compiler information - $this->project_configuration->getProject()->setCompiler($compiler); - - // Set the assembly information - $this->project_configuration->getAssembly()->setName($name); - $this->project_configuration->getAssembly()->setPackage($package); - $this->project_configuration->getAssembly()->setVersion('1.0.0'); - $this->project_configuration->getAssembly()->setUuid(Uuid::v1()->toRfc4122()); - - // Set the build information - $this->project_configuration->getBuild()->setSourcePath($src); - - if($this->project_configuration->getBuild()->getSourcePath() === null) - { - $this->project_configuration->getBuild()->setSourcePath($this->project_path); - } - - $this->project_configuration->getBuild()->setDefaultConfiguration('debug'); - - // Assembly constants if the program wishes to check for this - $this->project_configuration->getBuild()->addDefineConstant('ASSEMBLY_PACKAGE', '%ASSEMBLY.PACKAGE%'); - $this->project_configuration->getBuild()->addDefineConstant('ASSEMBLY_VERSION', '%ASSEMBLY.VERSION%'); - $this->project_configuration->getBuild()->addDefineConstant('ASSEMBLY_UID', '%ASSEMBLY.UID%'); - - // Generate configurations - $debug_configuration = new ProjectConfiguration\Build\BuildConfiguration(); - $debug_configuration->setName('debug'); - $debug_configuration->setOutputPath('build/debug'); - $debug_configuration->setDefinedConstant('DEBUG', '1'); // Debugging constant if the program wishes to check for this - $this->project_configuration->getBuild()->addBuildConfiguration($debug_configuration); - - $release_configuration = new ProjectConfiguration\Build\BuildConfiguration(); - $release_configuration->setName('release'); - $release_configuration->setOutputPath('build/release'); - $release_configuration->setDefinedConstant('DEBUG', '0'); // Debugging constant if the program wishes to check for this - $this->project_configuration->getBuild()->addBuildConfiguration($release_configuration); - - // Finally, create project.json - $this->project_configuration->toFile($this->project_path . DIRECTORY_SEPARATOR . 'project.json'); - - // And create the project directory for additional assets/resources - $Folders = [ - $this->project_path . DIRECTORY_SEPARATOR . 'ncc', - $this->project_path . DIRECTORY_SEPARATOR . 'ncc' . DIRECTORY_SEPARATOR . 'cache', - $this->project_path . DIRECTORY_SEPARATOR . 'ncc' . DIRECTORY_SEPARATOR . 'config', - ]; - - foreach($Folders as $folder) - { - if(!file_exists($folder) && !mkdir($folder) && !is_dir($folder)) - { - throw new RuntimeException(sprintf('Directory "%s" was not created', $folder)); - } - } - - // Process options - foreach($options as $option) - { - if ( - $option === InitializeProjectOptions::CREATE_SOURCE_DIRECTORY && - !file_exists($this->project_configuration->getBuild()->getSourcePath()) && - !mkdir($concurrentDirectory = $this->project_configuration->getBuild()->getSourcePath()) && - !is_dir($concurrentDirectory) - ) - { - throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); - } - } - } - - /** - * Determines if a project configuration is loaded or not - * - * @return bool - */ - public function projectLoaded(): bool - { - return $this->project_configuration !== null; - } - - /** - * Attempts to load the project configuration - * - * @return void - * @throws IOException - * @throws PathNotFoundException - */ - public function load(): void - { - if(!is_file($this->project_file_path)) - { - throw new PathNotFoundException($this->project_file_path); - } - + $this->project_file_path = $this->project_path . DIRECTORY_SEPARATOR . 'project.json'; $this->project_configuration = ProjectConfiguration::fromFile($this->project_file_path); } @@ -229,11 +101,6 @@ */ public function save(): void { - if(!$this->projectLoaded()) - { - return; - } - $this->project_configuration->toFile($this->project_file_path); } @@ -241,17 +108,9 @@ * Returns the ProjectConfiguration object * * @return ProjectConfiguration - * @throws ConfigurationException - * @throws IOException - * @throws PathNotFoundException */ public function getProjectConfiguration(): ProjectConfiguration { - if($this->project_configuration === null) - { - $this->load(); - } - return $this->project_configuration; } @@ -265,6 +124,15 @@ return $this->project_path; } + /** + * Returns the project's source path + * + * @return string|null + */ + public function getProjectSourcePath(): ?string + { + return $this->project_path . DIRECTORY_SEPARATOR . $this->project_configuration->getBuild()->getSourcePath(); + } /** * Compiles the project into a package @@ -281,4 +149,103 @@ { return PackageCompiler::compile($this, $build_configuration); } + + /** + * Applies the given template to the project + * + * @param string $template_name + * @return void + * @throws ConfigurationException + * @throws IOException + * @throws NotSupportedException + * @throws PathNotFoundException + */ + public function applyTemplate(string $template_name): void + { + switch(strtolower($template_name)) + { + case ProjectTemplates::PHP_CLI: + PhpCliTemplate::applyTemplate($this); + break; + + case ProjectTemplates::PHP_LIBRARY: + PhpLibraryTemplate::applyTemplate($this); + break; + + default: + throw new NotSupportedException('The given template \'' . $template_name . '\' is not supported'); + } + } + + /** + * Initializes the project structure + * + * @param string $project_path The directory for the project to be initialized in + * @param string $name The name of the project eg; ProjectLib + * @param string $package The standard package name eg; com.example.project + * @param Compiler $compiler The compiler to use for this project + * @param array $options An array of options to use when initializing the project + * @return ProjectManager + * @throws ConfigurationException + * @throws IOException + * @throws NotSupportedException + * @throws PathNotFoundException + */ + public static function initializeProject(string $project_path, string $name, string $package, string $compiler, array $options=[]): ProjectManager + { + if(str_ends_with($project_path, DIRECTORY_SEPARATOR)) + { + $project_path = substr($project_path, 0, -1); + } + + if(is_file($project_path . DIRECTORY_SEPARATOR . 'project.json')) + { + if(!isset($options[InitializeProjectOptions::OVERWRITE_PROJECT_FILE])) + { + throw new IOException('A project has already been initialized in \'' . $project_path . DIRECTORY_SEPARATOR . 'project.json' . '\''); + } + + Console::out(sprintf('Overwriting project.json in \'%s\'', $project_path)); + unlink($project_path . DIRECTORY_SEPARATOR . 'project.json'); + } + + $project_src = $options[InitializeProjectOptions::PROJECT_SRC_PATH] ?? ('src' . DIRECTORY_SEPARATOR . $name); + if(str_ends_with($project_src, DIRECTORY_SEPARATOR)) + { + $project_src = substr($project_src, 0, -1); + } + + if(!mkdir($project_path, 0777, true) && !is_dir($project_path)) + { + throw new IOException(sprintf('Project directory "%s" was not created', $project_path)); + } + + if(!mkdir($project_path . DIRECTORY_SEPARATOR . $project_src, 0777, true) && !is_dir($project_path . DIRECTORY_SEPARATOR . $project_src)) + { + throw new IOException(sprintf('Project source directory "%s" was not created', $project_path . DIRECTORY_SEPARATOR . $project_src)); + } + + // Create the build configuration + $build = new ProjectConfiguration\Build($project_src); + $build->addDefineConstant('ASSEMBLY_PACKAGE', '%ASSEMBLY.PACKAGE%'); + $build->addDefineConstant('ASSEMBLY_VERSION', '%ASSEMBLY.VERSION%'); + $build->addDefineConstant('ASSEMBLY_UID', '%ASSEMBLY.UID%'); + + // Generate the Debug & Release build configurations + $debug_configuration = new ProjectConfiguration\Build\BuildConfiguration('debug', 'build' . DIRECTORY_SEPARATOR . 'debug'); + $debug_configuration->setDefinedConstant('DEBUG', '1'); + $build->addBuildConfiguration(new ProjectConfiguration\Build\BuildConfiguration('release', 'build' . DIRECTORY_SEPARATOR . 'release')); + $build->addBuildConfiguration($debug_configuration); + $build->setDefaultConfiguration('release'); + + $project_configuration = new ProjectConfiguration( + new ProjectConfiguration\Project($compiler), + new ProjectConfiguration\Assembly($name, $package), + $build + ); + + // Finally, create project.json and return a new ProjectManager + $project_configuration->toFile($project_path . DIRECTORY_SEPARATOR . 'project.json'); + return new ProjectManager($project_path); + } } \ No newline at end of file diff --git a/src/ncc/Objects/ProjectConfiguration.php b/src/ncc/Objects/ProjectConfiguration.php index 8944a2b..10299ae 100644 --- a/src/ncc/Objects/ProjectConfiguration.php +++ b/src/ncc/Objects/ProjectConfiguration.php @@ -140,77 +140,6 @@ $this->execution_policies = $execution_policies; } - /** - * @return Installer|null - */ - public function getInstaller(): ?Installer - { - return $this->installer; - } - - /** - * @param Installer|null $installer - */ - public function setInstaller(?Installer $installer): void - { - $this->installer = $installer; - } - - /** - * @return Build - */ - public function getBuild(): Build - { - return $this->build; - } - - /** - * @param Build $build - */ - public function setBuild(Build $build): void - { - $this->build = $build; - } - - /** - * @inheritDoc - */ - public function validate(): void - { - $this->project->validate(); - $this->assembly->validate(); - $this->build->validate(); - - if($this->build->getMain() !== null) - { - if($this->execution_policies === null || count($this->execution_policies) === 0) - { - throw new ConfigurationException(sprintf('Build configuration build.main uses an execution policy "%s" but no policies are defined', $this->build->getMain())); - } - - - $found = false; - foreach($this->execution_policies as $policy) - { - if($policy->getName() === $this->build->getMain()) - { - $found = true; - break; - } - } - - if(!$found) - { - throw new ConfigurationException(sprintf('Build configuration build.main points to a undefined execution policy "%s"', $this->build->getMain())); - } - - if($this->build->getMain() === BuildConfigurationValues::ALL) - { - throw new ConfigurationException(sprintf('Build configuration build.main cannot be set to "%s"', BuildConfigurationValues::ALL)); - } - } - } - /** * @param string $name * @return ExecutionPolicy|null @@ -228,6 +157,44 @@ return null; } + /** + * @param ExecutionPolicy $policy + * @param bool $overwrite + * @return void + * @throws ConfigurationException + */ + public function addExecutionPolicy(ExecutionPolicy $policy, bool $overwrite=true): void + { + if(!$overwrite) + { + foreach($this->execution_policies as $executionPolicy) + { + if($executionPolicy->getName() === $policy->getName()) + { + throw new ConfigurationException('An execution policy with the name \'' . $policy->getName() . '\' already exists'); + } + } + } + + $this->execution_policies[] = $policy; + } + + /** + * @param string $name + * @return void + */ + public function removeExecutionPolicy(string $name): void + { + foreach($this->execution_policies as $key => $executionPolicy) + { + if($executionPolicy->getName() === $name) + { + unset($this->execution_policies[$key]); + return; + } + } + } + /** * Runs a check on the project configuration and determines what policies are required * @@ -387,6 +354,77 @@ return $required_policies; } + /** + * @return Installer|null + */ + public function getInstaller(): ?Installer + { + return $this->installer; + } + + /** + * @param Installer|null $installer + */ + public function setInstaller(?Installer $installer): void + { + $this->installer = $installer; + } + + /** + * @return Build + */ + public function getBuild(): Build + { + return $this->build; + } + + /** + * @param Build $build + */ + public function setBuild(Build $build): void + { + $this->build = $build; + } + + /** + * @inheritDoc + */ + public function validate(): void + { + $this->project->validate(); + $this->assembly->validate(); + $this->build->validate(); + + if($this->build->getMain() !== null) + { + if($this->execution_policies === null || count($this->execution_policies) === 0) + { + throw new ConfigurationException(sprintf('Build configuration build.main uses an execution policy "%s" but no policies are defined', $this->build->getMain())); + } + + + $found = false; + foreach($this->execution_policies as $policy) + { + if($policy->getName() === $this->build->getMain()) + { + $found = true; + break; + } + } + + if(!$found) + { + throw new ConfigurationException(sprintf('Build configuration build.main points to a undefined execution policy "%s"', $this->build->getMain())); + } + + if($this->build->getMain() === BuildConfigurationValues::ALL) + { + throw new ConfigurationException(sprintf('Build configuration build.main cannot be set to "%s"', BuildConfigurationValues::ALL)); + } + } + } + /** * Writes a json representation of the object to a file * @@ -493,7 +531,7 @@ foreach($this->execution_policies as $executionPolicy) { - $execution_policies[$executionPolicy->getName()] = $executionPolicy->toArray($bytecode); + $execution_policies[] = $executionPolicy->toArray($bytecode); } $results[($bytecode ? Functions::cbc('execution_policies') : 'execution_policies')] = $execution_policies; diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php index bb0f4aa..17de121 100644 --- a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php +++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php @@ -24,6 +24,7 @@ namespace ncc\Objects\ProjectConfiguration; + use ncc\Exceptions\ConfigurationException; use ncc\Interfaces\BytecodeObjectInterface; use ncc\Objects\ProjectConfiguration\ExecutionPolicy\Execute; use ncc\Objects\ProjectConfiguration\ExecutionPolicy\ExitHandlers; @@ -66,6 +67,20 @@ */ private $message; + /** + * ExecutionPolicy constructor. + * + * @param string $name + * @param string $runner + * @param Execute $execute + */ + public function __construct(string $name, string $runner, Execute $execute) + { + $this->name = $name; + $this->runner = $runner; + $this->execute = $execute; + } + /** * @return string */ @@ -192,22 +207,34 @@ /** * @inheritDoc + * @throws ConfigurationException */ public static function fromArray(array $data): ExecutionPolicy { - $object = new self(); + $name = Functions::array_bc($data, 'name'); + $runner = Functions::array_bc($data, 'runner'); + $execute = Functions::array_bc($data, 'execute'); - $object->name = Functions::array_bc($data, 'name'); - $object->runner = Functions::array_bc($data, 'runner'); - $object->message = Functions::array_bc($data, 'message'); - $object->execute = Functions::array_bc($data, 'execute'); - $object->exit_handlers = Functions::array_bc($data, 'exit_handlers'); - - if($object->execute !== null) + if($name === null || $name === '') { - $object->execute = Execute::fromArray($object->execute); + throw new ConfigurationException('ExecutionPolicy name cannot be null or empty'); } + if($runner === null || $runner === '') + { + throw new ConfigurationException('ExecutionPolicy runner cannot be null or empty'); + } + + if($execute === null) + { + throw new ConfigurationException('ExecutionPolicy execute cannot be null'); + } + + $object = new self($name, $runner, Execute::fromArray($execute)); + + $object->message = Functions::array_bc($data, 'message'); + $object->exit_handlers = Functions::array_bc($data, 'exit_handlers'); + if($object->exit_handlers !== null) { $object->exit_handlers = ExitHandlers::fromArray($object->exit_handlers); diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php index 9349c6f..6996b20 100644 --- a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php +++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php @@ -88,9 +88,12 @@ private $idle_timeout; /** - * Public Constructor + * Execute constructor. + * + * @param string $target + * @param string|null $working_directory */ - public function __construct(string $target, ?string $working_directory) + public function __construct(string $target, ?string $working_directory=null) { $this->target = $target; $this->working_directory = $working_directory ?? RuntimeConstants::CWD; diff --git a/src/ncc/Objects/ProjectConfiguration/Project.php b/src/ncc/Objects/ProjectConfiguration/Project.php index 159a009..40d91d3 100644 --- a/src/ncc/Objects/ProjectConfiguration/Project.php +++ b/src/ncc/Objects/ProjectConfiguration/Project.php @@ -52,9 +52,16 @@ /** * Public Constructor + * @param string|Compiler $compiler + * @throws NotSupportedException */ - public function __construct(Compiler $compiler) + public function __construct(string|Compiler $compiler) { + if(is_string($compiler)) + { + $compiler = new Compiler($compiler); + } + $this->compiler = $compiler; $this->options = []; } diff --git a/tests/autoload.php b/tests/autoload.php index 43e5e67..7708ba0 100644 --- a/tests/autoload.php +++ b/tests/autoload.php @@ -20,7 +20,7 @@ * */ - $build_directory = __DIR__ . DIRECTORY_SEPARATOR . 'ncc' . DIRECTORY_SEPARATOR . 'build'; + $build_directory = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'build'; $autoload_path = $build_directory . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'autoload.php'; if(!is_dir($build_directory)) diff --git a/tests/projects/php_cli/project.json b/tests/projects/php_cli/project.json deleted file mode 100644 index 571b4c8..0000000 --- a/tests/projects/php_cli/project.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "project": { - "compiler": { - "extension": "php", - "minimum_version": "8.0" - } - } -} \ No newline at end of file