Spaces:
Sleeping
Sleeping
Download bridge/src/Tokens.php from acecalisto3/UIKitV3-Automator: direct link, hf CLI and curl.
- Browser
- Download file 5.77 kB
-
https://huggingface.co/spaces/acecalisto3/UIKitV3-Automator/resolve/main/bridge/src/Tokens.php
- Command line
-
hf download hf://spaces/acecalisto3/UIKitV3-Automator/bridge/src/Tokens.php
-
curl -L -o Tokens.php https://huggingface.co/spaces/acecalisto3/UIKitV3-Automator/resolve/main/bridge/src/Tokens.php
5.77 kB
| /** | |
| * Design token graph. Framework-free on purpose: the Joomla component, the CLI | |
| * and any future automation all load tokens through this one class. | |
| */ | |
| namespace Fyoo\FigmaBridge; | |
| defined('_FYOO_BRIDGE') || define('_FYOO_BRIDGE', 1); | |
| class Tokens | |
| { | |
| public const GROUPS = ['color', 'typography', 'spacing', 'radius', 'shadow', 'breakpoints']; | |
| public array $data; | |
| public function __construct(array $data) | |
| { | |
| $this->data = $data; | |
| } | |
| public static function fromJson(string $json): self | |
| { | |
| $data = json_decode($json, true); | |
| if (!is_array($data)) { | |
| throw new \InvalidArgumentException('Token payload is not valid JSON.'); | |
| } | |
| return new self(self::validate($data)); | |
| } | |
| public static function fromFile(string $path): self | |
| { | |
| if (!is_readable($path)) { | |
| throw new \InvalidArgumentException("Cannot read token file: $path"); | |
| } | |
| return self::fromJson((string) file_get_contents($path)); | |
| } | |
| /** Trust boundary: everything below arrives from a Figma plugin over the network. */ | |
| public static function validate(array $data): array | |
| { | |
| foreach (['name', 'source', 'tokens'] as $key) { | |
| if (!isset($data[$key])) { | |
| throw new \InvalidArgumentException("Missing required key: $key"); | |
| } | |
| } | |
| if (!is_array($data['tokens'])) { | |
| throw new \InvalidArgumentException('tokens must be an object.'); | |
| } | |
| $clean = [ | |
| 'name' => self::text($data['name'], 120), | |
| 'source' => in_array($data['source'], ['figma', 'manual', 'api'], true) ? $data['source'] : 'manual', | |
| 'version' => self::text($data['version'] ?? '0.1', 20), | |
| 'generatedAt' => self::text($data['generatedAt'] ?? gmdate('c'), 40), | |
| 'fileKey' => self::text($data['fileKey'] ?? '', 64), | |
| 'tokens' => [], | |
| 'components' => [], | |
| 'assets' => [], | |
| ]; | |
| foreach (self::GROUPS as $group) { | |
| $clean['tokens'][$group] = []; | |
| foreach ((array) ($data['tokens'][$group] ?? []) as $key => $value) { | |
| $key = self::key((string) $key); | |
| if ($key === '') { | |
| continue; | |
| } | |
| $value = $group === 'typography' ? self::typography($value) : self::value($group, $value); | |
| if ($value !== null) { | |
| $clean['tokens'][$group][$key] = $value; | |
| } | |
| } | |
| } | |
| foreach ((array) ($data['components'] ?? []) as $name => $def) { | |
| $clean['components'][self::text((string) $name, 120)] = [ | |
| 'role' => self::key((string) ($def['role'] ?? '')), | |
| 'target' => self::key((string) ($def['target'] ?? '')), | |
| 'overrides' => array_map([self::class, 'cssValue'], (array) ($def['overrides'] ?? [])), | |
| ]; | |
| } | |
| foreach ((array) ($data['assets'] ?? []) as $asset) { | |
| if (!isset($asset['name'])) { | |
| continue; | |
| } | |
| $type = strtolower((string) ($asset['type'] ?? 'svg')); | |
| if (!in_array($type, ['svg', 'png', 'jpg'], true)) { | |
| continue; | |
| } | |
| $clean['assets'][] = [ | |
| 'name' => preg_replace('/[^A-Za-z0-9_.\-]/', '', (string) $asset['name']), | |
| 'type' => $type, | |
| 'data' => (string) ($asset['data'] ?? ''), | |
| ]; | |
| } | |
| return $clean; | |
| } | |
| public function group(string $name): array | |
| { | |
| return $this->data['tokens'][$name] ?? []; | |
| } | |
| public function count(): int | |
| { | |
| return array_sum(array_map('count', $this->data['tokens'])); | |
| } | |
| public function toJson(): string | |
| { | |
| return json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); | |
| } | |
| private static function text(string $value, int $max): string | |
| { | |
| return substr(trim(strip_tags($value)), 0, $max); | |
| } | |
| private static function key(string $value): string | |
| { | |
| $value = strtolower(trim($value)); | |
| $value = preg_replace('~[\s_/]+~', '-', $value); | |
| return trim(preg_replace('~[^a-z0-9.\-]~', '', (string) $value), '-'); | |
| } | |
| /** Rejects anything that could escape the Less declaration it is written into. */ | |
| public static function cssValue($value): ?string | |
| { | |
| if (is_numeric($value)) { | |
| return (string) $value; | |
| } | |
| if (!is_string($value)) { | |
| return null; | |
| } | |
| $value = trim($value); | |
| if ($value === '' || strlen($value) > 200 || preg_match('~[{};@\\\\]|/\*|url\s*\(|expression~i', $value)) { | |
| return null; | |
| } | |
| return $value; | |
| } | |
| private static function value(string $group, $value): ?string | |
| { | |
| $value = self::cssValue($value); | |
| if ($value === null) { | |
| return null; | |
| } | |
| if ($group === 'color' && !preg_match('~^(#[0-9a-f]{3,8}|rgba?\([\d\s.,%]+\)|hsla?\([\d\s.,%deg]+\))$~i', $value)) { | |
| return null; | |
| } | |
| return $value; | |
| } | |
| private static function typography($value): ?array | |
| { | |
| if (!is_array($value)) { | |
| return null; | |
| } | |
| $out = []; | |
| foreach (['fontFamily', 'fontSize', 'fontWeight', 'lineHeight', 'letterSpacing', 'textTransform'] as $prop) { | |
| if (isset($value[$prop])) { | |
| $clean = self::cssValue($value[$prop]); | |
| if ($clean !== null) { | |
| $out[$prop] = $clean; | |
| } | |
| } | |
| } | |
| return $out ?: null; | |
| } | |
| } | |