diff --git a/.travis.yml b/.travis.yml index f8fac35..4da865c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,15 @@ language: php - dist: trusty - sudo: false php: - 7.0 + - 7.1 + - 7.2 matrix: include: - - php: 7.0 + - php: 7.1 env: - BUILD_PHAR=true @@ -23,4 +23,4 @@ deploy: skip_cleanup: true on: tags: true - php: '7.0' \ No newline at end of file + php: '7.1' \ No newline at end of file diff --git a/bin/php-cs b/bin/php-cs index dae5cc1..cf2ba99 100755 --- a/bin/php-cs +++ b/bin/php-cs @@ -1,3 +1,4 @@ +#!/usr/bin/env php run(); \ No newline at end of file diff --git a/composer.json b/composer.json index 2af885f..74b5f56 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,6 @@ "email": "bgabor@ena.com" } ], - "autoload": { "psr-4": { "MyENA\\CloudStackClientGenerator\\": "src/" @@ -34,7 +33,6 @@ "files/constants.php" ] }, - "require": { "php": "^7.0", "symfony/console": "@stable", @@ -46,10 +44,8 @@ "guzzlehttp/guzzle": "@stable" }, "require-dev": { - "myena/default-logger": "@stable", - "phpunit/phpunit": "@stable" + "myena/default-logger": "@stable" }, - "scripts": { "build": [ "rm -rf vendor", diff --git a/files/config_prototype.yml b/files/config_prototype.yml index fa8fe16..6ccd975 100644 --- a/files/config_prototype.yml +++ b/files/config_prototype.yml @@ -12,18 +12,35 @@ # php_cs_generator must be the root key php_cs_generator: - dev: # Name of this config. Accessible via the "config-env" option - scheme: "http" - host: ~ # dev.ourcloudstack.com - port: 8080 - apipath: "client/api" - consolepath: "client/console" - out: ~ # choose output path - namespace: "\\MyGreatNamespace" - key: ~ # your api key - secret: ~ # your api secret - prod: - host: prod.ourcloudstack.com - port: 8765 - key: ~ # your api key - secret: ~ # your api secret \ No newline at end of file + # environment configuration + environments: + dev: # Name of this config. Accessible via the "env" option + scheme: "http" + host: ~ # dev.ourcloudstack.com + port: 8080 + apipath: "client/api" + consolepath: "client/console" + out: ~ # choose output path + namespace: "\\MyGreatNamespace" + key: ~ # your api key + secret: ~ # your api secret + logger: + class: ~ # psr-3 compliant logger instance + level: ~ #level the environment's logger should be + http_client: + class: ~ # any class that implements \GuzzleHttp\ClientInterface + config: + allow_redirects: true + http_errors: true + verify: false + prod: + host: prod.ourcloudstack.com + port: 8765 + key: ~ # your api key + secret: ~ # your api secret + # allows you to extend the classes constructed by this lib with your own class + overloaded_classes: + - name: Tags # name of class you are overloading + overload: MyGreatTagsClass # fully qualified name of class extending the base class + - name: ListVirtualMachinesRequest + overload: DoSomethingCrazyRequest \ No newline at end of file diff --git a/src/Client.php b/src/Client.php index 58ba4d8..ae2c449 100644 --- a/src/Client.php +++ b/src/Client.php @@ -3,21 +3,22 @@ use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Uri; use GuzzleHttp\RequestOptions; +use MyENA\CloudStackClientGenerator\Configuration\Environment; /** * Class Client * @package MyENA\CloudStackClientGenerator */ class Client { - /** @var \MyENA\CloudStackClientGenerator\Configuration */ - protected $config; + /** @var \MyENA\CloudStackClientGenerator\Configuration\Environment */ + protected $env; /** * Client constructor. - * @param \MyENA\CloudStackClientGenerator\Configuration $c + * @param \MyENA\CloudStackClientGenerator\Configuration\Environment $e */ - public function __construct(Configuration $c) { - $this->config = $c; + public function __construct(Environment $e) { + $this->env = $e; } /** @@ -25,12 +26,14 @@ public function __construct(Configuration $c) { * @param array $parameters * @param array $headers * @return \stdClass + * @throws \Exception + * @throws \GuzzleHttp\Exception\GuzzleException */ public function do(string $command, array $parameters = [], array $headers = []): \stdClass { static $defaultHeaders = ['Accept' => ['application/json'], 'Content-Type' => ['application/x-www-form-urlencoded']]; - $params = ['apikey' => $this->config->getKey(), 'command' => $command, 'response' => 'json'] + $parameters; + $params = ['apikey' => $this->env->getKey(), 'command' => $command, 'response' => 'json'] + $parameters; ksort($params); @@ -38,15 +41,15 @@ public function do(string $command, array $parameters = [], array $headers = []) $uri = new Uri(sprintf( '%s/%s?%s&signature=%s', - $this->config->getCompiledAddress(), - $this->config->getApiPath(), + $this->env->getCompiledAddress(), + $this->env->getApiPath(), $query, - $this->config->buildSignature($query) + $this->env->buildSignature($query) )); $r = new Request('GET', $uri, $headers + $defaultHeaders); - $resp = $this->config->HttpClient->send($r, [ + $resp = $this->env->getHttpClient()->send($r, [ RequestOptions::HTTP_ERRORS => false, RequestOptions::DECODE_CONTENT => false, ]); diff --git a/src/Command/AbstractCommand.php b/src/Command/AbstractCommand.php index 4e950b3..d646819 100644 --- a/src/Command/AbstractCommand.php +++ b/src/Command/AbstractCommand.php @@ -2,6 +2,7 @@ use MyENA\CloudStackClientGenerator\Client; use MyENA\CloudStackClientGenerator\Configuration; +use Psr\Log\NullLogger; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -20,6 +21,9 @@ abstract class AbstractCommand extends Command { /** @var \MyENA\CloudStackClientGenerator\Configuration */ protected $config; + /** @var \MyENA\CloudStackClientGenerator\Configuration\Environment */ + protected $env; + /** @var \MyENA\CloudStackClientGenerator\Client */ private $client; @@ -50,7 +54,7 @@ protected function addConfigOptions() { null, InputOption::VALUE_REQUIRED, 'HTTP Scheme to use (http or https)', - Configuration::DefaultScheme + Configuration\Environment::DefaultScheme ) ->addOption( 'host', @@ -63,21 +67,21 @@ protected function addConfigOptions() { null, InputOption::VALUE_REQUIRED, 'API Client port to use', - Configuration::DefaultPort + Configuration\Environment::DefaultPort ) ->addOption( 'apipath', null, InputOption::VALUE_REQUIRED, 'API path to use', - Configuration::DefaultAPIPath + Configuration\Environment::DefaultAPIPath ) ->addOption( 'consolepath', null, InputOption::VALUE_REQUIRED, 'Console path to use', - Configuration::DefaultConsolePath + Configuration\Environment::DefaultConsolePath ) ->addOption( 'key', @@ -102,7 +106,8 @@ protected function addConfigOptions() { null, InputOption::VALUE_REQUIRED, 'Namespace for generated code' - ); + ) + ; } /** @@ -110,7 +115,11 @@ protected function addConfigOptions() { * @param \Symfony\Component\Console\Output\OutputInterface $output */ protected function initialize(InputInterface $input, OutputInterface $output) { - $this->log = new ConsoleLogger($output); + if ((bool)$output->isQuiet()) { + $this->log = new NullLogger(); + } else { + $this->log = new ConsoleLogger($output); + } } /** @@ -119,11 +128,11 @@ protected function initialize(InputInterface $input, OutputInterface $output) { * @return bool */ protected function initializeConfig(InputInterface $input, OutputInterface $output): bool { - $this->config = new Configuration([], $this->log); + $this->config = new Configuration([]); // attempt to parse config file if ($file = $input->getOption('config')) { - $file = $this->tryResolvePath($file); + $file = Configuration\Environment::tryResolvePath($file); if (!file_exists($file) || !is_readable($file)) { $this->log->error("Config file {$file} either does not exist or is not readable"); return false; @@ -131,60 +140,70 @@ protected function initializeConfig(InputInterface $input, OutputInterface $outp if (!$this->parseYAML($file, (string)$input->getOption('env'))) { return false; } + } else { + $environment = new Configuration\Environment([ + 'name' => 'adhoc', + ]); + $this->config->getEnvironments()->setEnvironment($environment); + $this->env = $environment; + $this->log->info('No config file specified, using adhoc'); + } + if ($this->env->getLogger() === null || $this->env->getLogger() instanceof NullLogger) { + $this->env->setLogger($this->log); // TODO: might just set null logger again, do we care? } // Set any runtime command options - if (Configuration::DefaultScheme === $this->config->getScheme() && - Configuration::DefaultScheme !== ($scheme = $input->getOption('scheme'))) { - $this->config->setScheme($scheme); + if (Configuration\Environment::DefaultScheme === $this->env->getScheme() && + Configuration\Environment::DefaultScheme !== ($scheme = $input->getOption('scheme'))) { + $this->env->setScheme($scheme); } if ($host = $input->getOption('host')) { - $this->config->setHost($host); + $this->env->setHost($host); } - if (Configuration::DefaultPort === $this->config->getPort() && - Configuration::DefaultPort !== ($port = $input->getOption('port'))) { - $this->config->setPort($port); + if (Configuration\Environment::DefaultPort === $this->env->getPort() && + Configuration\Environment::DefaultPort !== ($port = $input->getOption('port'))) { + $this->env->setPort($port); } - if (Configuration::DefaultAPIPath === $this->config->getApiPath() && - Configuration::DefaultAPIPath !== ($apiPath = $input->getOption('apipath'))) { - $this->config->setApiPath($apiPath); + if (Configuration\Environment::DefaultAPIPath === $this->env->getApiPath() && + Configuration\Environment::DefaultAPIPath !== ($apiPath = $input->getOption('apipath'))) { + $this->env->setApiPath($apiPath); } if ($consolePath = $input->getOption('consolepath')) { - $this->config->setConsolePath($consolePath); + $this->env->setConsolePath($consolePath); } if ($key = $input->getOption('key')) { - $this->config->setKey($key); + $this->env->setKey($key); } if ($secret = $input->getOption('secret')) { - $this->config->setSecret($secret); + $this->env->setSecret($secret); } if ($out = $input->getOption('out')) { - $this->config->setOutputDir($this->tryResolvePath($out)); + $this->env->setOut(Configuration\Environment::tryResolvePath($out)); } if ($ns = $input->getOption('namespace')) { - $this->config->setNamespace($ns); + $this->env->setNamespace($ns); } // Do some basic validation - if ('' === $this->config->getHost()) { + if ('' === $this->env->getHost()) { $this->log->error('"host" cannot be empty'); return false; } - if ('' === $this->config->getKey()) { + if ('' === $this->env->getKey()) { $this->log->error('"key" cannot be empty'); return false; } - if ('' === $this->config->getSecret()) { + if ('' === $this->env->getSecret()) { $this->log->error('"secret" cannot be empty'); return false; } - if ('' === $this->config->getOutputDir()) { + if ('' === $this->env->getOut()) { $this->log->error('The "out" option must be passed!'); - } else if (!is_dir($this->config->getOutputDir()) && !mkdir($this->config->getOutputDir())) { - $this->log->error("Unable to create output directory \"{$this->config->getOutputDir()}\""); + } else if (!is_dir($this->env->getOut()) && !mkdir($this->env->getOut())) { + $this->log->error("Unable to create output directory \"{$this->env->getOut()}\""); return false; - } else if (!is_writable($this->config->getOutputDir())) { - $this->log->error("Output directory \"{$this->config->getOutputDir()}\" is not writable"); + } else if (!is_writable($this->env->getOut())) { + $this->log->error("Output directory \"{$this->env->getOut()}\" is not writable"); return false; } @@ -213,29 +232,25 @@ protected function parseYAML(string $file, string $env = ''): bool { $parsed = $parsed['php_cs_generator']; + $this->config = new Configuration($parsed); + if ('' !== $env) { - if (isset($parsed[$env])) { - $parsed = $parsed[$env]; - } else { + $environment = $this->config->getEnvironments()->getEnvironment($env); + if (null === $environment) { $this->log->error("Config file \"{$file}\" does not contain specified environment \"{$env}\""); return false; } - $this->log->info("Using \"{$env}\" configuration"); } else { - $this->log->info('No env specified, using first entry in config: "'.key($parsed).'"'); - $parsed = reset($parsed); - } - - foreach ($parsed as $k => $v) { - if ('path' === substr($k, -4)) { - $k = substr($k, 0, strlen($k) - 4).'Path'; - } else if ('out' === $k) { - $k = 'outputDir'; - $v = $this->tryResolvePath($v); + $first = $this->config->getEnvironments()->first(); + if (null === $first) { + $this->log->error("Config file \"{$file}\" contains no environments"); + return false; } - $this->config->{'set'.ucfirst($k)}($v); + $this->env = $first; } + $this->log->info("Using \"{$this->env->getName()}\" configuration"); + return true; } @@ -244,32 +259,8 @@ protected function parseYAML(string $file, string $env = ''): bool { */ protected function getClient(): Client { if (!isset($this->client)) { - $this->client = new Client($this->config); + $this->client = new Client($this->env); } return $this->client; } - - /** - * Will attempt to detect and expand a relative path. - * - * // TODO: This is probably a bad idea and I should stop being lazy. - * - * @param string $in - * @return string - */ - protected function tryResolvePath(string $in): string { - if (0 === strpos($in, './')) { - if ($rp = realpath(PHPCS_ROOT.'/'.substr($in, 2))) { - return $rp; - } - return PHPCS_ROOT.'/'.substr($in, 2); - } else if (0 !== strpos($in, '/')) { - if ($rp = realpath(PHPCS_ROOT.'/'.ltrim($in, "/"))) { - return $rp; - } - return PHPCS_ROOT.'/'.ltrim($in, "/"); - } else { - return $in; - } - } } \ No newline at end of file diff --git a/src/Command/GenerateClientCommand.php b/src/Command/GenerateClientCommand.php index 29f95eb..c397ccd 100644 --- a/src/Command/GenerateClientCommand.php +++ b/src/Command/GenerateClientCommand.php @@ -23,20 +23,8 @@ protected function configure() { Example Config: -# php_cs_generator must be the root key -php_cs_generator: - dev: # Name of this config. Accessible via the "config-env" option - host: dev.ourcloudstack.com - key: # your api key - secret: # your api secret - prod: - host: prod.ourcloudstack.com - port: 8765 - key: # your api key - secret: # your api secret - STRING - ); + .rtrim(file_get_contents(__DIR__.'/../../files/config_prototype.yml'))."\n\n"); $this->addConfigOptions(); } @@ -44,21 +32,22 @@ protected function configure() { /** * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output - * @return int + * @return int|null + * @throws \GuzzleHttp\Exception\GuzzleException */ protected function execute(InputInterface $input, OutputInterface $output) { if (!$this->initializeConfig($input, $output)) { return 1; } - $this->log->info("Generating client against host \"{$this->config->getHost()}\""); + $this->log->info("Generating client against host \"{$this->env->getHost()}\""); - $generator = new Generator($this->config); + $generator = new Generator($this->config, $this->env); try { $generator->generate(); } catch (\Throwable $e) { - $this->log->error("Unable to complete generation: {$e->getMessage()}."); + $this->log->error("Unable to complete generation: {$e->getMessage()}"); return 1; } diff --git a/src/Configuration.php b/src/Configuration.php index b816a14..ad505a8 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -1,84 +1,33 @@ logger = new NullLogger(); - } else { - $this->logger = $logger; - } - $this->now = new \DateTime(); + public function __construct(array $config = []) { $this->eventTypeMap = require __DIR__.'/../files/command_event_map.php'; - foreach ($config as $k => $v) { - if (false === strpos($k, '_')) { - $this->{'set' . ucfirst($k)}($v); - } else { - $this->{'set' . implode('', array_map('ucfirst', explode('_', $k)))}($v); - } - } - - if (!isset($this->HttpClient)) { - $this->HttpClient = new Client(); - } + $this->environments = new Environments($config['environments'] ?? []); + $this->overloadedClasses = new OverloadedClasses($config['overloaded_classes'] ?? []); } public function __debugInfo() { @@ -88,211 +37,16 @@ public function __debugInfo() { } /** - * @return \Psr\Log\LoggerInterface - */ - public function getLogger() { - return $this->logger; - } - - /** - * @return \DateTime - */ - public function getNow() { - return $this->now; - } - - /** - * @return string - */ - public function getKey() { - return $this->key; - } - - /** - * @param string $key - * @return Configuration - */ - public function setKey($key) { - $this->key = $key; - return $this; - } - - /** - * @return string - */ - public function getSecret() { - return $this->secret; - } - - /** - * @param string $secret - * @return Configuration - */ - public function setSecret($secret) { - $this->secret = $secret; - return $this; - } - - /** - * @return string - */ - public function getScheme() { - return $this->scheme; - } - - /** - * @param string $scheme - * @return Configuration - */ - public function setScheme($scheme) { - $this->scheme = $scheme; - $this->compiledAddress = ''; - return $this; - } - - /** - * @return string - */ - public function getHost() { - return $this->host; - } - - /** - * @param string $host - * @return Configuration - */ - public function setHost($host) { - $this->host = $host; - $this->compiledAddress = ''; - return $this; - } - - /** - * @return int - */ - public function getPort() { - return $this->port; - } - - /** - * @param int $port - * @return Configuration - */ - public function setPort($port) { - $this->port = $port; - $this->compiledAddress = ''; - return $this; - } - - /** - * @return string - */ - public function getApiPath() { - return $this->apiPath; - } - - /** - * @var string $apiPath ; - * @return Configuration - */ - public function setApiPath($apiPath) { - $this->apiPath = trim($apiPath, " \t\r\n\0\x0B/"); - return $this; - } - - /* - * @return string - */ - public function getConsolePath() { - return $this->consolePath; - } - - /** - * @param string $consolePath - * @return Configuration - */ - public function setConsolePath($consolePath) { - $this->consolePath = trim($consolePath, " \t\n\r\0\x0B/"); - return $this; - } - - /** - * @return string - */ - public function getNamespace() { - return $this->namespace; - } - - /** - * @param string $namespace - * @return Configuration - */ - public function setNamespace($namespace) { - $this->namespace = preg_replace('/[\\\]{2,}/', '\\', trim($namespace, " \t\r\n\0\x0B\\")); - return $this; - } - - /** - * @return string - */ - public function getOutputDir() { - return $this->outputDir; - } - - /** - * @param string $outputDir - * @return Configuration - */ - public function setOutputDir($outputDir) { - $this->outputDir = $outputDir; - return $this; - } - - /** - * @param \MyENA\CloudStackClientGenerator\API $api - * @return string - */ - public function getEventForAPI(API $api): string { - return $this->eventTypeMap[$api->getName()] ?? ''; - } - - /** - * @return string + * @return \MyENA\CloudStackClientGenerator\Configuration\Environments */ - public function getCompiledAddress() { - if ('' === $this->compiledAddress) { - $this->compiledAddress = rtrim(sprintf( - '%s://%s%s/', - $this->getScheme(), - $this->getHost(), - 0 === $this->port ? '' : sprintf(':%d', $this->port) - ), - "/"); - } - - return $this->compiledAddress; - } - - /** - * @param \GuzzleHttp\ClientInterface $HttpClient - * @return $this - */ - public function setHttpClient(ClientInterface $HttpClient) { - $this->HttpClient = $HttpClient; - return $this; + public function getEnvironments(): Configuration\Environments { + return $this->environments; } /** - * @param string $query - * @return string - * @throws \Exception + * @return \MyENA\CloudStackClientGenerator\Configuration\OverloadedClasses */ - public function buildSignature($query) { - if ('' === $query) { - throw new \Exception(STRTOSIGN_EMPTY_MSG, STRTOSIGN_EMPTY); - } - - $hash = @hash_hmac('SHA1', strtolower($query), $this->getSecret(), true); - return urlencode(base64_encode($hash)); + public function getOverloadedClasses(): Configuration\OverloadedClasses { + return $this->overloadedClasses; } } diff --git a/src/Configuration/Environment.php b/src/Configuration/Environment.php new file mode 100644 index 0000000..a98d7d0 --- /dev/null +++ b/src/Configuration/Environment.php @@ -0,0 +1,451 @@ + true, + 'key' => true, + 'secret' => true, + 'scheme' => true, + 'host' => true, + 'port' => true, + 'apiPath' => true, + 'consolePath' => true, + 'namespace' => true, + 'out' => true, + ]; + + /** @var array */ + private static $logLevels = [ + LogLevel::DEBUG => true, + LogLevel::INFO => true, + LogLevel::NOTICE => true, + LogLevel::WARNING => true, + LogLevel::ERROR => true, + LogLevel::ALERT => true, + LogLevel::CRITICAL => true, + LogLevel::EMERGENCY => true, + ]; + + /** + * Environment constructor. + * + * TODO: clean this up a bit. + * + * @param array $config + */ + public function __construct(array $config = []) { + $clientClass = Client::class; + $clientConfig = []; + $loggerClass = class_exists('\\MyEna\\DefaultANSILogger', true) + ? '\\MyEna\\DefaultANSILogger' + : NullLogger::class; + $loggerLevel = LogLevel::INFO; + foreach ($config as $k => $v) { + if ('http_client' === $k) { + if (null === $v) { + continue; + } + if (!is_array($v)) { + throw new \DomainException(sprintf( + 'Key "http_client" expected to be array, %s seen', + gettype($v) + )); + } + if (isset($v['class'])) { + if (!class_exists($v['class'], true)) { + throw new \RuntimeException(sprintf('Specified HttpClient class "%s" not found', $v['class'])); + } + if (!isset(class_implements($v['class'])['GuzzleHttp\\ClientInterface'])) { + throw new \DomainException(sprintf( + 'Specified HttpClient class "%s" does not seem to implement \\GuzzleHttp\\ClientInterface', + $v['class'] + )); + } + $clientClass = $v['class']; + } + if (isset($v['config'])) { + if (!is_array($v['config'])) { + throw new \InvalidArgumentException(sprintf( + 'http_client property config must be array, %s seen.', + gettype($v['config']) + )); + } + $clientConfig = $v['config']; + } + continue; + } + + if ('logger' === $k) { + if (null === $v) { + continue; + } + if (!is_array($v)) { + throw new \DomainException(sprintf( + 'Key "logger" expected to be array, %s seen', + gettype($v) + )); + } + if (isset($v['class'])) { + if (!class_exists($v['class'], true)) { + throw new \RuntimeException(sprintf( + 'Specified Logger class "%s" not found', + $v['class'] + )); + } + if (!isset(class_implements($v['class'])['Psr\\Log\\LoggerInterface'])) { + throw new \InvalidArgumentException(sprintf( + 'Specified Logger class "%s" does not seem to implement \\Psr\\Log\\LoggerInterface', + $v['class'] + )); + } + $loggerClass = $v['class']; + } + if (isset($v['level'])) { + if (!isset(self::$logLevels[$v['level']])) { + throw new \OutOfBoundsException(sprintf( + 'Specified Logger level "%s" is not in valid range ["%s"]', + $v['level'], + implode('", "', self::$logLevels) + )); + } + $loggerLevel = $v['level']; + } + continue; + } + + if (!isset(self::$settableParams[$k])) { + throw new \DomainException(sprintf('"%s" is not a configurable value', $k)); + } + + if (false === strpos($k, '_')) { + $this->{'set'.ucfirst($k)}($v); + } else { + $this->{'set'.implode('', array_map('ucfirst', explode('_', $k)))}($v); + } + } + + $this->httpClient = new $clientClass($clientConfig); + $this->logger = new $loggerClass(); + if (method_exists($this->logger, 'setLevel')) { + $this->logger->setLevel($loggerLevel); + } else if (method_exists($this->logger, 'setLogLevel')) { + $this->logger->setLogLevel($loggerLevel); + } else if (method_exists($this->logger, 'setLoggerLevel')) { + $this->logger->setLoggerLevel($loggerLevel); + } else { + $this->logger->warning(sprintf( + 'Unable to find method by which to set log level with logger "%s"', + get_class($this->logger) + )); + } + } + + /** + * @param string $name + * @return void + */ + public function setName(string $name) { + $this->name = $name; + } + + /** + * @return string + */ + public function getName(): string { + return $this->name; + } + + /** + * @param string $key + * @return void + */ + public function setKey(string $key) { + $this->key = $key; + } + + /** + * @return string + */ + public function getKey(): string { + return $this->key; + } + + /** + * @param string $secret + * @return void + */ + public function setSecret(string $secret) { + $this->secret = $secret; + } + + /** + * @return string + */ + public function getSecret(): string { + return $this->secret; + } + + /** + * @param string $scheme + * @return void + */ + public function setScheme(string $scheme) { + $this->scheme = $scheme; + $this->compiledAddress = ''; + } + + /** + * @return string + */ + public function getScheme(): string { + return $this->scheme; + } + + /** + * @param string $host + * @return void + */ + public function setHost(string $host) { + $this->host = $host; + $this->compiledAddress = ''; + } + + /** + * @return string + */ + public function getHost(): string { + return $this->host; + } + + /** + * @param int $port + * @return void + */ + public function setPort(int $port) { + $this->port = $port; + $this->compiledAddress = ''; + } + + /** + * @return int + */ + public function getPort(): int { + return $this->port; + } + + /** + * @param string $apiPath + * @return void + */ + public function setAPIPath(string $apiPath) { + $this->apiPath = trim($apiPath, " \t\n\r\0\x0B/"); + } + + /** + * @return string + */ + public function getAPIPath(): string { + return $this->apiPath; + } + + /** + * @param string $consolePath + * @return void + */ + public function setConsolePath(string $consolePath) { + $this->consolePath = trim($consolePath, " \t\n\r\0\x0B/"); + } + + /** + * @return string + */ + public function getConsolePath(): string { + return $this->consolePath; + } + + /** + * @param string $namespace + * @return void + */ + public function setNamespace(string $namespace) { + if (!preg_match(self::ValidNamespaceRegex, $namespace)) { + throw new \InvalidArgumentException(sprintf( + 'Provided namespace "%s" violates "%s"', + $namespace, + self::ValidNamespaceRegex + )); + } + $this->namespace = $namespace; + } + + /** + * @return string + */ + public function getNamespace(): string { + return $this->namespace; + } + + /** + * @param string $out + * @return void + */ + public function setOut(string $out) { + $this->out = self::tryResolvePath($out); + } + + /** + * @return string + */ + public function getOut(): string { + return $this->out; + } + + /** + * @return \Psr\Log\LoggerInterface + */ + public function getLogger(): LoggerInterface { + return $this->logger; + } + + /** + * @return \GuzzleHttp\ClientInterface + */ + public function getHttpClient(): ClientInterface { + return $this->httpClient; + } + + /** + * @param \GuzzleHttp\ClientInterface $httpClient + */ + public function setHttpClient(ClientInterface $httpClient) { + $this->httpClient = $httpClient; + } + + /** + * @return string + */ + public function getCompiledAddress(): string { + if ('' === $this->compiledAddress) { + $this->compiledAddress = rtrim(sprintf( + '%s://%s%s/', + $this->getScheme(), + $this->getHost(), + 0 === $this->port ? '' : sprintf(':%d', $this->port) + ), + "/"); + } + + return $this->compiledAddress; + } + + /** + * @param string $query + * @return string + * @throws \Exception + */ + public function buildSignature(string $query): string { + if ('' === $query) { + throw new \Exception(STRTOSIGN_EMPTY_MSG, STRTOSIGN_EMPTY); + } + + $hash = @hash_hmac('SHA1', strtolower($query), $this->getSecret(), true); + return urlencode(base64_encode($hash)); + } + + /** + * @return array + */ + public function jsonSerialize() { + return [ + 'name' => $this->getName(), + 'scheme' => $this->getScheme(), + 'host' => $this->getHost(), + 'port' => $this->getPort(), + 'apiPath' => $this->getAPIPath(), + 'consolePath' => $this->getConsolePath(), + 'namespace' => $this->getNamespace(), + 'outputDir' => $this->getOut(), + ]; + } + + /** + * Will attempt to detect and expand a relative path. + * + * // TODO: This is probably a bad idea and I should stop being lazy. + * + * @param string $in + * @return string + */ + public static function tryResolvePath(string $in): string { + if (0 === strpos($in, './')) { + if ($rp = realpath(PHPCS_ROOT.'/'.substr($in, 2))) { + return $rp; + } + return PHPCS_ROOT.'/'.substr($in, 2); + } else if (0 !== strpos($in, '/')) { + if ($rp = realpath(PHPCS_ROOT.'/'.ltrim($in, "/"))) { + return $rp; + } + return PHPCS_ROOT.'/'.ltrim($in, "/"); + } else { + return $in; + } + } +} diff --git a/src/Configuration/Environments.php b/src/Configuration/Environments.php new file mode 100644 index 0000000..8d9d075 --- /dev/null +++ b/src/Configuration/Environments.php @@ -0,0 +1,127 @@ + $env) { + if (is_array($env)) { + $env = new Environment(['name' => $name] + $env); + } + $this->setEnvironment($env); + } + } + + /** + * @param \MyENA\CloudStackClientGenerator\Configuration\Environment $environment + * @return void + */ + public function setEnvironment(Environment $environment) { + $this->_storage[$environment->getName()] = $environment; + } + + /** + * @param string $name + * @return \MyENA\CloudStackClientGenerator\Configuration\Environment|null + */ + public function getEnvironment(string $name) { + return $this->_storage[$name] ?? null; + } + + /** + * @param string $name + */ + public function removeEnvironment(string $name) { + unset($this->_storage[$name]); + } + + /** + * @return mixed|\MyENA\CloudStackClientGenerator\Configuration\Environment|null + */ + public function first() { + return 0 < count($this->_storage) ? reset($this->_storage) : null; + } + + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) { + return isset($this->_storage[$offset]); + } + + /** + * @param string $offset + * @return \MyENA\CloudStackClientGenerator\Configuration\Environment|null + */ + public function offsetGet($offset) { + return $this->_storage[$offset] ?? null; + } + + /** + * @param mixed $offset ignored + * @param \MyENA\CloudStackClientGenerator\Configuration\Environment $value + */ + public function offsetSet($offset, $value) { + $this->setEnvironment($value); + } + + /** + * @param string $offset + */ + public function offsetUnset($offset) { + unset($this->_storage[$offset]); + } + + /** + * @return \MyENA\CloudStackClientGenerator\Configuration\Environment + */ + public function current() { + return current($this->_storage); + } + + public function next() { + next($this->_storage); + } + + /** + * @return string + */ + public function key() { + return key($this->_storage); + } + + /** + * @return bool + */ + public function valid() { + return key($this->_storage) !== null; + } + + public function rewind() { + reset($this->_storage); + } + + /** + * @return int + */ + public function count() { + return count($this->_storage); + } + + /** + * @return \MyENA\CloudStackClientGenerator\Configuration\Environment[] + */ + public function jsonSerialize() { + return $this->_storage; + } +} \ No newline at end of file diff --git a/src/Configuration/OverloadedClass.php b/src/Configuration/OverloadedClass.php new file mode 100644 index 0000000..6430b38 --- /dev/null +++ b/src/Configuration/OverloadedClass.php @@ -0,0 +1,47 @@ +name = $name; + $this->overload = $overload; + } + + /** + * @return string + */ + public function getName(): string { + return $this->name; + } + + /** + * @return string + */ + public function getOverload(): string { + return $this->overload; + } + + /** + * @return array + */ + public function jsonSerialize() { + return [ + 'name' => $this->getName(), + 'overload' => $this->getOverload(), + ]; + } +} \ No newline at end of file diff --git a/src/Configuration/OverloadedClasses.php b/src/Configuration/OverloadedClasses.php new file mode 100644 index 0000000..02e08a4 --- /dev/null +++ b/src/Configuration/OverloadedClasses.php @@ -0,0 +1,117 @@ +setOverloadedClass($class); + } + } + + /** + * @param \MyENA\CloudStackClientGenerator\Configuration\OverloadedClass $class + * @return void + */ + public function setOverloadedClass(OverloadedClass $class) { + $this->_classes[$class->getName()] = $class; + } + + /** + * @param string $name + * @return \MyENA\CloudStackClientGenerator\Configuration\OverloadedClass|null + */ + public function getOverloadedClass(string $name) { + return $this->_classes[$name] ?? null; + } + + /** + * @param string $name + * @return void + */ + public function removeOverloadedClass(string $name) { + unset($this->_classes[$name]); + } + + /** + * @return \MyENA\CloudStackClientGenerator\Configuration\OverloadedClass + */ + public function current() { + return current($this->_classes); + } + + public function next() { + next($this->_classes); + } + + /** + * @return string + */ + public function key() { + return key($this->_classes); + } + + /** + * @return bool + */ + public function valid() { + return key($this->_classes) !== null; + } + + public function rewind() { + reset($this->_classes); + } + + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) { + return isset($this->_classes[$offset]); + } + + /** + * @param string $offset + * @return \MyENA\CloudStackClientGenerator\Configuration\OverloadedClass|null + */ + public function offsetGet($offset) { + return $this->_classes[$offset] ?? null; + } + + /** + * @param mixed $offset ignored + * @param \MyENA\CloudStackClientGenerator\Configuration\OverloadedClass $value + */ + public function offsetSet($offset, $value) { + $this->setOverloadedClass($value); + } + + public function offsetUnset($offset) { + unset($this->_classes[$offset]); + } + + /** + * @return int + */ + public function count() { + return count($this->_classes); + } + + /** + * @return array + */ + public function jsonSerialize() { + return [ + 'classes' => $this->_classes, + ]; + } +} \ No newline at end of file diff --git a/src/Generator.php b/src/Generator.php index 917cd6d..509d274 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -2,6 +2,7 @@ use MyENA\CloudStackClientGenerator\API\ObjectVariable; use MyENA\CloudStackClientGenerator\API\Variable; +use MyENA\CloudStackClientGenerator\Configuration\Environment; /** * Class Generator @@ -11,9 +12,14 @@ class Generator { /** @var \MyENA\CloudStackClientGenerator\Configuration */ protected $config; + /** @var \MyENA\CloudStackClientGenerator\Configuration\Environment */ + protected $env; /** @var \MyENA\CloudStackClientGenerator\Client */ protected $client; + /** @var \Psr\Log\LoggerInterface */ + protected $log; + /** @var \Twig_Environment */ protected $twig; @@ -40,14 +46,22 @@ class Generator { /** @var string */ protected $requestDir; + /** @var array */ + protected $commandEventMap; + /** * Generator constructor. - * - * @param \MyENA\CloudStackClientGenerator\Configuration $configuration + * @param \MyENA\CloudStackClientGenerator\Configuration $config + * @param \MyENA\CloudStackClientGenerator\Configuration\Environment $environment */ - public function __construct(Configuration $configuration) { - $this->config = $configuration; - $this->client = new Client($configuration); + public function __construct(Configuration $config, Environment $environment) { + $this->config = $config; + $this->env = $environment; + $this->client = new Client($environment); + + $this->log = $environment->getLogger(); + + $this->commandEventMap = require __DIR__.'/../files/command_event_map.php'; $twigLoader = new \Twig_Loader_Filesystem(__DIR__.'/../templates'); $this->twig = @@ -62,160 +76,258 @@ function($in) { ['is_safe' => ['html']] ) ); + $map = $this->commandEventMap; + $this->twig->addFunction(new \Twig_Function( + 'command_event', + function(string $in) use ($map): string { + return $map[$in] ?? ''; + }, + ['is_safe' => ['html']] + )); + $rootNS = $environment->getNamespace(); + $this->twig->addFunction(new \Twig_function( + 'namespace_stmt', + function(string $in = '') use ($rootNS): string { + if ('' === $rootNS) { + if ('' === $in) { + return ''; + } + return " namespace {$in};"; + } + if ('' === $in) { + return " namespace {$rootNS};"; + } + return " namespace {$rootNS}\\{$in};"; + }, + ['is_safe' => ['html']] + )); + $this->twig->addFunction(new \Twig_Function( + 'namespace_path', + function(string $in = '', bool $prefix = false) use ($rootNS): string { + if ('' === $rootNS) { + if ('' === $in) { + return ''; + } + return $prefix ? "\\{$in}" : $in; + } + if ('' === $in) { + return $prefix ? "\\{$rootNS}" : $rootNS; + } + return $prefix ? "\\{$rootNS}\\{$in}" : "{$rootNS}\\{$in}"; + }, + ['is_safe' => ['html']] + )); + $now = new \DateTime(); + $this->twig->addFunction(new \Twig_Function( + 'now', + function(string $format = '') use ($now) : string { + if ('' === $format) { + $format = 'Y-m-d'; + } + return $now->format($format); + }, + ['is_safe' => ['html']] + )); - $this->srcDir = sprintf('%s/src', $this->config->getOutputDir()); - if (!is_dir($this->srcDir) && !mkdir($this->srcDir)) { - throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->srcDir)); - } - - $this->filesDir = sprintf('%s/files', $this->config->getOutputDir()); - if (!is_dir($this->filesDir) && !mkdir($this->filesDir)) { - throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->filesDir)); - } - + $this->srcDir = sprintf('%s/src', $this->env->getOut()); + $this->filesDir = sprintf('%s/files', $this->env->getOut()); $this->responseDir = sprintf('%s/CloudStackResponse', $this->srcDir); - if (!is_dir($this->responseDir) && !mkdir($this->responseDir)) { - throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->responseDir)); - } - $this->responseTypesDir = sprintf('%s/Types', $this->responseDir); - if (!is_dir($this->responseTypesDir) && !mkdir($this->responseTypesDir)) { - throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->responseTypesDir)); - } - $this->requestDir = sprintf('%s/CloudStackRequest', $this->srcDir); - if (!is_dir($this->requestDir) && !mkdir($this->requestDir)) { - throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->requestDir)); - } } /** * Execute generation of CloudStack API client + * + * @throws \Exception + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Twig_Error_Loader + * @throws \Twig_Error_Runtime + * @throws \Twig_Error_Syntax */ public function generate() { - $log = $this->config->getLogger(); + $this->log->info('Initializing directories...'); + $this->initializeDirectories(); - $log->info("Compiling APIs from {$this->config->getHost()}..."); + $this->log->info("Compiling APIs from {$this->env->getHost()}..."); $this->compileAPIs(); ksort($this->apis, SORT_NATURAL); - $log->info('Writing static templates...'); + $this->log->debug('Setting Twig globals'); + $this->twig->addGlobal('config', $this->config); + $this->twig->addGlobal('env', $this->env); + $this->twig->addGlobal('log', $this->log); + $this->twig->addGlobal('capabilities', $this->getCapabilities()); + + $this->log->info('Writing static templates...'); $this->writeOutStaticTemplates(); - $log->info('Writing Client class...'); + $this->log->info('Writing Client class...'); $this->writeOutClient(); - $log->info('Writing Request Models...'); + $this->log->info('Writing Request Models...'); $this->writeOutRequestModels(); - $log->info('Writing Shared Response Models...'); + $this->log->info('Writing Shared Response Models...'); $this->writeOutSharedResponseModels(); - $log->info('Writing Response Models...'); + $this->log->info('Writing Response Models...'); $this->writeOutResponseModels(); } + protected function cleanDirectory(string $dir) { + if (0 < ($cnt = count(($phpFiles = glob($dir.'/*.php'))))) { + $this->log->info(sprintf('Directory "%s" has "%d" php file(s), emptying...', $dir, $cnt)); + foreach ($phpFiles as $phpFile) { + if (!@unlink($phpFile)) { + $this->log->warning(sprintf('Unable to delete file "%s"', $phpFile)); + } + } + } else { + $this->log->debug(sprintf('Directory "%s" is already empty', $dir)); + } + } + + protected function initializeDirectories() { + if (!is_dir($this->srcDir) && !mkdir($this->srcDir)) { + throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->srcDir)); + } + if (!is_dir($this->filesDir) && !mkdir($this->filesDir)) { + throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->filesDir)); + } + if (!is_dir($this->responseDir) && !mkdir($this->responseDir)) { + throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->responseDir)); + } + if (!is_dir($this->responseTypesDir) && !mkdir($this->responseTypesDir)) { + throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->responseTypesDir)); + } + if (!is_dir($this->requestDir) && !mkdir($this->requestDir)) { + throw new \RuntimeException(sprintf('Unable to create directory "%s"', $this->requestDir)); + } + $this->cleanDirectory($this->srcDir); + $this->cleanDirectory($this->filesDir); + $this->cleanDirectory($this->responseDir); + $this->cleanDirectory($this->responseTypesDir); + $this->cleanDirectory($this->requestDir); + } + /** * @param string $file * @param string $data * @return bool|int */ protected function writeFile(string $file, string $data) { - $this->config->getLogger()->debug('Writing '.mb_strlen($data).' bytes to '.$file); + $this->log->debug('Writing '.mb_strlen($data).' bytes to '.$file); return file_put_contents($file, $data); } + /** + * @throws \Exception + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Twig_Error_Loader + * @throws \Twig_Error_Runtime + * @throws \Twig_Error_Syntax + */ protected function writeOutStaticTemplates() { - $args = ['config' => $this->config, 'capabilities' => $this->getCapabilities()]; - $this->writeFile( - $this->config->getOutputDir().'/LICENSE', + $this->env->getOut().'/LICENSE', file_get_contents(__DIR__.'/../LICENSE') ); $this->writeFile( - $this->config->getOutputDir().'/composer.json', - $this->twig->load('composer.json.twig')->render($args) + $this->env->getOut().'/composer.json', + $this->twig->load('composer.json.twig')->render() ); $this->writeFile( $this->srcDir.'/CloudStackConfiguration.php', - $this->twig->load('configuration.php.twig')->render($args) + $this->twig->load('configuration.php.twig')->render() ); $this->writeFile( $this->filesDir.'/constants.php', - $this->twig->load('constants.php.twig')->render($args) + $this->twig->load('constants.php.twig')->render() ); $this->writeFile( $this->srcDir.'/CloudStackEventTypes.php', - $this->twig->load('eventTypes.php.twig')->render($args) + $this->twig->load('eventTypes.php.twig')->render() ); $this->writeFile( $this->responseDir.'/AsyncJobStartResponse.php', - $this->twig->load('responses/asyncJobStart.php.twig')->render($args) + $this->twig->load('responses/asyncJobStart.php.twig')->render() ); $this->writeFile( $this->responseDir.'/AccessVmConsoleProxyResponse.php', - $this->twig->load('responses/accessVmConsoleProxy.php.twig')->render($args) + $this->twig->load('responses/accessVmConsoleProxy.php.twig')->render() ); $this->writeFile( $this->responseTypesDir.'/DateType.php', - $this->twig->load('responses/dateType.php.twig')->render($args) + $this->twig->load('responses/dateType.php.twig')->render() ); $this->writeFile( $this->srcDir.'/CloudStackHelpers.php', - $this->twig->load('helpers.php.twig')->render($args) + $this->twig->load('helpers.php.twig')->render() ); $this->writeFile( $this->requestDir.'/CloudStackRequestInterfaces.php', - $this->twig->load('requests/interfaces.php.twig')->render($args) + $this->twig->load('requests/interfaces.php.twig')->render() ); $this->writeFile( $this->requestDir.'/AccessVmConsoleProxyRequest.php', - $this->twig->load('requests/accessVmConsoleProxy.php.twig')->render($args) + $this->twig->load('requests/accessVmConsoleProxy.php.twig')->render() ); $this->writeFile( $this->srcDir.'/CloudStackExceptions.php', - $this->twig->load('exceptions.php.twig')->render($args) + $this->twig->load('exceptions.php.twig')->render() ); } + /** + * @throws \Exception + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Twig_Error_Loader + * @throws \Twig_Error_Runtime + * @throws \Twig_Error_Syntax + */ protected function writeOutClient() { $this->writeFile( $this->srcDir.'/CloudStackClient.php', - $this->twig->load('client.php.twig')->render([ - 'config' => $this->config, - 'capabilities' => $this->getCapabilities(), - 'apis' => $this->apis, - ]) + $this->twig->load('client.php.twig')->render(['apis' => $this->apis]) ); } + /** + * @throws \Exception + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Twig_Error_Loader + * @throws \Twig_Error_Runtime + * @throws \Twig_Error_Syntax + */ protected function writeOutRequestModels() { - $capabilities = $this->getCapabilities(); $template = $this->twig->load('requests/model.php.twig'); foreach ($this->apis as $api) { $className = $api->getRequestClassName(); $this->writeFile( $this->requestDir.'/'.$className.'.php', - $template->render([ - 'api' => $api, - 'config' => $this->config, - 'capabilities' => $capabilities, - ]) + $template->render(['api' => $api]) ); } } + /** + * @throws \Exception + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Twig_Error_Loader + * @throws \Twig_Error_Runtime + * @throws \Twig_Error_Syntax + */ protected function writeOutSharedResponseModels() { - $capabilities = $this->getCapabilities(); $template = $this->twig->load('responses/model.php.twig'); foreach ($this->sharedObjectMap as $name => $class) { @@ -223,17 +335,19 @@ protected function writeOutSharedResponseModels() { $className = $class->getClassName(); $this->writeFile( $this->responseDir.'/'.$className.'.php', - $template->render([ - 'obj' => $class, - 'config' => $this->config, - 'capabilities' => $capabilities, - ]) + $template->render(['obj' => $class]) ); } } + /** + * @throws \Exception + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Twig_Error_Loader + * @throws \Twig_Error_Runtime + * @throws \Twig_Error_Syntax + */ protected function writeOutResponseModels() { - $capabilities = $this->getCapabilities(); $template = $this->twig->load('responses/model.php.twig'); foreach ($this->apis as $name => $api) { @@ -242,11 +356,7 @@ protected function writeOutResponseModels() { $this->writeFile( $this->responseDir.'/'.$className.'.php', - $template->render([ - 'obj' => $response, - 'config' => $this->config, - 'capabilities' => $capabilities, - ]) + $template->render(['obj' => $response]) ); } } @@ -339,7 +449,7 @@ protected function parseParameters(API $api, array $params) { * @param array $response */ protected function parseResponse(API $api, array $response) { - $obj = new ObjectVariable($this->config->getNamespace()); + $obj = new ObjectVariable($this->env->getNamespace()); $obj->setName($api->getName()); $obj->setDescription($api->getDescription()); $obj->setSince($api->getSince()); @@ -377,7 +487,7 @@ protected function buildSharedResponseObject(\stdClass $def) { return $this->sharedObjectMap[$name]; } - $obj = new ObjectVariable($this->config->getNamespace()); + $obj = new ObjectVariable($this->env->getNamespace()); $obj->setName($name); $obj->setType($def->type); $obj->setDescription($def->type); @@ -392,6 +502,8 @@ protected function buildSharedResponseObject(\stdClass $def) { /** * @return \stdClass + * @throws \Exception + * @throws \GuzzleHttp\Exception\GuzzleException */ protected function getCapabilities() { if (!isset($this->capabilities)) { @@ -402,6 +514,10 @@ protected function getCapabilities() { return $this->capabilities; } + /** + * @throws \Exception + * @throws \GuzzleHttp\Exception\GuzzleException + */ protected function compileAPIs() { $data = $this->client->do('listApis')->listapisresponse; @@ -423,7 +539,13 @@ protected function compileAPIs() { $api->getParameters()->nameSort(); - $api->setEventType($this->config->getEventForAPI($api)); + if ($api->isAsync()) { + if (isset($this->commandEventMap[$api->getName()])) { + $api->setEventType($this->commandEventMap[$api->getName()]); + } else { + $this->log->warning(sprintf('No async event present in map for %s', $api->getName())); + } + } $this->apis[$api->getName()] = $api; } diff --git a/templates/client.php.twig b/templates/client.php.twig index 1e3f904..c308506 100644 --- a/templates/client.php.twig +++ b/templates/client.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} @@ -35,7 +35,7 @@ use Psr\Http\Message\ResponseInterface; /** * CloudStackClient class - * @package \{% if config.namespace != '' %}{{ config.namespace }}{% endif %} + * @package {{ namespace_path('', true) }} */ class CloudStackClient { @@ -58,7 +58,7 @@ class CloudStackClient { /** * @param string $vmId - * @return \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackResponse\AccessVmConsoleProxyResponse + * @return {{ namespace_path('CloudStackResponse\\AccessVmConsoleProxyResponse', true) }} */ public function accessVmConsoleProxy($vmId) { $response = $this->doRequest(new CloudStackRequest\AccessVmConsoleProxyRequest($vmId), false); @@ -79,10 +79,10 @@ class CloudStackClient { /** * Will wait until either $tries threshold is met or async job completes, successful or otherwise. * - * @param \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackResponse\AsyncJobStartResponse $startResponse + * @param {{ namespace_path('CloudStackResponse\\AsyncJobStartResponse', true) }} $startResponse * @param int $sleep * @param int $tries - * @return \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackResponse\QueryAsyncJobResultResponse|null + * @return {{ namespace_path('CloudStackResponse\\QueryAsyncJobResultResponse', true) }}|null * @throws \InvalidArgumentException */ public function waitForAsync(CloudStackResponse\AsyncJobStartResponse $startResponse, $sleep = 2, $tries = 120) { @@ -138,7 +138,7 @@ class CloudStackClient { /** * Execute Request against CloudStack - * @param \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackRequest\CloudStackRequestInterface + * @param {{ namespace_path('CloudStackRequest\\CloudStackRequestInterface', true) }} * @param bool $decode * @return mixed */ @@ -190,7 +190,7 @@ class CloudStackClient { } /** - * @param \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackRequest\CloudStackRequestInterface $request + * @param {{ namespace_path('CloudStackRequest\\CloudStackRequestInterface', true) }} $request * @return \GuzzleHttp\Psr7\Request */ protected function createPsr7Request(CloudStackRequest\CloudStackRequestInterface $request) { @@ -280,7 +280,7 @@ class CloudStackClient { } /** - * @param \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackRequest\CloudStackRequestInterface $request + * @param {{ namespace_path('CloudStackRequest\\CloudStackRequestInterface', true) }} $request * @return array */ protected function compileParameters(CloudStackRequest\CloudStackRequestInterface $request) { @@ -298,7 +298,7 @@ class CloudStackClient { } /** - * @param \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackRequest\CloudStackRequestInterface $request + * @param {{ namespace_path('CloudStackRequest\\CloudStackRequestInterface', true) }} $request * @return string */ protected function compileQuery(CloudStackRequest\CloudStackRequestInterface $request) { diff --git a/templates/composer.json.twig b/templates/composer.json.twig index 8a79e76..d8ac692 100644 --- a/templates/composer.json.twig +++ b/templates/composer.json.twig @@ -13,7 +13,7 @@ { "name": "myena/generated-cloudstack-client", "type": "library", - "description": "Generated CloudStack PHP Client built on {{ config.now.format('Y-m-d') }} against ACS {{ capabilities.capability.cloudstackversion }}", + "description": "Generated CloudStack PHP Client built on {{ now('Y-m-d') }} against ACS {{ capabilities.capability.cloudstackversion }}", "license": "MIT", "authors": [ { @@ -47,7 +47,7 @@ }, "autoload": { "psr-4": { - "{% if config.namespace != '' %}{{ config.namespace|replace({'\\': '\\\\'}) }}\\{% endif %}": "src/" + "{{ namespace_path('\\', true)|replace({'\\': '\\\\'}) }}": "src/" }, "files": [ "files/constants.php", diff --git a/templates/configuration.php.twig b/templates/configuration.php.twig index 3060ece..69c9e26 100644 --- a/templates/configuration.php.twig +++ b/templates/configuration.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} diff --git a/templates/constants.php.twig b/templates/constants.php.twig index b1adea1..e273e85 100644 --- a/templates/constants.php.twig +++ b/templates/constants.php.twig @@ -15,7 +15,7 @@ /* * This file was autogenerated as part of the CloudStack PHP Client. * - * Date Generated: {{ config.now.format('Y-m-d') }} + * Date Generated: {{ now('Y-m-d') }} * API Version: {{ capabilities.capability.cloudstackversion }} * * (c) Quentin Pleplé {{ ''|raw }} diff --git a/templates/eventTypes.php.twig b/templates/eventTypes.php.twig index 0d1c5e8..a1c8f0a 100644 --- a/templates/eventTypes.php.twig +++ b/templates/eventTypes.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} diff --git a/templates/exceptions.php.twig b/templates/exceptions.php.twig index 6ffb3aa..71e32fc 100644 --- a/templates/exceptions.php.twig +++ b/templates/exceptions.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} diff --git a/templates/helpers.php.twig b/templates/helpers.php.twig index a77f5be..838b586 100644 --- a/templates/helpers.php.twig +++ b/templates/helpers.php.twig @@ -10,12 +10,12 @@ This file is part of the CloudStack Client Generator. For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} diff --git a/templates/method.php.twig b/templates/method.php.twig index 24239cd..19c21d1 100644 --- a/templates/method.php.twig +++ b/templates/method.php.twig @@ -37,7 +37,7 @@ {# end optional arguments #} * } {% endif %} - * @return \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackResponse\{% if api.isAsync %}AsyncJobStartResponse{% else %}{{ api.response.className }}{% if api.list %}[]{% endif %}{% endif %} + * @return {{ namespace_path('CloudStackResponse', true) }}\{% if api.isAsync %}AsyncJobStartResponse{% else %}{{ api.response.className }}{% if api.list %}[]{% endif %}{% endif %} */ public function {{ api.name }}({# only define required paramaters in function #} diff --git a/templates/requests/accessVmConsoleProxy.php.twig b/templates/requests/accessVmConsoleProxy.php.twig index bf6f101..242f70c 100644 --- a/templates/requests/accessVmConsoleProxy.php.twig +++ b/templates/requests/accessVmConsoleProxy.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} @@ -30,7 +30,7 @@ /** * Class AccessVmConsoleProxyRequest - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackRequest + * @package {{ namespace_path('CloudStackRequest', true) }} */ class AccessVmConsoleProxyRequest implements CloudStackRequestInterface, CloudStackConsoleCommandRequest { @@ -84,7 +84,7 @@ class AccessVmConsoleProxyRequest implements CloudStackRequestInterface, CloudSt * @return string */ public function getCloudStackResponseModel() { - return '\{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackResponse\AccessVmConsoleProxyResponse'; + return '{{ namespace_path('CloudStackResponse\AccessVmConsoleProxyResponse', true) }}'; } /** diff --git a/templates/requests/interfaces.php.twig b/templates/requests/interfaces.php.twig index 55e70b8..8086e37 100644 --- a/templates/requests/interfaces.php.twig +++ b/templates/requests/interfaces.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} @@ -30,7 +30,7 @@ {# TODO: I do not like how much junk I'm storing in the models. Find cleaner way? #} /** * Interface CloudStackRequestInterface - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackRequestInterface + * @package {{ namespace_path('CloudStackRequestInterface', true) }} */ interface CloudStackRequestInterface extends \JsonSerializable { /** @@ -67,7 +67,7 @@ interface CloudStackRequestInterface extends \JsonSerializable { /** * Indicates this is an "api" command request model * Interface CloudStackApiCommandRequest - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackApiCommandRequest + * @package {{ namespace_path('CloudStackApiCommandRequest', true) }} */ interface CloudStackApiCommandRequest { @@ -76,7 +76,7 @@ interface CloudStackApiCommandRequest { /** * Indicates this is a "console" command request model * Interface CloudStackConsoleCommandRequest - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackConsoleCommandRequest + * @package {{ namespace_path('CloudStackConsoleCommandRequest', true) }} */ interface CloudStackConsoleCommandRequest { @@ -85,7 +85,7 @@ interface CloudStackConsoleCommandRequest { /** * Indicates this is a synchronous request * Interface CloudStackSynchronousRequest - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackConsoleCommandRequest + * @package {{ namespace_path('CloudStackConsoleCommandRequest', true) }} */ interface CloudStackSynchronousRequest { @@ -94,7 +94,7 @@ interface CloudStackSynchronousRequest { /** * Indicates this is an asynchronous request * Interface CloudStackAsynchronousRequest - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackAsynchronousRequest + * @package {{ namespace_path('CloudStackAsynchronousRequest', true) }} */ interface CloudStackAsynchronousRequest { /** @@ -106,7 +106,7 @@ interface CloudStackAsynchronousRequest { /** * Indicates this is a List request * Interface CloudStackListRequest - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackListRequest + * @package {{ namespace_path('CloudStackListRequest', true) }} */ interface CloudStackListRequest { diff --git a/templates/requests/model.php.twig b/templates/requests/model.php.twig index 571f3d4..ec06d28 100644 --- a/templates/requests/model.php.twig +++ b/templates/requests/model.php.twig @@ -22,12 +22,12 @@ {% endif %} {% endfor %} {% endspaceless %} -'|raw }} @@ -40,21 +40,21 @@ * file that was distributed with this source code. */ -{% if config.namespace != '' %}{% if api.isAsync and api.eventType != ''%} -use {{ config.namespace}}\CloudStackEventTypes; +{% if namespace_path() != '' %}{% if api.isAsync and api.eventType != ''%} +use {{ namespace_path('CloudStackEventTypes') }}; {% endif %}{% if hasMapParameter %} -use {{ config.namespace }}\CloudStackHelpers; +use {{ namespace_path('CloudStackHelpers') }}; {% endif %}{% endif %} /** * Class {{ api.requestClassname }} -{{ api.sinceTag(0) }} * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackRequest +{{ api.sinceTag(0) }} * @package {{ namespace_path('CloudStackRequest', true) }} * @SWG\Definition(definition="{{ api.requestClassname }}") */ class {{ api.requestClassname }} implements CloudStackRequestInterface, CloudStackApiCommandRequest{% if api.isList %}, CloudStackListRequest{% endif %}{% if api.isAsync %}, CloudStackAsynchronousRequest{% else %}, CloudStackSynchronousRequest{% endif %} { {% if api.isAsync %} - const EVENT_TYPE = {% if api.eventType == '' %}''{% else %}{% if config.namespace == '' %}\{% endif %}CloudStackEventTypes::{{ api.eventType }}{% endif %}; + const EVENT_TYPE = {% if api.eventType == '' %}''{% else %}CloudStackEventTypes::{{ api.eventType }}{% endif %}; {% endif %} const API_NAME = '{{ api.name }}'; {% if requiredParamLength > 0 or optionalParamLength > 0 %} @@ -95,7 +95,7 @@ class {{ api.requestClassname }} implements CloudStackRequestInterface, CloudSta /** * @param array $data - * @return \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackRequest\{{ api.requestClassname }} + * @return {{ namespace_path('CloudStackRequest\\' ~ api.requestClassname, true) }} */ public static function fromArray(array $data) { {% if requiredParamLength == 0 and optionalParamLength == 0 %} @@ -191,7 +191,7 @@ class {{ api.requestClassname }} implements CloudStackRequestInterface, CloudSta {% for param in api.parameters %} {% if 'map' == param.type %} if (isset($this->{{ param.name }}) && is_array($this->{{ param.name }}) && count($this->{{ param.name }}) > 0) { - foreach({% if config.namespace == '' %}\{% endif %}CloudStackHelpers::formatCSQueryArray($this->{{ param.name }}, '{{ param.name }}') as $k => $v) { + foreach(CloudStackHelpers::formatCSQueryArray($this->{{ param.name }}, '{{ param.name }}') as $k => $v) { $a[$k] = $v; } } diff --git a/templates/responses/accessVmConsoleProxy.php.twig b/templates/responses/accessVmConsoleProxy.php.twig index 289120a..f3f3004 100644 --- a/templates/responses/accessVmConsoleProxy.php.twig +++ b/templates/responses/accessVmConsoleProxy.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} @@ -30,7 +30,7 @@ /** * Class AccessVmConsoleProxyResponse - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackResponse + * @package {{ namespace_path('CloudStackResponse', true) }} */ class AccessVmConsoleProxyResponse { diff --git a/templates/responses/asyncJobStart.php.twig b/templates/responses/asyncJobStart.php.twig index c627359..64651cf 100644 --- a/templates/responses/asyncJobStart.php.twig +++ b/templates/responses/asyncJobStart.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} @@ -30,7 +30,7 @@ /** * Class AsyncJobStartResponse - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackResponse + * @package {{ namespace_path('CloudStackResponse', true) }} * @SWG\Definition(definition="AsyncJobStartResponse") */ class AsyncJobStartResponse { diff --git a/templates/responses/dateType.php.twig b/templates/responses/dateType.php.twig index 9b5cc82..e9f7261 100644 --- a/templates/responses/dateType.php.twig +++ b/templates/responses/dateType.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} @@ -30,7 +30,7 @@ /** * Class DateType - * @package {% if config.namespace != '' %}\{{ config.namespace }}\{% endif %}Types + * @package {{ namespace_path('Types', true) }} */ class DateType extends \DateTime implements \JsonSerializable { diff --git a/templates/responses/model.php.twig b/templates/responses/model.php.twig index 296a53f..fea7c4b 100644 --- a/templates/responses/model.php.twig +++ b/templates/responses/model.php.twig @@ -10,12 +10,12 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -'|raw }} @@ -30,7 +30,7 @@ /** * Class {{ obj.className }} - * @package \{% if config.namespace != '' %}{{ config.namespace }}\{% endif %}CloudStackResponse + * @package {{ namespace_path('CloudStackResponse', true) }} * @SWG\Definition(definition="{{ obj.className }}") */ class {{ obj.className }} {