Skip to content

Instantly share code, notes, and snippets.

@cybersiddhu
Last active May 3, 2022 17:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cybersiddhu/1d877d07ca9f7c2d386fcb877262237b to your computer and use it in GitHub Desktop.
Save cybersiddhu/1d877d07ca9f7c2d386fcb877262237b to your computer and use it in GitHub Desktop.
Functional pipeline to return markdown content from wiki
"dependencies": {
"@isomorphic-git/lightning-fs": "~4.6.0",
"fp-ts": "~2.12.0",
"fp-ts-std": "~0.13.1",
"isomorphic-git": "~1.17.1",
"monocle-ts": "~2.3.13",
"newtype-ts": "~0.3.5",
"next": "12.1.5",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-markdown": "~8.0.3"
},
import { pipe, flow } from "fp-ts/lib/function"
import * as E from "fp-ts/lib/Either"
import * as TE from "fp-ts/lib/TaskEither"
import { clone } from "isomorphic-git"
import * as S from "fp-ts/string"
import { intercalate } from "fp-ts/Semigroup"
import http from "isomorphic-git/http/web"
import FS from "@isomorphic-git/lightning-fs"
export interface DirFileProps {
dir: string
file: string
url: string
browserFS: FS
}
export function cloneGithubWiki(props: DirFileProps) {
const defaultCloneParams = {
http: http,
fs: props.browserFS,
corsProxy: "https://cors.isomorphic-git.org",
}
const defaultSep = "/"
const wrapper = (props: DirFileProps) => TE.of(props)
const executeCloneRepo = ({ dir, url, file }: DirFileProps) =>
TE.tryCatch(async () => {
await clone({ dir, url, ...defaultCloneParams })
return { dir, url, file } as DirFileProps
}, E.toError)
// const errLogger = TE.fromIOK(Console.error)
// const infoLogger = TE.fromIOK(Console.info)
const errMsgExtract = (err: Error) => err.message
const addPath = ({ dir, file }: DirFileProps) => {
const pathDelimGroup = pipe(S.Semigroup, intercalate(defaultSep))
return pathDelimGroup.concat(dir, file)
}
const readFileFromRepo = (path: string) =>
TE.tryCatch(async () => {
const buffer = await props.browserFS.promises.readFile(path, {
encoding: "utf8",
})
return buffer.toString()
}, E.toError)
const payload = flow(
wrapper,
TE.chain(executeCloneRepo),
// TE.chainFirstIOK(infoLogger),
TE.map(addPath),
// TE.chainFirstIOK(infoLogger),
TE.chain(readFileFromRepo),
// TE.chainFirstIOK(infoLogger),
TE.mapLeft(errMsgExtract),
)
return payload(props)
}
import { useRouter } from "next/router"
import { useState, useEffect, useMemo } from "react"
import * as E from "fp-ts/Either"
import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/lib/function"
import { cloneGithubWiki } from "../../hooks/useGithubWiki"
import { toOutput, WikiContentEither } from "../../components/WikiDisplay"
import FS from "@isomorphic-git/lightning-fs"
export default function Gene() {
const router = useRouter()
const geneFile = `${router.query.id}.md`
const [content, setContent] = useState<WikiContentEither>(
E.right({ loading: true }),
)
const errSet = (err: string) => setContent(E.left(err))
const contentSet = (cont: string) =>
setContent(E.right({ markdown: cont, loading: false }))
const wikiFn = useMemo(() => {
return cloneGithubWiki({
dir: "/wiki",
file: geneFile,
browserFS: new FS("github-wiki"),
url: "https://github.com/dictybase-playground/ontomania.wiki.git",
})
}, [geneFile])
useEffect(() => {
const prog = async (fn: TE.TaskEither<string, string>) =>
pipe(await fn(), E.fold(errSet, contentSet))
prog(wikiFn)
}, [wikiFn])
return pipe(content, toOutput)
}
Gene.getInitialProps = () => ({ foo: "bar" })
// group of functions that maps the markdown content/error to the corresponding react component
import * as E from "fp-ts/Either"
import * as F from "fp-ts-std/Function"
import ReactMarkDown from "react-markdown"
interface WikiContentProps {
markdown?: string
loading: boolean
}
// type for Either monad
export type WikiContentEither = E.Either<string, WikiContentProps>
// predicate function that checks is the fetching is under process
const isLoading = (ma: WikiContentEither) => E.isRight(ma) && ma.right.loading
// predicate function to indicate if the fetching is completed
const isNotLoading = (ma: WikiContentEither) =>
E.isRight(ma) && !ma.right.loading
// react component for loading state
const loaderDisplay = () => <h1>Loading....</h1>
// react component for error display
const errDisplay = (ma: WikiContentEither) =>
E.isLeft(ma) && <h2>error {ma.left}</h2>
// react component for display markdown content
const nameDisplay = (ma: WikiContentEither) =>
E.isRight(ma) && <ReactMarkDown>{ma.right.markdown as string}</ReactMarkDown>
// react component when something unexpected happened
const defaultDisplay = () => <h2>Not sure what happened</h2>
// this function maps three conditions, error,success or loading to a react component
export const toOutput = F.guard([
[isLoading, loaderDisplay],
[E.isLeft, errDisplay],
[isNotLoading, nameDisplay],
])(defaultDisplay)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment