Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/system.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,4 @@ jobs:
- name: Install project dependencies
run: composer upgrade --no-interaction --no-progress --prefer-dist --no-dev
- name: System test with PHP ${{ matrix.php-version }}
run: php bin/sat-pys-scraper --json build/result.json --xml build/result.xml --sort key
env:
MAX_TRIES: 5
run: php bin/sat-pys-scraper --tries 5 --json build/result.json --xml build/result.xml --sort key
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ sin temor a romper tu aplicación.
|---------|----------|-----------------------------------|
| 1.0.0 | 8.2, 8.3 | 2023-12-13 Fuera de mantenimiento |
| 2.0.0 | 8.2, 8.3 | 2024-03-07 Fuera de mantenimiento |
| 3.0.0 | 8.2, 8.3 | 2024-03-07 |
| 3.0.0 | 8.2, 8.3 | 2024-03-07 Fuera de mantenimiento |
| 4.0.0 | 8.2, 8.3 | 2024-10-17 |

## Contribuciones

Expand Down
13 changes: 1 addition & 12 deletions bin/sat-pys-scraper
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,4 @@ use PhpCfdi\SatPysScraper\App\SatPysScraper;

require __DIR__ . '/../vendor/autoload.php';

exit(call_user_func(function (string ...$argv): int {
$result = 1;
$maxTries = ($_SERVER['MAX_TRIES'] ?? null) ?? ($_ENV['MAX_TRIES'] ?? null);
$maxTries = is_numeric($maxTries) ? intval($maxTries) : 1;
for ($try = 0; $try < $maxTries; $try++) {
$result = SatPysScraper::run($argv);
if (0 === $result) {
break;
}
}
return $result;
}, ...$argv));
exit((new SatPysScraper())->run($argv));
15 changes: 15 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ versión, aunque sí su incorporación en la rama principal de trabajo. Generalm

## Listado de cambios

### Versión 4.0.0 2024-10-17

Esta es una actualización de refactorización que obliga a crear una versión mayor.
Si no utilizas entidades del espacio de nombres `PhpCfdi\SatPysScraper\App` entonces puedes hacer el cambio
de la versión `3.x` a la versión `4.x` sin conflictos. En caso contrario debes revisar tu implementación.

- Se agrega el parámetro `--debug` que, si existe, vuelca los datos del error de ejecución.
- Se agrega el parámetro `--tries` que, si existe, reintenta la descarga de información hasta ese número de veces.
- Se extrae el procesamiento de argumentos a su propia clase.
- Se extrae el almacenamiento de argumentos a su propia clase en lugar de un arreglo.
- Se reorganizan las pruebas de acuerdo a los cambios previos.
- La ejecución del flujo de trabajo `system.yml` en el trabajo `system-tests` se configura con `--tries 5`.
- Se vuelve a simplificar la herramienta `bin/sat-pys-scraper` para que toda su lógica esté probada.
- Ya no se usa la variable de entorno `MAX_TRIES`.

### Versión 3.0.2 2024-10-17

A la herramienta `bin/sat-pys-scraper` se le puede definir un número máximo de ejecuciones en la
Expand Down
18 changes: 18 additions & 0 deletions src/App/Arguments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace PhpCfdi\SatPysScraper\App;

final readonly class Arguments
{
public function __construct(
public string $xml,
public string $json,
public string $sort,
public int $tries,
public bool $quiet,
public bool $debug,
) {
}
}
98 changes: 98 additions & 0 deletions src/App/ArgumentsBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace PhpCfdi\SatPysScraper\App;

