From 42f92db0ab0b9bc8595e734bdc18bbcc8a515ca5 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sat, 2 Apr 2022 16:29:12 +0100 Subject: [PATCH] v.doc: parse multi-line examples (so they get highlighted) (#13894) --- vlib/arrays/arrays.v | 4 ++-- vlib/builtin/string.v | 9 +++++---- vlib/v/doc/comment.v | 16 ++++++++++++--- vlib/v/doc/node.v | 45 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 11 deletions(-) diff --git a/vlib/arrays/arrays.v b/vlib/arrays/arrays.v index 1fffd5fd42..dc099714eb 100644 --- a/vlib/arrays/arrays.v +++ b/vlib/arrays/arrays.v @@ -175,7 +175,7 @@ pub struct WindowAttribute { // - `size` - snapshot size // - `step` - gap size between each snapshot, default is 1. // -// Example: arrays.window([1, 2, 3, 4], size: 2) => [[1, 2], [2, 3], [3, 4]] +// Example: arrays.window([1, 2, 3, 4], size: 2) // => [[1, 2], [2, 3], [3, 4]] // Example: arrays.window([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], size: 3, step: 2) // => [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9]] pub fn window(list []T, attr WindowAttribute) [][]T { // allocate snapshot array @@ -394,7 +394,7 @@ pub fn binary_search(arr []T, target T) ?int { // Example: // ```v // mut x := [1,2,3,4,5,6] -// arrays.rotate_left(mut x,2) +// arrays.rotate_left(mut x, 2) // println(x) // [3, 4, 5, 6, 1, 2] // ``` pub fn rotate_left(mut arr []T, mid int) { diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index 293b48bcca..70bbd013dc 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -555,7 +555,6 @@ pub fn (s string) u64() u64 { // This method directly exposes the `parse_uint` function from `strconv` // as a method on `string`. For more advanced features, // consider calling `strconv.common_parse_uint` directly. -// Example: pub fn (s string) parse_uint(_base int, _bit_size int) ?u64 { return strconv.parse_uint(s, _base, _bit_size) } @@ -1823,13 +1822,15 @@ pub fn (s string) fields() []string { // the value in ``. // // Example: +// ```v // st := 'Hello there, // |this is a string, // | Everything before the first | is removed'.strip_margin() -// Returns: -// Hello there, +// +// assert st == 'Hello there, // this is a string, -// Everything before the first | is removed +// Everything before the first | is removed' +// ``` pub fn (s string) strip_margin() string { return s.strip_margin_custom(`|`) } diff --git a/vlib/v/doc/comment.v b/vlib/v/doc/comment.v index 5a870b258a..57019004e0 100644 --- a/vlib/v/doc/comment.v +++ b/vlib/v/doc/comment.v @@ -13,13 +13,23 @@ pub mut: pos token.Pos } -// is_example returns true if the contents of this comment is a doc example. +// is_example returns true if the contents of this comment is an inline doc example. // The current convention is '// Example: ' pub fn (dc DocComment) is_example() bool { - return dc.text.starts_with(doc.example_pattern) + return dc.text.trim_space().starts_with(doc.example_pattern) } -// example returns the content of the example body +// example returns the content of the inline example body pub fn (dc DocComment) example() string { return dc.text.all_after(doc.example_pattern) } + +// is_multi_line_example returns true if an example line has no inline code +pub fn (dc DocComment) is_multi_line_example() bool { + return dc.text.trim_space() == '\x01 Example:' +} + +// has_triple_backtick returns true if the comment starts or ends a markdown code block +pub fn (dc DocComment) has_triple_backtick() bool { + return dc.text.starts_with('\x01 ```') +} diff --git a/vlib/v/doc/node.v b/vlib/v/doc/node.v index 736067f276..2e72997a84 100644 --- a/vlib/v/doc/node.v +++ b/vlib/v/doc/node.v @@ -54,16 +54,57 @@ pub fn (dc DocNode) merge_comments() string { // merge_comments_without_examples returns a `string` with the // combined contents of `DocNode.comments` - excluding any examples. pub fn (dc DocNode) merge_comments_without_examples() string { - sans_examples := dc.comments.filter(!it.is_example()) + mut sans_examples := []DocComment{cap: dc.comments.len} + for i := 0; i < dc.comments.len; i++ { + if dc.comments[i].is_example() { + continue + } + if dc.comments[i].is_multi_line_example() { + i++ + if i == dc.comments.len || !dc.comments[i].has_triple_backtick() { + eprintln('$dc.file_path:$dc.pos.line_nr: Expected code block after empty example line:') + eprintln('// ```') + if i < dc.comments.len { + eprintln('Found:') + eprintln('//' + dc.comments[i].text[1..]) + } + exit(1) + } + i++ + for i < dc.comments.len && !dc.comments[i].has_triple_backtick() { + i++ + } + } else { + sans_examples << dc.comments[i] + } + } return merge_doc_comments(sans_examples) } // examples returns a `[]string` containing examples parsed from `DocNode.comments`. pub fn (dn DocNode) examples() []string { mut output := []string{} - for comment in dn.comments { + for i := 0; i < dn.comments.len; i++ { + comment := dn.comments[i] if comment.is_example() { output << comment.example() + } else if comment.is_multi_line_example() { + i++ + if i + 2 < dn.comments.len && dn.comments[i].has_triple_backtick() { + i++ + mut ml_ex := '' + for i < dn.comments.len && !dn.comments[i].has_triple_backtick() { + if ml_ex.len > 0 { + ml_ex += '\n' + } + s := dn.comments[i].text + if s.len > 2 { + ml_ex += s[2..] + } + i++ + } + output << ml_ex + } } } return output