1- <center ><img src =" https://github.com/ClaudiuCeia/ts-duckling/blob/main/logo.png " width =" 160 " /></center >
1+ <p align =" center " >
2+ <img
3+ src="https://raw.githubusercontent.com/ClaudiuCeia/ts-duckling/main/logo.png "
4+ width="200"
5+ alt="ts-duckling logo"
6+ />
7+ </p >
28
39# ts-duckling
410
5- A Typescript library for Deno that parses text into structured data. Inspired by
6- [ duckling] ( https://github.com/facebook/duckling ) but using a more naive approach
7- with parser combinators.
11+ [ ![ CI] ( https://github.com/ClaudiuCeia/ts-duckling/actions/workflows/ci.yml/badge.svg )] ( https://github.com/ClaudiuCeia/ts-duckling/actions/workflows/ci.yml )
12+ [ ![ JSR] ( https://jsr.io/badges/@claudiu-ceia/ts-duckling )] ( https://jsr.io/@claudiu-ceia/ts-duckling )
13+ [ ![ License] ( https://img.shields.io/github/license/ClaudiuCeia/ts-duckling )] ( ./LICENSE )
14+ [ ![ Playground] ( https://img.shields.io/badge/playground-GitHub%20Pages-3b82f6 )] ( https://claudiuceia.github.io/ts-duckling/ )
815
9- What this means in practice is that while the library is easy to extend and is
10- relatively light, it will perform badly on larger data sets and it will be much
11- more error prone since the rules for entities are hard coded .
16+ A tiny, deterministic entity extractor for TypeScript/Deno, inspired by
17+ [ duckling ] ( https://github.com/facebook/duckling ) . This version uses parser
18+ combinator grammars (no ML) .
1219
1320Online playground: https://claudiuceia.github.io/ts-duckling/
1421
22+ ## Install (Deno / JSR)
23+
24+ ``` ts
25+ import { Duckling } from " jsr:@claudiu-ceia/ts-duckling@^0.0.13" ;
26+ ```
27+
28+ ## Quickstart
29+
30+ ``` ts
31+ import {
32+ Duckling ,
33+ Email ,
34+ Time ,
35+ URL ,
36+ } from " jsr:@claudiu-ceia/ts-duckling@^0.0.13" ;
37+
38+ const text =
39+ " Email me at foo@example.com and visit https://example.com tomorrow." ;
40+ const res = Duckling ([Email .parser , URL .parser , Time .parser ]).extract ({
41+ text ,
42+ index: 0 ,
43+ });
44+
45+ if (res .success ) {
46+ console .log (res .value );
47+ } else {
48+ console .error (res .expected , res .location );
49+ }
50+ ```
51+
52+ ## Supported Entities
53+
54+ - Time (relative, day-of-week, common dates, ISO ` ...Z ` timestamps)
55+ - Range (time ranges, year ranges, temperature ranges)
56+ - Temperature
57+ - Quantity
58+ - Location (countries, dataset-backed)
59+ - URL
60+ - Email
61+ - Institution
62+ - Language (dataset-backed)
63+ - Phone (E.164-ish)
64+ - IP address (IPv4 + deterministic IPv6 full form)
65+ - SSN (US)
66+ - Credit card (Luhn)
67+ - UUID
68+ - API keys (best-effort detection for common provider prefixes)
69+
1570## When would I use this?
1671
17- If you have a Deno Typescript codebase, and you want to extract entities from
72+ If you have a Deno TypeScript codebase, and you want to extract entities from
1873relatively small data samples (ie: blog posts, comments, messages, etc.), this
1974will probably work for you. Even more so if the format of the data is relatively
2075stable.
@@ -25,46 +80,66 @@ entities:
2580
2681``` ts
2782// ts-duckling falsely assumes that 6/2022 is a date
28- const res = Duckling ([Time .parser ]).extract (" 6/2022 is 0.00296735905" );
29- /**
30- [
31- {
32- end: 7,
33- kind: "time",
34- start: 0,
35- text: "6/2022 ",
36- value: {
37- grain: "day",
38- when: "2022-01-05T22:00:00.000Z",
39- },
40- },
41- ]
42- */
83+ const res = Duckling ([Time .parser ]).extract ({
84+ text: " 6/2022 is 0.00296735905" ,
85+ index: 0 ,
86+ });
87+
88+ if (res .success ) {
89+ console .log (res .value );
90+ // [
91+ // {
92+ // kind: "time",
93+ // start: 0,
94+ // end: 6,
95+ // text: "6/2022",
96+ // value: { when: "...", grain: "day", era: "CE" },
97+ // },
98+ // ]
99+ }
43100```
44101
45- ## Adding new entity types
102+ ## Adding New Entity Types
103+
104+ Define a parser that returns an ` Entity ` , then pass its ` .parser ` to ` Duckling ` .
46105
47106``` ts
48- type FizzBuzzLanguage = EntityLanguage <
49- {
50- fizz: Parser <string >;
51- buzz: Parser <string >;
52- fizzbuzz: Parser <string >;
107+ import {
108+ createLanguageThis ,
109+ map ,
110+ type Parser ,
111+ regex ,
112+ } from " jsr:@claudiu-ceia/combine@^0.2.8" ;
113+ import {
114+ type AnyEntity ,
115+ Duckling ,
116+ ent ,
117+ type Entity ,
118+ } from " jsr:@claudiu-ceia/ts-duckling@^0.0.13" ;
119+
120+ type HashtagEntity = Entity <" hashtag" , { tag: string }>;
121+
122+ const Hashtag = createLanguageThis ({
123+ Full() {
124+ return map (
125+ regex (/ #[A-Za-z0-9 _] {2,64} / , " hashtag" ),
126+ (m , b , a ) => ent ({ tag: m .slice (1 ) }, " hashtag" , b , a ),
127+ );
128+ },
129+ parser() {
130+ return this .Full ;
53131 },
54- string
55- >;
56-
57- const Fizzbuzz = createLanguage <FizzBuzzLanguage >({
58- fizz : () => fuzzyCase (" fizz" ),
59- buzz : () => fuzzyCase (" buzz" ),
60- fizzbuzz : (s ) => mapJoin (seq (s .fizz , s .buzz )),
61- parser : (s ) => either (s .fizz , s .buzz , s .fizzbuzz ),
62132});
63133
64- Duckling ([Fizzbuzz .parser ]).extract (`
65- FizzBuzz is a programming problem where you print fizz for multiples
66- of 3, buzz for multiples of 5, and fizzbuzz for multiples of both 3 and 5.
67- ` );
134+ // `Duckling` is typed to the built-in entity union (`AnyEntity`).
135+ // To mix in custom entities, widen the type (or cast) and handle it downstream.
136+ const res2 = Duckling ([Hashtag .parser as unknown as Parser <AnyEntity >]).extract (
137+ {
138+ text: " hello #duckling" ,
139+ index: 0 ,
140+ },
141+ );
142+ if (res2 .success ) console .log (res2 .value );
68143```
69144
70145# License
0 commit comments