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} #$head_tag>')
+ }
+ 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(' ')
+ for child in children {
+ toc.writeln('- ${child.name}
')
+ }
+ 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('')
+ 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('
')
+ }
+ 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)
+ }
+ }
+ }
}