- Fixed issue when registering ncc's extension, when using the INSTALLER, the installation path used in the process
appears to be incorrect, added a optional parameter to the `registerExtension` method to allow the installer to pass the correct installation path. - Implemented support in the AST traversal for the PHP statements `include`, `include_once`, `require`, and `require_once`. These statements are transformed into function calls. With this change, ncc can correctly handle and import files from system packages or direct binary package files.
This commit is contained in:
parent
c736a896fb
commit
173032df72
12 changed files with 557 additions and 74 deletions
|
@ -7,9 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [2.0.3] - Unreleased
|
## [2.0.3] - Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Implemented support in the AST traversal for the PHP statements `include`, `include_once`, `require`, and
|
||||||
|
`require_once`. These statements are transformed into function calls. With this change, ncc can correctly handle and
|
||||||
|
import files from system packages or direct binary package files.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- When finding package versions in the package lock, ncc will try to find a satisfying version rather than the exact
|
- When finding package versions in the package lock, ncc will try to find a satisfying version rather than the exact
|
||||||
version, this is to prevent errors when the package lock contains a version that is not available in the repository.
|
version, this is to prevent errors when the package lock contains a version that is not available in the repository.
|
||||||
|
- Fixed issue when registering ncc's extension, when using the INSTALLER, the installation path used in the process
|
||||||
|
appears to be incorrect, added a optional parameter to the `registerExtension` method to allow the installer to pass
|
||||||
|
the correct installation path.
|
||||||
|
|
||||||
|
|
||||||
## [2.0.2] - 2023-10-13
|
## [2.0.2] - 2023-10-13
|
||||||
|
|
|
@ -242,23 +242,6 @@
|
||||||
|
|
||||||
$NCC_FILESYSTEM->mkdir($NCC_INSTALL_PATH, 0755);
|
$NCC_FILESYSTEM->mkdir($NCC_INSTALL_PATH, 0755);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(is_file(__DIR__ . DIRECTORY_SEPARATOR . 'default_repositories.json'))
|
|
||||||
{
|
|
||||||
Functions::initializeFiles(Functions::loadJsonFile(__DIR__ . DIRECTORY_SEPARATOR . 'default_repositories.json', Functions::FORCE_ARRAY));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Functions::initializeFiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Exception $e)
|
|
||||||
{
|
|
||||||
Console::outException('Cannot initialize NCC files, ' . $e->getMessage(), $e, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy files to the installation path
|
// Copy files to the installation path
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -302,6 +285,24 @@
|
||||||
Console::inlineProgressBar($processed_items, $total_items);
|
Console::inlineProgressBar($processed_items, $total_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize ncc's files
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(is_file(__DIR__ . DIRECTORY_SEPARATOR . 'default_repositories.json'))
|
||||||
|
{
|
||||||
|
Functions::initializeFiles($NCC_INSTALL_PATH, Functions::loadJsonFile(__DIR__ . DIRECTORY_SEPARATOR . 'default_repositories.json', Functions::FORCE_ARRAY));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Functions::initializeFiles($NCC_INSTALL_PATH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception $e)
|
||||||
|
{
|
||||||
|
Console::outException('Cannot initialize NCC files, ' . $e->getMessage(), $e, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Generate executable shortcut
|
// Generate executable shortcut
|
||||||
try
|
try
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Functions::initializeFiles($default_repositories);
|
Functions::initializeFiles(null, $default_repositories);
|
||||||
}
|
}
|
||||||
catch(Exception $e)
|
catch(Exception $e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -639,6 +639,25 @@
|
||||||
return Resource::fromArray(ZiProto::decode($this->getByPointer($pointer, $length)));
|
return Resource::fromArray(ZiProto::decode($this->getByPointer($pointer, $length)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the package's directory for a file that matches the given filename
|
||||||
|
*
|
||||||
|
* @param string $filename
|
||||||
|
* @return string|false
|
||||||
|
*/
|
||||||
|
public function find(string $filename): string|false
|
||||||
|
{
|
||||||
|
foreach($this->headers[PackageStructure::DIRECTORY] as $name => $location)
|
||||||
|
{
|
||||||
|
if(str_ends_with($name, $filename))
|
||||||
|
{
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the offset of the package
|
* Returns the offset of the package
|
||||||
*
|
*
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
use ncc\ThirdParty\nikic\PhpParser\Comment;
|
use ncc\ThirdParty\nikic\PhpParser\Comment;
|
||||||
use ncc\ThirdParty\nikic\PhpParser\Node;
|
use ncc\ThirdParty\nikic\PhpParser\Node;
|
||||||
|
use ncc\ThirdParty\nikic\PhpParser\NodeTraverser;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionException;
|
use ReflectionException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
@ -60,9 +61,10 @@
|
||||||
*/
|
*/
|
||||||
public static function extractClasses(Node|array $node, string $prefix=''): array
|
public static function extractClasses(Node|array $node, string $prefix=''): array
|
||||||
{
|
{
|
||||||
|
$classes = [];
|
||||||
|
|
||||||
if(is_array($node))
|
if(is_array($node))
|
||||||
{
|
{
|
||||||
$classes = [];
|
|
||||||
foreach($node as $sub_node)
|
foreach($node as $sub_node)
|
||||||
{
|
{
|
||||||
/** @noinspection SlowArrayOperationsInLoopInspection */
|
/** @noinspection SlowArrayOperationsInLoopInspection */
|
||||||
|
@ -71,8 +73,6 @@
|
||||||
return $classes;
|
return $classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
$classes = [];
|
|
||||||
|
|
||||||
if ($node instanceof Node\Stmt\ClassLike)
|
if ($node instanceof Node\Stmt\ClassLike)
|
||||||
{
|
{
|
||||||
$classes[] = $prefix . $node->name;
|
$classes[] = $prefix . $node->name;
|
||||||
|
@ -80,9 +80,9 @@
|
||||||
|
|
||||||
if ($node instanceof Node\Stmt\Namespace_)
|
if ($node instanceof Node\Stmt\Namespace_)
|
||||||
{
|
{
|
||||||
if ($node->name && $node->name->parts)
|
if ($node->name && $node->name->getParts())
|
||||||
{
|
{
|
||||||
$prefix .= implode('\\', $node->name->parts) . '\\';
|
$prefix .= implode('\\', $node->name->getParts()) . '\\';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -254,4 +254,19 @@
|
||||||
|
|
||||||
throw new RuntimeException("Unknown node type \"$nodeType\"");
|
throw new RuntimeException("Unknown node type \"$nodeType\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms include, include_once, require and require_once statements into function calls.
|
||||||
|
*
|
||||||
|
* @param Node|array $stmts The AST node or array of nodes to transform.
|
||||||
|
* @param string|null $package Optionally. The package name to pass to the transformed function calls.
|
||||||
|
* @return Node|array The transformed AST node or array of nodes.
|
||||||
|
*/
|
||||||
|
public static function transformRequireCalls(Node|array $stmts, ?string $package=null): Node|array
|
||||||
|
{
|
||||||
|
$traverser = new NodeTraverser();
|
||||||
|
$traverser->addVisitor(new ExpressionTraverser($package));
|
||||||
|
|
||||||
|
return $traverser->traverse($stmts);
|
||||||
|
}
|
||||||
}
|
}
|
74
src/ncc/Classes/PhpExtension/ExpressionTraverser.php
Normal file
74
src/ncc/Classes/PhpExtension/ExpressionTraverser.php
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (c) Nosial 2022-2023, all rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
* associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
||||||
|
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following
|
||||||
|
* conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions
|
||||||
|
* of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace ncc\Classes\PhpExtension;
|
||||||
|
|
||||||
|
use ncc\ThirdParty\nikic\PhpParser\Node;
|
||||||
|
use ncc\ThirdParty\nikic\PhpParser\NodeVisitorAbstract;
|
||||||
|
use ncc\Utilities\Console;
|
||||||
|
|
||||||
|
class ExpressionTraverser extends NodeVisitorAbstract
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private ?string $package;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExpressionTraverser constructor.
|
||||||
|
*
|
||||||
|
* @param string|null $package
|
||||||
|
*/
|
||||||
|
public function __construct(?string $package)
|
||||||
|
{
|
||||||
|
$this->package = $package;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Node $node
|
||||||
|
* @return array|int|Node|null
|
||||||
|
*/
|
||||||
|
public function leaveNode(Node $node): array|int|Node|null
|
||||||
|
{
|
||||||
|
if($node instanceof Node\Expr\Include_)
|
||||||
|
{
|
||||||
|
Console::outDebug(sprintf('Processing ExpressionTraverser on: %s', $node->getType()));
|
||||||
|
$args = [$node->expr];
|
||||||
|
|
||||||
|
if(!is_null($this->package))
|
||||||
|
{
|
||||||
|
$args[] = new Node\Arg(new Node\Scalar\String_($this->package));
|
||||||
|
}
|
||||||
|
|
||||||
|
$types = [
|
||||||
|
Node\Expr\Include_::TYPE_INCLUDE => '\ncc\Classes\Runtime::runtimeInclude',
|
||||||
|
Node\Expr\Include_::TYPE_INCLUDE_ONCE => '\ncc\Classes\Runtime::runtimeIncludeOnce',
|
||||||
|
Node\Expr\Include_::TYPE_REQUIRE => '\ncc\Classes\Runtime::runtimeRequire',
|
||||||
|
Node\Expr\Include_::TYPE_REQUIRE_ONCE => '\ncc\Classes\Runtime::runtimeRequireOnce',
|
||||||
|
];
|
||||||
|
|
||||||
|
return new Node\Expr\FuncCall(new Node\Name($types[$node->type]), $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,6 +53,9 @@
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$stmts = (new ParserFactory())->create(ParserFactory::PREFER_PHP7)->parse(IO::fread($file_path));
|
$stmts = (new ParserFactory())->create(ParserFactory::PREFER_PHP7)->parse(IO::fread($file_path));
|
||||||
|
$stmts = AstWalker::transformRequireCalls(
|
||||||
|
$stmts, $this->getProjectManager()->getProjectConfiguration()->getAssembly()->getPackage()
|
||||||
|
);
|
||||||
|
|
||||||
$component = new Component($component_name, ZiProto::encode($stmts), ComponentDataType::AST);
|
$component = new Component($component_name, ZiProto::encode($stmts), ComponentDataType::AST);
|
||||||
$component->addFlag(ComponentFlags::PHP_AST);
|
$component->addFlag(ComponentFlags::PHP_AST);
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
use ncc\Enums\FileDescriptor;
|
use ncc\Enums\FileDescriptor;
|
||||||
use ncc\Enums\Flags\PackageFlags;
|
use ncc\Enums\Flags\PackageFlags;
|
||||||
use ncc\Enums\Options\BuildConfigurationOptions;
|
use ncc\Enums\Options\BuildConfigurationOptions;
|
||||||
|
use ncc\Enums\Options\ComponentDecodeOptions;
|
||||||
|
use ncc\Enums\PackageDirectory;
|
||||||
use ncc\Enums\Versions;
|
use ncc\Enums\Versions;
|
||||||
use ncc\Exceptions\ConfigurationException;
|
use ncc\Exceptions\ConfigurationException;
|
||||||
use ncc\Exceptions\ImportException;
|
use ncc\Exceptions\ImportException;
|
||||||
|
@ -39,8 +41,12 @@
|
||||||
use ncc\Extensions\ZiProto\ZiProto;
|
use ncc\Extensions\ZiProto\ZiProto;
|
||||||
use ncc\Managers\PackageManager;
|
use ncc\Managers\PackageManager;
|
||||||
use ncc\Objects\Package\Metadata;
|
use ncc\Objects\Package\Metadata;
|
||||||
|
use ncc\Utilities\Console;
|
||||||
use ncc\Utilities\IO;
|
use ncc\Utilities\IO;
|
||||||
|
use ncc\Utilities\Resolver;
|
||||||
|
use ncc\Utilities\Validate;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class Runtime
|
class Runtime
|
||||||
{
|
{
|
||||||
|
@ -59,6 +65,11 @@
|
||||||
*/
|
*/
|
||||||
private static $package_manager;
|
private static $package_manager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $included_files = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the main execution point of an imported package and returns the evaluated result
|
* Executes the main execution point of an imported package and returns the evaluated result
|
||||||
* This method may exit the program without returning a value
|
* This method may exit the program without returning a value
|
||||||
|
@ -170,6 +181,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$entry = self::getPackageManager()->getPackageLock()->getEntry($package);
|
$entry = self::getPackageManager()->getPackageLock()->getEntry($package);
|
||||||
|
self::$imported_packages[$package] = $entry->getPath($version);
|
||||||
|
|
||||||
foreach($entry->getClassMap($version) as $class => $component_name)
|
foreach($entry->getClassMap($version) as $class => $component_name)
|
||||||
{
|
{
|
||||||
|
@ -197,8 +209,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self::$imported_packages[$package] = $entry->getPath($version);
|
|
||||||
|
|
||||||
if(isset($entry->getMetadata($version)->getOptions()[PackageFlags::STATIC_DEPENDENCIES]))
|
if(isset($entry->getMetadata($version)->getOptions()[PackageFlags::STATIC_DEPENDENCIES]))
|
||||||
{
|
{
|
||||||
// Fake import the dependencies
|
// Fake import the dependencies
|
||||||
|
@ -345,7 +355,6 @@
|
||||||
if(is_string(self::$class_map[$class]) && is_file(self::$class_map[$class]))
|
if(is_string(self::$class_map[$class]) && is_file(self::$class_map[$class]))
|
||||||
{
|
{
|
||||||
require_once self::$class_map[$class];
|
require_once self::$class_map[$class];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,4 +370,274 @@
|
||||||
|
|
||||||
return self::$package_manager;
|
return self::$package_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of included files both from the php runtime and ncc runtime
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function runtimeGetIncludedFiles(): array
|
||||||
|
{
|
||||||
|
return array_merge(get_included_files(), self::$included_files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates and executes PHP code with error handling, this function
|
||||||
|
* gracefully handles <?php ?> tags and exceptions the same way as the
|
||||||
|
* require/require_once/include/include_once expressions
|
||||||
|
*
|
||||||
|
* @param string $code The PHP code to be executed
|
||||||
|
*/
|
||||||
|
public static function extendedEvaluate(string $code): void
|
||||||
|
{
|
||||||
|
if(ob_get_level() > 0)
|
||||||
|
{
|
||||||
|
ob_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
$exceptions = [];
|
||||||
|
$code = preg_replace_callback('/<\?php(.*?)\?>/s', static function ($matches) use (&$exceptions)
|
||||||
|
{
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
eval($matches[1]);
|
||||||
|
}
|
||||||
|
catch (Throwable $e)
|
||||||
|
{
|
||||||
|
$exceptions[] = $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ob_get_clean();
|
||||||
|
}, $code);
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
eval('?>' . $code);
|
||||||
|
}
|
||||||
|
catch (Throwable $e)
|
||||||
|
{
|
||||||
|
$exceptions[] = $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($exceptions))
|
||||||
|
{
|
||||||
|
print(ob_get_clean());
|
||||||
|
|
||||||
|
$exception_stack = null;
|
||||||
|
foreach ($exceptions as $e)
|
||||||
|
{
|
||||||
|
if($exception_stack === null)
|
||||||
|
{
|
||||||
|
$exception_stack = $e;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$exception_stack = new Exception($exception_stack->getMessage(), $exception_stack->getCode(), $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException('An exception occurred while evaluating the code', 0, $exception_stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
print(ob_get_clean());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the content of the aquired file
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param string|null $package
|
||||||
|
* @return string
|
||||||
|
* @throws ConfigurationException
|
||||||
|
* @throws IOException
|
||||||
|
* @throws OperationException
|
||||||
|
* @throws PathNotFoundException
|
||||||
|
*/
|
||||||
|
private static function acquireFile(string $path, ?string $package=null): string
|
||||||
|
{
|
||||||
|
$cwd_checked = false; // sanity check to prevent checking the cwd twice
|
||||||
|
|
||||||
|
// Check if the file is absolute
|
||||||
|
if(is_file($path))
|
||||||
|
{
|
||||||
|
Console::outDebug(sprintf('Acquired file "%s" from absolute path', $path));
|
||||||
|
return IO::fread($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since $package is not null, let's try to acquire the file from the package
|
||||||
|
if($package !== null && isset(self::$imported_packages[$package]))
|
||||||
|
{
|
||||||
|
$base_path = basename($path);
|
||||||
|
|
||||||
|
if(self::$imported_packages[$package] instanceof PackageReader)
|
||||||
|
{
|
||||||
|
$acquired_file = self::$imported_packages[$package]->find($base_path);
|
||||||
|
Console::outDebug(sprintf('Acquired file "%s" from package "%s"', $path, $package));
|
||||||
|
|
||||||
|
return match (Resolver::componentType($acquired_file))
|
||||||
|
{
|
||||||
|
PackageDirectory::RESOURCES => self::$imported_packages[$package]->getResource(Resolver::componentName($acquired_file))->getData(),
|
||||||
|
PackageDirectory::COMPONENTS => self::$imported_packages[$package]->getComponent(Resolver::componentName($acquired_file))->getData([ComponentDecodeOptions::AS_FILE]),
|
||||||
|
default => throw new IOException(sprintf('Unable to acquire file "%s" from package "%s" because it is not a resource or component', $path, $package)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_dir(self::$imported_packages[$package]))
|
||||||
|
{
|
||||||
|
$base_path = basename($path);
|
||||||
|
foreach(IO::scan(self::$imported_packages[$package]) as $file)
|
||||||
|
{
|
||||||
|
if(str_ends_with($file, $base_path))
|
||||||
|
{
|
||||||
|
Console::outDebug(sprintf('Acquired file "%s" from package "%s"', $path, $package));
|
||||||
|
return IO::fread($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not, let's try the include_path
|
||||||
|
foreach(explode(PATH_SEPARATOR, get_include_path()) as $file_path)
|
||||||
|
{
|
||||||
|
if($file_path === '.' && !$cwd_checked)
|
||||||
|
{
|
||||||
|
$cwd_checked = true;
|
||||||
|
$file_path = getcwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_file($file_path . DIRECTORY_SEPARATOR . $path))
|
||||||
|
{
|
||||||
|
Console::outDebug(sprintf('Acquired file "%s" from include_path', $path));
|
||||||
|
return IO::fread($file_path . DIRECTORY_SEPARATOR . $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_file($file_path . DIRECTORY_SEPARATOR . basename($path)))
|
||||||
|
{
|
||||||
|
Console::outDebug(sprintf('Acquired file "%s" from include_path (using basename)', $path));
|
||||||
|
return IO::fread($file_path . DIRECTORY_SEPARATOR . basename($path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the current working directory
|
||||||
|
if(!$cwd_checked)
|
||||||
|
{
|
||||||
|
if(is_file(getcwd() . DIRECTORY_SEPARATOR . $path))
|
||||||
|
{
|
||||||
|
Console::outDebug(sprintf('Acquired file "%s" from current working directory', $path));
|
||||||
|
return IO::fread(getcwd() . DIRECTORY_SEPARATOR . $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_file(getcwd() . DIRECTORY_SEPARATOR . basename($path)))
|
||||||
|
{
|
||||||
|
Console::outDebug(sprintf('Acquired file "%s" from current working directory (using basename)', $path));
|
||||||
|
return IO::fread(getcwd() . DIRECTORY_SEPARATOR . basename($path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the calling script's directory
|
||||||
|
$called_script_directory = dirname(debug_backtrace()[0]['file']);
|
||||||
|
$file_path = $called_script_directory . DIRECTORY_SEPARATOR . $path;
|
||||||
|
if(is_file($file_path))
|
||||||
|
{
|
||||||
|
Console::outDebug(sprintf('Acquired file "%s" from calling script\'s directory', $path));
|
||||||
|
return IO::fread($file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException(sprintf('Unable to acquire file "%s" because it does not exist', $path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes a file at runtime
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param string|null $package
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function runtimeInclude(string $path, ?string $package=null): void
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$acquired_file = self::acquireFile($path, $package);
|
||||||
|
}
|
||||||
|
catch(Exception $e)
|
||||||
|
{
|
||||||
|
$package ?
|
||||||
|
Console::outWarning(sprintf('Failed to acquire file "%s" from package "%s" at runtime: %s', $path, $package, $e->getMessage())) :
|
||||||
|
Console::outWarning(sprintf('Failed to acquire file "%s" at runtime: %s', $path, $e->getMessage()));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!in_array($path, self::$included_files, true))
|
||||||
|
{
|
||||||
|
self::$included_files[] = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::extendedEvaluate($acquired_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes a file at runtime if it's not already included
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param string|null $package
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function runtimeIncludeOnce(string $path, ?string $package=null): void
|
||||||
|
{
|
||||||
|
if(in_array($path, self::runtimeGetIncludedFiles(), true))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::runtimeInclude($path, $package);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires a file at runtime, throws an exception if the file failed to require
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param string|null $package
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function runtimeRequire(string $path, ?string $package=null): void
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$acquired_file = self::acquireFile($path, $package);
|
||||||
|
}
|
||||||
|
catch(Exception $e)
|
||||||
|
{
|
||||||
|
$package ?
|
||||||
|
throw new RuntimeException(sprintf('Failed to acquire file "%s" from package "%s" at runtime: %s', $path, $package, $e->getMessage()), $e->getCode(), $e) :
|
||||||
|
throw new RuntimeException(sprintf('Failed to acquire file "%s" at runtime: %s', $path, $e->getMessage()), $e->getCode(), $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!in_array($path, self::$included_files, true))
|
||||||
|
{
|
||||||
|
self::$included_files[] = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::extendedEvaluate($acquired_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires a file at runtime if it's not already required
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function runtimeRequireOnce(string $path): void
|
||||||
|
{
|
||||||
|
if(in_array($path, self::runtimeGetIncludedFiles(), true))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::runtimeRequire($path);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -285,13 +285,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes ncc system files
|
* Initializes the necessary files and directories for the ncc application
|
||||||
*
|
*
|
||||||
* @param array $default_repositories
|
* @param string|null $install_path The installation path of ncc (optional)
|
||||||
* @return void
|
* @param array $default_repositories The default repositories to initialize (optional)
|
||||||
* @throws OperationException
|
* @throws OperationException
|
||||||
*/
|
*/
|
||||||
public static function initializeFiles(array $default_repositories=[]): void
|
public static function initializeFiles(?string $install_path=null, array $default_repositories=[]): void
|
||||||
{
|
{
|
||||||
if(Resolver::resolveScope() !== Scopes::SYSTEM)
|
if(Resolver::resolveScope() !== Scopes::SYSTEM)
|
||||||
{
|
{
|
||||||
|
@ -342,7 +342,7 @@
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
self::registerExtension($filesystem);
|
self::registerExtension($filesystem, $install_path);
|
||||||
}
|
}
|
||||||
catch(Exception $e)
|
catch(Exception $e)
|
||||||
{
|
{
|
||||||
|
@ -351,23 +351,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the ncc extension with the given filesystem.
|
* Registers the ncc extension with the given filesystem and optional install path.
|
||||||
*
|
*
|
||||||
* @param Filesystem $filesystem The filesystem object used for file operations.
|
* @param Filesystem $filesystem The filesystem to register the extension with
|
||||||
* @throws IOException If the extension cannot be registered.
|
* @param string|null $install_path The optional install path for the extension
|
||||||
* @throws NotSupportedException If `get_include_path()` function is not available.
|
* @return void
|
||||||
* @throws PathNotFoundException If the default include path is not available.
|
* @throws IOException
|
||||||
|
* @throws NotSupportedException
|
||||||
|
* @throws PathNotFoundException
|
||||||
*/
|
*/
|
||||||
private static function registerExtension(Filesystem $filesystem): void
|
private static function registerExtension(Filesystem $filesystem, ?string $install_path=null): void
|
||||||
{
|
{
|
||||||
if(!function_exists('get_include_path'))
|
if(!function_exists('get_include_path'))
|
||||||
{
|
{
|
||||||
throw new NotSupportedException('Cannot register ncc extension, get_include_path() is not available');
|
throw new NotSupportedException('Cannot register ncc extension, get_include_path() is not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($install_path === null)
|
||||||
|
{
|
||||||
|
$install_path = NCC_EXEC_LOCATION;
|
||||||
|
}
|
||||||
|
|
||||||
$default_share = DIRECTORY_SEPARATOR . 'usr' . DIRECTORY_SEPARATOR . 'share' . DIRECTORY_SEPARATOR . 'php';
|
$default_share = DIRECTORY_SEPARATOR . 'usr' . DIRECTORY_SEPARATOR . 'share' . DIRECTORY_SEPARATOR . 'php';
|
||||||
$include_paths = explode(':', get_include_path());
|
$include_paths = explode(':', get_include_path());
|
||||||
$extension = str_ireplace('%ncc_install', NCC_EXEC_LOCATION, IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'extension'));
|
$extension = str_ireplace('%ncc_install', $install_path, IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'extension'));
|
||||||
|
|
||||||
if(in_array($default_share, $include_paths))
|
if(in_array($default_share, $include_paths))
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,8 +22,10 @@
|
||||||
|
|
||||||
namespace ncc\Utilities;
|
namespace ncc\Utilities;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
use ncc\Exceptions\IOException;
|
use ncc\Exceptions\IOException;
|
||||||
use ncc\Exceptions\PathNotFoundException;
|
use ncc\Exceptions\PathNotFoundException;
|
||||||
|
use RuntimeException;
|
||||||
use SplFileInfo;
|
use SplFileInfo;
|
||||||
use SplFileObject;
|
use SplFileObject;
|
||||||
|
|
||||||
|
@ -122,4 +124,48 @@
|
||||||
Console::outDebug(sprintf('reading %s', $uri));
|
Console::outDebug(sprintf('reading %s', $uri));
|
||||||
return $file->fread($length);
|
return $file->fread($length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all files in the specified path
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param bool $recursive
|
||||||
|
* @return array
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static function scan(string $path, bool $recursive = true): array
|
||||||
|
{
|
||||||
|
if (!is_readable($path))
|
||||||
|
{
|
||||||
|
throw new IOException(sprintf('Directory does not exist or is not readable: %s', $path));
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = [];
|
||||||
|
$items = scandir($path, SCANDIR_SORT_NONE);
|
||||||
|
|
||||||
|
if ($items === false)
|
||||||
|
{
|
||||||
|
throw new IOException(sprintf('Unable to list directory items: %s', $path));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($items as $file)
|
||||||
|
{
|
||||||
|
if ($file === '.' || $file === '..')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_path = $path . DIRECTORY_SEPARATOR . $file;
|
||||||
|
if (is_dir($file_path) && $recursive)
|
||||||
|
{
|
||||||
|
/** @noinspection SlowArrayOperationsInLoopInspection */
|
||||||
|
$files = array_merge($files, self::scan($file_path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files[] = $file_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -24,7 +24,9 @@
|
||||||
|
|
||||||
namespace ncc\Utilities;
|
namespace ncc\Utilities;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
use ncc\Enums\LogLevel;
|
use ncc\Enums\LogLevel;
|
||||||
|
use ncc\Enums\PackageDirectory;
|
||||||
use ncc\Enums\Scopes;
|
use ncc\Enums\Scopes;
|
||||||
use ncc\Enums\Types\ProjectType;
|
use ncc\Enums\Types\ProjectType;
|
||||||
use ncc\Exceptions\NotSupportedException;
|
use ncc\Exceptions\NotSupportedException;
|
||||||
|
@ -256,4 +258,69 @@
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component type based on the file name
|
||||||
|
*
|
||||||
|
* @param string $component_path Component name
|
||||||
|
* @return int Component type
|
||||||
|
* @throws InvalidArgumentException If the component name is invalid
|
||||||
|
* @see PackageDirectory
|
||||||
|
*/
|
||||||
|
public static function componentType(string $component_path): int
|
||||||
|
{
|
||||||
|
// Check for empty string and presence of ":"
|
||||||
|
if (empty($component_path) || !str_contains($component_path, ':'))
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException(sprintf('Invalid component format "%s"', $component_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the prefix before ":" and remove "@" character
|
||||||
|
$file_stub_code = str_ireplace('@', '', explode(':', $component_path, 2)[0]);
|
||||||
|
|
||||||
|
// Check if the prefix is numeric
|
||||||
|
if (!is_numeric($file_stub_code))
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException(sprintf('Invalid component prefix "%s"', $file_stub_code));
|
||||||
|
}
|
||||||
|
|
||||||
|
return match ((int)$file_stub_code)
|
||||||
|
{
|
||||||
|
PackageDirectory::METADATA => PackageDirectory::METADATA,
|
||||||
|
PackageDirectory::ASSEMBLY => PackageDirectory::ASSEMBLY,
|
||||||
|
PackageDirectory::EXECUTION_UNITS => PackageDirectory::EXECUTION_UNITS,
|
||||||
|
PackageDirectory::INSTALLER => PackageDirectory::INSTALLER,
|
||||||
|
PackageDirectory::DEPENDENCIES => PackageDirectory::DEPENDENCIES,
|
||||||
|
PackageDirectory::CLASS_POINTER => PackageDirectory::CLASS_POINTER,
|
||||||
|
PackageDirectory::RESOURCES => PackageDirectory::RESOURCES,
|
||||||
|
PackageDirectory::COMPONENTS => PackageDirectory::COMPONENTS,
|
||||||
|
default => throw new InvalidArgumentException(sprintf('Invalid component type "%s"', $component_path)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the component name based on the file name
|
||||||
|
*
|
||||||
|
* @param string $component_path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function componentName(string $component_path): string
|
||||||
|
{
|
||||||
|
// Check for empty string and presence of ":"
|
||||||
|
if (empty($component_path) || !str_contains($component_path, ':'))
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException(sprintf('Invalid component format "%s"', $component_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the prefix before ":" and remove "@" character
|
||||||
|
$file_stub_code = str_ireplace('@', '', explode(':', $component_path, 2)[0]);
|
||||||
|
|
||||||
|
// Check if the prefix is numeric
|
||||||
|
if (!is_numeric($file_stub_code))
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException(sprintf('Invalid component prefix "%s"', $file_stub_code));
|
||||||
|
}
|
||||||
|
|
||||||
|
return explode(':', $component_path, 2)[1];
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
<?php
|
|
||||||
/*
|
|
||||||
* Copyright (c) Nosial 2022-2023, all rights reserved.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
||||||
* associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
|
||||||
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following
|
|
||||||
* conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions
|
|
||||||
* of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
||||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
||||||
* PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
* DEALINGS IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
$build_directory = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'build';
|
|
||||||
$autoload_path = $build_directory . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'autoload.php';
|
|
||||||
|
|
||||||
if(!is_dir($build_directory))
|
|
||||||
{
|
|
||||||
throw new \RuntimeException('Build directory does not exist, to run tests you must build the project.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!is_file($autoload_path))
|
|
||||||
{
|
|
||||||
throw new \RuntimeException('Autoload file does not exist in \'' . $build_directory .'\', to run tests you must build the project.');
|
|
||||||
}
|
|
||||||
|
|
||||||
require($autoload_path);
|
|
Loading…
Add table
Reference in a new issue