Skip to content

Commit 4b13837

Browse files
committed
Feature tests for builderpattern and lazy method
1 parent 85b591a commit 4b13837

File tree

9 files changed

+237
-74
lines changed

9 files changed

+237
-74
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Examples: `->dot("zeroOrMore")` `->exact("hello worls", false, "1+")`
6060
- BuilderPattern should be able to reproduce patterns used in HSA✔️
6161

6262
- Add benchmarks and tests for search against large data ✔️
63-
- Add Feature Tests for BuilderPattern
63+
- Add Feature Tests for BuilderPattern ✔️
6464
- Add Dockblocs and comments for new methods
6565

6666
- Add facade for Laravel

searchTest/search.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,17 @@
3131
*/
3232

3333
function search($keyword, $file) {
34+
// Get data from file
3435
$data = file_get_contents(__DIR__."/".$file);
35-
preg_match_all("/.+(?=$keyword).+/", $data, $matches);
36-
return $matches[0];
36+
// Build search pattern
37+
$builder = new Builder($data);
38+
$builder->start()
39+
->anyChar()
40+
->lookAhead(function ($pattern) use ($keyword) {
41+
$pattern->exact($keyword);
42+
})->anyChar()->end(); // .+(?=$keyword).+
43+
44+
return $builder->get();
3745
}
3846

