[OCaml] Mobile-friendly clone of cgit.
Moderate amount of progress today 💯
Changed files
lib/config.ml
@@ -1,2 +1,2 @@
1
Removed:
let git_directory = Filename.concat (Unix.getenv "HOME") "git"
1
Added:
let git_directory = Filename.concat (Unix.getenv "HOME") "git.test"
2
2
let author = "Marius Peter"
lib/handlers.ml
@@ -1,4 +1,3 @@
1
Removed:
(* Handlers for different routes *)
2
1
let ogit_root _req = Views.Ogit_root.render () |> Dream_html.respond
3
2
4
3
let repo_root req =
@@ -7,19 +6,22 @@
7
6
8
7
let repo_tree req =
9
8
let repo_name = Dream.param req "repo_name" in
10
Removed:
let dir_path = Dream.param req "**" in
9
Added:
let dir_path = Dream.query req "path" |> Option.value ~default:"" in
11
10
Views.Repo_tree.render repo_name dir_path |> Dream_html.respond
12
11
13
12
let repo_blob req =
14
13
let repo_name = Dream.param req "repo_name" in
15
Removed:
let blob_name = Dream.param req "blob_name" in
16
Removed:
Views.Repo_blob.render repo_name blob_name |> Dream_html.respond
14
Added:
let blob_path = Dream.query req "path" |> Option.value ~default:"" in
15
Added:
Views.Repo_blob.render repo_name blob_path |> Dream_html.respond
17
16
18
Removed:
(* Route definitions *)
19
Removed:
let all_handlers = [
20
Removed:
Dream.get "/" ogit_root;
21
Removed:
Dream.get "/:repo_name" repo_root;
22
Removed:
Dream.get "/:repo_name/tree/**" repo_tree;
23
Removed:
Dream.get "/:repo_name/blob/:blob_name" repo_blob;
24
Removed:
Dream.get "/static/**" (Dream.static "./lib/static");
25
Removed:
]
17
Added:
let all_handlers =
18
Added:
[
19
Added:
Dream.get "/" ogit_root;
20
Added:
Dream.scope "/:repo_name" []
21
Added:
[
22
Added:
Dream.get "/" repo_root;
23
Added:
Dream.get "/tree" repo_tree;
24
Added:
Dream.get "/blob" repo_blob;
25
Added:
];
26
Added:
Dream.get "/static/**" (Dream.static "./lib/static");
27
Added:
]
lib/views.ml
@@ -29,7 +29,8 @@
29
29
[
30
30
title [] "%s" head_data.page_title;
31
31
link [ rel "stylesheet"; href "/static/styles.css" ];
32
Removed:
link [ rel "icon"; type_ "image/x-icon"; href "/static/git_icon.svg"];
32
Added:
link
33
Added:
[ rel "icon"; type_ "image/x-icon"; href "/static/git_icon.svg" ];
33
34
];
34
35
body []
35
36
[
@@ -50,7 +51,7 @@
50
51
let is_active = path = current_path in
51
52
let attrs = if is_active then [ id "active" ] else [] in
52
53
let url =
53
Removed:
if String.length path = 0 then Printf.sprintf "/%s" repo_path
54
Added:
if String.equal path "/" then Printf.sprintf "/%s/" repo_path
54
55
else Printf.sprintf "/%s/%s" repo_path path
55
56
in
56
57
li attrs [ a [ href "%s" url ] [ txt text ] ]
@@ -61,7 +62,7 @@
61
62
ul []
62
63
@@ List.map li_of_a
63
64
[
64
Removed:
("", "summary");
65
Added:
("/", "summary");
65
66
("refs", "refs");
66
67
("log", "log");
67
68
("tree", "tree");
@@ -129,15 +130,14 @@
129
130
open Dream_html
130
131
open HTML
131
132
132
Removed:
let full_path repo_path dir_path =
133
Removed:
Filename.concat (Filename.concat Config.git_directory repo_path) dir_path
133
Added:
let full_path repo_name dir_path =
134
Added:
Filename.concat (Filename.concat Config.git_directory repo_name) dir_path
134
135
135
136
(* Helper function to list contents of a given directory *)
136
Removed:
let ls_dir repo_path dir_path =
137
Removed:
let dir_full_path = full_path repo_path dir_path in
137
Added:
let ls_dir repo_name dir_path =
138
Added:
let dir_full_path = full_path repo_name dir_path in
138
139
try
139
Removed:
Sys.readdir dir_full_path
140
Removed:
|> Array.to_list
140
Added:
Sys.readdir dir_full_path |> Array.to_list
141
141
|> List.filter (fun name -> name <> ".git") (* Exclude .git *)
142
142
|> List.map (fun entry ->
143
143
let entry_rel_path = Filename.concat dir_path entry in
@@ -145,34 +145,70 @@
145
145
(entry, entry_rel_path, Sys.is_directory full_entry_path))
146
146
|> List.sort (fun (a, _, is_dir_a) (b, _, is_dir_b) ->
147
147
match (is_dir_a, is_dir_b) with
148
Removed:
| true, false -> -1 (* Directories first *)
148
Added:
| true, false -> -1 (* Directories first *)
149
149
| false, true -> 1
150
150
| _ -> String.compare a b)
151
151
with Sys_error _ -> []
152
152
153
153
(* Function to create a link based on file type *)
154
Removed:
let link_for_entry repo_path (entry, entry_rel_path, is_dir) =
154
Added:
let link_for_entry repo_name (entry, entry_rel_path, is_dir) =
155
155
let link =
156
Removed:
if is_dir then Printf.sprintf "/%s/tree/%s" repo_path entry_rel_path
157
Removed:
else Printf.sprintf "/%s/blob/%s" repo_path entry_rel_path
156
Added:
if is_dir then Printf.sprintf "/%s/tree?path=%s" repo_name entry_rel_path
157
Added:
else Printf.sprintf "/%s/blob?path=%s" repo_name entry_rel_path
158
158
in
159
Removed:
li [] [ a [ href "%s" link ] [ txt "%s" entry ] ]
159
Added:
let display_name = if is_dir then entry ^ "/" else entry in
160
Added:
li [] [ a [ href "%s" link ] [ txt "%s" display_name ] ]
160
161
161
Removed:
(* Render function, now supporting nested directories *)
162
Removed:
let render repo_path dir_path =
163
Removed:
let title = Filename.concat repo_path dir_path in
164
Removed:
let subtitle = "Repository Files" in
162
Added:
(* Generate breadcrumb navigation for the directory path *)
163
Added:
let breadcrumb_navigation repo_name dir_path =
164
Added:
let parts = String.split_on_char '/' dir_path in
165
165
166
Removed:
let repo_entries = ls_dir repo_path dir_path in
166
Added:
let rec build_paths acc paths =
167
Added:
match paths with
168
Added:
| [] -> List.rev acc
169
Added:
| part :: rest ->
170
Added:
let new_path =
171
Added:
match acc with [] -> part | _ -> List.hd acc ^ "/" ^ part
172
Added:
in
173
Added:
build_paths (new_path :: acc) rest
174
Added:
in
175
Added:
176
Added:
let breadcrumb_links =
177
Added:
build_paths [] parts
178
Added:
|> List.mapi (fun i path ->
179
Added:
let display_name = List.nth parts i in
180
Added:
a
181
Added:
[ href "%s" (Printf.sprintf "/%s/tree?path=%s" repo_name path) ]
182
Added:
[ txt "%s" display_name ])
183
Added:
in
184
Added:
185
Added:
(* Manually intersperse " / " separators *)
186
Added:
let rec intersperse sep = function
187
Added:
| [] -> []
188
Added:
| [ x ] -> [ x ]
189
Added:
| x :: xs -> x :: txt sep :: intersperse sep xs
190
Added:
in
191
Added:
192
Added:
match breadcrumb_links with
193
Added:
| [] -> txt "/"
194
Added:
| _ -> span [] (intersperse " / " breadcrumb_links)
195
Added:
196
Added:
(* Render function *)
197
Added:
let render repo_name dir_path =
198
Added:
let title = repo_name in
199
Added:
(* Only display the repo name as the title *)
200
Added:
let subtitle = "Files" in
201
Added:
202
Added:
let repo_entries = ls_dir repo_name dir_path in
167
203
let content =
168
204
[
169
Removed:
h3 [] [ txt "%s" (Printf.sprintf "Contents of /%s" dir_path) ];
170
Removed:
ul [] (List.map (link_for_entry repo_path) repo_entries);
205
Added:
h3 [] [ breadcrumb_navigation repo_name dir_path ];
206
Added:
ul [] (List.map (link_for_entry repo_name) repo_entries);
171
207
]
172
208
in
173
209
174
210
let body_data =
175
Removed:
{ title; subtitle; topnav = Topnav.repo repo_path "tree"; content }
211
Added:
{ title; subtitle; topnav = Topnav.repo repo_name "tree"; content }
176
212
in
177
213
Layout.application body_data
178
214
end
@@ -187,7 +223,8 @@
187
223
let read_blob repo_path blob_name =
188
224
let file_path = Filename.concat (full_path repo_path) blob_name in
189
225
try Some (In_channel.with_open_text file_path In_channel.input_all)
190
Removed:
with _ -> None (* Handle cases where the file doesn't exist or can't be read *)
226
Added:
with _ ->
227
Added:
None (* Handle cases where the file doesn't exist or can't be read *)
191
228
192
229
(* Render function *)
193
230
let render repo_path blob_name =
@@ -196,17 +233,25 @@
196
233
197
234
match read_blob repo_path blob_name with
198
235
| Some content ->
199
Removed:
let content_display =
200
Removed:
pre [] [ code [] [ txt "%s" content ] ] (* Render as code block *)
201
Removed:
in
236
Added:
let content_display = pre [] [ code [] [ txt "%s" content ] ] in
202
237
let body_data =
203
Removed:
{ title; subtitle; topnav = Topnav.repo repo_path "blob"; content = [content_display] }
238
Added:
{
239
Added:
title;
240
Added:
subtitle;
241
Added:
topnav = Topnav.repo repo_path "blob";
242
Added:
content = [ content_display ];
243
Added:
}
204
244
in
205
245
Layout.application body_data
206
246
| None ->
207
247
let error_message = p [] [ txt "Error: Unable to read file." ] in
208
248
let body_data =
209
Removed:
{ title; subtitle; topnav = Topnav.repo repo_path "blob"; content = [error_message] }
249
Added:
{
250
Added:
title;
251
Added:
subtitle;
252
Added:
topnav = Topnav.repo repo_path "blob";
253
Added:
content = [ error_message ];
254
Added:
}
210
255
in
211
256
Layout.application body_data
212
257
end