···
) $ output_dir_arg $ title_arg $ posts_per_page_arg)
609
+
(* Category management commands *)
610
+
module Category = struct
612
+
let categories = River.State.list_categories state in
613
+
if categories = [] then begin
615
+
Fmt.(styled `Yellow string)
616
+
"No categories defined yet. Use 'river category add' to create one."
619
+
Fmt.(styled `Bold (styled (`Fg `Cyan) string))
620
+
(Printf.sprintf "Categories (%d total)" (List.length categories));
621
+
Fmt.pr "%a@.@." Fmt.(styled `Faint string) (String.make 60 '-');
622
+
List.iter (fun cat ->
624
+
Fmt.(styled `Bold (styled (`Fg `Blue) string)) (River.Category.id cat)
625
+
Fmt.(styled `Green string) (River.Category.name cat);
626
+
(match River.Category.description cat with
629
+
Fmt.(styled `Faint string) "└─"
630
+
Fmt.(styled `Faint string) desc
637
+
let add state ~id ~name ?description () =
638
+
let category = River.Category.create ~id ~name ?description () in
639
+
match River.State.add_category state category with
641
+
Fmt.pr "@.%a Category %a added successfully@.@."
642
+
Fmt.(styled (`Fg `Green) string) "✓"
643
+
Fmt.(styled `Bold string) id;
646
+
Fmt.pr "@.%a Failed to add category: %s@.@."
647
+
Fmt.(styled (`Fg `Red) string) "✗"
651
+
let remove state ~id =
652
+
match River.State.remove_category state ~id with
654
+
Fmt.pr "@.%a Category %a removed successfully@.@."
655
+
Fmt.(styled (`Fg `Green) string) "✓"
656
+
Fmt.(styled `Bold string) id;
659
+
Fmt.pr "@.%a Failed to remove category: %s@.@."
660
+
Fmt.(styled (`Fg `Red) string) "✗"
664
+
let show state ~id =
665
+
match River.State.get_category state ~id with
667
+
Fmt.pr "@.%a Category %a not found@.@."
668
+
Fmt.(styled (`Fg `Red) string) "✗ Error:"
669
+
Fmt.(styled `Bold string) id;
673
+
Fmt.(styled `Bold (styled (`Fg `Cyan) string))
674
+
(Printf.sprintf "Category: %s" (River.Category.id cat));
675
+
Fmt.pr "%a@.@." Fmt.(styled `Faint string) (String.make 60 '-');
677
+
Fmt.(styled `Faint string) "Name: "
678
+
Fmt.(styled `Green string) (River.Category.name cat);
680
+
Fmt.(styled `Faint string) "Description:"
681
+
Fmt.string (Option.value (River.Category.description cat) ~default:"(none)");
683
+
let post_ids = River.State.get_posts_by_category state ~category_id:id in
685
+
Fmt.(styled `Bold string)
686
+
(Printf.sprintf "Posts (%d)" (List.length post_ids));
687
+
Fmt.pr "%a@." Fmt.(styled `Faint string) (String.make 60 '-');
688
+
if post_ids = [] then
690
+
Fmt.(styled `Faint string)
691
+
" No posts tagged with this category."
693
+
List.iter (fun post_id ->
695
+
Fmt.(styled `Faint string) "•"
696
+
Fmt.(styled (`Fg `Blue) string) post_id
703
+
(* Post category tagging commands *)
704
+
module Post_category = struct
705
+
let add state ~post_id ~category_id =
706
+
match River.State.add_post_category state ~post_id ~category_id with
708
+
Fmt.pr "@.%a Post %a tagged with category %a@.@."
709
+
Fmt.(styled (`Fg `Green) string) "✓"
710
+
Fmt.(styled `Bold string) post_id
711
+
Fmt.(styled `Bold string) category_id;
714
+
Fmt.pr "@.%a Failed to tag post: %s@.@."
715
+
Fmt.(styled (`Fg `Red) string) "✗"
719
+
let remove state ~post_id ~category_id =
720
+
match River.State.remove_post_category state ~post_id ~category_id with
722
+
Fmt.pr "@.%a Category %a removed from post %a@.@."
723
+
Fmt.(styled (`Fg `Green) string) "✓"
724
+
Fmt.(styled `Bold string) category_id
725
+
Fmt.(styled `Bold string) post_id;
728
+
Fmt.pr "@.%a Failed to remove category from post: %s@.@."
729
+
Fmt.(styled (`Fg `Red) string) "✗"
733
+
let list state ~post_id =
734
+
let category_ids = River.State.get_post_categories state ~post_id in
735
+
if category_ids = [] then begin
737
+
Fmt.(styled `Yellow string)
738
+
(Printf.sprintf "Post %s has no categories assigned." post_id)
741
+
Fmt.(styled `Bold (styled (`Fg `Cyan) string))
742
+
(Printf.sprintf "Categories for post: %s" post_id);
743
+
Fmt.pr "%a@.@." Fmt.(styled `Faint string) (String.make 60 '-');
744
+
List.iter (fun cat_id ->
745
+
match River.State.get_category state ~id:cat_id with
748
+
Fmt.(styled `Bold (styled (`Fg `Blue) string)) cat_id
749
+
Fmt.(styled `Green string) (River.Category.name cat)
752
+
Fmt.(styled `Bold (styled (`Fg `Blue) string)) cat_id
753
+
Fmt.(styled `Faint string) "(category not found)"
759
+
(* Cmdliner terms for category commands *)
760
+
let category_list =
761
+
Term.(const (fun env _xdg _profile ->
762
+
let state = River.State.create env ~app_name:"river" in
763
+
Category.list state
767
+
let id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"ID" ~doc:"Category ID (e.g., 'ocaml-projects')") in
768
+
let name_arg = Arg.(required & pos 1 (some string) None & info [] ~docv:"NAME" ~doc:"Category display name") in
769
+
let description_arg = Arg.(value & opt (some string) None & info ["d"; "description"] ~docv:"DESC" ~doc:"Category description") in
770
+
Term.(const (fun id name description env _xdg _profile ->
771
+
let state = River.State.create env ~app_name:"river" in
772
+
Category.add state ~id ~name ?description ()
773
+
) $ id_arg $ name_arg $ description_arg)
775
+
let category_remove =
776
+
let id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"ID" ~doc:"Category ID to remove") in
777
+
Term.(const (fun id env _xdg _profile ->
778
+
let state = River.State.create env ~app_name:"river" in
779
+
Category.remove state ~id
782
+
let category_show =
783
+
let id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"ID" ~doc:"Category ID to show") in
784
+
Term.(const (fun id env _xdg _profile ->
785
+
let state = River.State.create env ~app_name:"river" in
786
+
Category.show state ~id
790
+
let doc = "Manage custom categories" in
791
+
let info = Cmd.info "category" ~doc in
792
+
let list_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "list" ~doc:"List all categories") ~app_name:"river" ~service:"river" category_list in
793
+
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
794
+
let remove_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "remove" ~doc:"Remove a category") ~app_name:"river" ~service:"river" category_remove in
795
+
let show_cmd = Eiocmd.run ~use_keyeio:false ~info:(Cmd.info "show" ~doc:"Show category details") ~app_name:"river" ~service:"river" category_show in
796
+
Cmd.group info [list_cmd; add_cmd; remove_cmd; show_cmd]
798
+
(* Cmdliner terms for post category tagging commands *)
800
+
let post_id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"POST_ID" ~doc:"Post ID to tag") in
801
+
let category_id_arg = Arg.(required & pos 1 (some string) None & info [] ~docv:"CATEGORY_ID" ~doc:"Category ID") in
802
+
Term.(const (fun post_id category_id env _xdg _profile ->
803
+
let state = River.State.create env ~app_name:"river" in
804
+
Post_category.add state ~post_id ~category_id
805
+
) $ post_id_arg $ category_id_arg)
808
+
let post_id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"POST_ID" ~doc:"Post ID") in
809
+
let category_id_arg = Arg.(required & pos 1 (some string) None & info [] ~docv:"CATEGORY_ID" ~doc:"Category ID to remove") in
810
+
Term.(const (fun post_id category_id env _xdg _profile ->
811
+
let state = River.State.create env ~app_name:"river" in
812
+
Post_category.remove state ~post_id ~category_id
813
+
) $ post_id_arg $ category_id_arg)
816
+
let post_id_arg = Arg.(required & pos 0 (some string) None & info [] ~docv:"POST_ID" ~doc:"Post ID") in
817
+
Term.(const (fun post_id env _xdg _profile ->
818
+
let state = River.State.create env ~app_name:"river" in
819
+
Post_category.list state ~post_id
823
+
let doc = "Manage post category tags" in
824
+
let info = Cmd.info "tag" ~doc in
825
+
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
826
+
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
827
+
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
828
+
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
···
652
-
Cmd.group main_info [user_cmd; sync_cmd; list_cmd; info_cmd; merge_cmd; html_cmd]
873
+
Cmd.group main_info [user_cmd; sync_cmd; list_cmd; info_cmd; merge_cmd; html_cmd; category_cmd; tag_cmd]