PageRenderTime 24ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/bin/mysli.assets/lib/assets.php

https://gitlab.com/VxMxPx/mysli.old
PHP | 557 lines | 345 code | 71 blank | 141 comment | 51 complexity | da9570325ffb864cfeca1fba491aaad4 MD5 | raw file
  1. <?php
  2. namespace mysli\assets; class assets
  3. {
  4. const __use = <<<fin
  5. .{ map, exception.assets }
  6. mysli.toolkit.{ request, pkg, ym, type.arr -> arr }
  7. mysli.toolkit.fs.{ fs, file, dir }
  8. fin;
  9. /**
  10. * Cached maps.
  11. * --
  12. * @var array
  13. */
  14. protected static $cache = [ 'map' => [] ];
  15. /**
  16. * Publish assets for particular package.
  17. * --
  18. * @param string $package
  19. * --
  20. * @throws mysli\assets\exception\assets 10 No such directory.
  21. * --
  22. * @return integer Count of published directories.
  23. */
  24. static function publish($package)
  25. {
  26. $map = static::map($package);
  27. if (!isset($map['includes']))
  28. {
  29. return 0;
  30. }
  31. $count = 0;
  32. $source = static::path($package);
  33. foreach ($map['includes'] as $dir => $opt)
  34. {
  35. if (!isset($opt['publish']) || !$opt['publish'])
  36. {
  37. continue;
  38. }
  39. if (dir::exists(fs::ds($source, $dir, 'dist~')))
  40. {
  41. dir::copy(
  42. fs::ds($source, $dir, 'dist~'),
  43. fs::pubpath('assets', $package, $dir));
  44. $count++;
  45. }
  46. elseif (dir::exists(fs::ds($source, $dir)))
  47. {
  48. dir::copy(
  49. fs::ds($source, $dir),
  50. fs::pubpath('assets', $package, $dir));
  51. $count++;
  52. }
  53. else
  54. {
  55. throw new exception\assets(
  56. "No such directory: `{$dir}` in `{$source}`.", 10);
  57. }
  58. }
  59. return $count;
  60. }
  61. /**
  62. * Remove previously published assets.
  63. * --
  64. * @param string $package
  65. * --
  66. * @return boolean
  67. */
  68. static function unpublish($package)
  69. {
  70. return dir::remove(fs::pubpath('assets', $package));
  71. }
  72. /**
  73. * Get map for particular package.
  74. * --
  75. * @param string $package
  76. * @param boolean $reload If map is already cached, should be reloaded?
  77. * --
  78. * @throws mysli\assets\exception\assets 10 Map file not found.
  79. * @throws mysli\assets\exception\assets 20 Invalid map format.
  80. * --
  81. * @return array
  82. */
  83. static function map($package, $reload=false)
  84. {
  85. if ($reload || !isset(static::$cache['map'][$package]))
  86. {
  87. $path = static::path($package);
  88. $filename = fs::ds($path, 'map.ym');
  89. if (!file::exists($filename))
  90. throw new exception\assets(
  91. "Map file not found: `{$filename}`.", 10
  92. );
  93. // Decode map
  94. $map = ym::decode_file($filename);
  95. // Default settings
  96. $map_default = ym::decode_file(
  97. fs::pkgreal('mysli.assets', 'config/defaults.ym')
  98. );
  99. // Merge with defaults in a more reasonable way
  100. foreach ($map_default as $mkey => $mopt)
  101. {
  102. if (!isset($map[$mkey]))
  103. {
  104. $map[$mkey] = $mopt;
  105. }
  106. else
  107. {
  108. $map[$mkey] = array_merge($mopt, $map[$mkey]);
  109. }
  110. }
  111. // Resolve relative processes
  112. $map['modules'] = static::resolve_links($map['modules']);
  113. // Set cache
  114. static::$cache['map'][$package] = $map;
  115. }
  116. return static::$cache['map'][$package];
  117. }
  118. /**
  119. * Resolve files in particular map file.
  120. * --
  121. * @param array $map Map file.
  122. * @param string $root Assets root directory.
  123. * --
  124. * @throws mysli\assets\exception\assets
  125. * 10 Missing `includes` section in `map` array.
  126. * --
  127. * @return array
  128. */
  129. static function resolve_map(array $map, $root)
  130. {
  131. if (!isset($map['includes']))
  132. {
  133. throw new exception\assets(
  134. "Missing `includes` section in `map` array.", 10
  135. );
  136. }
  137. foreach ($map['includes'] as $dir => &$opt)
  138. {
  139. $path = fs::ds($root, $dir);
  140. // No such path, nothing to do
  141. if (!dir::exists($path))
  142. {
  143. $opt['ignore'] = true;
  144. continue;
  145. }
  146. // Set defaults
  147. $opt['id'] = $dir;
  148. $opt['resolved'] = [];
  149. $opt['use-modules'] = [];
  150. if (!isset($opt['publish'])) { $opt['publish'] = true; }
  151. if (!isset($opt['process'])) { $opt['process'] = true; }
  152. if (!isset($opt['ignore'])) { $opt['ignore'] = false; }
  153. if (!isset($opt['merge'])) { $opt['merge'] = false; }
  154. // No files to process
  155. if (!isset($opt['files'])) { $opt['files'] = [ '*.*' ]; }
  156. if (is_string($opt['files'])) { $opt['files'] = [ $opt['files'] ]; }
  157. // Process & publish explicity turned off, ignore directory
  158. if ((!$opt['process'] && !$opt['publish']) || $opt['ignore'])
  159. {
  160. continue;
  161. }
  162. // Modules
  163. $modules = isset($map['modules']) ? $map['modules'] : [];
  164. if (isset($opt['modules']))
  165. {
  166. $modules = array_merge($modules, $opt['modules']);
  167. }
  168. foreach ($opt['files'] as $file)
  169. {
  170. # reset variables
  171. $rsearch = [];
  172. $flags = [];
  173. // Do flags
  174. if (strpos($file, ' !') !== false)
  175. {
  176. $flags = explode(' !', $file);
  177. $file = trim( array_shift($flags) );
  178. }
  179. if (strpos($file, '*') !== false)
  180. {
  181. $rsearch = file::find($path, $file);
  182. }
  183. else
  184. {
  185. $rsearch[$file] = fs::ds($root, $dir, $file);
  186. }
  187. // Get modules & resolve file
  188. foreach ($rsearch as $sfile => $_)
  189. {
  190. if (substr($sfile, 0, 5) === 'dist~')
  191. {
  192. continue;
  193. }
  194. if (isset($opt['resolved'][ fs::ds($dir, $sfile) ]))
  195. {
  196. continue;
  197. }
  198. list($kfile, $cfile, $module) =
  199. static::resolve_file_module($sfile, $modules);
  200. if ($module && !in_array($module, $opt['use-modules']))
  201. {
  202. $opt['use-modules'][] = $module;
  203. }
  204. $opt['resolved'][ fs::ds($dir, $sfile) ] = [
  205. 'id' => $dir,
  206. 'module' => $module ? $modules[$module] : [],
  207. 'flags' => $flags,
  208. // File in source directory
  209. 'compressed' => fs::ds($dir, $cfile),
  210. 'source' => fs::ds($dir, $sfile),
  211. 'resolved' => fs::ds($dir, $kfile),
  212. // File in dist~ directory
  213. 'compressed_dist' => fs::ds($dir, 'dist~', $cfile),
  214. 'source_dist' => fs::ds($dir, 'dist~', $sfile),
  215. 'resolved_dist' => fs::ds($dir, 'dist~', $kfile),
  216. ];
  217. }
  218. }
  219. }
  220. return $map;
  221. }
  222. /**
  223. * Get module and new file extention for particular file.
  224. * --
  225. * @param string $file
  226. * @param array $modules
  227. * --
  228. * @return [ string $filename, string $filename_compressed, string $module_id ]
  229. */
  230. static function resolve_file_module($file, $modules)
  231. {
  232. // Do we have match in modules?
  233. foreach ($modules as $ext => $opt)
  234. {
  235. if (strtolower(substr($file, -(strlen($ext)))) === strtolower($ext))
  236. {
  237. return [
  238. substr($file, 0, -(strlen($ext))) . $opt['produce'],
  239. substr($file, 0, -(strlen($ext))) . 'min.' . $opt['produce'],
  240. $ext
  241. ];
  242. }
  243. }
  244. return [ $file, $file, null ];
  245. }
  246. /**
  247. * Get full absolute path to the package's assets (in package).
  248. * --
  249. * @param string $package
  250. * --
  251. * @return string
  252. */
  253. static function path($package)
  254. {
  255. $meta = pkg::get_meta($package);
  256. $path = pkg::get_path($package);
  257. $assets = 'assets';
  258. if (isset($meta['assets']))
  259. {
  260. if (isset($meta['assets']['path']))
  261. {
  262. $assets = $meta['assets']['path'];
  263. }
  264. }
  265. return fs::ds($path, $assets);
  266. }
  267. /**
  268. * Get full absolute public path to the package's assets.
  269. * --
  270. * @param string $package
  271. * --
  272. * @return string
  273. */
  274. static function pubpath($package)
  275. {
  276. return fs::pubpath("assets", $package);
  277. }
  278. /**
  279. * Return an ID from full absolute file path.
  280. * This will check DEV ids only.
  281. * --
  282. * @param string $path Full absolute path inc. filename.
  283. * @param string $root Assets root.
  284. * @param array $map Map.
  285. * --
  286. * @return string
  287. */
  288. static function id_from_file($path, $root, array $map)
  289. {
  290. $path_seg = trim(substr($path, strlen($root)), '\\/');
  291. $file = file::name($path);
  292. foreach ($map['includes'] as $id => $opt)
  293. {
  294. if (strpos($path_seg, $id) !== 0)
  295. {
  296. continue;
  297. }
  298. if (!isset($opt['files']))
  299. {
  300. $opt['files'] = [ '*.*' ];
  301. }
  302. if (!is_array($opt['files']))
  303. {
  304. $opt['files'] = [ $opt['files'] ];
  305. }
  306. foreach ($opt['files'] as $include)
  307. {
  308. if (strpos($include, '*') !== false)
  309. {
  310. $filter = fs::filter_to_regex($include);
  311. if (preg_match($filter, $file))
  312. {
  313. return $id;
  314. }
  315. }
  316. else
  317. {
  318. if ($include === $file)
  319. {
  320. return $id;
  321. }
  322. }
  323. }
  324. }
  325. // Not found
  326. return null;
  327. }
  328. /**
  329. * Get URL to the specific resource.
  330. * --
  331. * @param string $...
  332. * --
  333. * @return string
  334. */
  335. static function url()
  336. {
  337. return preg_replace(
  338. '/[\/\\\\]+/',
  339. '/',
  340. '/assets/'.implode('/', func_get_args())
  341. );
  342. }
  343. /**
  344. * Get HTML tags.
  345. * --
  346. * @param string $id
  347. * @param string $package
  348. * --
  349. * @return array
  350. */
  351. static function get_tags($id, $package)
  352. {
  353. $links = static::get_links($id, $package);
  354. $map = static::map($package);
  355. $tags = $map['tags'];
  356. foreach ($links as $id => &$link)
  357. {
  358. $ext = substr(file::extension($link), 1);
  359. foreach ($tags as $tag)
  360. {
  361. if (in_array($ext, $tag['match']))
  362. {
  363. $link = str_replace('{link}', $link, $tag['tag']);
  364. continue 2;
  365. }
  366. }
  367. unset($link);
  368. unset($links[$id]);
  369. }
  370. return $links;
  371. }
  372. /**
  373. * Get links.
  374. * --
  375. * @param string $id
  376. * @param string $package
  377. * --
  378. * @throws mysli\assets\exception\assets 10 No such ID.
  379. * --
  380. * @return array
  381. */
  382. static function get_links($id, $package)
  383. {
  384. $type = pkg::exists_as($package);
  385. $map = static::map($package);
  386. $path = static::pubpath($package);
  387. if (!isset($map['includes'][$id]))
  388. throw new exception\assets("No such ID: `{$id}`", 10);
  389. if ($type === pkg::phar && isset($map['includes'][$id]['merge']))
  390. {
  391. return [ static::url($package, $id, $map['includes'][$id]['merge']) ];
  392. }
  393. $links = [];
  394. $modules = isset($map['modules']) ? $map['modules'] : [];
  395. if (isset($map['includes'][$id]['modules']))
  396. {
  397. $modules = array_merge($modules, $map['includes'][$id]['modules']);
  398. }
  399. if (!isset($map['includes'][$id]['files']))
  400. {
  401. $map['includes'][$id]['files'] = [ '*.*' ];
  402. }
  403. // Need to be an array
  404. if (!is_array($map['includes'][$id]['files']))
  405. {
  406. $map['includes'][$id]['files'] = [ $map['includes'][$id]['files'] ];
  407. }
  408. foreach ($map['includes'][$id]['files'] as $file)
  409. {
  410. if (strpos($file, ' !') !== false)
  411. {
  412. $file = explode(' !', $file, 2)[0];
  413. }
  414. list($file, $_, $_) = static::resolve_file_module($file, $modules);
  415. if (strpos($file, '*') !== false)
  416. {
  417. $rsearch = file::find(fs::ds($path, $id), $file);
  418. }
  419. else
  420. {
  421. $rsearch[$file] = fs::ds($path, $id, $file);
  422. }
  423. foreach ($rsearch as $rfile => $afile)
  424. {
  425. if (file::exists($afile))
  426. {
  427. $url = static::url($package, $id, $rfile);
  428. if (!in_array($url, $links))
  429. {
  430. $links[] = $url;
  431. }
  432. }
  433. }
  434. }
  435. return $links;
  436. }
  437. /*
  438. --- Protected --------------------------------------------------------------
  439. */
  440. /**
  441. * Resolve internally linked modules.
  442. * Example of `styl` build referencing `css` build.
  443. * css:
  444. * produce: css
  445. * require: [ ... ]
  446. * process: [ ... ]
  447. * build: [ ... ]
  448. * styl:
  449. * produce: ...
  450. * require: [ ... ]
  451. * process: [ ... ]
  452. * build: &css.build
  453. * --
  454. * @param array $modules
  455. * --
  456. * @throws mysli\assets\exception\assets 10 Invalid reference.
  457. * --
  458. * @return array Resolved modules.
  459. */
  460. protected static function resolve_links(array $modules)
  461. {
  462. foreach ($modules as $pid => &$module)
  463. {
  464. foreach (['require', 'process', 'build'] as $item)
  465. {
  466. if (!isset($module[$item]))
  467. {
  468. $module[$item] = '';
  469. continue;
  470. }
  471. while (is_string($module[$item]) && substr($module[$item], 0, 1) === '$')
  472. {
  473. list($id, $key) = explode('.', substr($module[$item], 1));
  474. if (!isset($modules[$id]) || !isset($modules[$id][$key]))
  475. throw new exception\assets(
  476. "Invalid reference `{$id}.{$key}`, not found. ".
  477. "For: `{$pid}.{$item}`", 10
  478. );
  479. $module[$item] = $modules[$id][$key];
  480. }
  481. }
  482. unset($module);
  483. }
  484. return $modules;
  485. }
  486. }