Compare commits

...

9 Commits

Author SHA1 Message Date
Pavel Shevaev 5d5af1ac09 Adding some typehints
Publish PHP Package / docker (push) Successful in 7s Details
2025-04-17 17:47:55 +03:00
Pavel Shevaev f5874a9db4 Adding extra check if def macros are not actually present in the source
Publish PHP Package / docker (push) Successful in 6s Details
2025-02-28 01:54:13 +03:00
Pavel Shevaev 20e06b8433 Fixing new lines preserving for one line comments
Publish PHP Package / docker (push) Failing after 3s Details
2025-02-26 09:50:52 +03:00
Pavel Shevaev a6f7839b0c Adding further optimizations for content without any macros and type hints
Publish PHP Package / docker (push) Successful in 6s Details
2025-02-19 15:57:51 +03:00
Pavel Shevaev 89fa7d1f87 Обновить CHANGELOG.md 2024-12-18 18:45:33 +03:00
Pavel Shevaev 543ea0fd7c Добавить CHANGELOG.md 2024-12-18 18:43:55 +03:00
Pavel Shevaev 1f3ab806bd Disallowing duplicating def macro arguments
Publish PHP Package / docker (push) Successful in 6s Details
2024-12-17 18:23:50 +03:00
Pavel Shevaev 59a0b424fa Добавить .gitea/workflows/build_composer.yaml
Publish PHP Package / docker (push) Successful in 4s Details
2024-02-13 14:54:38 +03:00
Pavel Shevaev b859c28dc3 Tweaking composer autoload 2023-10-24 17:32:45 +03:00
4 changed files with 119 additions and 49 deletions

View File

@ -0,0 +1,29 @@
name: Publish PHP Package
on:
push:
tags:
- 'v*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Get tag name
run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: zip and send
run: |
ls -la
apt-get update -y
apt-get install -y zip
cd ../
zip -r ${{ gitea.event.repository.name }}.zip ${{ gitea.event.repository.name }} -x '*.git*'
curl -v \
--user composer-pbl:${{ secrets.COMPOSER_PSWD }} \
--upload-file ${{ gitea.event.repository.name }}.zip \
https://git.bit5.ru/api/packages/bit/composer?version=${{ env.TAG }}

9
CHANGELOG.md Normal file
View File

@ -0,0 +1,9 @@
## v1.4.0
- Disallowing duplicating def macro arguments
## v1.3.3
- Adding experimental support for prefixing of included macros via <%INC("path/to", "prefix")%>
## v1.2.0
- JSM now supports an array of base dirs
- Renaming @builtin into @global for better clarity

View File

@ -6,6 +6,6 @@
"php": ">=7.4" "php": ">=7.4"
}, },
"autoload": { "autoload": {
"classmap": ["jsm.inc.php"] "files": ["jsm.inc.php"]
} }
} }

View File

