Skip to content

Instantly share code, notes, and snippets.

@nicolas-zozol
Created February 12, 2025 22:46
Show Gist options
  • Select an option

  • Save nicolas-zozol/93348cd51bba1f0a9d455e5e11aaa872 to your computer and use it in GitHub Desktop.

Select an option

Save nicolas-zozol/93348cd51bba1f0a9d455e5e11aaa872 to your computer and use it in GitHub Desktop.
/*
I want a discriminate for post (or product) so that we don't look into a database:
Also '/learn' is the equivalent of '/blog'. It's a parameter named homeUrl
We have 3 discriminant for routes:
- locale. If empty, 'en'. Can be 'fr'.
- categories : ['blockchain'] or ['blockchain','security'] : can be undefined for blogroll
- page/number : Blog roll current page
- /s/slug : last part of the url, if present ; /s/ precises there will be a slug (s for slug)
Discriminants are needed so that NextJS can do pregeneration at
build time with ISR. If not, we would require searchParams
than can be done only at runtime.
- /: Home page
- /portfolio : Portfolio page
- /fr/portfolio/ : Portfolio page in French
- /learn : Blog homepage
- /learn/page/2 : Second page of the Blog homepage
- /learn/fr : Blog homepage in French
- /learn/blockchain : Blog category page
- /learn/blockchain/page/2 : Blog category page
- /learn/blockchain/s/what-is-blockchain : Blog post page
- /learn/fr/blockchain/ : Blog category page in French
- /learn/fr/blockchain/page/2 : Blog category page in French
- /learn/fr/blockchain/s/voici-la-blockchain/ : Blog post page in French
- /learn/blockchain/security : Blog category page
- /learn/blockchain/security/page/2 : Blog category page
*/
import { C, N, F, SingleParser, Tuple } from '@masala/parser';
import { X } from '@masala/x';
type RouteType = 'BLOG_ROLL' | 'CATEGORY' | 'POST';
interface ParseResultNoLocale {
type: RouteType;
categories?: string[] | undefined; // e.g. ['blockchain', 'security']
slug?: string | undefined;
page?: number; // 2 if
}
interface ParseResult extends ParseResultNoLocale {
isDefaultLocale: boolean;
locale: string; // 'en' or 'fr'
}
type PR = Partial<ParseResult>;
const slugDiscriminant = 's';
const slash = C.string('/');
const word = F.not(slash)
.rep()
.map((c) => c.join(''));
const categoryWord = word.filter((w) => w !== 'page' && w !== slugDiscriminant);
export const firstWordParser = C.char('/').then(word).last();
export const pageParser = C.string('/page/')
.then(N.integer())
.last()
.map((n) => ({ page: n }))
.then(F.eos())
.first() as SingleParser<{ page: number }>;
const singleCategory = (allCategories: string[][]) =>
slash
.then(
categoryWord.filter((w) =>
allCategories.some((cats) => cats.includes(w)),
),
)
.last();
const categoriesParser = (allCategories: string[][]) =>
singleCategory(allCategories)
.rep()
.array()
.filter((found) =>
allCategories.some((cats) => {
return JSON.stringify(cats) === JSON.stringify(found);
}),
)
.map((list) => ({ categories: list }));
const categories = (allCategories: string[][]) =>
X.split(slash.drop())
.filter((urlCategories: string[]) =>
allCategories.some(
(list) => JSON.stringify(list) == JSON.stringify(urlCategories),
),
)
.map((list: string[]) => ({ categories: list }));
const slugParser = C.string(`/${slugDiscriminant}/`)
.then(word)
.last()
.map((slug) => ({ slug }));
export const categoriesHome = (allCategories: string[][]) =>
categoriesParser(allCategories).then(F.eos()).first() as SingleParser<{
categories: string[];
}>;
export const categoriesHomeParser = (allCategories: string[][]) =>
categoriesParser(allCategories)
.then(F.eos())
.map((tuple) => {
const data = tuple.array() as PR[];
const categories = data[0];
return {
type: 'CATEGORY',
...categories,
page: 1,
} as ParseResultNoLocale;
});
export const categoriesPageParser = (allCategories: string[][]) =>
categoriesParser(allCategories)
.then(pageParser)
.map((tuple) => {
const data = tuple.array() as PR[];
const categories = data[0];
const page = data[1];
return {
type: 'CATEGORY',
...categories,
...page,
} as ParseResultNoLocale;
});
export const postParser = (allCategories: string[][]) =>
categoriesParser(allCategories)
.then(slugParser)
.map((tuple) => {
const data = tuple.array() as PR[];
const categories = data[0];
const slug = data[1];
return {
type: 'POST',
...slug,
...categories,
} as ParseResultNoLocale;
})
.then(F.eos().drop())
.single();
export const routeParsersForSpecs = {
categoriesParser,
slugParser,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment