| } | ||
| + public function highlight( FileRenderer $renderer, string $content ): string { | ||
| + $lang = match( $this->mediaType ) { | ||
| + 'text/x-php', 'application/x-php', 'application/x-httpd-php' => 'php', | ||
| + 'text/html' => 'html', | ||
| + 'text/css' => 'css', | ||
| + 'application/javascript', 'text/javascript', 'text/x-javascript' => 'javascript', | ||
| + 'application/json', 'text/json', 'application/x-json' => 'json', | ||
| + 'application/xml', 'text/xml', 'image/svg+xml' => 'xml', | ||
| + 'text/x-shellscript', 'application/x-sh' => 'bash', | ||
| + 'text/x-c', 'text/x-csrc' => 'c', | ||
| + 'text/x-c++src', 'text/x-c++', 'text/x-cpp' => 'cpp', | ||
| + 'text/x-java', 'text/x-java-source', 'application/java-archive' => 'java', | ||
| + 'text/x-python', 'application/x-python-code' => 'python', | ||
| + 'text/x-ruby', 'application/x-ruby' => 'ruby', | ||
| + 'text/x-go', 'text/go' => 'go', | ||
| + 'text/rust', 'text/x-rust' => 'rust', | ||
| + 'text/x-lua', 'text/lua' => 'lua', | ||
| + 'text/markdown', 'text/x-markdown' => 'markdown', | ||
| + 'text/x-r', 'text/x-r-source', 'application/R' => 'r', | ||
| + 'application/sql', 'text/sql', 'text/x-sql' => 'sql', | ||
| + 'text/yaml', 'text/x-yaml', 'application/yaml' => 'yaml', | ||
| + 'application/typescript', 'text/typescript' => 'typescript', | ||
| + default => null | ||
| + }; | ||
| + | ||
| + if ( $lang === null ) { | ||
| + $ext = strtolower( pathinfo( $this->name, PATHINFO_EXTENSION ) ); | ||
| + | ||
| + $lang = match( $ext ) { | ||
| + 'php', 'phtml', 'php8', 'php7' => 'php', | ||
| + 'c', 'h' => 'c', | ||
| + 'cpp', 'hpp', 'cc', 'cxx' => 'cpp', | ||
| + 'java' => 'java', | ||
| + 'js', 'jsx', 'mjs' => 'javascript', | ||
| + 'ts', 'tsx' => 'typescript', | ||
| + 'go' => 'go', | ||
| + 'rs' => 'rust', | ||
| + 'py', 'pyw' => 'python', | ||
| + 'rb', 'erb' => 'ruby', | ||
| + 'lua' => 'lua', | ||
| + 'sh', 'bash', 'zsh' => 'bash', | ||
| + 'md', 'markdown' => 'markdown', | ||
| + 'rmd' => 'rmd', | ||
| + 'r' => 'r', | ||
| + 'xml', 'svg' => 'xml', | ||
| + 'html', 'htm' => 'html', | ||
| + 'css' => 'css', | ||
| + 'json', 'lock' => 'json', | ||
| + 'sql' => 'sql', | ||
| + 'yaml', 'yml' => 'yaml', | ||
| + default => 'text' | ||
| + }; | ||
| + } | ||
| + | ||
| + return $renderer->highlight( $content, $lang ); | ||
| + } | ||
| + | ||
| public function isImage(): bool { | ||
| return $this->category === self::CAT_IMAGE; | ||
| private function detectMediaType( string $buffer ): string { | ||
| + if ( $buffer === '' ) return 'application/x-empty'; | ||
| + | ||
| $finfo = new finfo( FILEINFO_MIME_TYPE ); | ||
| $mediaType = $finfo->buffer( $buffer ); | ||
| echo '<div class="blob-content"><pre class="blob-code">' . | ||
| - htmlspecialchars( $content ) . '</pre></div>'; | ||
| + $file->highlight( $renderer, $content ) . '</pre></div>'; | ||
| } | ||
| } else { |
| public function renderSize( int $bytes ): void; | ||
| -} | ||
| - | ||
| -class HtmlFileRenderer implements FileRenderer { | ||
| - private string $repoSafeName; | ||
| - private string $currentPath; | ||
| - | ||
| - public function __construct( string $repoSafeName, string $currentPath = '' ) { | ||
| - $this->repoSafeName = $repoSafeName; | ||
| - $this->currentPath = trim( $currentPath, '/' ); | ||
| - } | ||
| - | ||
| - public function renderListEntry( | ||
| - string $name, | ||
| - string $sha, | ||
| - string $mode, | ||
| - string $iconClass, | ||
| - int $timestamp, | ||
| - int $size | ||
| - ): void { | ||
| - $fullPath = ($this->currentPath === '' ? '' : $this->currentPath . '/') . | ||
| - $name; | ||
| - | ||
| - $url = '?repo=' . urlencode( $this->repoSafeName ) . | ||
| - '&hash=' . $sha . | ||
| - '&name=' . urlencode( $fullPath ); | ||
| - | ||
| - echo '<a href="' . $url . '" class="file-item">'; | ||
| - echo '<span class="file-mode">' . $mode . '</span>'; | ||
| - echo '<span class="file-name">'; | ||
| - echo '<i class="fas ' . $iconClass . ' file-icon-container"></i>'; | ||
| - echo htmlspecialchars( $name ); | ||
| - echo '</span>'; | ||
| - | ||
| - if( $size > 0 ) { | ||
| - echo '<span class="file-size">' . $this->formatSize( $size ) . '</span>'; | ||
| - } | ||
| - | ||
| - if( $timestamp > 0 ) { | ||
| - echo '<span class="file-date">'; | ||
| - $this->renderTime( $timestamp ); | ||
| - echo '</span>'; | ||
| - } | ||
| - | ||
| - echo '</a>'; | ||
| - } | ||
| - | ||
| - public function renderMedia( | ||
| - File $file, | ||
| - string $url, | ||
| - string $mediaType | ||
| - ): bool { | ||
| - $rendered = false; | ||
| - | ||
| - if( $file->isImage() ) { | ||
| - echo '<div class="blob-content blob-content-image">' . | ||
| - '<img src="' . $url . '"></div>'; | ||
| - $rendered = true; | ||
| - } elseif( $file->isVideo() ) { | ||
| - echo '<div class="blob-content blob-content-video">' . | ||
| - '<video controls><source src="' . $url . '" type="' . | ||
| - $mediaType . '"></video></div>'; | ||
| - $rendered = true; | ||
| - } elseif( $file->isAudio() ) { | ||
| - echo '<div class="blob-content blob-content-audio">' . | ||
| - '<audio controls><source src="' . $url . '" type="' . | ||
| - $mediaType . '"></audio></div>'; | ||
| - $rendered = true; | ||
| - } | ||
| - | ||
| - return $rendered; | ||
| - } | ||
| - | ||
| - public function renderSize( int $bytes ): void { | ||
| - echo $this->formatSize( $bytes ); | ||
| - } | ||
| - | ||
| - private function renderTime( int $timestamp ): void { | ||
| - $tokens = [ | ||
| - 31536000 => 'year', | ||
| - 2592000 => 'month', | ||
| - 604800 => 'week', | ||
| - 86400 => 'day', | ||
| - 3600 => 'hour', | ||
| - 60 => 'minute', | ||
| - 1 => 'second' | ||
| - ]; | ||
| - | ||
| - $diff = $timestamp ? time() - $timestamp : null; | ||
| - $result = 'never'; | ||
| - | ||
| - if( $diff && $diff >= 5 ) { | ||
| - foreach( $tokens as $unit => $text ) { | ||
| - if( $diff < $unit ) { | ||
| - continue; | ||
| - } | ||
| - | ||
| - $num = floor( $diff / $unit ); | ||
| - $result = $num . ' ' . $text . ($num > 1 ? 's' : '') . ' ago'; | ||
| - break; | ||
| - } | ||
| - } elseif( $diff ) { | ||
| - $result = 'just now'; | ||
| - } | ||
| - | ||
| - echo $result; | ||
| - } | ||
| - | ||
| - private function formatSize( int $bytes ): string { | ||
| - $units = [ 'B', 'KB', 'MB', 'GB', 'TB' ]; | ||
| - $i = 0; | ||
| - | ||
| - while( $bytes >= 1024 && $i < count( $units ) - 1 ) { | ||
| - $bytes /= 1024; | ||
| - $i++; | ||
| - } | ||
| - return ($bytes === 0 ? 0 : round( $bytes )) . ' ' . $units[$i]; | ||
| - } | ||
| + public function highlight( string $content, string $language ): string; | ||
| } | ||
| +<?php | ||
| +require_once __DIR__ . '/LanguageDefinitions.php'; | ||
| + | ||
| +class Highlighter { | ||
| + private string $content; | ||
| + private string $lang; | ||
| + private array $rules; | ||
| + | ||
| + public function __construct(string $content, string $lang) { | ||
| + $this->content = $content; | ||
| + $this->lang = strtolower($lang); | ||
| + $this->rules = LanguageDefinitions::get($this->lang); | ||
| + } | ||
| + | ||
| + public function render(): string { | ||
| + if (empty($this->rules)) { | ||
| + return htmlspecialchars($this->content); | ||
| + } | ||
| + | ||
| + $patterns = []; | ||
| + foreach ($this->rules as $name => $pattern) { | ||
| + $delim = $pattern[0]; | ||
| + $inner = substr($pattern, 1, strrpos($pattern, $delim) - 1); | ||
| + $patterns[] = "(?P<$name>$inner)"; | ||
| + } | ||
| + | ||
| + $combined = '/' . implode('|', $patterns) . '/msu'; | ||
| + | ||
| + return preg_replace_callback($combined, function ($matches) { | ||
| + foreach ($matches as $key => $value) { | ||
| + if (!is_numeric($key) && $value !== '') { | ||
| + if ($key === 'string_interp') { | ||
| + return $this->renderInterpolatedString($value); | ||
| + } | ||
| + | ||
| + return '<span class="hl-' . $key . '">' . htmlspecialchars($value) . '</span>'; | ||
| + } | ||
| + } | ||
| + | ||
| + return htmlspecialchars($matches[0]); | ||
| + }, $this->content); | ||
| + } | ||
| + | ||
| + private function renderInterpolatedString(string $content): string { | ||
| + $pattern = '/(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*|\$\{[^}]+\})/'; | ||
| + $parts = preg_split($pattern, $content, -1, PREG_SPLIT_DELIM_CAPTURE); | ||
| + $output = '<span class="hl-string">'; | ||
| + | ||
| + foreach ($parts as $part) { | ||
| + if (str_starts_with($part, '$')) { | ||
| + $output .= '<span class="hl-variable">' . htmlspecialchars($part) . '</span>'; | ||
| + } else { | ||
| + $output .= htmlspecialchars($part); | ||
| + } | ||
| + } | ||
| + | ||
| + $output .= '</span>'; | ||
| + | ||
| + return $output; | ||
| + } | ||
| +} | ||
| +<?php | ||
| +require_once __DIR__ . '/Highlighter.php'; | ||
| +require_once __DIR__ . '/FileRenderer.php'; | ||
| + | ||
| +class HtmlFileRenderer implements FileRenderer { | ||
| + private string $repoSafeName; | ||
| + private string $currentPath; | ||
| + | ||
| + public function __construct( string $repoSafeName, string $currentPath = '' ) { | ||
| + $this->repoSafeName = $repoSafeName; | ||
| + $this->currentPath = trim( $currentPath, '/' ); | ||
| + } | ||
| + | ||
| + public function renderListEntry( | ||
| + string $name, | ||
| + string $sha, | ||
| + string $mode, | ||
| + string $iconClass, | ||
| + int $timestamp, | ||
| + int $size | ||
| + ): void { | ||
| + $fullPath = ($this->currentPath === '' ? '' : $this->currentPath . '/') . | ||
| + $name; | ||
| + | ||
| + $url = '?repo=' . urlencode( $this->repoSafeName ) . | ||
| + '&hash=' . $sha . | ||
| + '&name=' . urlencode( $fullPath ); | ||
| + | ||
| + echo '<a href="' . $url . '" class="file-item">'; | ||
| + echo '<span class="file-mode">' . $mode . '</span>'; | ||
| + echo '<span class="file-name">'; | ||
| + echo '<i class="fas ' . $iconClass . ' file-icon-container"></i>'; | ||
| + echo htmlspecialchars( $name ); | ||
| + echo '</span>'; | ||
| + | ||
| + if( $size > 0 ) { | ||
| + echo '<span class="file-size">' . $this->formatSize( $size ) . '</span>'; | ||
| + } | ||
| + | ||
| + if( $timestamp > 0 ) { | ||
| + echo '<span class="file-date">'; | ||
| + $this->renderTime( $timestamp ); | ||
| + echo '</span>'; | ||
| + } | ||
| + | ||
| + echo '</a>'; | ||
| + } | ||
| + | ||
| + public function renderMedia( | ||
| + File $file, | ||
| + string $url, | ||
| + string $mediaType | ||
| + ): bool { | ||
| + $rendered = false; | ||
| + | ||
| + if( $file->isImage() ) { | ||
| + echo '<div class="blob-content blob-content-image">' . | ||
| + '<img src="' . $url . '"></div>'; | ||
| + $rendered = true; | ||
| + } elseif( $file->isVideo() ) { | ||
| + echo '<div class="blob-content blob-content-video">' . | ||
| + '<video controls><source src="' . $url . '" type="' . | ||
| + $mediaType . '"></video></div>'; | ||
| + $rendered = true; | ||
| + } elseif( $file->isAudio() ) { | ||
| + echo '<div class="blob-content blob-content-audio">' . | ||
| + '<audio controls><source src="' . $url . '" type="' . | ||
| + $mediaType . '"></audio></div>'; | ||
| + $rendered = true; | ||
| + } | ||
| + | ||
| + return $rendered; | ||
| + } | ||
| + | ||
| + public function renderSize( int $bytes ): void { | ||
| + echo $this->formatSize( $bytes ); | ||
| + } | ||
| + | ||
| + public function highlight( string $content, string $language ): string { | ||
| + return (new Highlighter($content, $language))->render(); | ||
| + } | ||
| + | ||
| + private function renderTime( int $timestamp ): void { | ||
| + $tokens = [ | ||
| + 31536000 => 'year', | ||
| + 2592000 => 'month', | ||
| + 604800 => 'week', | ||
| + 86400 => 'day', | ||
| + 3600 => 'hour', | ||
| + 60 => 'minute', | ||
| + 1 => 'second' | ||
| + ]; | ||
| + | ||
| + $diff = $timestamp ? time() - $timestamp : null; | ||
| + $result = 'never'; | ||
| + | ||
| + if( $diff && $diff >= 5 ) { | ||
| + foreach( $tokens as $unit => $text ) { | ||
| + if( $diff < $unit ) { | ||
| + continue; | ||
| + } | ||
| + | ||
| + $num = floor( $diff / $unit ); | ||
| + $result = $num . ' ' . $text . ($num > 1 ? 's' : '') . ' ago'; | ||
| + break; | ||
| + } | ||
| + } elseif( $diff ) { | ||
| + $result = 'just now'; | ||
| + } | ||
| + | ||
| + echo $result; | ||
| + } | ||
| + | ||
| + private function formatSize( int $bytes ): string { | ||
| + $units = [ 'B', 'KB', 'MB', 'GB', 'TB' ]; | ||
| + $i = 0; | ||
| + | ||
| + while( $bytes >= 1024 && $i < count( $units ) - 1 ) { | ||
| + $bytes /= 1024; | ||
| + $i++; | ||
| + } | ||
| + | ||
| + return ($bytes === 0 ? 0 : round( $bytes )) . ' ' . $units[$i]; | ||
| + } | ||
| +} | ||
| +<?php | ||
| +class LanguageDefinitions { | ||
| + public static function get(string $lang): array { | ||
| + $int = '(-?\b\d+(\.\d+)?\b)'; | ||
| + $str = '(".*?"|\'.*?\')'; | ||
| + $float = '(-?\d+(\.\d+)?([eE][+-]?\d+)?)'; | ||
| + | ||
| + $rules = [ | ||
| + 'php' => [ | ||
| + 'comment' => '/(\/\/.*$|#.*$|\/\*.*?\*\/)/m', | ||
| + 'string_interp' => '/(".*?")/', | ||
| + 'string' => '/(\'.*?\')/', | ||
| + 'keyword' => '/\b(abstract|and|array|as|break|callable|case|catch|class|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|new|or|print|private|protected|public|require|require_once|return|static|switch|throw|trait|try|unset|use|var|while|xor|yield)\b/', | ||
| + 'variable' => '/(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'bash' => [ | ||
| + 'comment' => '/(#.*$)/m', | ||
| + 'string_interp' => '/(".*?")/', | ||
| + 'string' => '/(\'.*?\')/', | ||
| + 'keyword' => '/\b(if|then|else|elif|fi|case|esac|for|while|until|do|done|function|local|return|exit|break|continue|export|source|alias|eval|exec)\b/', | ||
| + 'variable' => '/(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{[^}]+\})/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'c' => [ | ||
| + 'comment' => '/(\/\/.*$|\/\*.*?\*\/)/m', | ||
| + 'string' => '/' . $str . '/', | ||
| + 'keyword' => '/\b(auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'cpp' => [ | ||
| + 'comment' => '/(\/\/.*$|\/\*.*?\*\/)/m', | ||
| + 'string' => '/' . $str . '/', | ||
| + 'keyword' => '/\b(alignas|alignof|and|and_eq|asm|auto|bitand|bitor|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|false|float|for|friend|goto|if|inline|int|long|mutable|namespace|new|noexcept|not|not_eq|nullptr|operator|or|or_eq|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|true|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while|xor|xor_eq)\b/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'java' => [ | ||
| + 'comment' => '/(\/\/.*$|\/\*.*?\*\/)/m', | ||
| + 'string' => '/' . $str . '/', | ||
| + 'keyword' => '/\b(abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|native|new|package|private|protected|public|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|transient|try|void|volatile|while)\b/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'go' => [ | ||
| + 'comment' => '/(\/\/.*$|\/\*.*?\*\/)/m', | ||
| + 'string' => '/(".*?"|`.*?`)/s', | ||
| + 'keyword' => '/\b(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go|goto|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'rust' => [ | ||
| + 'comment' => '/(\/\/.*$|\/\*.*?\*\/)/m', | ||
| + 'string' => '/(".*?"|\'.*?\')/', | ||
| + 'keyword' => '/\b(as|break|const|continue|crate|else|enum|extern|false|fn|for|if|impl|in|let|loop|match|mod|move|mut|pub|ref|return|self|Self|static|struct|super|trait|true|type|unsafe|use|where|while|async|await|dyn)\b/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'python' => [ | ||
| + 'comment' => '/(#.*$)/m', | ||
| + 'string' => '/(\'\'\'.*?\'\'\'|""".*?"""|".*?"|\'.*?\')/s', | ||
| + 'keyword' => '/\b(False|None|True|and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\b/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'ruby' => [ | ||
| + 'comment' => '/(#.*$)/m', | ||
| + 'string_interp' => '/(".*?")/', | ||
| + 'string' => '/(\'.*?\')/', | ||
| + 'keyword' => '/\b(alias|and|begin|break|case|class|def|defined|do|else|elsif|end|ensure|false|for|if|in|module|next|nil|not|or|redo|rescue|retry|return|self|super|then|true|undef|unless|until|when|while|yield)\b/', | ||
| + 'variable' => '/(@[a-zA-Z_]\w*|\$[a-zA-Z_]\w*)/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'lua' => [ | ||
| + 'comment' => '/(--.*$)/m', | ||
| + 'string' => '/' . $str . '/', | ||
| + 'keyword' => '/\b(and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'javascript' => [ | ||
| + 'comment' => '/(\/\/.*$|\/\*.*?\*\/)/m', | ||
| + 'string' => '/(".*?"|\'.*?\'|`.*?`)/s', | ||
| + 'keyword' => '/\b(async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|export|extends|false|finally|for|function|if|import|in|instanceof|new|null|return|super|switch|this|throw|true|try|typeof|var|void|while|with|yield|let|static|enum)\b/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'typescript' => [ | ||
| + 'comment' => '/(\/\/.*$|\/\*.*?\*\/)/m', | ||
| + 'string' => '/(".*?"|\'.*?\'|`.*?`)/s', | ||
| + 'keyword' => '/\b(any|as|boolean|break|case|catch|class|const|continue|debugger|declare|default|delete|do|else|enum|export|extends|false|finally|for|from|function|if|implements|import|in|instanceof|interface|let|module|namespace|new|null|number|of|package|private|protected|public|require|return|static|string|super|switch|this|throw|true|try|type|typeof|var|void|while|with|yield)\b/', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'xml' => [ | ||
| + 'comment' => '/()/s', | ||
| + 'string' => '/' . $str . '/', | ||
| + 'keyword' => '/(<\/?[a-zA-Z0-9:-]+)/', | ||
| + 'variable' => '/([a-zA-Z0-9:-]+=)/', | ||
| + ], | ||
| + 'html' => [ | ||
| + 'comment' => '/()/s', | ||
| + 'string' => '/' . $str . '/', | ||
| + 'keyword' => '/(<\/?[a-zA-Z0-9:-]+)/', | ||
| + 'variable' => '/([a-zA-Z0-9:-]+=)/', | ||
| + ], | ||
| + 'css' => [ | ||
| + 'comment' => '/(\/\*.*?\*\/)/s', | ||
| + 'keyword' => '/(@[a-zA-Z-]+)/', | ||
| + 'string' => '/' . $str . '/', | ||
| + 'variable' => '/(\.|#)[a-zA-Z0-9_-]+/', | ||
| + 'number' => '/(-?(\d*\.)?\d+(px|em|rem|%|vh|vw|s|ms|deg))/', | ||
| + ], | ||
| + 'json' => [ | ||
| + 'keyword' => '/(".*?")(?=\s*:)/', | ||
| + 'string' => '/(".*?")/', | ||
| + 'number' => '/\b(-?\d+(\.\d+)?([eE][+-]?\d+)?|true|false|null)\b/', | ||
| + ], | ||
| + 'sql' => [ | ||
| + 'comment' => '/(--.*$|\/\*.*?\*\/)/m', | ||
| + 'string' => '/(\'.*?\')/', | ||
| + 'keyword' => '/\b(SELECT|FROM|WHERE|INSERT|UPDATE|DELETE|JOIN|LEFT|RIGHT|INNER|OUTER|ON|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|CREATE|TABLE|DROP|ALTER|INDEX|KEY|PRIMARY|FOREIGN|CONSTRAINT|DEFAULT|NULL|NOT|AND|OR|IN|VALUES|SET|AS|DISTINCT|UNION|ALL|CASE|WHEN|THEN|ELSE|END)\b/i', | ||
| + 'number' => '/' . $int . '/', | ||
| + ], | ||
| + 'yaml' => [ | ||
| + 'comment' => '/(#.*$)/m', | ||
| + 'keyword' => '/^(\s*[a-zA-Z0-9_-]+:)/m', | ||
| + 'string' => '/' . $str . '/', | ||
| + 'number' => '/' . $float . '/', | ||
| + ], | ||
| + 'markdown' => [ | ||
| + 'comment' => '/()/s', | ||
| + 'keyword' => '/^(#{1,6}\s+.*)$/m', | ||
| + 'string' => '/(\*\*.*?\*\*|__.*?__|\*.*?\*|_.*?_)/', | ||
| + 'variable' => '/(\[.*?\]\(.*?\))/', | ||
| + 'number' => '/^(\s*[-*+]\s|\s*\d+\.\s)/m', | ||
| + ], | ||
| + 'rmd' => [ | ||
| + 'comment' => '/()/s', | ||
| + 'keyword' => '/^(#{1,6}\s+.*)$/m', | ||
| + 'variable' => '/(`{3}\{r.*?`{3})/s', | ||
| + ], | ||
| + 'r' => [ | ||
| + 'comment' => '/(#.*$)/m', | ||
| + 'string' => '/' . $str . '/', | ||
| + 'keyword' => '/\b(if|else|repeat|while|function|for|in|next|break|TRUE|FALSE|NULL|Inf|NaN|NA)\b/', | ||
| + 'number' => '/' . $float . '/', | ||
| + ] | ||
| + ]; | ||
| + | ||
| + return $rules[strtolower($lang)] ?? []; | ||
| + } | ||
| +} | ||
| Author | Dave Jarvis <email> |
|---|---|
| Date | 2026-02-14 18:41:50 GMT-0800 |
| Commit | c67e733dce1606a04810d9f5a04393c1a9722e4d |
| Parent | d23f15b |
| Delta | 392 lines added, 118 lines removed, 274-line increase |