tdk-api-wrapper 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli.ts CHANGED
@@ -4,6 +4,17 @@ import { TDK } from "./tdk";
4
4
  const rawArgs = process.argv.slice(2);
5
5
  const jsonMode = rawArgs.includes("--json");
6
6
  const args = rawArgs.filter((a) => a !== "--json");
7
+
8
+ const isColor = !jsonMode && Boolean(process.stdout.isTTY);
9
+ const c = {
10
+ bold: (s: string) => (isColor ? `\x1b[1m${s}\x1b[0m` : s),
11
+ dim: (s: string) => (isColor ? `\x1b[2m${s}\x1b[0m` : s),
12
+ green: (s: string) => (isColor ? `\x1b[32m${s}\x1b[0m` : s),
13
+ yellow: (s: string) => (isColor ? `\x1b[33m${s}\x1b[0m` : s),
14
+ cyan: (s: string) => (isColor ? `\x1b[36m${s}\x1b[0m` : s),
15
+ red: (s: string) => (isColor ? `\x1b[31m${s}\x1b[0m` : s),
16
+ };
17
+
7
18
  const KNOWN_COMMANDS = new Set([
8
19
  "ara",
9
20
  "anlam",
@@ -11,6 +22,7 @@ const KNOWN_COMMANDS = new Set([
11
22
  "ornek",
12
23
  "hece",
13
24
  "uyum",
25
+ "kucukuyum",
14
26
  "yazim",
15
27
  "kok",
16
28
  "stem",
@@ -25,6 +37,14 @@ const KNOWN_COMMANDS = new Set([
25
37
  "karsilastir",
26
38
  "analiz",
27
39
  "oneri",
40
+ "bulmaca",
41
+ "pattern",
42
+ "anagram",
43
+ "kafiye",
44
+ "rhyme",
45
+ "denetle",
46
+ "proofread",
47
+ "repl",
28
48
  "kubbealti",
29
49
  "nisanyan",
30
50
  "viki",
@@ -50,18 +70,96 @@ function printError(message: string) {
50
70
  if (jsonMode) {
51
71
  console.log(JSON.stringify({ error: message }));
52
72
  } else {
53
- console.log(`Hata: ${message}`);
73
+ console.log(c.red(`Hata: ${message}`));
54
74
  }
55
75
  }
56
76
 
77
+ async function startRepl() {
78
+ const readline = await import("node:readline");
79
+ const rl = readline.createInterface({
80
+ input: process.stdin,
81
+ output: process.stdout,
82
+ prompt: c.cyan("tdk> "),
83
+ });
84
+
85
+ console.log(c.bold("TDK İnteraktif Sözlük Kabuğu (Çıkmak için 'exit' veya Ctrl+C)"));
86
+ console.log(c.dim("Komutlar: ara <kelime>, hece <kelime>, bulmaca <desen>, denetle <metin> veya doğrudan kelime"));
87
+ rl.prompt();
88
+
89
+ rl.on("line", async (line) => {
90
+ const trimmed = line.trim();
91
+ if (!trimmed) {
92
+ rl.prompt();
93
+ return;
94
+ }
95
+ if (trimmed === "exit" || trimmed === "quit" || trimmed === ".exit") {
96
+ rl.close();
97
+ return;
98
+ }
99
+
100
+ const parts = trimmed.split(/\s+/);
101
+ let subCmd = parts[0].toLowerCase();
102
+ let subArg = parts.slice(1).join(" ");
103
+ if (!KNOWN_COMMANDS.has(subCmd)) {
104
+ subArg = trimmed;
105
+ subCmd = "anlam";
106
+ }
107
+
108
+ try {
109
+ if (subCmd === "ara" || subCmd === "anlam") {
110
+ const meanings = await TDK.getMeanings(subArg);
111
+ if (meanings.length === 0) console.log(c.dim("Sonuç bulunamadı."));
112
+ else meanings.forEach((m, i) => console.log(`${i + 1}. ${c.green(m)}`));
113
+ } else if (subCmd === "koken") {
114
+ const origin = await TDK.getOrigin(subArg);
115
+ console.log(`Köken: ${c.cyan(origin || "Bilinmiyor")}`);
116
+ } else if (subCmd === "hece") {
117
+ const s = TDK.syllabicate(subArg);
118
+ console.log(`Heceler: ${c.yellow(s.join("-"))}`);
119
+ } else if (subCmd === "uyum") {
120
+ const h = TDK.checkVowelHarmony(subArg);
121
+ console.log(`Büyük Ünlü Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
122
+ } else if (subCmd === "kucukuyum") {
123
+ const h = TDK.checkLabialHarmony(subArg);
124
+ console.log(`Küçük Ünlü Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
125
+ } else if (subCmd === "bulmaca" || subCmd === "pattern") {
126
+ const matches = await TDK.patternSearch(subArg);
127
+ console.log(matches.slice(0, 15).join(", "));
128
+ } else if (subCmd === "denetle" || subCmd === "proofread") {
129
+ const res = await TDK.proofread(subArg);
130
+ if (res.isCorrect) console.log(c.green("✓ Sorun bulunamadı."));
131
+ else res.issues.forEach((iss) => console.log(`- ${c.yellow(iss.word)}: ${iss.message}${iss.suggestion ? " -> " + c.green(iss.suggestion) : ""}`));
132
+ } else {
133
+ console.log(c.dim("Örnek komutlar: 'ara kalem', 'hece elektrik', 'bulmaca k_l_m', 'denetle Bugün evdeyim'"));
134
+ }
135
+ } catch (e: any) {
136
+ console.log(c.red(`Hata: ${e?.message || e}`));
137
+ }
138
+ rl.prompt();
139
+ });
140
+ }
141
+
57
142
  async function run() {
58
- if (!command || command === "--help" || command === "-h") {
143
+ if (!command) {
144
+ if (process.stdin.isTTY) {
145
+ await startRepl();
146
+ return;
147
+ }
59
148
  console.log("Kullanım: tdk [komut] <kelime> [--json]");
60
149
  console.log(
61
- "Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, kubbealti, nisanyan, viki"
150
+ "Komutlar: ara, anlam, koken, ornek, hece, uyum, kucukuyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, bulmaca, anagram, kafiye, denetle, repl, kubbealti, nisanyan, viki"
62
151
  );
63
152
  console.log("Not: Komut belirtilmezse doğrudan kelime anlamı aranır (örn: tdk selam)");
64
- process.exit(command ? 0 : 1);
153
+ process.exit(1);
154
+ }
155
+
156
+ if (command === "--help" || command === "-h") {
157
+ console.log("Kullanım: tdk [komut] <kelime> [--json]");
158
+ console.log(
159
+ "Komutlar: ara, anlam, koken, ornek, hece, uyum, kucukuyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, bulmaca, anagram, kafiye, denetle, repl, kubbealti, nisanyan, viki"
160
+ );
161
+ console.log("Not: Komut belirtilmezse doğrudan kelime anlamı aranır (örn: tdk selam)");
162
+ process.exit(0);
65
163
  }
66
164
 
67
165
  TDK.enableCache(false);
@@ -121,6 +219,16 @@ async function run() {
121
219
  break;
122
220
  }
123
221
 
222
+ case "kucukuyum":
223
+ case "labial": {
224
+ if (!word) throw new Error("Kelime belirtmelisiniz.");
225
+ const isHarmony = TDK.checkLabialHarmony(word);
226
+ printResult({ word, labialHarmony: isHarmony }, () =>
227
+ console.log(`Küçük Ünlü Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`)
228
+ );
229
+ break;
230
+ }
231
+
124
232
  case "yazim": {
125
233
  if (!word) throw new Error("Kelime belirtmelisiniz.");
126
234
  const spellResult = await TDK.checkSpelling(word);
@@ -299,6 +407,79 @@ async function run() {
299
407
  break;
300
408
  }
301
409
 
410
+ case "bulmaca":
411
+ case "pattern": {
412
+ if (!word) throw new Error("Desen belirtmelisiniz (örn: k_l_m).");
413
+ const matches = await TDK.patternSearch(word);
414
+ printResult(matches, () => {
415
+ if (matches.length === 0) {
416
+ console.log("Eşleşen kelime bulunamadı.");
417
+ } else {
418
+ console.log(c.bold(`Bulunan Kelimeler (${matches.length}):`));
419
+ matches.forEach((m, i) => console.log(`${i + 1}. ${c.cyan(m)}`));
420
+ }
421
+ });
422
+ break;
423
+ }
424
+
425
+ case "anagram": {
426
+ if (!word) throw new Error("Harfler belirtmelisiniz.");
427
+ const anagrams = await TDK.findAnagrams(word);
428
+ printResult(anagrams, () => {
429
+ if (anagrams.length === 0) {
430
+ console.log("Anagram veya bu harflerle türetilebilecek kelime bulunamadı.");
431
+ } else {
432
+ const clean = word.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
433
+ const hasExact = anagrams.some((a) => a.length === clean.length);
434
+ const title = hasExact
435
+ ? `Anagramlar (${anagrams.length}):`
436
+ : `Birebir anagram bulunamadı. Bu harflerle türetilen kelimeler (${anagrams.length}):`;
437
+ console.log(c.bold(title));
438
+ anagrams.forEach((a, i) => console.log(`${i + 1}. ${c.green(a)} ${c.dim(`(${a.length} harf)`)}`));
439
+ }
440
+ });
441
+ break;
442
+ }
443
+
444
+ case "kafiye":
445
+ case "rhyme": {
446
+ if (!word) throw new Error("Kelime belirtmelisiniz.");
447
+ const rhymes = await TDK.findRhymes(word);
448
+ printResult(rhymes, () => {
449
+ if (rhymes.length === 0) {
450
+ console.log("Kafiye bulunamadı.");
451
+ } else {
452
+ console.log(c.bold(`Kafiyeli Kelimeler (${rhymes.length}):`));
453
+ rhymes.forEach((r, i) => console.log(`${i + 1}. ${c.yellow(r)}`));
454
+ }
455
+ });
456
+ break;
457
+ }
458
+
459
+ case "denetle":
460
+ case "proofread": {
461
+ if (!word) throw new Error("Metin belirtmelisiniz.");
462
+ const result = await TDK.proofread(word);
463
+ printResult(result, () => {
464
+ if (result.isCorrect) {
465
+ console.log(c.green("✓ Metinde imla veya bağlaç hatası tespit edilmedi."));
466
+ } else {
467
+ console.log(c.bold(c.red(`Metinde ${result.issues.length} olası sorun tespit edildi:`)));
468
+ result.issues.forEach((issue, i) => {
469
+ const label = c.yellow(`[${issue.type}]`);
470
+ const sug = issue.suggestion ? c.green(` -> Öneri: ${issue.suggestion}`) : "";
471
+ console.log(`${i + 1}. ${label} "${c.bold(issue.word)}": ${issue.message}${sug}`);
472
+ });
473
+ }
474
+ });
475
+ break;
476
+ }
477
+
478
+ case "repl": {
479
+ await startRepl();
480
+ break;
481
+ }
482
+
302
483
  case "kubbealti": {
303
484
  if (!word) throw new Error("Kelime belirtmelisiniz.");
304
485
  const meanings = await TDK.getKubbealtiMeanings(word);
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { TDK } from "./tdk";
1
+ export { TDK, TDKClient } from "./tdk";
2
2
  export * from "./types";
3
3
  export * from "./errors";
4
4
  export * from "./morphology";
package/src/morphology.ts CHANGED
@@ -67,7 +67,8 @@ export const TURKISH_SUFFIXES: readonly string[] = [
67
67
  "miş", "mış", "muş", "müş", "dim", "dım", "dum", "düm", "tim", "tım", "tum", "tüm",
68
68
  "din", "dın", "dun", "dün", "tin", "tın", "tun", "tün", "dik", "dık", "duk", "dük",
69
69
  "tik", "tık", "tuk", "tük", "ydi", "ydı", "ydu", "ydü", "yim", "yım", "yum", "yüm",
70
- "sin", "sın", "sun", "sün", "siz", "sız", "suz", "süz", "lik", "lık", "luk", "lük",
70
+ "sin", "sın", "sun", "sün", "sen", "san", "sem", "sam", "sek", "sak",
71
+ "siz", "sız", "suz", "süz", "lik", "lık", "luk", "lük",
71
72
  "ici", "ıcı", "ucu", "ücü", "gen", "gan", "ken", "kan",
72
73
  "len", "lan", "leş", "laş", "mek", "mak", "yor",
73
74
  // 2-letter suffixes
@@ -126,6 +127,61 @@ export function restoreVowelDrop(stem: string): string[] {
126
127
  return [];
127
128
  }
128
129
 
130
+ /**
131
+ * Reverses Turkish consonant gemination (ünsüz türemesi / ikizleşmesi):
132
+ * In words of Arabic/foreign origin, when receiving a vowel-initial suffix, the final consonant doubles:
133
+ * e.g. hak->hakkı, his->hissi, sır->sırrı, af->affı, ret->reddi, tıp->tıbbı, zam->zammı, hat->hattı.
134
+ * Restores the single consonant form and checks consonant softening on the result (e.g. redd -> red -> ret).
135
+ */
136
+ export function restoreGemination(stem: string): string[] {
137
+ if (stem.length < 3) return [];
138
+ const c1 = stem[stem.length - 2];
139
+ const c2 = stem[stem.length - 1];
140
+ if (c1 === c2 && !isVowel(c1)) {
141
+ const single = stem.slice(0, -1);
142
+ const hardened = restoreConsonantSoftening(single);
143
+ return [single, ...hardened];
144
+ }
145
+ return [];
146
+ }
147
+
148
+ /**
149
+ * Reverses Turkish vowel narrowing (ünlü daralması):
150
+ * Verbs ending in wide vowels 'a' or 'e' narrow to 'ı', 'i', 'u', 'ü' before the continuous tense suffix -yor:
151
+ * e.g. başla-yor -> başlıyor, bekle-yor -> bekliyor, özle-yor -> özlüyor, anla-yor -> anlıyor.
152
+ * Also handles irregular monosyllabic verbs: de-yor -> diyor, ye-yor -> yiyor.
153
+ */
154
+ export function restoreVowelNarrowing(stem: string): string[] {
155
+ if (stem.length < 2) return [];
156
+
157
+ // Irregular monosyllabic verbs
158
+ if (stem === "di") return ["de"];
159
+ if (stem === "yi") return ["ye"];
160
+
161
+ const lastChar = stem[stem.length - 1];
162
+ const isLastNarrow = "ıiuü".includes(lastChar);
163
+
164
+ // Case 1: stem ends with narrow vowel (e.g. başlı, bekli, özlü, kutlu)
165
+ if (isLastNarrow) {
166
+ const vowelsInBase = stem.slice(0, -1).split("").filter(isVowel);
167
+ const lastVowel = vowelsInBase.length > 0 ? vowelsInBase[vowelsInBase.length - 1] : lastChar;
168
+ const widened = "aıou".includes(lastVowel) ? "a" : "e";
169
+ return [stem.slice(0, -1) + widened];
170
+ }
171
+
172
+ // Case 2: stem ends with consonant (e.g. başlıyor stripped by -ıyor -> stem: başl)
173
+ if (!isVowel(lastChar)) {
174
+ const vowelsInBase = stem.split("").filter(isVowel);
175
+ if (vowelsInBase.length > 0) {
176
+ const lastVowel = vowelsInBase[vowelsInBase.length - 1];
177
+ const widened = "aıou".includes(lastVowel) ? "a" : "e";
178
+ return [stem + widened];
179
+ }
180
+ }
181
+
182
+ return [];
183
+ }
184
+
129
185
  /**
130
186
  * Restores verb infinitive headword form (-mek / -mak):
131
187
  * Since TDK registers verbs in their infinitive form (e.g. okumak, gelmek, yazmak),
@@ -186,11 +242,55 @@ export function getStemCandidates(
186
242
 
187
243
  const hardened = restoreConsonantSoftening(stem);
188
244
  const vowelDropped = restoreVowelDrop(stem);
189
- // Infinitives only apply to direct stems or consonant-hardened stems (e.g. gid -> git -> gitmek),
190
- // NOT to vowel-dropped nouns (nouns like akıl/omuz/şehir don't take infinitive -mek/-mak).
191
- const verbalBases = [stem, ...hardened];
245
+ const geminated = restoreGemination(stem);
246
+
247
+ // Vowel narrowing (ünlü daralması) in Turkish strictly occurs with continuous tense (-yor)
248
+ // or with the monosyllabic verbs de-/ye- before buffer 'y' (diye, yiyen).
249
+ // Restricting narrowing to these suffixes prevents false-positive stems on other suffixes.
250
+ const isNarrowingSuffix =
251
+ suffix.startsWith("yor") ||
252
+ suffix.includes("iyor") ||
253
+ suffix.includes("ıyor") ||
254
+ suffix.includes("uyor") ||
255
+ suffix.includes("üyor");
256
+
257
+ const isDeYeBuffer = (stem === "di" || stem === "yi") && suffix.startsWith("y");
258
+ const narrowed = isNarrowingSuffix || isDeYeBuffer ? restoreVowelNarrowing(stem) : [];
259
+
260
+ // Suffix indicator for verbs: -yor, -ecek, -miş, -di, etc.
261
+ const isVerbSuffix =
262
+ isNarrowingSuffix ||
263
+ suffix.includes("ecek") ||
264
+ suffix.includes("acak") ||
265
+ suffix.includes("miş") ||
266
+ suffix.includes("mış") ||
267
+ suffix.includes("müş") ||
268
+ suffix.includes("muş") ||
269
+ suffix.includes("mek") ||
270
+ suffix.includes("mak") ||
271
+ suffix.includes("erek") ||
272
+ suffix.includes("arak") ||
273
+ suffix.includes("dik") ||
274
+ suffix.includes("dık") ||
275
+ suffix.includes("duk") ||
276
+ suffix.includes("dük") ||
277
+ suffix.includes("tik") ||
278
+ suffix.includes("tık") ||
279
+ suffix.includes("tuk") ||
280
+ suffix.includes("tük") ||
281
+ suffix.includes("sen") ||
282
+ suffix.includes("san") ||
283
+ suffix.includes("sem") ||
284
+ suffix.includes("sam") ||
285
+ suffix.includes("sek") ||
286
+ suffix.includes("sak");
287
+
288
+ // Infinitives apply to direct stems, hardened stems, and widened stems (e.g. başlı -> başla -> başlamak)
289
+ const verbalBases = [stem, ...hardened, ...narrowed];
192
290
  const infinitives = verbalBases.flatMap((v) => restoreInfinitive(v));
193
- const variants = [stem, ...hardened, ...vowelDropped, ...infinitives];
291
+
292
+ // Base candidates
293
+ const variants = [stem, ...hardened, ...vowelDropped, ...geminated, ...narrowed];
194
294
 
195
295
  for (const variant of variants) {
196
296
  if (!seen.has(variant) && variant !== normalized) {
@@ -199,6 +299,16 @@ export function getStemCandidates(
199
299
  candidatesWithWeight.push({ candidate: variant, baseLength: stem.length });
200
300
  }
201
301
  }
302
+
303
+ // Push infinitives with high priority if a verbal suffix matched, preventing noun false-positives
304
+ for (const inf of infinitives) {
305
+ if (!seen.has(inf) && inf !== normalized) {
306
+ seen.add(inf);
307
+ nextFrontier.push(inf);
308
+ const weight = isVerbSuffix ? stem.length + 5 : stem.length;
309
+ candidatesWithWeight.push({ candidate: inf, baseLength: weight });
310
+ }
311
+ }
202
312
  }
203
313
  }
204
314
  }