mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-11-28 14:34:18 +03:00
Use new custom router.
This commit is contained in:
parent
ab87bc049a
commit
80ffd029eb
83
examples/slides/src/Page/Cats/Name__.elm
Normal file
83
examples/slides/src/Page/Cats/Name__.elm
Normal file
@ -0,0 +1,83 @@
|
||||
module Page.Cats.Name__ exposing (Data, Model, Msg, page)
|
||||
|
||||
import DataSource
|
||||
import Document exposing (Document)
|
||||
import Element exposing (Element)
|
||||
import Head
|
||||
import Head.Seo as Seo
|
||||
import Html.Styled exposing (text)
|
||||
import Page exposing (Page, PageWithState, StaticPayload)
|
||||
import Pages.ImagePath as ImagePath
|
||||
import Shared
|
||||
|
||||
|
||||
type alias Model =
|
||||
()
|
||||
|
||||
|
||||
type alias Msg =
|
||||
Never
|
||||
|
||||
|
||||
type alias RouteParams =
|
||||
{ name : Maybe String }
|
||||
|
||||
|
||||
page : Page RouteParams Data
|
||||
page =
|
||||
Page.prerenderedRoute
|
||||
{ head = head
|
||||
, routes = routes
|
||||
, data = data
|
||||
}
|
||||
|> Page.buildNoState { view = view }
|
||||
|
||||
|
||||
routes : DataSource.DataSource (List RouteParams)
|
||||
routes =
|
||||
DataSource.succeed
|
||||
[ { name = Just "larry"
|
||||
}
|
||||
, { name = Nothing
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
data : RouteParams -> DataSource.DataSource Data
|
||||
data routeParams =
|
||||
DataSource.succeed ()
|
||||
|
||||
|
||||
head :
|
||||
StaticPayload Data RouteParams
|
||||
-> List Head.Tag
|
||||
head static =
|
||||
Seo.summary
|
||||
{ canonicalUrlOverride = Nothing
|
||||
, siteName = "elm-pages"
|
||||
, image =
|
||||
{ url = ImagePath.build [ "TODO" ]
|
||||
, alt = "elm-pages logo"
|
||||
, dimensions = Nothing
|
||||
, mimeType = Nothing
|
||||
}
|
||||
, description = "TODO"
|
||||
, locale = Nothing
|
||||
, title = "TODO title" -- metadata.title -- TODO
|
||||
}
|
||||
|> Seo.website
|
||||
|
||||
|
||||
type alias Data =
|
||||
()
|
||||
|
||||
|
||||
view :
|
||||
StaticPayload Data RouteParams
|
||||
-> Document Msg
|
||||
view static =
|
||||
{ body =
|
||||
[ text (static.routeParams.name |> Maybe.withDefault "NOTHING")
|
||||
]
|
||||
, title = ""
|
||||
}
|
@ -500,22 +500,27 @@ mapBoth fnA fnB ( a, b, c ) =
|
||||
`,
|
||||
routesModule: `module Route exposing (..)
|
||||
|
||||
import Url
|
||||
import Url.Parser as Parser exposing ((</>), Parser)
|
||||
import Router
|
||||
|
||||
|
||||
type Route
|
||||
= ${templates.map(routeHelpers.routeVariantDefinition).join("\n | ")}
|
||||
|
||||
|
||||
urlToRoute : Url.Url -> Maybe Route
|
||||
urlToRoute : { url | path : String } -> Maybe Route
|
||||
urlToRoute url =
|
||||
Parser.parse (Parser.oneOf routes) url
|
||||
Router.firstMatch matchers url.path
|
||||
|
||||
|
||||
routes : List (Parser (Route -> a) a)
|
||||
routes =
|
||||
[ ${templates.map((name) => `${routeParser(name)}\n`).join(" , ")}
|
||||
matchers : List (Router.Matcher Route)
|
||||
matchers =
|
||||
[ ${templates
|
||||
.map(
|
||||
(name) => `{ pattern = "^${routeRegex(name).pattern}$"
|
||||
, toRoute = ${routeRegex(name).toRoute}
|
||||
}\n`
|
||||
)
|
||||
.join(" , ")}
|
||||
]
|
||||
|
||||
|
||||
@ -536,32 +541,178 @@ routeToPath maybeRoute =
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} segment
|
||||
* @returns {'static' | 'dynamic' | 'optional' | 'index'}
|
||||
*/
|
||||
function segmentKind(segment) {
|
||||
if (segment === "Index") {
|
||||
return "index";
|
||||
}
|
||||
const routeParamMatch = segment.match(/([A-Z][A-Za-z0-9]*)(_?_?)$/);
|
||||
const segmentKind = (routeParamMatch && routeParamMatch[2]) || "";
|
||||
if (segmentKind === "") {
|
||||
return "static";
|
||||
} else if (segmentKind === "_") {
|
||||
return "dynamic";
|
||||
} else if (segmentKind === "__") {
|
||||
return "optional";
|
||||
} else {
|
||||
throw "Unhandled segmentKind";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} name
|
||||
*/
|
||||
function routeParser(name) {
|
||||
const parsedParams = routeHelpers.parseRouteParams(name);
|
||||
const includesOptional = parsedParams.some(
|
||||
(param) => param.kind === "optional"
|
||||
);
|
||||
const params = routeHelpers.routeParams(name);
|
||||
if (includesOptional) {
|
||||
const parserCode = name
|
||||
.map((section) => {
|
||||
const routeParamMatch = section.match(/([A-Z][A-Za-z0-9]*)(_?_?)$/);
|
||||
const maybeParam = routeParamMatch && routeParamMatch[1];
|
||||
switch (segmentKind(section)) {
|
||||
case "static": {
|
||||
return `Parser.s "${camelToKebab(section)}"`;
|
||||
}
|
||||
case "index": {
|
||||
return `Parser.top`;
|
||||
}
|
||||
case "dynamic": {
|
||||
return `Parser.string`;
|
||||
}
|
||||
case "optional": {
|
||||
return `Parser.string`;
|
||||
}
|
||||
}
|
||||
})
|
||||
.join(" </> ");
|
||||
|
||||
const parserCodeWithoutOptional = name
|
||||
.flatMap((section) => {
|
||||
const routeParamMatch = section.match(/([A-Z][A-Za-z0-9]*)(_?_?)$/);
|
||||
const maybeParam = routeParamMatch && routeParamMatch[1];
|
||||
switch (segmentKind(section)) {
|
||||
case "static": {
|
||||
return [`Parser.s "${camelToKebab(section)}"`];
|
||||
}
|
||||
case "index": {
|
||||
return [`Parser.top`];
|
||||
}
|
||||
case "dynamic": {
|
||||
return [`Parser.string`];
|
||||
}
|
||||
case "optional": {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
})
|
||||
.join(" </> ");
|
||||
return `Parser.oneOf
|
||||
[ Parser.map (\\${params.join(" ")} -> ${pathNormalizedName(
|
||||
name
|
||||
)} { ${params.map((param) => `${param} = Just ${param}`)} }) (${parserCode})
|
||||
, Parser.map (${pathNormalizedName(name)} { ${params.map(
|
||||
(param) => `${param} = Nothing`
|
||||
)} }) (${parserCodeWithoutOptional})
|
||||
]`;
|
||||
} else {
|
||||
const parserCode = name
|
||||
.map((section) => {
|
||||
const routeParamMatch = section.match(/([A-Z][A-Za-z0-9]*)(_?_?)$/);
|
||||
const maybeParam = routeParamMatch && routeParamMatch[1];
|
||||
switch (segmentKind(section)) {
|
||||
case "static": {
|
||||
return `Parser.s "${camelToKebab(section)}"`;
|
||||
}
|
||||
case "index": {
|
||||
return `Parser.top`;
|
||||
}
|
||||
case "dynamic": {
|
||||
return `Parser.string`;
|
||||
}
|
||||
case "optional": {
|
||||
return `(Debug.todo "optional")`;
|
||||
}
|
||||
}
|
||||
// if (maybeParam) {
|
||||
// return `Parser.string`;
|
||||
// } else if (section === "Index") {
|
||||
// // TODO give an error if it isn't the final element
|
||||
// return "Parser.top";
|
||||
// } else {
|
||||
// return `Parser.s "${camelToKebab(section)}"`;
|
||||
// }
|
||||
})
|
||||
.join(" </> ");
|
||||
if (params.length > 0) {
|
||||
return `Parser.map (\\${params.join(" ")} -> ${pathNormalizedName(
|
||||
name
|
||||
)} { ${params.map((param) => `${param} = ${param}`)} }) (${parserCode})`;
|
||||
} else {
|
||||
return `Parser.map (${pathNormalizedName(name)} {}) (${parserCode})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} name
|
||||
*/
|
||||
function routeRegex(name) {
|
||||
const parsedParams = routeHelpers.parseRouteParams(name);
|
||||
const includesOptional = parsedParams.some(
|
||||
(param) => param.kind === "optional"
|
||||
);
|
||||
const params = routeHelpers.routeParams(name);
|
||||
const parserCode = name
|
||||
.map((section) => {
|
||||
const routeParamMatch = section.match(/([A-Z][A-Za-z0-9]*)_$/);
|
||||
.flatMap((section) => {
|
||||
const routeParamMatch = section.match(/([A-Z][A-Za-z0-9]*)(_?_?)$/);
|
||||
const maybeParam = routeParamMatch && routeParamMatch[1];
|
||||
if (maybeParam) {
|
||||
return `Parser.string`;
|
||||
} else if (section === "Index") {
|
||||
// TODO give an error if it isn't the final element
|
||||
return "Parser.top";
|
||||
} else {
|
||||
return `Parser.s "${camelToKebab(section)}"`;
|
||||
switch (segmentKind(section)) {
|
||||
case "static": {
|
||||
return [camelToKebab(section)];
|
||||
}
|
||||
case "index": {
|
||||
return [];
|
||||
}
|
||||
case "dynamic": {
|
||||
return [`(?:([^/]+))`];
|
||||
}
|
||||
case "optional": {
|
||||
return [`(([^/]+))?`];
|
||||
}
|
||||
}
|
||||
})
|
||||
.join(" </> ");
|
||||
if (params.length > 0) {
|
||||
return `Parser.map (\\${params.join(" ")} -> ${pathNormalizedName(
|
||||
name
|
||||
)} { ${params.map((param) => `${param} = ${param}`)} }) (${parserCode})`;
|
||||
} else {
|
||||
return `Parser.map (${pathNormalizedName(name)} {}) (${parserCode})`;
|
||||
}
|
||||
.join("\\\\/");
|
||||
|
||||
const toRoute = `\\matches ->
|
||||
case matches of
|
||||
[ ${parsedParams
|
||||
.flatMap((parsedParam) => {
|
||||
switch (parsedParam.kind) {
|
||||
case "optional": {
|
||||
return parsedParam.name;
|
||||
}
|
||||
case "dynamic": {
|
||||
return `Just ${parsedParam.name}`;
|
||||
}
|
||||
}
|
||||
})
|
||||
.join(", ")} ] ->
|
||||
Just (${pathNormalizedName(name)} { ${params.map(
|
||||
(param) => `${param} = ${param}`
|
||||
)} })
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
`;
|
||||
|
||||
return { pattern: parserCode, toRoute };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,20 +4,62 @@
|
||||
function routeParams(name) {
|
||||
return name
|
||||
.map((section) => {
|
||||
const routeParamMatch = section.match(/([A-Z][A-Za-z0-9]*)_$/);
|
||||
const routeParamMatch = section.match(/([A-Z][A-Za-z0-9]*)__?$/);
|
||||
const maybeParam = routeParamMatch && routeParamMatch[1];
|
||||
return maybeParam && toFieldName(maybeParam);
|
||||
})
|
||||
.filter((maybeParam) => maybeParam !== null);
|
||||
}
|
||||
|
||||
/** @typedef { { kind: ('dynamic' | 'optional'); name: string } } Segment */
|
||||
|
||||
/**
|
||||
* @param {string[]} name
|
||||
* @returns {Segment[]}
|
||||
*/
|
||||
function parseRouteParams(name) {
|
||||
return name.flatMap((section) => {
|
||||
const routeParamMatch = section.match(/([A-Z][A-Za-z0-9]*)(_?_?)$/);
|
||||
const maybeParam = (routeParamMatch && routeParamMatch[1]) || "TODO";
|
||||
|
||||
// return maybeParam && toFieldName(maybeParam);
|
||||
if (routeParamMatch[2] === "") {
|
||||
return [];
|
||||
} else if (routeParamMatch[2] === "_") {
|
||||
return [
|
||||
{
|
||||
kind: "dynamic",
|
||||
name: toFieldName(maybeParam),
|
||||
},
|
||||
];
|
||||
} else if (routeParamMatch[2] === "__") {
|
||||
return [
|
||||
{
|
||||
kind: "optional",
|
||||
name: toFieldName(maybeParam),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
throw "Unhandled";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} name
|
||||
* @returns {string}
|
||||
*/
|
||||
function routeVariantDefinition(name) {
|
||||
return `${routeVariant(name)} { ${routeParams(name).map(
|
||||
(param) => `${param} : String`
|
||||
)} }`;
|
||||
return `${routeVariant(name)} { ${parseRouteParams(name).map((param) => {
|
||||
switch (param.kind) {
|
||||
case "dynamic": {
|
||||
return `${param.name} : String`;
|
||||
}
|
||||
case "optional": {
|
||||
return `${param.name} : Maybe String`;
|
||||
}
|
||||
}
|
||||
})} }`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,4 +89,5 @@ module.exports = {
|
||||
routeVariant,
|
||||
toFieldName,
|
||||
paramsRecord,
|
||||
parseRouteParams,
|
||||
};
|
||||
|
70
src/Router.elm
Normal file
70
src/Router.elm
Normal file
@ -0,0 +1,70 @@
|
||||
module Router exposing (Matcher, firstMatch)
|
||||
|
||||
import List.Extra
|
||||
import Regex
|
||||
|
||||
|
||||
firstMatch : List (Matcher route) -> String -> Maybe route
|
||||
firstMatch matchers path =
|
||||
List.Extra.findMap
|
||||
(\matcher ->
|
||||
if Regex.contains (matcher.pattern |> toRegex) (normalizePath path) then
|
||||
tryMatch matcher path
|
||||
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
matchers
|
||||
|
||||
|
||||
toRegex : String -> Regex.Regex
|
||||
toRegex pattern =
|
||||
Regex.fromString pattern
|
||||
|> Maybe.withDefault Regex.never
|
||||
|
||||
|
||||
type alias Matcher route =
|
||||
{ pattern : String, toRoute : List (Maybe String) -> Maybe route }
|
||||
|
||||
|
||||
tryMatch : { pattern : String, toRoute : List (Maybe String) -> Maybe route } -> String -> Maybe route
|
||||
tryMatch { pattern, toRoute } path =
|
||||
path
|
||||
|> normalizePath
|
||||
|> submatches pattern
|
||||
|> toRoute
|
||||
|
||||
|
||||
submatches : String -> String -> List (Maybe String)
|
||||
submatches pattern path =
|
||||
Regex.find
|
||||
(Regex.fromString pattern
|
||||
|> Maybe.withDefault Regex.never
|
||||
)
|
||||
path
|
||||
|> List.concatMap .submatches
|
||||
|
||||
|
||||
normalizePath : String -> String
|
||||
normalizePath path =
|
||||
path
|
||||
|> stripLeadingSlash
|
||||
|> stripTrailingSlash
|
||||
|
||||
|
||||
stripLeadingSlash : String -> String
|
||||
stripLeadingSlash path =
|
||||
if path |> String.startsWith "/" then
|
||||
String.dropLeft 1 path
|
||||
|
||||
else
|
||||
path
|
||||
|
||||
|
||||
stripTrailingSlash : String -> String
|
||||
stripTrailingSlash path =
|
||||
if path |> String.endsWith "/" then
|
||||
String.dropRight 1 path
|
||||
|
||||
else
|
||||
path
|
Loading…
Reference in New Issue
Block a user