Skip to content

Commit

Permalink
Add OPML file generation
Browse files Browse the repository at this point in the history
  • Loading branch information
xvw committed Jul 30, 2024
1 parent a04833f commit 76b016f
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 13 deletions.
26 changes: 24 additions & 2 deletions bin/ring.ml
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
open Yocaml

module Resolver = Gem.Resolver.Make (struct
let source = Path.rel []
let target = Path.rel [ "_www" ]
end)

let final_message _cache = Eff.log ~level:`Debug "ring.muhokama done"

let generate_opml chain =
Action.write_static_file Resolver.Target.ring_opml
(let open Task in
Pipeline.track_files
(Resolver.Source.members :: Resolver.Source.common_deps)
>>> const chain
>>> Gem.Chain.to_opml)

let process_all () =
let open Yocaml.Eff in
let open Eff in
let* cache, chain = Gem.Action.init_chain (module Resolver) in
return cache
>>= generate_opml chain
>>= Action.store_cache Resolver.Target.cache
>>= final_message

let () = print_endline "Hello World"
let () = Yocaml_eio.run ~level:`Debug process_all
2 changes: 2 additions & 0 deletions data/chain.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- xvw
- xhtmlboy
13 changes: 13 additions & 0 deletions data/members/xhtmlboy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
id: xhtmlboy
display_name: The XHTMLBoy
bio:
I am passionate about computer development and this site/blog will serve as a space to
document my attempts to become a Java Champion or Google Developer Expert. Apart from
software design and (valid) XHTML, I have a particular passion for everything related to
the concept of muscles. I love muscular people.
main_link:
url: https://xhtmlboi.github.io
lang: eng
main_feed:
url: https://xhtmlboi.github.io/feed.xml
lang: eng
11 changes: 11 additions & 0 deletions data/members/xvw.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
id: xvw
display_name: Xavier Van de Woestyne
bio: I'm a Belgian developer living in France (Nantes), very interested
in statically typed functional programming.
location: France, Nantes
main_link:
url: https://xvw.lol
lang: fra
main_feed:
url: https://xvw.lol/atom.xml
lang: fra
22 changes: 22 additions & 0 deletions lib/action.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
let init_chain (module R : Sigs.RESOLVER) =
let open Yocaml.Eff in
let* cache = Yocaml.Action.restore_cache ~on:`Target R.Target.cache in
let* chain =
Yocaml_yaml.Eff.read_file_as_metadata
(module Model.Chain)
~on:`Source R.Source.chain
in
let+ cache, members =
Yocaml.Action.fold ~only:`Files
~where:(Yocaml.Path.has_extension "yml")
~state:[] R.Source.members
(fun path state cache ->
let+ member =
Yocaml_yaml.Eff.read_file_as_metadata
(module Model.Member)
~on:`Source path
in
(cache, member :: state))
cache
in
(cache, Chain.init ~chain ~members)
5 changes: 5 additions & 0 deletions lib/chain.ml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,8 @@ let fold f start chain =
List.fold_left
(fun acc { pred; curr; succ } -> f acc ~pred ~curr ~succ)
start chain

let to_opml =
let open Yocaml.Task in
List.concat_map (fun { curr; _ } -> Model.Member.to_outline curr)
|>> Yocaml_syndication.Opml.opml2_from ~title:"ring.muhokama.fun" ()
3 changes: 3 additions & 0 deletions lib/chain.mli
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ val fold :
(** [fold f default chain] Traverses all the elements in the chain by applying a
function that takes the predecessor, the current member, the successor and
the current state. *)

val to_opml : (t, string) Yocaml.Task.t
(** [to_opml] An arrow that lift a chain into an OPML file. *)
2 changes: 1 addition & 1 deletion lib/dune
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
(public_name ring.gem)
(synopsis "The name [gem] is because it's set with a ring. lol")
(modules_without_implementation sigs)
(libraries yocaml yocaml_yaml))
(libraries yocaml yocaml_yaml yocaml_syndication))
38 changes: 38 additions & 0 deletions lib/model.ml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ module Link = struct
String.equal title_a title_b
&& Lang.equal lang_a lang_b
&& Url.equal url_a url_b

let title (title, _, _) = title
let lang (_, lang, _) = lang
let url (_, _, url) = url
end

module Link_table = struct end
Expand All @@ -131,6 +135,10 @@ module Member = struct
}

let id { id; _ } = id

let display_name { id; display_name; _ } =
Option.value ~default:id display_name

let entity_name = "Member"
let neutral = Yocaml.Metadata.required entity_name
let validate_id = Yocaml.(Data.Validation.(Slug.validate & minimal_length 2))
Expand Down Expand Up @@ -230,6 +238,36 @@ module Member = struct
&& List.equal Link.equal additional_links other.additional_links
&& List.equal Link.equal additional_feeds other.additional_feeds
&& Option.equal String.equal location other.location

let feed_to_outline ?main_link title description feed_url =
Yocaml_syndication.Opml.subscription
~language:(feed_url |> Link.lang |> Lang.to_string)
~title ~description
?html_url:
(main_link |> Option.map (fun x -> x |> Link.url |> Url.to_string))
~feed_url:(feed_url |> Link.url |> Url.to_string)
()

let to_outline member =
let display_name = display_name member in
let main_feed =
let description = "Main feed of " ^ display_name in
let title = member.main_link |> Link.title in
member.main_feed
|> Option.map
(feed_to_outline ~main_link:member.main_link title description)
|> Option.to_list
in
let additional_feeds =
member.additional_feeds
|> List.mapi (fun index feed ->
let title = feed |> Link.title in
let description =
"Additional feed " ^ string_of_int index ^ "of " ^ display_name
in
feed_to_outline title description feed)
in
main_feed @ additional_feeds
end

module Chain = struct
Expand Down
5 changes: 5 additions & 0 deletions lib/model.mli
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ module Member : sig

val id : t -> string
(** Returns the id of a member. *)

(** {1 OPML generation} *)

val to_outline : t -> Yocaml_syndication.Opml.outline list
(** [to_outline m] transform a member to a list of OPML outlines. *)
end

module Chain : sig
Expand Down
9 changes: 6 additions & 3 deletions lib/resolver.ml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ module Make (R : Sigs.RESOLVABLE) = struct

module Source = struct
let root = R.source
let binary = Path.rel [ Sys.argv.(0) ]
let data = Path.(R.source / "data")
let static = Path.(R.source / "static")
let css = Path.(static / "css")
let templates = Path.(static / "templates")
let template file = Path.(templates / file)
let members = Path.(data / "members")
let chain = Path.(data / "chain.yml")
let common_deps = [ binary; chain ]
end

module Target = struct
let root = R.target
let cache = Path.(R.target / "cache")
let data = Path.(R.target / "data")
let members = Path.(data / "members")
let chain = Path.(data / "chain.yml")
let opml = Path.(R.target / "opml")
let ring_opml = Path.(opml / "ring.opml")
end
end
23 changes: 16 additions & 7 deletions lib/sigs.mli
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ module type RESOLVER = sig
val root : Yocaml.Path.t
(** [R.Source.root] is [R.source]. *)

val binary : Yocaml.Path.t
(** Resolve the binary. *)

val common_deps : Yocaml.Path.t list
(** A list of default dependencies. *)

val data : Yocaml.Path.t
(** Resolve the [data-path] ([source / data]). *)

Expand All @@ -49,6 +55,12 @@ module type RESOLVER = sig
{b Warning} The function simply produces a path, there is no guarantee
that the template exists. *)

val members : Yocaml.Path.t
(** Resolve the members location. *)

val chain : Yocaml.Path.t
(** Resolve the chain enumeration location. *)
end

(** {1 Target}
Expand All @@ -64,13 +76,10 @@ module type RESOLVER = sig
val cache : Yocaml.Path.t
(** Resolve the cache location. *)

val data : Yocaml.Path.t
(** Resolve the data location. *)
val opml : Yocaml.Path.t
(** Resolve the OPML folder location. *)

val members : Yocaml.Path.t
(** Resolve the members location. *)

val chain : Yocaml.Path.t
(** Resolve the chain enumeration location. *)
val ring_opml : Yocaml.Path.t
(** Resolve the OPML file for member's feed. *)
end
end

0 comments on commit 76b016f

Please sign in to comment.