From 0dcb26034f20a7170470b090b9614950f12617db Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Sat, 10 Aug 2024 18:15:05 +0900 Subject: [PATCH] refactor(ts): Make deep file structures Separate files by their roles, and merge tests. --- deno.json | 3 - src/libs/convert.ts | 40 --------- src/libs/io.ts | 29 ------ src/libs/mod.ts | 4 +- src/libs/toml/convert.ts | 15 ++++ src/libs/toml/convert_test.ts | 54 +++++++++++ src/libs/toml/io.ts | 15 ++++ {test/libs => src/libs/toml}/io_test.ts | 37 +++----- src/{types/feed.ts => libs/types.ts} | 7 ++ src/libs/{ => utils}/sites.ts | 13 +-- {test/libs => src/libs/utils}/sites_test.ts | 4 +- src/libs/xml/convert.ts | 22 +++++ src/libs/xml/convert_test.ts | 47 ++++++++++ src/libs/xml/io.ts | 16 ++++ src/libs/xml/io_test.ts | 30 +++++++ src/main.ts | 3 +- src/types/mod.ts | 3 - src/types/opml.ts | 6 -- src/types/sites.ts | 4 - test/libs/convert_test.ts | 99 --------------------- 20 files changed, 233 insertions(+), 218 deletions(-) delete mode 100644 src/libs/convert.ts delete mode 100644 src/libs/io.ts create mode 100644 src/libs/toml/convert.ts create mode 100644 src/libs/toml/convert_test.ts create mode 100644 src/libs/toml/io.ts rename {test/libs => src/libs/toml}/io_test.ts (58%) rename src/{types/feed.ts => libs/types.ts} (63%) rename src/libs/{ => utils}/sites.ts (80%) rename {test/libs => src/libs/utils}/sites_test.ts (91%) create mode 100644 src/libs/xml/convert.ts create mode 100644 src/libs/xml/convert_test.ts create mode 100644 src/libs/xml/io.ts create mode 100644 src/libs/xml/io_test.ts delete mode 100644 src/types/mod.ts delete mode 100644 src/types/opml.ts delete mode 100644 src/types/sites.ts delete mode 100644 test/libs/convert_test.ts diff --git a/deno.json b/deno.json index ffe1f0b..4c7dd27 100644 --- a/deno.json +++ b/deno.json @@ -1,15 +1,12 @@ { "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", "fmt": { "exclude": ["LICENSE", ".github/**/*.md"] }, - "test": { "include": ["src/", "test/"] }, "tasks": { "gen": "deno run --allow-env --allow-read --allow-write ./src/main.ts", "test": "deno test --allow-env --allow-read --allow-write --parallel --shuffle", "cov": "deno task test --coverage && deno coverage --lcov > coverage.lcov" }, "imports": { - "@5ouma/opml-generator/libs": "./src/libs/mod.ts", - "@5ouma/opml-generator/types": "./src/types/mod.ts", "@libs/xml": "jsr:@libs/xml@5.4.13", "@std/assert": "jsr:@std/assert@1.0.2", "@std/cli": "jsr:@std/cli@1.0.3", diff --git a/src/libs/convert.ts b/src/libs/convert.ts deleted file mode 100644 index 3fba2d0..0000000 --- a/src/libs/convert.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { stringify } from "@libs/xml"; -import { parse } from "@std/toml"; -import { transcodeXmlUrl } from "./sites.ts"; -import type { - Feed, - List, - Lists, - OPMLOutline, -} from "@5ouma/opml-generator/types"; - -export function convertFromTOML(data: string): Lists { - const lists: Lists = parse(data) as Lists; - lists.lists.map((list: List) => { - list.feeds.map((feed: Feed) => { - feed.xmlUrl = feed.xmlUrl - ? new URL(feed.xmlUrl) - : transcodeXmlUrl(feed.title, feed.type, feed.id); - }); - }); - return lists; -} - -export function convertToOPML(list: List): string { - const body = { - outline: list.feeds.map((feed: Feed): OPMLOutline => { - return { - "@title": feed.title, - "@text": feed.title, - "@xmlUrl": feed.xmlUrl - ? new URL(feed.xmlUrl) - : transcodeXmlUrl(feed.title, feed.type, feed.id), - "@type": "rss", - }; - }), - }; - - return ` -${stringify({ opml: { "@version": "2.0", body: [body] } })} -`; -} diff --git a/src/libs/io.ts b/src/libs/io.ts deleted file mode 100644 index 741ba34..0000000 --- a/src/libs/io.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { paramCase } from "@wok/case"; -import { format } from "@std/path"; -import { convertFromTOML, convertToOPML } from "./convert.ts"; -import type { List, Lists } from "@5ouma/opml-generator/types"; - -export async function readTOML(file: string): Promise { - try { - const data: string = await Deno.readTextFile(file); - return convertFromTOML(data); - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - throw new Error(`file not found: "${file}"`); - } else if (error instanceof Deno.errors.PermissionDenied) { - throw new Error(`permission denied: "${file}"`); - } else throw error; - } -} - -export async function writeXML(feeds: Lists, dir: string): Promise { - await Deno.mkdir(dir, { recursive: true }); - feeds.lists.map(async (list: List) => { - const file: string = format({ - dir: dir, - name: paramCase(list.name), - ext: ".xml", - }); - await Deno.writeTextFile(file, convertToOPML(list)); - }); -} diff --git a/src/libs/mod.ts b/src/libs/mod.ts index 82c43f3..59a419b 100644 --- a/src/libs/mod.ts +++ b/src/libs/mod.ts @@ -1 +1,3 @@ -export * from "./io.ts"; +export * from "./toml/io.ts"; +export * from "./xml/io.ts"; +export type { Lists } from "./types.ts"; diff --git a/src/libs/toml/convert.ts b/src/libs/toml/convert.ts new file mode 100644 index 0000000..16b0260 --- /dev/null +++ b/src/libs/toml/convert.ts @@ -0,0 +1,15 @@ +import { parse } from "@std/toml"; +import { transcodeXmlUrl } from "../utils/sites.ts"; +import type { Feed, List, Lists } from "../types.ts"; + +export function convert(data: string): Lists { + const lists: Lists = parse(data) as Lists; + lists.lists.map((list: List) => { + list.feeds.map((feed: Feed) => { + feed.xmlUrl = feed.xmlUrl + ? new URL(feed.xmlUrl) + : transcodeXmlUrl(feed.title, feed.type, feed.id); + }); + }); + return lists; +} diff --git a/src/libs/toml/convert_test.ts b/src/libs/toml/convert_test.ts new file mode 100644 index 0000000..0568ef5 --- /dev/null +++ b/src/libs/toml/convert_test.ts @@ -0,0 +1,54 @@ +import { assertEquals } from "@std/assert"; +import { convert } from "./convert.ts"; +import type { Lists } from "../types.ts"; + +Deno.test("Parse TOML", async (t: Deno.TestContext) => { + await t.step("rss", () => { + const toml = ` +[[lists]] +name = "list name" + +[[lists.feeds]] +title = "feed title" +xmlUrl = "https://example.com/feed" +`; + + const feeds: Lists = { + lists: [{ + name: "list name", + feeds: [{ + title: "feed title", + xmlUrl: new URL("https://example.com/feed"), + }], + }], + }; + + assertEquals(convert(toml), feeds); + }); + + await t.step("site", () => { + const toml = ` +[[lists]] +name = "list name" + +[[lists.feeds]] +title = "feed title" +type = "bluesky" +id = "username" +`; + + const feeds: Lists = { + lists: [{ + name: "list name", + feeds: [{ + title: "feed title", + type: "bluesky", + id: "username", + xmlUrl: new URL("https://bsky.app/profile/username/rss"), + }], + }], + }; + + assertEquals(convert(toml), feeds); + }); +}); diff --git a/src/libs/toml/io.ts b/src/libs/toml/io.ts new file mode 100644 index 0000000..be40c7a --- /dev/null +++ b/src/libs/toml/io.ts @@ -0,0 +1,15 @@ +import { convert } from "./convert.ts"; +import type { Lists } from "../types.ts"; + +export async function readTOML(file: string): Promise { + try { + const data: string = await Deno.readTextFile(file); + return convert(data); + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + throw new Error(`file not found: "${file}"`); + } else if (error instanceof Deno.errors.PermissionDenied) { + throw new Error(`permission denied: "${file}"`); + } else throw error; + } +} diff --git a/test/libs/io_test.ts b/src/libs/toml/io_test.ts similarity index 58% rename from test/libs/io_test.ts rename to src/libs/toml/io_test.ts index 4f0527d..cc01941 100644 --- a/test/libs/io_test.ts +++ b/src/libs/toml/io_test.ts @@ -1,8 +1,6 @@ import { assertEquals, assertIsError } from "@std/assert"; -import { join } from "@std/path"; -import { readTOML, writeXML } from "@5ouma/opml-generator/libs"; -import { convertFromTOML, convertToOPML } from "../../src/libs/convert.ts"; -import type { Lists } from "@5ouma/opml-generator/types"; +import { readTOML } from "./io.ts"; +import type { Lists } from "../types.ts"; Deno.test("Read TOML", async (t: Deno.TestContext) => { await t.step("normal", async () => { @@ -15,11 +13,20 @@ title = "feed title" xmlUrl = "https://example.com/feed" `; + const lists: Lists = { + lists: [{ + name: "list name", + feeds: [{ + title: "feed title", + xmlUrl: new URL("https://example.com/feed"), + }], + }], + }; + const file: string = await Deno.makeTempFile({ suffix: ".toml" }); await Deno.writeTextFile(file, toml); - const lists: Lists = await readTOML(file); - assertEquals(convertFromTOML(toml), lists); + assertEquals(await readTOML(file), lists); }); await t.step("file not found", async () => { @@ -48,21 +55,3 @@ xmlUrl = "https://example.com/feed" } }); }); - -Deno.test("Write XML", async () => { - const feeds: Lists = { - lists: [{ - name: "list name", - feeds: [{ - title: "feed title", - xmlUrl: new URL("https://example.com/feed"), - }], - }], - }; - - const dir: string = await Deno.makeTempDir(); - await writeXML(feeds, dir); - const data: string = await Deno.readTextFile(join(dir, "list-name.xml")); - - assertEquals(convertToOPML(feeds.lists[0]), data); -}); diff --git a/src/types/feed.ts b/src/libs/types.ts similarity index 63% rename from src/types/feed.ts rename to src/libs/types.ts index d588be4..ff0f9f9 100644 --- a/src/types/feed.ts +++ b/src/libs/types.ts @@ -13,3 +13,10 @@ export type Feed = { id?: string; xmlUrl?: URL; }; + +export type OPMLOutline = { + "@title": string; + "@text": string; + "@xmlUrl": URL; + "@type": "rss"; +}; diff --git a/src/libs/sites.ts b/src/libs/utils/sites.ts similarity index 80% rename from src/libs/sites.ts rename to src/libs/utils/sites.ts index ddf9581..de21cae 100644 --- a/src/libs/sites.ts +++ b/src/libs/utils/sites.ts @@ -1,13 +1,16 @@ -import { join } from "@std/url"; -import type { site } from "@5ouma/opml-generator/types"; +type site = { + type: string; + url: URL; +}; const sites: site[] = [ { type: "bluesky", url: new URL("https://bsky.app/profile/{id}/rss") }, { type: "nitter", - url: join( - new URL(`https://${Deno.env.get("NITTER_DOMAIN") ?? "cdn.xcancel.com"}`), - "/search/rss?f=tweets&q={id}", + url: new URL( + `https://${ + Deno.env.get("NITTER_DOMAIN") ?? "cdn.xcancel.com" + }/search/rss?f=tweets&q={id}`, ), }, { type: "note", url: new URL("https://note.com/{id}/rss") }, diff --git a/test/libs/sites_test.ts b/src/libs/utils/sites_test.ts similarity index 91% rename from test/libs/sites_test.ts rename to src/libs/utils/sites_test.ts index ae0a8cc..8fbcfc8 100644 --- a/test/libs/sites_test.ts +++ b/src/libs/utils/sites_test.ts @@ -1,6 +1,6 @@ import { assertEquals } from "@std/assert"; -import { transcodeXmlUrl } from "../../src/libs/sites.ts"; -import type { Feed } from "@5ouma/opml-generator/types"; +import { transcodeXmlUrl } from "./sites.ts"; +import type { Feed } from "../types.ts"; Deno.test("Get XML URL", async (t: Deno.TestContext) => { await t.step("site", () => { diff --git a/src/libs/xml/convert.ts b/src/libs/xml/convert.ts new file mode 100644 index 0000000..db8865d --- /dev/null +++ b/src/libs/xml/convert.ts @@ -0,0 +1,22 @@ +import { stringify } from "@libs/xml"; +import { transcodeXmlUrl } from "../utils/sites.ts"; +import type { Feed, List, OPMLOutline } from "../types.ts"; + +export function convert(list: List): string { + const body = { + outline: list.feeds.map((feed: Feed): OPMLOutline => { + return { + "@title": feed.title, + "@text": feed.title, + "@xmlUrl": feed.xmlUrl + ? new URL(feed.xmlUrl) + : transcodeXmlUrl(feed.title, feed.type, feed.id), + "@type": "rss", + }; + }), + }; + + return ` +${stringify({ opml: { "@version": "2.0", body: [body] } })} +`; +} diff --git a/src/libs/xml/convert_test.ts b/src/libs/xml/convert_test.ts new file mode 100644 index 0000000..28bb5e2 --- /dev/null +++ b/src/libs/xml/convert_test.ts @@ -0,0 +1,47 @@ +import { assertEquals } from "@std/assert"; +import { convert } from "./convert.ts"; +import type { List } from "../types.ts"; +Deno.test("Convert Lists to OPML", async (t: Deno.TestContext) => { + await t.step("rss", () => { + const xml = `\ + + + + + + +`; + + const list: List = { + name: "list name", + feeds: [{ + title: "feed title", + xmlUrl: new URL("https://example.com/feed"), + }], + }; + + assertEquals(convert(list), xml); + }); + + await t.step("site", () => { + const xml = `\ + + + + + + +`; + + const list: List = { + name: "list name", + feeds: [{ + title: "feed title", + type: "bluesky", + id: "username", + }], + }; + + assertEquals(convert(list), xml); + }); +}); diff --git a/src/libs/xml/io.ts b/src/libs/xml/io.ts new file mode 100644 index 0000000..00ac4da --- /dev/null +++ b/src/libs/xml/io.ts @@ -0,0 +1,16 @@ +import { paramCase } from "@wok/case"; +import { format } from "@std/path"; +import { convert } from "./convert.ts"; +import type { List, Lists } from "../types.ts"; + +export async function writeXML(feeds: Lists, dir: string): Promise { + await Deno.mkdir(dir, { recursive: true }); + feeds.lists.map(async (list: List) => { + const file: string = format({ + dir: dir, + name: paramCase(list.name), + ext: ".xml", + }); + await Deno.writeTextFile(file, convert(list)); + }); +} diff --git a/src/libs/xml/io_test.ts b/src/libs/xml/io_test.ts new file mode 100644 index 0000000..bc1066e --- /dev/null +++ b/src/libs/xml/io_test.ts @@ -0,0 +1,30 @@ +import { assertEquals } from "@std/assert"; +import { join } from "@std/path"; +import { writeXML } from "./io.ts"; +import type { Lists } from "../types.ts"; + +Deno.test("Write XML", async () => { + const lists: Lists = { + lists: [{ + name: "list name", + feeds: [{ + title: "feed title", + xmlUrl: new URL("https://example.com/feed"), + }], + }], + }; + + const xml = `\ + + + + + + +`; + + const dir: string = await Deno.makeTempDir(); + await writeXML(lists, dir); + + assertEquals(await Deno.readTextFile(join(dir, "list-name.xml")), xml); +}); diff --git a/src/main.ts b/src/main.ts index 705e127..a620409 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,6 @@ import { parseArgs } from "@std/cli"; import { basename, resolve } from "@std/path"; -import { readTOML, writeXML } from "@5ouma/opml-generator/libs"; -import type { Lists } from "@5ouma/opml-generator/types"; +import { type Lists, readTOML, writeXML } from "./libs/mod.ts"; const flags = parseArgs(Deno.args, { string: ["feeds", "output"], diff --git a/src/types/mod.ts b/src/types/mod.ts deleted file mode 100644 index e4ab14f..0000000 --- a/src/types/mod.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./opml.ts"; -export * from "./feed.ts"; -export * from "./sites.ts"; diff --git a/src/types/opml.ts b/src/types/opml.ts deleted file mode 100644 index b1bfc7f..0000000 --- a/src/types/opml.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type OPMLOutline = { - "@title": string; - "@text": string; - "@xmlUrl": URL; - "@type": "rss"; -}; diff --git a/src/types/sites.ts b/src/types/sites.ts deleted file mode 100644 index 3b4381b..0000000 --- a/src/types/sites.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type site = { - type: string; - url: URL; -}; diff --git a/test/libs/convert_test.ts b/test/libs/convert_test.ts deleted file mode 100644 index 4682ebc..0000000 --- a/test/libs/convert_test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { assertEquals } from "@std/assert"; -import { convertFromTOML, convertToOPML } from "../../src/libs/convert.ts"; -import type { List, Lists } from "@5ouma/opml-generator/types"; - -Deno.test("Parse TOML", async (t: Deno.TestContext) => { - await t.step("rss", () => { - const toml = ` -[[lists]] -name = "list name" - -[[lists.feeds]] -title = "feed title" -xmlUrl = "https://example.com/feed" -`; - - const feeds: Lists = { - lists: [{ - name: "list name", - feeds: [{ - title: "feed title", - xmlUrl: new URL("https://example.com/feed"), - }], - }], - }; - - assertEquals(convertFromTOML(toml), feeds); - }); - - await t.step("site", () => { - const toml = ` -[[lists]] -name = "list name" - -[[lists.feeds]] -title = "feed title" -type = "bluesky" -id = "username" -`; - - const feeds: Lists = { - lists: [{ - name: "list name", - feeds: [{ - title: "feed title", - type: "bluesky", - id: "username", - xmlUrl: new URL("https://bsky.app/profile/username/rss"), - }], - }], - }; - - assertEquals(convertFromTOML(toml), feeds); - }); -}); - -Deno.test("Convert Lists to OPML", async (t: Deno.TestContext) => { - await t.step("rss", () => { - const xml = `\ - - - - - - -`; - - const list: List = { - name: "list name", - feeds: [{ - title: "feed title", - xmlUrl: new URL("https://example.com/feed"), - }], - }; - - assertEquals(convertToOPML(list), xml); - }); - - await t.step("site", () => { - const xml = `\ - - - - - - -`; - - const list: List = { - name: "list name", - feeds: [{ - title: "feed title", - type: "bluesky", - id: "username", - }], - }; - - assertEquals(convertToOPML(list), xml); - }); -});