Skip to content

Commit 65e42b2

Browse files
author
MerkushevKirill
committed
rft - non backward compatibility - change multiply method same as in original JS implementation
add - oneOrMore, zeroOrMore and atLeast methods add - one more complex example
1 parent 4db7da6 commit 65e42b2

File tree

5 files changed

+301
-28
lines changed

5 files changed

+301
-28
lines changed

src/main/java/ru/lanwen/verbalregex/VerbalExpression.java

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.util.regex.Matcher;
44
import java.util.regex.Pattern;
55

6+
import static java.lang.String.valueOf;
7+
68
public class VerbalExpression {
79

810
private final Pattern pattern;
@@ -16,6 +18,7 @@ public static class Builder {
1618

1719
/**
1820
* Package private. Use {@link #regex()} to build a new one
21+
*
1922
* @since 1.2
2023
*/
2124
Builder() {
@@ -70,6 +73,7 @@ public Builder add(final String pValue) {
7073

7174
/**
7275
* Append a regex from builder and wrap it with unnamed group (?: ... )
76+
*
7377
* @param regex - VerbalExpression.Builder, that not changed
7478
* @return this builder
7579
* @since 1.2
@@ -407,6 +411,15 @@ public Builder withAnyCase(final boolean pEnable) {
407411
return this;
408412
}
409413

414+
/**
415+
* Turn ON matching with ignoring case
416+
* Example:
417+
* // matches "a"
418+
* // matches "A"
419+
* regex().find("a").withAnyCase()
420+
*
421+
* @return this builder
422+
*/
410423
public Builder withAnyCase() {
411424
return withAnyCase(true);
412425
}
@@ -420,16 +433,59 @@ public Builder searchOneLine(final boolean pEnable) {
420433
return this;
421434
}
422435

423-
public Builder multiple(final String pValue) {
424-
switch (pValue.charAt(0)) {
425-
case '*':
426-
case '+':
427-
return this.add(pValue);
436+
/**
437+
* Convenient method to show that string usage count is exact count, range count or simply one or more
438+
* Usage:
439+
* regex().multiply("abc") // Produce (?:abc)+
440+
* regex().multiply("abc", null) // Produce (?:abc)+
441+
* regex().multiply("abc", (int)from) // Produce (?:abc){from}
442+
* regex().multiply("abc", (int)from, (int)to) // Produce (?:abc){from, to}
443+
* regex().multiply("abc", (int)from, (int)to, (int)...) // Produce (?:abc)+
444+
*
445+
* @param pValue - the string to be looked for
446+
* @param count - (optional) if passed one or two numbers, it used to show count or range count
447+
* @return this builder
448+
* @see #oneOrMore()
449+
* @see #then(String)
450+
* @see #zeroOrMore()
451+
*/
452+
public Builder multiple(final String pValue, final int... count) {
453+
if (count == null) {
454+
return this.then(pValue).oneOrMore();
455+
}
456+
switch (count.length) {
457+
case 1:
458+
return this.then(pValue).count(count[0]);
459+
case 2:
460+
return this.then(pValue).count(count[0], count[1]);
428461
default:
429-
return this.add(this.sanitize(pValue) + '+');
462+
return this.then(pValue).oneOrMore();
430463
}
431464
}
432465

466+
/**
467+
* Adds "+" char to regexp
468+
* Same effect as {@link #atLeast(int)} with "1" argument
469+
* Also, used by {@link #multiple(String, int...)} when second argument is null, or have length more than 2
470+
*
471+
* @return this builder
472+
* @since 1.2
473+
*/
474+
public Builder oneOrMore() {
475+
return this.add("+");
476+
}
477+
478+
/**
479+
* Adds "*" char to regexp, means zero or more times repeated
480+
* Same effect as {@link #atLeast(int)} with "0" argument
481+
*
482+
* @return this builder
483+
* @since 1.2
484+
*/
485+
public Builder zeroOrMore() {
486+
return this.add("*");
487+
}
488+
433489
/**
434490
* Add count of previous group
435491
* for example:
@@ -458,6 +514,22 @@ public Builder count(final int from, final int to) {
458514
return this;
459515
}
460516

517+
/**
518+
* Produce range count with only minimal number of occurrences
519+
* for example:
520+
* .find("w").atLeast(1) // produce (?:w){1,}
521+
*
522+
* @param from - minimal number of occurrences
523+
* @return this Builder
524+
* @see #count(int)
525+
* @see #oneOrMore()
526+
* @see #zeroOrMore()
527+
* @since 1.2
528+
*/
529+
public Builder atLeast(final int from) {
530+
return this.add("{").add(valueOf(from)).add(",}");
531+
}
532+
461533
/**
462534
* Add a alternative expression to be matched
463535
*
@@ -505,12 +577,11 @@ public Builder capt() {
505577
* Same as {@link #capture()}, but don't save result
506578
* May be used to set count of duplicated captures, without creating a new saved capture
507579
* Example:
508-
* // Without group() - count(2) applies only to second capture
509-
* regex().group()
510-
* .capt().range("0", "1").endCapt().tab()
511-
* .capt().digit().count(5).endCapt()
512-
* .endGr().count(2);
513-
*
580+
* // Without group() - count(2) applies only to second capture
581+
* regex().group()
582+
* .capt().range("0", "1").endCapt().tab()
583+
* .capt().digit().count(5).endCapt()
584+
* .endGr().count(2);
514585
*
515586
* @return this builder
516587
* @since 1.2
@@ -537,6 +608,7 @@ public Builder endCapture() {
537608

538609
/**
539610
* Shortcut for {@link #endCapture()}
611+
*
540612
* @return this builder
541613
* @since 1.2
542614
*/
@@ -545,11 +617,12 @@ public Builder endCapt() {
545617
}
546618

547619
/**
548-
* Closes current unnamed and unsaved group
620+
* Closes current unnamed and unmatching group
549621
* Shortcut for {@link #endCapture()}
550622
* Use it with {@link #group()} for prettify code
551623
* Example:
552-
* regex().group().maybe("word").count(2).endGr()
624+
* regex().group().maybe("word").count(2).endGr()
625+
*
553626
* @return this builder
554627
* @since 1.2
555628
*/

src/test/java/ru/lanwen/verbalregex/BasicFunctionalityUnitTest.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static org.hamcrest.CoreMatchers.*;
66
import static org.junit.Assert.*;
77
import static ru.lanwen.verbalregex.VerbalExpression.regex;
8+
import static ru.lanwen.verbalregex.matchers.EqualToRegexMatcher.equalToRegex;
89
import static ru.lanwen.verbalregex.matchers.TestMatchMatcher.matchesTo;
910
import static ru.lanwen.verbalregex.matchers.TestsExactMatcher.matchesExactly;
1011

@@ -352,6 +353,24 @@ public void testOrWithCapture() {
352353
assertThat(testRegex.getText("xxxabcdefzzz", 2), equalTo("abcnull"));
353354
}
354355

356+
357+
@Test
358+
public void testOrWithClosedCapture() {
359+
VerbalExpression testRegex = regex()
360+
.capture()
361+
.find("abc")
362+
.endCapt()
363+
.or("def")
364+
.build();
365+
assertThat("Starts with abc or def", testRegex, matchesTo("defzzz"));
366+
assertThat("Starts with abc or def", testRegex, matchesTo("abczzz"));
367+
assertThat("Doesn't start with abc or def", testRegex, not(matchesExactly("xyzabcefg")));
368+
369+
assertThat(testRegex.getText("xxxabcdefzzz", 1), equalTo("abcnull"));
370+
assertThat(testRegex.getText("xxxdefzzz", 2), equalTo("null"));
371+
assertThat(testRegex.getText("xxxabcdefzzz", 2), equalTo("abcnull"));
372+
}
373+
355374
@Test
356375
public void addRegexBuilderWrapsItWithUnsavedGroup() throws Exception {
357376
VerbalExpression regex = regex()
@@ -366,4 +385,83 @@ public void addRegexBuilderWrapsItWithUnsavedGroup() throws Exception {
366385
assertThat(regex, matchesExactly(example + example));
367386
assertThat(regex, not(matchesExactly(example2digit)));
368387
}
388+
389+
@Test
390+
public void multiplyWith1NumProduceSameAsCountResult() throws Exception {
391+
VerbalExpression regex = regex().multiple("a", 1).build();
392+
393+
assertThat(regex, equalToRegex(regex().find("a").count(1)));
394+
}
395+
396+
@Test
397+
public void multiplyWith2NumProduceSameAsCountRangeResult() throws Exception {
398+
VerbalExpression regex = regex().multiple("a", 1, 2).build();
399+
400+
assertThat(regex, equalToRegex(regex().find("a").count(1, 2)));
401+
}
402+
403+
@Test
404+
public void atLeast1HaveSameEffectAsOneOrMore() throws Exception {
405+
VerbalExpression regex = regex().find("a").atLeast(1).build();
406+
407+
String matched = "aaaaaa";
408+
String oneMatchedExactly = "a";
409+
String oneMatched = "ab";
410+
String empty = "";
411+
412+
assertThat(regex, matchesExactly(matched));
413+
assertThat(regex, matchesExactly(oneMatchedExactly));
414+
assertThat(regex, not(matchesExactly(oneMatched)));
415+
assertThat(regex, matchesTo(oneMatched));
416+
assertThat(regex, not(matchesTo(empty)));
417+
}
418+
419+
@Test
420+
public void oneOreMoreSameAsAtLeast1() throws Exception {
421+
VerbalExpression regexWithOneOrMore = regex().find("a").oneOrMore().build();
422+
423+
String matched = "aaaaaa";
424+
String oneMatchedExactly = "a";
425+
String oneMatched = "ab";
426+
String empty = "";
427+
428+
assertThat(regexWithOneOrMore, matchesExactly(matched));
429+
assertThat(regexWithOneOrMore, matchesExactly(oneMatchedExactly));
430+
assertThat(regexWithOneOrMore, not(matchesExactly(oneMatched)));
431+
assertThat(regexWithOneOrMore, matchesTo(oneMatched));
432+
assertThat(regexWithOneOrMore, not(matchesTo(empty)));
433+
}
434+
435+
@Test
436+
public void atLeast0HaveSameEffectAsZeroOrMore() throws Exception {
437+
VerbalExpression regex = regex().find("a").atLeast(0).build();
438+
439+
String matched = "aaaaaa";
440+
String oneMatchedExactly = "a";
441+
String oneMatched = "ab";
442+
String empty = "";
443+
444+
assertThat(regex, matchesExactly(matched));
445+
assertThat(regex, matchesExactly(oneMatchedExactly));
446+
assertThat(regex, not(matchesExactly(oneMatched)));
447+
assertThat(regex, matchesTo(empty));
448+
assertThat(regex, matchesExactly(empty));
449+
}
450+
451+
@Test
452+
public void zeroOreMoreSameAsAtLeast0() throws Exception {
453+
VerbalExpression regexWithOneOrMore = regex().find("a").zeroOrMore().build();
454+
455+
String matched = "aaaaaa";
456+
String oneMatchedExactly = "a";
457+
String oneMatched = "ab";
458+
String empty = "";
459+
460+
assertThat(regexWithOneOrMore, matchesExactly(matched));
461+
assertThat(regexWithOneOrMore, matchesExactly(oneMatchedExactly));
462+
assertThat(regexWithOneOrMore, not(matchesExactly(oneMatched)));
463+
assertThat(regexWithOneOrMore, matchesTo(oneMatched));
464+
assertThat(regexWithOneOrMore, matchesTo(empty));
465+
assertThat(regexWithOneOrMore, matchesExactly(empty));
466+
}
369467
}

src/test/java/ru/lanwen/verbalregex/NegativeCasesTest.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static org.hamcrest.CoreMatchers.containsString;
88
import static org.hamcrest.CoreMatchers.equalTo;
99
import static org.junit.Assert.assertThat;
10+
import static ru.lanwen.verbalregex.VerbalExpression.regex;
1011
import static ru.lanwen.verbalregex.matchers.TestMatchMatcher.matchesTo;
1112

1213
/**
@@ -18,37 +19,37 @@ public class NegativeCasesTest {
1819

1920
@Test(expected = IllegalStateException.class)
2021
public void testEndCaptureOnEmptyRegex() {
21-
VerbalExpression.regex().endCapture().build();
22+
regex().endCapture().build();
2223
}
2324

2425
@Test(expected = IndexOutOfBoundsException.class)
2526
public void shouldExceptionWhenTryGetMoreThanCapturedGroup() {
2627
String text = "abc";
27-
VerbalExpression regex = VerbalExpression.regex().find("b").capture().find("c").build();
28+
VerbalExpression regex = regex().find("b").capture().find("c").build();
2829

2930
regex.getText(text, 2);
3031
}
3132

3233
@Test(expected = PatternSyntaxException.class)
3334
public void testRangeWithoutArgs() throws Exception {
34-
VerbalExpression.regex().startOfLine().range().build();
35+
regex().startOfLine().range().build();
3536
}
3637

3738
@Test(expected = PatternSyntaxException.class)
3839
public void testRangeWithOneArg() throws Exception {
39-
VerbalExpression.regex().startOfLine().range("a").build();
40+
regex().startOfLine().range("a").build();
4041
}
4142

4243
@Test
4344
public void rangeWithThreeArgsUsesOnlyFirstTwo() throws Exception {
44-
VerbalExpression regex = VerbalExpression.regex().startOfLine().range("a", "z", "A").build();
45+
VerbalExpression regex = regex().startOfLine().range("a", "z", "A").build();
4546

4647
assertThat("Range with three args differs from expected", regex.toString(), equalTo("^[a-z]"));
4748
}
4849

4950
@Test
5051
public void orWithNullMatchesAny() throws Exception {
51-
VerbalExpression regex = VerbalExpression.regex().startOfLine().then("a").or(null).build();
52+
VerbalExpression regex = regex().startOfLine().then("a").or(null).build();
5253
assertThat("regex don't matches writed letter", regex, matchesTo("a"));
5354
assertThat("or(null) should match any", regex, matchesTo("bcd"));
5455

@@ -57,7 +58,7 @@ public void orWithNullMatchesAny() throws Exception {
5758

5859
@Test
5960
public void orAfterCaptureProduceEmptyGroup() throws Exception {
60-
VerbalExpression regex = VerbalExpression.regex().startOfLine().then("a").capture().or("b").build();
61+
VerbalExpression regex = regex().startOfLine().then("a").capture().or("b").build();
6162

6263
assertThat(regex.toString(), containsString("()|"));
6364

@@ -66,4 +67,26 @@ public void orAfterCaptureProduceEmptyGroup() throws Exception {
6667
assertThat("second group should produce empty string", regex.getText("abcd", 2), equalTo(""));
6768

6869
}
70+
71+
@Test
72+
public void multiplyWithNullOnCountEqualToWithOneAndMore() throws Exception {
73+
VerbalExpression regex = regex().multiple("some", null).build();
74+
75+
assertThat("Multiply with null should be equal to oneOrMore",
76+
regex.toString(), equalTo(regex().find("some").oneOrMore().build().toString()));
77+
}
78+
79+
@Test
80+
public void multiplyWithMoreThan3ParamsOnCountEqualToWithOneAndMore() throws Exception {
81+
VerbalExpression regex = regex().multiple("some", 1, 2, 3).build();
82+
83+
assertThat("Multiply with 3 args should be equal to oneOrMore",
84+
regex.toString(), equalTo(regex().find("some").oneOrMore().build().toString()));
85+
}
86+
87+
@Test(expected = java.util.regex.PatternSyntaxException.class)
88+
public void twoOpenCaptsWithOrThrowSyntaxException() throws Exception {
89+
VerbalExpression regex = regex().capt().capt().or("0").build();
90+
String ignored = regex.toString();
91+
}
6992
}

0 commit comments

Comments
 (0)