labo86/temple_core

View on GitHub
src/TemplateFiller.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php
declare(strict_types=1);

namespace labo86\temple_core;

use DirectoryIterator;
use labo86\exception_with_data\ExceptionWithData;
use Generator;

class TemplateFiller
{

    public array $ignored_file_list = [];
    public array $replacement_map;

    /**
     * Esta clase sirve para llenar un directorio template.
     * <h2>Modo de uso</h2>
     * <code>
     * $filler = new TemplateFiller(['tpl_company_tpl' => 'labo86', 'tpl_project_tpl' => 'project']);
     *
     * //ignoramos los archivos o carpetas con nombre .git
     * $filler->ignore('.git');
     *
     * //construimos template
     * $filler->fillTemplate('input_dir', 'output_dir');
     * </code>
     * @see fillTemplate() para saber como se transforma el directorio
     * @see replace() para saber como se transforman los nombres y los contenidos de los archivos
     * @param array $replacement_map
     */
    public function __construct(array $replacement_map) {
        $this->replacement_map = $replacement_map;
    }

    /**
     * Registra nombres de archivos a ignorar por {@see filesToReplace()}.
     * @param string ...$files un nombre de archivo sin ruta
     */
    public function ignore(string ...$files) {
        foreach ( $files as $file)
            $this->ignored_file_list[] = $file;
    }

    /**
     * Transforma un string de un template.
     * Este método transforma las ocurrencias de las variables de template por sus correspondientes versiones.
     * @param string $string el string a reemplazar
     * @return string el string reemplazado
     */
    public function replace(string $string) : string {
        foreach ( $this->replacement_map as $variable_name => $value ) {
            $string = str_replace($variable_name, $value, $string);
        }
        return $string;
    }

    /**
     * Esta función recorre los archivos de un directorio excluyendo los siguientes archivos:
     *  - .
     *  - ..
     *  - los nombres de archivos registrador con {@see ignore()}
     *
     * La llave de cada arreglo es el nombre del archivo original y el valor es nombre transformado por {@see replace()}.
     * <strong>Ambos excluyen su ruta</strong>
     * @param string $path la ruta del directorio que tiene los templates
     * @return Generator|string[]
     */
    public function filesToReplace(string $path) {
        foreach ( new DirectoryIterator($path) as $file_info ) {
            $absolute_filename = $file_info->getRealPath();
            $output_filename = $file_info->getBasename();
            if ( $file_info->isDot() ) continue;
            if ( in_array($output_filename, $this->ignored_file_list) ) continue;
            $output_filename = $this->replace($output_filename);
            yield $absolute_filename => $output_filename;
        }
    }

    /**
     * Esta función crea una copia de un directorio que tiene las siguientes diferencias:
     * - con nombres de archivos transformados por {@see replace()}
     * - con contenido reemplazado por {@see replace()}
     * - se excluyen los archivos registrados por {@see ignore()}
     *
     * @param string $current_directory el directorio original con los templates
     * @param string $output_directory el directorio de salida con los templates llenados
     * @throws ExceptionWithData
     * @api
     */
    public function fillTemplate(string $current_directory, string $output_directory) {
        if ( !$this->isDirForPhar($current_directory) )
            throw new ExceptionWithData("current directory does not exists", [ "current_directory" => $current_directory, "output_directory" => $output_directory]);
        if ( file_exists($output_directory) )
            throw new ExceptionWithData("output directory exists", [ "current_directory" => $current_directory, "output_directory" => $output_directory]);

        mkdir($output_directory, 0777, true);

        foreach ($this->filesToReplace($current_directory) as $absolute_filename => $output_filename) {
            if ( is_file($absolute_filename) ) {
                $contents = file_get_contents($absolute_filename);
                $contents = $this->replace($contents);
                file_put_contents($output_directory . "/" . $output_filename, $contents);

            } else if ( is_dir($absolute_filename) ) {
                $this->fillTemplate($absolute_filename, $output_directory . "/" . $output_filename);

            }
        }
    }

    /**
     * Esta función resuelve el problema de usar {@see is_dir()} dentro de un Phar con rutas relativas.
     * Cuando se usa solamente {@see is_dir} {@see TemplateFiller2Test::testRunPharRelative2()} falla.
     * @param string $input_dir
     * @return false|string
     */
    public static function isDirForPhar(string $input_dir) : bool {
        if ( $input_dir = realpath($input_dir) )
            if ( is_dir($input_dir) )
                return true;
        return false;

    }

    /**
     * Construye un mapa de reemplazo para usarlo en el constructor de esta clase desde un arreglo que proviene de la línea de comandos
     * @param array $args
     * @return array
     * @throws ExceptionWithData
     */
    public static function buildReplacementMapFromCommandLineArgs(array $args) : array {
        $replacement_map  = [];
        while ( true ) {
            $position = array_search('-d', $args);
            if ( $position === FALSE ) return $replacement_map;
            if ( !isset($args[$position + 1]) )
                throw new ExceptionWithData('variable definition name is not found', [
                    'args' => $args,
                    'position' => $position + 1
                ]);

            if ( !isset($args[$position + 2]) )
                throw new ExceptionWithData('variable definition value is not found', [
                    'args' => $args,
                    'position' => $position + 2
                ]);


             $replacement_map[$args[$position + 1]] = $args[$position + 2];
             $args = array_slice($args, $position + 2);
        }
    }

}