Last active
May 4, 2019 22:52
-
-
Save radist2s/21509a7d366ec98aad978bc2fde2beef to your computer and use it in GitHub Desktop.
Tool for renaming BEM Blocks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Usage | |
* bem-block-rename.php ./templates/ .renaming-block-name .destination-block-name # will change every `.blade.php` in directory | |
* bem-block-rename.php .renaming-block--with-mod .dest-block--with-renamed-mode # will change every `.blade.php` in CWD directory | |
* bem-block-rename.php specified-file.php .renaming-block-name__child .destination-block-name__renamed-child # will change only `specified-file.php` | |
*/ | |
namespace BemClassesTools; | |
const FILE_FORMAT = '.blade.php'; | |
const ELEMENTS_SEPARATOR = '__'; | |
const MODIFIER_SEPARATOR = '--'; | |
/** | |
* @param $path | |
* @param string $template_ext | |
* @return array | |
* @throws \Exception | |
*/ | |
function find_templates($path, $template_ext = FILE_FORMAT): array { | |
if (is_file($path)) { | |
return [$path]; | |
} | |
if (!is_dir($path)) { | |
throw new \Exception("Specified path is not and directory: `$path`"); | |
} | |
$files = array_diff(scandir($path), ['.', '..']); | |
if (!$files) { | |
return []; | |
} | |
$templates = []; | |
$template_ext_escaped = preg_quote($template_ext); | |
$template_ext_regexp = "~$template_ext_escaped$~iu"; | |
$base_dir = rtrim($path, DIRECTORY_SEPARATOR); | |
foreach ($files as $file) { | |
$file_path = $base_dir . DIRECTORY_SEPARATOR . $file; | |
if (is_dir($file_path)) { | |
$templates = $templates + find_templates($file_path); | |
} | |
elseif (is_file($file_path) and preg_match($template_ext_regexp, $file_path)) { | |
$templates[] = $file_path; | |
} | |
} | |
return $templates; | |
} | |
/** | |
* @param $name | |
* @throws \Exception | |
*/ | |
function validate_block_name($name) { | |
if (!preg_match('~^\.~', $name)) { | |
throw new \Exception("Block name must begin with dot. Example: `.block-name`, provided name: `$name`"); | |
} | |
if (!preg_match('~[^\W_]$~iu', $name)) { | |
throw new \Exception("Block name must end with letter or number. Example: `.block-name`, provided name: `$name`"); | |
} | |
} | |
/** | |
* @param string $code | |
* @param string $block_source | |
* @param string $block_dest | |
* @param string $elements_separator | |
* @param string $modifier_separator | |
* @return array | |
*/ | |
function replace_block_name(string $code, string $block_source, string $block_dest, $elements_separator = ELEMENTS_SEPARATOR, $modifier_separator = MODIFIER_SEPARATOR) { | |
$block_source = trim($block_source); | |
$block_dest = trim($block_dest); | |
validate_block_name($block_source); | |
validate_block_name($block_dest); | |
$block_name_dest = trim($block_dest, '.'); | |
$block_name_escaped = preg_quote(trim($block_source, '.')); | |
$sep_el = preg_quote($elements_separator); | |
$sep_mod = preg_quote($modifier_separator); | |
$block_find_regex = "(?<=^|\s|['\"`])(?P<Block>{$block_name_escaped})(?P<BlockAfter>$|{$sep_el}|{$sep_mod}|[^\w{$sep_el}{$sep_mod}])"; | |
$code_highlighted = ''; | |
$code_to_highlight = $code; | |
$lines_count = 0; | |
$replace_callback = function ($matches) use ($block_name_dest, &$code_highlighted, &$code_to_highlight, &$lines_count) { | |
['0' => $matched_string, 'BlockAfter' => $block_after] = $matches; | |
$matched_position_start = mb_strpos($code_to_highlight, $matched_string); | |
$matched_before_code = mb_substr($code_to_highlight, 0, $matched_position_start); | |
$matched_before_code_lines = explode("\n", $matched_before_code); | |
// $lines_count = $lines_count + count($matched_before_code_lines); // todo::fixme | |
$matched_line_beginning = end($matched_before_code_lines); | |
$matched_after_code = mb_substr($code_to_highlight, $matched_position_start + mb_strlen($matched_string)); | |
$matched_line_ending = explode("\n", $matched_after_code, 2)[0] ?? ''; | |
$code_highlighted .= | |
// "\e[36mLine {$lines_count}: \e[0m" . // echo colors: https://misc.flogisoft.com/bash/tip_colors_and_formatting | |
$matched_line_beginning . | |
"\e[42m{$block_name_dest}\e[0m" . | |
$block_after . | |
$matched_line_ending . | |
PHP_EOL; | |
$code_to_highlight = mb_substr($code_to_highlight, $matched_position_start + mb_strlen($matched_string)); | |
return $block_name_dest . $block_after; | |
}; | |
$code_refactored = preg_replace_callback("~$block_find_regex~mu", $replace_callback, $code); | |
return [ | |
$code_refactored, | |
$code_highlighted | |
]; | |
} | |
/** | |
* @param string $path | |
* @param string $block_source | |
* @param string $block_dest | |
* @param string $elements_separator | |
* @param string $modifier_separator | |
* @return array | |
* @throws \Exception | |
*/ | |
function replace_block_name_by_path(string $path, string $block_source, string $block_dest, $elements_separator = ELEMENTS_SEPARATOR, $modifier_separator = MODIFIER_SEPARATOR) { | |
if (!is_writable($path)) { | |
throw new \Exception("Specified file is not writable: `$path`"); | |
} | |
$code = file_get_contents($path); | |
return replace_block_name($code, $block_source, $block_dest, $elements_separator, $modifier_separator); | |
} | |
function get_relative_path($from, $to) { | |
// some compatibility fixes for Windows paths | |
$from = is_dir($from) ? rtrim($from, '\/') . '/' : $from; | |
$to = is_dir($to) ? rtrim($to, '\/') . '/' : $to; | |
$from = str_replace('\\', '/', $from); | |
$to = str_replace('\\', '/', $to); | |
$from = explode('/', $from); | |
$to = explode('/', $to); | |
$relPath = $to; | |
foreach ($from as $depth => $dir) { | |
// find first non-matching dir | |
if ($dir === $to[$depth]) { | |
// ignore this directory | |
array_shift($relPath); | |
} | |
else { | |
// get number of remaining dirs to $from | |
$remaining = count($from) - $depth; | |
if ($remaining > 1) { | |
// add traversals up to first matching dir | |
$padLength = (count($relPath) + $remaining - 1) * -1; | |
$relPath = array_pad($relPath, $padLength, '..'); | |
break; | |
} | |
else { | |
$relPath[0] = './' . $relPath[0]; | |
} | |
} | |
} | |
return implode('/', $relPath); | |
} | |
/** | |
* @param array $args | |
* @throws \Exception | |
*/ | |
function main(...$args) { | |
$option_args = array_filter($args, function ($arg) { | |
return mb_strpos($arg, '-') === 0; | |
}); | |
$command_args = array_diff($args, $option_args); | |
$options = array_map( | |
function ($arg) { | |
return ltrim($arg, '-'); | |
}, | |
$option_args | |
); | |
$command_args_length = count($command_args); | |
if ($command_args_length === 3) { | |
[$path, $block_source, $block_dest] = $command_args; | |
} | |
else if ($command_args_length == 2) { | |
[$block_source, $block_dest] = $command_args; | |
if (is_file($block_source)) { | |
throw new \Exception("Too ambiguous, found file `$block_source`, but this is proved as block name. Provide `path` as first argument."); | |
} | |
$path = getcwd(); | |
} | |
else { | |
throw new \Exception("Wrong args count provided."); | |
} | |
$file_list = find_templates($path); | |
$is_dry_run = !array_intersect($options, ['f', 'force', 'write']); | |
$files_refactored_list = []; | |
foreach ($file_list as $path) { | |
[$code_refactored, $code_highlighted] = replace_block_name_by_path($path, $block_source, $block_dest); | |
if (!$code_refactored or !$code_highlighted) { | |
continue; | |
} | |
$files_refactored_list[] = $path; | |
$file_relative_path = get_relative_path(getcwd(), $path); | |
echo | |
"\e[32m{$file_relative_path}\e[0m\n", | |
$code_highlighted, | |
PHP_EOL; | |
if (!$is_dry_run) { | |
file_put_contents($path, $code_refactored); | |
} | |
} | |
if ($files_refactored_list) { | |
if ($is_dry_run) { | |
$message = "Dry run is complete. To replace block names in destination files provide `-f` argument."; | |
echo "\e[31m{$message}\e[0m\n", PHP_EOL; | |
} | |
} | |
else { | |
$message = "Block not found, provided: `$block_source`"; | |
echo "\e[31m{$message}\e[0m\n", PHP_EOL; | |
} | |
} | |
call_user_func_array('\BemClassesTools\main', array_slice($argv, 1)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment