Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
145 lines
4.3 KiB
OCaml
145 lines
4.3 KiB
OCaml
type human
|
|
type json
|
|
type edn
|
|
|
|
module Mode = struct
|
|
type _ t = Human : human t | Json : json t | Edn : edn t
|
|
and packed = Packed : _ t -> packed
|
|
|
|
let default = Human
|
|
|
|
let of_string = function
|
|
| "human" -> Some (Packed Human)
|
|
| "json" -> Some (Packed Json)
|
|
| "edn" -> Some (Packed Edn)
|
|
| _ -> None
|
|
|
|
let to_string : type a. a t -> string = function
|
|
| Human -> "human"
|
|
| Json -> "json"
|
|
| Edn -> "edn"
|
|
|
|
let structured : type a. a t -> bool = function
|
|
| Human -> false
|
|
| Json | Edn -> true
|
|
end
|
|
|
|
module Human_output = struct
|
|
type t = {
|
|
headers : string Rrbvec.t option;
|
|
rows : string Rrbvec.t Rrbvec.t;
|
|
footer : string option;
|
|
}
|
|
|
|
let create ?headers ?footer ~rows () = { headers; rows; footer }
|
|
|
|
let has_suffix ~suffix value =
|
|
let suffix_len = String.length suffix in
|
|
let value_len = String.length value in
|
|
value_len >= suffix_len
|
|
&& String.sub value (value_len - suffix_len) suffix_len = suffix
|
|
|
|
let is_datetime_header header =
|
|
let header = String.lowercase_ascii header in
|
|
has_suffix ~suffix:"created-at" header
|
|
|| has_suffix ~suffix:"updated-at" header
|
|
|
|
let display_cell ~now_time ?header value =
|
|
let value = if value = "" then "-" else value in
|
|
match header with
|
|
| Some header when is_datetime_header header -> (
|
|
match Int64.of_string_opt value with
|
|
| Some epoch_ms ->
|
|
Humanize_types.relative_datetime
|
|
~then_time:(Time.time_of_epoch_ms epoch_ms)
|
|
~now_time
|
|
| None -> value)
|
|
| _ -> value
|
|
|
|
let display_rows t =
|
|
let now_time = Time.now () in
|
|
let headers = Option.value t.headers ~default:Vec.empty in
|
|
Vec.map
|
|
(fun row ->
|
|
Vec.mapi
|
|
(fun index value ->
|
|
let header =
|
|
match Vec.nth_opt headers index with
|
|
| Some header when is_datetime_header header -> Some header
|
|
| Some header
|
|
when String.lowercase_ascii header = "value"
|
|
&& Vec.length row >= 2 ->
|
|
Vec.nth_opt row 0
|
|
| header -> header
|
|
in
|
|
display_cell ~now_time ?header value)
|
|
row)
|
|
t.rows
|
|
|
|
let column_widths rows =
|
|
rows
|
|
|> Vec.fold_left
|
|
(fun widths row ->
|
|
let width = max (Vec.length widths) (Vec.length row) in
|
|
Vec.init width (fun index ->
|
|
max
|
|
(Option.value (Vec.nth_opt widths index) ~default:0)
|
|
(Display_width.width
|
|
(Option.value (Vec.nth_opt row index) ~default:""))))
|
|
Vec.empty
|
|
|
|
let pad_right width value =
|
|
let padding = width - Display_width.width value in
|
|
if padding <= 0 then value else value ^ String.make padding ' '
|
|
|
|
let render_row widths row =
|
|
let last_index = Vec.length widths - 1 in
|
|
widths
|
|
|> Vec.mapi (fun index width ->
|
|
let value = Option.value (Vec.nth_opt row index) ~default:"" in
|
|
if index = last_index then value else pad_right width value)
|
|
|> Vec.string_concat " "
|
|
|
|
let strip_leading_colon value =
|
|
if String.length value > 0 && value.[0] = ':' then
|
|
String.sub value 1 (String.length value - 1)
|
|
else value
|
|
|
|
let is_keyword_namespace_char = function
|
|
| 'a' .. 'z' | '0' .. '9' | '.' | '-' | '_' -> true
|
|
| _ -> false
|
|
|
|
let is_keyword_namespace value =
|
|
value <> "" && String.for_all is_keyword_namespace_char value
|
|
|
|
let display_header header =
|
|
let header = strip_leading_colon header in
|
|
let parts = Vec.split_on_char '/' header in
|
|
if Vec.length parts = 2 then
|
|
let namespace = Vec.nth parts 0 in
|
|
let name = Vec.nth parts 1 in
|
|
if is_keyword_namespace namespace && name <> "" then name else header
|
|
else header
|
|
|
|
let lines t =
|
|
let rows = display_rows t in
|
|
let table_rows =
|
|
match t.headers with
|
|
| None -> rows
|
|
| Some headers when Vec.is_empty headers -> rows
|
|
| Some headers -> Vec.push_front rows (Vec.map display_header headers)
|
|
in
|
|
let widths = column_widths table_rows in
|
|
let lines = Vec.map (render_row widths) table_rows in
|
|
match t.footer with
|
|
| Some footer -> Vec.push_back lines footer
|
|
| None -> lines
|
|
|
|
let to_string t = Vec.string_concat "\n" (lines t)
|
|
end
|
|
|
|
type _ t =
|
|
| Human : Human_output.t -> human t
|
|
| Edn : Melange_edn_melange.any -> edn t
|
|
| Json : Melange_edn_melange.any -> json t
|