final class ArgumentsBuilder
{
private string $xml = '';

private string $json = '';

private string $sort = 'key';

private int $tries = 1;

private bool $quiet = false;

private bool $debug = false;

/** @throws ArgumentException */
public function build(string ...$arguments): Arguments
{
$arguments = array_values($arguments);
while ([] !== $arguments) {
$argument = (string) array_shift($arguments);
match (true) {
in_array($argument, ['--xml', '-x'], true) => $this->setXml((string) array_shift($arguments)),
in_array($argument, ['--json', '-j'], true) => $this->setJson((string) array_shift($arguments)),
in_array($argument, ['--sort', '-s'], true) => $this->setSort((string) array_shift($arguments)),
in_array($argument, ['--tries', '-t'], true) => $this->setTries((string) array_shift($arguments)),
in_array($argument, ['--quiet', '-q'], true) => $this->setQuiet(),
in_array($argument, ['--debug', '-d'], true) => $this->setDebug(),
default => throw new ArgumentException(sprintf('Invalid argument "%s"', $argument)),
};
}

if ('' === $this->xml && '' === $this->json) {
throw new ArgumentException('Did not specify --xml or --json arguments');
}
if ('-' === $this->xml && '-' === $this->json) {
throw new ArgumentException('Cannot send --xml and --json result to standard output at the same time');
}

return new Arguments(
xml: '-' === $this->xml ? 'php://stdout' : $this->xml,
json: '-' === $this->json ? 'php://stdout' : $this->json,
sort: $this->sort,
tries: $this->tries,
quiet: $this->quiet,
debug: $this->debug,
);
}

private function setXml(string $argument): void
{
$this->xml = $argument;
if ('-' === $argument) {
$this->quiet = true;
}
}

private function setJson(string $argument): void
{
$this->json = $argument;
if ('-' === $argument) {
$this->quiet = true;
}
}

/** @throws ArgumentException */
private function setSort(string $argument): void
{
if (! in_array($argument, ['key', 'name'], true)) {
throw new ArgumentException(sprintf('Invalid sort "%s"', $argument));
}
$this->sort = $argument;
}

/** @throws ArgumentException */
private function setTries(string $argument): void
{
$this->tries = (int) $argument;
if ((string) $this->tries !== $argument || $this->tries < 1) {
throw new ArgumentException(sprintf('Invalid tries "%s"', $argument));
}
}

private function setQuiet(): void
{
$this->quiet = true;
}

private function setDebug(): void
{
$this->debug = true;
}
}
156 changes: 60 additions & 96 deletions src/App/SatPysScraper.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use GuzzleHttp\Client;
use PhpCfdi\SatPysScraper\Data\Types;
use PhpCfdi\SatPysScraper\Exceptions\HttpException;
use PhpCfdi\SatPysScraper\Exceptions\HttpServerException;
use PhpCfdi\SatPysScraper\Generator;
use PhpCfdi\SatPysScraper\NullGeneratorTracker;
use PhpCfdi\SatPysScraper\Scraper;
Expand All @@ -15,40 +17,14 @@