@ -31,19 +31,19 @@ class JSM
$this->args_parser = new JSM_ArgsParser(); $this->args_parser = new JSM_ArgsParser();
} }
function getRootFile() function getRootFile() : string
{ {
return $this->file; return $this->file;
} }
function getModule($file) static function getModule($file) : JSM_Module
{ {
if(isset(self::$modules[$file])) if(isset(self::$modules[$file]))
return self::$modules[$file]; return self::$modules[$file];
throw new Exception("Module for '$file' not found"); throw new Exception("Module for '$file' not found");
} }
function _extractModule($file) function _extractModule($file) : JSM_Module
{ {
$txt = file_get_contents($file); $txt = file_get_contents($file);
if($txt === false) if($txt === false)
@ -54,11 +54,12 @@ class JSM
$m->node = new JSM_MacroDefNode($file/*using file as name*/); $m->node = new JSM_MacroDefNode($file/*using file as name*/);
$this->_pushCurrentModule($m); $this->_pushCurrentModule($m);
$txt = $this->_fixJunk($txt); $txt = self::_fixJunk($txt);
$txt = $this->_removeBlockComments($txt); $txt = self::_removeBlockComments($txt);
$txt = $this->_removeLineComments($txt); $txt = self::_removeLineComments($txt);
$txt = $this->_processIncludes($file, $txt); $txt = $this->_processIncludes($file, $txt);
$txt = $this->_extractScriptDefs($file, $txt); $txt = $this->_extractScriptDefs($file, $txt);
$this->_parseDefBody($m->node, $file, $txt); $this->_parseDefBody($m->node, $file, $txt);
$this->_popCurrentModule(); $this->_popCurrentModule();
@ -92,7 +93,8 @@ class JSM
return $this->_currentModule(); return $this->_currentModule();
} }
function process() //NOTE: returns [json, module]
function process() : array
{ {
$m = $this->_extractModule($this->file); $m = $this->_extractModule($this->file);
@ -102,9 +104,17 @@ class JSM
$this->defs = $m->defs; $this->defs = $m->defs;
$this->includes = $m->includes; $this->includes = $m->includes;
$this->_pushBuffer(); $res = '';
$m->node->call($this); if($m->node->raw_text_node)
$res = $this->_popBuffer(); {
$res = $m->node->raw_text_node->txt;
}
else
{
$this->_pushBuffer();
$m->node->call($this);
$res = $this->_popBuffer();
}
if($this->buffers) if($this->buffers)
throw new Exception("Non closed buffers " . sizeof($this->buffers)); throw new Exception("Non closed buffers " . sizeof($this->buffers));
@ -233,34 +243,37 @@ class JSM
throw new Exception("Var '$name' not resolved"); throw new Exception("Var '$name' not resolved");
} }
private function _fixJunk($txt) private static function _fixJunk(string $txt) : string
{ {
//windows eol //windows eol
$txt = str_replace("\r\n", "\n", $txt); $txt = str_replace("\r\n", "\n", $txt);
return $txt; return $txt;
} }
private function _removeBlockComments($txt) private static function _removeBlockComments(string $txt) : string
{ {
if(strpos($txt, '/*') === false) if(strpos($txt, '/*') === false)
return $txt; return $txt;
$regex = '~/\*.*?\*/~s';
$txt = preg_replace_callback( $txt = preg_replace_callback(
$regex, '~/\*.*?\*/~s',
//preserve the new lines for better error reporting //preserve the new lines for better error reporting
function($m) { return str_repeat("\n", substr_count($m[0], "\n")); }, function($m) { return str_repeat("\n", substr_count($m[0], "\n")); },
$txt); $txt);
return $txt; return $txt;
} }
private function _removeLineComments($txt) private static function _removeLineComments(string $txt) : string
{ {
//TODO: it's not robust enough, note a hack for URL addresses //TODO: it's not robust enough, note a hack for URL addresses
$txt = preg_replace("~\s*(?<!:)//.*~", "\n", $txt); $txt = preg_replace_callback(
'~\s*(?<!:)//.*~',
//preserve the new lines for better error reporting
function($m) { return str_repeat("\n", substr_count($m[0], "\n")); },
$txt);
return $txt; return $txt;
} }
/*private */function _relpathCallback(array $m, $curr_dir) /*private */function _relpathCallback(array $m, string $curr_dir) : string
{ {
//1) checking if such a file exists and normalizing all .. in the path //1) checking if such a file exists and normalizing all .. in the path
$full_path = realpath($curr_dir . '/' . $m[1]); $full_path = realpath($curr_dir . '/' . $m[1]);
@ -273,7 +286,7 @@ class JSM
return $rel_path; return $rel_path;
} }
/*private */function _relconfCallback(array $m, $curr_dir) /*private */function _relconfCallback(array $m, string $curr_dir) : string
{ {
//1) checking if such a file exists and normalizing all .. in the path //1) checking if such a file exists and normalizing all .. in the path
$full_path = realpath($curr_dir . '/' . $m[1] . '.conf.js'); $full_path = realpath($curr_dir . '/' . $m[1] . '.conf.js');
@ -286,7 +299,7 @@ class JSM
return "@$rel_path"; return "@$rel_path";
} }
private function _processRelpaths($file, $txt) private function _processRelpaths(string $file, string $txt) : string
{ {
//normalizing all file paths //normalizing all file paths
if(strpos($txt, './') !== false) if(strpos($txt, './') !== false)
@ -305,7 +318,7 @@ class JSM
return $txt; return $txt;
} }
/*private */function _includesCallback($inc_path, $prefix, $curr_file) /*private */function _includesCallback(string $inc_path, string $prefix, string $curr_file) : string
{ {
$file = jsm_resolve_inc_path($this->base_dirs, $curr_file, $inc_path); $file = jsm_resolve_inc_path($this->base_dirs, $curr_file, $inc_path);
@ -313,11 +326,11 @@ class JSM
if(!isset(self::$modules[$file])) if(!isset(self::$modules[$file]))
{ {
if($this->_hasExtension($file, '.js')) if(self::_hasExtension($file, '.js'))
{ {
self::$modules[$file] = $this->_extractModule($file); self::$modules[$file] = $this->_extractModule($file);
} }
else if($this->_hasExtension($file, '.php')) else if(self::_hasExtension($file, '.php'))
{ {
include_once($file); include_once($file);
//special mark for PHP include //special mark for PHP include
@ -353,7 +366,7 @@ class JSM
return $need_include_macro ? "<%INC($file)%>" : ''; return $need_include_macro ? "<%INC($file)%>" : '';
} }
private function _processIncludes($file, $txt) private function _processIncludes(string $file, string $txt) : string
{ {
if(strpos($txt, 'INC') !== false) if(strpos($txt, 'INC') !== false)
{ {
@ -367,25 +380,25 @@ class JSM
return $txt; return $txt;
} }
function _isIncluded($file) function _isIncluded(string $file) : bool
{ {
$file = jsm_normalize_path($file); $file = jsm_normalize_path($file);
return isset($this->includes[$file]); return isset($this->includes[$file]);
} }
function _hasExtension($file, $ext) static function _hasExtension(string $file, string $ext) : bool
{ {
return strpos($file, $ext, strlen($file) - strlen($ext) - 1) !== false; return strpos($file, $ext, strlen($file) - strlen($ext) - 1) !== false;
} }
static function getDeps(array $base_dirs, $file) static function getDeps(array $base_dirs, string $file) : array
{ {
$deps = array(); $deps = array();
self::_getDeps($base_dirs, $file, $deps); self::_getDeps($base_dirs, $file, $deps);
return $deps; return $deps;
} }
static private function _extractDeps($txt) static private function _extractDeps(string $txt) : array
{ {
$dep_files = array(); $dep_files = array();
if(preg_match_all('~<%\s*INC\s*\(\s*"([^\n]+)~', $txt, $ms)) if(preg_match_all('~<%\s*INC\s*\(\s*"([^\n]+)~', $txt, $ms))
@ -399,7 +412,7 @@ class JSM
return array_keys($dep_files); return array_keys($dep_files);
} }
static private function _getDeps(array $base_dirs, $file, &$deps) static private function _getDeps(array $base_dirs, string $file, array &$deps)
{ {
static $cache = array(); static $cache = array();
@ -441,7 +454,7 @@ class JSM
} }
} }
private function _extractDefNameAndArgs($full_name) private function _extractDefNameAndArgs(string $full_name) : array
{ {
$pos = strpos($full_name, '('); $pos = strpos($full_name, '(');
if($pos == -1) if($pos == -1)
@ -454,14 +467,14 @@ class JSM
return array($name, $args); return array($name, $args);
} }
private function _parseMacroDefArgs($args_str) private function _parseMacroDefArgs(string $args_str)
{ {
if(!$args_str) if(!$args_str)
return array(); return array();
return $this->args_parser->parseDef($args_str); return $this->args_parser->parseDef($args_str);
} }
function nodes2str($nodes_or_str) function nodes2str($nodes_or_str) : string
{ {
$str = ''; $str = '';
if(is_array($nodes_or_str)) if(is_array($nodes_or_str))
@ -478,13 +491,13 @@ class JSM
return $str; return $str;
} }
function _processMacroArgs($arg_nodes_or_str) function _processMacroArgs($arg_nodes_or_str) : array
{ {
$str = $this->nodes2str($arg_nodes_or_str); $str = $this->nodes2str($arg_nodes_or_str);
return $this->_parseMacroArgsStr($str); return $this->_parseMacroArgsStr($str);
} }
function _parseMacroArgsStr($orig_args_str) function _parseMacroArgsStr($orig_args_str) : array
{ {
static $cache = array(); static $cache = array();
@ -496,8 +509,8 @@ class JSM
if($may_cache && isset($cache[$orig_args_str])) if($may_cache && isset($cache[$orig_args_str]))
return $cache[$orig_args_str]; return $cache[$orig_args_str];
$args_str = $this->_removeBlockComments($orig_args_str); $args_str = self::_removeBlockComments($orig_args_str);
$args_str = $this->_removeLineComments($args_str); $args_str = self::_removeLineComments($args_str);
$args_str = rtrim($args_str, ','); $args_str = rtrim($args_str, ',');
$res = $this->args_parser->parseInvoke($args_str); $res = $this->args_parser->parseInvoke($args_str);
@ -508,9 +521,14 @@ class JSM
return $res; return $res;
} }
private function _extractScriptDefs($file, $txt) private function _extractScriptDefs(string $file, string $txt) : string
{ {
if(strpos($txt, 'def') === false) $strpos = strpos($txt, 'def');
if($strpos === false)
return $txt;
//let's extra check if newline symbols preceed 'def'
if($strpos > 0 && !($txt[$strpos-1] === "\n" || $txt[$strpos-1] === "\r"))
return $txt; return $txt;
//NOTE: make it more robust //NOTE: make it more robust
@ -549,8 +567,11 @@ class JSM
return $txt; return $txt;
} }
private function _replaceVarsWithMacro($file, $txt) private function _replaceVarsWithMacro(string $file, string $txt) : string
{ {
if(strpos($txt, '$') === false)
return $txt;
//simple vals //simple vals
$txt = preg_replace( $txt = preg_replace(
'~\{(\$[a-zA-Z0-9_]+)\}~', '~\{(\$[a-zA-Z0-9_]+)\}~',
@ -566,14 +587,16 @@ class JSM
return $txt; return $txt;
} }
private function _parseDefBody(JSM_MacroDefNode $node, $file, $txt) private function _parseDefBody(JSM_MacroDefNode $node, string $file, string $txt)
{ {
$txt = $this->_replaceVarsWithMacro($file, $txt); $txt = $this->_replaceVarsWithMacro($file, $txt);
//let's exit early if there are no macro calls //let's exit early if there are no macro calls
if(strpos($txt, '<%') === false) if(strpos($txt, '<%') === false)
{ {
$node->addChild(new JSM_TextNode($txt)); $raw_text_node = new JSM_TextNode($txt);
$node->raw_text_node = $raw_text_node;
$node->addChild($raw_text_node);
return; return;
} }
@ -629,7 +652,7 @@ class JSM
$this->_popNode(); $this->_popNode();
} }
function _newCall($name) function _newCall(string $name)
{ {
$func = "macro_ex_{$name}"; $func = "macro_ex_{$name}";
if(is_callable($func)) if(is_callable($func))
@ -642,7 +665,7 @@ class JSM
} }
} }
function _getMacro($name) function _getMacro(string $name) : JSM_MacroUserNode
{ {
if(isset($this->defs[$name])) if(isset($this->defs[$name]))
return $this->defs[$name]; return $this->defs[$name];
@ -660,22 +683,23 @@ class JSM
class JSM_Module class JSM_Module
{ {
var $file = ''; var string $file = '';
var $defs = array(); var $defs = array();
//NOTE: all includes (even those included from other modules)
var $includes = array(); var $includes = array();
var $node = null; var $node = null;
function __construct($file) function __construct(string $file)
{ {
$this->file = $file; $this->file = $file;
} }
function getIncludes() function getIncludes() : array
{ {
return $this->includes; return $this->includes;
} }
function getFile() function getFile() : string
{ {
return $this->file; return $this->file;
} }
@ -686,6 +710,11 @@ interface JSM_MacroNode
function call(JSM $jsm); function call(JSM $jsm);
} }
interface JSM_MacroUserNode extends JSM_MacroNode
{
function setUserArgs(array $args);
}
interface JSM_MacroInternalNode extends JSM_MacroNode interface JSM_MacroInternalNode extends JSM_MacroNode
{ {
function addChild(JSM_MacroNode $node); function addChild(JSM_MacroNode $node);
@ -745,7 +774,7 @@ class JSM_MacroCallNode implements JSM_MacroInternalNode
} }
} }
class JSM_MacroPHPNode implements JSM_MacroNode class JSM_MacroPHPNode implements JSM_MacroUserNode
{ {
var $func; var $func;
@ -857,13 +886,14 @@ class JSM_MacroPHPNode implements JSM_MacroNode
} }
} }
class JSM_MacroDefNode implements JSM_MacroInternalNode class JSM_MacroDefNode implements JSM_MacroInternalNode,JSM_MacroUserNode
{ {
var $name = ''; var $name = '';
var $file = ''; var $file = '';
var $decl_args = array(); var $decl_args = array();
var $node_args = array(); var $node_args = array();
var $children = array(); var $children = array();
var $raw_text_node = null;
function __construct($name, $decl_args = array(), $file = '') function __construct($name, $decl_args = array(), $file = '')
{ {
@ -1565,6 +1595,8 @@ class JSM_ArgsParser
{ {
$this->skip_whitespace(); $this->skip_whitespace();
$value = $this->parse_arg_value(); $value = $this->parse_arg_value();
if(isset($out[$name]))
throw new Exception("Argument '{$name}' is already defined in def macro");
$out[$name] = $value; $out[$name] = $value;
$this->skip_whitespace(); $this->skip_whitespace();
$ch = $this->next(); $ch = $this->next();