MishkaDocument.Address (Mishka Document v0.0.1)

Copy Markdown View Source

A document's own address — where it is read when its admin gives it one, /chelekom/docs — and where each of its pages is read under it.

A document with no address is read under the documentation's base by its slug, as it always was: /docs/<slug>, its pages /docs/<slug>/<page>. One with an address is read there and nowhere else: /chelekom/docs, its pages /chelekom/docs/<page>, and /docs/chelekom is a missing page, as any slug that names no document there is.

A page's own part of the address is the slugs of the pages it is filed under, then its own: headless/switch is the page switch filed under headless. A slug is unique among the pages that share its parent, so the same name may be used under two parents — /chelekom/docs/tabs and /chelekom/docs/headless/tabs are two pages.

A version's front page (front_page, a page at the top) is drawn at the document's own address: its own part of the address is empty, so /chelekom/docs is that page, /chelekom/docs/<its slug> is a missing page, and the pages filed under it sit directly under the document's address.

A group (group_heading) is only a heading in the contents: it has no address at all, and the pages filed under it are addressed as if it were not there — breadcrumb under the group navigation is /chelekom/docs/breadcrumb, and /chelekom/docs/navigation is a missing page.

A group with a page of its own (group_page) is read at its own slug — /chelekom/docs/navigation — as any page is, and the pages filed under it still leave its slug out: breadcrumb stays /chelekom/docs/breadcrumb. group_page on a page that is not a group changes nothing.

An address is a site path: segments of lower-case letters, digits and dashes, and :lang at most once, where the site's language goes, as in a page's path. The rules live here because the document, its pages, the head and the sitemap all read them, and must agree.

Summary

Functions

Whether a page has an address: any page but a group with no page of its own.

:ok for a stored address the site can answer, else {:error, reason}: :not_a_path, or :in_docs_base for one under the base the documents with no address are read in.

Whether a page is its version's front page, drawn at its document's own address.

Whether a page is a group: a heading in the contents whose slug the pages under it leave out of their address.

Whether a page is a group with a page of its own, read at its own slug.

Whether an address holds the site's language.

The address as it is stored: trimmed, lower case, one leading slash and no trailing one. nil for nothing at all, and a value that is not a path is handed back as it was, for check/1 to refuse.

The address a page at page_path serves — the page path itself, or the path a *section capture closes — and nil for any other kind of path.

Whether one address holds the other — the same, or one under the other — so one document's pages would answer at the other's: /chelekom/docs and /chelekom/docs/headless overlap.

The page among sections whose own part of the address is segments — ["headless", "switch"], or the "headless/switch" a *section capture holds — or nil. Nothing asked, [] or "", is the front page, when they hold one. A group is the page at an address only when it has a page of its own.

The two page paths a document at address is read at: its front page, and every page of it.

Every page's own part of its address among sections — the pages of one version a reader is shown, each with its id, slug, parent_section_id, front_page, group_heading and group_page — as %{id => ["headless", "switch"]}, and [] for the front page. A group with no page of its own has none, and is not in it.

A page's own part of its address, root first: the slugs of the pages it is drawn under, then its own, a front page's and a group's left out — [] for the front page itself. A group with a page of its own keeps its own slug in its own address, and only there. parent_of answers the parent each page is drawn under, or nil at the top — which may not be the parent it names, when that one is not published. A parent chain deeper than twenty, which only a loop makes, stops there.

The segments of an address, :lang among them.

The query a reader reaches version at among its document's published versions (version_shown/2): %{} for the one shown when none is asked, %{"v" => version_number} for another, and nil for one no ?v= reaches — not among them, or sharing its number with a newer one. Versions are told apart by their id.

The version a reader is shown among a document's published versions, each with its version_number, is_latest_for_version, published_at and inserted_at: the one asked names (?v=), else the one marked latest, else the newest published. nil when there is none.

Functions

addressed?(section)

@spec addressed?(map()) :: boolean()

Whether a page has an address: any page but a group with no page of its own.

check(address)

@spec check(String.t()) :: :ok | {:error, :not_a_path | :in_docs_base}

:ok for a stored address the site can answer, else {:error, reason}: :not_a_path, or :in_docs_base for one under the base the documents with no address are read in.

front_page?(section)

@spec front_page?(map()) :: boolean()

Whether a page is its version's front page, drawn at its document's own address.

group?(section)

@spec group?(map()) :: boolean()

Whether a page is a group: a heading in the contents whose slug the pages under it leave out of their address.

group_page?(section)

@spec group_page?(map()) :: boolean()

Whether a page is a group with a page of its own, read at its own slug.

language?(address)

@spec language?(String.t()) :: boolean()

Whether an address holds the site's language.

normalize(value)

@spec normalize(term()) :: String.t() | nil

The address as it is stored: trimmed, lower case, one leading slash and no trailing one. nil for nothing at all, and a value that is not a path is handed back as it was, for check/1 to refuse.

normalize(" /Chelekom/Docs/ ") #=> "/chelekom/docs"
normalize("")                   #=> nil

of_page(page_path)

@spec of_page(term()) :: String.t() | nil

The address a page at page_path serves — the page path itself, or the path a *section capture closes — and nil for any other kind of path.

of_page("/chelekom/docs/*section") #=> "/chelekom/docs"

overlap?(one, other)

@spec overlap?(String.t(), String.t()) :: boolean()

Whether one address holds the other — the same, or one under the other — so one document's pages would answer at the other's: /chelekom/docs and /chelekom/docs/headless overlap.

page_at(sections, wanted)

@spec page_at([map()], [String.t()] | String.t()) :: map() | nil

The page among sections whose own part of the address is segments — ["headless", "switch"], or the "headless/switch" a *section capture holds — or nil. Nothing asked, [] or "", is the front page, when they hold one. A group is the page at an address only when it has a page of its own.

page_paths(address)

@spec page_paths(String.t()) :: %{document: String.t(), page: String.t()}

The two page paths a document at address is read at: its front page, and every page of it.

paths(sections)

@spec paths([map()]) :: %{optional(term()) => [String.t()]}

Every page's own part of its address among sections — the pages of one version a reader is shown, each with its id, slug, parent_section_id, front_page, group_heading and group_page — as %{id => ["headless", "switch"]}, and [] for the front page. A group with no page of its own has none, and is not in it.

A page is under its parent when that parent is among them and walking up from it reaches the top; otherwise it is at the top. That is the rule the documentation's blocks draw the contents by, so a page's address is where the reader finds it.

section_path(section, parent_of)

@spec section_path(map(), (map() -> map() | nil)) :: [String.t()]

A page's own part of its address, root first: the slugs of the pages it is drawn under, then its own, a front page's and a group's left out — [] for the front page itself. A group with a page of its own keeps its own slug in its own address, and only there. parent_of answers the parent each page is drawn under, or nil at the top — which may not be the parent it names, when that one is not published. A parent chain deeper than twenty, which only a loop makes, stops there.

segments(address)

@spec segments(String.t()) :: [String.t()]

The segments of an address, :lang among them.

version_query(version, versions)

@spec version_query(map(), [map()]) :: %{optional(String.t()) => String.t()} | nil

The query a reader reaches version at among its document's published versions (version_shown/2): %{} for the one shown when none is asked, %{"v" => version_number} for another, and nil for one no ?v= reaches — not among them, or sharing its number with a newer one. Versions are told apart by their id.

version_shown(versions, asked)

@spec version_shown([map()], String.t() | nil) :: map() | nil

The version a reader is shown among a document's published versions, each with its version_number, is_latest_for_version, published_at and inserted_at: the one asked names (?v=), else the one marked latest, else the newest published. nil when there is none.