(** URL paths, in both directions. One {!t} value describes a page, and the same value both generates a link ({!path_of}) and is recovered from an incoming request ({!dispatch}). For every route [r], [dispatch] inverts [path_of]: [dispatch (path_of r without its leading slash) = Some r]. Keeping the two directions in one module is what stops generated links and served routes from drifting apart. Only path-shaped routes live here. Query parameters — the commit list's filters and page number — are not modelled, because they refine a page rather than identify one. The segments [summary], [commits], [commit], [files], [file], and [raw] are reserved: they mark where a repository path ends, so a repository or directory carrying one of these names is not reachable. *) (** {1 Routes} *) type t = | Root | Project_dir of string | Repo of string | Commits of string | Commits_branch of string * string | Commit of string * string | Files of string | File of string * string | File_at of string * string | Raw_file of string * string | Raw_at of string * string (** A page in the application. The string arguments carry the repository name and either an object identifier ({!File}, {!Raw_file}) or a slash-separated file path from the repository root ({!File_at}, {!Raw_at}). Path arguments are percent-encoded by {!path_of} and decoded by {!dispatch}, so file names survive the round trip unaltered. A single-segment path that is itself 40 hexadecimal characters is indistinguishable from an object id and dispatches as one. *) val path_of : t -> string (** Generate the URL path for a route. *) (** {1 Dispatch} *) val dispatch : string -> t option (** Parse a request path (without leading slash) back into a route. Returns [None] when the path matches no known route, including a recognised route followed by extra segments. A path without a reserved segment comes back as {!Project_dir} even when it names a repository: only the filesystem can tell the two apart, so the handler resolves which page to serve. *)