v1.2.0 #3

Merged
anthony merged 4 commits from dev into master 2023-08-13 02:28:35 +00:00
12 changed files with 454 additions and 142 deletions

View File

@ -15,6 +15,8 @@ These formats are supported for now: `http`, `curl`, `wget`.
## Supported features ## Supported features
* [collection schema **v2.1**](https://schema.postman.com/json/collection/v2.1.0/collection.json); * [collection schema **v2.1**](https://schema.postman.com/json/collection/v2.1.0/collection.json);
* `Bearer` auth;
* replace vars in requests by stored in collection and environment file;
* export one or several collections (or even whole directories) into one or all of formats supported at the same time; * export one or several collections (or even whole directories) into one or all of formats supported at the same time;
* all headers (including disabled for `http`-format); * all headers (including disabled for `http`-format);
* `json` body (forces header `Content-Type` to `application/json`); * `json` body (forces header `Content-Type` to `application/json`);
@ -22,12 +24,10 @@ These formats are supported for now: `http`, `curl`, `wget`.
## Planned features ## Planned features
- support as many as possible/necessary of authentication kinds (_currently no ones_); - support as many as possible/necessary of authentication kinds (_currently only `Bearer` supported_);
- support as many as possible/necessary of body formats (_currently only `json` and `formdata`_); - support as many as possible/necessary of body formats (_currently only `json` and `formdata`_);
- documentation generation support (markdown) with responce examples (if present); - documentation generation support (markdown) with responce examples (if present);
- maybe some another convert formats (like httpie or something...); - maybe some another convert formats (like httpie or something...);
- replace `{{vars}}` from folder;
- replace `{{vars}}` from environment;
- better logging; - better logging;
- tests, phpcs, psalm, etc.; - tests, phpcs, psalm, etc.;
- web version. - web version.
@ -52,39 +52,45 @@ export PATH="$PATH:~/.config/composer/vendor/bin"
$ pm-convert --help $ pm-convert --help
Postman collection converter Postman collection converter
Usage: Usage:
./pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS] ./pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]
php pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS] php pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]
composer pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS] composer pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]
./vendor/bin/pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS] ./vendor/bin/pm-convert -f|-d PATH -o OUTPUT_PATH [ARGUMENTS] [FORMATS]
Possible ARGUMENTS: Possible ARGUMENTS:
-f, --file - a PATH to single collection located in PATH to convert from -f, --file - a PATH to single collection located in PATH to convert from
-d, --dir - a directory with collections located in COLLECTION_FILEPATH to convert from -d, --dir - a directory with collections located in COLLECTION_FILEPATH to convert from
-o, --output - a directory OUTPUT_PATH to put results in -o, --output - a directory OUTPUT_PATH to put results in
-p, --preserve - do not delete OUTPUT_PATH (if exists) -e, --env - use environment file with variable values to replace in request
-h, --help - show this help message and exit -p, --preserve - do not delete OUTPUT_PATH (if exists)
-v, --version - show version info and exit -h, --help - show this help message and exit
-v, --version - show version info and exit
If both -c and -d are specified then only unique set of files will be converted. If no ARGUMENTS passed then --help implied.
If both -f and -d are specified then only unique set of files will be converted.
-f or -d are required to be specified at least once, but each may be specified multiple times. -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. PATH must be a valid path to readable json-file or directory.
OUTPUT_PATH must be a valid path to writeable directory. OUTPUT_PATH must be a valid path to writeable directory.
If -o is specified several times then only last one will be applied. If -o is specified several times then only last one will be used.
If -e is specified several times then only last one will be used.
If -e is not specified then only collection vars will be replaced (if any).
Possible FORMATS: Possible FORMATS:
--http - generate raw *.http files (default) --http - generate raw *.http files (default)
--curl - generate shell scripts with curl command --curl - generate shell scripts with curl command
--wget - generate shell scripts with wget command --wget - generate shell scripts with wget command
If no FORMATS specified then --http implied. If no FORMATS specified then --http implied.
Any of FORMATS can be specified at the same time. Any of FORMATS can be specified at the same time.
Example: Example:
./pm-convert \ ./pm-convert \
-f ~/dir1/first.postman_collection.json \ -f ~/dir1/first.postman_collection.json \
--directory ~/team \ --directory ~/team \
--file ~/dir2/second.postman_collection.json \ --file ~/dir2/second.postman_collection.json \
-d ~/personal \ --env ~/localhost.postman_environment.json \
-o ~/postman_export -d ~/personal \
-o ~/postman_export
``` ```
### Notice ### Notice

