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
2 changes: 2 additions & 0 deletions src/Highlighter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Tempest\Highlight\Languages\Gdscript\GdscriptLanguage;
use Tempest\Highlight\Languages\Graphql\GraphqlLanguage;
use Tempest\Highlight\Languages\Html\HtmlLanguage;
use Tempest\Highlight\Languages\Http\HttpLanguage;
use Tempest\Highlight\Languages\Ini\IniLanguage;
use Tempest\Highlight\Languages\JavaScript\JavaScriptLanguage;
use Tempest\Highlight\Languages\Json\JsonLanguage;
Expand Down Expand Up @@ -73,6 +74,7 @@ public function __construct(
->addLanguage(new GdscriptLanguage())
->addLanguage(new GraphqlLanguage())
->addLanguage(new HtmlLanguage())
->addLanguage(new HttpLanguage())
->addLanguage(new JavaScriptLanguage())
->addLanguage(new JsonLanguage())
->addLanguage(new MarkdownLanguage())
Expand Down
51 changes: 51 additions & 0 deletions src/Languages/Http/HttpLanguage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Http;

use Tempest\Highlight\Languages\Base\BaseLanguage;
use Tempest\Highlight\Languages\Http\Patterns\HttpAttributePattern;
use Tempest\Highlight\Languages\Http\Patterns\HttpCommentPattern;
use Tempest\Highlight\Languages\Http\Patterns\HttpMethodPattern;
use Tempest\Highlight\Languages\Http\Patterns\HttpStatusPattern;
use Tempest\Highlight\Languages\Http\Patterns\HttpUrlPattern;
use Tempest\Highlight\Languages\Http\Patterns\HttpVersionPattern;

class HttpLanguage extends BaseLanguage
{
public function getName(): string
{
return 'http';
}

public function getAliases(): array
{
return ['https'];
}

public function getPatterns(): array
{
return [
...parent::getPatterns(),

// HTTP Comments
new HttpCommentPattern(),

// Start Line: Methods (GET, POST)
new HttpMethodPattern(),

// Start Line: HTTP Version (HTTP/1.1, HTTP/2)
new HttpVersionPattern(),

// Start Line: URL/Path
new HttpUrlPattern(),

// Response: Status Codes (200, 404)
new HttpStatusPattern(),

// Headers: Attribute names
new HttpAttributePattern(),
];
}
}
27 changes: 27 additions & 0 deletions src/Languages/Http/Patterns/HttpAttributePattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Http\Patterns;

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

#[PatternTest(input: 'Content-Type: application/json', output: 'Content-Type')]
#[PatternTest(input: 'x-frame-options: SAMEORIGIN', output: 'x-frame-options')]
final class HttpAttributePattern implements Pattern
{
use IsPattern;

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

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

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Http\Patterns;

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

#[PatternTest(input: '# Test Comment', output: '# Test Comment')]
#[PatternTest(input: '// Test Comment', output: '// Test Comment')]
final class HttpCommentPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '/(?<match>(?m)^(\/\/|\#).*)/';
}

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

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Http\Patterns;

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

#[PatternTest(input: 'GET /index.html HTTP/1.1', output: 'GET')]
#[PatternTest(input: 'DELETE /user/1 HTTP/1.1', output: 'DELETE')]
#[PatternTest(input: 'POSTAL /a HTTP/1.1', output: null)]
final class HttpMethodPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '(?<match>(?m)^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE)\b)';
}

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

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Http\Patterns;

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

#[PatternTest(input: 'HTTP/2 301', output: '301')]
#[PatternTest(input: 'HTTP/1.1 200 OK', output: '200')]
#[PatternTest(input: 'content-length: 220', output: null)]
final class HttpStatusPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
// Only match the status code on a response start line (e.g. `HTTP/2 301`),
// never a three-digit value within a header (e.g. `content-length: 220`).
return '/(?m)(?<=^HTTP\/[32] |^HTTP\/1\.[01] )(?<match>\d{3})/';
}

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

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Http\Patterns;

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

