From 58e6157f405b814553574314d4111194efa4d5cd Mon Sep 17 00:00:00 2001 From: Anthony Axenov Date: Sun, 4 Aug 2024 23:37:39 +0800 Subject: [PATCH] Some refactorings related to main Processor class --- .gitignore | 2 + README.md | 2 +- pm-convert | 17 +- src/ArgumentParser.php | 178 ++++++++++++ src/Collection.php | 34 ++- src/Converters/Abstract/AbstractRequest.php | 4 +- .../Postman20/Postman20Converter.php | 2 +- .../Postman21/Postman21Converter.php | 2 +- src/Enums/ArgumentNames.php | 25 ++ src/{ => Enums}/CollectionVersion.php | 2 +- src/{ => Enums}/HttpVersion.php | 2 +- src/Environment.php | 4 +- src/FileSystem.php | 16 +- src/Handler.php | 238 ++++++++++++++++ src/Processor.php | 261 +---------------- src/Settings.php | 262 +++++++++++++----- tests/AbstractRequestTest.php | 4 +- tests/pm-convert-settings.json | 10 + 18 files changed, 701 insertions(+), 364 deletions(-) create mode 100644 src/ArgumentParser.php create mode 100644 src/Enums/ArgumentNames.php rename src/{ => Enums}/CollectionVersion.php (84%) rename src/{ => Enums}/HttpVersion.php (92%) create mode 100644 src/Handler.php create mode 100644 tests/pm-convert-settings.json diff --git a/.gitignore b/.gitignore index 46b5631..38a6112 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /.idea /.vscode /vendor +/nbproject + .phpunit.result.cache .phpunit.cache diff --git a/README.md b/README.md index e80223e..ecf7edd 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Possible ARGUMENTS: -p, --preserve - do not delete OUTPUT_PATH (if exists) --dump - convert provided arguments into settings file in `pwd` -h, --help - show this help message and exit - -v, --version - show version info and exit + --version - show version info and exit If no ARGUMENTS passed then --help implied. If both -f and -d are specified then only unique set of files from both arguments will be converted. diff --git a/pm-convert b/pm-convert index 1546575..9f01ed2 100755 --- a/pm-convert +++ b/pm-convert @@ -2,10 +2,12 @@ handle(); -} catch (InvalidArgumentException $e) { - fwrite(STDERR, sprintf('ERROR: %s%s', $e->getMessage(), EOL)); - print(implode(EOL, Processor::usage())); - die(1); + $handler->init($argv); + $handler->start(); } catch (Exception $e) { fwrite(STDERR, sprintf('ERROR: %s%s', $e->getMessage(), EOL)); - die(1); + exit(1); } diff --git a/src/ArgumentParser.php b/src/ArgumentParser.php new file mode 100644 index 0000000..b79e11c --- /dev/null +++ b/src/ArgumentParser.php @@ -0,0 +1,178 @@ +raw = array_slice($argv, 1); + } + + /** + * Parses raw arguments + * + * @return array Settings according to settings file format + */ + public function parse(): array + { + foreach ($this->raw as $idx => $arg) { + switch ($arg) { + case '-c': + case '--config': + if (empty($this->raw[$idx + 1])) { + throw new InvalidArgumentException('a configuration file path is expected for -c (--config)'); + } + if (isset($this->parsed[AN::Config])) { + printf( + "INFO: Config file is already set to '%s' and will be overwritten to '%s'", + $this->parsed[AN::Config], + $this->raw[$idx + 1], + ); + } + $this->parsed[AN::Config] = $this->raw[$idx + 1]; + break; + + case '-d': + case '--dir': + if (empty($this->raw[$idx + 1])) { + throw new InvalidArgumentException('a directory path is expected for -d (--dir)'); + } + $this->parsed[AN::Directories][] = $this->raw[$idx + 1]; + break; + + case '-f': + case '--file': + if (empty($this->raw[$idx + 1])) { + throw new InvalidArgumentException('a directory path is expected for -f (--file)'); + } + $this->parsed[AN::Files][] = $this->raw[$idx + 1]; + break; + + case '-e': + case '--env': + if (empty($this->raw[$idx + 1])) { + throw new InvalidArgumentException('an environment file path is expected for -e (--env)'); + } + $this->parsed[AN::Environment][] = $this->raw[$idx + 1]; + break; + + case '-o': + case '--output': + if (empty($this->raw[$idx + 1])) { + throw new InvalidArgumentException('an output path is expected for -o (--output)'); + } + $this->parsed[AN::Output][] = $this->raw[$idx + 1]; + break; + + case '-p': + case '--preserve': + $this->parsed[AN::PreserveOutput] = true; + break; + + case '--http': + $this->parsed[AN::Formats][] = ConvertFormat::Http; + break; + + case '--curl': + $this->parsed[AN::Formats][] = ConvertFormat::Curl; + break; + + case '--wget': + $this->parsed[AN::Formats][] = ConvertFormat::Wget; + break; + + case '--v2.0': + $this->parsed[AN::Formats][] = ConvertFormat::Postman20; + break; + + case '--v2.1': + $this->parsed[AN::Formats][] = ConvertFormat::Postman21; + break; + + case '-a': + case '--all': + foreach (ConvertFormat::cases() as $format) { + $this->parsed[AN::Formats][] = $format; + } + break; + + case '--var': + $definition = trim($this->raw[$idx + 1]); + $name = strtok($definition, '='); // take first part before equal sign as var name + $value = strtok(''); // take the rest of argument as var value + if (isset($this->parsed[AN::Vars][$name])) { + printf( + "INFO: Variable '%s' is already set to '%s' and will be overwritten to '%s'", + $name, + $this->parsed[AN::Vars][$name], + $value, + ); + } + $this->parsed[AN::Vars][$name] = $value; + break; + + case '--dev': + $this->parsed[AN::DevMode] = true; + break; + + case '-v': + case '--verbose': + $this->parsed[AN::Verbose] = true; + break; + + case '--dump': + $this->parsed[AN::Dump] = true; + break; + + case '--version': + $this->parsed[AN::Version] = true; + break; + + case '-h': + case '--help': + $this->parsed[AN::Help] = true; + break; + } + } + + foreach ([AN::Directories, AN::Files, AN::Formats] as $field) { + if (!empty($this->parsed[$field])) { + $this->parsed[$field] = array_unique($this->parsed[$field] ?? []); + } + } + + return $this->parsed; + } + + /** + * Returns parsed arguments (if set) or parses raw ones + * + * @return array + */ + public function parsed(): array + { + return $this->parsed ??= $this->parse(); + } +} diff --git a/src/Collection.php b/src/Collection.php index 1428715..d9c5319 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -7,6 +7,7 @@ namespace PmConverter; use Exception; use Generator; use JsonException; +use PmConverter\Enums\CollectionVersion; use Stringable; /** @@ -111,26 +112,23 @@ class Collection implements Stringable */ public static function detectFileVersion(string $filepath): CollectionVersion { - $handle = fopen($filepath, 'r'); - if ($handle === false) { - throw new Exception("Cannot open file for reading: $filepath"); + $content = file_get_contents($filepath); + + if ($content === false) { + throw new Exception("cannot read file: $filepath"); } - $content = ''; - // Postman collection files may be HUGE and I don't need to parse - // them here to find value .info.schema field because normally it - // is stored at the beginning of a file, so if it's not then this - // is a user problem, not mine. - while (\mb_strlen($content) <= 2048) { - $content .= fgets($handle, 50); - if (str_contains($content, 'https://schema.getpostman.com/json/collection')) { - if (str_contains($content, '/v2.0.')) { - return CollectionVersion::Version20; - } - if (str_contains($content, '/v2.1.')) { - return CollectionVersion::Version21; - } - } + + $json = json_decode($content, true); + $schema = $json['info']['schema'] ?? ''; + + if (str_ends_with($schema, 'v2.0.0/collection.json')) { + return CollectionVersion::Version20; } + + if (str_ends_with($schema, 'v2.1.0/collection.json')) { + return CollectionVersion::Version21; + } + return CollectionVersion::Unknown; } diff --git a/src/Converters/Abstract/AbstractRequest.php b/src/Converters/Abstract/AbstractRequest.php index ea177cb..49259e8 100644 --- a/src/Converters/Abstract/AbstractRequest.php +++ b/src/Converters/Abstract/AbstractRequest.php @@ -4,11 +4,11 @@ declare(strict_types=1); namespace PmConverter\Converters\Abstract; -use PmConverter\CollectionVersion; use PmConverter\Converters\RequestContract; +use PmConverter\Enums\CollectionVersion; +use PmConverter\Enums\HttpVersion; use PmConverter\Exceptions\EmptyHttpVerbException; use PmConverter\Exceptions\InvalidHttpVersionException; -use PmConverter\HttpVersion; use Stringable; /** diff --git a/src/Converters/Postman20/Postman20Converter.php b/src/Converters/Postman20/Postman20Converter.php index 4b34e48..e62dae5 100644 --- a/src/Converters/Postman20/Postman20Converter.php +++ b/src/Converters/Postman20/Postman20Converter.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace PmConverter\Converters\Postman20; use PmConverter\Collection; -use PmConverter\CollectionVersion; use PmConverter\Converters\Abstract\AbstractConverter; +use PmConverter\Enums\CollectionVersion; use PmConverter\Exceptions\CannotCreateDirectoryException; use PmConverter\Exceptions\DirectoryIsNotWriteableException; use PmConverter\FileSystem; diff --git a/src/Converters/Postman21/Postman21Converter.php b/src/Converters/Postman21/Postman21Converter.php index 3ae59b9..88a22b6 100644 --- a/src/Converters/Postman21/Postman21Converter.php +++ b/src/Converters/Postman21/Postman21Converter.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace PmConverter\Converters\Postman21; use PmConverter\Collection; -use PmConverter\CollectionVersion; use PmConverter\Converters\Abstract\AbstractConverter; +use PmConverter\Enums\CollectionVersion; use PmConverter\Exceptions\CannotCreateDirectoryException; use PmConverter\Exceptions\DirectoryIsNotWriteableException; use PmConverter\FileSystem; diff --git a/src/Enums/ArgumentNames.php b/src/Enums/ArgumentNames.php new file mode 100644 index 0000000..8595643 --- /dev/null +++ b/src/Enums/ArgumentNames.php @@ -0,0 +1,25 @@ +arguments = (new ArgumentParser($argv))->parsed(); + + if (!empty($this->arguments[AN::Help])) { + self::printHelp(); + exit; + } + + if (!empty($this->arguments[AN::Version])) { + self::printVersion(); + exit; + } + + $this->settings = new Settings(); + $this->settings->loadFromFile($this->arguments[AN::Config] ?? null); + $this->settings->override($this->arguments); + + if (empty($this->settings->collectionPaths())) { + throw new \Exception('at least 1 collection file must be defined'); + } + + if (!empty($arguments[AN::Dump])) { + $this->handleSettingsDump(); + } + + $this->env = Environment::instance() + ->readFromFile($this->settings->envFilepath()) + ->setCustomVars($this->settings->vars()); + } + + /** + * Starts convertions + * + * @return void + * @throws Exceptions\CannotCreateDirectoryException + * @throws Exceptions\DirectoryIsNotReadableException + * @throws Exceptions\DirectoryIsNotWriteableException + * @throws Exceptions\DirectoryNotExistsException + * @throws Exceptions\IncorrectSettingsFileException + * @throws JsonException + */ + public function start(): void + { + $this->processor = new Processor($this->settings, $this->env); + $this->processor->start(); + } + + /** + * Handles settings file saving when requested by --dump + * + * @return never + */ + protected function handleSettingsDump(): never + { + $answer = 'o'; + + if ($this->settings->fileExists()) { + echo 'Settings file already exists: ' . $this->settings->filePath() . EOL; + echo 'Do you want to (o)verwrite it, (b)ackup it and create new one or (c)ancel (default)?' . EOL; + $answer = strtolower(trim(readline('> '))); + } + + if (!in_array($answer, ['o', 'b'])) { + die('Current settings file has not been changed' . EOL); + } + + if ($answer === 'b') { + $filepath = $this->settings->backup(); + printf("Settings file has been backed up to file:%s\t%s%s", EOL, $filepath, EOL); + } + + $this->settings->dump(); + printf("Arguments has been converted into settings file:%s\t%s%s", EOL, $this->settings->filePath(), EOL); + die('Review and edit it if needed.' . EOL); + } + + /** + * Returns usage help strings + * + * @return array + */ + protected static function help(): array + { + return array_merge(self::version(), [ + 'Usage:', + "\t./pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]", + "\tphp pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]", + "\tcomposer pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]", + "\t./vendor/bin/pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]", + '', + 'Possible ARGUMENTS:', + "\t-f, --file - a PATH to a single collection file to convert from", + "\t-d, --dir - a PATH to a directory with collections to convert from", + "\t-o, --output - a directory OUTPUT_PATH to put results in", + "\t-e, --env - use environment file with variables to replace in requests", + "\t--var \"NAME=VALUE\" - force replace specified env variable called NAME with custom VALUE", + "\t-p, --preserve - do not delete OUTPUT_PATH (if exists)", + "\t --dump - convert provided arguments into settings file in `pwd", + "\t-h, --help - show this help message and exit", + "\t-v, --version - show version info and exit", + '', + 'If no ARGUMENTS passed then --help implied.', + 'If both -f and -d are specified then only unique set of files from both arguments will be converted.', + '-f or -d are required to be specified at least once, but each may be specified multiple times.', + 'PATH must be a valid path to readable json-file or directory.', + 'OUTPUT_PATH must be a valid path to writeable directory.', + 'If -o or -e was specified several times then only last one will be used.', + '', + 'Possible FORMATS:', + "\t--http - generate raw *.http files (default)", + "\t--curl - generate shell scripts with curl command", + "\t--wget - generate shell scripts with wget command", + "\t--v2.0 - convert from Postman Collection Schema v2.1 into v2.0", + "\t--v2.1 - convert from Postman Collection Schema v2.0 into v2.1", + "\t-a, --all - convert to all of formats listed above", + '', + 'If no FORMATS specified then --http implied.', + 'Any of FORMATS can be specified at the same time or replaced by --all.', + '', + 'Example:', + " ./pm-convert \ ", + " -f ~/dir1/first.postman_collection.json \ ", + " --directory ~/team \ ", + " --file ~/dir2/second.postman_collection.json \ ", + " --env ~/localhost.postman_environment.json \ ", + " -d ~/personal \ ", + " --var \"myvar=some value\" \ ", + " -o ~/postman_export \ ", + " --all", + "", + ], self::copyright()); + } + + /** + * Prints usage help message in stdout + * + * @return void + */ + public static function printHelp(): void + { + self::printArray(self::help()); + } + + /** + * Returns version strings + * + * @return string[] + */ + protected static function version(): array + { + return ['Postman collection converter v' . PM_VERSION, '']; + } + + /** + * Prints version message in stdout + * + * @return void + */ + public static function printVersion(): void + { + self::printArray(self::version()); + } + + /** + * Returns copyright strings + * + * @return string[] + */ + protected static function copyright(): array + { + return [ + 'Anthony Axenov (c) 2023 - ' . (int)date('Y') . ', MIT license', + 'https://git.axenov.dev/anthony/pm-convert', + '', + ]; + } + + /** + * Prints copyright message in stdout + * + * @return void + */ + public static function printCopyright(): void + { + self::printArray(self::copyright()); + } + + /** + * Prints an arrays of string to stdout + * + * @param ...$strings + * @return void + */ + protected static function printArray(...$strings): void + { + fwrite(STDOUT, implode("\n", array_merge(...$strings))); + } +} diff --git a/src/Processor.php b/src/Processor.php index 914102c..07587fb 100644 --- a/src/Processor.php +++ b/src/Processor.php @@ -6,12 +6,9 @@ namespace PmConverter; use Exception; use Generator; -use InvalidArgumentException; -use JetBrains\PhpStorm\NoReturn; use JsonException; use PmConverter\Converters\Abstract\AbstractConverter; use PmConverter\Converters\ConverterContract; -use PmConverter\Converters\ConvertFormat; use PmConverter\Exceptions\CannotCreateDirectoryException; use PmConverter\Exceptions\DirectoryIsNotReadableException; use PmConverter\Exceptions\DirectoryIsNotWriteableException; @@ -19,15 +16,10 @@ use PmConverter\Exceptions\DirectoryNotExistsException; use PmConverter\Exceptions\IncorrectSettingsFileException; /** - * Main class + * Processor class */ class Processor { - /** - * Converter version - */ - public const VERSION = '1.6.1'; - /** * @var int Initial timestamp */ @@ -38,150 +30,23 @@ class Processor */ protected readonly int $initRam; - /** - * @var Settings Settings (lol) - */ - protected Settings $settings; - /** * @var ConverterContract[] Converters will be used for conversion according to chosen formats */ protected array $converters = []; - /** - * @var bool Do we need to save settings file and exit or not? - */ - protected bool $needDumpSettings = false; - - /** - * @var Environment - */ - public Environment $env; - /** * Constructor * - * @param array $argv Arguments came from cli - * @throws IncorrectSettingsFileException - * @throws JsonException + * @param Settings $settings Settings (lol) + * @param Environment $env Environment */ - public function __construct(protected readonly array $argv) - { + public function __construct( + protected Settings $settings, + protected Environment $env, + ) { $this->initTime = hrtime(true); $this->initRam = memory_get_usage(true); - $this->settings = Settings::init(); - $this->env = Environment::instance() - ->readFromFile($this->settings->envFilepath()) - ->setCustomVars($this->settings->vars()); - $this->parseArgs(); - $this->needDumpSettings && $this->dumpSettingsFile(); - } - - /** - * Parses an array of arguments came from cli - * - * @return void - * @throws JsonException - */ - protected function parseArgs(): void - { - $arguments = array_slice($this->argv, 1); - $needHelp = count($arguments) === 0 && !$this->settings::fileExists(); - foreach ($arguments as $idx => $arg) { - switch ($arg) { - case '-f': - case '--file': - $this->settings->addFilePath($this->argv[$idx + 1]); - break; - - case '-o': - case '--output': - if (empty($this->argv[$idx + 1])) { - throw new InvalidArgumentException('-o is required'); - } - $this->settings->setOutputPath($this->argv[$idx + 1]); - break; - - case '-d': - case '--dir': - if (empty($this->argv[$idx + 1])) { - throw new InvalidArgumentException('a directory path is expected for -d (--dir)'); - } - $this->settings->addDirPath($this->argv[$idx + 1]); - break; - - case '-e': - case '--env': - $this->settings->setEnvFilepath($this->argv[$idx + 1]); - break; - - case '-p': - case '--preserve': - $this->settings->setPreserveOutput(true); - break; - - case '--http': - $this->settings->addFormat(ConvertFormat::Http); - break; - - case '--curl': - $this->settings->addFormat(ConvertFormat::Curl); - break; - - case '--wget': - $this->settings->addFormat(ConvertFormat::Wget); - break; - - case '--v2.0': - $this->settings->addFormat(ConvertFormat::Postman20); - break; - - case '--v2.1': - $this->settings->addFormat(ConvertFormat::Postman21); - break; - - case '-a': - case '--all': - foreach (ConvertFormat::cases() as $format) { - $this->settings->addFormat($format); - } - break; - - case '--var': - //TODO split by first equal sign - $this->env->setCustomVar(...explode('=', trim($this->argv[$idx + 1]))); - break; - - case '--dev': - $this->settings->setDevMode(true); - break; - - case '--dump': - $this->needDumpSettings = true; - break; - - case '-v': - case '--version': - die(implode(EOL, $this->version()) . EOL); - - case '-h': - case '--help': - $needHelp = true; - break; - } - } - if ($needHelp) { - die(implode(EOL, $this->usage()) . EOL); - } - if (empty($this->settings->collectionPaths())) { - throw new InvalidArgumentException('there are no collections to convert'); - } - if (empty($this->settings->outputPath())) { - throw new InvalidArgumentException('-o is required'); - } - if (empty($this->settings->formats())) { - $this->settings->addFormat(ConvertFormat::Http); - } } /** @@ -193,41 +58,14 @@ class Processor * @throws DirectoryIsNotWriteableException * @throws DirectoryNotExistsException * @throws JsonException - * @throws IncorrectSettingsFileException */ - public function handle(): void + public function start(): void { $this->prepareOutputDirectory(); $this->initConverters(); $this->convert(); } - /** - * Writes all settings into file if --dump provided - * - * @return never - */ - #[NoReturn] - protected function dumpSettingsFile(): never - { - $answer = 'o'; - if ($this->settings::fileExists()) { - echo 'Settings file already exists: ' . $this->settings::filepath() . EOL; - echo 'Do you want to (o)verwrite it, (b)ackup it and create new one or (c)ancel (default)?' . EOL; - $answer = strtolower(trim(readline('> '))); - } - if (!in_array($answer, ['o', 'b'])) { - die('Current settings file has not been changed' . EOL); - } - if ($answer === 'b') { - $filepath = $this->settings->backup(); - printf("Settings file has been backed up to file:%s\t%s%s", EOL, $filepath, EOL); - } - $this->settings->dump($this->env->customVars()); - printf("Arguments has been converted into settings file:%s\t%s%s", EOL, $this->settings::filepath(), EOL); - die('Review and edit it if needed.' . EOL); - } - /** * Initializes output directory * @@ -264,7 +102,7 @@ class Processor * @return Generator * @throws JsonException */ - protected function newCollection(): Generator + protected function nextCollection(): Generator { foreach ($this->settings->collectionPaths() as $collectionPath) { yield Collection::fromFile($collectionPath); @@ -280,9 +118,7 @@ class Processor { $count = count($this->settings->collectionPaths()); $current = $success = 0; - $collection = null; - print(implode(EOL, array_merge($this->version(), $this->copyright())) . EOL . EOL); - foreach ($this->newCollection() as $collection) { + foreach ($this->nextCollection() as $collection) { ++$current; printf("Converting '%s' (%d/%d):%s", $collection->name(), $current, $count, EOL); foreach ($this->converters as $type => $converter) { @@ -297,7 +133,7 @@ class Processor } catch (Exception $e) { printf(' ERROR %s: %s%s', $e->getCode(), $e->getMessage(), EOL); if ($this->settings->isDevMode()) { - array_map(static fn ($line) => printf(' %s%s', $line, EOL), $e->getTrace()); + array_map(static fn (string $line) => printf(' %s%s', $line, EOL), $e->getTrace()); } } } @@ -326,79 +162,4 @@ class Processor $ram = (memory_get_peak_usage(true) - $this->initRam) / 1024 / 1024; printf("Converted %d/%d in %.2f $timeFmt using up to %.2f MiB RAM%s", $success, $count, $time, $ram, EOL); } - - /** - * @return string[] - */ - public static function version(): array - { - return ['Postman collection converter v' . self::VERSION]; - } - - /** - * @return string[] - */ - public static function copyright(): array - { - $years = ($year = (int)date('Y')) > 2023 ? "2023 - $year" : $year; - return [ - "Anthony Axenov (c) $years, MIT license", - 'https://git.axenov.dev/anthony/pm-convert' - ]; - } - - /** - * @return array - */ - public static function usage(): array - { - return array_merge(static::version(), [ - 'Usage:', - "\t./pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]", - "\tphp pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]", - "\tcomposer pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]", - "\t./vendor/bin/pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]", - '', - 'Possible ARGUMENTS:', - "\t-f, --file - a PATH to a single collection file to convert from", - "\t-d, --dir - a PATH to a directory with collections to convert from", - "\t-o, --output - a directory OUTPUT_PATH to put results in", - "\t-e, --env - use environment file with variables to replace in requests", - "\t--var \"NAME=VALUE\" - force replace specified env variable called NAME with custom VALUE", - "\t-p, --preserve - do not delete OUTPUT_PATH (if exists)", - "\t --dump - convert provided arguments into settings file in `pwd", - "\t-h, --help - show this help message and exit", - "\t-v, --version - show version info and exit", - '', - 'If no ARGUMENTS passed then --help implied.', - 'If both -f and -d are specified then only unique set of files from both arguments will be converted.', - '-f or -d are required to be specified at least once, but each may be specified multiple times.', - 'PATH must be a valid path to readable json-file or directory.', - 'OUTPUT_PATH must be a valid path to writeable directory.', - 'If -o or -e was specified several times then only last one will be used.', - '', - 'Possible FORMATS:', - "\t--http - generate raw *.http files (default)", - "\t--curl - generate shell scripts with curl command", - "\t--wget - generate shell scripts with wget command", - "\t--v2.0 - convert from Postman Collection Schema v2.1 into v2.0", - "\t--v2.1 - convert from Postman Collection Schema v2.0 into v2.1", - "\t-a, --all - convert to all of formats listed above", - '', - 'If no FORMATS specified then --http implied.', - 'Any of FORMATS can be specified at the same time or replaced by --all.', - '', - 'Example:', - " ./pm-convert \ ", - " -f ~/dir1/first.postman_collection.json \ ", - " --directory ~/team \ ", - " --file ~/dir2/second.postman_collection.json \ ", - " --env ~/localhost.postman_environment.json \ ", - " -d ~/personal \ ", - " --var \"myvar=some value\" \ ", - " -o ~/postman_export \ ", - " --all", - "", - ], static::copyright()); - } } diff --git a/src/Settings.php b/src/Settings.php index 4b65aff..6b6c857 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -4,10 +4,11 @@ declare(strict_types=1); namespace PmConverter; +use Exception; use InvalidArgumentException; use JsonException; use PmConverter\Converters\ConvertFormat; -use PmConverter\Exceptions\IncorrectSettingsFileException; +use PmConverter\Enums\ArgumentNames as AN; /** * Class responsible for settings storage and dumping @@ -15,9 +16,9 @@ use PmConverter\Exceptions\IncorrectSettingsFileException; class Settings { /** - * @var string Full path to settings file + * @var string|null Full path to settings file */ - protected static string $filepath; + protected ?string $filePath = null; /** * @var bool Flag to output some debug-specific messages @@ -27,7 +28,7 @@ class Settings /** * @var string[] Paths to collection directories */ - protected array $directories = []; + protected array $dirPaths = []; /** * @var string[] Paths to collection files @@ -35,14 +36,14 @@ class Settings protected array $collectionPaths = []; /** - * @var string Output path where to put results in + * @var string|null Output path where to put results in */ - protected string $outputPath = ''; + protected ?string $outputPath; /** * @var bool Flag to remove output directories or not before conversion started */ - protected bool $preserveOutput = false; + protected bool $preserveOutput; /** * @var string[] Additional variables @@ -55,36 +56,138 @@ class Settings protected array $formats = []; /** - * @var string Path to environment file + * @var string|null Path to environment file */ - protected string $envFilepath = ''; + protected ?string $envFilePath = null; /** - * @return bool + * @throws JsonException */ - public static function fileExists(): bool + public function __construct() { - return file_exists(self::$filepath); + $this->loadFromDefaults(); } /** - * @return self - * @throws IncorrectSettingsFileException + * Loads settings from file + * + * @param string|null $filePath + * @return void + * @throws Exception + */ + public function loadFromFile(?string $filePath = null): void + { + if (is_null($filePath)) { + $filePath = sprintf('%s%spm-convert-settings.json', $_SERVER['PWD'], DS); + } + + $filePath = trim($filePath); + + if (!file_exists($filePath)) { + throw new Exception("file does not exist: $filePath"); + } + + if (!is_file($filePath)) { + throw new Exception("not a file: $filePath"); + } + + if (!is_readable($filePath)) { + throw new Exception("file is not readable: $filePath"); + } + + $content = file_get_contents($filePath); + $settings = json_decode($content ?: '{}', true, JSON_THROW_ON_ERROR); + + $this->setFromArray($settings); + $this->filePath = $filePath; + } + + /** + * Rewrites some defined settings with new values + * + * @param array $settings + * @return void * @throws JsonException */ - public static function init(): self + public function override(array $settings): void { - $content = '{}'; - self::$filepath = sprintf('%s%spm-convert-settings.json', $_SERVER['PWD'], DS); - if (self::fileExists()) { - $content = trim(file_get_contents(self::$filepath)); + $settings = array_replace_recursive($this->__serialize(), $settings); + $this->setFromArray($settings); + } + + /** + * Loads settings with default values + * + * @return void + * @throws JsonException + */ + public function loadFromDefaults(): void + { + $this->setFromArray(self::defaults()); + } + + /** + * Returns default settings values + * + * @return array + */ + public static function defaults(?string $key = null): mixed + { + $values = [ + AN::Config => null, + AN::Directories => [], + AN::Files => [], + AN::Environment => null, + AN::Output => null, + AN::PreserveOutput => false, + AN::Formats => ['http'], + AN::Vars => [], + AN::DevMode => false, + AN::Verbose => false, + ]; + + return $key ? $values[$key] : $values; + } + + /** + * Set settings from array + * + * @param array $settings + * @return void + * @throws JsonException + */ + protected function setFromArray(array $settings): void + { + foreach ($settings[AN::Directories] ?? self::defaults(AN::Directories) as $path) { + $this->addDirPath($path); } - try { - $settings = json_decode($content ?: '{}', flags: JSON_THROW_ON_ERROR); - } catch (JsonException $e) { - throw new IncorrectSettingsFileException('Incorrect settings file: ' . $e->getMessage(), $e->getCode()); + + foreach ($settings[AN::Files] ?? self::defaults(AN::Files) ?? [] as $path) { + $this->addFilePath($path); } - return new self($settings); + + $this->setEnvFilePath($settings[AN::Environment] ?? self::defaults(AN::Environment)); + $this->setOutputPath($settings[AN::Output] ?? self::defaults(AN::Output)); + $this->setPreserveOutput($settings[AN::PreserveOutput] ?? self::defaults(AN::PreserveOutput)); + + foreach ($settings[AN::Formats] ?? self::defaults(AN::Formats) as $format) { + $this->addFormat(ConvertFormat::fromArg($format)); + } + + $this->vars = $settings[AN::Vars] ?? self::defaults(AN::Vars); + $this->setDevMode($settings[AN::DevMode] ?? self::defaults(AN::DevMode)); + } + + /** + * Checks wether settings file exists or not + * + * @return bool + */ + public function fileExists(): bool + { + return is_file($this->filePath) + && is_readable($this->filePath) + && is_writable($this->filePath); } /** @@ -92,77 +195,65 @@ class Settings * * @return string */ - public static function filepath(): string + public function filePath(): string { - return self::$filepath; - } - - /** - * @param object $settings - * @throws JsonException - */ - protected function __construct(object $settings) - { - foreach ($settings->directories ?? [] as $path) { - $this->addDirPath($path); - } - foreach ($settings->files ?? [] as $path) { - $this->addFilePath($path); - } - $this->setDevMode(!empty($settings->devMode)); - $this->setPreserveOutput(!empty($settings->preserveOutput)); - isset($settings->environment) && $this->setEnvFilepath($settings->environment); - isset($settings->output) && $this->setOutputPath($settings->output); - foreach ($settings->formats ?? [] as $format) { - $this->addFormat(ConvertFormat::fromArg($format)); - } - foreach ($settings->vars ?? [] as $name => $value) { - $this->vars[$name] = $value; - } + return $this->filePath; } /** + * Adds directory path into current settings array and fills files array with its content + * * @param string $path * @return void * @throws JsonException */ public function addDirPath(string $path): void { - $this->directories = array_unique(array_merge( - $this->directories ?? [], + $this->dirPaths = array_unique(array_merge( + $this->dirPaths ?? [], [FileSystem::normalizePath($path)] )); + $files = array_filter( FileSystem::dirContents($path), static fn ($filename) => FileSystem::isCollectionFile($filename) ); + $this->collectionPaths = array_unique(array_merge($this->collectionPaths ?? [], $files)); } /** + * Adds collection file into current settings array + * * @param string $path * @return void * @throws JsonException */ public function addFilePath(string $path): void { - $normpath = FileSystem::normalizePath($path); - if (!FileSystem::isCollectionFile($normpath)) { + if (!FileSystem::isCollectionFile($path)) { throw new InvalidArgumentException("not a valid collection: $path"); } - in_array($path, $this->collectionPaths) || $this->collectionPaths[] = $path; + + if (!in_array($path, $this->collectionPaths)) { + $this->collectionPaths[] = FileSystem::normalizePath($path); + } } /** - * @param string $outputPath + * Sets output directory path + * + * @param string|null $outputPath * @return void */ - public function setOutputPath(string $outputPath): void + public function setOutputPath(?string $outputPath): void { $this->outputPath = $outputPath; } /** + * Sets developer mode setting + * * @param bool $devMode * @return void */ @@ -172,6 +263,8 @@ class Settings } /** + * Adds a format to convert to into current settings array + * * @param ConvertFormat $format * @return void */ @@ -191,6 +284,8 @@ class Settings } /** + * Sets a setting responsible for saving old convertion results + * * @param bool $preserveOutput * @return void */ @@ -200,15 +295,21 @@ class Settings } /** - * @param string $filepath + * Sets environment filepath setting + * + * @param string|null $filepath * @return void */ - public function setEnvFilepath(string $filepath): void + public function setEnvFilePath(?string $filepath): void { - $this->envFilepath = FileSystem::normalizePath($filepath); + $this->envFilePath = is_string($filepath) + ? FileSystem::normalizePath($filepath) + : $filepath; } /** + * Returns current value of developer mode setting + * * @return bool */ public function isDevMode(): bool @@ -217,6 +318,8 @@ class Settings } /** + * Returns current value of collection files setting + * * @return string[] */ public function collectionPaths(): array @@ -225,14 +328,18 @@ class Settings } /** - * @return string + * Returns current value of output directory path setting + * + * @return string|null */ - public function outputPath(): string + public function outputPath(): ?string { return $this->outputPath; } /** + * Returns current value of preserve output setting + * * @return bool */ public function isPreserveOutput(): bool @@ -241,6 +348,8 @@ class Settings } /** + * Returns current convert formats + * * @return ConvertFormat[] */ public function formats(): array @@ -249,11 +358,13 @@ class Settings } /** - * @return string + * Returns current value of environment filepath setting + * + * @return string|null */ - public function envFilepath(): string + public function envFilepath(): ?string { - return $this->envFilepath; + return $this->envFilePath; } /** @@ -264,17 +375,17 @@ class Settings public function __serialize(): array { return [ - 'dev' => $this->isDevMode(), - 'directories' => $this->directories, - 'files' => $this->collectionPaths(), - 'environment' => $this->envFilepath(), - 'output' => $this->outputPath(), - 'preserve-output' => $this->isPreserveOutput(), - 'formats' => array_values(array_map( + AN::DevMode => $this->isDevMode(), + AN::Directories => $this->dirPaths, + AN::Files => $this->collectionPaths(), + AN::Environment => $this->envFilepath(), + AN::Output => $this->outputPath(), + AN::PreserveOutput => $this->isPreserveOutput(), + AN::Formats => array_values(array_map( static fn (ConvertFormat $format) => $format->toArg(), $this->formats(), )), - 'vars' => $this->vars, + AN::Vars => $this->vars, ]; } @@ -307,7 +418,8 @@ class Settings */ public function backup(): string { - copy(self::$filepath, $newfilepath = self::$filepath . '.bak.' . time()); - return $newfilepath; + $newFilePath = $this->filePath() . '.bak.' . time(); + copy($this->filePath(), $newFilePath); + return $newFilePath; } } diff --git a/tests/AbstractRequestTest.php b/tests/AbstractRequestTest.php index 7afd877..dc8e2a0 100644 --- a/tests/AbstractRequestTest.php +++ b/tests/AbstractRequestTest.php @@ -10,7 +10,7 @@ class AbstractRequestTest extends TestCase { /** * @covers PmConverter\Converters\Abstract\AbstractRequest - * @covers PmConverter\HttpVersion + * @covers \PmConverter\Enums\HttpVersion * @return void * @throws InvalidHttpVersionException */ @@ -25,7 +25,7 @@ class AbstractRequestTest extends TestCase /** * @covers PmConverter\Converters\Abstract\AbstractRequest * @covers PmConverter\Converters\Abstract\AbstractRequest::getVerb() - * @covers PmConverter\HttpVersion + * @covers \PmConverter\Enums\HttpVersion * @return void * @throws InvalidHttpVersionException */ diff --git a/tests/pm-convert-settings.json b/tests/pm-convert-settings.json new file mode 100644 index 0000000..f7caf87 --- /dev/null +++ b/tests/pm-convert-settings.json @@ -0,0 +1,10 @@ +{ + "files": [ + "collections/20-API Lifecycle.postman_collection.json" + ], + "output": "converted", + "preserveOutput": false, + "formats": [ + "http" + ] +}