final readonly class SatPysScraper
{
/**
* @param list<string> $arguments
*/
public function __construct(private string $command, private array $arguments, private ScraperInterface $scraper)
public function printHelp(string $command): void
{
}

/** @param string[] $argv */
public static function run(
array $argv,
ScraperInterface $scraper = new Scraper(new Client()),
string $stdErrFile = 'php://stderr'
): int {
$command = (string) array_shift($argv);
$argv = array_values($argv);
$app = new self($command, $argv, $scraper);
try {
$app->execute();
return 0;
} catch (Throwable $exception) {
file_put_contents($stdErrFile, 'ERROR: ' . $exception->getMessage() . PHP_EOL, FILE_APPEND);
return 1;
}
}

public function printHelp(): void
{
$command = basename($this->command);
echo <<< HELP
$command - Crea un archivo XML con la clasificación de productos y servicios del SAT.

Sintaxis:
$command help|-h|--help
$command [--quiet|-q] [--json|-j JSON_FILE] [--xml|-x XML_FILE]
$command [--quiet|-q] [--debug|-d] [--json|-j JSON_FILE] [--xml|-x XML_FILE] [--tries|-t TRIES]

Argumentos:
--xml|-x XML_FILE
Expand All @@ -59,12 +35,18 @@ public function printHelp(): void
los datos generados en formato JSON.
--sort|-s SORT
Establece el orden de elementos, default: key, se puede usar "key" o "name".
--tries|-t TRIES
Establece cuántas veces debe intentar hacer la descarga si encuentra un error de servidor.
Default: 1. El valor debe ser mayor o igual a 1.
--debug|-d
Mensajes de intentos e información del error se envían a la salida estándar de error.
--quiet|-q
Modo de operación silencioso.

Notas:
Debe especificar al menos un argumento "--xml" o "--json", o ambos.
No se puede especificar "-" como salida de "--xml" y "--json" al mismo tiempo.
Al especificar la salida "-" se activa automáticamente el modo silencioso.

Acerca de:
Este script pertenece al proyecto https://github.com/phpcfdi/sat-pys-scraper
Expand All @@ -74,84 +56,66 @@ public function printHelp(): void
HELP;
}

/** @throws ArgumentException */
public function execute(): void
/** @param list<string> $argv */
public function run(array $argv, ScraperInterface|null $scraper = null, string $stdErrFile = 'php://stderr'): int
{
if ([] !== array_intersect($this->arguments, ['help', '-h', '--help'])) {
$this->printHelp();
return;
}

$arguments = $this->processArguments(...$this->arguments);
$tracker = ($arguments['quiet']) ? new NullGeneratorTracker() : new PrinterGeneratorTracker();
$types = (new Generator($this->scraper, $tracker))->generate();

// sort types
match ($arguments['sort']) {
'key' => $types->sortByKey(),
'name' => $types->sortByName(),
default => throw new ArgumentException('Unrecognized sort argument'),
};
$command = (string) array_shift($argv);
$app = new self();

if ('' !== $arguments['xml']) {
$this->toXml($arguments['xml'], $types);
}
if ('' !== $arguments['json']) {
$this->toJson($arguments['json'], $types);
if ([] !== array_intersect($argv, ['help', '-h', '--help'])) {
$app->printHelp(basename($command));
return 0;
}
}
$debug = [] !== array_intersect($argv, ['-d', '--debug']);
$try = 0;

/**
* @return array{xml: string, json: string, quiet: bool, sort: string}
* @throws ArgumentException
*/
public function processArguments(string ...$arguments): array
{
$arguments = array_values($arguments);
$xml = '';
$json = '';
$quiet = false;
$sort = 'key';

while ([] !== $arguments) {
$argument = (string) array_shift($arguments);
if (in_array($argument, ['--xml', '-x'], true)) {
$xml = (string) array_shift($arguments);
} elseif (in_array($argument, ['--json', '-j'], true)) {
$json = (string) array_shift($arguments);
} elseif (in_array($argument, ['--sort', '-s'], true)) {
$sort = (string) array_shift($arguments);
if (! in_array($sort, ['key', 'name'])) {
throw new ArgumentException(sprintf('Invalid sort "%s"', $sort));
try {
$arguments = (new ArgumentsBuilder())->build(...$argv);
$debug = $arguments->debug;
do {
$try = $try + 1;
try {
$app->execute($arguments, $scraper);
$serverException = null;
break;
} catch (HttpServerException $exception) {
$serverException = $exception;
usleep(1000);
}
} elseif (in_array($argument, ['--quiet', '-q'], true)) {
$quiet = true;
} else {
throw new ArgumentException(sprintf('Invalid argument "%s"', $argument));
} while ($try < $arguments->tries);
if (null !== $serverException) {
throw $serverException;
}
} catch (Throwable $exception) {
file_put_contents($stdErrFile, 'ERROR: ' . $exception->getMessage() . PHP_EOL, FILE_APPEND);
if ($debug) {
file_put_contents($stdErrFile, "The procedure was executed $try times\n", FILE_APPEND);
file_put_contents($stdErrFile, print_r($exception, true), FILE_APPEND);
}
return 1;
}
return 0;
}

if ('' === $xml && '' === $json) {
throw new ArgumentException('Did not specify --xml or --json arguments');
}
if ('-' === $xml && '-' === $json) {
throw new ArgumentException('Cannot send --xml and --json result to standard output at the same time');
}
if ('-' === $xml) {
$xml = 'php://stdout';
$quiet = true;
/** @throws HttpServerException|HttpException */
private function execute(Arguments $arguments, ScraperInterface|null $scraper): void
{
$tracker = ($arguments->quiet) ? new NullGeneratorTracker() : new PrinterGeneratorTracker();
$scraper ??= new Scraper(new Client());
$types = (new Generator($scraper, $tracker))->generate();

// sort types
match ($arguments->sort) {
'name' => $types->sortByName(),
default => $types->sortByKey(),
};

if ('' !== $arguments->xml) {
$this->toXml($arguments->xml, $types);
}
if ('-' === $json) {
$json = 'php://stdout';
$quiet = true;
if ('' !== $arguments->json) {
$this->toJson($arguments->json, $types);
}

return [
'xml' => $xml,
'json' => $json,
'quiet' => $quiet,
'sort' => $sort,
];
}

public function toXml(string $output, Types $types): void
Expand Down
Loading