From c1c3bbf0fbd24dc45103880859eb634fd9927e0a Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Mon, 13 Nov 2023 22:36:18 +0100 Subject: [PATCH 01/10] build: use deno and deno2node Co-authored-by: Dunkan <70066170+dcdunkan@users.noreply.github.com> Co-authored-by: Wojciech Pawlik --- .github/dependabot.yml | 14 ---- .github/workflows/{nodejs.yml => deno.yml} | 38 +++++++--- .gitignore | 1 - .vscode/settings.json | 3 + README.md | 53 ++++++++----- deno.jsonc | 10 +++ package.json | 39 ++-------- source/html.test.ts | 71 +++++++++--------- source/html.ts | 14 ++-- source/index.ts | 5 -- source/markdown.test.ts | 86 +++++++++++----------- source/markdown.ts | 38 ++++++---- source/markdownv2.test.ts | 77 +++++++++---------- source/markdownv2.ts | 24 +++--- source/mod.ts | 5 ++ source/types.ts | 2 +- tsconfig.json | 9 ++- 17 files changed, 254 insertions(+), 235 deletions(-) delete mode 100644 .github/dependabot.yml rename .github/workflows/{nodejs.yml => deno.yml} (53%) create mode 100644 .vscode/settings.json create mode 100644 deno.jsonc delete mode 100644 source/index.ts create mode 100644 source/mod.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 7f8c9fa..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,14 +0,0 @@ -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "npm" - directory: "/" - open-pull-requests-limit: 30 - schedule: - interval: "weekly" - day: "saturday" - time: "02:42" # UTC - commit-message: - prefix: "build(npm):" diff --git a/.github/workflows/nodejs.yml b/.github/workflows/deno.yml similarity index 53% rename from .github/workflows/nodejs.yml rename to .github/workflows/deno.yml index 8936899..d805f91 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/deno.yml @@ -1,44 +1,60 @@ -name: Node.js +name: Deno on: push: pull_request: schedule: - # Check if it works with current dependencies + # Check if it works with current versions - cron: '42 2 * * 6' # weekly on Saturday 2:42 UTC jobs: + denofmt-and-lint: + runs-on: ubuntu-latest + steps: + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - uses: actions/checkout@v4 + + - run: deno lint + - run: deno fmt --check + test: - name: Node.js runs-on: ubuntu-latest steps: - - uses: actions/setup-node@v4 + - uses: denoland/setup-deno@v1 with: - node-version: 'lts/*' + deno-version: v1.x - uses: actions/checkout@v4 - - run: npm install - - run: npm test - - run: npm pack - publish: + - run: deno cache source/*.ts + - run: deno check source/*.ts + - run: deno test + + npm: name: Publish on NPM - if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest - needs: test + needs: [denofmt-and-lint, test] permissions: contents: write id-token: write steps: - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x - uses: actions/setup-node@v4 with: node-version: 'lts/*' registry-url: 'https://registry.npmjs.org' - run: npm install + - run: npm pack - run: npm publish + if: startsWith(github.ref, 'refs/tags/v') env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_ACCESS: public NPM_CONFIG_PROVENANCE: true - name: Create GitHub release + if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v1 diff --git a/.gitignore b/.gitignore index 3148a6d..7866d79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /*-*.*.*.tgz -coverage dist node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..43df84f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "deno.enable": true +} diff --git a/README.md b/README.md index 8a4bcdc..27599ad 100644 --- a/README.md +++ b/README.md @@ -2,58 +2,73 @@ [![NPM Version](https://img.shields.io/npm/v/telegram-format.svg)](https://www.npmjs.com/package/telegram-format) [![node](https://img.shields.io/node/v/telegram-format.svg)](https://www.npmjs.com/package/telegram-format) +[![deno module](https://shield.deno.dev/x/telegram_format)](https://deno.land/x/telegram_format) > Format Telegram message texts with Markdown or HTML -This library abstracts the [formatting options](https://core.telegram.org/bots/api#formatting-options) for you. +This library abstracts the +[formatting options](https://core.telegram.org/bots/api#formatting-options) for +you. ## Install +Node.js: + ```bash npm install telegram-format ``` +Deno: + +```ts +import {/* ... */} from "https://deno.land/x/telegram_format/mod.ts"; +``` + ## Usage ```js -import {html as format} from 'telegram-format'; -import {markdownv2 as format} from 'telegram-format'; +import { html as format } from "telegram-format"; +import { markdownv2 as format } from "telegram-format"; -format.bold('hey'); +format.bold("hey"); //=> '*hey*' -format.italic('you'); +format.italic("you"); //=> '_you_' -format.bold(format.italic('they')) +format.bold(format.italic("they")); //=> '*_they_*' -format.url('me', 'https://edjopato.de'); +format.url("me", "https://edjopato.de"); //=> '[me](https://edjopato.de)' // Legacy but still works -import {markdown as format} from 'telegram-format'; +import { markdown as format } from "telegram-format"; ``` -When using `format` as an alias its easy to switch between Markdown and HTML fast. +When using `format` as an alias its easy to switch between Markdown and HTML +fast. ### Escaping Input -When you have something that might be unescaped you need to use `format.escape` before formatting it. +When you have something that might be unescaped you need to use `format.escape` +before formatting it. ```js -const username = 'master_yoda' -format.italic(format.escape(username)) +const username = "master_yoda"; +format.italic(format.escape(username)); ``` -`format.monospace` and `format.monospaceBlock` will escape on their own as they only need to escape specific characters. -You do not need to escape the input in this cases. +`format.monospace` and `format.monospaceBlock` will escape on their own as they +only need to escape specific characters. You do not need to escape the input in +this cases. -`MarkdownV2` and `HTML` are fairly similar in escaping inputs but `Markdown` is not. -`Markdown` is still supported by this library and by Telegram for legacy reasons but it behaves a bit differently. -`Markdown` still escapes inputs and does not need `format.escape` before. -Also nested formatting is not supported by telegram itself. -Consider switching to `MarkdownV2` or `HTML` for simplicity reasons. +`MarkdownV2` and `HTML` are fairly similar in escaping inputs but `Markdown` is +not. `Markdown` is still supported by this library and by Telegram for legacy +reasons but it behaves a bit differently. `Markdown` still escapes inputs and +does not need `format.escape` before. Also nested formatting is not supported by +telegram itself. Consider switching to `MarkdownV2` or `HTML` for simplicity +reasons. ## API diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..de48e40 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,10 @@ +{ + "lock": false, + "fmt": { + "useTabs": true + }, + "exclude": [ + "./dist/", + "./node_modules/" + ] +} diff --git a/package.json b/package.json index f6b2a54..f2ca2c7 100644 --- a/package.json +++ b/package.json @@ -17,44 +17,17 @@ "url": "https://edjopato.de" }, "scripts": { - "build": "del-cli dist && tsc", - "prepack": "npm run build", - "test": "tsc --sourceMap && xo && c8 --all ava" + "prepare": "deno2node", + "test": "echo use deno test && exit 1" }, "type": "module", "engines": { "node": ">=14" }, "devDependencies": { - "@sindresorhus/tsconfig": "^5.0.0", - "ava": "^5.0.1", - "c8": "^8.0.1", - "del-cli": "^5.0.0", - "typescript": "^5.0.2", - "xo": "^0.56.0" + "deno2node": "1.10.1" }, - "files": [ - "dist", - "!*.test.*" - ], - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "xo": { - "rules": { - "@typescript-eslint/naming-convention": "off", - "@typescript-eslint/prefer-readonly-parameter-types": "error", - "unicorn/prefer-string-replace-all": "off", - "ava/no-ignored-test-files": "off" - }, - "overrides": [ - { - "files": [ - "**/*.test.*" - ], - "rules": { - "@typescript-eslint/prefer-readonly-parameter-types": "off" - } - } - ] - } + "files": ["dist"], + "main": "./dist/mod.js", + "types": "./dist/mod.d.ts" } diff --git a/source/html.test.ts b/source/html.test.ts index 0cc3301..9261260 100644 --- a/source/html.test.ts +++ b/source/html.test.ts @@ -1,67 +1,70 @@ -import test from 'ava'; -import {html as format} from './html.js'; +import { assertEquals } from "https://deno.land/std@0.206.0/assert/mod.ts"; +import { html as format } from "./html.ts"; -test('bold', t => { - t.is(format.bold('bold'), 'bold'); +Deno.test("bold", () => { + assertEquals(format.bold("bold"), "bold"); }); -test('italic', t => { - t.is(format.italic('italic'), 'italic'); +Deno.test("italic", () => { + assertEquals(format.italic("italic"), "italic"); }); -test('strikethrough', t => { - t.is(format.strikethrough('strikethrough'), 'strikethrough'); +Deno.test("strikethrough", () => { + assertEquals(format.strikethrough("strikethrough"), "strikethrough"); }); -test('underline', t => { - t.is(format.underline('underline'), 'underline'); +Deno.test("underline", () => { + assertEquals(format.underline("underline"), "underline"); }); -test('spoiler', t => { - t.is(format.spoiler('spoiler'), 'spoiler'); +Deno.test("spoiler", () => { + assertEquals( + format.spoiler("spoiler"), + 'spoiler', + ); }); -test('url', t => { - t.is( - format.url('me', 'https://edjopato.de'), +Deno.test("url", () => { + assertEquals( + format.url("me", "https://edjopato.de"), 'me', ); }); -test('escape', t => { - t.is(format.escape('hy'), 'h<e>y'); +Deno.test("escape", () => { + assertEquals(format.escape("hy"), "h<e>y"); }); -test('bold italic', t => { - t.is(format.bold(format.italic('green')), 'green'); +Deno.test("bold italic", () => { + assertEquals(format.bold(format.italic("green")), "green"); }); -test('user mention', t => { - t.is( - format.userMention('inline mention of a user', 123_456_789), +Deno.test("user mention", () => { + assertEquals( + format.userMention("inline mention of a user", 123_456_789), 'inline mention of a user', ); }); -test('monospace', t => { - t.is( - format.monospace('inline fixed-width code'), - 'inline fixed-width code', +Deno.test("monospace", () => { + assertEquals( + format.monospace("inline fixed-width code"), + "inline fixed-width code", ); }); -test('monospaceBlock w/o language', t => { - t.is( - format.monospaceBlock('pre-formatted fixed-width code block'), - '
pre-formatted fixed-width code block
', +Deno.test("monospaceBlock w/o language", () => { + assertEquals( + format.monospaceBlock("pre-formatted fixed-width code block"), + "
pre-formatted fixed-width code block
", ); }); -test('monospaceBlock w/ language', t => { - t.is( +Deno.test("monospaceBlock w/ language", () => { + assertEquals( format.monospaceBlock( - 'pre-formatted fixed-width code block written in the Python programming language', - 'python', + "pre-formatted fixed-width code block written in the Python programming language", + "python", ), '
pre-formatted fixed-width code block written in the Python programming language
', ); diff --git a/source/html.ts b/source/html.ts index 174fe64..4206f36 100644 --- a/source/html.ts +++ b/source/html.ts @@ -1,10 +1,10 @@ -import type {Formatter} from './types.js'; +import type { Formatter } from "./types.ts"; function escape(text: string): string { return text - .replace(/&/g, '&') - .replace(//g, '>'); + .replace(/&/g, "&") + .replace(//g, ">"); } function bold(text: string): string { @@ -33,7 +33,9 @@ function monospace(text: string): string { function monospaceBlock(text: string, programmingLanguage?: string): string { if (programmingLanguage) { - return `
${escape(text)}
`; + return `
${
+			escape(text)
+		}
`; } return `
${escape(text)}
`; @@ -49,7 +51,7 @@ function userMention(label: string, userId: number): string { /** https://core.telegram.org/bots/api#html-style */ export const html = { - parse_mode: 'HTML', + parse_mode: "HTML", escape, bold, italic, diff --git a/source/index.ts b/source/index.ts deleted file mode 100644 index 41ea91b..0000000 --- a/source/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type {Formatter} from './types.js'; - -export {html} from './html.js'; -export {markdown} from './markdown.js'; -export {markdownv2} from './markdownv2.js'; diff --git a/source/markdown.test.ts b/source/markdown.test.ts index 55a5630..1ea26d6 100644 --- a/source/markdown.test.ts +++ b/source/markdown.test.ts @@ -1,75 +1,75 @@ -import test from 'ava'; -import {markdown as format} from './markdown.js'; +import { + assertEquals, + assertThrows, +} from "https://deno.land/std@0.206.0/assert/mod.ts"; +import { markdown as format } from "./markdown.ts"; -test('bold', t => { - t.is(format.bold('bold'), '*bold*'); +Deno.test("bold", () => { + assertEquals(format.bold("bold"), "*bold*"); }); -test('italic', t => { - t.is(format.italic('italic'), '_italic_'); +Deno.test("italic", () => { + assertEquals(format.italic("italic"), "_italic_"); }); -test('url', t => { - t.is(format.url('me', 'https://edjopato.de'), '[me](https://edjopato.de)'); +Deno.test("url", () => { + assertEquals( + format.url("me", "https://edjopato.de"), + "[me](https://edjopato.de)", + ); }); -test('escape', t => { - t.is(format.escape('[h_]e(*y)`'), 'hey'); +Deno.test("escape", () => { + assertEquals(format.escape("[h_]e(*y)`"), "hey"); }); -test('bold malicious', t => { - t.is(format.bold('bo*ld'), '*bold*'); +Deno.test("bold malicious", () => { + assertEquals(format.bold("bo*ld"), "*bold*"); }); -test('italic malicious', t => { - t.is(format.italic('ita_lic'), '_italic_'); +Deno.test("italic malicious", () => { + assertEquals(format.italic("ita_lic"), "_italic_"); }); -test('user mention', t => { - t.is( - format.userMention('inline mention of a user', 123_456_789), - '[inline mention of a user](tg://user?id=123456789)', +Deno.test("user mention", () => { + assertEquals( + format.userMention("inline mention of a user", 123_456_789), + "[inline mention of a user](tg://user?id=123456789)", ); }); -test('monospace', t => { - t.is( - format.monospace('inline fixed-width code'), - '`inline fixed-width code`', +Deno.test("monospace", () => { + assertEquals( + format.monospace("inline fixed-width code"), + "`inline fixed-width code`", ); }); -test('monospaceBlock w/o language', t => { - t.is( - format.monospaceBlock('pre-formatted fixed-width code block'), - '```\npre-formatted fixed-width code block\n```', +Deno.test("monospaceBlock w/o language", () => { + assertEquals( + format.monospaceBlock("pre-formatted fixed-width code block"), + "```\npre-formatted fixed-width code block\n```", ); }); -test('monospaceBlock w/ language', t => { - t.is( +Deno.test("monospaceBlock w/ language", () => { + assertEquals( format.monospaceBlock( - 'pre-formatted fixed-width code block written in the Python programming language', - 'python', + "pre-formatted fixed-width code block written in the Python programming language", + "python", ), - '```python\npre-formatted fixed-width code block written in the Python programming language\n```', + "```python\npre-formatted fixed-width code block written in the Python programming language\n```", ); }); -test('strikethrough', t => { - t.throws(() => format.strikethrough('1337'), { - message: 'strikethrough is not supported by Markdown. Use MarkdownV2 instead.', - }); +Deno.test("strikethrough", () => { + assertThrows(() => format.strikethrough("1337")); }); -test('underline', t => { - t.throws(() => format.underline('1337'), { - message: 'underline is not supported by Markdown. Use MarkdownV2 instead.', - }); +Deno.test("underline", () => { + assertThrows(() => format.underline("1337")); }); -test('spoiler', t => { - t.throws(() => format.spoiler('1337'), { - message: 'spoiler is not supported by Markdown. Use MarkdownV2 instead.', - }); +Deno.test("spoiler", () => { + assertThrows(() => format.spoiler("1337")); }); diff --git a/source/markdown.ts b/source/markdown.ts index eaccff6..9155d69 100644 --- a/source/markdown.ts +++ b/source/markdown.ts @@ -1,50 +1,56 @@ -import type {Formatter} from './types.js'; +import type { Formatter } from "./types.ts"; function escape(text: string): string { - return text.replace(/[*_`[\]()]/g, ''); + return text.replace(/[*_`[\]()]/g, ""); } function bold(text: string): string { - return `*${text.replace(/\*/g, '')}*`; + return `*${text.replace(/\*/g, "")}*`; } function italic(text: string): string { - return `_${text.replace(/_/g, '')}_`; + return `_${text.replace(/_/g, "")}_`; } function strikethrough(_text: string): string { - throw new Error('strikethrough is not supported by Markdown. Use MarkdownV2 instead.'); + throw new Error( + "strikethrough is not supported by Markdown. Use MarkdownV2 instead.", + ); } function underline(_text: string): string { - throw new Error('underline is not supported by Markdown. Use MarkdownV2 instead.'); + throw new Error( + "underline is not supported by Markdown. Use MarkdownV2 instead.", + ); } function spoiler(_text: string): string { - throw new Error('spoiler is not supported by Markdown. Use MarkdownV2 instead.'); + throw new Error( + "spoiler is not supported by Markdown. Use MarkdownV2 instead.", + ); } function monospace(text: string): string { - return '`' + text.replace(/`/g, '') + '`'; + return "`" + text.replace(/`/g, "") + "`"; } function monospaceBlock(text: string, programmingLanguage?: string): string { - let result = ''; - result += '```'; + let result = ""; + result += "```"; if (programmingLanguage) { result += programmingLanguage; } - result += '\n'; - result += text.replace(/```/g, ''); - result += '\n'; - result += '```'; + result += "\n"; + result += text.replace(/```/g, ""); + result += "\n"; + result += "```"; return result; } function url(label: string, url: string): string { - return `[${label.replace(/]/, '')}](${url.replace(/\)/, '')})`; + return `[${label.replace(/]/, "")}](${url.replace(/\)/, "")})`; } function userMention(label: string, userId: number): string { @@ -53,7 +59,7 @@ function userMention(label: string, userId: number): string { /** https://core.telegram.org/bots/api#markdown-style */ export const markdown = { - parse_mode: 'Markdown', + parse_mode: "Markdown", escape, bold, italic, diff --git a/source/markdownv2.test.ts b/source/markdownv2.test.ts index 02a362a..c83cb9d 100644 --- a/source/markdownv2.test.ts +++ b/source/markdownv2.test.ts @@ -1,69 +1,72 @@ -import test from 'ava'; -import {markdownv2 as format} from './markdownv2.js'; +import { assertEquals } from "https://deno.land/std@0.206.0/assert/mod.ts"; +import { markdownv2 as format } from "./markdownv2.ts"; -test('bold', t => { - t.is(format.bold('bold'), '*bold*'); +Deno.test("bold", () => { + assertEquals(format.bold("bold"), "*bold*"); }); -test('italic', t => { - t.is(format.italic('italic'), '_italic_'); +Deno.test("italic", () => { + assertEquals(format.italic("italic"), "_italic_"); }); -test('strikethrough', t => { - t.is(format.strikethrough('strikethrough'), '~strikethrough~'); +Deno.test("strikethrough", () => { + assertEquals(format.strikethrough("strikethrough"), "~strikethrough~"); }); -test('underline', t => { - t.is(format.underline('underline'), '__underline__'); +Deno.test("underline", () => { + assertEquals(format.underline("underline"), "__underline__"); }); -test('spoiler', t => { - t.is(format.spoiler('spoiler'), '||spoiler||'); +Deno.test("spoiler", () => { + assertEquals(format.spoiler("spoiler"), "||spoiler||"); }); -test('url', t => { - t.is(format.url('me', 'https://edjopato.de'), '[me](https://edjopato.de)'); +Deno.test("url", () => { + assertEquals( + format.url("me", "https://edjopato.de"), + "[me](https://edjopato.de)", + ); }); -test('escape', t => { - t.is(format.escape('[h_]e(*y\\)`'), '\\[h\\_\\]e\\(\\*y\\\\\\)\\`'); +Deno.test("escape", () => { + assertEquals(format.escape("[h_]e(*y\\)`"), "\\[h\\_\\]e\\(\\*y\\\\\\)\\`"); }); -test('escape with number', t => { - t.is(format.escape('h1e2y'), 'h1e2y'); +Deno.test("escape with number", () => { + assertEquals(format.escape("h1e2y"), "h1e2y"); }); -test('bold italic', t => { - t.is(format.bold(format.italic('green')), '*_green_*'); +Deno.test("bold italic", () => { + assertEquals(format.bold(format.italic("green")), "*_green_*"); }); -test('user mention', t => { - t.is( - format.userMention('inline mention of a user', 123_456_789), - '[inline mention of a user](tg://user?id=123456789)', +Deno.test("user mention", () => { + assertEquals( + format.userMention("inline mention of a user", 123_456_789), + "[inline mention of a user](tg://user?id=123456789)", ); }); -test('monospace', t => { - t.is( - format.monospace('inline fixed-width code'), - '`inline fixed-width code`', +Deno.test("monospace", () => { + assertEquals( + format.monospace("inline fixed-width code"), + "`inline fixed-width code`", ); }); -test('monospaceBlock w/o language', t => { - t.is( - format.monospaceBlock('pre-formatted fixed-width code block'), - '```\npre-formatted fixed-width code block\n```', +Deno.test("monospaceBlock w/o language", () => { + assertEquals( + format.monospaceBlock("pre-formatted fixed-width code block"), + "```\npre-formatted fixed-width code block\n```", ); }); -test('monospaceBlock w/ language', t => { - t.is( +Deno.test("monospaceBlock w/ language", () => { + assertEquals( format.monospaceBlock( - 'pre-formatted fixed-width code block written in the Python programming language', - 'python', + "pre-formatted fixed-width code block written in the Python programming language", + "python", ), - '```python\npre-formatted fixed-width code block written in the Python programming language\n```', + "```python\npre-formatted fixed-width code block written in the Python programming language\n```", ); }); diff --git a/source/markdownv2.ts b/source/markdownv2.ts index e8afe72..5518660 100644 --- a/source/markdownv2.ts +++ b/source/markdownv2.ts @@ -1,11 +1,11 @@ -import type {Formatter} from './types.js'; +import type { Formatter } from "./types.ts"; function escapeInternal(text: string, escapeChars: string): string { - return text.replace(new RegExp(`[${escapeChars}\\\\]`, 'g'), '\\$&'); + return text.replace(new RegExp(`[${escapeChars}\\\\]`, "g"), "\\$&"); } function escape(text: string): string { - return text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, '\\$&'); + return text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, "\\$&"); } function bold(text: string): string { @@ -29,26 +29,26 @@ function spoiler(text: string): string { } function monospace(text: string): string { - return '`' + escapeInternal(text, '`') + '`'; + return "`" + escapeInternal(text, "`") + "`"; } function monospaceBlock(text: string, programmingLanguage?: string): string { - let result = ''; - result += '```'; + let result = ""; + result += "```"; if (programmingLanguage) { result += programmingLanguage; } - result += '\n'; - result += escapeInternal(text, '`'); - result += '\n'; - result += '```'; + result += "\n"; + result += escapeInternal(text, "`"); + result += "\n"; + result += "```"; return result; } function url(label: string, url: string): string { - return `[${label}](${escapeInternal(url, ')')})`; + return `[${label}](${escapeInternal(url, ")")})`; } function userMention(label: string, userId: number): string { @@ -57,7 +57,7 @@ function userMention(label: string, userId: number): string { /** https://core.telegram.org/bots/api#markdownv2-style */ export const markdownv2 = { - parse_mode: 'MarkdownV2', + parse_mode: "MarkdownV2", escape, bold, italic, diff --git a/source/mod.ts b/source/mod.ts new file mode 100644 index 0000000..a35b014 --- /dev/null +++ b/source/mod.ts @@ -0,0 +1,5 @@ +export type { Formatter } from "./types.ts"; + +export { html } from "./html.ts"; +export { markdown } from "./markdown.ts"; +export { markdownv2 } from "./markdownv2.ts"; diff --git a/source/types.ts b/source/types.ts index e14603f..7db82b5 100644 --- a/source/types.ts +++ b/source/types.ts @@ -1,5 +1,5 @@ export type Formatter = { - parse_mode: 'HTML' | 'Markdown' | 'MarkdownV2'; + parse_mode: "HTML" | "Markdown" | "MarkdownV2"; bold: (text: string) => string; escape: (text: string) => string; italic: (text: string) => string; diff --git a/tsconfig.json b/tsconfig.json index 431fb68..9ab5ac9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,14 @@ { - "extends": "@sindresorhus/tsconfig/tsconfig.json", "include": [ "source" ], + "exclude": [ + "**/*.test.*", + "**/test.*" + ], "compilerOptions": { - "target": "ES2020", - "lib": ["ES2020"], + "declaration": true, + "target": "ES2020", // Node.js 14 "outDir": "dist" } } From 59daa2859db7a02feee22a5a75351cde213d70a8 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Tue, 14 Nov 2023 19:13:05 +0100 Subject: [PATCH 02/10] fixup! build: use deno and deno2node --- .github/workflows/deno.yml | 3 +-- .gitignore | 1 + .vscode/settings.json | 3 --- deno.jsonc | 28 ++++++++++++++++++++++++++++ package.json | 7 +++++-- source/README.md | 4 ++++ source/deps.test.ts | 2 ++ source/html.test.ts | 2 +- source/markdown.test.ts | 5 +---- source/markdownv2.test.ts | 2 +- 10 files changed, 44 insertions(+), 13 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 source/README.md create mode 100644 source/deps.test.ts diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml index d805f91..7eee22f 100644 --- a/.github/workflows/deno.yml +++ b/.github/workflows/deno.yml @@ -32,9 +32,8 @@ jobs: - run: deno test npm: - name: Publish on NPM + name: Node.js Package runs-on: ubuntu-latest - needs: [denofmt-and-lint, test] permissions: contents: write id-token: write diff --git a/.gitignore b/.gitignore index 7866d79..10eed09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode /*-*.*.*.tgz dist node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 43df84f..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "deno.enable": true -} diff --git a/deno.jsonc b/deno.jsonc index de48e40..a9ca861 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,8 +1,36 @@ { + // https://deno.land/manual/getting_started/configuration_file "lock": false, + // https://deno.land/manual/advanced/typescript/configuration + // https://www.typescriptlang.org/tsconfig/ + "compilerOptions": { + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useUnknownInCatchVariables": true + }, "fmt": { "useTabs": true }, + "lint": { + "rules": { + // https://lint.deno.land/?all=on + "include": [ + "default-param-last", + "eqeqeq", + "explicit-module-boundary-types", + "no-await-in-loop", + "no-eval", + "no-non-null-asserted-optional-chain", + "no-non-null-assertion", + "no-sparse-arrays" + ] + } + }, "exclude": [ "./dist/", "./node_modules/" diff --git a/package.json b/package.json index f2ca2c7..9ecd99d 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,13 @@ "markdownv2" ], "license": "MIT", - "repository": "EdJoPaTo/telegram-format", + "repository": { + "type": "git", + "url": "https://github.com/EdJoPaTo/telegram-format.git" + }, "author": { "name": "EdJoPaTo", - "email": "telegram-format-npm-package@edjopato.de", + "email": "telegram-format-typescript-package@edjopato.de", "url": "https://edjopato.de" }, "scripts": { diff --git a/source/README.md b/source/README.md new file mode 100644 index 0000000..fea2b99 --- /dev/null +++ b/source/README.md @@ -0,0 +1,4 @@ +# telegram_format + +See the [README on GitHub](https://github.com/EdJoPaTo/telegram-format#readme) +for more information on this package. diff --git a/source/deps.test.ts b/source/deps.test.ts new file mode 100644 index 0000000..dfc8c9a --- /dev/null +++ b/source/deps.test.ts @@ -0,0 +1,2 @@ +export { assertEquals } from "https://deno.land/std@0.206.0/assert/assert_equals.ts"; +export { assertThrows } from "https://deno.land/std@0.206.0/assert/assert_throws.ts"; diff --git a/source/html.test.ts b/source/html.test.ts index 9261260..8104f55 100644 --- a/source/html.test.ts +++ b/source/html.test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.206.0/assert/mod.ts"; +import { assertEquals } from "./deps.test.ts"; import { html as format } from "./html.ts"; Deno.test("bold", () => { diff --git a/source/markdown.test.ts b/source/markdown.test.ts index 1ea26d6..d7e7344 100644 --- a/source/markdown.test.ts +++ b/source/markdown.test.ts @@ -1,7 +1,4 @@ -import { - assertEquals, - assertThrows, -} from "https://deno.land/std@0.206.0/assert/mod.ts"; +import { assertEquals, assertThrows } from "./deps.test.ts"; import { markdown as format } from "./markdown.ts"; Deno.test("bold", () => { diff --git a/source/markdownv2.test.ts b/source/markdownv2.test.ts index c83cb9d..dec4f10 100644 --- a/source/markdownv2.test.ts +++ b/source/markdownv2.test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.206.0/assert/mod.ts"; +import { assertEquals } from "./deps.test.ts"; import { markdownv2 as format } from "./markdownv2.ts"; Deno.test("bold", () => { From 5da8f2da8f13556176b7147c5c574416e67c126e Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Tue, 21 Nov 2023 18:59:25 +0100 Subject: [PATCH 03/10] build(deno): sort keys containing bigger values --- deno.jsonc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deno.jsonc b/deno.jsonc index a9ca861..e2aa1d7 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -13,6 +13,10 @@ "noUnusedParameters": true, "useUnknownInCatchVariables": true }, + "exclude": [ + "./dist/", + "./node_modules/" + ], "fmt": { "useTabs": true }, @@ -30,9 +34,5 @@ "no-sparse-arrays" ] } - }, - "exclude": [ - "./dist/", - "./node_modules/" - ] + } } From 8763860f174909f4ec6d3925c50d294974e9c224 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Wed, 22 Nov 2023 15:01:49 +0100 Subject: [PATCH 04/10] ci(deno): deno2node doesnt require deno installed --- .github/workflows/deno.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml index 7eee22f..4796a47 100644 --- a/.github/workflows/deno.yml +++ b/.github/workflows/deno.yml @@ -39,9 +39,6 @@ jobs: id-token: write steps: - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - uses: actions/setup-node@v4 with: node-version: 'lts/*' From 629c3b8aa420c1a1e28dcfffab4f4da179aad595 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Thu, 23 Nov 2023 00:54:45 +0100 Subject: [PATCH 05/10] test: use node:test --- .github/workflows/deno.yml | 5 ++-- package.json | 10 +++++-- source/deps.test.ts | 2 -- source/html.test.ts | 51 ++++++++++++++++++----------------- source/markdown.test.ts | 55 +++++++++++++++++++------------------- source/markdownv2.test.ts | 55 +++++++++++++++++++------------------- tsconfig.json | 4 --- 7 files changed, 93 insertions(+), 89 deletions(-) delete mode 100644 source/deps.test.ts diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml index 4796a47..9902d42 100644 --- a/.github/workflows/deno.yml +++ b/.github/workflows/deno.yml @@ -31,8 +31,8 @@ jobs: - run: deno check source/*.ts - run: deno test - npm: - name: Node.js Package + nodejs: + name: Node.js runs-on: ubuntu-latest permissions: contents: write @@ -44,6 +44,7 @@ jobs: node-version: 'lts/*' registry-url: 'https://registry.npmjs.org' - run: npm install + - run: npm test - run: npm pack - run: npm publish if: startsWith(github.ref, 'refs/tags/v') diff --git a/package.json b/package.json index 9ecd99d..224f7aa 100644 --- a/package.json +++ b/package.json @@ -21,16 +21,22 @@ }, "scripts": { "prepare": "deno2node", - "test": "echo use deno test && exit 1" + "pretest": "deno2node", + "test": "node --test" }, "type": "module", "engines": { "node": ">=14" }, "devDependencies": { + "@types/node": "^16.18.64", "deno2node": "1.10.1" }, - "files": ["dist"], + "files": [ + "dist", + "!**/*.test.*", + "!**/test.*" + ], "main": "./dist/mod.js", "types": "./dist/mod.d.ts" } diff --git a/source/deps.test.ts b/source/deps.test.ts deleted file mode 100644 index dfc8c9a..0000000 --- a/source/deps.test.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { assertEquals } from "https://deno.land/std@0.206.0/assert/assert_equals.ts"; -export { assertThrows } from "https://deno.land/std@0.206.0/assert/assert_throws.ts"; diff --git a/source/html.test.ts b/source/html.test.ts index 8104f55..063d796 100644 --- a/source/html.test.ts +++ b/source/html.test.ts @@ -1,67 +1,68 @@ -import { assertEquals } from "./deps.test.ts"; +import { strictEqual } from "node:assert"; +import { test } from "node:test"; import { html as format } from "./html.ts"; -Deno.test("bold", () => { - assertEquals(format.bold("bold"), "bold"); +test("bold", () => { + strictEqual(format.bold("bold"), "bold"); }); -Deno.test("italic", () => { - assertEquals(format.italic("italic"), "italic"); +test("italic", () => { + strictEqual(format.italic("italic"), "italic"); }); -Deno.test("strikethrough", () => { - assertEquals(format.strikethrough("strikethrough"), "strikethrough"); +test("strikethrough", () => { + strictEqual(format.strikethrough("strikethrough"), "strikethrough"); }); -Deno.test("underline", () => { - assertEquals(format.underline("underline"), "underline"); +test("underline", () => { + strictEqual(format.underline("underline"), "underline"); }); -Deno.test("spoiler", () => { - assertEquals( +test("spoiler", () => { + strictEqual( format.spoiler("spoiler"), 'spoiler', ); }); -Deno.test("url", () => { - assertEquals( +test("url", () => { + strictEqual( format.url("me", "https://edjopato.de"), 'me', ); }); -Deno.test("escape", () => { - assertEquals(format.escape("hy"), "h<e>y"); +test("escape", () => { + strictEqual(format.escape("hy"), "h<e>y"); }); -Deno.test("bold italic", () => { - assertEquals(format.bold(format.italic("green")), "green"); +test("bold italic", () => { + strictEqual(format.bold(format.italic("green")), "green"); }); -Deno.test("user mention", () => { - assertEquals( +test("user mention", () => { + strictEqual( format.userMention("inline mention of a user", 123_456_789), 'inline mention of a user', ); }); -Deno.test("monospace", () => { - assertEquals( +test("monospace", () => { + strictEqual( format.monospace("inline fixed-width code"), "inline fixed-width code", ); }); -Deno.test("monospaceBlock w/o language", () => { - assertEquals( +test("monospaceBlock w/o language", () => { + strictEqual( format.monospaceBlock("pre-formatted fixed-width code block"), "
pre-formatted fixed-width code block
", ); }); -Deno.test("monospaceBlock w/ language", () => { - assertEquals( +test("monospaceBlock w/ language", () => { + strictEqual( format.monospaceBlock( "pre-formatted fixed-width code block written in the Python programming language", "python", diff --git a/source/markdown.test.ts b/source/markdown.test.ts index d7e7344..0ff4c8a 100644 --- a/source/markdown.test.ts +++ b/source/markdown.test.ts @@ -1,56 +1,57 @@ -import { assertEquals, assertThrows } from "./deps.test.ts"; +import { strictEqual, throws } from "node:assert"; +import { test } from "node:test"; import { markdown as format } from "./markdown.ts"; -Deno.test("bold", () => { - assertEquals(format.bold("bold"), "*bold*"); +test("bold", () => { + strictEqual(format.bold("bold"), "*bold*"); }); -Deno.test("italic", () => { - assertEquals(format.italic("italic"), "_italic_"); +test("italic", () => { + strictEqual(format.italic("italic"), "_italic_"); }); -Deno.test("url", () => { - assertEquals( +test("url", () => { + strictEqual( format.url("me", "https://edjopato.de"), "[me](https://edjopato.de)", ); }); -Deno.test("escape", () => { - assertEquals(format.escape("[h_]e(*y)`"), "hey"); +test("escape", () => { + strictEqual(format.escape("[h_]e(*y)`"), "hey"); }); -Deno.test("bold malicious", () => { - assertEquals(format.bold("bo*ld"), "*bold*"); +test("bold malicious", () => { + strictEqual(format.bold("bo*ld"), "*bold*"); }); -Deno.test("italic malicious", () => { - assertEquals(format.italic("ita_lic"), "_italic_"); +test("italic malicious", () => { + strictEqual(format.italic("ita_lic"), "_italic_"); }); -Deno.test("user mention", () => { - assertEquals( +test("user mention", () => { + strictEqual( format.userMention("inline mention of a user", 123_456_789), "[inline mention of a user](tg://user?id=123456789)", ); }); -Deno.test("monospace", () => { - assertEquals( +test("monospace", () => { + strictEqual( format.monospace("inline fixed-width code"), "`inline fixed-width code`", ); }); -Deno.test("monospaceBlock w/o language", () => { - assertEquals( +test("monospaceBlock w/o language", () => { + strictEqual( format.monospaceBlock("pre-formatted fixed-width code block"), "```\npre-formatted fixed-width code block\n```", ); }); -Deno.test("monospaceBlock w/ language", () => { - assertEquals( +test("monospaceBlock w/ language", () => { + strictEqual( format.monospaceBlock( "pre-formatted fixed-width code block written in the Python programming language", "python", @@ -59,14 +60,14 @@ Deno.test("monospaceBlock w/ language", () => { ); }); -Deno.test("strikethrough", () => { - assertThrows(() => format.strikethrough("1337")); +test("strikethrough", () => { + throws(() => format.strikethrough("1337")); }); -Deno.test("underline", () => { - assertThrows(() => format.underline("1337")); +test("underline", () => { + throws(() => format.underline("1337")); }); -Deno.test("spoiler", () => { - assertThrows(() => format.spoiler("1337")); +test("spoiler", () => { + throws(() => format.spoiler("1337")); }); diff --git a/source/markdownv2.test.ts b/source/markdownv2.test.ts index dec4f10..8138f89 100644 --- a/source/markdownv2.test.ts +++ b/source/markdownv2.test.ts @@ -1,68 +1,69 @@ -import { assertEquals } from "./deps.test.ts"; +import { strictEqual } from "node:assert"; +import { test } from "node:test"; import { markdownv2 as format } from "./markdownv2.ts"; -Deno.test("bold", () => { - assertEquals(format.bold("bold"), "*bold*"); +test("bold", () => { + strictEqual(format.bold("bold"), "*bold*"); }); -Deno.test("italic", () => { - assertEquals(format.italic("italic"), "_italic_"); +test("italic", () => { + strictEqual(format.italic("italic"), "_italic_"); }); -Deno.test("strikethrough", () => { - assertEquals(format.strikethrough("strikethrough"), "~strikethrough~"); +test("strikethrough", () => { + strictEqual(format.strikethrough("strikethrough"), "~strikethrough~"); }); -Deno.test("underline", () => { - assertEquals(format.underline("underline"), "__underline__"); +test("underline", () => { + strictEqual(format.underline("underline"), "__underline__"); }); -Deno.test("spoiler", () => { - assertEquals(format.spoiler("spoiler"), "||spoiler||"); +test("spoiler", () => { + strictEqual(format.spoiler("spoiler"), "||spoiler||"); }); -Deno.test("url", () => { - assertEquals( +test("url", () => { + strictEqual( format.url("me", "https://edjopato.de"), "[me](https://edjopato.de)", ); }); -Deno.test("escape", () => { - assertEquals(format.escape("[h_]e(*y\\)`"), "\\[h\\_\\]e\\(\\*y\\\\\\)\\`"); +test("escape", () => { + strictEqual(format.escape("[h_]e(*y\\)`"), "\\[h\\_\\]e\\(\\*y\\\\\\)\\`"); }); -Deno.test("escape with number", () => { - assertEquals(format.escape("h1e2y"), "h1e2y"); +test("escape with number", () => { + strictEqual(format.escape("h1e2y"), "h1e2y"); }); -Deno.test("bold italic", () => { - assertEquals(format.bold(format.italic("green")), "*_green_*"); +test("bold italic", () => { + strictEqual(format.bold(format.italic("green")), "*_green_*"); }); -Deno.test("user mention", () => { - assertEquals( +test("user mention", () => { + strictEqual( format.userMention("inline mention of a user", 123_456_789), "[inline mention of a user](tg://user?id=123456789)", ); }); -Deno.test("monospace", () => { - assertEquals( +test("monospace", () => { + strictEqual( format.monospace("inline fixed-width code"), "`inline fixed-width code`", ); }); -Deno.test("monospaceBlock w/o language", () => { - assertEquals( +test("monospaceBlock w/o language", () => { + strictEqual( format.monospaceBlock("pre-formatted fixed-width code block"), "```\npre-formatted fixed-width code block\n```", ); }); -Deno.test("monospaceBlock w/ language", () => { - assertEquals( +test("monospaceBlock w/ language", () => { + strictEqual( format.monospaceBlock( "pre-formatted fixed-width code block written in the Python programming language", "python", diff --git a/tsconfig.json b/tsconfig.json index 9ab5ac9..69bb64e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,10 +2,6 @@ "include": [ "source" ], - "exclude": [ - "**/*.test.*", - "**/test.*" - ], "compilerOptions": { "declaration": true, "target": "ES2020", // Node.js 14 From a3075b1aebb12b671194888aa37b5cad0f513604 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Thu, 23 Nov 2023 01:44:45 +0100 Subject: [PATCH 06/10] refactor: improve documentation --- .gitignore | 1 + README.md | 94 --------------------------------------- deno.jsonc | 4 ++ source/README.md | 2 +- source/html.test.ts | 5 ++- source/html.ts | 51 +++++++++++---------- source/markdown.test.ts | 5 ++- source/markdown.ts | 51 +++++++++++---------- source/markdownv2.test.ts | 5 ++- source/markdownv2.ts | 51 +++++++++++---------- source/mod.ts | 6 +-- source/types.ts | 16 ++++++- 12 files changed, 110 insertions(+), 181 deletions(-) diff --git a/.gitignore b/.gitignore index 10eed09..f6aea18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode /*-*.*.*.tgz dist +docs node_modules diff --git a/README.md b/README.md index 27599ad..5cc3b29 100644 --- a/README.md +++ b/README.md @@ -69,97 +69,3 @@ reasons but it behaves a bit differently. `Markdown` still escapes inputs and does not need `format.escape` before. Also nested formatting is not supported by telegram itself. Consider switching to `MarkdownV2` or `HTML` for simplicity reasons. - -## API - -### bold(text) - -#### text - -Type: `string` - -Text to be formatted bold - -### italic(text) - -#### text - -Type: `string` - -Text to be formatted italic - -### strikethrough(text) - -#### text - -Type: `string` - -Text to be striked through - -### underline(text) - -#### text - -Type: `string` - -Text to be underlined - -### monospace(text) - -#### text - -Type: `string` - -Text to be displayed as inline monospace - -### monospaceBlock(text, [programmingLanguage]) - -#### text - -Type: `string` - -Text to be displayed as monospace block - -#### programmingLanguage - -Type: `string` (optional) - -Can be used to specify the programming language of the code - -### url(label, url) - -#### label - -Type: `string` - -Label of the url field - -#### url - -Type: `string` - -Url which is called when the user taps on the label - -### userMention(label, userId) - -#### label - -Type: `string` - -Label of the user mention. Might be used with the first name for example. - -#### userID - -Type: `number` - -User id to which is linked - -### escape(text) - -#### text - -Type: `string` - -Text which should be escaped so it does not interfer with formatting. - -User generated Text should always be escapted to prevent errors. diff --git a/deno.jsonc b/deno.jsonc index e2aa1d7..871028d 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -15,6 +15,7 @@ }, "exclude": [ "./dist/", + "./docs/", "./node_modules/" ], "fmt": { @@ -34,5 +35,8 @@ "no-sparse-arrays" ] } + }, + "tasks": { + "doc": "deno doc --html --name=telegram_format source/mod.ts" } } diff --git a/source/README.md b/source/README.md index fea2b99..5382501 100644 --- a/source/README.md +++ b/source/README.md @@ -1,4 +1,4 @@ # telegram_format See the [README on GitHub](https://github.com/EdJoPaTo/telegram-format#readme) -for more information on this package. +or the [documentation on deno.land](https://deno.land/x/telegram-format?doc). diff --git a/source/html.test.ts b/source/html.test.ts index 063d796..f910122 100644 --- a/source/html.test.ts +++ b/source/html.test.ts @@ -1,6 +1,9 @@ import { strictEqual } from "node:assert"; import { test } from "node:test"; -import { html as format } from "./html.ts"; +import * as format from "./html.ts"; +import { Formatter } from "./types.ts"; + +format satisfies Formatter; test("bold", () => { strictEqual(format.bold("bold"), "bold"); diff --git a/source/html.ts b/source/html.ts index 4206f36..45e05ad 100644 --- a/source/html.ts +++ b/source/html.ts @@ -1,37 +1,49 @@ -import type { Formatter } from "./types.ts"; +/** https://core.telegram.org/bots/api#html-style */ +export const parse_mode = "HTML"; -function escape(text: string): string { +/** Escape the input text to be displayed as it is */ +export function escape(text: string): string { return text .replace(/&/g, "&") .replace(//g, ">"); } -function bold(text: string): string { +/** Format the input text bold */ +export function bold(text: string): string { return `${text}`; } -function italic(text: string): string { +/** Format the input text italic */ +export function italic(text: string): string { return `${text}`; } -function strikethrough(text: string): string { +/** Strikethrough the input text */ +export function strikethrough(text: string): string { return `${text}`; } -function underline(text: string): string { +/** Underline the input text */ +export function underline(text: string): string { return `${text}`; } -function spoiler(text: string): string { +/** Format the input text as spoiler */ +export function spoiler(text: string): string { return `${text}`; } -function monospace(text: string): string { +/** Format the input text as monospace */ +export function monospace(text: string): string { return `${escape(text)}`; } -function monospaceBlock(text: string, programmingLanguage?: string): string { +/** Format the input text as a monospace block optionally with a programming language */ +export function monospaceBlock( + text: string, + programmingLanguage?: string, +): string { if (programmingLanguage) { return `
${
 			escape(text)
@@ -41,25 +53,12 @@ function monospaceBlock(text: string, programmingLanguage?: string): string {
 	return `
${escape(text)}
`; } -function url(label: string, url: string): string { +/** Create an url with a label text */ +export function url(label: string, url: string): string { return `${label}`; } -function userMention(label: string, userId: number): string { +/** Create a user mention with a label text */ +export function userMention(label: string, userId: number): string { return url(label, `tg://user?id=${userId}`); } - -/** https://core.telegram.org/bots/api#html-style */ -export const html = { - parse_mode: "HTML", - escape, - bold, - italic, - strikethrough, - underline, - spoiler, - monospace, - monospaceBlock, - url, - userMention, -} as const satisfies Formatter; diff --git a/source/markdown.test.ts b/source/markdown.test.ts index 0ff4c8a..408e282 100644 --- a/source/markdown.test.ts +++ b/source/markdown.test.ts @@ -1,6 +1,9 @@ import { strictEqual, throws } from "node:assert"; import { test } from "node:test"; -import { markdown as format } from "./markdown.ts"; +import * as format from "./markdown.ts"; +import { Formatter } from "./types.ts"; + +format satisfies Formatter; test("bold", () => { strictEqual(format.bold("bold"), "*bold*"); diff --git a/source/markdown.ts b/source/markdown.ts index 9155d69..99a710a 100644 --- a/source/markdown.ts +++ b/source/markdown.ts @@ -1,40 +1,52 @@ -import type { Formatter } from "./types.ts"; +/** https://core.telegram.org/bots/api#markdown-style */ +export const parse_mode = "Markdown"; -function escape(text: string): string { +/** Escape the input text to be displayed as it is */ +export function escape(text: string): string { return text.replace(/[*_`[\]()]/g, ""); } -function bold(text: string): string { +/** Format the input text bold */ +export function bold(text: string): string { return `*${text.replace(/\*/g, "")}*`; } -function italic(text: string): string { +/** Format the input text italic */ +export function italic(text: string): string { return `_${text.replace(/_/g, "")}_`; } -function strikethrough(_text: string): string { +/** @deprecated unsupported by Telegram. Use MarkdownV2 or HTML instead */ +export function strikethrough(_text: string): string { throw new Error( "strikethrough is not supported by Markdown. Use MarkdownV2 instead.", ); } -function underline(_text: string): string { +/** @deprecated unsupported by Telegram. Use MarkdownV2 or HTML instead */ +export function underline(_text: string): string { throw new Error( "underline is not supported by Markdown. Use MarkdownV2 instead.", ); } -function spoiler(_text: string): string { +/** @deprecated unsupported by Telegram. Use MarkdownV2 or HTML instead */ +export function spoiler(_text: string): string { throw new Error( "spoiler is not supported by Markdown. Use MarkdownV2 instead.", ); } -function monospace(text: string): string { +/** Format the input text as monospace */ +export function monospace(text: string): string { return "`" + text.replace(/`/g, "") + "`"; } -function monospaceBlock(text: string, programmingLanguage?: string): string { +/** Format the input text as a monospace block optionally with a programming language */ +export function monospaceBlock( + text: string, + programmingLanguage?: string, +): string { let result = ""; result += "```"; @@ -49,25 +61,12 @@ function monospaceBlock(text: string, programmingLanguage?: string): string { return result; } -function url(label: string, url: string): string { +/** Create an url with a label text */ +export function url(label: string, url: string): string { return `[${label.replace(/]/, "")}](${url.replace(/\)/, "")})`; } -function userMention(label: string, userId: number): string { +/** Create a user mention with a label text */ +export function userMention(label: string, userId: number): string { return url(label, `tg://user?id=${userId}`); } - -/** https://core.telegram.org/bots/api#markdown-style */ -export const markdown = { - parse_mode: "Markdown", - escape, - bold, - italic, - strikethrough, - underline, - spoiler, - monospace, - monospaceBlock, - url, - userMention, -} as const satisfies Formatter; diff --git a/source/markdownv2.test.ts b/source/markdownv2.test.ts index 8138f89..19264fb 100644 --- a/source/markdownv2.test.ts +++ b/source/markdownv2.test.ts @@ -1,6 +1,9 @@ import { strictEqual } from "node:assert"; import { test } from "node:test"; -import { markdownv2 as format } from "./markdownv2.ts"; +import * as format from "./markdownv2.ts"; +import { Formatter } from "./types.ts"; + +format satisfies Formatter; test("bold", () => { strictEqual(format.bold("bold"), "*bold*"); diff --git a/source/markdownv2.ts b/source/markdownv2.ts index 5518660..8a4d34c 100644 --- a/source/markdownv2.ts +++ b/source/markdownv2.ts @@ -1,38 +1,50 @@ -import type { Formatter } from "./types.ts"; +/** https://core.telegram.org/bots/api#markdownv2-style */ +export const parse_mode = "MarkdownV2"; function escapeInternal(text: string, escapeChars: string): string { return text.replace(new RegExp(`[${escapeChars}\\\\]`, "g"), "\\$&"); } -function escape(text: string): string { +/** Escape the input text to be displayed as it is */ +export function escape(text: string): string { return text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, "\\$&"); } -function bold(text: string): string { +/** Format the input text bold */ +export function bold(text: string): string { return `*${text}*`; } -function italic(text: string): string { +/** Format the input text italic */ +export function italic(text: string): string { return `_${text}_`; } -function strikethrough(text: string): string { +/** Strikethrough the input text */ +export function strikethrough(text: string): string { return `~${text}~`; } -function underline(text: string): string { +/** Underline the input text */ +export function underline(text: string): string { return `__${text}__`; } -function spoiler(text: string): string { +/** Format the input text as spoiler */ +export function spoiler(text: string): string { return `||${text}||`; } -function monospace(text: string): string { +/** Format the input text as monospace */ +export function monospace(text: string): string { return "`" + escapeInternal(text, "`") + "`"; } -function monospaceBlock(text: string, programmingLanguage?: string): string { +/** Format the input text as a monospace block optionally with a programming language */ +export function monospaceBlock( + text: string, + programmingLanguage?: string, +): string { let result = ""; result += "```"; @@ -47,25 +59,12 @@ function monospaceBlock(text: string, programmingLanguage?: string): string { return result; } -function url(label: string, url: string): string { +/** Create an url with a label text */ +export function url(label: string, url: string): string { return `[${label}](${escapeInternal(url, ")")})`; } -function userMention(label: string, userId: number): string { +/** Create a user mention with a label text */ +export function userMention(label: string, userId: number): string { return url(label, `tg://user?id=${userId}`); } - -/** https://core.telegram.org/bots/api#markdownv2-style */ -export const markdownv2 = { - parse_mode: "MarkdownV2", - escape, - bold, - italic, - strikethrough, - underline, - spoiler, - monospace, - monospaceBlock, - url, - userMention, -} as const satisfies Formatter; diff --git a/source/mod.ts b/source/mod.ts index a35b014..72774d6 100644 --- a/source/mod.ts +++ b/source/mod.ts @@ -1,5 +1,5 @@ export type { Formatter } from "./types.ts"; -export { html } from "./html.ts"; -export { markdown } from "./markdown.ts"; -export { markdownv2 } from "./markdownv2.ts"; +export * as html from "./html.ts"; +export * as markdown from "./markdown.ts"; +export * as markdownv2 from "./markdownv2.ts"; diff --git a/source/types.ts b/source/types.ts index 7db82b5..51c97a8 100644 --- a/source/types.ts +++ b/source/types.ts @@ -1,13 +1,25 @@ -export type Formatter = { +/** All exported formatters satisfy this interface */ +export interface Formatter { + /** parse_mode which can be used on sendMessage */ parse_mode: "HTML" | "Markdown" | "MarkdownV2"; + /** Format the input text bold */ bold: (text: string) => string; + /** Escape the input text to be displayed as it is */ escape: (text: string) => string; + /** Format the input text italic */ italic: (text: string) => string; + /** Format the input text as monospace */ monospace: (text: string) => string; + /** Format the input text as a monospace block optionally with a programming language */ monospaceBlock: (text: string, programmingLanguage?: string) => string; + /** Format the input text as spoiler */ spoiler: (text: string) => string; + /** Strikethrough the input text */ strikethrough: (text: string) => string; + /** Underline the input text */ underline: (text: string) => string; + /** Create an url with a label text */ url: (label: string, url: string) => string; + /** Create a user mention with a label text */ userMention: (label: string, userId: number) => string; -}; +} From dd9538f9010db086b6f1e1e16776f7aacd49a8ea Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Thu, 23 Nov 2023 02:38:59 +0100 Subject: [PATCH 07/10] ci: split deno and nodejs --- .github/workflows/deno.yml | 27 +--------------------- .github/workflows/nodejs.yml | 44 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/nodejs.yml diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml index 9902d42..7664cab 100644 --- a/.github/workflows/deno.yml +++ b/.github/workflows/deno.yml @@ -4,7 +4,7 @@ on: push: pull_request: schedule: - # Check if it works with current versions + # Check if it works with current deno version - cron: '42 2 * * 6' # weekly on Saturday 2:42 UTC jobs: @@ -30,28 +30,3 @@ jobs: - run: deno cache source/*.ts - run: deno check source/*.ts - run: deno test - - nodejs: - name: Node.js - runs-on: ubuntu-latest - permissions: - contents: write - id-token: write - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - registry-url: 'https://registry.npmjs.org' - - run: npm install - - run: npm test - - run: npm pack - - run: npm publish - if: startsWith(github.ref, 'refs/tags/v') - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - NPM_CONFIG_ACCESS: public - NPM_CONFIG_PROVENANCE: true - - name: Create GitHub release - if: startsWith(github.ref, 'refs/tags/v') - uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..8936899 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,44 @@ +name: Node.js + +on: + push: + pull_request: + schedule: + # Check if it works with current dependencies + - cron: '42 2 * * 6' # weekly on Saturday 2:42 UTC + +jobs: + test: + name: Node.js + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - uses: actions/checkout@v4 + - run: npm install + - run: npm test + - run: npm pack + + publish: + name: Publish on NPM + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + needs: test + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + registry-url: 'https://registry.npmjs.org' + - run: npm install + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_ACCESS: public + NPM_CONFIG_PROVENANCE: true + - name: Create GitHub release + uses: softprops/action-gh-release@v1 From ce4cc0df9f5cb4b64e4d1ffd3a81aefea7f1cbde Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Sun, 26 Nov 2023 11:27:35 +0100 Subject: [PATCH 08/10] build: add test coverage task --- .github/workflows/deno.yml | 3 ++- .gitignore | 1 + deno.jsonc | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml index 7664cab..fc0d102 100644 --- a/.github/workflows/deno.yml +++ b/.github/workflows/deno.yml @@ -29,4 +29,5 @@ jobs: - run: deno cache source/*.ts - run: deno check source/*.ts - - run: deno test + - run: deno test --coverage=coverage/ + - run: deno coverage coverage/ diff --git a/.gitignore b/.gitignore index f6aea18..8443fca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vscode /*-*.*.*.tgz +coverage dist docs node_modules diff --git a/deno.jsonc b/deno.jsonc index 871028d..0c9d637 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -14,6 +14,7 @@ "useUnknownInCatchVariables": true }, "exclude": [ + "./coverage/", "./dist/", "./docs/", "./node_modules/" @@ -37,6 +38,7 @@ } }, "tasks": { + "coverage": "deno test --coverage=coverage/ && deno coverage coverage/", "doc": "deno doc --html --name=telegram_format source/mod.ts" } } From a9ed5544cf097974097ee8ff1ea79e6c05e03c4a Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Tue, 28 Nov 2023 16:32:02 +0100 Subject: [PATCH 09/10] build(npm): simplify test file exclusion --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 224f7aa..6dd7999 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ }, "files": [ "dist", - "!**/*.test.*", - "!**/test.*" + "!*.test.*", + "!test.*" ], "main": "./dist/mod.js", "types": "./dist/mod.d.ts" From 0b9ba97fd0762ad9a85e1d3a62d0573dd41db4f6 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Tue, 28 Nov 2023 16:33:20 +0100 Subject: [PATCH 10/10] docs(readme): code blocks with ts instead of js --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5cc3b29..b6855d1 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ import {/* ... */} from "https://deno.land/x/telegram_format/mod.ts"; ## Usage -```js +```ts import { html as format } from "telegram-format"; import { markdownv2 as format } from "telegram-format"; @@ -54,7 +54,7 @@ fast. When you have something that might be unescaped you need to use `format.escape` before formatting it. -```js +```ts const username = "master_yoda"; format.italic(format.escape(username)); ```