#[PatternTest(input: 'POST /task?id=1 HTTP/1.1', output: '/task?id=1')]
#[PatternTest(input: 'POST https://api.example.com/users HTTP/2', output: 'https://api.example.com/users')]
final class HttpUrlPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '(?<=\s)(?<match>\/[^\s]*|\w+:\/\/[^\s]*)(?=\sHTTP)';
}

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

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Http\Patterns;

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

#[PatternTest(input: 'GET /index.html HTTP/1.1', output: 'HTTP/1.1')]
#[PatternTest(input: 'HTTP/2 301', output: 'HTTP/2')]
#[PatternTest(input: 'HTTP/3 200', output: 'HTTP/3')]
final class HttpVersionPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '(?<match>HTTP\/([32]|1\.[01]))';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::PROPERTY;
}
}
12 changes: 12 additions & 0 deletions tests/Bench/Fixtures/http.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### Get all users
GET https://api.example.com/users
Accept: application/json

### Create a new user
POST https://api.example.com/users
Content-Type: application/json

{
"name": "Jane Doe",
"email": "jane@example.com"
}
1 change: 1 addition & 0 deletions tests/Bench/HighlighterBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ final class HighlighterBench
'gdscript' => 'gdscript.txt',
'graphql' => 'graphql.txt',
'html' => 'html.txt',
'http' => 'http.txt',
'ini' => 'ini.txt',
'javascript' => 'javascript.txt',
'json' => 'json.txt',
Expand Down
80 changes: 80 additions & 0 deletions tests/Languages/Http/HttpLanguageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Tests\Languages\Http;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Languages\Http\HttpLanguage;

class HttpLanguageTest extends TestCase
{
#[DataProvider('provide_highlighting_cases')]
public function test_highlighting(string $content, string $expected)
{
$highlighter = new Highlighter();
$highlighter->addLanguage(new HttpLanguage());

$this->assertSame(
trim($expected),
trim($highlighter->parse($content, 'http'))
);
}

public static function provide_highlighting_cases(): iterable
{
return [
// Standard Request Line
[
'GET /index.html HTTP/1.1',
'<span class="hl-keyword">GET</span> <span class="hl-value">/index.html</span> <span class="hl-property">HTTP/1.1</span>',
],
// Request with absolute URL
[
'POST https://api.tempest.php/v1/data HTTP/2',
'<span class="hl-keyword">POST</span> <span class="hl-value">https://api.tempest.php/v1/data</span> <span class="hl-property">HTTP/2</span>',
],
// Response Line
[
'HTTP/1.1 200 OK',
'<span class="hl-property">HTTP/1.1</span> <span class="hl-number">200</span> OK',
],
// Headers
[
'Host: localhost
Content-Type: application/json
X-Custom-Header: value',
'<span class="hl-attribute">Host</span>: localhost
<span class="hl-attribute">Content-Type</span>: application/json
<span class="hl-attribute">X-Custom-Header</span>: value',
],
// Full Request Example
[
"DELETE /user/1 HTTP/1.1\nHost: api.com\nAccept: */*",
"<span class=\"hl-keyword\">DELETE</span> <span class=\"hl-value\">/user/1</span> <span class=\"hl-property\">HTTP/1.1</span>\n<span class=\"hl-attribute\">Host</span>: api.com\n<span class=\"hl-attribute\">Accept</span>: */*",
],
// Error Response
[
'HTTP/2 404 Not Found',
'<span class="hl-property">HTTP/2</span> <span class="hl-number">404</span> Not Found',
],
// HTTP/3 response (highlight.js http3 fixture)
[
'HTTP/3 200',
'<span class="hl-property">HTTP/3</span> <span class="hl-number">200</span>',
],
// Three-digit header values must NOT be highlighted as a status code
[
"HTTP/2 301\ncontent-length: 220",
"<span class=\"hl-property\">HTTP/2</span> <span class=\"hl-number\">301</span>\n<span class=\"hl-attribute\">content-length</span>: 220",
],
// Comments / request separators
[
'### Get users',
'<span class="hl-comment">### Get users</span>',
],
];
}
}
11 changes: 11 additions & 0 deletions tests/targets/http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```http
### Get users
POST https://api.example.com/users HTTP/1.1
Content-Type: application/json

HTTP/1.1 202 Accepted
{
"name": "Jane Doe",
"email": "jane@example.com"
}
```
Loading