[OCaml] Mobile-friendly clone of cgit.
refactor extract commit message parsing into its own module
Move parse_commit_message and parse_conventional out of the view layer (lib/views/repo.ml) into lib/commit_message.ml. Handlers now call Commit_message directly instead of reaching into Views.Repo for domain logic.
Changed files
lib/commit_message.ml
@@ -0,0 +1,53 @@
1
Added:
(** Parsing commit messages and Conventional Commits metadata.
2
Added:
3
Added:
Extracted from the view layer so that both handlers (for filtering) and
4
Added:
views (for display) can use it without a layer violation. *)
5
Added:
6
Added:
type t = { summary : string; body : string }
7
Added:
8
Added:
let parse = function
9
Added:
| None -> { summary = ""; body = "" }
10
Added:
| Some message -> (
11
Added:
match String.split_on_char '\n' message with
12
Added:
| [] -> { summary = ""; body = "" }
13
Added:
| summary :: rest ->
14
Added:
{ summary; body = String.concat "\n" rest |> String.trim })
15
Added:
16
Added:
let conventional_commit_types =
17
Added:
[
18
Added:
"feat";
19
Added:
"fix";
20
Added:
"docs";
21
Added:
"style";
22
Added:
"refactor";
23
Added:
"perf";
24
Added:
"test";
25
Added:
"build";
26
Added:
"ci";
27
Added:
"chore";
28
Added:
"revert";
29
Added:
"remove";
30
Added:
]
31
Added:
32
Added:
(** Split a Conventional Commits subject into its type and the remaining title.
33
Added:
An unrecognised prefix is left in the title untouched, so non-conforming
34
Added:
histories still read correctly. *)
35
Added:
let parse_conventional summary =
36
Added:
match String.index_opt summary ':' with
37
Added:
| None -> (None, summary)
38
Added:
| Some colon_pos ->
39
Added:
let prefix = String.sub summary 0 colon_pos in
40
Added:
let type_name =
41
Added:
match String.index_opt prefix '(' with
42
Added:
| Some paren_pos -> String.sub prefix 0 paren_pos
43
Added:
| None -> prefix
44
Added:
in
45
Added:
let type_lower = String.lowercase_ascii type_name in
46
Added:
if List.mem type_lower conventional_commit_types then
47
Added:
let rest =
48
Added:
String.sub summary (colon_pos + 1)
49
Added:
(String.length summary - colon_pos - 1)
50
Added:
|> String.trim
51
Added:
in
52
Added:
(Some type_lower, rest)
53
Added:
else (None, summary)
lib/commit_message.mli
@@ -0,0 +1,10 @@
1
Added:
(** Parsing commit messages and Conventional Commits metadata. *)
2
Added:
3
Added:
type t = { summary : string; body : string }
4
Added:
5
Added:
val parse : string option -> t
6
Added:
(** Extract the summary line and body from a raw commit message. *)
7
Added:
8
Added:
val parse_conventional : string -> string option * string
9
Added:
(** Split a Conventional Commits subject into [(Some type, title)] or
10
Added:
[(None, original)] when the prefix is unrecognised. *)
lib/handlers.ml
@@ -164,7 +164,7 @@
164
164
| [] -> ""
165
165
| summary :: _ -> summary)
166
166
in
167
Removed:
let commit_type, _ = Views.Repo.parse_conventional summary in
167
Added:
let commit_type, _ = Commit_message.parse_conventional summary in
168
168
commit_type = Some expected
169
169
in
170
170
let author_matches =
lib/views.ml
@@ -10,8 +10,8 @@
10
10
}
11
11
12
12
let context = Repo.context
13
Removed:
let parse_commit_message = Repo.parse_commit_message
14
Removed:
let parse_conventional = Repo.parse_conventional
13
Added:
let parse_commit_message = Commit_message.parse
14
Added:
let parse_conventional = Commit_message.parse_conventional
15
15
let summary = Repo.summary
16
16
let commits = Repo.commits
17
17
let files = Repo.files
lib/views/repo.ml
@@ -8,58 +8,14 @@
8
8
type context = { repo : string; description : string; site : Layout.site }
9
9
(** What every repository page needs to know about its subject. *)
10
10
11
Removed:
type commit_message = { summary : string; body : string }
11
Added:
type commit_message = Commit_message.t = { summary : string; body : string }
12
12
13
13
let context ~site ~repo ~description = { repo; description; site }
14
14
15
15
(** {1 Commit messages} *)
16
16
17
Removed:
let parse_commit_message = function
18
Removed:
| None -> { summary = ""; body = "" }
19
Removed:
| Some message -> (
20
Removed:
match String.split_on_char '\n' message with
21
Removed:
| [] -> { summary = ""; body = "" }
22
Removed:
| summary :: rest ->
23
Removed:
{ summary; body = String.concat "\n" rest |> String.trim })
24
Removed:
25
Removed:
let conventional_commit_types =
26
Removed:
[
27
Removed:
"feat";
28
Removed:
"fix";
29
Removed:
"docs";
30
Removed:
"style";
31
Removed:
"refactor";
32
Removed:
"perf";
33
Removed:
"test";
34
Removed:
"build";
35
Removed:
"ci";
36
Removed:
"chore";
37
Removed:
"revert";
38
Removed:
"remove";
39
Removed:
]
40
Removed:
41
Removed:
(** Split a Conventional Commits subject into its type and the remaining title.
42
Removed:
An unrecognised prefix is left in the title untouched, so non-conforming
43
Removed:
histories still read correctly. *)
44
Removed:
let parse_conventional summary =
45
Removed:
match String.index_opt summary ':' with
46
Removed:
| None -> (None, summary)
47
Removed:
| Some colon_pos ->
48
Removed:
let prefix = String.sub summary 0 colon_pos in
49
Removed:
let type_name =
50
Removed:
match String.index_opt prefix '(' with
51
Removed:
| Some paren_pos -> String.sub prefix 0 paren_pos
52
Removed:
| None -> prefix
53
Removed:
in
54
Removed:
let type_lower = String.lowercase_ascii type_name in
55
Removed:
if List.mem type_lower conventional_commit_types then
56
Removed:
let rest =
57
Removed:
String.sub summary (colon_pos + 1)
58
Removed:
(String.length summary - colon_pos - 1)
59
Removed:
|> String.trim
60
Removed:
in
61
Removed:
(Some type_lower, rest)
62
Removed:
else (None, summary)
17
Added:
let parse_commit_message = Commit_message.parse
18
Added:
let parse_conventional = Commit_message.parse_conventional
63
19
64
20
(** {1 Links into the commit list} *)
65
21