Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Highlighter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Tempest\Highlight;

use ReflectionClass;
use Tempest\Highlight\Languages\Apache\ApacheLanguage;
use Tempest\Highlight\Languages\Base\Injections\GutterInjection;
use Tempest\Highlight\Languages\Bash\BashLanguage;
use Tempest\Highlight\Languages\BBCode\BBCodeLanguage;
Expand Down Expand Up @@ -55,7 +56,8 @@ final class Highlighter

public function __construct(private readonly Theme $theme = new CssTheme())
{
$this->addLanguage(new BashLanguage())
$this->addLanguage(new ApacheLanguage())
->addLanguage(new BashLanguage())
->addLanguage(new BBCodeLanguage())
->addLanguage(new BladeLanguage())
->addLanguage(new CssLanguage())
Expand Down
45 changes: 45 additions & 0 deletions src/Languages/Apache/ApacheLanguage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Apache;

use Override;
use Tempest\Highlight\Languages\Apache\Patterns\ApacheCloseTagPattern;
use Tempest\Highlight\Languages\Apache\Patterns\ApacheCommentPattern;
use Tempest\Highlight\Languages\Apache\Patterns\ApacheDirectivePattern;
use Tempest\Highlight\Languages\Apache\Patterns\ApacheFlagPattern;
use Tempest\Highlight\Languages\Apache\Patterns\ApacheOpenTagPattern;
use Tempest\Highlight\Languages\Base\BaseLanguage;
use Tempest\Highlight\Languages\Php\Patterns\DoubleQuoteValuePattern;

final class ApacheLanguage extends BaseLanguage
{
public function getName(): string
{
return 'apache';
}

#[Override]
public function getAliases(): array
{
return [
'apacheconf',
'htaccess',
];
}

#[Override]
public function getPatterns(): array
{
return [
...parent::getPatterns(),
new ApacheCommentPattern(),
new ApacheDirectivePattern(),
new ApacheOpenTagPattern(),
new ApacheCloseTagPattern(),
new ApacheFlagPattern(),
new DoubleQuoteValuePattern(),
];
}
}
28 changes: 28 additions & 0 deletions src/Languages/Apache/Patterns/ApacheCloseTagPattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Apache\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\PatternTest;
use Tempest\Highlight\Tokens\TokenTypeEnum;

#[PatternTest(input: '</VirtualHost>', output: 'VirtualHost')]
#[PatternTest(input: '</Directory>', output: 'Directory')]
#[PatternTest(input: '</IfModule>', output: 'IfModule')]
final readonly class ApacheCloseTagPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '<\/(?<match>[A-Za-z]\w*)';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::KEYWORD;
}
}
27 changes: 27 additions & 0 deletions src/Languages/Apache/Patterns/ApacheCommentPattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Apache\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\PatternTest;
use Tempest\Highlight\Tokens\TokenTypeEnum;

#[PatternTest(input: '# This is a comment', output: '# This is a comment')]
#[PatternTest(input: 'ServerName example.com # inline', output: '# inline')]
final readonly class ApacheCommentPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '(?<match>#.*)';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::COMMENT;
}
}
55 changes: 55 additions & 0 deletions src/Languages/Apache/Patterns/ApacheDirectivePattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Apache\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\PatternTest;
use Tempest\Highlight\Tokens\TokenTypeEnum;

#[PatternTest(input: 'ServerName example.com', output: 'ServerName')]
#[PatternTest(input: 'DocumentRoot /var/www/html', output: 'DocumentRoot')]
#[PatternTest(input: ' RewriteRule ^(.*)$ index.php [L]', output: 'RewriteRule')]
#[PatternTest(input: 'Listen 80', output: 'Listen')]
#[PatternTest(input: 'SSLEngine on', output: 'SSLEngine')]
final readonly class ApacheDirectivePattern implements Pattern
{
use IsPattern;

private const DIRECTIVES = [
'ServerRoot', 'Listen', 'LoadModule',
'ServerAdmin', 'ServerName', 'ServerAlias',
'DocumentRoot', 'ErrorLog', 'CustomLog', 'LogLevel', 'LogFormat',
'DirectoryIndex', 'Options', 'AllowOverride',
'Require', 'Order', 'Allow', 'Deny',
'Include', 'IncludeOptional',
'RewriteEngine', 'RewriteCond', 'RewriteRule', 'RewriteBase',
'SSLEngine', 'SSLCertificateFile', 'SSLCertificateKeyFile', 'SSLCertificateChainFile',
'ProxyPass', 'ProxyPassReverse', 'ProxyPreserveHost',
'Header', 'RequestHeader',
'SetEnv', 'SetEnvIf', 'PassEnv',
'Redirect', 'RedirectMatch', 'RedirectPermanent',
'Alias', 'ScriptAlias',
'AddType', 'AddHandler', 'AddOutputFilterByType',
'ErrorDocument',
'Timeout', 'KeepAlive', 'MaxKeepAliveRequests', 'KeepAliveTimeout',
'AccessFileName', 'DefaultType', 'HostnameLookups',
'UseCanonicalName', 'ServerSignature', 'ServerTokens',
'TypesConfig', 'MIMEMagicFile',
'ExpiresActive', 'ExpiresByType', 'ExpiresDefault',
];

public function getPattern(): string
{
$directives = implode('|', self::DIRECTIVES);

return "/^\s*(?<match>{$directives})\b/m";
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::KEYWORD;
}
}
31 changes: 31 additions & 0 deletions src/Languages/Apache/Patterns/ApacheFlagPattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Apache\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\PatternTest;
use Tempest\Highlight\Tokens\TokenTypeEnum;

