Skip to content

Commit

Permalink
A ring.exe cli based on Cmdliner
Browse files Browse the repository at this point in the history
  • Loading branch information
xvw committed Jul 30, 2024
1 parent 7596283 commit 7d61c73
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 8 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,29 @@ being development dependencies of the project).

## Run the binary `ring.exe`

Just run the `ring.exe` binary compiled from `bin/ring.ml` like this:
Just run the `ring.exe` binary compiled from `bin/ring.ml`, which should display
the `manpage`, giving information on how to interact with the binary, like this:

```shell
dune exec bin/ring.exe
```

Broadly speaking, here are the two main actions proposed by the `ring.exe`
binary:

- `dune exec bin/ring.exe` display the manpage of the binary
- `dune exec bin/ring.exe -- build [COMMON_OPTIONS]` builds the ring in `_www`
using the current directory as source
- `dune exec bin/ring.exe -- build [COMMON_OPTIONS] [--port PORT]` launches a
development server that rebuilds the ring each time a page is refreshed

The common options are:

- `--target PATH` describes the compilation target (the directory where the ring
is to be built)
- `--source PATH` describes the compilation source (the directory where the data of the ring are located)
- `--log-level (info | app | debug | error | warning)` describes the log-level

## Launching tests

The build procedure is based on `dune`, without any particular sorcery, so
Expand Down
2 changes: 1 addition & 1 deletion bin/dune
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(executable
(name ring)
(public_name ring)
(libraries yocaml yocaml_eio gem))
(libraries cmdliner yocaml yocaml_eio gem))
138 changes: 132 additions & 6 deletions bin/ring.ml
Original file line number Diff line number Diff line change
@@ -1,8 +1,134 @@
open Yocaml
let run_build target source log_level =
let module Resolver = Gem.Resolver.Make (struct
let source = source
let target = target
end) in
Yocaml_eio.run ~level:log_level (Gem.Action.process_all (module Resolver))

module Resolver = Gem.Resolver.Make (struct
let source = Path.rel []
let target = Path.rel [ "_www" ]
end)
let run_watch target source log_level port =
let module Resolver = Gem.Resolver.Make (struct
let source = source
let target = target
end) in
Yocaml_eio.serve ~target:Resolver.target ~level:log_level ~port
(Gem.Action.process_all (module Resolver))

let () = Yocaml_eio.run ~level:`Debug (Gem.Action.process_all (module Resolver))
open Cmdliner

let exits = Cmd.Exit.defaults
let version = "dev"

let path_conv =
Arg.conv ~docv:"PATH"
((fun str -> str |> Yocaml.Path.from_string |> Result.ok), Yocaml.Path.pp)

let port_conv =
Arg.conv' ~docv:"PORT"
( (fun str ->
match int_of_string_opt str with
| None -> Result.error (str ^ " is not a valid port value")
| Some x when x < 0 -> Result.error (str ^ " is < 0")
| Some x when x > 9999 -> Result.error (str ^ " is > 9999")
| Some x -> Result.ok x),
fun ppf -> Format.fprintf ppf "%04d" )

let log_level_conv =
Arg.conv ~docv:"LEVEL"
( (fun str ->
match String.(str |> trim |> lowercase_ascii) with
| "app" -> Result.ok `App
| "info" -> Result.ok `Info
| "error" -> Result.ok `Error
| "warning" -> Result.ok `Warning
| _ -> Result.ok `Debug),
fun ppf -> function
| `App -> Format.fprintf ppf "app"
| `Debug -> Format.fprintf ppf "debug"
| `Info -> Format.fprintf ppf "info"
| `Error -> Format.fprintf ppf "error"
| `Warning -> Format.fprintf ppf "warning" )

let target_arg =
let default = Yocaml.Path.rel [ "_www" ] in
let doc = "The directory where the ring will be built" in
let arg =
Arg.info ~doc ~docs:Manpage.s_common_options [ "target"; "output" ]
in
Arg.(value (opt path_conv default arg))

let source_arg =
let default = Yocaml.Path.rel [] in
let doc = "The directory used as source" in
let arg =
Arg.info ~doc ~docs:Manpage.s_common_options [ "source"; "input" ]
in
Arg.(value (opt path_conv default arg))

let port_arg =
let default = 8888 in
let doc = "The port used to serve the development server" in
let arg = Arg.info ~doc ~docs:Manpage.s_common_options [ "port"; "listen" ] in
Arg.(value (opt port_conv default arg))

let log_level_arg default =
let doc =
"The log-level (app | info | debug | error | warning), by default"
in
let arg = Arg.info ~doc ~docs:Manpage.s_common_options [ "log-level" ] in
Arg.(value (opt log_level_conv default arg))

let bug_report =
"The application's source code is published on \
<https://github.com/muhokama/ring>. Feel free to contribute or report bugs \
on <https://github.com/muhokama/ring/issues>."

let description =
"ring.muhokama is free software that lets you build a static site that \
describes a webring, in homage to the webrings of the 1990s, a return to \
the small-web."

let build =
let doc =
"Build the ring in a given TARGET, based on a given SOURCE with a given \
LOG_LEVEL"
in
let man =
[
`S Manpage.s_description; `P description; `S Manpage.s_bugs; `P bug_report;
]
in
let info = Cmd.info "build" ~version ~doc ~exits ~man in
let term =
Term.(const run_build $ target_arg $ source_arg $ log_level_arg `Debug)
in
Cmd.v info term

