')
}
// do not mess with md_content further, its formatting is important, just output it 1:1 !
- dnw.writeln('$md_content\n')
+ dnw.writeln('$md_content\n')
+ // Write examples if any found
+ examples := dd.examples()
+ if include_examples && examples.len > 0 {
+ example_title := if examples.len > 1 { 'Examples' } else { 'Example' }
+ dnw.writeln('
$example_title
')
+ for example in examples {
+ // hl_example := html_highlight(example, tb)
+ dnw.writeln('
$example
')
+ }
+ dnw.writeln('')
+ }
+ dnw.writeln('')
dnw_str := dnw.str()
defer {
dnw.free()
@@ -499,12 +518,12 @@ fn (cfg DocConfig) write_content(cn &doc.DocNode, dcs &doc.Doc, mut hw strings.B
}
src_link := get_src_link(cfg.manifest.repo_url, file_path_name, cn.pos.line)
if cn.content.len != 0 || (cn.name == 'Constants') {
- hw.write(doc_node_html(cn, src_link, false, dcs.table))
+ hw.write(doc_node_html(cn, src_link, false, cfg.include_examples, dcs.table))
}
for child in cn.children {
child_file_path_name := child.file_path.replace('$base_dir/', '')
child_src_link := get_src_link(cfg.manifest.repo_url, child_file_path_name, child.pos.line)
- hw.write(doc_node_html(child, child_src_link, false, dcs.table))
+ hw.write(doc_node_html(child, child_src_link, false, cfg.include_examples, dcs.table))
}
}
@@ -515,7 +534,7 @@ fn (cfg DocConfig) gen_html(idx int) string {
dcs := cfg.docs[idx]
dcs_contents := dcs.contents.arr()
// generate toc first
- contents.writeln(doc_node_html(dcs.head, '', true, dcs.table))
+ contents.writeln(doc_node_html(dcs.head, '', true, cfg.include_examples, dcs.table))
if is_module_readme(dcs.head) {
write_toc(dcs.head, mut symbols_toc)
}
@@ -602,8 +621,13 @@ fn (cfg DocConfig) gen_plaintext(idx int) string {
dcs := cfg.docs[idx]
mut pw := strings.new_builder(200)
pw.writeln('$dcs.head.content\n')
- if dcs.head.comment.trim_space().len > 0 && !cfg.pub_only {
- pw.writeln(dcs.head.comment.split_into_lines().map(' ' + it).join('\n'))
+ comments := if cfg.include_examples {
+ dcs.head.merge_comments()
+ } else {
+ dcs.head.merge_comments_without_examples()
+ }
+ if comments.trim_space().len > 0 && !cfg.pub_only {
+ pw.writeln(comments.split_into_lines().map(' ' + it).join('\n'))
}
cfg.write_plaintext_content(dcs.contents.arr(), mut pw)
return pw.str()
@@ -613,8 +637,13 @@ fn (cfg DocConfig) write_plaintext_content(contents []doc.DocNode, mut pw string
for cn in contents {
if cn.content.len > 0 {
pw.writeln(cn.content)
- if cn.comment.len > 0 && !cfg.pub_only {
- pw.writeln(cn.comment.trim_space().split_into_lines().map(' ' + it).join('\n'))
+ if cn.comments.len > 0 && !cfg.pub_only {
+ comments := if cfg.include_examples {
+ cn.merge_comments()
+ } else {
+ cn.merge_comments_without_examples()
+ }
+ pw.writeln(comments.trim_space().split_into_lines().map(' ' + it).join('\n'))
}
if cfg.show_loc {
pw.writeln('Location: $cn.file_path:$cn.pos.line\n')
@@ -629,8 +658,13 @@ fn (cfg DocConfig) gen_markdown(idx int, with_toc bool) string {
mut hw := strings.new_builder(200)
mut cw := strings.new_builder(200)
hw.writeln('# $dcs.head.content\n')
- if dcs.head.comment.len > 0 {
- hw.writeln('$dcs.head.comment\n')
+ if dcs.head.comments.len > 0 {
+ comments := if cfg.include_examples {
+ dcs.head.merge_comments()
+ } else {
+ dcs.head.merge_comments_without_examples()
+ }
+ hw.writeln('$comments\n')
}
if with_toc {
hw.writeln('## Contents')
@@ -648,7 +682,18 @@ fn (cfg DocConfig) write_markdown_content(contents []doc.DocNode, mut cw strings
cw.writeln('## $cn.name')
}
if cn.content.len > 0 {
- cw.writeln('```v\n$cn.content\n```$cn.comment\n')
+ comments := cn.merge_comments_without_examples()
+ cw.writeln('```v\n$cn.content\n```\n$comments\n')
+ // Write examples if any found
+ examples := cn.examples()
+ if cfg.include_examples && examples.len > 0 {
+ example_title := if examples.len > 1 { 'Examples' } else { 'Example' }
+ cw.writeln('$example_title\n```v\n')
+ for example in examples {
+ cw.writeln('$example\n')
+ }
+ cw.writeln('```\n')
+ }
cw.writeln('[\[Return to contents\]](#Contents)\n')
}
cfg.write_markdown_content(cn.children, mut cw, mut hw, indent + 1, with_toc)
@@ -742,8 +787,13 @@ fn (mut cfg DocConfig) collect_search_index() {
for doc in cfg.docs {
mod := doc.head.name
cfg.search_module_index << mod
+ comments := if cfg.include_examples {
+ doc.head.merge_comments()
+ } else {
+ doc.head.merge_comments_without_examples()
+ }
cfg.search_module_data << SearchModuleResult{
- description: trim_doc_node_description(doc.head.comment)
+ description: trim_doc_node_description(comments)
link: cfg.get_file_name(mod)
}
for _, dn in doc.contents {
@@ -756,7 +806,12 @@ fn (mut cfg DocConfig) create_search_results(mod string, dn doc.DocNode) {
if dn.kind == .const_group {
return
}
- dn_description := trim_doc_node_description(dn.comment)
+ comments := if cfg.include_examples {
+ dn.merge_comments()
+ } else {
+ dn.merge_comments_without_examples()
+ }
+ dn_description := trim_doc_node_description(comments)
cfg.search_index << dn.name
cfg.search_data << SearchResult{
prefix: if dn.parent_name != '' {
@@ -894,13 +949,16 @@ fn (mut cfg DocConfig) generate_docs_from_file() {
}
if cfg.include_readme {
readme_contents := cfg.get_readme(dir_path)
+ comment := doc.DocComment{
+ text: readme_contents
+ }
if cfg.output_type == .stdout {
println(markdown.to_plain(readme_contents))
} else if cfg.output_type == .html && cfg.is_multi {
cfg.docs << doc.Doc{
head: doc.DocNode{
name: 'README'
- comment: readme_contents
+ comments: [comment]
}
time_generated: time.now()
}
@@ -932,7 +990,10 @@ fn (mut cfg DocConfig) generate_docs_from_file() {
if !is_local_and_single {
if cfg.is_multi || (!cfg.is_multi && cfg.include_readme) {
readme_contents := cfg.get_readme(dirpath)
- dcs.head.comment = readme_contents
+ comment := doc.DocComment{
+ text: readme_contents
+ }
+ dcs.head.comments = [comment]
}
if cfg.pub_only {
for name, dc in dcs.contents {
@@ -1183,6 +1244,9 @@ fn main() {
'-no-timestamp' {
cfg.no_timestamp = true
}
+ '-no-examples' {
+ cfg.include_examples = false
+ }
'-readme' {
cfg.include_readme = true
}
@@ -1231,7 +1295,7 @@ fn main() {
}
fn is_module_readme(dn doc.DocNode) bool {
- if dn.comment.len > 0 && dn.content == 'module $dn.name' {
+ if dn.comments.len > 0 && dn.content == 'module $dn.name' {
return true
}
return false
diff --git a/vlib/v/doc/comment.v b/vlib/v/doc/comment.v
new file mode 100644
index 0000000000..91f1493d3d
--- /dev/null
+++ b/vlib/v/doc/comment.v
@@ -0,0 +1,23 @@
+module doc
+
+const (
+ example_pattern = '\x01 Example: '
+)
+
+pub struct DocComment {
+pub mut:
+ text string // Raw text content of the comment, excluding the comment token chars ('//, /*, */')
+ is_multi bool // Is a block / multi-line comment
+ pos DocPos = DocPos{-1, -1, 0}
+}
+
+// is_example returns true if the contents of this comment is a doc example.
+// The current convention is '// Example: '
+pub fn (dc DocComment) is_example() bool {
+ return dc.text.starts_with(example_pattern)
+}
+
+// example returns the content of the example body
+pub fn (dc DocComment) example() string {
+ return dc.text.all_after(example_pattern)
+}
diff --git a/vlib/v/doc/doc.v b/vlib/v/doc/doc.v
index e7dc0a0b77..8f4bbe10cf 100644
--- a/vlib/v/doc/doc.v
+++ b/vlib/v/doc/doc.v
@@ -80,7 +80,7 @@ pub struct DocNode {
pub mut:
name string
content string
- comment string
+ comments []DocComment
pos DocPos = DocPos{-1, -1, 0}
file_path string
kind SymbolKind
@@ -128,7 +128,6 @@ pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode {
mut node := DocNode{
name: d.stmt_name(stmt)
content: d.stmt_signature(stmt)
- comment: ''
pos: d.convert_pos(filename, stmt.position())
file_path: os.join_path(d.base_path, filename)
is_pub: d.stmt_pub(stmt)
@@ -139,7 +138,7 @@ pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode {
if node.name.starts_with(d.orig_mod_name + '.') {
node.name = node.name.all_after(d.orig_mod_name + '.')
}
- if node.name.len == 0 && node.comment.len == 0 && node.content.len == 0 {
+ if node.name.len == 0 && node.comments.len == 0 && node.content.len == 0 {
return error('empty stmt')
}
match stmt {
@@ -246,12 +245,13 @@ pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode {
last_import_stmt_idx = sidx
}
}
- mut prev_comments := []ast.Comment{}
+ mut preceeding_comments := []DocComment{}
mut imports_section := true
for sidx, stmt in stmts {
if stmt is ast.ExprStmt {
+ // Collect comments
if stmt.expr is ast.Comment {
- prev_comments << stmt.expr
+ preceeding_comments << ast_comment_to_doc_comment(stmt.expr)
continue
}
}
@@ -261,37 +261,41 @@ pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode {
continue
}
// the previous comments were probably a copyright/license one
- module_comment := get_comment_block_right_before(prev_comments)
- prev_comments = []
+ module_comment := merge_doc_comments(preceeding_comments)
+ preceeding_comments = []
if !d.is_vlib && !module_comment.starts_with('Copyright (c)') {
- if module_comment in ['', d.head.comment] {
+ if module_comment == '' {
continue
}
+ /*
if d.head.comment != '' {
d.head.comment += '\n'
}
- d.head.comment += module_comment
+ */
+ d.head.comments << preceeding_comments //+= module_comment
}
continue
}
if last_import_stmt_idx > 0 && sidx == last_import_stmt_idx {
// the accumulated comments were interspersed before/between the imports;
- // just add them all to the module comment:
+ // just add them all to the module comments:
if d.with_head {
- import_comments := merge_comments(prev_comments)
+ // import_comments := merge_comments(preceeding_comments)
+ /*
if d.head.comment != '' {
d.head.comment += '\n'
}
- d.head.comment += import_comments
+ */
+ d.head.comments << preceeding_comments //+= import_comments
}
- prev_comments = []
+ preceeding_comments = []
imports_section = false
}
if stmt is ast.Import {
continue
}
mut node := d.stmt(stmt, os.base(file_ast.path)) or {
- prev_comments = []
+ preceeding_comments = []
continue
}
if node.parent_name !in contents {
@@ -305,18 +309,20 @@ pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode {
kind: parent_node_kind
}
}
- if d.with_comments && (prev_comments.len > 0) {
+ if d.with_comments && (preceeding_comments.len > 0) {
// last_comment := contents[contents.len - 1].comment
- // cmt := last_comment + '\n' + get_comment_block_right_before(prev_comments)
- mut cmt := get_comment_block_right_before(prev_comments)
+ // cmt := last_comment + '\n' + merge_doc_comments(preceeding_comments)
+ /*
+ mut cmt := merge_doc_comments(preceeding_comments)
len := node.name.len
// fixed-width symbol name at start of comment
if cmt.starts_with(node.name) && cmt.len > len && cmt[len] == ` ` {
cmt = '`${cmt[..len]}`' + cmt[len..]
}
- node.comment = cmt
+ */
+ node.comments << preceeding_comments //= cmt
}
- prev_comments = []
+ preceeding_comments = []
if node.parent_name.len > 0 {
parent_name := node.parent_name
if node.parent_name == 'Constants' {
diff --git a/vlib/v/doc/node.v b/vlib/v/doc/node.v
index 09f73d5e69..736067f276 100644
--- a/vlib/v/doc/node.v
+++ b/vlib/v/doc/node.v
@@ -45,3 +45,26 @@ pub fn (cnts map[string]DocNode) arr() []DocNode {
contents.sort_by_kind()
return contents
}
+
+// merge_comments returns a `string` with the combined contents of `DocNode.comments`.
+pub fn (dc DocNode) merge_comments() string {
+ return merge_doc_comments(dc.comments)
+}
+
+// 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())
+ 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 {
+ if comment.is_example() {
+ output << comment.example()
+ }
+ }
+ return output
+}
diff --git a/vlib/v/doc/utils.v b/vlib/v/doc/utils.v
index b209668f1b..0e80e4a396 100644
--- a/vlib/v/doc/utils.v
+++ b/vlib/v/doc/utils.v
@@ -16,9 +16,33 @@ pub fn merge_comments(comments []ast.Comment) string {
return res.join('\n')
}
-// get_comment_block_right_before merges all the comments starting from
+// ast_comment_to_doc_comment converts an `ast.Comment` node type to a `DocComment`
+pub fn ast_comment_to_doc_comment(ast_node ast.Comment) DocComment {
+ text := ast_node.text // TODO .trim_left('\x01') // BUG why are this byte here in the first place?
+ return DocComment{
+ text: text
+ is_multi: ast_node.is_multi
+ pos: DocPos{
+ line: ast_node.pos.line_nr - 1
+ col: 0 // ast_node.pos.pos - ast_node.text.len
+ len: text.len
+ }
+ }
+}
+
+// ast_comments_to_doc_comments converts an array of `ast.Comment` nodes to
+// an array of `DocComment` nodes
+pub fn ast_comments_to_doc_comments(ast_nodes []ast.Comment) []DocComment {
+ mut doc_comments := []DocComment{len: ast_nodes.len}
+ for ast_comment in ast_nodes {
+ doc_comments << ast_comment_to_doc_comment(ast_comment)
+ }
+ return doc_comments
+}
+
+// merge_doc_comments merges all the comments starting from
// the last up to the first item of the array.
-pub fn get_comment_block_right_before(comments []ast.Comment) string {
+pub fn merge_doc_comments(comments []DocComment) string {
if comments.len == 0 {
return ''
}
@@ -26,7 +50,7 @@ pub fn get_comment_block_right_before(comments []ast.Comment) string {
mut last_comment_line_nr := 0
for i := comments.len - 1; i >= 0; i-- {
cmt := comments[i]
- if last_comment_line_nr != 0 && cmt.pos.line_nr < last_comment_line_nr - 1 {
+ if last_comment_line_nr != 0 && cmt.pos.line < last_comment_line_nr - 1 {
// skip comments that are not part of a continuous block,
// located right above the top level statement.
// break
@@ -58,7 +82,7 @@ pub fn get_comment_block_right_before(comments []ast.Comment) string {
// eprintln('cmt: $cmt')
cseparator := if cmt_content.starts_with('```') { '\n' } else { ' ' }
comment = cmt_content + cseparator + comment
- last_comment_line_nr = cmt.pos.line_nr
+ last_comment_line_nr = cmt.pos.line
}
return comment
}