···
) $ output_dir_arg $ title_arg $ posts_per_page_arg)
+
(* Category management commands *)
+
module Category = struct
+
let categories = River.State.list_categories state in
+
if categories = [] then begin
+
Fmt.(styled `Yellow string)
+
"No categories defined yet. Use 'river category add' to create one."
+
Fmt.(styled `Bold (styled (`Fg `Cyan) string))
+
(Printf.sprintf "Categories (%d total)" (List.length categories));
+
Fmt.pr "%a@.@." Fmt.(styled `Faint string) (String.make 60 '-');
+
Fmt.(styled `Bold (styled (`Fg `Blue) string)) (River.Category.id cat)
+
Fmt.(styled `Green string) (River.Category.name cat);
+
(match River.Category.description cat with
+
Fmt.(styled `Faint string) "└─"
+
Fmt.(styled `Faint string) desc
+
let add state ~id ~name ?description () =
+
let category = River.Category.create ~id ~name ?description () in
+
match River.State.add_category state category with
+
Fmt.pr "@.%a Category %a added successfully@.@."
+
Fmt.(styled (`Fg `Green) string) "✓"
+
Fmt.(styled `Bold string) id;
+
Fmt.pr "@.%a Failed to add category: %s@.@."
+
Fmt.(styled (`Fg `Red) string) "✗"
+
match River.State.remove_category state ~id with
+
Fmt.pr "@.%a Category %a removed successfully@.@."
+
Fmt.(styled (`Fg `Green) string) "✓"
+
Fmt.(styled `Bold string) id;
+
Fmt.pr "@.%a Failed to remove category: %s@.@."
+
Fmt.(styled (`Fg `Red) string) "✗"
+
match River.State.get_category state ~id with
+
Fmt.pr "@.%a Category %a not found@.@."
+
Fmt.(styled (`Fg `Red) string) "✗ Error:"
+
Fmt.(styled `Bold string) id;
+
Fmt.(styled `Bold (styled (`Fg `Cyan) string))
+
(Printf.sprintf "Category: %s" (River.Category.id cat));
+
Fmt.pr "%a@.@." Fmt.(styled `Faint string) (String.make 60 '-');
+
Fmt.(styled `Faint string) "Name: "
+
Fmt.(styled `Green string) (River.Category.name cat);
+
Fmt.(styled `Faint string) "Description:"
+
Fmt.string (Option.value (River.Category.description cat) ~default:"(none)");
+
let post_ids = River.State.get_posts_by_category state ~category_id:id in
+
Fmt.(styled `Bold string)
+
(Printf.sprintf "Posts (%d)" (List.length post_ids));
+
Fmt.pr "%a@." Fmt.(styled `Faint string) (String.make 60 '-');
+
Fmt.(styled `Faint string)
+
" No posts tagged with this category."
+
List.iter (fun post_id ->
+
Fmt.(styled `Faint string) "•"
+
Fmt.(styled (`Fg `Blue) string) post_id
+
(* Post category tagging commands *)
+
module Post_category = struct
+
let add state ~post_id ~category_id =
+
match River.State.add_post_category state ~post_id ~category_id with
+
Fmt.pr "@.%a Post %a tagged with category %a@.@."
+
Fmt.(styled (`Fg `Green) string) "✓"
+
Fmt.(styled `Bold string) post_id
+
Fmt.(styled `Bold string) category_id;
+
Fmt.pr "@.%a Failed to tag post: %s@.@."
+
Fmt.(styled (`Fg `Red) string) "✗"
+
let remove state ~post_id ~category_id =
+
match River.State.remove_post_category state ~post_id ~category_id with
+
Fmt.pr "@.%a Category %a removed from post %a@.@."
+
Fmt.(styled (`Fg `Green) string) "✓"
+
Fmt.(styled `Bold string) category_id
+
Fmt.(styled `Bold string) post_id;
+
Fmt.pr "@.%a Failed to remove category from post: %s@.@."
+
Fmt.(styled (`Fg `Red) string) "✗"
+
let list state ~post_id =
+
let category_ids = River.State.get_post_categories state ~post_id in
+
if category_ids = [] then begin
+
Fmt.(styled `Yellow string)
+
(Printf.sprintf "Post %s has no categories assigned." post_id)
+
Fmt.(styled `Bold (styled (`Fg `Cyan) string))
+
(Printf.sprintf "Categories for post: %s" post_id);
+
Fmt.pr "%a@.@." Fmt.(styled `Faint string) (String.make 60 '-');
+
List.iter (fun cat_id ->
+
match River.State.get_category state ~id:cat_id with
+
Fmt.(styled `Bold (styled (`Fg `Blue) string)) cat_id
+
Fmt.(styled `Green string) (River.Category.name cat)
+
Fmt.(styled `Bold (styled (`Fg `Blue) string)) cat_id
+
Fmt.(styled `Faint string) "(category not found)"
+
(* Cmdliner terms for category commands *)
+
Term.(const (fun env _xdg _profile ->
+
let state = River.State.create env ~app_name:"river" in
+
let id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"ID" ~doc:"Category ID (e.g., 'ocaml-projects')") in
+
let name_arg = Arg.(required & pos 1 (some string) None & info [] ~docv:"NAME" ~doc:"Category display name") in
+
let description_arg = Arg.(value & opt (some string) None & info ["d"; "description"] ~docv:"DESC" ~doc:"Category description") in
+
Term.(const (fun id name description env _xdg _profile ->
+
let state = River.State.create env ~app_name:"river" in
+
Category.add state ~id ~name ?description ()
+
) $ id_arg $ name_arg $ description_arg)
+
let id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"ID" ~doc:"Category ID to remove") in
+
Term.(const (fun id env _xdg _profile ->
+
let state = River.State.create env ~app_name:"river" in
+
Category.remove state ~id
+
let id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"ID" ~doc:"Category ID to show") in
+
Term.(const (fun id env _xdg _profile ->
+
let state = River.State.create env ~app_name:"river" in
+
Category.show state ~id
+
let doc = "Manage custom categories" in
+
let info = Cmd.info "category" ~doc in
+
let list_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "list" ~doc:"List all categories") ~app_name:"river" ~service:"river" category_list in
+
let add_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "add" ~doc:"Add a new category") ~app_name:"river" ~service:"river" category_add in
+
let remove_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "remove" ~doc:"Remove a category") ~app_name:"river" ~service:"river" category_remove in
+
let show_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "show" ~doc:"Show category details") ~app_name:"river" ~service:"river" category_show in
+
Cmd.group info [list_cmd; add_cmd; remove_cmd; show_cmd]
+
(* Cmdliner terms for post category tagging commands *)
+
let post_id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"POST_ID" ~doc:"Post ID to tag") in
+
let category_id_arg = Arg.(required & pos 1 (some string) None & info [] ~docv:"CATEGORY_ID" ~doc:"Category ID") in
+
Term.(const (fun post_id category_id env _xdg _profile ->
+
let state = River.State.create env ~app_name:"river" in
+
Post_category.add state ~post_id ~category_id
+
) $ post_id_arg $ category_id_arg)
+
let post_id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"POST_ID" ~doc:"Post ID") in
+
let category_id_arg = Arg.(required & pos 1 (some string) None & info [] ~docv:"CATEGORY_ID" ~doc:"Category ID to remove") in
+
Term.(const (fun post_id category_id env _xdg _profile ->
+
let state = River.State.create env ~app_name:"river" in
+
Post_category.remove state ~post_id ~category_id
+
) $ post_id_arg $ category_id_arg)
+
let post_id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"POST_ID" ~doc:"Post ID") in
+
Term.(const (fun post_id env _xdg _profile ->
+
let state = River.State.create env ~app_name:"river" in
+
Post_category.list state ~post_id
+
let doc = "Manage post category tags" in
+
let info = Cmd.info "tag" ~doc in
+
let add_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "add" ~doc:"Tag a post with a category") ~app_name:"river" ~service:"river" tag_add in
+
let remove_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "remove" ~doc:"Remove a category from a post") ~app_name:"river" ~service:"river" tag_remove in
+
let list_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "list" ~doc:"List categories for a post") ~app_name:"river" ~service:"river" tag_list in
+
Cmd.group info [add_cmd; remove_cmd; list_cmd]
let doc = "River feed management CLI" in
let main_info = Cmd.info "river-cli" ~version:"1.0" ~doc in
···
+
Cmd.group main_info [user_cmd; sync_cmd; list_cmd; info_cmd; merge_cmd; html_cmd; category_cmd; tag_cmd]