let watch =
let doc =
"Build the ring and launch the dev-server in a given TARGET, based on a \
given SOURCE with a given LOG_LEVEL listen to a dedicated PORT"
in
let man =
[
`S Manpage.s_description; `P description; `S Manpage.s_bugs; `P bug_report;
]
in
let info = Cmd.info "watch" ~version ~doc ~exits ~man in
let term =
Term.(
const run_watch $ target_arg $ source_arg $ log_level_arg `Info $ port_arg)
in
Cmd.v info term

let index =
let doc = "ring.muhokama" in
let man =
[
`S Manpage.s_description; `P description; `S Manpage.s_bugs; `P bug_report;
]
in
let info = Cmd.info "dune exec bin/ring.exe" ~version ~doc ~man in
let default = Term.(ret (const (`Help (`Pager, None)))) in
Cmd.group info ~default [ build; watch ]

let () = exit @@ Cmd.eval index
2 changes: 2 additions & 0 deletions dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
yocaml_eio
yocaml_syndication

(cmdliner (= 1.3.0))

(ppx_expect :with-test)

(ocamlformat :with-dev-setup)
Expand Down
5 changes: 5 additions & 0 deletions lib/action.ml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ let init_chain (module R : Sigs.RESOLVER) =
in
(cache, Chain.init ~chain ~members)

let init_message (module R : Sigs.RESOLVER) =
Yocaml.Eff.logf ~level:`Debug "ring.muhokama [source: `%a`, target: `%a`]"
Yocaml.Path.pp R.source Yocaml.Path.pp R.target

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

let generate_opml (module R : Sigs.RESOLVER) chain =
Expand All @@ -32,6 +36,7 @@ let generate_opml (module R : Sigs.RESOLVER) chain =

let process_all (module R : Sigs.RESOLVER) () =
let open Yocaml.Eff in
let* () = init_message (module R) in
let* cache, chain = init_chain (module R) in
return cache
>>= generate_opml (module R) chain
Expand Down
1 change: 1 addition & 0 deletions ring.opam
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ depends: [
"yocaml_omd"
"yocaml_eio"
"yocaml_syndication"
"cmdliner" {= "1.3.0"}
"ppx_expect" {with-test}
"ocamlformat" {with-dev-setup}
"ocp-indent" {with-dev-setup}
Expand Down

0 comments on commit 7d61c73

Please sign in to comment.