3947
function makeObject($json) {

src/Contracts/PatternContract.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ public function getInputValidationPattern(): string;
7676
*/
7777
public function getMatchesValidationPattern(): string;
7878

79+
public function addExpressionFlag(string $flag): void;
80+
7981
/**
8082
* Executes the pattern with the provided arguments.
8183
*

src/Patterns/BuilderPattern.php

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class BuilderPattern extends BasePattern {
1717

1818
protected array $options = [];
1919
protected string $pattern = "";
20+
protected bool $lazy = false;
2021
protected BuilderContract $builder; // Reference to the main Builder object
2122

2223
public function __construct(BuilderContract $builder = new Builder()) {
@@ -50,18 +51,34 @@ private function applyQuantifier($pattern, $quantifier) {
5051

5152
private function getLengthOption($length = null, $minLength = 0, $maxLength = 0): string {
5253
if (is_int($length) && $length >= 0) {
53-
return "{" . $length . "}";
54+
$qntf = "{" . $length . "}";
55+
return $this->lazy ? $this->addLazy($qntf) : $qntf;
5456
}
5557

5658
if ($minLength > 0 && $maxLength > 0) {
57-
return "{" . $minLength . "," . $maxLength . "}";
59+
$qntf = "{" . $minLength . "," . $maxLength . "}";
60+
return $this->lazy ? $this->addLazy($qntf) : $qntf;
5861
} else if ($minLength > 0) {
59-
return "{" . $minLength . ",}";
62+
$qntf = "{" . $minLength . ",}";
63+
return $this->lazy ? $this->addLazy($qntf) : $qntf;
6064
} else if ($maxLength > 0) {
61-
return "{0," . $maxLength . "}";
65+
$qntf = "{0," . $maxLength . "}";
66+
return $this->lazy ? $this->addLazy($qntf) : $qntf;
6267
}
6368

64-
return "+"; // Default case, one or more times
69+
$qntf = "+"; // Default case, one or more times
70+
return $this->lazy ? $this->addLazy($qntf) : $qntf;
71+
}
72+
73+
private function addLazy($quantifier): string {
74+
$this->lazy = false;
75+
return $quantifier . "?";
76+
}
77+
78+
// The next quantifier-based method will considered as lazy (adds "?")
79+
public function lazy(): self {
80+
$this->lazy = true;
81+
return $this;
6582
}
6683

6784
public function set(callable $callback): self {
@@ -71,6 +88,13 @@ public function set(callable $callback): self {
7188
return $this;
7289
}
7390

91+
public function negativeSet(callable $callback): self {
92+
$subPattern = new self();
93+
$callback($subPattern);
94+
$this->pattern .= '[^' . $subPattern->getPattern() . ']';
95+
return $this;
96+
}
97+
7498
public function group(callable $callback): self {
7599
$subPattern = new self();
76100
$callback($subPattern);

src/Traits/BuilderPatternTraits/SpecificCharsTrait.php

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,53 @@
44

55
trait SpecificCharsTrait {
66

7-
// Specific Characters START
8-
9-
public function exact(string $string, $caseSensitive = true, $quantifier = null): self {
10-
$escapedString = preg_quote($string, '/');
7+
private function handleExact(string|array $string, $caseSensitive = true, $quantifier = null) {
8+
if (is_array($string)) {
9+
$string = $this->escapeArray($string);
10+
$escapedString = "(" . implode("|", $string) . ")";
11+
} else {
12+
$escapedString = preg_quote($string, '/');
13+
}
1114
$pattern = $caseSensitive ? $escapedString : "(?i)" . $escapedString;
1215
$this->pattern .= $this->applyQuantifier($pattern, $quantifier);
1316
return $this;
1417
}
15-
16-
public function character(string $char, $caseSensitive = true, $quantifier = null): self {
18+
19+
private function escapeAndAdd(string $char): self {
1720
$escapedChar = preg_quote($char, '/');
18-
$pattern = $caseSensitive ? $escapedChar : "(?i)" . $escapedChar;
19-
$this->pattern .= $this->applyQuantifier($pattern, $quantifier);
21+
$this->pattern .= $escapedChar;
2022
return $this;
2123
}
2224

23-
public function dot($quantifier = null): self {
24-
$this->pattern .= '.'; // Matches any character except newline
25-
$this->pattern = $this->applyQuantifier($this->pattern, $quantifier);
26-
return $this;
25+
private function escapeArray(array $arr) {
26+
return array_map(function ($item) {
27+
return preg_quote($item, '/');
28+
}, $arr);
2729
}
28-
29-
// Specific Characters END
3030

31-
private function escapeAndAdd(string $char): self {
32-
$escapedChar = preg_quote($char, '/');
33-
$this->pattern .= $escapedChar;
34-
return $this;
31+
// Specific Characters START
32+
33+
public function exact(string|array $string, $caseSensitive = true, $quantifier = null): self {
34+
return $this->handleExact($string, $caseSensitive, $quantifier);
35+
}
36+
37+
public function exactly(string|array $string, $caseSensitive = true, $quantifier = null): self {
38+
return $this->handleExact($string, $caseSensitive, $quantifier);
3539
}
3640

41+
public function literal(string|array $string, $caseSensitive = true, $quantifier = null): self {
42+
return $this->handleExact($string, $caseSensitive, $quantifier);
43+
}
44+
45+
public function character(string $char, $caseSensitive = true, $quantifier = null): self {
46+
return $this->handleExact($char, $caseSensitive, $quantifier);
47+
}
48+
49+
public function char(string $char, $caseSensitive = true, $quantifier = null): self {
50+
return $this->handleExact($char, $caseSensitive, $quantifier);
51+
}
52+
53+
3754
public function tab(): self {
3855
$this->pattern .= "\\t"; // Matches a tab character
3956
return $this;
@@ -63,6 +80,14 @@ public function dash() {
6380
return $this->escapeAndAdd("-");
6481
}
6582

83+
public function dot($quantifier = null): self {
84+
return $this->escapeAndAdd("."); // Matches dot "." character
85+
}
86+
87+
public function space() {
88+
return $this->escapeAndAdd(" ");
89+
}
90+
6691
public function backslash(): self {
6792
return $this->escapeAndAdd("\\");
6893
}
@@ -75,6 +100,10 @@ public function slash(): self {
75100
return $this->escapeAndAdd("/");
76101
}
77102

103+
public function doubleSlash(): self {
104+
return $this->escapeAndAdd("//");
105+
}
106+
78107
public function underscore(): self {
79108
return $this->escapeAndAdd("_");
80109
}
@@ -103,6 +132,10 @@ public function atSign(): self {
103132
return $this->escapeAndAdd("@");
104133
}
105134

135+
public function atSymbol(): self {
136+
return $this->escapeAndAdd("@");
137+
}
138+
106139
public function exclamationMark(): self {
107140
return $this->escapeAndAdd("!");
108141
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
use Maestroerror\EloquentRegex\Builder;
4+
5+
it('reproduces alt prefix pattern from HSA', function () {
6+
$builder = new Builder("");
7+
8+
$regex = $builder->start()->exact("alt=")->group(function ($pattern) {
9+
$pattern->doubleQuote()->orPattern(function ($pattern) {
10+
$pattern->singleQuote();
11+
});
12+
})->end()->toRegex();
13+
14+
15+
expect($regex)->toBe("alt\=(\"|')");
16+
});
17+
18+
it('reproduces hashtag prefix pattern from HSA', function () {
19+
$builder = new Builder("");
20+
21+
$regex = $builder->start()->lookBehind(function ($pattern) {
22+
$pattern->set(function ($pattern) {
23+
$pattern->doubleQuote()->closeAngleBracket()->addRawRegex("\\s");
24+
});
25+
})->hash()->end()->toRegex();
26+
27+
expect($regex)->toBe('(?<=["\>\s])\#');
28+
});
29+
30+
it('reproduces Text suffix pattern from HSA', function () {
31+
$builder = new Builder("");
32+
33+
$regex = $builder->start()
34+
->openAngleBracket()->slash()->alphanumericRange(0, 10)->closeAngleBracket()
35+
->end()->toRegex();
36+
37+
expect($regex)->toBe('\<\/[a-zA-Z0-9]{0,10}\>');
38+
});
39+
40+
it('constructs regex for simple email validation', function () {
41+
$builder = new Builder();
42+
43+
$regex = $builder->start()
44+
->textLowercase()
45+
->atSymbol()
46+
->textLowercase()
47+
->dot()
48+
->textLowercaseRange(2, 4)
49+
->end()->toRegex();
50+
51+
expect($regex)->toBe('[a-z]+@[a-z]+\.[a-z]{2,4}');
52+
});
53+
54+
it('constructs regex for URL validation', function () {
55+
$builder = new Builder();
56+
57+
$regex = $builder->start()
58+
->exact(['http', 'https'])
59+
->colon()
60+
->doubleSlash()
61+
->text()
62+
->dot()
63+
->text()
64+
->end()->toRegex();
65+
66+
expect($regex)->toBe('(http|https)\:\/\/[a-zA-Z]+\.[a-zA-Z]+');
67+
});
68+
69+
it('constructs regex for specific phone number format', function () {
70+
$builder = new Builder();
71+
72+
$regex = $builder->start()
73+
->openParenthesis()
74+
->digits(3)
75+
->closeParenthesis()
76+
->space()
77+
->digits(3)
78+
->dash()
79+
->digits(4)
80+
->end()->toRegex();
81+
82+
expect($regex)->toBe('\(\d{3}\) \d{3}\-\d{4}');
83+
});
84+
85+
it('extracts dates in specific format from text', function () {
86+
$builder = new Builder("Meeting on 2021-09-15 and 2021-10-20");
87+
88+
$matches = $builder->start()
89+
->digits(4)
90+
->dash()
91+
->digits(2)
92+
->dash()
93+
->digits(2)
94+
->end()->get();
95+
96+
expect($matches)->toEqual(['2021-09-15', '2021-10-20']);
97+
});
98+
99+
it('validates usernames in a string', function () {
100+
$builder = new Builder("Users: user_123, JohnDoe99");
101+
102+
$check = $builder->start()
103+
->alphanumeric()
104+
->underscore("?")
105+
->digitsRange(0, 2)
106+
->end()->checkString();
107+
108+
expect($check)->toBeTrue();
109+
});
110+
111+
it('extracts hashtags from text', function () {
112+
$builder = new Builder("#hello #world This is a #test");
113+
114+
$matches = $builder->start()
115+
->hash()
116+
->text()
117+
->end()->get();
118+
119+
expect($matches)->toEqual(['#hello', '#world', '#test']);
120+
});
121+
122+
it('extracts secret coded messages from text', function () {
123+
$text = "Normal text {secret: message one} more text {secret: another hidden text} end";
124+
$builder = new Builder($text);
125+
126+
// Pattern: Look for curly braces containing 'secret: ' followed by any characters (non-greedy)
127+
$matches = $builder->start()
128+
->lookBehind(function ($pattern) {
129+
$pattern->openCurlyBrace()->exact('secret: ');
130+
})
131+
->lazy()->anyChar()
132+
->lookAhead(function ($pattern) {
133+
$pattern->closeCurlyBrace();
134+
})
135+
->end()->get();
136+
137+
// Expected secret messages are 'secret: message one' and 'secret: another hidden text'
138+
expect($matches)->toEqual(['message one', 'another hidden text']);
139+
});
140+

tests/Feature/BuilderTest.php

Lines changed: 0 additions & 38 deletions
This file was deleted.

tests/Feature/ExampleTest.php

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)