65
src/Environment.php Normal file
View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace PmConverter;
class Environment implements \ArrayAccess
{
/**
* @var array
*/
protected array $vars = [];
/**
* @param object $env
*/
public function __construct(protected object $env)
{
foreach ($env->values as $var) {
$this->vars["{{{$var->key}}}"] = $var->value;
}
}
/**
* Tells if there are some vars or not
*
* @return bool
*/
public function hasVars(): bool
{
return !empty($this->vars);
}
/**
* @inheritDoc
*/
public function offsetExists(mixed $offset): bool
{
return array_key_exists($offset, $this->vars);
}
/**
* @inheritDoc
*/
public function offsetGet(mixed $offset): mixed
{
return $this->vars[$offset];
}
/**
* @inheritDoc
*/
public function offsetSet(mixed $offset, mixed $value): void
{
$this->vars[$offset] = $value;
}
/**
* @inheritDoc
*/
public function offsetUnset(mixed $offset): void
{
unset($this->vars[$offset]);
}
}

View File

@ -5,14 +5,16 @@ declare(strict_types=1);
namespace PmConverter\Exporters\Abstract; namespace PmConverter\Exporters\Abstract;
use Exception; use Exception;
use PmConverter\Environment;
use PmConverter\Exporters\{ use PmConverter\Exporters\{
ConverterContract,
RequestContract}; RequestContract};
use PmConverter\FileSystem; use PmConverter\FileSystem;
/** /**
* *
*/ */
abstract class AbstractConverter abstract class AbstractConverter implements ConverterContract
{ {
/** /**
* @var object|null * @var object|null
@ -25,6 +27,30 @@ abstract class AbstractConverter
protected string $outputPath; protected string $outputPath;
/** /**
* @var string[]
*/
protected array $vars = [];
/**
* @var Environment
*/
protected Environment $env;
/**
* Sets an environment with vars
*
* @param Environment $env
* @return $this
*/
public function withEnv(Environment $env): static
{
$this->env = $env;
return $this;
}
/**
* Converts collection requests
*
* @throws Exception * @throws Exception
*/ */
public function convert(object $collection, string $outputPath): void public function convert(object $collection, string $outputPath): void
@ -32,12 +58,30 @@ abstract class AbstractConverter
$outputPath = sprintf('%s%s%s', $outputPath, DIRECTORY_SEPARATOR, static::OUTPUT_DIR); $outputPath = sprintf('%s%s%s', $outputPath, DIRECTORY_SEPARATOR, static::OUTPUT_DIR);
$this->outputPath = FileSystem::makeDir($outputPath); $this->outputPath = FileSystem::makeDir($outputPath);
$this->collection = $collection; $this->collection = $collection;
$this->setVariables();
foreach ($collection->item as $item) { foreach ($collection->item as $item) {
$this->convertItem($item); $this->convertItem($item);
} }
} }
/** /**
* Prepares collection variables
*
* @return $this
*/
protected function setVariables(): static
{
if ($this->collection->variable) {
foreach ($this->collection->variable as $var) {
$this->vars["{{{$var->key}}}"] = $var->value;
}
}
return $this;
}
/**
* Returns output path
*
* @return string * @return string
*/ */
public function getOutputPath(): string public function getOutputPath(): string
@ -46,6 +90,8 @@ abstract class AbstractConverter
} }
/** /**
* Checks whether item contains another items or not
*
* @param object $item * @param object $item
* @return bool * @return bool
*/ */
@ -55,6 +101,8 @@ abstract class AbstractConverter
} }
/** /**
* Converts an item to request object and writes it into file
*
* @throws Exception * @throws Exception
*/ */
protected function convertItem(mixed $item): void protected function convertItem(mixed $item): void
@ -77,18 +125,23 @@ abstract class AbstractConverter
} }
/** /**
* Initialiazes request object to be written in file
*
* @param object $item * @param object $item
* @return RequestContract * @return RequestContract
*/ */
protected function initRequest(object $item): RequestContract protected function initRequest(object $item): RequestContract
{ {
$request_class = static::REQUEST; $request_class = static::REQUEST_CLASS;
$result = new $request_class();
/** @var RequestContract $result */
$result = new $request_class($this->vars);
$result->setName($item->name); $result->setName($item->name);
$result->setDescription($item->request?->description ?? null); $result->setDescription($item->request?->description ?? null);
$result->setVerb($item->request->method); $result->setVerb($item->request->method);
$result->setUrl($item->request->url->raw); $result->setUrl($item->request->url->raw);
$result->setHeaders($item->request->header); $result->setHeaders($item->request->header);
$result->setAuth($item->request?->auth ?? $this->collection?->auth ?? null);
if ($item->request->method !== 'GET' && !empty($item->request->body)) { if ($item->request->method !== 'GET' && !empty($item->request->body)) {
$result->setBody($item->request->body); $result->setBody($item->request->body);
} }
@ -96,6 +149,8 @@ abstract class AbstractConverter
} }
/** /**
* Writes converted request object to file
*
* @param RequestContract $request * @param RequestContract $request
* @param string|null $subpath * @param string|null $subpath
* @return bool * @return bool
@ -106,6 +161,33 @@ abstract class AbstractConverter
$filedir = sprintf('%s%s%s', $this->outputPath, DIRECTORY_SEPARATOR, $subpath); $filedir = sprintf('%s%s%s', $this->outputPath, DIRECTORY_SEPARATOR, $subpath);
$filedir = FileSystem::makeDir($filedir); $filedir = FileSystem::makeDir($filedir);
$filepath = sprintf('%s%s%s.%s', $filedir, DIRECTORY_SEPARATOR, $request->getName(), static::FILE_EXT); $filepath = sprintf('%s%s%s.%s', $filedir, DIRECTORY_SEPARATOR, $request->getName(), static::FILE_EXT);
return file_put_contents($filepath, (string)$request) > 0; $content = $this->interpolate((string)$request);
return file_put_contents($filepath, $content) > 0;
}
/**
* Replaces variables in request with values from collection or environment
*
* @param string $content
* @return string
*/
protected function interpolate(string $content): string
{
if (empty($this->vars) && $this->env->hasVars()) {
return $content;
}
$matches = [];
if (preg_match_all('/\{\{[a-zA-Z][a-zA-Z0-9]+}}/', $content, $matches, PREG_PATTERN_ORDER) > 0) {
foreach ($matches[0] as $key => $var) {
if (str_contains($content, $var)) {
$content = str_replace($var, $this->vars[$var] ?? $this->env[$var] ?? $var, $content);
unset($matches[0][$key]);
}
}
}
// if (!empty($matches[0])) {
// fwrite(STDERR, sprintf(' No values found: %s%s', implode(', ', $matches[0]), PHP_EOL));
// }
return $content;
} }
} }

