Skip to content

Commit 9643aae

Browse files
committed
feat: URL parser matches bare domains (domain.tld)
1 parent cebd007 commit 9643aae

3 files changed

Lines changed: 74 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ const entities = Duckling([Email.parser, Hashtag.parser]).extract(
369369
| **Temperature** | `temperature` | `72°F`, `20 celsius`, `-5°C` |
370370
| **Quantity** | `quantity` | `5 kg`, `100 miles`, `3,500.00` |
371371
| **Location** | `location` | `United States`, `Germany`, `Japan` |
372-
| **URL** | `url` | `https://example.com/path?q=1` |
372+
| **URL** | `url` | `https://example.com/path?q=1`, `docs.example.org` |
373373
| **Institution** | `institution` | `University of Oxford`, `New York City Hall` |
374374
| **Language** | `language` | `English`, `Japanese`, `Portuguese` |
375375

src/URL.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type URLLanguage = {
4646
Suffix: Parser<string>;
4747
Domain: Parser<string>;
4848
Full: Parser<URLEntity>;
49+
Bare: Parser<URLEntity>;
4950
parser: Parser<URLEntity>;
5051
};
5152

@@ -108,7 +109,26 @@ export const URL: URLLanguage = createLanguage<URLLanguage>({
108109
),
109110
);
110111
},
112+
Bare: (s): Parser<URLEntity> => {
113+
return map(
114+
seq(
115+
s.Domain,
116+
optional(seq(str(":"), s.Port)),
117+
optional(s.Suffix),
118+
),
119+
([domain, maybePort, maybeSuffix], b, a) =>
120+
url(
121+
{
122+
url: `${domain}${
123+
maybePort ? `:${maybePort[1]}` : ""
124+
}${maybeSuffix ?? ""}`,
125+
},
126+
b,
127+
a,
128+
),
129+
);
130+
},
111131
parser: (s): Parser<URLEntity> => {
112-
return dot(any(s.Full));
132+
return dot(any(s.Full, s.Bare));
113133
},
114134
});

tests/URL.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,55 @@ Deno.test("URL with query params (Wikipedia create account)", () => {
8686
"https://en.wikipedia.org/w/index.php?title=Special:CreateAccount&returnto=Master+Juba",
8787
);
8888
});
89+
90+
Deno.test("URL bare domain", () => {
91+
const res = Duckling().extract("Visit google.com for more");
92+
93+
assertEquals(res, [
94+
{
95+
start: 6,
96+
end: 16,
97+
kind: "url",
98+
text: "google.com",
99+
value: { url: "google.com" },
100+
},
101+
]);
102+
});
103+
104+
Deno.test("URL bare domain with subdomain", () => {
105+
const res = Duckling().extract("Check docs.example.org please");
106+
107+
assertEquals(res, [
108+
{
109+
start: 6,
110+
end: 22,
111+
kind: "url",
112+
text: "docs.example.org",
113+
value: { url: "docs.example.org" },
114+
},
115+
]);
116+
});
117+
118+
Deno.test("URL bare domain with path", () => {
119+
const res = Duckling().extract("See example.com/about for info");
120+
121+
assertEquals(res[0].kind, "url");
122+
assertEquals(res[0].text, "example.com/about");
123+
assertEquals(res[0].value, { url: "example.com/about" });
124+
});
125+
126+
Deno.test("URL bare domain with port", () => {
127+
const res = Duckling().extract("Running at localhost.com:3000 now");
128+
129+
assertEquals(res[0].kind, "url");
130+
assertEquals(res[0].text, "localhost.com:3000");
131+
assertEquals(res[0].value, { url: "localhost.com:3000" });
132+
});
133+
134+
Deno.test("URL prefers full URL over bare domain", () => {
135+
const res = Duckling().extract("Go to https://example.com/path please");
136+
137+
assertEquals(res.length, 1);
138+
assertEquals(res[0].text, "https://example.com/path");
139+
assertEquals(res[0].value, { url: "https://example.com/path" });
140+
});

0 commit comments

Comments
 (0)