vdoc: fix sorting; fix missing symbols; document functions (#7161)

pull/7172/head
Ned Palacios 2020-12-07 09:43:25 +08:00 committed by GitHub
parent dcca821000
commit 2ba8d31118
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 85 additions and 52 deletions

View File

@ -154,9 +154,7 @@ fn (mut cfg DocConfig) serve_html() {
} }
def_name := docs.keys()[0] def_name := docs.keys()[0]
server_url := 'http://localhost:' + cfg.server_port.str() server_url := 'http://localhost:' + cfg.server_port.str()
server := net.listen_tcp(cfg.server_port) or { server := net.listen_tcp(cfg.server_port) or { panic(err) }
panic(err)
}
println('Serving docs on: $server_url') println('Serving docs on: $server_url')
if cfg.open_docs { if cfg.open_docs {
open_url(server_url) open_url(server_url)
@ -178,9 +176,7 @@ fn (mut cfg DocConfig) serve_html() {
panic(err) panic(err)
} }
handle_http_connection(mut conn, server_context) handle_http_connection(mut conn, server_context)
conn.close() or { conn.close() or { eprintln('error closing the connection: $err') }
eprintln('error closing the connection: $err')
}
} }
} }
@ -191,9 +187,7 @@ struct VdocHttpServerContext {
} }
fn handle_http_connection(mut con net.TcpConn, ctx &VdocHttpServerContext) { fn handle_http_connection(mut con net.TcpConn, ctx &VdocHttpServerContext) {
mut reader := io.new_buffered_reader({ mut reader := io.new_buffered_reader(reader: io.make_reader(con))
reader: io.make_reader(con)
})
first_line := reader.read_line() or { first_line := reader.read_line() or {
send_http_response(mut con, 501, ctx.content_type, 'bad request') send_http_response(mut con, 501, ctx.content_type, 'bad request')
return return
@ -230,15 +224,11 @@ fn send_http_response(mut con net.TcpConn, http_code int, content_type string, h
http_response.write('\r\n') http_response.write('\r\n')
http_response.write(html) http_response.write(html)
sresponse := http_response.str() sresponse := http_response.str()
con.write_str(sresponse) or { con.write_str(sresponse) or { eprintln('error sending http response: $err') }
eprintln('error sending http response: $err')
}
} }
fn get_src_link(repo_url string, file_name string, line_nr int) string { fn get_src_link(repo_url string, file_name string, line_nr int) string {
mut url := urllib.parse(repo_url) or { mut url := urllib.parse(repo_url) or { return '' }
return ''
}
if url.path.len <= 1 || file_name.len == 0 { if url.path.len <= 1 || file_name.len == 0 {
return '' return ''
} }
@ -656,13 +646,13 @@ fn (mut cfg DocConfig) render_static() {
return return
} }
cfg.assets = { cfg.assets = {
'doc_css': cfg.get_resource(css_js_assets[0], true) 'doc_css': cfg.get_resource(css_js_assets[0], true)
'normalize_css': cfg.get_resource(css_js_assets[1], true) 'normalize_css': cfg.get_resource(css_js_assets[1], true)
'doc_js': cfg.get_resource(css_js_assets[2], !cfg.serve_http) 'doc_js': cfg.get_resource(css_js_assets[2], !cfg.serve_http)
'light_icon': cfg.get_resource('light.svg', true) 'light_icon': cfg.get_resource('light.svg', true)
'dark_icon': cfg.get_resource('dark.svg', true) 'dark_icon': cfg.get_resource('dark.svg', true)
'menu_icon': cfg.get_resource('menu.svg', true) 'menu_icon': cfg.get_resource('menu.svg', true)
'arrow_icon': cfg.get_resource('arrow.svg', true) 'arrow_icon': cfg.get_resource('arrow.svg', true)
} }
} }
@ -679,9 +669,7 @@ fn (cfg DocConfig) get_readme(path string) string {
} }
readme_path := os.join_path(path, '${fname}.md') readme_path := os.join_path(path, '${fname}.md')
cfg.vprintln('Reading README file from $readme_path') cfg.vprintln('Reading README file from $readme_path')
readme_contents := os.read_file(readme_path) or { readme_contents := os.read_file(readme_path) or { '' }
''
}
return readme_contents return readme_contents
} }
@ -809,16 +797,12 @@ fn (mut cfg DocConfig) generate_docs_from_file() {
cfg.output_path = os.real_path('.') cfg.output_path = os.real_path('.')
} }
if !os.exists(cfg.output_path) { if !os.exists(cfg.output_path) {
os.mkdir(cfg.output_path) or { os.mkdir(cfg.output_path) or { panic(err) }
panic(err)
}
} }
if cfg.is_multi { if cfg.is_multi {
cfg.output_path = os.join_path(cfg.output_path, '_docs') cfg.output_path = os.join_path(cfg.output_path, '_docs')
if !os.exists(cfg.output_path) { if !os.exists(cfg.output_path) {
os.mkdir(cfg.output_path) or { os.mkdir(cfg.output_path) or { panic(err) }
panic(err)
}
} else { } else {
for fname in css_js_assets { for fname in css_js_assets {
os.rm(os.join_path(cfg.output_path, fname)) os.rm(os.join_path(cfg.output_path, fname))
@ -865,9 +849,7 @@ fn get_ignore_paths(path string) ?[]string {
} }
res = final.map(os.join_path(path, it.trim_right('/'))) res = final.map(os.join_path(path, it.trim_right('/')))
} else { } else {
mut dirs := os.ls(path) or { mut dirs := os.ls(path) or { return []string{} }
return []string{}
}
res = dirs.map(os.join_path(path, it)).filter(os.is_dir(it)) res = dirs.map(os.join_path(path, it)).filter(os.is_dir(it))
} }
return res.map(it.replace('/', os.path_separator)) return res.map(it.replace('/', os.path_separator))
@ -887,12 +869,8 @@ fn is_included(path string, ignore_paths []string) bool {
} }
fn get_modules_list(path string, ignore_paths2 []string) []string { fn get_modules_list(path string, ignore_paths2 []string) []string {
files := os.ls(path) or { files := os.ls(path) or { return []string{} }
return []string{} mut ignore_paths := get_ignore_paths(path) or { []string{} }
}
mut ignore_paths := get_ignore_paths(path) or {
[]string{}
}
ignore_paths << ignore_paths2 ignore_paths << ignore_paths2
mut dirs := []string{} mut dirs := []string{}
for file in files { for file in files {
@ -912,9 +890,7 @@ fn get_modules_list(path string, ignore_paths2 []string) []string {
fn (cfg DocConfig) get_resource(name string, minify bool) string { fn (cfg DocConfig) get_resource(name string, minify bool) string {
path := os.join_path(res_path, name) path := os.join_path(res_path, name)
mut res := os.read_file(path) or { mut res := os.read_file(path) or { panic('vdoc: could not read $path') }
panic('vdoc: could not read $path')
}
if minify { if minify {
if name.ends_with('.js') { if name.ends_with('.js') {
res = js_compress(res) res = js_compress(res)

View File

@ -18,6 +18,7 @@ const (
'nonexistant', 'nonexistant',
] ]
vfmt_verify_list = [ vfmt_verify_list = [
'cmd/tools/vdoc.v'
'cmd/v/v.v', 'cmd/v/v.v',
'vlib/builtin/array.v', 'vlib/builtin/array.v',
'vlib/os/file.v', 'vlib/os/file.v',

View File

@ -11,7 +11,8 @@ import v.scanner
import v.table import v.table
import v.util import v.util
// intentionally in order as a guide when arranging the docnodes // SymbolKind categorizes the symbols it documents.
// The names are intentionally not in order as a guide when sorting the nodes.
pub enum SymbolKind { pub enum SymbolKind {
none_ none_
const_group const_group
@ -75,10 +76,12 @@ pub mut:
parent_name string parent_name string
return_type string return_type string
children []DocNode children []DocNode
attrs map[string]string attrs map[string]string [json: attributes]
from_scope bool from_scope bool
is_pub bool [json: public]
} }
// new_vdoc_preferences creates a new instance of pref.Preferences tailored for v.doc.
pub fn new_vdoc_preferences() &pref.Preferences { pub fn new_vdoc_preferences() &pref.Preferences {
// vdoc should be able to parse as much user code as possible // vdoc should be able to parse as much user code as possible
// so its preferences should be permissive: // so its preferences should be permissive:
@ -87,6 +90,7 @@ pub fn new_vdoc_preferences() &pref.Preferences {
} }
} }
// new creates a new instance of a `Doc` struct.
pub fn new(input_path string) Doc { pub fn new(input_path string) Doc {
mut d := Doc{ mut d := Doc{
base_path: os.real_path(input_path) base_path: os.real_path(input_path)
@ -105,6 +109,9 @@ pub fn new(input_path string) Doc {
return d return d
} }
// stmt reads the data of an `ast.Stmt` node and returns a `DocNode`.
// An option error is thrown if the symbol is not exposed to the public
// (when `pub_only` is enabled) or the content's of the AST node is empty.
pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode { pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode {
mut node := DocNode{ mut node := DocNode{
name: d.stmt_name(stmt) name: d.stmt_name(stmt)
@ -112,9 +119,10 @@ pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode {
comment: '' comment: ''
pos: d.convert_pos(filename, stmt.position()) pos: d.convert_pos(filename, stmt.position())
file_path: os.join_path(d.base_path, filename) file_path: os.join_path(d.base_path, filename)
is_pub: d.stmt_pub(stmt)
} }
if (!node.content.starts_with('pub') && d.pub_only) || stmt is ast.GlobalDecl { if (!node.is_pub && d.pub_only) || stmt is ast.GlobalDecl {
return error('symbol not public') return error('symbol $node.name not public')
} }
if node.name.starts_with(d.orig_mod_name + '.') { if node.name.starts_with(d.orig_mod_name + '.') {
node.name = node.name.all_after(d.orig_mod_name + '.') node.name = node.name.all_after(d.orig_mod_name + '.')
@ -205,6 +213,7 @@ pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode {
return node return node
} }
// file_ast reads the contents of `ast.File` and returns a map of `DocNode`s.
pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode { pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode {
mut contents := map[string]DocNode{} mut contents := map[string]DocNode{}
stmts := file_ast.stmts stmts := file_ast.stmts
@ -220,7 +229,6 @@ pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode {
mut prev_comments := []ast.Comment{} mut prev_comments := []ast.Comment{}
mut imports_section := true mut imports_section := true
for sidx, stmt in stmts { for sidx, stmt in stmts {
// eprintln('stmt typeof: ' + typeof(stmt))
if stmt is ast.ExprStmt { if stmt is ast.ExprStmt {
if stmt.expr is ast.Comment { if stmt.expr is ast.Comment {
prev_comments << stmt.expr prev_comments << stmt.expr
@ -302,6 +310,8 @@ pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode {
return contents return contents
} }
// file_ast_with_pos has the same function as the `file_ast` but
// instead returns a list of variables in a given offset-based position.
pub fn (mut d Doc) file_ast_with_pos(file_ast ast.File, pos int) map[string]DocNode { pub fn (mut d Doc) file_ast_with_pos(file_ast ast.File, pos int) map[string]DocNode {
lscope := file_ast.scope.innermost(pos) lscope := file_ast.scope.innermost(pos)
mut contents := map[string]DocNode{} mut contents := map[string]DocNode{}
@ -323,6 +333,8 @@ pub fn (mut d Doc) file_ast_with_pos(file_ast ast.File, pos int) map[string]DocN
return contents return contents
} }
// generate is a `Doc` method that will start documentation
// process based on a file path provided.
pub fn (mut d Doc) generate() ? { pub fn (mut d Doc) generate() ? {
// get all files // get all files
d.base_path = if os.is_dir(d.base_path) { d.base_path } else { os.real_path(os.dir(d.base_path)) } d.base_path = if os.is_dir(d.base_path) { d.base_path } else { os.real_path(os.dir(d.base_path)) }
@ -353,6 +365,8 @@ pub fn (mut d Doc) generate() ? {
return d.file_asts(file_asts) return d.file_asts(file_asts)
} }
// file_asts has the same function as the `file_ast` function but
// accepts an array of `ast.File` and throws an error if necessary.
pub fn (mut d Doc) file_asts(file_asts []ast.File) ? { pub fn (mut d Doc) file_asts(file_asts []ast.File) ? {
mut fname_has_set := false mut fname_has_set := false
d.orig_mod_name = file_asts[0].mod.name d.orig_mod_name = file_asts[0].mod.name
@ -380,17 +394,27 @@ pub fn (mut d Doc) file_asts(file_asts []ast.File) ? {
} }
contents := d.file_ast(file_ast) contents := d.file_ast(file_ast)
for name, node in contents { for name, node in contents {
if name in d.contents && (d.contents[name].kind != .none_ || node.kind == .none_) { if name !in d.contents {
d.contents[name].children << node.children d.contents[name] = node
d.contents[name].children.sort_by_name()
continue continue
} }
d.contents[name] = node if d.contents[name].kind == .typedef && node.kind !in [.typedef, .none_] {
old_children := d.contents[name].children.clone()
d.contents[name] = node
d.contents[name].children = old_children
}
if d.contents[name].kind != .none_ || node.kind == .none_ {
d.contents[name].children << node.children
d.contents[name].children.sort_by_name()
d.contents[name].children.sort_by_kind()
}
} }
} }
d.time_generated = time.now() d.time_generated = time.now()
} }
// generate documents a certain file directory and returns an
// instance of `Doc` if it is successful. Otherwise, it will throw an error.
pub fn generate(input_path string, pub_only bool, with_comments bool) ?Doc { pub fn generate(input_path string, pub_only bool, with_comments bool) ?Doc {
mut doc := new(input_path) mut doc := new(input_path)
doc.pub_only = pub_only doc.pub_only = pub_only
@ -399,6 +423,8 @@ pub fn generate(input_path string, pub_only bool, with_comments bool) ?Doc {
return doc return doc
} }
// generate_with_pos has the same function as the `generate` function but
// accepts an offset-based position and enables the comments by default.
pub fn generate_with_pos(input_path string, filename string, pos int) ?Doc { pub fn generate_with_pos(input_path string, filename string, pos int) ?Doc {
mut doc := new(input_path) mut doc := new(input_path)
doc.pub_only = false doc.pub_only = false

View File

@ -6,7 +6,7 @@ import v.parser
import v.ast import v.ast
import v.pref import v.pref
// get_parent_mod - return the parent mod name, in dot format. // get_parent_mod returns the parent mod name, in dot format.
// It works by climbing up the folder hierarchy, until a folder, // It works by climbing up the folder hierarchy, until a folder,
// that either contains main .v files, or a v.mod file is reached. // that either contains main .v files, or a v.mod file is reached.
// For example, given something like /languages/v/vlib/x/websocket/tests/autobahn // For example, given something like /languages/v/vlib/x/websocket/tests/autobahn
@ -55,6 +55,8 @@ fn get_parent_mod(input_dir string) ?string {
return file_ast.mod.name return file_ast.mod.name
} }
// lookup_module_with_path looks up the path of a given module name.
// Throws an error if the module was not found.
pub fn lookup_module_with_path(mod string, base_path string) ?string { pub fn lookup_module_with_path(mod string, base_path string) ?string {
vexe := pref.vexe_path() vexe := pref.vexe_path()
vroot := os.dir(vexe) vroot := os.dir(vexe)
@ -76,10 +78,13 @@ pub fn lookup_module_with_path(mod string, base_path string) ?string {
return error('module "$mod" not found.') return error('module "$mod" not found.')
} }
// lookup_module returns the result of the `lookup_module_with_path`
// but with the current directory as the provided base lookup path.
pub fn lookup_module(mod string) ?string { pub fn lookup_module(mod string) ?string {
return lookup_module_with_path(mod, os.dir('.')) return lookup_module_with_path(mod, os.dir('.'))
} }
// generate_from_mod generates a documentation from a specific module.
pub fn generate_from_mod(module_name string, pub_only bool, with_comments bool) ?Doc { pub fn generate_from_mod(module_name string, pub_only bool, with_comments bool) ?Doc {
mod_path := lookup_module(module_name) ? mod_path := lookup_module(module_name) ?
return generate(mod_path, pub_only, with_comments) return generate(mod_path, pub_only, with_comments)

View File

@ -10,10 +10,12 @@ pub fn (nodes []DocNode) find(symname string) ?DocNode {
return error('symbol not found') return error('symbol not found')
} }
// sort_by_name sorts the array based on the symbol names.
pub fn (mut nodes []DocNode) sort_by_name() { pub fn (mut nodes []DocNode) sort_by_name() {
nodes.sort_with_compare(compare_nodes_by_name) nodes.sort_with_compare(compare_nodes_by_name)
} }
// sort_by_kind sorts the array based on the symbol kind.
pub fn (mut nodes []DocNode) sort_by_kind() { pub fn (mut nodes []DocNode) sort_by_kind() {
nodes.sort_with_compare(compare_nodes_by_kind) nodes.sort_with_compare(compare_nodes_by_kind)
} }
@ -36,6 +38,7 @@ fn compare_nodes_by_name(a &DocNode, b &DocNode) int {
return compare_strings(al, bl) return compare_strings(al, bl)
} }
// arr() converts the map into an array of `DocNode`.
pub fn (cnts map[string]DocNode) arr() []DocNode { pub fn (cnts map[string]DocNode) arr() []DocNode {
mut contents := cnts.keys().map(cnts[it]) mut contents := cnts.keys().map(cnts[it])
contents.sort_by_name() contents.sort_by_name()

View File

@ -7,6 +7,7 @@ import v.token
import v.table import v.table
import os import os
// merge_comments merges all the comment contents into a single text.
pub fn merge_comments(comments []ast.Comment) string { pub fn merge_comments(comments []ast.Comment) string {
mut res := []string{} mut res := []string{}
for comment in comments { for comment in comments {
@ -15,6 +16,8 @@ pub fn merge_comments(comments []ast.Comment) string {
return res.join('\n') return res.join('\n')
} }
// get_comment_block_right_before 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 get_comment_block_right_before(comments []ast.Comment) string {
if comments.len == 0 { if comments.len == 0 {
return '' return ''
@ -60,6 +63,7 @@ pub fn get_comment_block_right_before(comments []ast.Comment) string {
return comment return comment
} }
// convert_pos converts the `token.Position` data into a `DocPos`.
fn (mut d Doc) convert_pos(filename string, pos token.Position) DocPos { fn (mut d Doc) convert_pos(filename string, pos token.Position) DocPos {
if filename !in d.sources { if filename !in d.sources {
d.sources[filename] = util.read_file(os.join_path(d.base_path, filename)) or { '' } d.sources[filename] = util.read_file(os.join_path(d.base_path, filename)) or { '' }
@ -74,6 +78,7 @@ fn (mut d Doc) convert_pos(filename string, pos token.Position) DocPos {
} }
} }
// stmt_signature returns the signature of a given `ast.Stmt` node.
pub fn (mut d Doc) stmt_signature(stmt ast.Stmt) string { pub fn (mut d Doc) stmt_signature(stmt ast.Stmt) string {
match stmt { match stmt {
ast.Module { ast.Module {
@ -90,6 +95,7 @@ pub fn (mut d Doc) stmt_signature(stmt ast.Stmt) string {
} }
} }
// stmt_name returns the name of a given `ast.Stmt` node.
pub fn (d Doc) stmt_name(stmt ast.Stmt) string { pub fn (d Doc) stmt_name(stmt ast.Stmt) string {
match stmt { match stmt {
ast.FnDecl, ast.StructDecl, ast.EnumDecl, ast.InterfaceDecl { return stmt.name } ast.FnDecl, ast.StructDecl, ast.EnumDecl, ast.InterfaceDecl { return stmt.name }
@ -101,10 +107,26 @@ pub fn (d Doc) stmt_name(stmt ast.Stmt) string {
} }
} }
// stmt_pub returns a boolean if a given `ast.Stmt` node
// is exposed to the public.
pub fn (d Doc) stmt_pub(stmt ast.Stmt) bool {
match stmt {
ast.FnDecl, ast.StructDecl, ast.EnumDecl, ast.InterfaceDecl, ast.ConstDecl { return stmt.is_pub }
ast.TypeDecl { match stmt {
ast.FnTypeDecl, ast.AliasTypeDecl, ast.SumTypeDecl { return stmt.is_pub }
} }
else { return false }
}
}
// type_to_str is a wrapper function around `fmt.table.type_to_str`.
pub fn (d Doc) type_to_str(typ table.Type) string { pub fn (d Doc) type_to_str(typ table.Type) string {
return d.fmt.table.type_to_str(typ).all_after('&') return d.fmt.table.type_to_str(typ).all_after('&')
} }
// expr_typ_to_string has the same function as `Doc.typ_to_str`
// but for `ast.Expr` nodes. The checker will check first the
// node and it executes the `type_to_str` method.
pub fn (mut d Doc) expr_typ_to_string(ex ast.Expr) string { pub fn (mut d Doc) expr_typ_to_string(ex ast.Expr) string {
expr_typ := d.checker.expr(ex) expr_typ := d.checker.expr(ex)
return d.type_to_str(expr_typ) return d.type_to_str(expr_typ)