[OCaml] Mobile-friendly clone of cgit.
feat add module interface files (.mli) for all library modules
Changed files
- lib/config.mli
- lib/handlers.mli
- lib/highlight.mli
- lib/highlight_grammars.mli
- lib/line_diff.mli
- lib/list_ext.mli
- lib/main.mli
- lib/prose/prose.mli
- lib/prose/prose_format.mli
- lib/prose/prose_markdown.mli
- lib/prose/prose_mld.mli
- lib/prose/prose_org.mli
- lib/prose/prose_plaintext.mli
- lib/routes.mli
- lib/static_handler.mli
- lib/syntax.mli
- lib/views.mli
- lib/views/components.mli
- lib/views/error.mli
- lib/views/layout.mli
- lib/views/repo.mli
- lib/views/root.mli
- lib/views/time_format.mli
lib/config.mli
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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. *)