| 1 | /** |
| 2 | * Where `#N` in text leads on the site: the rules of |
| 3 | * `crates/contracts/src/work/refs.rs`, which every reader of references |
| 4 | * follows. A reference is `#N`, `owner/name#N` (read in that repository) |
| 5 | * or `workspace#N` (in that workspace). Read in a repository, `#N` is its |
| 6 | * pull request N, else the work item that was its issue N, else the |
| 7 | * workspace's work item N, else a pull request N of another of its |
| 8 | * repositories, numbered after work items came (an older pull request's |
| 9 | * number is only its repository's): only the work service knows which, so a link goes through |
| 10 | * `/<owner>/<repo>/ref/<n>`, which asks it (`resolveRef`) and sends the |
| 11 | * browser on. Read in a workspace alone, `#N` is its work item N, else its |
| 12 | * pull request N numbered after work items came: the work item's page sends |
| 13 | * a pull request's number on. |
| 14 | * Pure, so it is tested under Node. |
| 15 | */ |
| 16 | |
| 17 | /** Where text is read: in a repository (and its workspace), or in a workspace alone. */ |
| 18 | export type RefContext = { repo: { namespace: string; name: string } } | { workspace: string }; |
| 19 | |
| 20 | /** A reference found in text. */ |
| 21 | export type Ref = |
| 22 | | { scope: "here"; number: number } |
| 23 | | { scope: "repo"; namespace: string; name: string; number: number } |
| 24 | | { scope: "workspace"; workspace: string; number: number }; |
| 25 | |
| 26 | /** A work item's page. */ |
| 27 | export function itemPath(workspace: string, number: number): string { |
| 28 | return `/${workspace.toLowerCase()}/-/work/${number}`; |
| 29 | } |
| 30 | |
| 31 | /** The page that reads `#number` in a repository and sends the browser to what it names. */ |
| 32 | export function repoRefPath(repo: { namespace: string; name: string }, number: number): string { |
| 33 | return `/${repo.namespace.toLowerCase()}/${repo.name}/ref/${number}`; |
| 34 | } |
| 35 | |
| 36 | /** Where a reference leads, read in `context`; null for `#N` with no context to read it in. */ |
| 37 | export function refHref(ref: Ref, context: RefContext | null | undefined): string | null { |
| 38 | switch (ref.scope) { |
| 39 | case "repo": |
| 40 | return repoRefPath({ namespace: ref.namespace, name: ref.name }, ref.number); |
| 41 | case "workspace": |
| 42 | return itemPath(ref.workspace, ref.number); |
| 43 | case "here": |
| 44 | if (!context) return null; |
| 45 | return "repo" in context ? repoRefPath(context.repo, ref.number) : itemPath(context.workspace, ref.number); |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | /** A reference in its written form: `owner/name#12`, `workspace#12` or `#12`. */ |
| 50 | export function parseRef(owner: string | undefined, name: string | undefined, number: string): Ref | null { |
| 51 | const n = Number(number); |
| 52 | if (!Number.isInteger(n) || n < 1 || number.length > 9) return null; |
| 53 | if (owner && name) return { scope: "repo", namespace: owner.toLowerCase(), name, number: n }; |
| 54 | if (owner) return { scope: "workspace", workspace: owner.toLowerCase(), number: n }; |
| 55 | return { scope: "here", number: n }; |
| 56 | } |