@rog0x/mcp-string-tools 1.0.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/README.md +73 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +153 -0
- package/dist/tools/slug-generator.d.ts +14 -0
- package/dist/tools/slug-generator.js +85 -0
- package/dist/tools/string-analyzer.d.ts +21 -0
- package/dist/tools/string-analyzer.js +109 -0
- package/dist/tools/string-diff.d.ts +20 -0
- package/dist/tools/string-diff.js +130 -0
- package/dist/tools/string-transformer.d.ts +16 -0
- package/dist/tools/string-transformer.js +177 -0
- package/dist/tools/template-engine.d.ts +8 -0
- package/dist/tools/template-engine.js +109 -0
- package/package.json +20 -0
- package/src/index.ts +186 -0
- package/src/tools/slug-generator.ts +108 -0
- package/src/tools/string-analyzer.ts +135 -0
- package/src/tools/string-diff.ts +163 -0
- package/src/tools/string-transformer.ts +227 -0
- package/src/tools/template-engine.ts +149 -0
- package/tsconfig.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @rog0x/mcp-string-tools
|
|
2
|
+
|
|
3
|
+
Advanced string manipulation tools for AI agents, served over the Model Context Protocol (MCP).
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### analyze_string
|
|
8
|
+
Analyze text to get detailed metrics:
|
|
9
|
+
- Character count (with and without spaces)
|
|
10
|
+
- Word, sentence, and paragraph counts
|
|
11
|
+
- Estimated reading time
|
|
12
|
+
- Flesch-Kincaid reading level score and grade
|
|
13
|
+
- Unique word count and most common words
|
|
14
|
+
- Longest word
|
|
15
|
+
|
|
16
|
+
### transform_string
|
|
17
|
+
Transform text with various operations:
|
|
18
|
+
- **remove_duplicate_lines** - Remove duplicate lines from text
|
|
19
|
+
- **remove_extra_whitespace** - Collapse extra whitespace and blank lines
|
|
20
|
+
- **extract_emails** - Extract all email addresses from text
|
|
21
|
+
- **extract_urls** - Extract all URLs from text
|
|
22
|
+
- **extract_phone_numbers** - Extract phone numbers from text
|
|
23
|
+
- **mask_sensitive_data** - Mask emails, phone numbers, and credit card numbers
|
|
24
|
+
- **truncate** - Truncate text with ellipsis at word boundaries
|
|
25
|
+
- **wrap_text** - Wrap text at a specified column width
|
|
26
|
+
|
|
27
|
+
### diff_strings
|
|
28
|
+
Compare two strings and see the differences:
|
|
29
|
+
- **character** mode - Character-by-character diff
|
|
30
|
+
- **word** mode - Word-by-word diff (default)
|
|
31
|
+
- **line** mode - Line-by-line diff
|
|
32
|
+
- Shows additions, deletions, and unchanged segments with positions
|
|
33
|
+
|
|
34
|
+
### render_template
|
|
35
|
+
Simple template engine:
|
|
36
|
+
- Replace `{{variables}}` with values from a JSON data object
|
|
37
|
+
- Conditionals: `{{#if var}}...{{#else}}...{{/if}}`
|
|
38
|
+
- Loops: `{{#each arr}}...{{this}}...{{@index}}...{{/each}}`
|
|
39
|
+
- Nested property access: `{{user.name}}`
|
|
40
|
+
|
|
41
|
+
### generate_slug
|
|
42
|
+
Generate URL-friendly slugs:
|
|
43
|
+
- Transliterates accented characters (e.g., `cafe` from `cafe`)
|
|
44
|
+
- Removes special characters
|
|
45
|
+
- Configurable separator (default: `-`)
|
|
46
|
+
- Optional max length with clean truncation
|
|
47
|
+
- Optional lowercase toggle
|
|
48
|
+
|
|
49
|
+
## Setup
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install
|
|
53
|
+
npm run build
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Usage with Claude Desktop
|
|
57
|
+
|
|
58
|
+
Add to your `claude_desktop_config.json`:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"string-tools": {
|
|
64
|
+
"command": "node",
|
|
65
|
+
"args": ["D:/products/mcp-servers/mcp-string-tools/dist/index.js"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
const string_analyzer_js_1 = require("./tools/string-analyzer.js");
|
|
8
|
+
const string_transformer_js_1 = require("./tools/string-transformer.js");
|
|
9
|
+
const string_diff_js_1 = require("./tools/string-diff.js");
|
|
10
|
+
const template_engine_js_1 = require("./tools/template-engine.js");
|
|
11
|
+
const slug_generator_js_1 = require("./tools/slug-generator.js");
|
|
12
|
+
const server = new mcp_js_1.McpServer({
|
|
13
|
+
name: "mcp-string-tools",
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
});
|
|
16
|
+
// Tool 1: String Analyzer
|
|
17
|
+
server.tool("analyze_string", "Analyze text to get character count, word count, sentence count, paragraph count, reading time, Flesch-Kincaid reading level, unique words, most common words, and longest word.", {
|
|
18
|
+
text: zod_1.z.string().describe("The text to analyze"),
|
|
19
|
+
}, async ({ text }) => {
|
|
20
|
+
const result = (0, string_analyzer_js_1.analyzeString)(text);
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
// Tool 2: String Transformer
|
|
26
|
+
server.tool("transform_string", "Transform text with operations: remove_duplicate_lines, remove_extra_whitespace, extract_emails, extract_urls, extract_phone_numbers, mask_sensitive_data, truncate, wrap_text.", {
|
|
27
|
+
text: zod_1.z.string().describe("The text to transform"),
|
|
28
|
+
operation: zod_1.z
|
|
29
|
+
.enum([
|
|
30
|
+
"remove_duplicate_lines",
|
|
31
|
+
"remove_extra_whitespace",
|
|
32
|
+
"extract_emails",
|
|
33
|
+
"extract_urls",
|
|
34
|
+
"extract_phone_numbers",
|
|
35
|
+
"mask_sensitive_data",
|
|
36
|
+
"truncate",
|
|
37
|
+
"wrap_text",
|
|
38
|
+
])
|
|
39
|
+
.describe("The transformation operation to apply"),
|
|
40
|
+
mask_char: zod_1.z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.default("*")
|
|
44
|
+
.describe("Character used for masking (mask_sensitive_data only)"),
|
|
45
|
+
max_length: zod_1.z
|
|
46
|
+
.number()
|
|
47
|
+
.optional()
|
|
48
|
+
.default(100)
|
|
49
|
+
.describe("Maximum length for truncation (truncate only)"),
|
|
50
|
+
wrap_width: zod_1.z
|
|
51
|
+
.number()
|
|
52
|
+
.optional()
|
|
53
|
+
.default(80)
|
|
54
|
+
.describe("Column width for text wrapping (wrap_text only)"),
|
|
55
|
+
ellipsis: zod_1.z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.default("...")
|
|
59
|
+
.describe("Ellipsis string for truncation (truncate only)"),
|
|
60
|
+
}, async ({ text, operation, mask_char, max_length, wrap_width, ellipsis }) => {
|
|
61
|
+
const result = (0, string_transformer_js_1.transformString)({
|
|
62
|
+
text,
|
|
63
|
+
operation,
|
|
64
|
+
maskChar: mask_char,
|
|
65
|
+
maxLength: max_length,
|
|
66
|
+
wrapWidth: wrap_width,
|
|
67
|
+
ellipsis,
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
// Tool 3: String Diff
|
|
74
|
+
server.tool("diff_strings", "Compare two strings showing additions, deletions, and unchanged segments with positions. Supports character-level, word-level, and line-level diff modes.", {
|
|
75
|
+
old_text: zod_1.z.string().describe("The original text"),
|
|
76
|
+
new_text: zod_1.z.string().describe("The new/modified text"),
|
|
77
|
+
mode: zod_1.z
|
|
78
|
+
.enum(["character", "word", "line"])
|
|
79
|
+
.optional()
|
|
80
|
+
.default("word")
|
|
81
|
+
.describe("Diff granularity: character, word, or line"),
|
|
82
|
+
}, async ({ old_text, new_text, mode }) => {
|
|
83
|
+
const result = (0, string_diff_js_1.diffStrings)(old_text, new_text, mode);
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
// Tool 4: Template Engine
|
|
89
|
+
server.tool("render_template", "Render a template string by replacing {{variables}} with values from a data object. Supports {{#if var}}...{{#else}}...{{/if}} conditionals and {{#each arr}}...{{/each}} loops with {{this}}, {{@index}}, and {{this.prop}}.", {
|
|
90
|
+
template: zod_1.z.string().describe("Template string with {{variable}} placeholders"),
|
|
91
|
+
data: zod_1.z
|
|
92
|
+
.string()
|
|
93
|
+
.describe("JSON string of key-value pairs to substitute into the template"),
|
|
94
|
+
}, async ({ template, data }) => {
|
|
95
|
+
let parsedData;
|
|
96
|
+
try {
|
|
97
|
+
parsedData = JSON.parse(data);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: JSON.stringify({ error: "Invalid JSON in data parameter" }),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const result = (0, template_engine_js_1.renderTemplate)(template, parsedData);
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
// Tool 5: Slug Generator
|
|
116
|
+
server.tool("generate_slug", "Generate a URL-friendly slug from text. Removes special characters, lowercases, replaces spaces with hyphens, transliterates accented characters. Configurable separator and max length.", {
|
|
117
|
+
text: zod_1.z.string().describe("The text to convert to a slug"),
|
|
118
|
+
separator: zod_1.z
|
|
119
|
+
.string()
|
|
120
|
+
.optional()
|
|
121
|
+
.default("-")
|
|
122
|
+
.describe("Character to use as word separator (default: -)"),
|
|
123
|
+
max_length: zod_1.z
|
|
124
|
+
.number()
|
|
125
|
+
.optional()
|
|
126
|
+
.default(0)
|
|
127
|
+
.describe("Maximum slug length (0 = unlimited)"),
|
|
128
|
+
lowercase: zod_1.z
|
|
129
|
+
.boolean()
|
|
130
|
+
.optional()
|
|
131
|
+
.default(true)
|
|
132
|
+
.describe("Convert to lowercase (default: true)"),
|
|
133
|
+
}, async ({ text, separator, max_length, lowercase }) => {
|
|
134
|
+
const result = (0, slug_generator_js_1.generateSlug)({
|
|
135
|
+
text,
|
|
136
|
+
separator,
|
|
137
|
+
maxLength: max_length,
|
|
138
|
+
lowercase,
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
async function main() {
|
|
145
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
146
|
+
await server.connect(transport);
|
|
147
|
+
console.error("mcp-string-tools server running on stdio");
|
|
148
|
+
}
|
|
149
|
+
main().catch((err) => {
|
|
150
|
+
console.error("Fatal error:", err);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
});
|
|
153
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface SlugOptions {
|
|
2
|
+
text: string;
|
|
3
|
+
separator?: string;
|
|
4
|
+
maxLength?: number;
|
|
5
|
+
lowercase?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface SlugResult {
|
|
8
|
+
original: string;
|
|
9
|
+
slug: string;
|
|
10
|
+
length: number;
|
|
11
|
+
wasTruncated: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function generateSlug(options: SlugOptions): SlugResult;
|
|
14
|
+
//# sourceMappingURL=slug-generator.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateSlug = generateSlug;
|
|
4
|
+
const TRANSLITERATION_MAP = {
|
|
5
|
+
"\u00e0": "a", "\u00e1": "a", "\u00e2": "a", "\u00e3": "a", "\u00e4": "a", "\u00e5": "a",
|
|
6
|
+
"\u00e6": "ae",
|
|
7
|
+
"\u00e7": "c",
|
|
8
|
+
"\u00e8": "e", "\u00e9": "e", "\u00ea": "e", "\u00eb": "e",
|
|
9
|
+
"\u00ec": "i", "\u00ed": "i", "\u00ee": "i", "\u00ef": "i",
|
|
10
|
+
"\u00f0": "d",
|
|
11
|
+
"\u00f1": "n",
|
|
12
|
+
"\u00f2": "o", "\u00f3": "o", "\u00f4": "o", "\u00f5": "o", "\u00f6": "o", "\u00f8": "o",
|
|
13
|
+
"\u00f9": "u", "\u00fa": "u", "\u00fb": "u", "\u00fc": "u",
|
|
14
|
+
"\u00fd": "y", "\u00ff": "y",
|
|
15
|
+
"\u00fe": "th",
|
|
16
|
+
"\u00df": "ss",
|
|
17
|
+
"\u0100": "a", "\u0101": "a", "\u0102": "a", "\u0103": "a", "\u0104": "a", "\u0105": "a",
|
|
18
|
+
"\u0106": "c", "\u0107": "c", "\u0108": "c", "\u0109": "c", "\u010a": "c", "\u010b": "c", "\u010c": "c", "\u010d": "c",
|
|
19
|
+
"\u010e": "d", "\u010f": "d", "\u0110": "d", "\u0111": "d",
|
|
20
|
+
"\u0112": "e", "\u0113": "e", "\u0114": "e", "\u0115": "e", "\u0116": "e", "\u0117": "e", "\u0118": "e", "\u0119": "e", "\u011a": "e", "\u011b": "e",
|
|
21
|
+
"\u011c": "g", "\u011d": "g", "\u011e": "g", "\u011f": "g", "\u0120": "g", "\u0121": "g", "\u0122": "g", "\u0123": "g",
|
|
22
|
+
"\u0124": "h", "\u0125": "h", "\u0126": "h", "\u0127": "h",
|
|
23
|
+
"\u0128": "i", "\u0129": "i", "\u012a": "i", "\u012b": "i", "\u012c": "i", "\u012d": "i", "\u012e": "i", "\u012f": "i", "\u0130": "i", "\u0131": "i",
|
|
24
|
+
"\u0139": "l", "\u013a": "l", "\u013b": "l", "\u013c": "l", "\u013d": "l", "\u013e": "l", "\u0141": "l", "\u0142": "l",
|
|
25
|
+
"\u0143": "n", "\u0144": "n", "\u0145": "n", "\u0146": "n", "\u0147": "n", "\u0148": "n",
|
|
26
|
+
"\u014c": "o", "\u014d": "o", "\u014e": "o", "\u014f": "o", "\u0150": "o", "\u0151": "o", "\u0152": "oe", "\u0153": "oe",
|
|
27
|
+
"\u0154": "r", "\u0155": "r", "\u0156": "r", "\u0157": "r", "\u0158": "r", "\u0159": "r",
|
|
28
|
+
"\u015a": "s", "\u015b": "s", "\u015c": "s", "\u015d": "s", "\u015e": "s", "\u015f": "s", "\u0160": "s", "\u0161": "s",
|
|
29
|
+
"\u0162": "t", "\u0163": "t", "\u0164": "t", "\u0165": "t", "\u0166": "t", "\u0167": "t",
|
|
30
|
+
"\u0168": "u", "\u0169": "u", "\u016a": "u", "\u016b": "u", "\u016c": "u", "\u016d": "u", "\u016e": "u", "\u016f": "u", "\u0170": "u", "\u0171": "u", "\u0172": "u", "\u0173": "u",
|
|
31
|
+
"\u0174": "w", "\u0175": "w",
|
|
32
|
+
"\u0176": "y", "\u0177": "y", "\u0178": "y",
|
|
33
|
+
"\u0179": "z", "\u017a": "z", "\u017b": "z", "\u017c": "z", "\u017d": "z", "\u017e": "z",
|
|
34
|
+
};
|
|
35
|
+
function transliterate(text) {
|
|
36
|
+
let result = "";
|
|
37
|
+
for (const char of text) {
|
|
38
|
+
const lower = char.toLowerCase();
|
|
39
|
+
if (TRANSLITERATION_MAP[lower]) {
|
|
40
|
+
const replacement = TRANSLITERATION_MAP[lower];
|
|
41
|
+
// Preserve case for first letter
|
|
42
|
+
if (char === char.toUpperCase() && char !== char.toLowerCase()) {
|
|
43
|
+
result += replacement.charAt(0).toUpperCase() + replacement.slice(1);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
result += replacement;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
result += char;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
function generateSlug(options) {
|
|
56
|
+
const { text, separator = "-", maxLength = 0, lowercase = true, } = options;
|
|
57
|
+
let slug = transliterate(text);
|
|
58
|
+
if (lowercase) {
|
|
59
|
+
slug = slug.toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
// Replace non-alphanumeric chars (except separator) with separator
|
|
62
|
+
const escapedSep = separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
63
|
+
slug = slug.replace(/[^a-zA-Z0-9]+/g, separator);
|
|
64
|
+
// Remove leading/trailing separators
|
|
65
|
+
const trimRegex = new RegExp(`^${escapedSep}+|${escapedSep}+$`, "g");
|
|
66
|
+
slug = slug.replace(trimRegex, "");
|
|
67
|
+
// Collapse multiple consecutive separators
|
|
68
|
+
const multiRegex = new RegExp(`${escapedSep}{2,}`, "g");
|
|
69
|
+
slug = slug.replace(multiRegex, separator);
|
|
70
|
+
let wasTruncated = false;
|
|
71
|
+
if (maxLength > 0 && slug.length > maxLength) {
|
|
72
|
+
wasTruncated = true;
|
|
73
|
+
slug = slug.slice(0, maxLength);
|
|
74
|
+
// Don't end on a separator
|
|
75
|
+
const endTrimRegex = new RegExp(`${escapedSep}+$`);
|
|
76
|
+
slug = slug.replace(endTrimRegex, "");
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
original: text,
|
|
80
|
+
slug,
|
|
81
|
+
length: slug.length,
|
|
82
|
+
wasTruncated,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=slug-generator.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface AnalysisResult {
|
|
2
|
+
charCount: number;
|
|
3
|
+
charCountNoSpaces: number;
|
|
4
|
+
wordCount: number;
|
|
5
|
+
sentenceCount: number;
|
|
6
|
+
paragraphCount: number;
|
|
7
|
+
readingTimeMinutes: number;
|
|
8
|
+
readingLevel: {
|
|
9
|
+
score: number;
|
|
10
|
+
grade: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
uniqueWordCount: number;
|
|
14
|
+
mostCommonWords: Array<{
|
|
15
|
+
word: string;
|
|
16
|
+
count: number;
|
|
17
|
+
}>;
|
|
18
|
+
longestWord: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function analyzeString(text: string): AnalysisResult;
|
|
21
|
+
//# sourceMappingURL=string-analyzer.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeString = analyzeString;
|
|
4
|
+
function countSyllables(word) {
|
|
5
|
+
const w = word.toLowerCase().replace(/[^a-z]/g, "");
|
|
6
|
+
if (w.length <= 2)
|
|
7
|
+
return 1;
|
|
8
|
+
let count = 0;
|
|
9
|
+
const vowels = "aeiouy";
|
|
10
|
+
let prevVowel = false;
|
|
11
|
+
for (let i = 0; i < w.length; i++) {
|
|
12
|
+
const isVowel = vowels.includes(w[i]);
|
|
13
|
+
if (isVowel && !prevVowel) {
|
|
14
|
+
count++;
|
|
15
|
+
}
|
|
16
|
+
prevVowel = isVowel;
|
|
17
|
+
}
|
|
18
|
+
// Silent e
|
|
19
|
+
if (w.endsWith("e") && count > 1) {
|
|
20
|
+
count--;
|
|
21
|
+
}
|
|
22
|
+
// -le ending
|
|
23
|
+
if (w.endsWith("le") && w.length > 2 && !vowels.includes(w[w.length - 3])) {
|
|
24
|
+
count++;
|
|
25
|
+
}
|
|
26
|
+
return Math.max(count, 1);
|
|
27
|
+
}
|
|
28
|
+
function analyzeString(text) {
|
|
29
|
+
const charCount = text.length;
|
|
30
|
+
const charCountNoSpaces = text.replace(/\s/g, "").length;
|
|
31
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
32
|
+
const wordCount = words.length;
|
|
33
|
+
const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
34
|
+
const sentenceCount = sentences.length;
|
|
35
|
+
const paragraphs = text.split(/\n\s*\n/).filter((p) => p.trim().length > 0);
|
|
36
|
+
const paragraphCount = Math.max(paragraphs.length, text.trim().length > 0 ? 1 : 0);
|
|
37
|
+
const readingTimeMinutes = Math.max(Math.ceil(wordCount / 200), wordCount > 0 ? 1 : 0);
|
|
38
|
+
// Flesch-Kincaid
|
|
39
|
+
const totalSyllables = words.reduce((sum, w) => sum + countSyllables(w), 0);
|
|
40
|
+
let fkScore = 0;
|
|
41
|
+
let fkGrade = "N/A";
|
|
42
|
+
let fkDescription = "Text too short to analyze";
|
|
43
|
+
if (wordCount > 0 && sentenceCount > 0) {
|
|
44
|
+
fkScore =
|
|
45
|
+
206.835 -
|
|
46
|
+
1.015 * (wordCount / sentenceCount) -
|
|
47
|
+
84.6 * (totalSyllables / wordCount);
|
|
48
|
+
fkScore = Math.round(fkScore * 10) / 10;
|
|
49
|
+
if (fkScore >= 90) {
|
|
50
|
+
fkGrade = "5th grade";
|
|
51
|
+
fkDescription = "Very easy to read";
|
|
52
|
+
}
|
|
53
|
+
else if (fkScore >= 80) {
|
|
54
|
+
fkGrade = "6th grade";
|
|
55
|
+
fkDescription = "Easy to read";
|
|
56
|
+
}
|
|
57
|
+
else if (fkScore >= 70) {
|
|
58
|
+
fkGrade = "7th grade";
|
|
59
|
+
fkDescription = "Fairly easy to read";
|
|
60
|
+
}
|
|
61
|
+
else if (fkScore >= 60) {
|
|
62
|
+
fkGrade = "8th-9th grade";
|
|
63
|
+
fkDescription = "Plain English";
|
|
64
|
+
}
|
|
65
|
+
else if (fkScore >= 50) {
|
|
66
|
+
fkGrade = "10th-12th grade";
|
|
67
|
+
fkDescription = "Fairly difficult to read";
|
|
68
|
+
}
|
|
69
|
+
else if (fkScore >= 30) {
|
|
70
|
+
fkGrade = "College";
|
|
71
|
+
fkDescription = "Difficult to read";
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
fkGrade = "College graduate";
|
|
75
|
+
fkDescription = "Very difficult to read";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Word frequency
|
|
79
|
+
const wordFreq = new Map();
|
|
80
|
+
for (const w of words) {
|
|
81
|
+
const lower = w.toLowerCase().replace(/[^a-z0-9'-]/g, "");
|
|
82
|
+
if (lower.length > 0) {
|
|
83
|
+
wordFreq.set(lower, (wordFreq.get(lower) || 0) + 1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const uniqueWordCount = wordFreq.size;
|
|
87
|
+
const mostCommonWords = Array.from(wordFreq.entries())
|
|
88
|
+
.sort((a, b) => b[1] - a[1])
|
|
89
|
+
.slice(0, 10)
|
|
90
|
+
.map(([word, count]) => ({ word, count }));
|
|
91
|
+
const longestWord = words.reduce((longest, w) => (w.length > longest.length ? w : longest), "");
|
|
92
|
+
return {
|
|
93
|
+
charCount,
|
|
94
|
+
charCountNoSpaces,
|
|
95
|
+
wordCount,
|
|
96
|
+
sentenceCount,
|
|
97
|
+
paragraphCount,
|
|
98
|
+
readingTimeMinutes,
|
|
99
|
+
readingLevel: {
|
|
100
|
+
score: fkScore,
|
|
101
|
+
grade: fkGrade,
|
|
102
|
+
description: fkDescription,
|
|
103
|
+
},
|
|
104
|
+
uniqueWordCount,
|
|
105
|
+
mostCommonWords,
|
|
106
|
+
longestWord,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=string-analyzer.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type DiffType = "addition" | "deletion" | "unchanged";
|
|
2
|
+
export interface DiffSegment {
|
|
3
|
+
type: DiffType;
|
|
4
|
+
value: string;
|
|
5
|
+
position: {
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface DiffResult {
|
|
11
|
+
mode: "character" | "word" | "line";
|
|
12
|
+
segments: DiffSegment[];
|
|
13
|
+
summary: {
|
|
14
|
+
additions: number;
|
|
15
|
+
deletions: number;
|
|
16
|
+
unchanged: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare function diffStrings(oldStr: string, newStr: string, mode?: "character" | "word" | "line"): DiffResult;
|
|
20
|
+
//# sourceMappingURL=string-diff.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffStrings = diffStrings;
|
|
4
|
+
function lcs(a, b) {
|
|
5
|
+
const m = a.length;
|
|
6
|
+
const n = b.length;
|
|
7
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
8
|
+
for (let i = 1; i <= m; i++) {
|
|
9
|
+
for (let j = 1; j <= n; j++) {
|
|
10
|
+
if (a[i - 1] === b[j - 1]) {
|
|
11
|
+
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const result = [];
|
|
19
|
+
let i = m;
|
|
20
|
+
let j = n;
|
|
21
|
+
while (i > 0 && j > 0) {
|
|
22
|
+
if (a[i - 1] === b[j - 1]) {
|
|
23
|
+
result.unshift(a[i - 1]);
|
|
24
|
+
i--;
|
|
25
|
+
j--;
|
|
26
|
+
}
|
|
27
|
+
else if (dp[i - 1][j] > dp[i][j - 1]) {
|
|
28
|
+
i--;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
j--;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
function computeDiff(oldTokens, newTokens) {
|
|
37
|
+
const common = lcs(oldTokens, newTokens);
|
|
38
|
+
const segments = [];
|
|
39
|
+
let oi = 0;
|
|
40
|
+
let ni = 0;
|
|
41
|
+
let ci = 0;
|
|
42
|
+
let pos = 0;
|
|
43
|
+
while (ci < common.length) {
|
|
44
|
+
// Deletions from old
|
|
45
|
+
while (oi < oldTokens.length && oldTokens[oi] !== common[ci]) {
|
|
46
|
+
const start = pos;
|
|
47
|
+
const value = oldTokens[oi];
|
|
48
|
+
segments.push({
|
|
49
|
+
type: "deletion",
|
|
50
|
+
value,
|
|
51
|
+
position: { start, end: start + value.length },
|
|
52
|
+
});
|
|
53
|
+
oi++;
|
|
54
|
+
}
|
|
55
|
+
// Additions from new
|
|
56
|
+
while (ni < newTokens.length && newTokens[ni] !== common[ci]) {
|
|
57
|
+
const start = pos;
|
|
58
|
+
const value = newTokens[ni];
|
|
59
|
+
segments.push({
|
|
60
|
+
type: "addition",
|
|
61
|
+
value,
|
|
62
|
+
position: { start, end: start + value.length },
|
|
63
|
+
});
|
|
64
|
+
pos += value.length;
|
|
65
|
+
ni++;
|
|
66
|
+
}
|
|
67
|
+
// Unchanged
|
|
68
|
+
if (ci < common.length) {
|
|
69
|
+
const value = common[ci];
|
|
70
|
+
const start = pos;
|
|
71
|
+
segments.push({
|
|
72
|
+
type: "unchanged",
|
|
73
|
+
value,
|
|
74
|
+
position: { start, end: start + value.length },
|
|
75
|
+
});
|
|
76
|
+
pos += value.length;
|
|
77
|
+
oi++;
|
|
78
|
+
ni++;
|
|
79
|
+
ci++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Remaining deletions
|
|
83
|
+
while (oi < oldTokens.length) {
|
|
84
|
+
const value = oldTokens[oi];
|
|
85
|
+
segments.push({
|
|
86
|
+
type: "deletion",
|
|
87
|
+
value,
|
|
88
|
+
position: { start: pos, end: pos + value.length },
|
|
89
|
+
});
|
|
90
|
+
oi++;
|
|
91
|
+
}
|
|
92
|
+
// Remaining additions
|
|
93
|
+
while (ni < newTokens.length) {
|
|
94
|
+
const value = newTokens[ni];
|
|
95
|
+
segments.push({
|
|
96
|
+
type: "addition",
|
|
97
|
+
value,
|
|
98
|
+
position: { start: pos, end: pos + value.length },
|
|
99
|
+
});
|
|
100
|
+
pos += value.length;
|
|
101
|
+
ni++;
|
|
102
|
+
}
|
|
103
|
+
return segments;
|
|
104
|
+
}
|
|
105
|
+
function diffStrings(oldStr, newStr, mode = "word") {
|
|
106
|
+
let oldTokens;
|
|
107
|
+
let newTokens;
|
|
108
|
+
switch (mode) {
|
|
109
|
+
case "character":
|
|
110
|
+
oldTokens = oldStr.split("");
|
|
111
|
+
newTokens = newStr.split("");
|
|
112
|
+
break;
|
|
113
|
+
case "word":
|
|
114
|
+
oldTokens = oldStr.split(/(\s+)/).filter((t) => t.length > 0);
|
|
115
|
+
newTokens = newStr.split(/(\s+)/).filter((t) => t.length > 0);
|
|
116
|
+
break;
|
|
117
|
+
case "line":
|
|
118
|
+
oldTokens = oldStr.split("\n");
|
|
119
|
+
newTokens = newStr.split("\n");
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
const segments = computeDiff(oldTokens, newTokens);
|
|
123
|
+
const summary = {
|
|
124
|
+
additions: segments.filter((s) => s.type === "addition").length,
|
|
125
|
+
deletions: segments.filter((s) => s.type === "deletion").length,
|
|
126
|
+
unchanged: segments.filter((s) => s.type === "unchanged").length,
|
|
127
|
+
};
|
|
128
|
+
return { mode, segments, summary };
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=string-diff.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TransformOptions {
|
|
2
|
+
operation: string;
|
|
3
|
+
text: string;
|
|
4
|
+
maskChar?: string;
|
|
5
|
+
maxLength?: number;
|
|
6
|
+
wrapWidth?: number;
|
|
7
|
+
ellipsis?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TransformResult {
|
|
10
|
+
original: string;
|
|
11
|
+
transformed: string;
|
|
12
|
+
operation: string;
|
|
13
|
+
details?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export declare function transformString(options: TransformOptions): TransformResult;
|
|
16
|
+
//# sourceMappingURL=string-transformer.d.ts.map
|