From b99ba21ddd0beb538a87a2a369bfc342782df40e Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Tue, 2 Jun 2020 18:10:01 +0800 Subject: [PATCH] vdoc: markdown, html, json generation + lots of fixes --- cmd/tools/vdoc.v | 472 +++++++++++++++++++++++++++++++++++++++++ cmd/v/help/default.txt | 3 +- cmd/v/help/doc.txt | 16 ++ cmd/v/v.v | 18 +- vlib/v/doc/doc.v | 384 ++++++++++++++++++++++----------- vlib/v/doc/doc_test.v | 12 +- vlib/v/util/util.v | 20 +- 7 files changed, 772 insertions(+), 153 deletions(-) create mode 100644 cmd/tools/vdoc.v create mode 100644 cmd/v/help/doc.txt diff --git a/cmd/tools/vdoc.v b/cmd/tools/vdoc.v new file mode 100644 index 0000000000..566516da62 --- /dev/null +++ b/cmd/tools/vdoc.v @@ -0,0 +1,472 @@ +module main + +import markdown +import net +import net.urllib +import os +import os.cmdline +import strings +import v.doc +import v.util +import v.vmod + +enum OutputType { + html + markdown + json + plaintext + stdout +} + +struct DocConfig { + pub_only bool = true + show_loc bool = false // for plaintext + serve_http bool = false // for html + is_multi bool = false + include_readme bool = false +mut: + opath string + src_path string + docs []doc.Doc + manifest vmod.Manifest +} + +fn slug(title string) string { + return title.replace(' ', '-') +} + +fn open_url(url string) { + $if windows { + os.system('start $url') + } + $if macos { + os.system('open $url') + } + $if linux { + os.system('xdg-open $url') + } +} + +fn (mut cfg DocConfig) serve_html() { + docs := cfg.multi_render(.html) + def_name := docs.keys()[0] + server := net.listen(8046) or { + panic(err) + } + println('Serving docs on: http://localhost:8046') + open_url('http://localhost:8046') + for { + con := server.accept() or { + server.close() or { } + panic(err) + } + s := con.read_line() + first_line := s.all_before('\n') + mut filename := def_name + if first_line.len != 0 { + data := first_line.split(' ') + url := urllib.parse(data[1]) or { return } + filename = if url.path == '/' { def_name } else { url.path.trim_left('/') } + } + html := docs[filename] + con.write('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n$html') or { + con.close() or { return } + return + } + con.close() or { return } + } +} + +fn get_src_link(repo_url string, file_name string, line_nr int) string { + mut url := urllib.parse(repo_url) or { + return '' + } + if url.path.len <= 1 || file_name.len == 0 { + return '' + } + url.path = url.path.trim_right('/') + match url.host { + 'github.com' { '/blob/master/$file_name' } + 'gitlab.com' { '/-/blob/master/$file_name' } + 'git.sir.ht' { '/tree/master/$file_name' } + else { '' } + } + if url.path == '/' { return '' } + url.fragment = 'L$line_nr' + return url.str() +} + +fn escape(str string) string { + return str.replace_each(['"', '\\"', '\r\n', '\\n', '\n', '\\n']) +} + +fn (cfg DocConfig) gen_json(idx int) string { + dcs := cfg.docs[idx] + mut jw := strings.new_builder(200) + jw.writeln('{\n\t"module_name": "$dcs.head.name",\n\t"description": "${escape(dcs.head.comment)}",\n\t"contents": [') + for i, cn in dcs.contents { + name := cn.name[dcs.head.name.len+1..] + jw.writeln('\t\t{') + jw.writeln('\t\t\t"name": "$name",') + jw.writeln('\t\t\t"signature": "${escape(cn.content)}",') + jw.writeln('\t\t\t"description": "${escape(cn.comment)}"') + jw.write('\t\t}') + if i < dcs.contents.len-1 { jw.writeln(',') } + } + jw.writeln('\n\t],') + jw.write('\t"generator": "vdoc",\n\t"time_generated": "${dcs.time_generated.str()}"\n}') + return jw.str() +} + +fn (cfg DocConfig) gen_html(idx int) string { + dcs := cfg.docs[idx] + mut hw := strings.new_builder(200) + mut toc := strings.new_builder(200) + mut doc_node_html := fn (dd doc.DocNode, link string, head bool) string { + mut dnw := strings.new_builder(200) + head_tag := if head { 'h1' } else { 'h2' } + md_content := markdown.to_html(dd.comment) + dnw.writeln('
') + if dd.name != 'README' { + dnw.write('<$head_tag>${dd.name} #') + } + if head { + dnw.write(md_content) + } else { + dnw.writeln('${dd.content}') + dnw.writeln(md_content) + if link.len != 0 { + dnw.writeln('

[Source]

') + } + } + dnw.writeln('
') + return dnw.str() + } + // generate toc first + for cn in dcs.contents { + if cn.parent_type !in ['void', ''] { continue } + toc.write('
  • ${cn.name}') + children := dcs.contents.find_children_of(cn.name) + if children.len != 0 { + toc.writeln(' ') + } + toc.writeln('
  • ') + } // write head + hw.write(' + + + + ${dcs.head.name} | vdoc + ') + // write css + hw.write(r'') + version := if cfg.manifest.version.len != 0 { '${cfg.manifest.version}' } else { '' } + repo_link := if cfg.manifest.repo_url.len != 0 { + ' + Repository + + ' + } else { '' } + header_name := if cfg.is_multi && cfg.docs.len > 1 { os.file_name(os.real_path(cfg.src_path)) } else { dcs.head.name } + // write nav + hw.write('
    +
    +

    ${header_name}

    + ${version} + +
    + \n
    ') + hw.write('
    \n
    \n') + hw.write(doc_node_html(dcs.head, '', true)) + for cn in dcs.contents { + if cn.parent_type !in ['void', ''] { continue } + hw.write(doc_node_html(cn, get_src_link(cfg.manifest.repo_url, os.file_name(cn.file_path), cn.pos.line), false)) + + children := dcs.contents.find_children_of(cn.name) + + if children.len != 0 { + for child in children { + hw.write(doc_node_html(child, get_src_link(cfg.manifest.repo_url, os.file_name(child.file_path), child.pos.line), false)) + } + } + } + hw.write('\n
    \n') + if cfg.is_multi && cfg.docs.len > 1 && dcs.head.name != 'README' { + hw.write('
    \n

    Contents

    \n
      \n${toc.str()}
    \n
    ') + } + hw.write('
    + + + ') + return hw.str() +} + +fn (cfg DocConfig) gen_plaintext(idx int) string { + dcs := cfg.docs[idx] + mut pw := strings.new_builder(200) + head_lines := '='.repeat(dcs.head.content.len) + pw.writeln('${dcs.head.content}\n$head_lines\n') + for cn in dcs.contents { + pw.writeln(cn.content) + if cn.comment.len > 0 { + pw.writeln('\n' + cn.comment) + } + if cfg.show_loc { + pw.writeln('Location: ${cn.file_path}:${cn.pos.line}:${cn.pos.col}\n\n') + } + } + pw.writeln('Generated on $dcs.time_generated') + return pw.str() +} + +fn (cfg DocConfig) gen_markdown(idx int, with_toc bool) string { + dcs := cfg.docs[idx] + mut hw := strings.new_builder(200) + mut cw := strings.new_builder(200) + hw.writeln('# ${dcs.head.content}\n${dcs.head.comment}\n') + if with_toc { + hw.writeln('## Contents') + } + for cn in dcs.contents { + name := cn.name.all_after(dcs.head.name+'.') + + if with_toc { + hw.writeln('- [#$name](${slug(name)})') + } + cw.writeln('## $name') + cw.writeln('```v\n${cn.content}\n```${cn.comment}\n') + cw.writeln('[\[Return to contents\]](#Contents)\n') + } + cw.writeln('#### Generated by vdoc. Last generated: ${dcs.time_generated.str()}') + return hw.str() + '\n' + cw.str() +} + +fn (cfg DocConfig) multi_render(output_type OutputType) map[string]string { + mut docs := map[string]string + for i, doc in cfg.docs { + mut name := if doc.head.name == 'README' { + 'index' + } else if cfg.docs.len == 1 && !os.is_dir(cfg.opath) { + os.dir(os.file_name(cfg.opath)) + } else { + doc.head.name + } + name = name + match output_type { + .html { '.html' } + .markdown { '.md' } + .json { '.json' } + else { '.txt' } + } + docs[name] = match output_type { + .html { cfg.gen_html(i) } + .markdown { cfg.gen_markdown(i, true) } + .json { cfg.gen_json(i) } + else { cfg.gen_plaintext(i) } + } + } + return docs +} + +fn (mut config DocConfig) generate_docs_from_file() { + if config.opath.len != 0 { + config.opath = os.join_path(os.real_path(os.base_dir(config.opath)), os.file_name(config.opath)) + } + println(config.opath) + mut output_type := OutputType.plaintext + // identify output type + if config.serve_http { + output_type = .html + } else if config.opath.len == 0 { + output_type = .stdout + } else { + ext := os.file_ext(config.opath) + if ext in ['.md', '.markdown'] || config.opath in [':md:', ':markdown:'] { + output_type = .markdown + } else if ext in ['.html', '.htm'] || config.opath == ':html:' { + output_type = .html + } else if ext == '.json' || config.opath == ':json:' { + output_type = .json + } else { + output_type = .plaintext + } + } + if config.include_readme && output_type != .html { + eprintln('vdoc: Including README.md for doc generation is supported on HTML output only.') + exit(1) + } + mut manifest_path := os.join_path(if os.is_dir(config.src_path) { config.src_path } else { os.base_dir(config.src_path) }, 'v.mod') + if os.exists(manifest_path) && 'vlib' !in config.src_path { + if manifest := vmod.from_file(manifest_path) { + config.manifest = manifest + } + } + if 'vlib' in config.src_path { + config.manifest.version = util.v_version + } + readme_path := if 'vlib' in config.src_path { + os.join_path(os.base_dir(@VEXE), 'README.md') + } else { + os.join_path(config.src_path, 'README.md') + } + // check README.md + if os.exists(readme_path) && config.include_readme { + println('Found README.md...') + readme_contents := os.read_file(readme_path) or { '' } + config.docs << doc.Doc{ + head: doc.DocNode{ + name: 'README', + comment: readme_contents + } + } + } + if config.is_multi { + dirs := get_modules_list(config.src_path) + for dirpath in dirs { + dcs := doc.generate(dirpath, config.pub_only, 'vlib' !in config.src_path) or { + panic(err) + } + if dcs.contents.len == 0 { continue } + config.docs << dcs + } + } else { + dcs := doc.generate(config.src_path, config.pub_only, 'vlib' !in config.src_path) or { + panic(err) + } + config.docs << dcs + } + if config.serve_http { + config.serve_html() + return + } + outputs := config.multi_render(output_type) + if output_type == .stdout || (config.opath.starts_with(':') && config.opath.ends_with(':')) { + first := outputs.keys()[0] + println(outputs[first]) + } else { + if !os.is_dir(config.opath) { + config.opath = os.base_dir(config.opath) + } + if config.is_multi { + config.opath = os.join_path(config.opath, '_docs') + if !os.exists(config.opath) { + os.mkdir(config.opath) or { + panic(err) + } + } + } + for file_name, content in outputs { + opath := os.join_path(config.opath, file_name) + println('Generating ${opath}...') + os.write_file(opath, content) + } + } +} + +fn lookup_module(mod string) ?string { + mod_path := mod.replace('.', '/') + vexe_path := os.base_dir(@VEXE) + compile_dir := os.real_path(os.base_dir('.')) + modules_dir := os.join_path(compile_dir, 'modules', mod_path) + vlib_path := os.join_path(vexe_path, 'vlib', mod_path) + vmodules_path := os.join_path(os.home_dir(), '.vmodules', mod_path) + paths := [modules_dir, vlib_path, vmodules_path] + for path in paths { + if os.is_dir_empty(path) { continue } + return path + } + return error('vdoc: Module "${mod}" not found.') +} + +fn get_modules_list(path string) []string { + files := os.walk_ext(path, 'v') + mut dirs := []string{} + for file in files { + if 'test' in file || 'js' in file || 'x64' in file || 'bare' in file || 'uiold' in file || 'vweb' in file { continue } + dirname := os.base_dir(file) + if dirname in dirs { continue } + dirs << dirname + } + dirs.sort() + return dirs +} + +fn main() { + args_after_doc := cmdline.options_after(os.args[1..], ['doc']) + opts := cmdline.only_options(os.args[1..]) + args := cmdline.only_non_options(args_after_doc) + if args.len == 0 || args[0] == 'help' { + os.system('v help doc') + exit(0) + } + mut config := DocConfig{ + src_path: args[0], + opath: if args.len >= 2 { args[1] } else { '' }, + pub_only: '-all' !in opts, + show_loc: '-loc' in opts, + serve_http: '-s' in opts, + is_multi: '-m' in opts, + include_readme: '-r' in opts, + manifest: vmod.Manifest{ repo_url: '' } + } + is_path := config.src_path.ends_with('.v') || config.src_path.split('/').len > 1 || config.src_path == '.' + if !is_path { + mod_path := lookup_module(config.src_path) or { + eprintln(err) + exit(1) + } + config.src_path = mod_path + } + config.generate_docs_from_file() +} diff --git a/cmd/v/help/default.txt b/cmd/v/help/default.txt index fee9de4fc9..4a8824e497 100644 --- a/cmd/v/help/default.txt +++ b/cmd/v/help/default.txt @@ -18,6 +18,7 @@ V supports the following commands: test Run all test files in the provided directory. fmt Format the V code provided. doc Generate the documentation for a V module. + vlib-docs Generate and open the documentation of all the vlib modules. repl Run the REPL. * Installation/self updating: symlink Create a symbolic link for V. @@ -37,4 +38,4 @@ Use "v help " for more information about a command, example: `v help bu Use "v help other" to see less frequently used commands. Note: Help is required to write more help topics. -Only build, fmt, run, test, search, install, remove, update, bin2v are properly documented currently. +Only build, doc, fmt, run, test, search, install, remove, update, bin2v are properly documented currently. diff --git a/cmd/v/help/doc.txt b/cmd/v/help/doc.txt new file mode 100644 index 0000000000..8b87b2423c --- /dev/null +++ b/cmd/v/help/doc.txt @@ -0,0 +1,16 @@ +Usage: + v doc [flags] module_name output_file[.json|.md|.html] + v doc [flags] path_to_source.v output_file[.json|.md|.html] + v doc [flags] ./directory output_file[.json|.md|.html] + v doc [flags] . output_file[.json|.md|.html] + +Generates the documentation of a given directory, module, or V source file +and prints or saves them to its desired format. It can generate HTML, JSON, +or Markdown format. + +Options: + -all Includes private and public functions/methods/structs/consts/enums. + -loc Show the locations of the generated signatures. (For plaintext only) + -m Generate docs for modules listed in that folder. + -s Serve HTML-generated docs via HTTP. + -r Include README.md to docs if present. \ No newline at end of file diff --git a/cmd/v/v.v b/cmd/v/v.v index 2c158c1903..804ea0dd61 100644 --- a/cmd/v/v.v +++ b/cmd/v/v.v @@ -5,8 +5,6 @@ module main import help import os -import v.table -import v.doc import v.pref import v.util import v.builder @@ -19,7 +17,7 @@ const ( 'repl', 'build-tools', 'build-examples', 'build-vbinaries', - 'setup-freetype' + 'setup-freetype', 'doc' ] list_of_flags_that_allow_duplicates = ['cc', 'd', 'define', 'cf', 'cflags'] ) @@ -80,6 +78,9 @@ fn main_v() { util.launch_tool(prefs.is_verbose, 'vpm', os.args[1..]) return } + 'vlib-docs' { + util.launch_tool(prefs.is_verbose, 'vdoc', ['doc', '-m', '-s', '-r', os.join_path(os.base_dir(@VEXE), 'vlib')]) + } 'get' { println('V Error: Use `v install` to install modules from vpm.vlang.io') exit(1) @@ -88,17 +89,6 @@ fn main_v() { println(util.full_v_version(prefs.is_verbose)) return } - 'doc' { - mut mod := '' - if args.len == 1 { - println('v doc [module]') - } else if args.len > 1 { - mod = args[1] - } - table := table.new_table() - println(doc.doc(mod, table, prefs)) - return - } else {} } if command in ['run', 'build-module'] || command.ends_with('.v') || os.exists(command) { diff --git a/vlib/v/doc/doc.v b/vlib/v/doc/doc.v index f74bfff9c8..bceaf9f409 100644 --- a/vlib/v/doc/doc.v +++ b/vlib/v/doc/doc.v @@ -1,149 +1,289 @@ module doc -import strings -import v.pref -import v.table -import v.parser -import v.ast import os +import strings +import time +import v.ast +import v.fmt +import v.parser +import v.pref +import v.scanner +import v.table +import v.token +import v.util -struct Doc { - table &table.Table - mod string -mut: - out strings.Builder - stmts []ast.Stmt // all module statements from all files +pub struct Doc { +pub mut: + input_path string = '' + prefs &pref.Preferences = &pref.Preferences{} + table &table.Table = &table.Table{} + pub_only bool = true + head DocNode + with_comments bool = true + contents []DocNode + time_generated time.Time } -type FilterFn = fn (node ast.FnDecl) bool +pub struct DocPos { +pub: + line int + col int +} -pub fn doc(mod string, table &table.Table, prefs &pref.Preferences) string { - mut d := Doc{ - out: strings.new_builder(1000) - table: table - mod: mod +pub struct DocNode { +pub mut: + name string + content string = '' + comment string + pos DocPos = DocPos{-1,-1} + file_path string = '' + parent_type string = '' +} + +pub fn write_comment_bw(stmts []ast.Stmt, start_idx int) string { + mut comment := '' + + for i := start_idx; i >= 0; i-- { + stmt := stmts[i] + + if stmt is ast.Comment { + cmt := stmt as ast.Comment + cmt_content := cmt.text.trim_left('|') + comment = cmt_content + if cmt_content.starts_with('```') { '\n' } else { ' ' } + comment + } else { + panic('Not a comment') + } + + if i-1 >= 0 && !(stmts[i-1] is ast.Comment) { + break + } } - vlib_path := os.dir(pref.vexe_path()) + '/vlib' - mod_path := mod.replace('.', os.path_separator) - path := os.join_path(vlib_path, mod_path) - if mod == '' || !os.exists(path) { - if mod != '' { - println('module "$mod" not found') + + return comment +} + +fn convert_pos(file_path string, pos token.Position) DocPos { + source := util.read_file(file_path) or {''} + mut p := util.imax(0, util.imin(source.len - 1, pos.pos)) + column := util.imax(0, pos.pos - p - 1) + return DocPos{ line: pos.line_nr+1, col: util.imax(1, column+1) } +} + +pub fn (d Doc) get_signature(stmt ast.Stmt) string { + mut f := fmt.Fmt{ + out: strings.new_builder(1000) + out_imports: strings.new_builder(200) + table: d.table + indent: 0 + is_debug: false + } + + match stmt { + ast.Module { + return 'module $it.name' } - println('\navailable modules:') - mut files := os.ls(vlib_path) or { - return '' + ast.FnDecl { + return it.str(d.table) } - files.sort() - for file in files { - println(file) + else { + f.stmt(stmt) + return f.out.str().trim_space() } - // println(path) + } +} + +pub fn (d Doc) get_pos(stmt ast.Stmt) token.Position { + match stmt { + ast.FnDecl { return it.pos } + ast.StructDecl { return it.pos } + ast.EnumDecl { return it.pos } + ast.InterfaceDecl { return it.pos } + ast.ConstDecl { return it.pos } + else { return token.Position{} } + } +} + +pub fn (d Doc) get_name(stmt ast.Stmt) string { + match stmt { + ast.FnDecl { return it.name } + ast.StructDecl { return it.name } + ast.EnumDecl { return it.name } + ast.InterfaceDecl { return it.name } + ast.ConstDecl { return 'Constants' } + else { return '' } + } +} + +pub fn new(input_path string) Doc { + return Doc{ + input_path: os.real_path(input_path), + prefs: &pref.Preferences{}, + table: table.new_table(), + head: DocNode{}, + contents: []DocNode{}, + time_generated: time.now() + } +} + +pub fn (nodes []DocNode) find_children_of(parent_type string) []DocNode { + if parent_type.len == 0 { return []DocNode{} } + mut children := []DocNode{} + for node in nodes { + if node.parent_type != parent_type { continue } + children << node + } + + return children +} + +fn get_parent_mod(dir string) ?string { + base_dir := os.base_dir(dir) + if os.file_name(base_dir) in ['encoding', 'v'] && 'vlib' in base_dir { + return os.file_name(base_dir) + } + prefs := &pref.Preferences{} + files := os.ls(base_dir) or { []string{} } + v_files := prefs.should_compile_filtered_files(base_dir, files) + if v_files.len == 0 { + parent_mod := get_parent_mod(base_dir) or { '' } + if parent_mod.len > 0 { + return parent_mod + '.' + os.file_name(base_dir) + } + return error('No V files found.') + } + file_ast := parser.parse_file( + v_files[0], + table.new_table(), + .skip_comments, + prefs, + &ast.Scope{parent: 0} + ) + if file_ast.mod.name == 'main' { return '' } - // vfiles := os.walk_ext(path, '.v') - files := os.ls(path) or { - panic(err) + parent_mod := get_parent_mod(base_dir) or { '' } + if parent_mod.len > 0 { + return parent_mod + '.' + file_ast.mod.name } - filtered_files := prefs.should_compile_filtered_files(path, files) - for file in filtered_files { - fscope := &ast.Scope{ - parent: 0 - } - file_ast := parser.parse_file(file, table, .skip_comments, prefs, fscope) - d.stmts << file_ast.stmts - } - if d.stmts.len == 0 { - println('nothing here') - exit(1) - } - d.print_structs() - d.print_enums() - d.print_fns() - d.out.writeln('') - d.print_methods() - /* - for stmt in file_ast.stmts { - d.stmt(stmt) - } - println(path) - */ - return d.out.str().trim_space() + return file_ast.mod.name } -fn (d &Doc) get_fn_node(f ast.FnDecl) string { - return f.str(d.table).replace_each([d.mod + '.', '', 'pub ', '']) -} +pub fn (mut d Doc) generate() ?bool { + // get all files + base_path := if os.is_dir(d.input_path) { d.input_path } else { os.real_path(os.base_dir(d.input_path)) } + project_files := os.ls(base_path) or { panic(err) } + v_files := d.prefs.should_compile_filtered_files(base_path, project_files) -fn (mut d Doc) print_fns() { - fn_signatures := d.get_fn_signatures(is_pub_function) - d.write_fn_signatures(fn_signatures) -} - -fn (mut d Doc) print_methods() { - fn_signatures := d.get_fn_signatures(is_pub_method) - d.write_fn_signatures(fn_signatures) -} - -[inline] -fn (mut d Doc) write_fn_signatures(fn_signatures []string) { - for s in fn_signatures { - d.out.writeln(s) + if v_files.len == 0 { + return error('vdoc: No valid V files were found.') } -} + // parse files + mut file_asts := []ast.File{} + // TODO: remove later for vlib + comments_mode := if d.with_comments { + scanner.CommentsMode.parse_comments + } else { + scanner.CommentsMode.skip_comments + } + for file in v_files { + file_ast := parser.parse_file( + file, + d.table, + comments_mode, + d.prefs, + &ast.Scope{parent: 0} + ) -fn (d Doc) get_fn_signatures(filter_fn FilterFn) []string { - mut fn_signatures := []string{} - for stmt in d.stmts { - match stmt { - ast.FnDecl { - if filter_fn(it) { - fn_signatures << d.get_fn_node(it) - } + file_asts << file_ast + } + + mut module_name := '' + mut parent_mod_name := '' + mut orig_mod_name := '' + + for i, file_ast in file_asts { + if i == 0 { + parent_mod_name = get_parent_mod(base_path) or { '' } + module_name = file_ast.mod.name + orig_mod_name = module_name + if module_name != 'main' && parent_mod_name.len > 0 { + module_name = parent_mod_name + '.' + module_name } - else {} - } - } - fn_signatures.sort() - return fn_signatures -} - -fn is_pub_method(node ast.FnDecl) bool { - return node.is_pub && node.is_method && !node.is_deprecated -} - -fn is_pub_function(node ast.FnDecl) bool { - return node.is_pub && !node.is_method && !node.is_deprecated -} - -// TODO it's probably better to keep using AST, not `table` -fn (mut d Doc) print_enums() { - for typ in d.table.types { - if typ.kind != .enum_ { + d.head = DocNode{ + name: module_name, + content: 'module $module_name', + comment: '' + } + } else if file_ast.mod.name != module_name { continue } - d.out.writeln('enum $typ.name {') - info := typ.info as table.Enum - for val in info.vals { - d.out.writeln('\t$val') + + stmts := file_ast.stmts + for si, stmt in stmts { + if stmt is ast.Comment { continue } + + if !(stmt is ast.Module) { + // todo: accumulate consts + mut name := d.get_name(stmt) + signature := d.get_signature(stmt) + pos := d.get_pos(stmt) + + if !signature.starts_with('pub') && d.pub_only { + continue + } + + if name.starts_with(orig_mod_name + '.') { + name = name.all_after(orig_mod_name + '.') + } + + mut node := DocNode{ + name: name, + content: signature, + comment: '', + pos: convert_pos(v_files[i], pos), + file_path: v_files[i] + } + + if stmt is ast.FnDecl { + fnd := stmt as ast.FnDecl + + if fnd.receiver.typ != 0 { + mut parent_type := d.table.get_type_name(fnd.receiver.typ) + + if parent_type.starts_with(module_name + '.') { + parent_type = parent_type.all_after(module_name + '.') + } + + node.parent_type = parent_type + } + } + + d.contents << node + } + + if d.with_comments && (si-1 >= 0 && stmts[si-1] is ast.Comment) { + if stmt is ast.Module { + d.head.comment = write_comment_bw(stmts, si-1) + } else { + last_comment := d.contents[d.contents.len-1].comment + d.contents[d.contents.len-1].comment = last_comment + '\n' + write_comment_bw(stmts, si-1) + } + } } - d.out.writeln('}') } + + d.time_generated = time.now() + return true } -fn (mut d Doc) print_structs() { - for typ in d.table.types { - if typ.kind != .struct_ || !typ.name.starts_with(d.mod + '.') { - // !typ.name[0].is_capital() || typ.name.starts_with('C.') { - continue - } - name := typ.name.after('.') - d.out.writeln('struct $name {') - info := typ.info as table.Struct - for field in info.fields { - sym := d.table.get_type_symbol(field.typ) - d.out.writeln('\t$field.name $sym.name') - } - d.out.writeln('}\n') +pub fn generate(input_path string, pub_only bool, with_comments bool) ?Doc { + mut doc := doc.new(input_path) + doc.pub_only = pub_only + doc.with_comments = with_comments + + _ = doc.generate() or { + return error(err) } -} + + return doc +} \ No newline at end of file diff --git a/vlib/v/doc/doc_test.v b/vlib/v/doc/doc_test.v index 4a0b54e47d..51496558bb 100644 --- a/vlib/v/doc/doc_test.v +++ b/vlib/v/doc/doc_test.v @@ -2,9 +2,9 @@ import v.table import v.doc import v.pref -fn test_vdoc() { - mut prefs := &pref.Preferences{} - prefs.fill_with_defaults() - table := table.new_table() - println(doc.doc('net', table, prefs)) -} +// fn test_vdoc() { +// mut prefs := &pref.Preferences{} +// prefs.fill_with_defaults() +// table := table.new_table() +// println(doc.doc('net', table, prefs)) +// } diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index f11edd61b4..b7ca5e205b 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -316,14 +316,14 @@ and the existing module `${modulename}` may still work.') } pub fn ensure_modules_for_all_tools_are_installed(is_verbose bool) { - for tool_name, tool_modules in external_module_dependencies_for_tool { - if is_verbose { - eprintln('Installing modules for tool: $tool_name ...') - } - for emodule in tool_modules { - util.check_module_is_installed(emodule, is_verbose) or { - panic(err) - } - } - } + for tool_name, tool_modules in external_module_dependencies_for_tool { + if is_verbose { + eprintln('Installing modules for tool: $tool_name ...') + } + for emodule in tool_modules { + util.check_module_is_installed(emodule, is_verbose) or { + panic(err) + } + } + } }