Skip to content

Commit

Permalink
feat: use embeds for docs (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart authored May 26, 2023
1 parent 82301e1 commit 36344ab
Showing 1 changed file with 128 additions and 107 deletions.
235 changes: 128 additions & 107 deletions src/DocsLookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Schedule,
Schema,
SchemaClass,
identity,
pipe,
} from "./_common.js"

Expand All @@ -25,87 +26,6 @@ const docUrls = [
"https://effect-ts.github.io/stream",
]

class DocEntry extends SchemaClass({
doc: Schema.string,
title: Schema.string,
content: Schema.string,
url: Schema.string,
relUrl: Schema.string,
}) {
get isSignature() {
return (
this.content.trim().length > 0 &&
this.url.includes("#") &&
!this.title.includes(" overview")
)
}

get subpackage() {
const [, subpackage, suffix] = this.url.match(/github\.io\/(.+?)\/(.+?)\//)!
return suffix !== "modules" && subpackage !== suffix
? `${subpackage}-${suffix}`
: subpackage
}

get package() {
return `@effect/${this.subpackage}`
}

get module() {
return this.doc.replace(/\.ts$/, "")
}

get signature() {
return `${this.module}.${this.title}`
}

get searchTerm(): string {
return `/${this.subpackage}/${this.module}.${this.title}`.toLowerCase()
}

get formattedContent() {
return Effect.map(
Effect.forEach(
this.content.split(" . ").map(_ => HtmlEnt.decode(_)),
text =>
text.startsWith("export ")
? Effect.map(
makePretty(text),
text => "```typescript\n" + text + "\n```",
)
: Effect.succeed(text),
),
_ => _.join("\n"),
)
}
}

const decodeEntries = Schema.parseEffect(Schema.array(DocEntry.schema()))

class QueryTooShort extends Data.TaggedClass("QueryTooShort")<{
readonly actual: number
readonly min: number
}> {}

const retryPolicy = Schedule.fixed(Duration.seconds(3))

const makePretty = (code: string) =>
pipe(
Effect.try(() => {
const codeWithNewlines = code.replace(
/ (<|\[|readonly|(?<!readonly |\()\b\w+\??:)/g,
"\n$1",
)
return Prettier.format(codeWithNewlines, {
parser: "typescript",
trailingComma: "all",
semi: false,
arrowParens: "avoid",
})
}),
Effect.catchAllCause(_ => Effect.succeed(code)),
)

const make = Effect.gen(function* (_) {
const registry = yield* _(InteractionsRegistry)

Expand All @@ -116,16 +36,8 @@ const make = Effect.gen(function* (_) {
Http.fetchJson(),
Effect.retry(retryPolicy),
Effect.map(_ => Object.values(_ as object)),
Effect.flatMap(_ => decodeEntries(_)),
Effect.map(entries =>
entries
.filter(_ => _.isSignature)
.map(entry =>
entry.copyWith({
url: `${baseUrl}${entry.relUrl}`,
}),
),
),
Effect.flatMap(decodeEntries),
Effect.map(entries => entries.filter(_ => _.isSignature)),
)

return searchData.map(entry => ({
Expand All @@ -148,10 +60,11 @@ const make = Effect.gen(function* (_) {
return pipe(
Effect.logDebug("searching"),
Effect.zipRight(allDocs),
Effect.map(_ =>
_.map((_, index) => [_, index] as const).filter(([_]) =>
_.term.includes(query),
),
Effect.map(entries =>
entries
.map((_, index) => [_, index] as const)
.filter(([_]) => _.term.includes(query))
.map(([_, index]) => [_.entry, index] as const),
),
Effect.logAnnotate("module", "DocsLookup"),
Effect.logAnnotate("query", query),
Expand All @@ -164,7 +77,7 @@ const make = Effect.gen(function* (_) {
description: "Search the Effect reference docs",
options: [
{
type: Discord.ApplicationCommandOptionType.STRING,
type: Discord.ApplicationCommandOptionType.INTEGER,
name: "query",
description: "The query to search for",
required: true,
Expand All @@ -178,19 +91,15 @@ const make = Effect.gen(function* (_) {
index: ix.optionValue("query"),
docs: allDocs,
}),
Effect.let("entry", ({ index, docs }) => docs[Number(index)].entry),
Effect.bind("content", ({ entry }) => entry.formattedContent),
Effect.map(({ entry, content }) =>
Ix.response({
Effect.bind("embed", ({ index, docs }) => docs[index].entry.embed),
Effect.map(({ embed }) => {
return Ix.response({
type: Discord.InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `View the documentation for \`${entry.signature}\` from \`${entry.package}\` here:
${entry.url}
${content}`,
embeds: [embed],
},
}),
),
})
}),
),
)

Expand All @@ -209,7 +118,7 @@ ${content}`,
.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT,
data: {
choices: results.slice(0, 25).map(
([{ entry }, index]): Discord.ApplicationCommandOptionChoice => ({
([entry, index]): Discord.ApplicationCommandOptionChoice => ({
name: `${entry.signature} (${entry.package})`,
value: index.toString(),
}),
Expand Down Expand Up @@ -242,3 +151,115 @@ export const DocsLookupLive = Layer.provide(
InteractionsRegistryLive,
Layer.effectDiscard(make),
)

// schema

class DocEntry extends SchemaClass({
doc: Schema.string,
title: Schema.string,
content: Schema.string,
url: pipe(
Schema.string,
Schema.transform(
Schema.string,
path => `https://effect-ts.github.io${path}`,
identity,
),
),
relUrl: Schema.string,
}) {
get isSignature() {
return (
this.content.trim().length > 0 &&
this.url.includes("#") &&
!this.title.includes(" overview")
)
}

get subpackage() {
const [, subpackage, suffix] = this.url.match(/github\.io\/(.+?)\/(.+?)\//)!
return suffix !== "modules" && subpackage !== suffix
? `${subpackage}-${suffix}`
: subpackage
}

get package() {
return `@effect/${this.subpackage}`
}

get module() {
return this.doc.replace(/\.ts$/, "")
}

get signature() {
return `${this.module}.${this.title}`
}

get searchTerm(): string {
return `/${this.subpackage}/${this.module}.${this.title}`.toLowerCase()
}

get formattedContent() {
return Effect.forEach(
this.content
.split(" . ")
.map(_ => _.trim())
.filter(_ => _.length)
.map(_ => HtmlEnt.decode(_)),
text =>
text.startsWith("export ") ? wrapCodeBlock(text) : Effect.succeed(text),
)
}

get embed() {
return pipe(
this.formattedContent,
Effect.map((content): Discord.Embed => {
const footer = content.pop()!

return {
author: {
name: this.package,
},
title: this.signature,
description: content.join("\n\n"),
color: 0x882ecb,
url: this.url,
footer: {
text: footer,
},
}
}),
)
}
}

const decodeEntries = Schema.parseEffect(Schema.array(DocEntry.schema()))

// errors

class QueryTooShort extends Data.TaggedClass("QueryTooShort")<{
readonly actual: number
readonly min: number
}> {}

const retryPolicy = Schedule.fixed(Duration.seconds(3))

// helpers

const wrapCodeBlock = (code: string) =>
pipe(
Effect.try(() => {
const codeWithNewlines = code
.replace(/ (<|\[|readonly|(?<!readonly |\()\b\w+\??:|\/\*\*)/g, "\n$1")
.replace(/\*\//g, "*/\n")
return Prettier.format(codeWithNewlines, {
parser: "typescript",
trailingComma: "all",
semi: false,
arrowParens: "avoid",
})
}),
Effect.catchAllCause(_ => Effect.succeed(code)),
Effect.map(_ => "```typescript\n" + _ + "\n```"),
)

0 comments on commit 36344ab

Please sign in to comment.