#[PatternTest(input: 'SSLEngine on', output: 'on')]
#[PatternTest(input: 'SSLEngine off', output: 'off')]
#[PatternTest(input: 'Options All', output: 'All')]
#[PatternTest(input: 'Options None', output: 'None')]
#[PatternTest(input: 'Require all granted', output: 'granted')]
#[PatternTest(input: 'Require all denied', output: 'denied')]
final readonly class ApacheFlagPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '/\b(?<match>on|off|All|None|granted|denied)\b/';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::VALUE;
}
}
30 changes: 30 additions & 0 deletions src/Languages/Apache/Patterns/ApacheOpenTagPattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Apache\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\PatternTest;
use Tempest\Highlight\Tokens\TokenTypeEnum;

#[PatternTest(input: '<VirtualHost *:80>', output: 'VirtualHost')]
#[PatternTest(input: '<Directory /var/www>', output: 'Directory')]
#[PatternTest(input: '<Location /admin>', output: 'Location')]
#[PatternTest(input: '<IfModule mod_ssl.c>', output: 'IfModule')]
#[PatternTest(input: '<FilesMatch "\.php$">', output: 'FilesMatch')]
final readonly class ApacheOpenTagPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '<(?<match>[A-Za-z]\w*)';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::KEYWORD;
}
}
37 changes: 37 additions & 0 deletions tests/Bench/Fixtures/apache.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Apache2 Virtual Host Configuration

<VirtualHost *:80>
ServerAdmin webmaster@example.com
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/html

<Directory /var/www/html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

RewriteEngine on
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>

<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/html

SSLEngine on
SSLCertificateFile "/etc/ssl/certs/server.crt"
SSLCertificateKeyFile "/etc/ssl/private/server.key"

<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000"
</IfModule>

ProxyPass /api http://localhost:3000
ProxyPassReverse /api http://localhost:3000
</VirtualHost>
1 change: 1 addition & 0 deletions tests/Bench/HighlighterBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ final class HighlighterBench
private const string FIXTURES_DIR = __DIR__ . '/Fixtures';

public const array LANGUAGES = [
'apache' => 'apache.txt',
'bash' => 'bash.txt',
'bbcode' => 'bbcode.txt',
'blade' => 'blade.txt',
Expand Down
84 changes: 84 additions & 0 deletions tests/Languages/Apache/ApacheLanguageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Languages\Apache;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Tempest\Highlight\Highlighter;

final class ApacheLanguageTest extends TestCase
{
#[DataProvider('provide_highlight_cases')]
public function test_highlight(string $content, string $expected): void
{
$highlighter = new Highlighter();

$this->assertSame(
$expected,
$highlighter->parse($content, 'apache'),
);

$this->assertSame($expected, $highlighter->parse($content, 'apacheconf'));
$this->assertSame($expected, $highlighter->parse($content, 'htaccess'));
}

public static function provide_highlight_cases(): iterable
{
return [
'comments' => [
<<<'APACHE'
# This is a comment
ServerName example.com
APACHE,
'<span class="hl-comment"># This is a comment</span>
<span class="hl-keyword">ServerName</span> example.com',
],
'virtual host block' => [
<<<'APACHE'
<VirtualHost *:80>
ServerName example.com
DocumentRoot "/var/www/html"
</VirtualHost>
APACHE,
'&lt;<span class="hl-keyword">VirtualHost</span> *:80&gt;
<span class="hl-keyword">ServerName</span> example.com
<span class="hl-keyword">DocumentRoot</span> <span class="hl-value">&quot;/var/www/html&quot;</span>
&lt;/<span class="hl-keyword">VirtualHost</span>&gt;',
],
'rewrite rules' => [
<<<'APACHE'
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L]
APACHE,
'<span class="hl-keyword">RewriteEngine</span> <span class="hl-value">on</span>
<span class="hl-keyword">RewriteCond</span> %{REQUEST_FILENAME} !-f
<span class="hl-keyword">RewriteRule</span> ^(.*)$ index.php [L]',
],
'ssl configuration' => [
<<<'APACHE'
SSLEngine on
SSLCertificateFile "/etc/ssl/certs/server.crt"
APACHE,
'<span class="hl-keyword">SSLEngine</span> <span class="hl-value">on</span>
<span class="hl-keyword">SSLCertificateFile</span> <span class="hl-value">&quot;/etc/ssl/certs/server.crt&quot;</span>',
],
'directory block with flags' => [
<<<'APACHE'
<Directory /var/www>
Options All
AllowOverride None
Require all granted
</Directory>
APACHE,
'&lt;<span class="hl-keyword">Directory</span> /var/www&gt;
<span class="hl-keyword">Options</span> <span class="hl-value">All</span>
<span class="hl-keyword">AllowOverride</span> <span class="hl-value">None</span>
<span class="hl-keyword">Require</span> all <span class="hl-value">granted</span>
&lt;/<span class="hl-keyword">Directory</span>&gt;',
],
];
}
}
Loading