mirror of
https://github.com/ryannhg/elm-spa.git
synced 2024-11-22 17:52:33 +03:00
generate route to href function
This commit is contained in:
parent
2f3b68db4c
commit
7befcbba86
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
.elm-spa
|
||||||
elm-stuff
|
elm-stuff
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
@ -9,6 +9,7 @@ import MsgTemplate from '../templates/msg'
|
|||||||
import ParamsTemplate from '../templates/params'
|
import ParamsTemplate from '../templates/params'
|
||||||
import * as Process from '../process'
|
import * as Process from '../process'
|
||||||
import { bold, underline, colors, reset, check, dim } from "../terminal"
|
import { bold, underline, colors, reset, check, dim } from "../terminal"
|
||||||
|
import { isStaticPage } from "../templates/utils"
|
||||||
|
|
||||||
export const build = (env : Environment) => () =>
|
export const build = (env : Environment) => () =>
|
||||||
createMissingDefaultFiles()
|
createMissingDefaultFiles()
|
||||||
@ -58,7 +59,7 @@ const createMissingDefaultFiles = async () => {
|
|||||||
const scanForStaticPages = async (entries: PageEntry[]) : Promise<string[][]> => {
|
const scanForStaticPages = async (entries: PageEntry[]) : Promise<string[][]> => {
|
||||||
const contents = await Promise.all(entries.map(e => File.read(e.filepath)))
|
const contents = await Promise.all(entries.map(e => File.read(e.filepath)))
|
||||||
return contents
|
return contents
|
||||||
.map((content, i) => content.includes('exposing (page)') ? i : undefined)
|
.map((content, i) => isStaticPage(content) ? i : undefined)
|
||||||
.filter(a => typeof a === 'number')
|
.filter(a => typeof a === 'number')
|
||||||
.map((i : any) => entries[i].segments)
|
.map((i : any) => entries[i].segments)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { routeTypeDefinition, indent, routeParserList, paramsImports, Options } from "./utils"
|
import { routeTypeDefinition, indent, routeParserList, paramsImports, Options, routeToHref } from "./utils"
|
||||||
|
|
||||||
export default (pages : string[][], _options : Options) : string => `
|
export default (pages : string[][], _options : Options) : string => `
|
||||||
module Gen.Route exposing
|
module Gen.Route exposing
|
||||||
( Route(..)
|
( Route(..)
|
||||||
, fromUrl
|
, fromUrl
|
||||||
-- , toUrl
|
, toHref
|
||||||
)
|
)
|
||||||
|
|
||||||
${paramsImports(pages)}
|
${paramsImports(pages)}
|
||||||
@ -25,8 +25,13 @@ routes =
|
|||||||
${indent(routeParserList(pages), 1)}
|
${indent(routeParserList(pages), 1)}
|
||||||
|
|
||||||
|
|
||||||
-- toUrl : Route -> Url
|
toHref : Route -> String
|
||||||
-- toUrl route =
|
toHref route =
|
||||||
-- Debug.todo "Gen.Route.toUrl"
|
let
|
||||||
|
joinAsHref : List String -> String
|
||||||
|
joinAsHref segments =
|
||||||
|
"/" ++ String.join "/" segments
|
||||||
|
in
|
||||||
|
${indent(routeToHref(pages), 1)}
|
||||||
|
|
||||||
`.trimLeft()
|
`.trimLeft()
|
@ -12,13 +12,18 @@ const isHomepage = (path: string[]) =>
|
|||||||
const isNotFoundPage = (path: string[]) =>
|
const isNotFoundPage = (path: string[]) =>
|
||||||
path.join('') === config.reserved.notFound
|
path.join('') === config.reserved.notFound
|
||||||
|
|
||||||
// [ 'Users', 'Name_', 'Settings' ] => [ 'Name_' ]
|
// [ 'Users', 'Name_', 'Settings' ] => [ 'Name' ]
|
||||||
const dynamicRouteSegments = (path : string[]) : string[] =>
|
const dynamicRouteSegments = (path : string[]) : string[] =>
|
||||||
isHomepage(path) || isNotFoundPage(path)
|
isHomepage(path) || isNotFoundPage(path)
|
||||||
? []
|
? []
|
||||||
: path.filter(segment => segment.endsWith('_'))
|
: path.filter(isDynamicSegment)
|
||||||
.map(segment => segment.substr(0, segment.length - 1))
|
.map(segment => segment.substr(0, segment.length - 1))
|
||||||
|
|
||||||
|
const isDynamicSegment = (segment : string) : boolean =>
|
||||||
|
segment !== config.reserved.homepage
|
||||||
|
&& segment !== config.reserved.notFound
|
||||||
|
&& segment.endsWith('_')
|
||||||
|
|
||||||
// "AboutUs" => "aboutUs"
|
// "AboutUs" => "aboutUs"
|
||||||
const fromPascalToCamelCase = (str : string) : string =>
|
const fromPascalToCamelCase = (str : string) : string =>
|
||||||
str[0].toLowerCase() + str.substring(1)
|
str[0].toLowerCase() + str.substring(1)
|
||||||
@ -127,6 +132,29 @@ export const routeTypeDefinition = (paths: string[][]) : string =>
|
|||||||
export const routeParserList = (paths: string[][]) : string =>
|
export const routeParserList = (paths: string[][]) : string =>
|
||||||
multilineList(paths.map(routeParserMap))
|
multilineList(paths.map(routeParserMap))
|
||||||
|
|
||||||
|
export const routeToHref = (paths: string[][]) : string =>
|
||||||
|
caseExpression(paths, {
|
||||||
|
variable: 'route',
|
||||||
|
condition: (path) =>
|
||||||
|
(dynamicRouteSegments(path).length === 0)
|
||||||
|
? routeVariant(path)
|
||||||
|
: `${routeVariant(path)} params`,
|
||||||
|
result: (path) => `joinAsHref ${routeToHrefSegments(path)}`
|
||||||
|
})
|
||||||
|
|
||||||
|
export const routeToHrefSegments = (path: string[]) : string => {
|
||||||
|
const segments = path.filter(p => p !== config.reserved.homepage)
|
||||||
|
const hrefFragments =
|
||||||
|
segments.map(segment =>
|
||||||
|
isDynamicSegment(segment)
|
||||||
|
? `params.${fromPascalToCamelCase(segment.substring(0, segment.length - 1))}`
|
||||||
|
: `"${fromPascalToSlugCase(segment)}"`
|
||||||
|
)
|
||||||
|
return hrefFragments.length === 0
|
||||||
|
? `[]`
|
||||||
|
: `[ ${hrefFragments.join(', ')} ]`
|
||||||
|
}
|
||||||
|
|
||||||
export const paramsImports = (paths: string[][]) : string =>
|
export const paramsImports = (paths: string[][]) : string =>
|
||||||
paths.map(path => `import Gen.Params.${path.join('.')}`).join('\n')
|
paths.map(path => `import Gen.Params.${path.join('.')}`).join('\n')
|
||||||
|
|
||||||
@ -193,7 +221,6 @@ const msgVariant = (path: string[]) : string =>
|
|||||||
const msg = (path: string[]) : string =>
|
const msg = (path: string[]) : string =>
|
||||||
`Pages.${path.join('.')}.Msg`
|
`Pages.${path.join('.')}.Msg`
|
||||||
|
|
||||||
|
|
||||||
export const pagesInitBody = (paths: string[][]) : string =>
|
export const pagesInitBody = (paths: string[][]) : string =>
|
||||||
indent(caseExpression(paths, {
|
indent(caseExpression(paths, {
|
||||||
variable: 'route',
|
variable: 'route',
|
||||||
@ -241,3 +268,13 @@ const pageModelArguments = (path: string[], options : Options) : string =>
|
|||||||
options.isStatic(path)
|
options.isStatic(path)
|
||||||
? `params ()`
|
? `params ()`
|
||||||
: `params model`
|
: `params model`
|
||||||
|
|
||||||
|
// Used in place of sophisticated AST parsing
|
||||||
|
const exposes = (keyword: string) => (elmSourceCode: string): boolean =>
|
||||||
|
new RegExp(`module\\s(\\S)+\\sexposing(\\s)+\\([^\\)]*${keyword}[^\\)]*\\)`, 'm').test(elmSourceCode)
|
||||||
|
|
||||||
|
export const exposesModel = exposes('Model')
|
||||||
|
export const exposesMsg = exposes('Msg')
|
||||||
|
|
||||||
|
export const isStaticPage = (sourceCode : string) : boolean =>
|
||||||
|
!exposesModel(sourceCode) || !exposesMsg(sourceCode)
|
@ -143,4 +143,59 @@ type Route
|
|||||||
]
|
]
|
||||||
`.trim())
|
`.trim())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
[ [ config.reserved.homepage ], `[]` ],
|
||||||
|
[ [ "AboutUs" ], `[ "about-us" ]` ],
|
||||||
|
[ [ "AboutUs", "Offices" ], `[ "about-us", "offices" ]` ],
|
||||||
|
[ [ "Posts" ], `[ "posts" ]` ],
|
||||||
|
[ [ "Posts", "Id_" ], `[ "posts", params.id ]` ],
|
||||||
|
[ [ "Users", "Name_", "Settings" ], `[ "users", params.name, "settings" ]` ],
|
||||||
|
[ [ "Users", "Name_", "Posts", "Id_" ], `[ "users", params.name, "posts", params.id ]` ],
|
||||||
|
])(".routeVariant(%p)", (input, output) => {
|
||||||
|
expect(Utils.routeToHrefSegments(input)).toBe(output)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe.each([['Model'], ['Msg']])
|
||||||
|
('Utils.exposes%s', (name: string) => {
|
||||||
|
const fn = (Utils as any)[`exposes${name}`] as (val: string) => boolean
|
||||||
|
|
||||||
|
test('fails for exposing all', () =>
|
||||||
|
expect(fn(`module Layout exposing (..)`)).toBe(false)
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`fails if missing keyword`, () => {
|
||||||
|
expect(fn(`module Layout exposing (OtherImport)`)).toBe(false)
|
||||||
|
expect(fn(`module Layout exposing
|
||||||
|
( OtherImport
|
||||||
|
)
|
||||||
|
`)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`works with single-line exposing "${name}"`, () => {
|
||||||
|
expect(fn(`module Layout exposing (${name})`)).toBe(true)
|
||||||
|
expect(fn(`module Layout exposing (OtherImport, ${name})`)).toBe(true)
|
||||||
|
expect(fn(`module Layout exposing (${name}, OtherImport)`)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`works with multi-line exposing "${name}"`, () => {
|
||||||
|
expect(fn(`
|
||||||
|
module Layout exposing
|
||||||
|
( ${name}
|
||||||
|
)
|
||||||
|
`)).toBe(true)
|
||||||
|
expect(fn(`
|
||||||
|
module Layout exposing
|
||||||
|
( OtherImport
|
||||||
|
, ${name}
|
||||||
|
)
|
||||||
|
`)).toBe(true)
|
||||||
|
expect(fn(`
|
||||||
|
module Layout exposing
|
||||||
|
( ${name}
|
||||||
|
, OtherImport
|
||||||
|
)
|
||||||
|
`)).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
Loading…
Reference in New Issue
Block a user