View File

@ -8,14 +8,14 @@ use PmConverter\Exporters\Http\HttpRequest;
use PmConverter\Exporters\RequestContract; use PmConverter\Exporters\RequestContract;
/** /**
* * Class to determine file content with any request format
*/ */
abstract class AbstractRequest implements RequestContract abstract class AbstractRequest implements RequestContract
{ {
/** /**
* @var string * @var string
*/ */
protected string $http = 'HTTP/1.1'; //TODO verb protected string $http = 'HTTP/1.1'; //TODO proto
/** /**
* @var string * @var string
@ -53,6 +53,8 @@ abstract class AbstractRequest implements RequestContract
protected string $url; protected string $url;
/** /**
* Sets name from collection item to request object
*
* @param string $name * @param string $name
* @return HttpRequest * @return HttpRequest
*/ */
@ -63,6 +65,8 @@ abstract class AbstractRequest implements RequestContract
} }
/** /**
* Returns name of request
*
* @return string * @return string
*/ */
public function getName(): string public function getName(): string
@ -71,6 +75,8 @@ abstract class AbstractRequest implements RequestContract
} }
/** /**
* Sets description from collection item to request object
*
* @param string|null $description * @param string|null $description
* @return HttpRequest * @return HttpRequest
*/ */
@ -81,6 +87,8 @@ abstract class AbstractRequest implements RequestContract
} }
/** /**
* Sets HTTP verb from collection item to request object
*
* @param string $verb * @param string $verb
* @return HttpRequest * @return HttpRequest
*/ */
@ -91,6 +99,8 @@ abstract class AbstractRequest implements RequestContract
} }
/** /**
* Sets URL from collection item to request object
*
* @param string $url * @param string $url
* @return HttpRequest * @return HttpRequest
*/ */
@ -101,6 +111,8 @@ abstract class AbstractRequest implements RequestContract
} }
/** /**
* Sets headers from collection item to request object
*
* @param object[]|null $headers * @param object[]|null $headers
* @return $this * @return $this
*/ */
@ -116,6 +128,32 @@ abstract class AbstractRequest implements RequestContract
} }
/** /**
* Sets authorization headers
*
* @param object|null $auth
* @return $this
*/
public function setAuth(?object $auth): static
{
if (!empty($auth)) {
switch ($auth->type) {
case 'bearer':
$this->headers['Authorization'] = [
'value' => 'Bearer ' . $auth->{$auth->type}[0]->value,
'disabled' => false,
];
break;
default:
break;
}
}
return $this;
}
/**
* Sets body mode from collection item to request object
*
* @param string $bodymode * @param string $bodymode
* @return HttpRequest * @return HttpRequest
*/ */
@ -126,8 +164,10 @@ abstract class AbstractRequest implements RequestContract
} }
/** /**
* @param string $body * Sets body from collection item to request object
* @return HttpRequest *
* @param object $body
* @return $this
*/ */
public function setBody(object $body): static public function setBody(object $body): static
{ {
@ -153,11 +193,29 @@ abstract class AbstractRequest implements RequestContract
} }
/** /**
* @return string * Returns array of description lines
*
* @return array
*/ */
abstract protected function prepareBody(): ?string; abstract protected function prepareDescription(): array;
/** /**
* Returns array of headers
*
* @return array
*/
abstract protected function prepareHeaders(): array;
/**
* Returns array of request body lines
*
* @return array
*/
abstract protected function prepareBody(): array;
/**
* Converts request object to string to be written in result file
*
* @return string * @return string
*/ */
abstract public function __toString(): string; abstract public function __toString(): string;

