<$head_tag>$sym_name$hash_link$head_tag>')
+ } else {
+ dnw.write('${tabs[2]}
<$head_tag>$dn.kind $sym_name$hash_link$head_tag>')
+ }
+ if link.len != 0 {
+ dnw.write('
$link_svg ')
+ }
+ dnw.write('
')
+ }
+ if !head && dn.content.len > 0 {
+ dnw.writeln('
$hlighted_code
')
+ }
+ // do not mess with md_content further, its formatting is important, just output it 1:1 !
+ dnw.writeln('$md_content\n')
+ // Write examples if any found
+ examples := dn.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()
+ }
+ return dnw_str
+}
+
+fn html_tag_escape(str string) string {
+ return str.replace_each(['<', '<', '>', '>'])
+}
+
+fn js_compress(str string) string {
+ mut js := strings.new_builder(200)
+ lines := str.split_into_lines()
+ rules := [') {', ' = ', ', ', '{ ', ' }', ' (', '; ', ' + ', ' < ', ' - ', ' || ', ' var',
+ ': ', ' >= ', ' && ', ' else if', ' === ', ' !== ', ' else ']
+ clean := ['){', '=', ',', '{', '}', '(', ';', '+', '<', '-', '||', 'var', ':', '>=', '&&',
+ 'else if', '===', '!==', 'else']
+ for line in lines {
+ mut trimmed := line.trim_space()
+ if trimmed.starts_with('//') || (trimmed.starts_with('/*') && trimmed.ends_with('*/')) {
+ continue
+ }
+ for i in 0 .. rules.len - 1 {
+ trimmed = trimmed.replace(rules[i], clean[i])
+ }
+ js.write(trimmed)
+ }
+ js_str := js.str()
+ js.free()
+ return js_str
+}
+
+fn write_toc(dn doc.DocNode, mut toc strings.Builder) {
+ mut toc_slug := if dn.name.len == 0 || dn.content.len == 0 { '' } else { slug(dn.name) }
+ if toc_slug == '' && dn.children.len > 0 {
+ if dn.children[0].name == '' {
+ toc_slug = slug(dn.name)
+ } else {
+ toc_slug = slug(dn.name + '.' + dn.children[0].name)
+ }
+ }
+ if is_module_readme(dn) {
+ toc.write('
README ')
+ } else if dn.name != 'Constants' {
+ toc.write('$dn.kind $dn.name ')
+ toc.writeln(' ')
+ for child in dn.children {
+ cname := dn.name + '.' + child.name
+ toc.writeln('$child.kind $child.name ')
+ }
+ toc.writeln(' ')
+ } else {
+ toc.write('$dn.name ')
+ }
+ toc.writeln('')
+}
diff --git a/cmd/tools/vdoc/httpserver.v b/cmd/tools/vdoc/httpserver.v
new file mode 100644
index 0000000000..f67dc82a18
--- /dev/null
+++ b/cmd/tools/vdoc/httpserver.v
@@ -0,0 +1,95 @@
+module main
+
+import io
+import net
+import strings
+
+fn (mut vd VDoc) serve_html(out Output) {
+ cfg := vd.cfg
+ if out.typ == .html {
+ vd.render_static_html(true, out)
+ }
+ docs := vd.render(out)
+ dkeys := docs.keys()
+ if dkeys.len < 1 {
+ eprintln('no documentation created, the module has no `pub` functions')
+ exit(1)
+ }
+ def_name := docs.keys()[0]
+ server_url := 'http://localhost:' + cfg.server_port.str()
+ server := net.listen_tcp(cfg.server_port) or { panic(err) }
+ println('Serving docs on: $server_url')
+ if cfg.open_docs {
+ open_url(server_url)
+ }
+ content_type := match out.typ {
+ .html { 'text/html' }
+ .markdown { 'text/markdown' }
+ .json { 'application/json' }
+ else { 'text/plain' }
+ }
+ server_context := VdocHttpServerContext{
+ docs: docs
+ content_type: content_type
+ default_filename: def_name
+ }
+ for {
+ mut conn := server.accept() or {
+ server.close() or { }
+ panic(err)
+ }
+ handle_http_connection(mut conn, server_context)
+ conn.close() or { eprintln('error closing the connection: $err') }
+ }
+}
+
+struct VdocHttpServerContext {
+ docs map[string]string
+ content_type string
+ default_filename string
+}
+
+fn handle_http_connection(mut con net.TcpConn, ctx &VdocHttpServerContext) {
+ mut reader := io.new_buffered_reader(reader: io.make_reader(con))
+ first_line := reader.read_line() or {
+ send_http_response(mut con, 501, ctx.content_type, 'bad request')
+ return
+ }
+ request_parts := first_line.split(' ')
+ if request_parts.len != 3 {
+ send_http_response(mut con, 501, ctx.content_type, 'bad request')
+ return
+ }
+ urlpath := request_parts[1]
+ filename := if urlpath == '/' {
+ ctx.default_filename.trim_left('/')
+ } else {
+ urlpath.trim_left('/')
+ }
+ if ctx.docs[filename].len == 0 {
+ send_http_response(mut con, 404, ctx.content_type, 'file not found')
+ return
+ }
+ send_http_response(mut con, 200, ctx.content_type, ctx.docs[filename])
+}
+
+fn send_http_response(mut con net.TcpConn, http_code int, content_type string, html string) {
+ content_length := html.len.str()
+ shttp_code := http_code.str()
+ mut http_response := strings.new_builder(20000)
+ http_response.write('HTTP/1.1 ')
+ http_response.write(shttp_code)
+ http_response.write(' OK\r\n')
+ http_response.write('Server: VDoc\r\n')
+ http_response.write('Content-Type: ')
+ http_response.write(content_type)
+ http_response.write('\r\n')
+ http_response.write('Content-Length: ')
+ http_response.write(content_length)
+ http_response.write('\r\n')
+ http_response.write('Connection: close\r\n')
+ http_response.write('\r\n')
+ http_response.write(html)
+ sresponse := http_response.str()
+ con.write_str(sresponse) or { eprintln('error sending http response: $err') }
+}
diff --git a/cmd/tools/vdoc/markdown.v b/cmd/tools/vdoc/markdown.v
new file mode 100644
index 0000000000..01000a86e1
--- /dev/null
+++ b/cmd/tools/vdoc/markdown.v
@@ -0,0 +1,50 @@
+module main
+
+import strings
+import v.doc
+
+fn (vd VDoc) gen_markdown(d doc.Doc, with_toc bool) string {
+ mut hw := strings.new_builder(200)
+ mut cw := strings.new_builder(200)
+ hw.writeln('# $d.head.content\n')
+ if d.head.comments.len > 0 {
+ comments := if vd.cfg.include_examples {
+ d.head.merge_comments()
+ } else {
+ d.head.merge_comments_without_examples()
+ }
+ hw.writeln('$comments\n')
+ }
+ if with_toc {
+ hw.writeln('## Contents')
+ }
+ vd.write_markdown_content(d.contents.arr(), mut cw, mut hw, 0, with_toc)
+ footer_text := gen_footer_text(d, !vd.cfg.no_timestamp)
+ cw.writeln('#### $footer_text')
+ return hw.str() + '\n' + cw.str()
+}
+
+fn (vd VDoc) write_markdown_content(contents []doc.DocNode, mut cw strings.Builder, mut hw strings.Builder, indent int, with_toc bool) {
+ for cn in contents {
+ if with_toc && cn.name.len > 0 {
+ hw.writeln(' '.repeat(2 * indent) + '- [#$cn.name](${slug(cn.name)})')
+ cw.writeln('## $cn.name')
+ }
+ if cn.content.len > 0 {
+ 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 vd.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')
+ }
+ vd.write_markdown_content(cn.children, mut cw, mut hw, indent + 1, with_toc)
+ }
+}
diff --git a/cmd/tools/vdoc/utils.v b/cmd/tools/vdoc/utils.v
new file mode 100644
index 0000000000..4f0f488bc9
--- /dev/null
+++ b/cmd/tools/vdoc/utils.v
@@ -0,0 +1,145 @@
+module main
+
+import os
+import v.doc
+
+[inline]
+fn slug(title string) string {
+ return title.replace(' ', '-')
+}
+
+[inline]
+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 escape(str string) string {
+ return str.replace_each(['"', '\\"', '\r\n', '\\n', '\n', '\\n', '\t', '\\t'])
+}
+
+fn get_sym_name(dn doc.DocNode) string {
+ sym_name := if dn.parent_name.len > 0 && dn.parent_name != 'void' {
+ '($dn.parent_name) $dn.name'
+ } else {
+ dn.name
+ }
+ return sym_name
+}
+
+fn get_node_id(dn doc.DocNode) string {
+ tag := if dn.parent_name.len > 0 && dn.parent_name != 'void' {
+ '${dn.parent_name}.$dn.name'
+ } else {
+ dn.name
+ }
+ return slug(tag)
+}
+
+fn is_module_readme(dn doc.DocNode) bool {
+ if dn.comments.len > 0 && dn.content == 'module $dn.name' {
+ return true
+ }
+ return false
+}
+
+fn trim_doc_node_description(description string) string {
+ mut dn_description := description.replace_each(['\r\n', '\n', '"', '\\"'])
+ // 80 is enough to fill one line
+ if dn_description.len > 80 {
+ dn_description = dn_description[..80]
+ }
+ if '\n' in dn_description {
+ dn_description = dn_description.split('\n')[0]
+ }
+ // if \ is last character, it ends with \" which leads to a JS error
+ if dn_description.ends_with('\\') {
+ dn_description = dn_description.trim_right('\\')
+ }
+ return dn_description
+}
+
+fn set_output_type_from_str(format string) OutputType {
+ output_type := match format {
+ 'htm', 'html' { OutputType.html }
+ 'md', 'markdown' { OutputType.markdown }
+ 'json' { OutputType.json }
+ 'stdout' { OutputType.stdout }
+ else { OutputType.plaintext }
+ }
+ return output_type
+}
+
+fn get_ignore_paths(path string) ?[]string {
+ ignore_file_path := os.join_path(path, '.vdocignore')
+ ignore_content := os.read_file(ignore_file_path) or {
+ return error_with_code('ignore file not found.', 1)
+ }
+ mut res := []string{}
+ if ignore_content.trim_space().len > 0 {
+ rules := ignore_content.split_into_lines().map(it.trim_space())
+ mut final := []string{}
+ for rule in rules {
+ if rule.contains('*.') || rule.contains('**') {
+ println('vdoc: Wildcards in ignore rules are not allowed for now.')
+ continue
+ }
+ final << rule
+ }
+ res = final.map(os.join_path(path, it.trim_right('/')))
+ } else {
+ mut dirs := os.ls(path) or { return []string{} }
+ res = dirs.map(os.join_path(path, it)).filter(os.is_dir(it))
+ }
+ return res.map(it.replace('/', os.path_separator))
+}
+
+fn is_included(path string, ignore_paths []string) bool {
+ if path.len == 0 {
+ return true
+ }
+ for ignore_path in ignore_paths {
+ if ignore_path !in path {
+ continue
+ }
+ return false
+ }
+ return true
+}
+
+fn get_modules_list(path string, ignore_paths2 []string) []string {
+ files := os.ls(path) or { return []string{} }
+ mut ignore_paths := get_ignore_paths(path) or { []string{} }
+ ignore_paths << ignore_paths2
+ mut dirs := []string{}
+ for file in files {
+ fpath := os.join_path(path, file)
+ if os.is_dir(fpath) && is_included(fpath, ignore_paths) && !os.is_link(path) {
+ dirs << get_modules_list(fpath, ignore_paths.filter(it.starts_with(fpath)))
+ } else if fpath.ends_with('.v') && !fpath.ends_with('_test.v') {
+ if path in dirs {
+ continue
+ }
+ dirs << path
+ }
+ }
+ dirs.sort()
+ return dirs
+}
+
+fn gen_footer_text(d &doc.Doc, include_timestamp bool) string {
+ footer_text := 'Powered by vdoc.'
+ if !include_timestamp {
+ return footer_text
+ }
+ generated_time := d.time_generated
+ time_str := '$generated_time.day $generated_time.smonth() $generated_time.year $generated_time.hhmmss()'
+ return '$footer_text Generated on: $time_str'
+}
diff --git a/cmd/tools/vdoc/vdoc.v b/cmd/tools/vdoc/vdoc.v
index 4f96abd6fc..67db6fa709 100644
--- a/cmd/tools/vdoc/vdoc.v
+++ b/cmd/tools/vdoc/vdoc.v
@@ -1,8 +1,6 @@
module main
import markdown
-import net
-import net.urllib
import os
import os.cmdline
import time
@@ -10,93 +8,14 @@ import strings
import sync
import runtime
import v.doc
-import v.scanner
-import v.table
-import v.token
-import v.vmod
import v.pref
+import v.vmod
import json
-import io
-
-enum HighlightTokenTyp {
- unone
- boolean
- builtin
- char
- comment
- function
- keyword
- name
- number
- operator
- punctuation
- string
- symbol
-}
const (
- css_js_assets = ['doc.css', 'normalize.css', 'doc.js', 'dark-mode.js']
allowed_formats = ['md', 'markdown', 'json', 'text', 'stdout', 'html', 'htm']
- res_path = os.resource_abs_path('resources')
- favicons_path = os.join_path(res_path, 'favicons')
vexe = pref.vexe_path()
vroot = os.dir(vexe)
- html_content = '
-
-
-
-
-
-
{{ title }} | vdoc
-
-
-
-
-
-
-
-
-
-
- {{ head_assets }}
-
-
-
-
-
-
-
-
{{ head_name }}
-
-
{{ version }}
-
{{ light_icon }}{{ dark_icon }}
-
- {{ menu_icon }}
-
-
-
-
-
-
-
-
-
- {{ footer_assets }}
-
-
-'
tabs = ['\t\t', '\t\t\t\t\t\t', '\t\t\t\t\t\t\t']
)
@@ -109,531 +28,85 @@ enum OutputType {
stdout
}
-struct DocConfig {
+struct VDoc {
+ cfg Config [required]
mut:
- is_local bool
- local_filename string
- local_pos int
- pub_only bool = true
- show_loc bool // for plaintext
- serve_http bool // for html
- is_multi bool
- is_vlib bool
- is_verbose bool
- include_readme bool
- include_examples bool = true
- open_docs bool
- server_port int = 8046
- inline_assets bool
- no_timestamp bool
- output_path string
- input_path string
- symbol_name string
- output_type OutputType = .unset
docs []doc.Doc
- manifest vmod.Manifest
assets map[string]string
+ manifest vmod.Manifest
search_index []string
search_data []SearchResult
search_module_index []string // search results are split into a module part and the rest
search_module_data []SearchModuleResult
}
-struct SearchModuleResult {
- description string
- link string
+struct Config {
+mut:
+ pub_only bool = true
+ is_local bool
+ local_filename string
+ local_pos int
+ show_loc bool // for plaintext
+ serve_http bool // for html
+ is_multi bool
+ is_vlib bool
+ is_verbose bool
+ include_readme bool
+ include_examples bool = true
+ open_docs bool
+ server_port int = 8046
+ inline_assets bool
+ no_timestamp bool
+ output_path string
+ output_type OutputType = .unset
+ input_path string
+ symbol_name string
}
-struct SearchResult {
- prefix string
- badge string
- description string
- link string
+//
+struct Output {
+mut:
+ path string
+ typ OutputType = .unset
}
struct ParallelDoc {
- d doc.Doc
- i int
+ d doc.Doc
+ out Output
}
-[inline]
-fn slug(title string) string {
- return title.replace(' ', '-')
-}
-
-[inline]
-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() {
- cfg.render_static()
- docs := cfg.render()
- dkeys := docs.keys()
- if dkeys.len < 1 {
- eprintln('no documentation created, the module has no `pub` functions')
- exit(1)
- }
- def_name := docs.keys()[0]
- server_url := 'http://localhost:' + cfg.server_port.str()
- server := net.listen_tcp(cfg.server_port) or { panic(err) }
- println('Serving docs on: $server_url')
- if cfg.open_docs {
- open_url(server_url)
- }
- content_type := match cfg.output_type {
- .html { 'text/html' }
- .markdown { 'text/markdown' }
- .json { 'application/json' }
- else { 'text/plain' }
- }
- server_context := VdocHttpServerContext{
- docs: docs
- content_type: content_type
- default_filename: def_name
- }
- for {
- mut conn := server.accept() or {
- server.close() or { }
- panic(err)
- }
- handle_http_connection(mut conn, server_context)
- conn.close() or { eprintln('error closing the connection: $err') }
- }
-}
-
-struct VdocHttpServerContext {
- docs map[string]string
- content_type string
- default_filename string
-}
-
-fn handle_http_connection(mut con net.TcpConn, ctx &VdocHttpServerContext) {
- mut reader := io.new_buffered_reader(reader: io.make_reader(con))
- first_line := reader.read_line() or {
- send_http_response(mut con, 501, ctx.content_type, 'bad request')
- return
- }
- request_parts := first_line.split(' ')
- if request_parts.len != 3 {
- send_http_response(mut con, 501, ctx.content_type, 'bad request')
- return
- }
- urlpath := request_parts[1]
- filename := if urlpath == '/' {
- ctx.default_filename.trim_left('/')
- } else {
- urlpath.trim_left('/')
- }
- if ctx.docs[filename].len == 0 {
- send_http_response(mut con, 404, ctx.content_type, 'file not found')
- return
- }
- send_http_response(mut con, 200, ctx.content_type, ctx.docs[filename])
-}
-
-fn send_http_response(mut con net.TcpConn, http_code int, content_type string, html string) {
- content_length := html.len.str()
- shttp_code := http_code.str()
- mut http_response := strings.new_builder(20000)
- http_response.write('HTTP/1.1 ')
- http_response.write(shttp_code)
- http_response.write(' OK\r\n')
- http_response.write('Server: VDoc\r\n')
- http_response.write('Content-Type: ')
- http_response.write(content_type)
- http_response.write('\r\n')
- http_response.write('Content-Length: ')
- http_response.write(content_length)
- http_response.write('\r\n')
- http_response.write('Connection: close\r\n')
- http_response.write('\r\n')
- http_response.write(html)
- sresponse := http_response.str()
- con.write_str(sresponse) or { eprintln('error sending http response: $err') }
-}
-
-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 js_compress(str string) string {
- mut js := strings.new_builder(200)
- lines := str.split_into_lines()
- rules := [') {', ' = ', ', ', '{ ', ' }', ' (', '; ', ' + ', ' < ', ' - ', ' || ', ' var',
- ': ', ' >= ', ' && ', ' else if', ' === ', ' !== ', ' else ']
- clean := ['){', '=', ',', '{', '}', '(', ';', '+', '<', '-', '||', 'var', ':', '>=', '&&',
- 'else if', '===', '!==', 'else']
- for line in lines {
- mut trimmed := line.trim_space()
- if trimmed.starts_with('//') || (trimmed.starts_with('/*') && trimmed.ends_with('*/')) {
- continue
- }
- for i in 0 .. rules.len - 1 {
- trimmed = trimmed.replace(rules[i], clean[i])
- }
- js.write(trimmed)
- }
- js_str := js.str()
- js.free()
- return js_str
-}
-
-fn escape(str string) string {
- return str.replace_each(['"', '\\"', '\r\n', '\\n', '\n', '\\n', '\t', '\\t'])
-}
-
-fn (cfg DocConfig) gen_json(idx int) string {
- dcs := cfg.docs[idx]
+fn (vd VDoc) gen_json(d doc.Doc) string {
+ cfg := vd.cfg
mut jw := strings.new_builder(200)
comments := if cfg.include_examples {
- dcs.head.merge_comments()
+ d.head.merge_comments()
} else {
- dcs.head.merge_comments_without_examples()
+ d.head.merge_comments_without_examples()
}
- jw.write('{"module_name":"$dcs.head.name","description":"${escape(comments)}","contents":')
- jw.write(json.encode(dcs.contents.keys().map(dcs.contents[it])))
- jw.write(',"generator":"vdoc","time_generated":"$dcs.time_generated.str()"}')
+ jw.write('{"module_name":"$d.head.name","description":"${escape(comments)}","contents":')
+ jw.write(json.encode(d.contents.keys().map(d.contents[it])))
+ jw.write(',"generator":"vdoc","time_generated":"$d.time_generated.str()"}')
return jw.str()
}
-fn html_highlight(code string, tb &table.Table) string {
- builtin := ['bool', 'string', 'i8', 'i16', 'int', 'i64', 'i128', 'byte', 'u16', 'u32', 'u64',
- 'u128', 'rune', 'f32', 'f64', 'any_int', 'any_float', 'byteptr', 'voidptr', 'any']
- highlight_code := fn (tok token.Token, typ HighlightTokenTyp) string {
- lit := if typ in [.unone, .operator, .punctuation] {
- tok.kind.str()
- } else if typ == .string {
- "'$tok.lit'"
- } else if typ == .char {
- '`$tok.lit`'
- } else {
- tok.lit
- }
- return if typ in [.unone, .name] {
- lit
- } else {
- '
$lit '
- }
- }
- mut s := scanner.new_scanner(code, .parse_comments, &pref.Preferences{})
- mut tok := s.scan()
- mut next_tok := s.scan()
- mut buf := strings.new_builder(200)
- mut i := 0
- for i < code.len {
- if i == tok.pos {
- mut tok_typ := HighlightTokenTyp.unone
- match tok.kind {
- .name {
- if tok.lit in builtin || tb.known_type(tok.lit) {
- tok_typ = .builtin
- } else if next_tok.kind == .lcbr {
- tok_typ = .symbol
- } else if next_tok.kind == .lpar {
- tok_typ = .function
- } else {
- tok_typ = .name
- }
- }
- .comment {
- tok_typ = .comment
- }
- .chartoken {
- tok_typ = .char
- }
- .string {
- tok_typ = .string
- }
- .number {
- tok_typ = .number
- }
- .key_true, .key_false {
- tok_typ = .boolean
- }
- .lpar, .lcbr, .rpar, .rcbr, .lsbr, .rsbr, .semicolon, .colon, .comma, .dot {
- tok_typ = .punctuation
- }
- else {
- if token.is_key(tok.lit) || token.is_decl(tok.kind) {
- tok_typ = .keyword
- } else if tok.kind == .decl_assign || tok.kind.is_assign() || tok.is_unary() ||
- tok.kind.is_relational() || tok.kind.is_infix() {
- tok_typ = .operator
- }
- }
- }
- buf.write(highlight_code(tok, tok_typ))
- if next_tok.kind != .eof {
- i = tok.pos + tok.len
- tok = next_tok
- next_tok = s.scan()
- } else {
- break
- }
- } else {
- buf.write_b(code[i])
- i++
- }
- }
- return buf.str()
-}
-
-fn doc_node_html(dd doc.DocNode, link string, head bool, include_examples bool, tb &table.Table) string {
- mut dnw := strings.new_builder(200)
- link_svg := '
'
- head_tag := if head { 'h1' } else { 'h2' }
- comments := dd.merge_comments_without_examples()
- md_content := markdown.to_html(html_tag_escape(comments))
- hlighted_code := html_highlight(dd.content, tb)
- node_class := if dd.kind == .const_group { ' const' } else { '' }
- sym_name := get_sym_name(dd)
- mut node_id := get_node_id(dd)
- mut hash_link := if !head { '
# ' } else { '' }
- if head && is_module_readme(dd) {
- node_id = 'readme_$node_id'
- hash_link = '
# '
- }
- dnw.writeln('${tabs[1]}
')
- if dd.name.len > 0 {
- if dd.kind == .const_group {
- dnw.write('${tabs[2]}<$head_tag>$sym_name$hash_link$head_tag>')
- } else {
- dnw.write('${tabs[2]}
<$head_tag>$dd.kind $sym_name$hash_link$head_tag>')
- }
- if link.len != 0 {
- dnw.write('
$link_svg ')
- }
- dnw.write('
')
- }
- if !head && dd.content.len > 0 {
- dnw.writeln('
$hlighted_code
')
- }
- // do not mess with md_content further, its formatting is important, just output it 1:1 !
- 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()
- }
- return dnw_str
-}
-
-fn html_tag_escape(str string) string {
- return str.replace_each(['<', '<', '>', '>'])
-}
-
-fn get_sym_name(dn doc.DocNode) string {
- sym_name := if dn.parent_name.len > 0 && dn.parent_name != 'void' {
- '($dn.parent_name) $dn.name'
- } else {
- dn.name
- }
- return sym_name
-}
-
-fn get_node_id(dn doc.DocNode) string {
- tag := if dn.parent_name.len > 0 && dn.parent_name != 'void' {
- '${dn.parent_name}.$dn.name'
- } else {
- dn.name
- }
- return slug(tag)
-}
-
-fn (cfg DocConfig) readme_idx() int {
- for i, dc in cfg.docs {
- if dc.head.name != 'README' {
- continue
- }
- return i
- }
- return -1
-}
-
-fn write_toc(dn doc.DocNode, mut toc strings.Builder) {
- mut toc_slug := if dn.name.len == 0 || dn.content.len == 0 { '' } else { slug(dn.name) }
- if toc_slug == '' && dn.children.len > 0 {
- if dn.children[0].name == '' {
- toc_slug = slug(dn.name)
- } else {
- toc_slug = slug(dn.name + '.' + dn.children[0].name)
- }
- }
- if is_module_readme(dn) {
- toc.write('
README ')
- } else if dn.name != 'Constants' {
- toc.write('$dn.kind $dn.name ')
- toc.writeln(' ')
- for child in dn.children {
- cname := dn.name + '.' + child.name
- toc.writeln('$child.kind $child.name ')
- }
- toc.writeln(' ')
- } else {
- toc.write('$dn.name ')
- }
- toc.writeln('')
-}
-
-fn (cfg DocConfig) write_content(cn &doc.DocNode, dcs &doc.Doc, mut hw strings.Builder) {
- base_dir := os.dir(os.real_path(cfg.input_path))
- file_path_name := if cfg.is_multi {
- cn.file_path.replace('$base_dir/', '')
- } else {
- os.file_name(cn.file_path)
- }
- 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, 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, cfg.include_examples, dcs.table))
- }
-}
-
-fn (cfg DocConfig) gen_html(idx int) string {
- mut symbols_toc := strings.new_builder(200)
- mut modules_toc := strings.new_builder(200)
- mut contents := strings.new_builder(200)
- dcs := cfg.docs[idx]
- dcs_contents := dcs.contents.arr()
- // generate toc first
- 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)
- }
- for cn in dcs_contents {
- cfg.write_content(&cn, &dcs, mut contents)
- write_toc(cn, mut symbols_toc)
- } // write head
- // write css
- version := if cfg.manifest.version.len != 0 { cfg.manifest.version } else { '' }
- header_name := if cfg.is_multi && cfg.docs.len > 1 {
- os.file_name(os.real_path(cfg.input_path))
- } else {
- dcs.head.name
- }
- // write nav1
- if cfg.is_multi || cfg.docs.len > 1 {
- mut submod_prefix := ''
- for i, doc in cfg.docs {
- if i - 1 >= 0 && doc.head.name.starts_with(submod_prefix + '.') {
- continue
- }
- names := doc.head.name.split('.')
- submod_prefix = if names.len > 1 { names[0] } else { doc.head.name }
- mut href_name := './${doc.head.name}.html'
- if (cfg.is_vlib && doc.head.name == 'builtin' && !cfg.include_readme) ||
- doc.head.name == 'README' {
- href_name = './index.html'
- } else if submod_prefix !in cfg.docs.map(it.head.name) {
- href_name = '#'
- }
- submodules := cfg.docs.filter(it.head.name.starts_with(submod_prefix + '.'))
- dropdown := if submodules.len > 0 { cfg.assets['arrow_icon'] } else { '' }
- active_class := if doc.head.name == dcs.head.name { ' active' } else { '' }
- modules_toc.write('
')
- for j, cdoc in submodules {
- if j == 0 {
- modules_toc.write('')
- }
- submod_name := cdoc.head.name.all_after(submod_prefix + '.')
- sub_selected_classes := if cdoc.head.name == dcs.head.name {
- ' class="active"'
- } else {
- ''
- }
- modules_toc.write('$submod_name ')
- if j == submodules.len - 1 {
- modules_toc.write(' ')
- }
- }
- modules_toc.write(' ')
- }
- }
- modules_toc_str := modules_toc.str()
- symbols_toc_str := symbols_toc.str()
- modules_toc.free()
- symbols_toc.free()
- return html_content.replace('{{ title }}', dcs.head.name).replace('{{ head_name }}',
- header_name).replace('{{ version }}', version).replace('{{ light_icon }}', cfg.assets['light_icon']).replace('{{ dark_icon }}',
- cfg.assets['dark_icon']).replace('{{ menu_icon }}', cfg.assets['menu_icon']).replace('{{ head_assets }}',
- if cfg.inline_assets {
- '\n${tabs[0]}\n${tabs[0]}\n${tabs[0]}'
- } else {
- '\n${tabs[0]}
\n${tabs[0]}
\n${tabs[0]}'
- }).replace('{{ toc_links }}', if cfg.is_multi || cfg.docs.len > 1 {
- modules_toc_str
- } else {
- symbols_toc_str
- }).replace('{{ contents }}', contents.str()).replace('{{ right_content }}', if cfg.is_multi &&
- cfg.docs.len > 1 && dcs.head.name != 'README' {
- '
'
- } else {
- ''
- }).replace('{{ footer_content }}', cfg.gen_footer_text(idx)).replace('{{ footer_assets }}',
- if cfg.inline_assets {
- ''
- } else {
- ''
- })
-}
-
-fn (cfg DocConfig) gen_plaintext(idx int) string {
- dcs := cfg.docs[idx]
+fn (vd VDoc) gen_plaintext(d doc.Doc) string {
+ cfg := vd.cfg
mut pw := strings.new_builder(200)
- pw.writeln('$dcs.head.content\n')
+ pw.writeln('$d.head.content\n')
comments := if cfg.include_examples {
- dcs.head.merge_comments()
+ d.head.merge_comments()
} else {
- dcs.head.merge_comments_without_examples()
+ d.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)
+ vd.write_plaintext_content(d.contents.arr(), mut pw)
return pw.str()
}
-fn (cfg DocConfig) write_plaintext_content(contents []doc.DocNode, mut pw strings.Builder) {
+fn (vd VDoc) write_plaintext_content(contents []doc.DocNode, mut pw strings.Builder) {
+ cfg := vd.cfg
for cn in contents {
if cn.content.len > 0 {
pw.writeln(cn.content)
@@ -649,89 +122,32 @@ fn (cfg DocConfig) write_plaintext_content(contents []doc.DocNode, mut pw string
pw.writeln('Location: $cn.file_path:$cn.pos.line\n')
}
}
- cfg.write_plaintext_content(cn.children, mut pw)
+ vd.write_plaintext_content(cn.children, mut pw)
}
}
-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')
- 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')
- }
- cfg.write_markdown_content(dcs.contents.arr(), mut cw, mut hw, 0, with_toc)
- footer_text := cfg.gen_footer_text(idx)
- cw.writeln('#### $footer_text')
- return hw.str() + '\n' + cw.str()
-}
-
-fn (cfg DocConfig) write_markdown_content(contents []doc.DocNode, mut cw strings.Builder, mut hw strings.Builder, indent int, with_toc bool) {
- for cn in contents {
- if with_toc && cn.name.len > 0 {
- hw.writeln(' '.repeat(2 * indent) + '- [#$cn.name](${slug(cn.name)})')
- cw.writeln('## $cn.name')
- }
- if cn.content.len > 0 {
- 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)
- }
-}
-
-fn (cfg DocConfig) gen_footer_text(idx int) string {
- current_doc := cfg.docs[idx]
- footer_text := 'Powered by vdoc.'
- if cfg.no_timestamp {
- return footer_text
- }
- generated_time := current_doc.time_generated
- time_str := '$generated_time.day $generated_time.smonth() $generated_time.year $generated_time.hhmmss()'
- return '$footer_text Generated on: $time_str'
-}
-
-fn (cfg DocConfig) render_doc(doc doc.Doc, i int) (string, string) {
- name := cfg.get_file_name(doc.head.name)
- output := match cfg.output_type {
- .html { cfg.gen_html(i) }
- .markdown { cfg.gen_markdown(i, true) }
- .json { cfg.gen_json(i) }
- else { cfg.gen_plaintext(i) }
+fn (vd VDoc) render_doc(d doc.Doc, out Output) (string, string) {
+ name := vd.get_file_name(d.head.name, out)
+ output := match out.typ {
+ .html { vd.gen_html(d) }
+ .markdown { vd.gen_markdown(d, true) }
+ .json { vd.gen_json(d) }
+ else { vd.gen_plaintext(d) }
}
return name, output
}
// get_file_name returns the final file name from a module name
-fn (cfg DocConfig) get_file_name(mod string) string {
+fn (vd VDoc) get_file_name(mod string, out Output) string {
+ cfg := vd.cfg
mut name := mod
// since builtin is generated first, ignore it
if (cfg.is_vlib && mod == 'builtin' && !cfg.include_readme) || mod == 'README' {
name = 'index'
- } else if !cfg.is_multi && !os.is_dir(cfg.output_path) {
- name = os.file_name(cfg.output_path)
+ } else if !cfg.is_multi && !os.is_dir(out.path) {
+ name = os.file_name(out.path)
}
- name = name + match cfg.output_type {
+ name = name + match out.typ {
.html { '.html' }
.markdown { '.md' }
.json { '.json' }
@@ -740,153 +156,47 @@ fn (cfg DocConfig) get_file_name(mod string) string {
return name
}
-fn (cfg DocConfig) work_processor(mut work sync.Channel, mut wg sync.WaitGroup) {
+fn (vd VDoc) work_processor(mut work sync.Channel, mut wg sync.WaitGroup) {
for {
mut pdoc := ParallelDoc{}
if !work.pop(&pdoc) {
break
}
- file_name, content := cfg.render_doc(pdoc.d, pdoc.i)
- output_path := os.join_path(cfg.output_path, file_name)
- println('Generating $output_path')
+ file_name, content := vd.render_doc(pdoc.d, pdoc.out)
+ output_path := os.join_path(pdoc.out.path, file_name)
+ println('Generating $pdoc.out.typ in "$output_path"')
os.write_file(output_path, content)
}
wg.done()
}
-fn (cfg DocConfig) render_parallel() {
+fn (vd VDoc) render_parallel(out Output) {
vjobs := runtime.nr_jobs()
- mut work := sync.new_channel
(cfg.docs.len)
+ mut work := sync.new_channel(vd.docs.len)
mut wg := sync.new_waitgroup()
- for i in 0 .. cfg.docs.len {
- p_doc := ParallelDoc{cfg.docs[i], i}
+ for i in 0 .. vd.docs.len {
+ p_doc := ParallelDoc{vd.docs[i], out}
work.push(&p_doc)
}
work.close()
wg.add(vjobs)
for _ in 0 .. vjobs {
- go cfg.work_processor(mut work, mut wg)
+ go vd.work_processor(mut work, mut wg)
}
wg.wait()
}
-fn (cfg DocConfig) render() map[string]string {
+fn (vd VDoc) render(out Output) map[string]string {
mut docs := map[string]string{}
- for i, doc in cfg.docs {
- name, output := cfg.render_doc(doc, i)
+ for doc in vd.docs {
+ name, output := vd.render_doc(doc, out)
docs[name] = output.trim_space()
}
- cfg.vprintln('Rendered: ' + docs.keys().str())
+ vd.vprintln('Rendered: ' + docs.keys().str())
return docs
}
-fn (mut cfg DocConfig) collect_search_index() {
- if cfg.output_type != .html {
- return
- }
- 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(comments)
- link: cfg.get_file_name(mod)
- }
- for _, dn in doc.contents {
- cfg.create_search_results(mod, dn)
- }
- }
-}
-
-fn (mut cfg DocConfig) create_search_results(mod string, dn doc.DocNode) {
- if dn.kind == .const_group {
- return
- }
- 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 != '' {
- '$dn.kind ($dn.parent_name)'
- } else {
- '$dn.kind '
- }
- description: dn_description
- badge: mod
- link: cfg.get_file_name(mod) + '#' + get_node_id(dn)
- }
- for child in dn.children {
- cfg.create_search_results(mod, child)
- }
-}
-
-fn trim_doc_node_description(description string) string {
- mut dn_description := description.replace_each(['\r\n', '\n', '"', '\\"'])
- // 80 is enough to fill one line
- if dn_description.len > 80 {
- dn_description = dn_description[..80]
- }
- if '\n' in dn_description {
- dn_description = dn_description.split('\n')[0]
- }
- // if \ is last character, it ends with \" which leads to a JS error
- if dn_description.ends_with('\\') {
- dn_description = dn_description.trim_right('\\')
- }
- return dn_description
-}
-
-fn (cfg DocConfig) render_search_index() {
- mut js_search_index := strings.new_builder(200)
- mut js_search_data := strings.new_builder(200)
- js_search_index.write('var searchModuleIndex = [')
- js_search_data.write('var searchModuleData = [')
- for i, title in cfg.search_module_index {
- data := cfg.search_module_data[i]
- js_search_index.write('"$title",')
- js_search_data.write('["$data.description","$data.link"],')
- }
- js_search_index.writeln('];')
- js_search_index.write('var searchIndex = [')
- js_search_data.writeln('];')
- js_search_data.write('var searchData = [')
- for i, title in cfg.search_index {
- data := cfg.search_data[i]
- js_search_index.write('"$title",')
- // array instead of object to reduce file size
- js_search_data.write('["$data.badge","$data.description","$data.link","$data.prefix"],')
- }
- js_search_index.writeln('];')
- js_search_data.writeln('];')
- out_file_path := os.join_path(cfg.output_path, 'search_index.js')
- os.write_file(out_file_path, js_search_index.str() + js_search_data.str())
-}
-
-fn (mut cfg DocConfig) render_static() {
- if cfg.output_type != .html {
- return
- }
- cfg.assets = {
- 'doc_css': cfg.get_resource(css_js_assets[0], true)
- 'normalize_css': cfg.get_resource(css_js_assets[1], true)
- 'doc_js': cfg.get_resource(css_js_assets[2], !cfg.serve_http)
- 'dark_mode_js': cfg.get_resource(css_js_assets[3], !cfg.serve_http)
- 'light_icon': cfg.get_resource('light.svg', true)
- 'dark_icon': cfg.get_resource('dark.svg', true)
- 'menu_icon': cfg.get_resource('menu.svg', true)
- 'arrow_icon': cfg.get_resource('arrow.svg', true)
- }
-}
-
-fn (cfg DocConfig) get_readme(path string) string {
+fn (vd VDoc) get_readme(path string) string {
mut fname := ''
for name in ['readme', 'README'] {
if os.exists(os.join_path(path, '${name}.md')) {
@@ -898,12 +208,13 @@ fn (cfg DocConfig) get_readme(path string) string {
return ''
}
readme_path := os.join_path(path, '${fname}.md')
- cfg.vprintln('Reading README file from $readme_path')
+ vd.vprintln('Reading README file from $readme_path')
readme_contents := os.read_file(readme_path) or { '' }
return readme_contents
}
-fn (cfg DocConfig) emit_generate_err(err string, errcode int) {
+fn (vd VDoc) emit_generate_err(err string, errcode int) {
+ cfg := vd.cfg
mut err_msg := err
if errcode == 1 {
mod_list := get_modules_list(cfg.input_path, []string{})
@@ -916,20 +227,25 @@ fn (cfg DocConfig) emit_generate_err(err string, errcode int) {
eprintln(err_msg)
}
-fn (mut cfg DocConfig) generate_docs_from_file() {
- if cfg.output_path.len == 0 {
- if cfg.output_type == .unset {
- cfg.output_type = .stdout
- } else {
- cfg.vprintln('No output path has detected. Using input path instead.')
- cfg.output_path = cfg.input_path
- }
- } else if cfg.output_type == .unset {
- cfg.vprintln('Output path detected. Identifying output type..')
- ext := os.file_ext(cfg.output_path)
- cfg.set_output_type_from_str(ext.all_after('.'))
+fn (mut vd VDoc) generate_docs_from_file() {
+ cfg := vd.cfg
+ mut out := Output{
+ path: cfg.output_path
+ typ: cfg.output_type
}
- if cfg.include_readme && cfg.output_type !in [.html, .stdout] {
+ if out.path.len == 0 {
+ if cfg.output_type == .unset {
+ out.typ = .stdout
+ } else {
+ vd.vprintln('No output path has detected. Using input path instead.')
+ out.path = cfg.input_path
+ }
+ } else if out.typ == .unset {
+ vd.vprintln('Output path detected. Identifying output type..')
+ ext := os.file_ext(out.path)
+ out.typ = set_output_type_from_str(ext.all_after('.'))
+ }
+ if cfg.include_readme && out.typ !in [.html, .stdout] {
eprintln('vdoc: Including README.md for doc generation is supported on HTML output, or when running directly in the terminal.')
exit(1)
}
@@ -942,20 +258,20 @@ fn (mut cfg DocConfig) generate_docs_from_file() {
}
manifest_path := os.join_path(dir_path, 'v.mod')
if os.exists(manifest_path) {
- cfg.vprintln('Reading v.mod info from $manifest_path')
+ vd.vprintln('Reading v.mod info from $manifest_path')
if manifest := vmod.from_file(manifest_path) {
- cfg.manifest = manifest
+ vd.manifest = manifest
}
}
if cfg.include_readme {
- readme_contents := cfg.get_readme(dir_path)
+ readme_contents := vd.get_readme(dir_path)
comment := doc.DocComment{
text: readme_contents
}
- if cfg.output_type == .stdout {
+ if out.typ == .stdout {
println(markdown.to_plain(readme_contents))
- } else if cfg.output_type == .html && cfg.is_multi {
- cfg.docs << doc.Doc{
+ } else if out.typ == .html && cfg.is_multi {
+ vd.docs << doc.Doc{
head: doc.DocNode{
name: 'README'
comments: [comment]
@@ -972,15 +288,15 @@ fn (mut cfg DocConfig) generate_docs_from_file() {
is_local_and_single := cfg.is_local && !cfg.is_multi
for dirpath in dirs {
mut dcs := doc.Doc{}
- cfg.vprintln('Generating docs for $dirpath')
+ vd.vprintln('Generating $out.typ docs for "$dirpath"')
if is_local_and_single {
dcs = doc.generate_with_pos(dirpath, cfg.local_filename, cfg.local_pos) or {
- cfg.emit_generate_err(err, errcode)
+ vd.emit_generate_err(err, errcode)
exit(1)
}
} else {
dcs = doc.generate(dirpath, cfg.pub_only, true) or {
- cfg.emit_generate_err(err, errcode)
+ vd.emit_generate_err(err, errcode)
exit(1)
}
}
@@ -989,7 +305,7 @@ 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)
+ readme_contents := vd.get_readme(dirpath)
comment := doc.DocComment{
text: readme_contents
}
@@ -1011,21 +327,25 @@ fn (mut cfg DocConfig) generate_docs_from_file() {
}
}
}
- cfg.docs << dcs
+ vd.docs << dcs
}
+ // Important. Let builtin be in the top of the module list
+ // if we are generating docs for vlib.
if cfg.is_vlib {
- mut docs := cfg.docs.filter(it.head.name == 'builtin')
- docs << cfg.docs.filter(it.head.name != 'builtin')
- cfg.docs = docs
+ mut docs := vd.docs.filter(it.head.name == 'builtin')
+ docs << vd.docs.filter(it.head.name != 'builtin')
+ vd.docs = docs
}
if cfg.serve_http {
- cfg.serve_html()
+ vd.serve_html(out)
return
}
- cfg.vprintln('Rendering docs...')
- if cfg.output_path.len == 0 || cfg.output_path == 'stdout' {
- cfg.render_static()
- outputs := cfg.render()
+ vd.vprintln('Rendering docs...')
+ if out.path.len == 0 || out.path == 'stdout' {
+ if out.typ == .html {
+ vd.render_static_html(cfg.serve_http, out)
+ }
+ outputs := vd.render(out)
if outputs.len == 0 {
println('No documentation for $dirs')
} else {
@@ -1033,146 +353,50 @@ fn (mut cfg DocConfig) generate_docs_from_file() {
println(outputs[first])
}
} else {
- if !os.is_dir(cfg.output_path) {
- cfg.output_path = os.real_path('.')
+ if !os.is_dir(out.path) {
+ out.path = os.real_path('.')
}
- if !os.exists(cfg.output_path) {
- os.mkdir(cfg.output_path) or { panic(err) }
+ if !os.exists(out.path) {
+ os.mkdir(out.path) or { panic(err) }
}
if cfg.is_multi {
- cfg.output_path = os.join_path(cfg.output_path, '_docs')
- if !os.exists(cfg.output_path) {
- os.mkdir(cfg.output_path) or { panic(err) }
+ out.path = os.join_path(out.path, '_docs')
+ if !os.exists(out.path) {
+ os.mkdir(out.path) or { panic(err) }
} else {
for fname in css_js_assets {
- os.rm(os.join_path(cfg.output_path, fname))
+ os.rm(os.join_path(out.path, fname))
}
}
}
- cfg.render_static()
- cfg.render_parallel()
+ if out.typ == .html {
+ vd.render_static_html(cfg.serve_http, out)
+ }
+ vd.render_parallel(out)
println('Creating search index...')
- cfg.collect_search_index()
- cfg.render_search_index()
- // move favicons to target directory
- println('Copying favicons...')
- favicons := os.ls(favicons_path) or { panic(err) }
- for favicon in favicons {
- favicon_path := os.join_path(favicons_path, favicon)
- destination_path := os.join_path(cfg.output_path, favicon)
- os.cp(favicon_path, destination_path)
+ if out.typ == .html {
+ vd.collect_search_index(out)
+ vd.render_search_index(out)
+ // move favicons to target directory
+ println('Copying favicons...')
+ favicons := os.ls(favicons_path) or { panic(err) }
+ for favicon in favicons {
+ favicon_path := os.join_path(favicons_path, favicon)
+ destination_path := os.join_path(out.path, favicon)
+ os.cp(favicon_path, destination_path)
+ }
}
}
}
-fn (mut cfg DocConfig) set_output_type_from_str(format string) {
- match format {
- 'htm', 'html' { cfg.output_type = .html }
- 'md', 'markdown' { cfg.output_type = .markdown }
- 'json' { cfg.output_type = .json }
- 'stdout' { cfg.output_type = .stdout }
- else { cfg.output_type = .plaintext }
- }
- cfg.vprintln('Setting output type to "$cfg.output_type"')
-}
-
-fn (cfg DocConfig) vprintln(str string) {
- if cfg.is_verbose {
+fn (vd VDoc) vprintln(str string) {
+ if vd.cfg.is_verbose {
println('vdoc: $str')
}
}
-fn get_ignore_paths(path string) ?[]string {
- ignore_file_path := os.join_path(path, '.vdocignore')
- ignore_content := os.read_file(ignore_file_path) or {
- return error_with_code('ignore file not found.', 1)
- }
- mut res := []string{}
- if ignore_content.trim_space().len > 0 {
- rules := ignore_content.split_into_lines().map(it.trim_space())
- mut final := []string{}
- for rule in rules {
- if rule.contains('*.') || rule.contains('**') {
- println('vdoc: Wildcards in ignore rules are not allowed for now.')
- continue
- }
- final << rule
- }
- res = final.map(os.join_path(path, it.trim_right('/')))
- } else {
- mut dirs := os.ls(path) or { return []string{} }
- res = dirs.map(os.join_path(path, it)).filter(os.is_dir(it))
- }
- return res.map(it.replace('/', os.path_separator))
-}
-
-fn is_included(path string, ignore_paths []string) bool {
- if path.len == 0 {
- return true
- }
- for ignore_path in ignore_paths {
- if ignore_path !in path {
- continue
- }
- return false
- }
- return true
-}
-
-fn get_modules_list(path string, ignore_paths2 []string) []string {
- files := os.ls(path) or { return []string{} }
- mut ignore_paths := get_ignore_paths(path) or { []string{} }
- ignore_paths << ignore_paths2
- mut dirs := []string{}
- for file in files {
- fpath := os.join_path(path, file)
- if os.is_dir(fpath) && is_included(fpath, ignore_paths) && !os.is_link(path) {
- dirs << get_modules_list(fpath, ignore_paths.filter(it.starts_with(fpath)))
- } else if fpath.ends_with('.v') && !fpath.ends_with('_test.v') {
- if path in dirs {
- continue
- }
- dirs << path
- }
- }
- dirs.sort()
- return dirs
-}
-
-fn (cfg DocConfig) get_resource(name string, minify bool) string {
- path := os.join_path(res_path, name)
- mut res := os.read_file(path) or { panic('vdoc: could not read $path') }
- if minify {
- if name.ends_with('.js') {
- res = js_compress(res)
- } else {
- res = res.split_into_lines().map(it.trim_space()).join('')
- }
- }
- // TODO: Make SVG inline for now
- if cfg.inline_assets || path.ends_with('.svg') {
- return res
- } else {
- output_path := os.join_path(cfg.output_path, name)
- if !os.exists(output_path) {
- println('Generating $output_path')
- os.write_file(output_path, res)
- }
- return name
- }
-}
-
-fn main() {
- if os.args.len < 2 || '-h' in os.args || '--help' in os.args || os.args[1..] == ['doc', 'help'] {
- os.system('$vexe help doc')
- exit(0)
- }
- args := os.args[2..].clone()
- mut cfg := DocConfig{
- manifest: vmod.Manifest{
- repo_url: ''
- }
- }
+fn parse_arguments(args []string) Config {
+ mut cfg := Config{}
for i := 0; i < args.len; i++ {
arg := args[i]
current_args := args[i..]
@@ -1192,7 +416,7 @@ fn main() {
eprintln('vdoc: "$format" is not a valid format. Only $allowed_str are allowed.')
exit(1)
}
- cfg.set_output_type_from_str(format)
+ cfg.output_type = set_output_type_from_str(format)
i++
}
'-inline-assets' {
@@ -1265,10 +489,7 @@ fn main() {
}
}
}
- if cfg.input_path.len == 0 {
- eprintln('vdoc: No input path found.')
- exit(1)
- }
+ // Correct from configuration from user input
if cfg.output_path == 'stdout' && cfg.output_type == .html {
cfg.inline_assets = true
}
@@ -1284,19 +505,34 @@ fn main() {
cfg.is_multi = true
cfg.input_path = os.join_path(vroot, 'vlib')
} else if !is_path {
- cfg.vprintln('Input "$cfg.input_path" is not a valid path. Looking for modules named "$cfg.input_path"...')
+ // TODO vd.vprintln('Input "$cfg.input_path" is not a valid path. Looking for modules named "$cfg.input_path"...')
mod_path := doc.lookup_module(cfg.input_path) or {
eprintln('vdoc: $err')
exit(1)
}
cfg.input_path = mod_path
}
- cfg.generate_docs_from_file()
+ return cfg
}
-fn is_module_readme(dn doc.DocNode) bool {
- if dn.comments.len > 0 && dn.content == 'module $dn.name' {
- return true
+fn main() {
+ if os.args.len < 2 || '-h' in os.args || '--help' in os.args || os.args[1..] == ['doc', 'help'] {
+ os.system('$vexe help doc')
+ exit(0)
}
- return false
+ args := os.args[2..].clone()
+ cfg := parse_arguments(args)
+ if cfg.input_path.len == 0 {
+ eprintln('vdoc: No input path found.')
+ exit(1)
+ }
+ // Config is immutable from this point on
+ mut vd := VDoc{
+ cfg: cfg
+ manifest: vmod.Manifest{
+ repo_url: ''
+ }
+ }
+ vd.vprintln('Setting output type to "$cfg.output_type"')
+ vd.generate_docs_from_file()
}
diff --git a/cmd/tools/vtest-cleancode.v b/cmd/tools/vtest-cleancode.v
index f7e0f19e9e..d50c9443f9 100644
--- a/cmd/tools/vtest-cleancode.v
+++ b/cmd/tools/vtest-cleancode.v
@@ -18,8 +18,8 @@ const (
'nonexistant',
]
vfmt_verify_list = [
- 'cmd/tools/vdoc/vdoc.v',
'cmd/v/v.v',
+ 'cmd/tools/vdoc/',
'vlib/arrays/',
'vlib/benchmark/',
'vlib/bitfield/',
diff --git a/cmd/tools/vtest-compiler.v b/cmd/tools/vtest-compiler.v
index 1fb10fa2ef..325b1ad474 100644
--- a/cmd/tools/vtest-compiler.v
+++ b/cmd/tools/vtest-compiler.v
@@ -34,7 +34,7 @@ fn v_test_compiler(vargs string) {
eprintln('v.c can be compiled without warnings. This is good :)')
}
}
- building_tools_failed := testing.v_build_failing(vargs, 'cmd/tools')
+ building_tools_failed := os.system('"$vexe" build-tools') != 0
eprintln('')
testing.eheader('Testing all _test.v files...')
mut compiler_test_session := testing.new_test_session(vargs)