Richard W.M. Jones
2023-Oct-10 14:06 UTC
[Libguestfs] [PATCH libnbd 0/4] Miscellaneous Rust cleanups
Add an overview libnbd-rust(3) man page pointing to the real documentation. This is like OCaml & Golang. When reviewing the real rustdocs I noticed they basically converted the man pages into plain text, resulting in lots of problems such as internal links not working, no `code` annotations, etc. So I wrote a simple POD to rustdoc translator. It is by no means perfect, but it fixes many of the issues. Also build the examples, to make sure we don't get any regressions. Rich.
Richard W.M. Jones
2023-Oct-10 14:06 UTC
[Libguestfs] [PATCH libnbd 1/4] rust: Annotate 'endif' with corresponding label
Although not required, it makes automake 'Makefile.am's easier to read. --- rust/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/Makefile.am b/rust/Makefile.am index 027097af9b..75738a0c30 100644 --- a/rust/Makefile.am +++ b/rust/Makefile.am @@ -117,6 +117,6 @@ clean-local: $(CARGO) clean $(CARGO) clean --manifest-path cargo_test/Cargo.toml -endif +endif HAVE_RUST CLEANFILES += libnbd-sys/libnbd_version -- 2.41.0
Richard W.M. Jones
2023-Oct-10 14:06 UTC
[Libguestfs] [PATCH libnbd 2/4] rust: Add overview documentation
--- .gitignore | 1 + docs/libnbd.pod | 4 ++++ rust/Makefile.am | 13 +++++++++++ rust/libnbd-rust.pod | 53 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/.gitignore b/.gitignore index 36bf8b60f8..0b1cf7646a 100644 --- a/.gitignore +++ b/.gitignore @@ -180,6 +180,7 @@ Makefile.in /python/run-python-tests /run /rust/Cargo.lock +/rust/libnbd-rust.3 /rust/libnbd-sys/Cargo.lock /rust/libnbd-sys/libnbd_version /rust/libnbd-sys/src/generated.rs diff --git a/docs/libnbd.pod b/docs/libnbd.pod index f15ea6403d..2fc78212a5 100644 --- a/docs/libnbd.pod +++ b/docs/libnbd.pod @@ -51,6 +51,10 @@ Using the API from OCaml. Using the API from Go. +=item L<libnbd-rust(3)> + +Using the API from Rust. + =item L<nbdsh(1)> Using the NBD shell (nbdsh) for command line and Python scripting. diff --git a/rust/Makefile.am b/rust/Makefile.am index 75738a0c30..5c73512c87 100644 --- a/rust/Makefile.am +++ b/rust/Makefile.am @@ -84,6 +84,7 @@ source_files = \ EXTRA_DIST = \ $(source_files) \ + libnbd-rust.pod \ $(NULL) if HAVE_RUST @@ -102,6 +103,18 @@ target/debug/liblibnbd.rlib: $(source_files) target/doc/libnbd/index.html: $(source_files) $(abs_top_builddir)/run $(CARGO) doc +if HAVE_POD + +man_MANS = libnbd-rust.3 +CLEANFILES += $(man_MANS) + +libnbd-rust.3: libnbd-rust.pod $(top_builddir)/podwrapper.pl + $(PODWRAPPER) --section=3 --man $@ \ + --html $(top_builddir)/html/$@.html \ + $< + +endif HAVE_POD + TESTS_ENVIRONMENT = \ LIBNBD_DEBUG=1 \ $(MALLOC_CHECKS) \ diff --git a/rust/libnbd-rust.pod b/rust/libnbd-rust.pod new file mode 100644 index 0000000000..622f0b4ac5 --- /dev/null +++ b/rust/libnbd-rust.pod @@ -0,0 +1,53 @@ +=head1 NAME + +libnbd-rust - how to use libnbd from Rust + +=head1 SYNOPSIS + + let nbd = libnbd::Handle::new().unwrap(); + nbd.connect_uri("nbd://localhost").unwrap(); + let size = nbd.get_size().unwrap(); + println!("{size} bytes"); + +In C<Cargo.toml> add: + + [dependencies] + libnbd = VERSION | { path = "libnbd/rust" } + +=head1 DESCRIPTION + +This manual page documents how to use libnbd to access Network Block +Device (NBD) servers from the Rust programming language. + +The Rust bindings work very similarly to the C bindings so you should +start by reading L<libnbd(3)>. + +There is also a higher level asynchronous API using Tokio. + +If you build libnbd from source, the main documentation can be found +in F<libnbd/rust/target/doc/libnbd/index.html> + +For the ordinary interface, start by reading the documentation for +C<Handle>. For the higher level asynchronous API, start by reading +C<AsyncHandle>. + +C<libnbd-sys> is a very low level wrapper around the libnbd API which +should not be used directly. + +=head1 EXAMPLES + +This directory contains examples written in Rust: + +L<https://gitlab.com/nbdkit/libnbd/tree/master/rust/examples> + +=head1 SEE ALSO + +L<libnbd(3)>. + +=head1 AUTHORS + +Tage Johansson + +=head1 COPYRIGHT + +Copyright Tage Johansson -- 2.41.0
Richard W.M. Jones
2023-Oct-10 14:06 UTC
[Libguestfs] [PATCH libnbd 3/4] rust: Write a custom translator from POD to rustdoc
For more faithful translation of API documentation, write a custom translator from POD to rustdoc. --- generator/Rust.ml | 99 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/generator/Rust.ml b/generator/Rust.ml index 302bd4c48b..fd703f0493 100644 --- a/generator/Rust.ml +++ b/generator/Rust.ml @@ -476,27 +476,88 @@ let pr "\n" (* Print the comment for a rust function for a handle call. *) -let print_rust_handle_call_comment call +let rec print_rust_handle_call_comment name call (* Print comments. *) if call.shortdesc <> "" then pr "/// %s\n" (String.concat "\n/// " (String.split_on_char '\n' call.shortdesc)); if call.longdesc <> "" then ( - (* If a short comment was printed, print a blank comment line befor the - long description. *) + (* If a short comment was printed, print a blank comment line before + the long description. *) if call.shortdesc <> "" then pr "/// \n"; - (* Print all lines of the long description. Since Rust comments are - supposed to be Markdown, all indented lines will be treated as code - blocks. Hence we trim all lines. Also brackets ("[" and "]") must be - escaped. *) - List.iter - (fun line -> - let unindented = String.trim line in - let escaped - Str.global_replace (Str.regexp {|\(\[\|\]\)|}) {|\\\1|} unindented - in - pr "/// %s\n" escaped) - (pod2text call.longdesc)) + let md = longdesc_to_markdown name call.longdesc in + List.iter (pr "/// %s\n") md + ) + +(* Convert POD to rustdoc markdown. *) +and longdesc_to_markdown name longdesc + (* Replace any POD <> expression *) + let content + Str.global_substitute (Str.regexp {|[A-Z]<[^>]+?>|}) + (fun s -> + let expr = Str.matched_string s in + let len = String.length expr in + let c = expr.[0] and content = String.sub expr 2 (len-3) in + match c with + | 'C' -> sprintf "`%s`" content (* C<...> becomes `...` *) + | 'B' -> sprintf "<b>%s</b>" content + | 'I' | 'F' -> sprintf "<i>%s</i>" content + | 'E' -> sprintf "&%s;" content + | 'L' -> + let len = String.length content in + if String.starts_with ~prefix:"nbd_" content then ( + let n = String.sub content 4 (len - 7) in + if n <> "get_error" && n <> "get_errno" && n <> "close" then + sprintf "[%s](Handle::%s)" n n + else + sprintf "`%s`" n + ) + else (* external manual page - how to link XXX *) + sprintf "<i>%s</i>" content + | _ -> + failwithf "rust: API documentation for %s contains '%s' which + cannot be converted to Rust markdown" name expr + ) + longdesc in + + (* Split input into lines for rest of the processing. *) + let lines = nsplit "\n" content in + + (* Surround any group of lines starting with whitespace with ```text *) + let lines + List.map (fun line -> String.starts_with ~prefix:" " line, line) lines in + let (lines : (bool * string list) list) = group_by lines in + let lines + List.map (function + | true (* verbatim *), lines -> [ "```text" ] @ lines @ [ "```" ] + | false, lines -> lines + ) lines in + let lines = List.flatten lines in + + (* Replace any = directives *) + filter_map ( + fun s -> + (* This is a very approximate way to translate bullet lists. *) + if String.starts_with ~prefix:"=over" s || + String.starts_with ~prefix:"=back" s then + None + else if String.starts_with ~prefix:"=item" s then ( + let len = String.length s in + let s' = String.sub s 5 (len-5) in + Some ("-" ^ s') + ) + else if String.starts_with ~prefix:"=head" s then ( + let i = int_of_string (String.make 1 s.[5]) in + let len = String.length s in + let s' = String.sub s 6 (len-6) in + Some (String.make i '#' ^ s') + ) + else if String.starts_with ~prefix:"=" s then + failwithf "rust: API documentation for %s contains '%s' which + cannot be converted to Rust markdown" name s + else + Some s + ) lines (* Print a Rust expression which converts Rust like arguments to FFI like arguments, makes a call on the raw FFI handle, and converts the return @@ -533,7 +594,7 @@ let String.concat ", " (List.map2 (sprintf "%s: %s") rust_args_names rust_args_types) in - print_rust_handle_call_comment call; + print_rust_handle_call_comment name call; (* Print visibility modifier. *) if NameSet.mem name hidden_handle_calls then ( (* If this is hidden to the public API, it might be used only if some feature @@ -653,7 +714,7 @@ let (* Print the Rust function for a synchronous handle call. *) let print_rust_sync_handle_call name call - print_rust_handle_call_comment call; + print_rust_handle_call_comment name call; pr "pub fn %s(&self, %s) -> %s\n" name (rust_async_handle_call_args call) (rust_ret_type call); @@ -698,7 +759,7 @@ let let optargs_without_completion_cb optargs_before_completion_cb @ optargs_after_completion_cb in - print_rust_handle_call_comment call; + print_rust_handle_call_comment name call; pr "pub async fn %s(&self, %s) -> SharedResult<()> {\n" name (rust_async_handle_call_args { call with optargs = optargs_without_completion_cb }); @@ -747,7 +808,7 @@ let let print_rust_async_handle_call_changing_state name aio_name call (predicate, value) let value = if value then "true" else "false" in - print_rust_handle_call_comment call; + print_rust_handle_call_comment name call; pr "pub async fn %s(&self, %s) -> SharedResult<()>\n" name (rust_async_handle_call_args call); pr "{\n"; -- 2.41.0
Richard W.M. Jones
2023-Oct-10 14:06 UTC
[Libguestfs] [PATCH libnbd 4/4] rust: Build the examples
It's worth checking that the examples actually build, so we don't get any regressions. --- rust/Makefile.am | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rust/Makefile.am b/rust/Makefile.am index 5c73512c87..a7700d69bd 100644 --- a/rust/Makefile.am +++ b/rust/Makefile.am @@ -90,7 +90,8 @@ EXTRA_DIST = \ if HAVE_RUST all-local: libnbd-sys/libnbd_version target/debug/liblibnbd.rlib \ - target/doc/libnbd/index.html + target/doc/libnbd/index.html \ + target/debug/examples/get-size libnbd-sys/libnbd_version: Makefile rm -f libnbd-sys/libnbd_version.t @@ -103,6 +104,10 @@ target/debug/liblibnbd.rlib: $(source_files) target/doc/libnbd/index.html: $(source_files) $(abs_top_builddir)/run $(CARGO) doc +# This will actually build all the examples: +target/debug/examples/get-size: $(source_files) + $(abs_top_builddir)/run $(CARGO) build --examples + if HAVE_POD man_MANS = libnbd-rust.3 -- 2.41.0
Eric Blake
2023-Oct-18 14:22 UTC
[Libguestfs] [PATCH libnbd 0/4] Miscellaneous Rust cleanups
On Tue, Oct 10, 2023 at 03:06:06PM +0100, Richard W.M. Jones wrote:> Add an overview libnbd-rust(3) man page pointing to the real > documentation. This is like OCaml & Golang. > > When reviewing the real rustdocs I noticed they basically converted > the man pages into plain text, resulting in lots of problems such as > internal links not working, no `code` annotations, etc. So I wrote a > simple POD to rustdoc translator. It is by no means perfect, but it > fixes many of the issues. > > Also build the examples, to make sure we don't get any regressions.For the series: Reviewed-by: Eric Blake <eblake at redhat.com> There is an XXX about displying links rather than just italicized texts for pod L<> constructs linking to non-libnbd pages, which can be solved later, but your approach for now is definitely better than plain text. -- Eric Blake, Principal Software Engineer Red Hat, Inc. Virtualization: qemu.org | libguestfs.org