View File

@ -11,7 +11,8 @@ use PmConverter\Exporters\{
class CurlConverter extends AbstractConverter implements ConverterContract class CurlConverter extends AbstractConverter implements ConverterContract
{ {
protected const FILE_EXT = 'sh'; protected const FILE_EXT = 'sh';
protected const OUTPUT_DIR = 'curl'; protected const OUTPUT_DIR = 'curl';
protected const REQUEST = CurlRequest::class; protected const REQUEST_CLASS = CurlRequest::class;
} }

View File

@ -7,59 +7,77 @@ namespace PmConverter\Exporters\Curl;
use PmConverter\Exporters\Abstract\AbstractRequest; use PmConverter\Exporters\Abstract\AbstractRequest;
/** /**
* * Class to determine file content with curl request format
*/ */
class CurlRequest extends AbstractRequest class CurlRequest extends AbstractRequest
{ {
/** /**
* @return string * @inheritDoc
*/ */
protected function prepareBody(): ?string protected function prepareDescription(): array
{ {
switch ($this->bodymode) { return empty($this->description)
case 'formdata': ? []
$body = []; : ['# ' . str_replace("\n", "\n# ", $this->description), ''];
foreach ($this->body as $data) {
$body[] = sprintf(
"%s\t--form '%s=%s' \ ",
isset($data->disabled) ? '# ' : '',
$data->key,
$data->type === 'file' ? "@$data->src" : $data->value
);
}
return implode(PHP_EOL, $body);
default:
return $this->body;
}
} }
/** /**
* @return string * @inheritDoc
*/ */
public function __toString(): string protected function prepareHeaders(): array
{ {
$output[] = '#!/bin/sh'; $output = [];
if ($this->description) {
$output[] = '# ' . str_replace("\n", "\n# ", $this->description);
$output[] = '';
}
$output[] = "curl \ ";
$output[] = "\t--http1.1 \ "; //TODO verb
$output[] = "\t--request $this->verb \ ";
$output[] = "\t--location $this->url \ ";
foreach ($this->headers as $header_key => $header) { foreach ($this->headers as $header_key => $header) {
if ($header['disabled']) { if ($header['disabled']) {
continue; continue;
} }
$output[] = sprintf("\t--header '%s=%s' \ ", $header_key, $header['value']); $output[] = sprintf("\t--header '%s=%s' \ ", $header_key, $header['value']);
} }
if (!is_null($body = $this->prepareBody())) { return $output;
$output[] = match ($this->bodymode) { }
'formdata' => $body,
default => "\t--data '$body'", /**
}; * @inheritDoc
*/
protected function prepareBody(): array
{
$output = [];
switch ($this->bodymode) {
case 'formdata':
foreach ($this->body as $data) {
$output[] = sprintf(
"%s\t--form '%s=%s' \ ",
isset($data->disabled) ? '# ' : '',
$data->key,
$data->type === 'file' ? "@$data->src" : $data->value
);
}
break;
default:
$output = ["\t--data '$this->body'"];
break;
} }
return $output;
}
/**
* @inheritDoc
*/
public function __toString(): string
{
$output = array_merge(
['#!/bin/sh'],
$this->prepareDescription(),
[
"curl \ ",
"\t--http1.1 \ ", //TODO proto
"\t--request $this->verb \ ",
"\t--location $this->url \ ",
],
$this->prepareHeaders(),
$this->prepareBody()
);
$output[] = rtrim(array_pop($output), '\ '); $output[] = rtrim(array_pop($output), '\ ');
return implode(PHP_EOL, $output); return implode(PHP_EOL, array_merge($output, ['']));
} }
} }

View File

@ -11,7 +11,8 @@ use PmConverter\Exporters\{
class HttpConverter extends AbstractConverter implements ConverterContract class HttpConverter extends AbstractConverter implements ConverterContract
{ {
protected const FILE_EXT = 'http'; protected const FILE_EXT = 'http';
protected const OUTPUT_DIR = 'http'; protected const OUTPUT_DIR = 'http';
protected const REQUEST = HttpRequest::class; protected const REQUEST_CLASS = HttpRequest::class;
} }

View File

@ -7,47 +7,64 @@ namespace PmConverter\Exporters\Http;
use PmConverter\Exporters\Abstract\AbstractRequest; use PmConverter\Exporters\Abstract\AbstractRequest;
/** /**
* * Class to determine file content with http request format
*/ */
class HttpRequest extends AbstractRequest class HttpRequest extends AbstractRequest
{ {
/** /**
* @return string * @inheritDoc
*/ */
protected function prepareBody(): ?string protected function prepareDescription(): array
{ {
switch ($this->bodymode) { return empty($this->description)
case 'formdata': ? []
$body = []; : ['# ' . str_replace("\n", "\n# ", $this->description), ''];
foreach ($this->body as $data) {
$body[] = sprintf(
'%s%s=%s',
empty($data->disabled) ? '' : '# ',
$data->key,
$data->type === 'file' ? "$data->src" : $data->value
);
}
return implode(PHP_EOL, $body);
default:
return $this->body;
}
} }
/** /**
* @return string * @inheritDoc
*/ */
public function __toString(): string protected function prepareHeaders(): array
{ {
if ($this->description) {
$output[] = '# ' . str_replace("\n", "\n# ", $this->description);
$output[] = '';
}
$output[] = "$this->verb $this->url $this->http"; $output[] = "$this->verb $this->url $this->http";
foreach ($this->headers as $header_key => $header) { foreach ($this->headers as $header_key => $header) {
$output[] = sprintf('%s%s: %s', $header['disabled'] ? '# ' : '', $header_key, $header['value']); $output[] = sprintf('%s%s: %s', $header['disabled'] ? '# ' : '', $header_key, $header['value']);
} }
$output[] = ''; return $output;
$output[] = (string)$this->prepareBody(); }
/**
* @inheritDoc
*/
protected function prepareBody(): array
{
switch ($this->bodymode) {
case 'formdata':
$output = [''];
foreach ($this->body as $data) {
$output[] = sprintf(
'%s%s=%s',
empty($data->disabled) ? '' : '# ',
$data->key,
$data->type === 'file' ? $data->src : $data->value
);
}
return $output;
default:
return ['', $this->body];
}
}
/**
* @inheritDoc
*/
public function __toString(): string
{
$output = array_merge(
$this->prepareDescription(),
$this->prepareHeaders(),
$this->prepareBody()
);
return implode(PHP_EOL, $output); return implode(PHP_EOL, $output);
} }
} }

View File

@ -11,6 +11,7 @@ interface RequestContract
public function setDescription(?string $description): static; public function setDescription(?string $description): static;
public function setVerb(string $verb): static; public function setVerb(string $verb): static;
public function setUrl(string $url): static; public function setUrl(string $url): static;
public function setAuth(object $auth): static;
public function setHeaders(?array $headers): static; public function setHeaders(?array $headers): static;
public function setBodymode(string $bodymode): static; public function setBodymode(string $bodymode): static;
public function setBody(object $body): static; public function setBody(object $body): static;

View File

@ -11,7 +11,8 @@ use PmConverter\Exporters\{
class WgetConverter extends AbstractConverter implements ConverterContract class WgetConverter extends AbstractConverter implements ConverterContract
{ {
protected const FILE_EXT = 'sh'; protected const FILE_EXT = 'sh';
protected const OUTPUT_DIR = 'wget'; protected const OUTPUT_DIR = 'wget';
protected const REQUEST = WgetRequest::class; protected const REQUEST_CLASS = WgetRequest::class;
} }

View File

@ -7,57 +7,76 @@ namespace PmConverter\Exporters\Wget;
use PmConverter\Exporters\Abstract\AbstractRequest; use PmConverter\Exporters\Abstract\AbstractRequest;
/** /**
* * Class to determine file content with wget request format
*/ */
class WgetRequest extends AbstractRequest class WgetRequest extends AbstractRequest
{ {
/** /**
* @return string * @inheritDoc
*/ */
protected function prepareBody(): ?string protected function prepareDescription(): array
{ {
switch ($this->bodymode) { return empty($this->description)
case 'formdata': ? []
$lines = []; : ['# ' . str_replace("\n", "\n# ", $this->description), ''];
foreach ($this->body as &$data) {
if ($data->type === 'file') {
continue;
}
$lines[$data->key] = $data->value;
}
$body[] = http_build_query($lines);
return implode(PHP_EOL, $body);
default:
return $this->body;
}
} }
/** /**
* @return string * @inheritDoc
*/ */
public function __toString(): string protected function prepareHeaders(): array
{ {
$output[] = '#!/bin/sh'; $output = [];
if ($this->description) {
$output[] = '# ' . str_replace("\n", "\n# ", $this->description);
$output[] = '';
}
$output[] = 'wget \ ';
$output[] = "\t--no-check-certificate \ ";
$output[] = "\t--quiet \ ";
$output[] = "\t--timeout=0 \ ";
$output[] = "\t--method $this->verb \ ";
foreach ($this->headers as $header_key => $header) { foreach ($this->headers as $header_key => $header) {
if ($header['disabled']) { if ($header['disabled']) {
continue; continue;
} }
$output[] = sprintf("\t--header '%s=%s' \ ", $header_key, $header['value']); $output[] = sprintf("\t--header '%s=%s' \ ", $header_key, $header['value']);
} }
if (!is_null($body = $this->prepareBody())) { return $output;
$output[] = "\t--body-data '$body' \ "; }
/**
* @inheritDoc
*/
protected function prepareBody(): array
{
switch ($this->bodymode) {
case 'formdata':
$params = [];
foreach ($this->body as $data) {
if ($data->type === 'file') {
continue;
}
$params[$data->key] = $data->value;
}
$output[] = http_build_query($params);
return $output;
default:
return ["\t$this->body"];
} }
}
/**
* @inheritDoc
*/
public function __toString(): string
{
$output = array_merge(
['#!/bin/sh'],
$this->prepareDescription(),
[
'wget \ ',
"\t--no-check-certificate \ ",
"\t--quiet \ ",
"\t--timeout=0 \ ",
"\t--method $this->verb \ ",
],
$this->prepareHeaders(),
$this->prepareBody()
);
$output[] = rtrim(array_pop($output), '\ '); $output[] = rtrim(array_pop($output), '\ ');
$output[] = "\t'$this->url'"; $output[] = "\t'$this->url'";
return implode(PHP_EOL, $output); return implode(PHP_EOL, array_merge($output, ['']));
} }
} }

View File

@ -22,7 +22,7 @@ class Processor
/** /**
* Converter version * Converter version
*/ */
public const VERSION = '1.1.1'; public const VERSION = '1.2.0';
/** /**
* @var string[] Paths to collection files * @var string[] Paths to collection files
@ -57,12 +57,22 @@ class Processor
/** /**
* @var int Initial timestamp * @var int Initial timestamp
*/ */
protected int $init_time; protected int $initTime;
/** /**
* @var int Initial RAM usage * @var int Initial RAM usage
*/ */
protected int $init_ram; protected int $initRam;
/**
* @var string
*/
protected string $envFile;
/**
* @var Environment
*/
protected Environment $env;
/** /**
* Constructor * Constructor
@ -71,8 +81,8 @@ class Processor
*/ */
public function __construct(protected array $argv) public function __construct(protected array $argv)
{ {
$this->init_time = hrtime(true); $this->initTime = hrtime(true);
$this->init_ram = memory_get_usage(true); $this->initRam = memory_get_usage(true);
} }
/** /**
@ -115,14 +125,19 @@ class Processor
if (empty($this->argv[$idx + 1])) { if (empty($this->argv[$idx + 1])) {
throw new InvalidArgumentException('a directory path is expected for -d (--dir)'); throw new InvalidArgumentException('a directory path is expected for -d (--dir)');
} }
$normpath = $this->argv[$idx + 1]; $rawpath = $this->argv[$idx + 1];
$files = array_filter( $files = array_filter(
FileSystem::dirContents($normpath), FileSystem::dirContents($rawpath),
static fn($filename) => FileSystem::isCollectionFile($filename) static fn($filename) => FileSystem::isCollectionFile($filename)
); );
$this->collectionPaths = array_unique(array_merge($this?->collectionPaths ?? [], $files)); $this->collectionPaths = array_unique(array_merge($this?->collectionPaths ?? [], $files));
break; break;
case '-e':
case '--env':
$this->envFile = FileSystem::normalizePath($this->argv[$idx + 1]);
break;
case '-p': case '-p':
case '--preserve': case '--preserve':
$this->preserveOutput = true; $this->preserveOutput = true;
@ -158,6 +173,8 @@ class Processor
} }
/** /**
* Initializes output directory
*
* @return void * @return void
* @throws CannotCreateDirectoryException * @throws CannotCreateDirectoryException
* @throws DirectoryIsNotWriteableException * @throws DirectoryIsNotWriteableException
@ -185,6 +202,8 @@ class Processor
} }
/** /**
* Initializes collection objects
*
* @throws JsonException * @throws JsonException
*/ */
protected function initCollections(): void protected function initCollections(): void
@ -199,6 +218,22 @@ class Processor
} }
} }
/**
* Initializes environment object
*
* @return void
* @throws JsonException
*/
protected function initEnv(): void
{
$content = file_get_contents(FileSystem::normalizePath($this->envFile));
$content = json_decode($content, flags: JSON_THROW_ON_ERROR);
if (!property_exists($content, 'environment') || empty($content?->environment)) {
throw new JsonException("not a valid environment: $this->envFile");
}
$this->env = new Environment($content->environment);
}
/** /**
* Begins a conversion * Begins a conversion
* *
@ -210,6 +245,7 @@ class Processor
$this->initOutputDirectory(); $this->initOutputDirectory();
$this->initConverters(); $this->initConverters();
$this->initCollections(); $this->initCollections();
$this->initEnv();
$count = count($this->collections); $count = count($this->collections);
$current = 0; $current = 0;
$success = 0; $success = 0;
@ -218,10 +254,13 @@ class Processor
++$current; ++$current;
printf("Converting '%s' (%d/%d):%s", $collectionName, $current, $count, PHP_EOL); printf("Converting '%s' (%d/%d):%s", $collectionName, $current, $count, PHP_EOL);
foreach ($this->converters as $type => $exporter) { foreach ($this->converters as $type => $exporter) {
printf(' > %s', strtolower($type)); printf('> %s%s', strtolower($type), PHP_EOL);
$outputPath = sprintf('%s%s%s', $this->outputPath, DIRECTORY_SEPARATOR, $collectionName); $outputPath = sprintf('%s%s%s', $this->outputPath, DIRECTORY_SEPARATOR, $collectionName);
if (!empty($this->env)) {
$exporter->withEnv($this->env);
}
$exporter->convert($collection, $outputPath); $exporter->convert($collection, $outputPath);
printf(' - OK: %s%s', $exporter->getOutputPath(), PHP_EOL); printf(' OK: %s%s', $exporter->getOutputPath(), PHP_EOL);
} }
print(PHP_EOL); print(PHP_EOL);
++$success; ++$success;
@ -238,8 +277,8 @@ class Processor
*/ */
protected function printStats(int $success, int $count): void protected function printStats(int $success, int $count): void
{ {
$time = (hrtime(true) - $this->init_time) / 1_000_000; $time = (hrtime(true) - $this->initTime) / 1_000_000;
$ram = (memory_get_peak_usage(true) - $this->init_ram) / 1024 / 1024; $ram = (memory_get_peak_usage(true) - $this->initRam) / 1024 / 1024;
printf('Converted %d of %d in %.3f ms using %.3f MiB RAM', $success, $count, $time, $ram); printf('Converted %d of %d in %.3f ms using %.3f MiB RAM', $success, $count, $time, $ram);
} }
@ -278,16 +317,19 @@ class Processor
"\t-f, --file - a PATH to single collection located in PATH to convert from", "\t-f, --file - a PATH to single collection located in PATH to convert from",
"\t-d, --dir - a directory with collections located in COLLECTION_FILEPATH to convert from", "\t-d, --dir - a directory with collections located in COLLECTION_FILEPATH to convert from",
"\t-o, --output - a directory OUTPUT_PATH to put results in", "\t-o, --output - a directory OUTPUT_PATH to put results in",
"\t-e, --env - use environment file with variable values to replace in request",
"\t-p, --preserve - do not delete OUTPUT_PATH (if exists)", "\t-p, --preserve - do not delete OUTPUT_PATH (if exists)",
"\t-h, --help - show this help message and exit", "\t-h, --help - show this help message and exit",
"\t-v, --version - show version info and exit", "\t-v, --version - show version info and exit",
'', '',
'If no ARGUMENTS passed then --help implied.', 'If no ARGUMENTS passed then --help implied.',
'If both -c and -d are specified then only unique set of files will be converted.', 'If both -f and -d are specified then only unique set of files will be converted.',
'-f or -d are required to be specified at least once, but each may be specified multiple times.', '-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.', 'PATH must be a valid path to readable json-file or directory.',
'OUTPUT_PATH must be a valid path to writeable directory.', 'OUTPUT_PATH must be a valid path to writeable directory.',
'If -o is specified several times then only last one will be used.', 'If -o is specified several times then only last one will be used.',
'If -e is specified several times then only last one will be used.',
'If -e is not specified then only collection vars will be replaced (if any).',
'', '',
'Possible FORMATS:', 'Possible FORMATS:',
"\t--http - generate raw *.http files (default)", "\t--http - generate raw *.http files (default)",
@ -301,6 +343,7 @@ class Processor
" -f ~/dir1/first.postman_collection.json \ ", " -f ~/dir1/first.postman_collection.json \ ",
" --directory ~/team \ ", " --directory ~/team \ ",
" --file ~/dir2/second.postman_collection.json \ ", " --file ~/dir2/second.postman_collection.json \ ",
" --env ~/localhost.postman_environment.json \ ",
" -d ~/personal \ ", " -d ~/personal \ ",
" -o ~/postman_export ", " -o ~/postman_export ",
"", "",