feat add module interface files (.mli) for all library modules

Commit
cc3afae4aa96eb8604eaeae871455f11bcfb1534
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/config.mli
index 00000000..42322206 000000..100644
@@ -0,0 +1,72 @@
1 Added: (** Reading and validating the TOML configuration.
2 Added:
3 Added: The file is looked up at [$OGIT_CONFIG], then
4 Added: [$XDG_CONFIG_HOME/ogit/config.toml], then [/etc/ogit/config.toml]. When no
5 Added: file is chosen explicitly a missing one is fine and {!default} applies; a
6 Added: file that exists but is malformed or invalid is always an error, so a
7 Added: mistake never silently degrades into defaults.
8 Added:
9 Added: {!to_table} and {!write_file} support the [ogit-write-config] executable,
10 Added: which emits a config file pre-filled with the defaults. *)
11 Added:
12 Added: (** {1 Configuration record} *)
13 Added:
14 Added: type t = {
15 Added: user_name : string;
16 Added: default_branch : string;
17 Added: git_project_root : string;
18 Added: commits_max_displayed : int;
19 Added: root_title : string;
20 Added: nav_logo : string;
21 Added: host : string;
22 Added: port : int;
23 Added: favorite_repositories : string list;
24 Added: archived_repositories : string list;
25 Added: }
26 Added: (** All settings that influence the server's behaviour. *)
27 Added:
28 Added: (** {1 Errors} *)
29 Added:
30 Added: type load_error =
31 Added: | Not_found of string
32 Added: | Parse_error of string
33 Added: | Invalid_value of string
34 Added: | Io_error of string
35 Added:
36 Added: val pp_load_error : Format.formatter -> load_error -> unit
37 Added: (** Format a load error for diagnostics. *)
38 Added:
39 Added: val show_load_error : load_error -> string
40 Added: (** Render a load error as a string. *)
41 Added:
42 Added: (** {1 Defaults} *)
43 Added:
44 Added: val default : t
45 Added: (** The configuration used when no file is found. *)
46 Added:
47 Added: (** {1 Loading} *)
48 Added:
49 Added: val load : unit -> (t, load_error) result
50 Added: (** Locate and parse the configuration file, or return {!default} when no
51 Added: implicit file exists. An explicit [$OGIT_CONFIG] that is missing or
52 Added: malformed is always an error. *)
53 Added:
54 Added: val read_file : ?file:string -> unit -> (t, load_error) result
55 Added: (** Parse a specific configuration file. *)
56 Added:
57 Added: (** {1 Serialisation} *)
58 Added:
59 Added: val to_table : t -> Toml.Types.table
60 Added: (** Convert a configuration record to a TOML table, suitable for writing back to
61 Added: disk. *)
62 Added:
63 Added: val write_file : ?file:string -> Toml.Types.table -> unit
64 Added: (** Write a TOML table to the given file, or to the default configuration path.
65 Added: *)
66 Added:
67 Added: (** {1 Internals exposed for tooling} *)
68 Added:
69 Added: val locate_config_file : unit -> string
70 Added: (** Return the path that would be used for the configuration file, following the
71 Added: [$OGIT_CONFIG] then [$XDG_CONFIG_HOME] then [/etc] resolution order. Useful
72 Added: for diagnostic tools. *)
lib/handlers.mli
index 00000000..244271da 000000..100644
@@ -0,0 +1,64 @@
1 Added: (** Request handlers: the seam between Git data and rendered pages.
2 Added:
3 Added: Each handler opens exactly one repository context, reads what its page
4 Added: needs, renders it, and closes the context — so the Git store, resolved
5 Added: metadata and default-branch policy are shared by every operation in a
6 Added: request instead of being reopened per query.
7 Added:
8 Added: Errors keep the category {!module:Resolvers} gave them until they reach
9 Added: {!error_response}, which is the single place a category becomes an HTTP
10 Added: status: malformed input is [400], a missing repository or object is [404],
11 Added: and storage failures are [500]. Handlers therefore never choose a status
12 Added: themselves.
13 Added:
14 Added: This is also the only layer allowed to touch both configuration and the
15 Added: filesystem; views receive plain values. *)
16 Added:
17 Added: val error_response : Resolvers.error -> Dream.response Dream.promise
18 Added: (** Map a resolver failure to the status and wording shown to the reader. *)
19 Added:
20 Added: (** Repository-scoped request handlers. *)
21 Added: module Repo : sig
22 Added: val with_repository :
23 Added: Config.t ->
24 Added: string ->
25 Added: (Resolvers.repository -> Repo.context -> Dream.response Dream.promise) ->
26 Added: Dream.response Dream.promise
27 Added: (** Open a repository by name, build a view context, run the continuation,
28 Added: then close the repository. Renders an error page on failure. *)
29 Added:
30 Added: val handle :
31 Added: Config.t ->
32 Added: (Resolvers.repository -> Repo.context -> Dream.response Dream.promise) ->
33 Added: Dream.request ->
34 Added: string ->
35 Added: Dream.response Dream.promise
36 Added: (** Dispatch a handler that needs repository and context. *)
37 Added:
38 Added: val handle_request :
39 Added: Config.t ->
40 Added: (Dream.request ->
41 Added: Resolvers.repository ->
42 Added: Repo.context ->
43 Added: Dream.response Dream.promise) ->
44 Added: Dream.request ->
45 Added: string ->
46 Added: Dream.response Dream.promise
47 Added: (** Dispatch a handler that additionally needs the request. *)
48 Added:
49 Added: val handle_id :
50 Added: Config.t ->
51 Added: (Resolvers.repository ->
52 Added: Repo.context ->
53 Added: string ->
54 Added: Dream.response Dream.promise) ->
55 Added: Dream.request ->
56 Added: string ->
57 Added: string ->
58 Added: Dream.response Dream.promise
59 Added: (** Dispatch a handler that resolves an object by identifier. *)
60 Added: end
61 Added:
62 Added: val routes : Config.t -> Dream.route list
63 Added: (** The complete set of Dream routes for the application. Requires a loaded
64 Added: configuration to bind repository discovery and rendering. *)
lib/highlight.mli
index 00000000..c72a9866 000000..100644
@@ -0,0 +1,19 @@
1 Added: (** Server-side syntax highlighting engine.
2 Added:
3 Added: Tokenizes source code using TextMate grammars (via hilite) and produces
4 Added: {!Dream_html.node} spans ready for embedding in the page. Falls back
5 Added: gracefully to plain text when no grammar is available for the requested
6 Added: language. *)
7 Added:
8 Added: type line = Dream_html.node list
9 Added: (** A single highlighted line: a list of HTML nodes (spans with classes). *)
10 Added:
11 Added: val highlight : lang:string option -> string -> line list
12 Added: (** [highlight ~lang source] tokenizes [source] and returns styled lines.
13 Added:
14 Added: When [lang] is [None] or the language is not supported, returns plain-text
15 Added: lines (no spans, just escaped text).
16 Added:
17 Added: The CSS classes follow hilite's convention:
18 Added: [{lang_scope}-{token_scope_segments}], e.g.
19 Added: [source.python-storage-type-function]. *)
lib/highlight_grammars.mli
index 00000000..47039737 000000..100644
@@ -0,0 +1,14 @@
1 Added: (** Registry of bundled TextMate grammars for syntax highlighting.
2 Added:
3 Added: Loads grammars embedded at build time via [ocaml-crunch] and registers them
4 Added: with a shared {!TmLanguage.t} instance. Language lookup is by the canonical
5 Added: name string produced by {!Syntax.detect}. *)
6 Added:
7 Added: val registry : TmLanguage.t Lazy.t
8 Added: (** The shared grammar registry, lazily initialised. All bundled grammars are
9 Added: registered on first force. *)
10 Added:
11 Added: val scope_of_lang : string -> string option
12 Added: (** Map a canonical language name (as returned by {!Syntax.detect}) to the
13 Added: TextMate scope name used by the grammar file. Returns [None] for languages
14 Added: without a bundled grammar. *)
lib/line_diff.mli
index 00000000..f723e89d 000000..100644
@@ -0,0 +1,61 @@
1 Added: (** Line-oriented diffs between two blobs.
2 Added:
3 Added: Computes a longest-common-subsequence diff and groups the result into hunks
4 Added: with the surrounding context, which is the shape a reader expects from
5 Added: [git diff]. Large inputs fall back to a coarser result rather than spending
6 Added: unbounded time and memory on the LCS matrix.
7 Added:
8 Added: The module is named for the granularity it works at: whole lines, matched as
9 Added: opaque units, with no word- or character-level refinement.
10 Added:
11 Added: It produces data only. Rendering it is {!module:Ui.Diff}'s job. *)
12 Added:
13 Added: (** {1 Line-level results} *)
14 Added:
15 Added: type line_kind =
16 Added: | Context
17 Added: | Addition
18 Added: | Deletion (** Whether a line is unchanged, added, or removed. *)
19 Added:
20 Added: type line = {
21 Added: kind : line_kind;
22 Added: old_number : int option;
23 Added: new_number : int option;
24 Added: text : string;
25 Added: }
26 Added: (** A single diff line with its position in the old and new files. *)
27 Added:
28 Added: (** {1 Hunks} *)
29 Added:
30 Added: type hunk = {
31 Added: old_start : int;
32 Added: old_count : int;
33 Added: new_start : int;
34 Added: new_count : int;
35 Added: lines : line list;
36 Added: }
37 Added: (** A contiguous group of changes with surrounding context. *)
38 Added:
39 Added: (** {1 Files} *)
40 Added:
41 Added: type file = {
42 Added: path : string;
43 Added: old_hash : string option;
44 Added: new_hash : string option;
45 Added: old_mode : int option;
46 Added: new_mode : int option;
47 Added: binary : bool;
48 Added: hunks : hunk list;
49 Added: }
50 Added: (** A single file's worth of changes, including metadata. *)
51 Added:
52 Added: (** {1 Computing diffs} *)
53 Added:
54 Added: val of_contents : string -> string -> line list
55 Added: (** [of_contents old_content new_content] produces a flat list of diff lines
56 Added: between two file contents. Falls back to listing all deletions followed by
57 Added: all additions when the LCS matrix would exceed 4 million cells. *)
58 Added:
59 Added: val hunks : ?context:int -> line list -> hunk list
60 Added: (** Group diff lines into hunks with [context] lines of surrounding unchanged
61 Added: content (default 3). *)
lib/list_ext.mli
index 00000000..d724ca11 000000..100644
@@ -0,0 +1,9 @@
1 Added: (** List utilities not in the OCaml 5.2 stdlib. *)
2 Added:
3 Added: val take : int -> 'a list -> 'a list
4 Added: (** [take n l] returns up to the first [n] elements of [l]. Returns fewer if the
5 Added: list is shorter than [n]. *)
6 Added:
7 Added: val drop : int -> 'a list -> 'a list
8 Added: (** [drop n l] removes the first [n] elements of [l]. Returns the empty list if
9 Added: [n] exceeds the list length. *)
lib/main.mli
index 00000000..ca35cc7c 000000..100644
@@ -0,0 +1,13 @@
1 Added: (** Server startup.
2 Added:
3 Added: Loads configuration, then hands the resulting routes to Dream. Startup fails
4 Added: loudly rather than falling back to defaults, so a typo in a config file is
5 Added: noticed immediately instead of silently changing which repositories are
6 Added: served. The one exception is an explicit [git_project_root] on the command
7 Added: line, which is enough on its own to run without any config file. *)
8 Added:
9 Added: val run : ?git_project_root:string -> unit -> unit
10 Added: (** Load configuration and start the HTTP server. When [git_project_root] is
11 Added: provided it overrides the configured value (and suppresses the startup
12 Added: failure when no config file exists). Does not return under normal operation.
13 Added: *)
lib/prose/prose.mli
index 00000000..34d8edd6 000000..100644
@@ -0,0 +1,16 @@
1 Added: (** Prose rendering facade.
2 Added:
3 Added: Detects documentation formats by filename extension and renders content as
4 Added: styled HTML. This module is the single entry point for prose rendering
5 Added: throughout ogit. *)
6 Added:
7 Added: val is_readme_filename : string -> bool
8 Added: (** [true] when the filename starts with "readme" (case-insensitive). *)
9 Added:
10 Added: val is_doc_filename : string -> bool
11 Added: (** [true] when the extension indicates a renderable documentation format (.md,
12 Added: .markdown, .org, .mld, .txt). *)
13 Added:
14 Added: val render : filename:string -> string -> Ui.node
15 Added: (** [render ~filename content] detects the format from the extension and
16 Added: produces a styled HTML region. *)
lib/prose/prose_format.mli
index 00000000..8f2eb8a7 000000..100644
@@ -0,0 +1,64 @@
1 Added: (** Shared document AST and renderer for prose formats.
2 Added:
3 Added: Format-specific parsing is supplied by the {!format} type; the renderer, TOC
4 Added: generation, and anchor management are format-independent.
5 Added:
6 Added: Every text fragment is emitted through {!Ui}, ensuring safe escaping of
7 Added: repository content. *)
8 Added:
9 Added: (** {1 Document AST} *)
10 Added:
11 Added: type inline =
12 Added: | Text of string
13 Added: | Code of string
14 Added: | Verbatim of string
15 Added: | Link of { href : string; text : string } (** Inline markup elements. *)
16 Added:
17 Added: type block =
18 Added: | Heading of int * string
19 Added: | Paragraph of string
20 Added: | Unordered_list of string list
21 Added: | Ordered_list of string list
22 Added: | Definition_list of (string * string) list
23 Added: | Code_block of string option * string (** Block-level document elements. *)
24 Added:
25 Added: type document = {
26 Added: title : string option;
27 Added: metadata : (string * string) list;
28 Added: blocks : block list;
29 Added: }
30 Added: (** A parsed document with optional title, metadata, and body blocks. *)
31 Added:
32 Added: (** {1 Format interface} *)
33 Added:
34 Added: type format = {
35 Added: name : string;
36 Added: css_class : string;
37 Added: parse : string -> document;
38 Added: inline : string -> Ui.node list;
39 Added: }
40 Added: (** A documentation format provides parsing and inline markup rendering. *)
41 Added:
42 Added: (** {1 Shared utilities} *)
43 Added:
44 Added: val first_word : string -> string option
45 Added: (** Extract the first whitespace-delimited word from a string. *)
46 Added:
47 Added: val is_continuation : string -> bool
48 Added: (** [true] when a line is indented and non-blank, indicating it continues the
49 Added: previous list item. *)
50 Added:
51 Added: val take_continuations : string list -> string list * string list
52 Added: (** Split off leading continuation lines from the remaining input. *)
53 Added:
54 Added: val take_until :
55 Added: (string -> bool) -> string list -> string list -> string list * string list
56 Added: (** [take_until close collected lines] collects lines until [close] returns
57 Added: [true], returning the collected lines and the remainder after the closing
58 Added: line. *)
59 Added:
60 Added: (** {1 Rendering} *)
61 Added:
62 Added: val render : format -> string -> Ui.node
63 Added: (** Render document content using the given format. Handles parsing, TOC
64 Added: generation, and block rendering. *)
lib/prose/prose_markdown.mli
index 00000000..8a285a6a 000000..100644
@@ -0,0 +1,8 @@
1 Added: (** Markdown documentation format.
2 Added:
3 Added: Parses ATX headings, fenced code blocks, unordered and ordered lists, and
4 Added: paragraphs. Inline markup is passed through as plain text. *)
5 Added:
6 Added: val format : Prose_format.format
7 Added: (** The Markdown format descriptor. Used as the default for README files and
8 Added: [.md] / [.markdown] extensions. *)
lib/prose/prose_mld.mli
index 00000000..323645a4 000000..100644
@@ -0,0 +1,7 @@
1 Added: (** Mld (ocamldoc) documentation format.
2 Added:
3 Added: Parses section headings, code blocks, and paragraphs. Inline markup handles
4 Added: bold, italic, emphasis, and code spans. *)
5 Added:
6 Added: val format : Prose_format.format
7 Added: (** The mld format descriptor, for [.mld] files. *)
lib/prose/prose_org.mli
index 00000000..db133671 000000..100644
@@ -0,0 +1,8 @@
1 Added: (** Org mode documentation format.
2 Added:
3 Added: Parses Org headings, [#+BEGIN_SRC] blocks, metadata directives, unordered,
4 Added: ordered, and definition lists, and paragraphs. Inline markup handles
5 Added: [=verbatim=], [~code~], and [[[link][desc]]] syntax. *)
6 Added:
7 Added: val format : Prose_format.format
8 Added: (** The Org mode format descriptor, for [.org] files. *)
lib/prose/prose_plaintext.mli
index 00000000..9cadde64 000000..100644
@@ -0,0 +1,6 @@
1 Added: (** Plain text documentation format.
2 Added:
3 Added: Each non-blank line becomes a paragraph. No inline markup is applied. *)
4 Added:
5 Added: val format : Prose_format.format
6 Added: (** The plain text format descriptor, for [.txt] files and fallback cases. *)
lib/routes.mli
index 00000000..b0edc81f 000000..100644
@@ -0,0 +1,44 @@
1 Added: (** URL paths, in both directions.
2 Added:
3 Added: One {!t} value describes a page, and the same value both generates a link
4 Added: ({!path_of}) and is recovered from an incoming request ({!dispatch}).
5 Added: Keeping the two directions in one module is what stops generated links and
6 Added: served routes from drifting apart.
7 Added:
8 Added: Only path-shaped routes live here. Query parameters — the commit list's
9 Added: filters and page number — are not modelled, because they refine a page
10 Added: rather than identify one. *)
11 Added:
12 Added: (** {1 Routes} *)
13 Added:
14 Added: type t =
15 Added: | Root
16 Added: | Project_dir of string
17 Added: | Repo of string
18 Added: | Commits of string
19 Added: | Commits_branch of string * string
20 Added: | Commit of string * string
21 Added: | Files of string
22 Added: | File of string * string
23 Added: | Raw_file of string * string
24 Added: (** A page in the application. The string arguments carry the repository
25 Added: name and an object identifier where applicable. *)
26 Added:
27 Added: val path_of : t -> string
28 Added: (** Generate the URL path for a route. *)
29 Added:
30 Added: (** {1 Dispatch} *)
31 Added:
32 Added: type action =
33 Added: | Summary
34 Added: | Commits_page
35 Added: | Commits_for_branch of string
36 Added: | Commit_detail of string
37 Added: | Files_page
38 Added: | File_detail of string
39 Added: | Raw of string
40 Added: (** What the handler should do once the repository is identified. *)
41 Added:
42 Added: val dispatch : string -> (string * action) option
43 Added: (** Parse a request path (without leading slash) into a repository name and
44 Added: action. Returns [None] when the path does not match any known route. *)
lib/static_handler.mli
index 00000000..aede7cda 000000..100644
@@ -0,0 +1,11 @@
1 Added: (** Serving the static assets embedded at build time.
2 Added:
3 Added: Assets are compiled into the executable by [ocaml-crunch] (see the rule in
4 Added: [lib/dune]), so a deployment is a single binary with no asset directory to
5 Added: keep in sync. Responses are cached for a day; because the content ships with
6 Added: the binary, a new release is the only thing that can change them. *)
7 Added:
8 Added: val handler : Dream.handler
9 Added: (** Dream handler for requests under [/static/**]. Looks up the path in the
10 Added: embedded asset store and responds with appropriate content type and cache
11 Added: headers, or 404 when the asset is not found. *)
lib/syntax.mli
index 00000000..8bd0d38d 000000..100644
@@ -0,0 +1,32 @@
1 Added: (** Guessing a file's language for syntax highlighting.
2 Added:
3 Added: Detection is best-effort and purely advisory: the blob renders identically
4 Added: whether or not a language is found, so a wrong guess degrades to plain text
5 Added: rather than breaking the page.
6 Added:
7 Added: Sources are tried in descending order of reliability: the filename
8 Added: extension, then a shebang, then an Emacs file variable, then a Vim modeline.
9 Added: *)
10 Added:
11 Added: val of_filename : string -> string option
12 Added: (** Detect language from a filename or its extension. Recognises well-known
13 Added: extensionless files (Makefile, Dockerfile, dune) as well as common
14 Added: extensions. *)
15 Added:
16 Added: val of_shebang : string -> string option
17 Added: (** Detect language from a [#!] line. Handles [env] indirection and strips
18 Added: version suffixes. *)
19 Added:
20 Added: val of_emacs_variables : string -> string option
21 Added: (** Detect language from an Emacs [-*- mode: ... -*-] file variable line. *)
22 Added:
23 Added: val of_vim_modeline : string -> string option
24 Added: (** Detect language from a Vim [ft=] or [filetype=] modeline. *)
25 Added:
26 Added: val of_content : string -> string option
27 Added: (** Inspect the first and last five lines for shebangs, Emacs variables, or Vim
28 Added: modelines. *)
29 Added:
30 Added: val detect : filename:string option -> string -> string option
31 Added: (** [detect ~filename content] returns a canonical language name. Prefers the
32 Added: filename when available, falling back to markers inside the content. *)
lib/views.mli
index 00000000..83bde340 000000..100644
@@ -0,0 +1,68 @@
1 Added: (** View layer — re-exports layout and page modules.
2 Added:
3 Added: This module is the public entry point for the view layer. Handlers use it to
4 Added: render pages without depending on individual view submodules. *)
5 Added:
6 Added: val error_page :
7 Added: ?title:string ->
8 Added: ?status:[< Dream.status > `Internal_Server_Error ] ->
9 Added: string ->
10 Added: Dream.response Dream.promise
11 Added: (** Render an error page. See {!Error.render}. *)
12 Added:
13 Added: val root :
14 Added: Layout.site ->
15 Added: dates:(string * (int64 * Git.User.tz_offset option) option) list ->
16 Added: ?prefix:string ->
17 Added: ?favorites:Resolvers.fs_node list ->
18 Added: ?archived:Resolvers.fs_node list ->
19 Added: ?readme:Resolvers.Blob.t ->
20 Added: Resolvers.fs_node list ->
21 Added: Dream.response Dream.promise
22 Added: (** Render the repository list page. See {!Root.render}. *)
23 Added:
24 Added: (** Repository page views. *)
25 Added: module Repo : sig
26 Added: type commit_message = { summary : string; body : string }
27 Added:
28 Added: val context :
29 Added: site:Layout.site -> repo:string -> description:string -> Repo.context
30 Added:
31 Added: val parse_commit_message : string option -> commit_message
32 Added: val parse_conventional : string -> string option * string
33 Added:
34 Added: val summary :
35 Added: Repo.context ->
36 Added: ?readme:Resolvers.Readme.t ->
37 Added: unit ->
38 Added: Dream.response Dream.promise
39 Added:
40 Added: val commits :
41 Added: ?filter_type:string ->
42 Added: ?author:string ->
43 Added: ?committer:string ->
44 Added: page_number:int ->
45 Added: has_prev:bool ->
46 Added: has_next:bool ->
47 Added: Repo.context ->
48 Added: Resolvers.Commit.t list ->
49 Added: Dream.response Dream.promise
50 Added:
51 Added: val files :
52 Added: Repo.context ->
53 Added: (string * string) list ->
54 Added: Resolvers.Tree.tree_node list ->
55 Added: Dream.response Dream.promise
56 Added:
57 Added: val file :
58 Added: Repo.context ->
59 Added: (string * string) list ->
60 Added: Resolvers.Blob.t ->
61 Added: Dream.response Dream.promise
62 Added:
63 Added: val commit :
64 Added: Repo.context ->
65 Added: Resolvers.Commit.t ->
66 Added: Resolvers.Diff.file list ->
67 Added: Dream.response Dream.promise
68 Added: end
lib/views/components.mli
index 00000000..10762bd3 000000..100644
@@ -0,0 +1,86 @@
1 Added: (** Ogit's vocabulary of page parts.
2 Added:
3 Added: Where {!Ui} supplies generic building blocks, this module names the parts
4 Added: specific to a Git browser and wires them to {!Routes}, so page modules can
5 Added: describe a page without mentioning HTML or URL strings.
6 Added:
7 Added: Every function returns a {!Ui.node}. Nothing here performs I/O. *)
8 Added:
9 Added: (** {1 Page identity} *)
10 Added:
11 Added: type page =
12 Added: | Summary
13 Added: | Commits
14 Added: | Files
15 Added: (** Which repository page is being shown. Drives the [aria-current] marker
16 Added: in the navigation. *)
17 Added:
18 Added: type site = { user_name : string; root_title : string; nav_logo : string }
19 Added: (** Site-wide presentation settings, resolved once from configuration. *)
20 Added:
21 Added: val site : user_name:string -> root_title:string -> nav_logo:string -> site
22 Added: (** Construct a {!site} record. *)
23 Added:
24 Added: (** {1 Routes as links} *)
25 Added:
26 Added: val url : Routes.t -> string
27 Added: (** Generate the URL path for a route. *)
28 Added:
29 Added: val route_link :
30 Added: ?class_:string -> ?label:string -> Routes.t -> string -> Ui.node
31 Added: (** A link to a route, with the route standing in for a hand-written URL. *)
32 Added:
33 Added: val commits_url :
34 Added: ?filter_type:string ->
35 Added: ?author:string ->
36 Added: ?committer:string ->
37 Added: ?page_number:int ->
38 Added: string ->
39 Added: string
40 Added: (** Build the URL for a commit list page, preserving active filters as query
41 Added: parameters. *)
42 Added:
43 Added: val clone_url : string -> string
44 Added: (** The URL from which a repository can be cloned. *)
45 Added:
46 Added: (** {1 Navigation} *)
47 Added:
48 Added: val site_nav :
49 Added: title:string -> logo:string -> ?home_href:string -> unit -> Ui.node
50 Added: (** Top navigation for the repository list and project directory pages. *)
51 Added:
52 Added: val repo_nav : active:page -> logo:string -> string -> Ui.node
53 Added: (** Top navigation within a repository. *)
54 Added:
55 Added: val compact_repo_nav : active:page -> string -> Ui.node
56 Added: (** Bottom navigation for narrow viewports within a repository. *)
57 Added:
58 Added: (** {1 Toolbar} *)
59 Added:
60 Added: val toolbar : Ui.node list -> Ui.node
61 Added: (** A toolbar container for repository page controls. *)
62 Added:
63 Added: (** {1 Trees} *)
64 Added:
65 Added: val directory :
66 Added: ?modifier:string -> route:Routes.t -> name:string -> Ui.node list -> Ui.node
67 Added: (** A directory row that both expands in place and links to its own page. *)
68 Added:
69 Added: val file_entry : ?modifier:string -> route:Routes.t -> string -> Ui.node
70 Added: (** A file row linking to the blob view. *)
71 Added:
72 Added: val overflow_row : route:Routes.t -> int -> Ui.node
73 Added: (** The row closing a truncated listing, linking to the full contents. *)
74 Added:
75 Added: (** {1 Sections} *)
76 Added:
77 Added: val group : ?expanded:bool -> title:string -> Ui.node list -> Ui.node
78 Added: (** A collapsible group of repositories on the root page. *)
79 Added:
80 Added: (** {1 Inline pieces} *)
81 Added:
82 Added: val commit_type_badge : ?href:string -> string -> Ui.node
83 Added: (** A conventional-commit type badge, coloured per type. *)
84 Added:
85 Added: val inline_readme : ?filename:string -> string -> Ui.node
86 Added: (** A README rendered as semantic documentation. *)
lib/views/error.mli
index 00000000..a3c3f392 000000..100644
@@ -0,0 +1,15 @@
1 Added: (** Error pages.
2 Added:
3 Added: These are deliberately self-contained rather than going through {!Layout}:
4 Added: an error may be raised before a repository context exists, so the page can
5 Added: depend on nothing but the status and message. *)
6 Added:
7 Added: val render :
8 Added: ?title:string ->
9 Added: ?status:[< Dream.status > `Internal_Server_Error ] ->
10 Added: string ->
11 Added: Dream.response Dream.promise
12 Added: (** Render an error page and respond with the given HTTP status.
13 Added:
14 Added: @param title the heading shown on the page (default ["Request failed"]).
15 Added: @param status the HTTP status code (default [`Internal_Server_Error]). *)
lib/views/layout.mli
index 00000000..ba4a1e24 000000..100644
@@ -0,0 +1,43 @@
1 Added: (** The page shell: everything that surrounds a page's own content.
2 Added:
3 Added: Pages hand this module a {!body_data} description and receive a complete
4 Added: document. The shell decides which navigation applies, whether a toolbar is
5 Added: present, and what goes in the head — pages never assemble those themselves.
6 Added: *)
7 Added:
8 Added: (** {1 Re-exported types} *)
9 Added:
10 Added: type page = Components.page =
11 Added: | Summary
12 Added: | Commits
13 Added: | Files (** Which repository page is being shown. *)
14 Added:
15 Added: type site = Components.site = {
16 Added: user_name : string;
17 Added: root_title : string;
18 Added: nav_logo : string;
19 Added: }
20 Added: (** Site-wide presentation settings. *)
21 Added:
22 Added: val site : user_name:string -> root_title:string -> nav_logo:string -> site
23 Added: (** Construct a {!site} record. *)
24 Added:
25 Added: (** {1 Page data} *)
26 Added:
27 Added: type body_data = {
28 Added: title : string;
29 Added: repo : string option;
30 Added: subtitle : string;
31 Added: active : page;
32 Added: toolbar : Ui.node list;
33 Added: content : Ui.node list;
34 Added: home_href : string option;
35 Added: }
36 Added: (** Everything the shell needs to wrap a page's content in the standard document
37 Added: structure. *)
38 Added:
39 Added: (** {1 Rendering} *)
40 Added:
41 Added: val render : ?page_title:string -> site -> body_data -> Ui.node
42 Added: (** Produce a complete HTML document from a page description. [page_title]
43 Added: becomes the [<title>] element (default ["Ogit"]). *)
lib/views/repo.mli
index 00000000..ab67e68e 000000..100644
@@ -0,0 +1,66 @@
1 Added: (** The repository pages: summary, commit list, file tree, blob, and commit
2 Added: detail.
3 Added:
4 Added: Each page is a description: it names the parts it is made of and hands them
5 Added: to {!Layout}. Markup lives in {!Ui}, ogit's page parts in {!Components},
6 Added: language guessing in {!Syntax}, and date formatting in {!Time_format}. *)
7 Added:
8 Added: (** {1 Context} *)
9 Added:
10 Added: type context
11 Added: (** What every repository page needs to know about its subject. *)
12 Added:
13 Added: val context : site:Layout.site -> repo:string -> description:string -> context
14 Added: (** Build a page context from site settings and repository metadata. *)
15 Added:
16 Added: (** {1 Commit messages} *)
17 Added:
18 Added: type commit_message = { summary : string; body : string }
19 Added: (** A commit message split into its first-line summary and remaining body. *)
20 Added:
21 Added: val parse_commit_message : string option -> commit_message
22 Added: (** Split a raw commit message into summary and body. *)
23 Added:
24 Added: val parse_conventional : string -> string option * string
25 Added: (** [parse_conventional summary] returns [(Some type, title)] if the summary
26 Added: follows Conventional Commits, or [(None, summary)] otherwise. *)
27 Added:
28 Added: (** {1 Pages} *)
29 Added:
30 Added: val summary :
31 Added: context -> ?readme:Resolvers.Readme.t -> unit -> Dream.response Dream.promise
32 Added: (** The repository summary page, optionally showing a README. *)
33 Added:
34 Added: val commits :
35 Added: ?filter_type:string ->
36 Added: ?author:string ->
37 Added: ?committer:string ->
38 Added: page_number:int ->
39 Added: has_prev:bool ->
40 Added: has_next:bool ->
41 Added: context ->
42 Added: Resolvers.Commit.t list ->
43 Added: Dream.response Dream.promise
44 Added: (** The paginated commit list, with optional type/author/committer filters. *)
45 Added:
46 Added: val files :
47 Added: context ->
48 Added: (string * string) list ->
49 Added: Resolvers.Tree.tree_node list ->
50 Added: Dream.response Dream.promise
51 Added: (** The file tree page. The trail is the path from root to the current
52 Added: directory. *)
53 Added:
54 Added: val file :
55 Added: context ->
56 Added: (string * string) list ->
57 Added: Resolvers.Blob.t ->
58 Added: Dream.response Dream.promise
59 Added: (** A single file view with syntax highlighting or prose rendering. *)
60 Added:
61 Added: val commit :
62 Added: context ->
63 Added: Resolvers.Commit.t ->
64 Added: Resolvers.Diff.file list ->
65 Added: Dream.response Dream.promise
66 Added: (** The commit detail page showing metadata and a diff. *)
lib/views/root.mli
index 00000000..4e3a6915 000000..100644
@@ -0,0 +1,22 @@
1 Added: (** The repository list, and the project directory pages that share its shape.
2 Added:
3 Added: A project directory is the same page scoped to a subtree, so both are
4 Added: described here. *)
5 Added:
6 Added: val render :
7 Added: Layout.site ->
8 Added: dates:(string * (int64 * Git.User.tz_offset option) option) list ->
9 Added: ?prefix:string ->
10 Added: ?favorites:Resolvers.fs_node list ->
11 Added: ?archived:Resolvers.fs_node list ->
12 Added: ?readme:Resolvers.Blob.t ->
13 Added: Resolvers.fs_node list ->
14 Added: Dream.response Dream.promise
15 Added: (** Render the repository list page.
16 Added:
17 Added: @param dates
18 Added: associates repository paths with their most recent commit timestamp.
19 Added: @param prefix the subdirectory path, empty for the site root.
20 Added: @param favorites repositories pinned to the top in a collapsible section.
21 Added: @param archived repositories shown in a collapsed section at the bottom.
22 Added: @param readme optional root-level README to display alongside the list. *)
lib/views/time_format.mli
index 00000000..9fb46e70 000000..100644
@@ -0,0 +1,20 @@
1 Added: (** Formatting Git dates for display.
2 Added:
3 Added: Git records a commit date as a Unix timestamp plus the offset of the zone
4 Added: the author was in. Relative and short forms drop that offset and use the
5 Added: server's local zone, which is what a reader scanning a list wants; the
6 Added: detailed form preserves it, because on a single commit the author's own
7 Added: wall-clock time is the meaningful one. *)
8 Added:
9 Added: val relative_time : int64 * 'a -> string
10 Added: (** A human-friendly relative duration such as ["3 days ago"] or ["just now"].
11 Added: The timezone offset is ignored; comparison is against the server's current
12 Added: time. *)
13 Added:
14 Added: val short_time : int64 * 'a -> string
15 Added: (** Minute-precision server-local time, formatted as [YYYY-MM-DD HH:MM]. *)
16 Added:
17 Added: val exact_time : int64 * Git.User.tz_offset option -> string * string
18 Added: (** Second-precision time in the recorded zone. Returns a pair of
19 Added: [(machine_readable, human_readable)] strings, where the first is suitable
20 Added: for a [datetime] attribute and the second for display. *)