<$head_tag>$sym_name$hash_link$head_tag>')
+ } else {
+ dnw.write_string('${tabs[2]}
<$head_tag>$dn.kind $sym_name$hash_link$head_tag>')
+ }
+ if link.len != 0 {
+ dnw.write_string('
$link_svg ')
+ }
+ dnw.write_string('
')
+ }
+ if tags.len > 0 || has_deprecated {
+ mut attributes := if has_deprecated {
+ '
deprecated
'
+ } else {
+ ''
+ }
+ attributes += tags.map('
$it
').join('')
+ dnw.writeln('
$attributes
')
+ }
+ if !head && dn.content.len > 0 {
+ dnw.writeln('
$highlighted_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()
+ return dnw_str
+}
+
+fn html_tag_escape(str string) string {
+ excaped_string := str.replace_each(['<', '<', '>', '>'])
+ mut re := regex.regex_opt(r'`.+[(<)(>)].+`') or { regex.RE{} }
+ if re.find_all_str(excaped_string).len > 0 {
+ return str
+ }
+ return excaped_string
+}
+
+/*
+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_string(trimmed)
+ }
+ js_str := js.str()
+ 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) {
+ if dn.comments.len == 0 || (dn.comments.len > 0 && dn.comments[0].text.len == 0) {
+ return
+ }
+ toc.write_string('
README ')
+ } else if dn.name != 'Constants' {
+ toc.write_string('$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_string('$dn.name ')
+ }
+ toc.writeln('')
+}
diff --git a/v_windows/v/cmd/tools/vdoc/html_tag_escape_test.v b/v_windows/v/cmd/tools/vdoc/html_tag_escape_test.v
new file mode 100644
index 0000000..64a7d1c
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/html_tag_escape_test.v
@@ -0,0 +1,6 @@
+module main
+
+fn test_html_tag_escape() {
+ assert html_tag_escape('
') == '<abc>'
+ assert html_tag_escape('``') == '``'
+}
diff --git a/v_windows/v/cmd/tools/vdoc/markdown.v b/v_windows/v/cmd/tools/vdoc/markdown.v
new file mode 100644
index 0000000..cc24ad2
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/markdown.v
@@ -0,0 +1,55 @@
+module main
+
+import strings
+import v.doc
+
+fn markdown_escape_script_tags(str string) string {
+ return str.replace_each(['', '`'])
+}
+
+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) + '- [${slug(cn.name)}](#$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(r'[[Return to contents]](#Contents)')
+ cw.writeln('')
+ }
+ vd.write_markdown_content(cn.children, mut cw, mut hw, indent + 1, with_toc)
+ }
+}
diff --git a/v_windows/v/cmd/tools/vdoc/resources/arrow.svg b/v_windows/v/cmd/tools/vdoc/resources/arrow.svg
new file mode 100644
index 0000000..2a0456f
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/arrow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/v_windows/v/cmd/tools/vdoc/resources/dark-mode.js b/v_windows/v/cmd/tools/vdoc/resources/dark-mode.js
new file mode 100644
index 0000000..075dbb5
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/dark-mode.js
@@ -0,0 +1,6 @@
+(function() {
+ var html = document.getElementsByTagName('html')[0];
+ if (localStorage.getItem('dark-mode') === 'true') {
+ html.classList.add('dark');
+ }
+})();
diff --git a/v_windows/v/cmd/tools/vdoc/resources/dark.svg b/v_windows/v/cmd/tools/vdoc/resources/dark.svg
new file mode 100644
index 0000000..2067d05
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/dark.svg
@@ -0,0 +1 @@
+
diff --git a/v_windows/v/cmd/tools/vdoc/resources/doc.css b/v_windows/v/cmd/tools/vdoc/resources/doc.css
new file mode 100644
index 0000000..5f7058e
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/doc.css
@@ -0,0 +1,732 @@
+:root {
+ --background-color: #fff;
+ --link-color: #2779bd;
+ --text-color: #000;
+ --ref-symbol-color: #dae1e7;
+ --ref-symbol-hover-color: #b8c2cc;
+ --title-bottom-line-color: #f1f5f8;
+ --footer-top-line-color: #f1f5f8;
+ --footer-text-color: #616161;
+ --code-signature-border-color: #a0aec0;
+ --menu-background-color: #4b6c88;
+ --menu-text-color: #fff;
+ --menu-indent-line-color: #3b3b3b66;
+ --menu-indent-line-active-color: #00000066;
+ --menu-scrollbar-color: #a0aec0;
+ --menu-toggle-icon-color: #fff;
+ --menu-toggle-icon-hover-color: #00000044;
+ --menu-search-background-color: #00000044;
+ --menu-search-font-color: #fff;
+ --menu-search-result-background-hover-color: #00000021;
+ --menu-search-separator-color: #00000044;
+ --menu-search-title-text-color: #d5efff;
+ --menu-search-badge-background-color: #00000044;
+ --menu-search-badge-background-hover-color: #0000004d;
+ --toc-text-color: #2779bd;
+ --toc-indicator-color: #4299e1;
+ --code-default-text-color: #5c6e74;
+ --code-background-color: #edf2f7;
+ --code-keyword-text-color: #2b6cb0;
+ --code-builtin-text-color: #0a0a0a;
+ --code-function-text-color: #319795;
+ --code-comment-text-color: #93a1a1;
+ --code-punctuation-text-color: #999999;
+ --code-symbol-text-color: #702459;
+ --code-operator-text-color: #a67f59;
+ --attribute-deprecated-background-color: #f59f0b48;
+ --attribute-deprecated-text-color: #92400e;
+ --attribute-text-color: #000000af;
+}
+:root.dark .dark-icon {
+ display: none;
+}
+:root:not(.dark) .light-icon {
+ display: none;
+}
+
+.dark body {
+ --background-color: #1a202c;
+ --text-color: #fff;
+ --link-color: #90cdf4;
+ --ref-symbol-color: #2d3748;
+ --ref-symbol-hover-color: #4a5568;
+ --title-bottom-line-color: #2d3748;
+ --footer-top-line-color: #2d3748;
+ --footer-text-color: #bbd3e1;
+ --code-signature-border-color: #4a5568;
+ --menu-background-color: #2d3748;
+ --menu-text-color: #fff;
+ --menu-indent-line-color: #4a5568;
+ --menu-indent-line-active-color: #90cdf4; /*#4a5568*/
+ --menu-scrollbar-color: #4a5568;
+ --menu-toggle-icon-color: #fff;
+ --menu-search-background-color: #4a5568;
+ --menu-search-font-color: #fff;
+ --menu-search-separator-color: #4a5568;
+ --menu-search-title-text-color: #90cdf4;
+ --menu-search-badge-background-color: #4a5568;
+ --menu-search-badge-background-hover-color: #4a5568;
+ --toc-text-color: #90cdf4;
+ --toc-indicator-color: #4299e1;
+ --code-default-text-color: #cbd5e0;
+ --code-background-color: #2d3748;
+ --code-builtin-text-color: #68d391;
+ --code-keyword-text-color: #63b3ed;
+ --code-function-text-color: #4fd1c5;
+ --code-comment-text-color: #a0aec0;
+ --code-punctuation-text-color: #a0aec0;
+ --code-symbol-text-color: #ed64a6;
+ --attribute-background-color: #ffffff20;
+ --attribute-text-color: #ffffffaf;
+ --attribute-deprecated-text-color: #fef3c7;
+}
+html {
+ height: 100%;
+}
+body {
+ margin: 0;
+ font-family: Jost, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica,
+ Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ background-color: #fff;
+ background-color: var(--background-color);
+ color: #000;
+ color: var(--text-color);
+ height: 100%;
+}
+#page {
+ height: 100%;
+ padding-top: 56px;
+ box-sizing: border-box;
+ overflow: hidden;
+}
+
+/** Reset for menus */
+.doc-nav ul,
+.doc-toc ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+/* Left nav */
+.doc-nav {
+ position: fixed;
+ width: 100%;
+ left: 0;
+ right: 0;
+ top: 0;
+ display: flex;
+ background-color: #4b6c88;
+ background-color: var(--menu-background-color);
+ color: #fff;
+ color: var(--menu-text-color);
+ flex-direction: column;
+ overflow-y: auto;
+ height: 100vh;
+ z-index: 10;
+ scrollbar-width: thin;
+ scrollbar-color: #a0aec0 transparent;
+ scrollbar-color: var(--menu-scrollbar-color) transparent;
+ font-family: Jost, sans-serif;
+}
+*::-webkit-scrollbar {
+ width: 4px;
+ height: 4px;
+}
+*::-webkit-scrollbar-track {
+ background: transparent;
+}
+*::-webkit-scrollbar-thumb {
+ background-color: #a0aec0;
+ background-color: var(--menu-scrollbar-color);
+ border: 3px solid transparent;
+}
+.doc-nav .content li {
+ line-height: 1.8;
+}
+.doc-nav .content.show {
+ display: flex;
+}
+.doc-nav .content.hidden {
+ display: none;
+}
+.doc-nav #toggle-menu {
+ cursor: pointer;
+ padding: 0.3rem;
+ fill: #fff;
+ fill: var(--menu-toggle-icon-color);
+}
+.doc-nav > .heading-container {
+ position: relative;
+ /* IE11 */
+ position: sticky;
+ position: -webkit-sticky;
+ top: 0;
+ background-color: #4b6c88;
+ background-color: var(--menu-background-color);
+ z-index: 10;
+}
+.doc-nav > .heading-container > .heading {
+ display: flex;
+ flex-direction: column;
+}
+.doc-nav > .heading-container > .heading > .info {
+ display: flex;
+ padding: 0 1rem;
+ height: 56px;
+}
+.doc-nav > .heading-container > .heading > .info > .module {
+ font-size: 1.6rem;
+ font-weight: 500;
+ margin: 0;
+}
+.doc-nav > .heading-container > .heading > .info > .toggle-version-container {
+ display: flex;
+ align-items: center;
+}
+.doc-nav
+ > .heading-container
+ > .heading
+ > .info
+ > .toggle-version-container
+ > #dark-mode-toggle {
+ cursor: pointer;
+ fill: #fff;
+ display: flex;
+ visibility: hidden;
+}
+.doc-nav
+ > .heading-container
+ > .heading
+ > .info
+ > .toggle-version-container
+ > #dark-mode-toggle
+ > svg {
+ width: 1.2rem;
+ height: 1.2rem;
+}
+.doc-nav > .heading-container > .heading > #search {
+ margin: 0.6rem 1.2rem 1rem 1.2rem;
+ border: none;
+ border-radius: 0.2rem;
+ padding: 0.5rem 1rem;
+ outline: none;
+ background-color: #00000044;
+ background-color: var(--menu-search-background-color);
+ color: #fff;
+ color: var(--menu-search-text-color);
+}
+.doc-nav > .heading-container > .heading > #search::placeholder {
+ color: #edf2f7;
+ text-transform: uppercase;
+ font-size: 12px;
+ font-weight: 600;
+}
+.doc-nav > .heading-container > .heading > #search:-ms-input-placeholder {
+ color: #edf2f7;
+ text-transform: uppercase;
+ font-size: 12px;
+ font-weight: 600;
+}
+.doc-nav > .content {
+ padding: 0 2rem 2rem 2rem;
+ display: flex;
+ flex-direction: column;
+}
+.doc-nav > .content > ul > li.active {
+ font-weight: 600;
+}
+.doc-nav > .content > ul > li.open ul {
+ display: initial;
+}
+.doc-nav > .content > ul > li.open > .menu-row > .dropdown-arrow {
+ transform: initial;
+}
+.doc-nav > .content > ul > li > .menu-row {
+ display: flex;
+ align-items: center;
+}
+.doc-nav > .content > ul > li > .menu-row > .dropdown-arrow {
+ transform: rotate(-90deg);
+ height: 18px;
+ width: 18px;
+ margin-left: calc(-18px - 0.3rem);
+ margin-right: 0.3rem;
+ cursor: pointer;
+ fill: #fff;
+ pointer-events: all;
+}
+.doc-nav > .content > ul > li > ul {
+ margin: 0.4rem 0;
+ display: none;
+}
+.doc-nav > .content > ul > li > ul > li {
+ border-color: #ffffff66;
+ border-color: var(--menu-indent-line-color);
+ border-left-width: 1.7px;
+ border-left-style: solid;
+ padding-left: 0.7rem;
+}
+.doc-nav > .content > ul > li > ul > li.active {
+ border-color: #00000066;
+ border-color: var(--menu-search-result-hover-background-color);
+}
+.doc-nav > .content a {
+ color: #fff;
+ color: var(--menu-text-color);
+ text-decoration: none;
+ user-select: none;
+}
+.doc-nav > .content a:hover {
+ text-decoration: underline;
+}
+.doc-nav .search.hidden {
+ display: none;
+}
+.doc-nav .search li {
+ line-height: 1.5;
+}
+.doc-nav > .search .result:hover {
+ background-color: #00000021;
+ background-color: var(--menu-search-result-background-hover-color);
+}
+.doc-nav > .search .result:hover > .link > .definition > .badge {
+ background-color: #0000004d;
+ background-color: var(--menu-search-badge-background-hover-color);
+}
+.doc-nav > .search .result > .link {
+ padding: 0.5rem 1.4rem;
+ text-decoration: none;
+ color: #fff;
+ color: var(--menu-text-color);
+ display: block;
+}
+.doc-nav > .search .result > .link > .definition {
+ display: flex;
+}
+.doc-nav > .search .result > .link > .definition > .title {
+ color: #90cdf4;
+ color: var(--menu-search-title-text-color);
+ font-size: 0.875rem;
+ font-weight: 500;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.doc-nav > .search .result > .link > .definition > .badge {
+ font-size: 0.75rem;
+ display: inline-flex;
+ padding: 0 0.5rem;
+ background-color: #00000044;
+ background-color: var(--menu-search-badge-background-color);
+ margin-left: auto;
+ align-items: center;
+ border-radius: 9999px;
+ font-weight: 500;
+}
+.doc-nav > .search .result > .link > .description {
+ font-family: Jost, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica,
+ Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ font-size: 0.75rem;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ margin-top: 0.25rem;
+}
+.doc-nav > .search > hr.separator {
+ margin: 0.5rem 0;
+ border-color: #00000044;
+ border-color: var(--menu-search-separator-color);
+ box-sizing: content-box;
+ height: 0;
+ border-width: 0;
+ border-top-width: 1px;
+ border-style: solid;
+ overflow: visible;
+}
+
+/* Main content */
+.doc-scrollview {
+ height: 100%;
+ overflow-y: scroll;
+}
+.doc-container {
+ display: flex;
+ flex-direction: column-reverse;
+}
+.doc-content {
+ display: flex;
+ flex-direction: column;
+ padding: 1rem;
+ overflow: hidden;
+}
+.doc-content img {
+ width: auto;
+ max-width: 100%;
+}
+.doc-content p {
+ font-size: 1rem;
+ line-height: 1.6;
+ letter-spacing: 0.025em;
+}
+.doc-content a {
+ color: #2779bd;
+ color: var(--link-color);
+}
+.doc-content p code {
+ font-size: 0.85rem;
+}
+.doc-content > .doc-node {
+ padding: 5rem 0 2rem 0;
+ margin-top: -4rem;
+ overflow: hidden;
+ word-break: break-all; /* IE11 */
+ word-break: break-word;
+}
+.doc-content > .doc-node.const:nth-child(2) {
+ padding-bottom: 0 !important;
+}
+.doc-content > .doc-node.const:not(:first-child) {
+ padding-top: 4rem;
+}
+.doc-content > .doc-node.const:not(:last-child) {
+ padding-bottom: 2rem;
+}
+.doc-content > .timestamp {
+ font-size: 0.8rem;
+ color: #b8c2cc;
+ color: var(--timestamp-color);
+}
+.doc-content > .doc-node > .title {
+ display: flex;
+ font-family: Jost, sans-serif;
+ font-weight: 500;
+ padding: 0.3rem;
+ align-items: center;
+ margin-bottom: 1rem;
+ border-bottom: 1px solid #f1f5f8;
+ border-bottom: 1px solid var(--title-bottom-line-color);
+}
+.doc-content > .doc-node > .attributes {
+ margin-bottom: 0.6rem;
+}
+.doc-content > .doc-node > .attributes > .attribute {
+ display: inline-block;
+ border-radius: 100px;
+ padding: 0.3rem 0.6rem;
+ background-color: var(--code-background-color);
+ color: var(--attribute-text-color);
+ margin-right: 0.8rem;
+}
+.doc-content > .doc-node > .attributes > .attribute-deprecated {
+ background-color: var(--attribute-deprecated-background-color);
+ color: var(--attribute-deprecated-text-color);
+}
+.doc-content > .doc-node > .title > .link {
+ display: flex;
+ margin-left: auto;
+ fill: #dae1e7;
+ fill: var(--ref-symbol-color);
+}
+.doc-content > .doc-node > .title > .link:hover {
+ fill: #b8c2cc;
+ fill: var(--ref-symbol-hover-color);
+}
+.doc-content > .doc-node h1 {
+ font-size: 2rem;
+}
+.doc-content > .doc-node h2 {
+ font-size: 1.3rem;
+}
+.doc-content > .doc-node .signature {
+ border-color: #a0aec0;
+ border-color: var(--code-signature-border-color);
+ border-left-width: 3px;
+ border-left-style: solid;
+}
+.doc-content > .doc-node > ul > li .task-list-item-checkbox {
+ margin-right: 0.5rem;
+}
+.doc-content > .doc-node > .title h1,
+.doc-content > .doc-node > .title h2,
+.doc-content > .doc-node > .title h3,
+.doc-content > .doc-node > .title h4,
+.doc-content > .doc-node > .title h5,
+.doc-content > .doc-node > .title h6 {
+ font-weight: 500;
+ margin: 0;
+}
+.doc-content > .doc-node > .title h1 a,
+.doc-content > .doc-node > .title h2 a,
+.doc-content > .doc-node > .title h3 a,
+.doc-content > .doc-node > .title h4 a,
+.doc-content > .doc-node > .title h5 a,
+.doc-content > .doc-node > .title h6 a {
+ text-decoration: none;
+ color: #dae1e7;
+ color: var(--ref-symbol-color);
+}
+.doc-content > .doc-node > .title h1 a:hover,
+.doc-content > .doc-node > .title h2 a:hover,
+.doc-content > .doc-node > .title h3 a:hover,
+.doc-content > .doc-node > .title h4 a:hover,
+.doc-content > .doc-node > .title h5 a:hover,
+.doc-content > .doc-node > .title h6 a:hover {
+ color: #b8c2cc;
+ color: var(--ref-symbol-hover-color);
+}
+.doc-content > .footer {
+ padding-top: 1rem;
+ margin-top: auto;
+ bottom: 1rem;
+ color: 616161;
+ color: var(--footer-text-color);
+ border-color: #f1f5f8;
+ border-color: var(--footer-top-line-color);
+ border-top-style: solid;
+ border-top-width: 1px;
+ font-size: 0.8rem;
+ font-weight: 500;
+}
+
+/* Right menu */
+.doc-toc {
+ right: 0;
+ top: 0;
+ height: 100%;
+ overflow-y: auto;
+ padding: 1rem 1rem 0 1rem;
+ width: 100%;
+ box-sizing: border-box;
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ font-family: Jost, sans-serif;
+}
+.doc-toc::-webkit-scrollbar {
+ display: none;
+}
+.doc-toc li {
+ line-height: 1.5;
+}
+.doc-toc a {
+ color: #2779bd;
+ color: var(--toc-text-color);
+ font-size: 0.9rem;
+ font-weight: 600;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: block;
+ text-decoration: none;
+ border-left-width: 2px;
+ border-left-style: solid;
+ border-color: transparent;
+ padding-left: 0.4rem;
+}
+.doc-toc a:hover {
+ text-decoration: underline;
+}
+.doc-toc a.active {
+ border-color: #4299e1;
+ border-color: var(--toc-indicator-color);
+}
+.doc-toc li ul {
+ margin: 0.2rem 0 0.2rem;
+ font-size: 0.7rem;
+ list-style: none;
+}
+.doc-toc li ul a {
+ font-weight: 400;
+ padding-left: 0.8rem;
+}
+
+/* Code highlight */
+pre,
+code,
+pre code {
+ color: #5c6e74;
+ color: var(--code-default-text-color);
+ font-size: 0.948em;
+ text-shadow: none;
+ font-family: "Jetbrains Mono", monospace;
+ background-color: #edf2f7;
+ background-color: var(--code-background-color);
+ border-radius: 0.25rem;
+}
+pre code {
+ direction: ltr;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ line-height: 1.5;
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
+ display: block;
+ overflow-x: auto;
+ padding: 1rem;
+}
+code {
+ padding: 0 0.2rem;
+}
+pre {
+ overflow: auto;
+ margin: 0;
+}
+.namespace {
+ opacity: 0.7;
+}
+.token.comment {
+ color: #93a1a1;
+ color: var(--code-comment-text-color);
+}
+.token.punctuation {
+ color: #999999;
+ color: var(--code-punctuation-text-color);
+}
+.token.number,
+.token.symbol {
+ color: #702459;
+ color: var(--code-symbol-text-color);
+}
+.token.string,
+.token.char,
+.token.builtin {
+ color: #38a169;
+ color: var(--code-builtin-text-color);
+}
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+ color: #a67f59;
+ color: var(--code-operator-text-color);
+ background: transparent;
+}
+.token.boolean,
+.token.keyword {
+ color: #2b6cb0;
+ color: var(--code-keyword-text-color);
+}
+.token.function {
+ color: #319795;
+ color: var(--code-function-text-color);
+}
+.examples > h4 {
+ margin: 0 0 0.4rem 0;
+}
+
+/* Medium screen and up */
+@media (min-width: 768px) {
+ *::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+ }
+ *::-webkit-scrollbar-thumb {
+ border: 3px solid transparent;
+ }
+ .doc-container {
+ flex-direction: row;
+ }
+ .doc-content {
+ font-size: 0.95rem;
+ flex: 1;
+ padding: 0rem 2rem 1rem 2rem;
+ }
+ .doc-toc {
+ position: relative;
+ /* IE11 */
+ position: sticky;
+ position: -webkit-sticky;
+ align-self: flex-start;
+ top: 56px;
+ height: auto;
+ height: 100vh;
+ min-width: 200px;
+ width: auto;
+ max-width: 300px;
+ }
+ .doc-toc > ul {
+ padding-bottom: 1rem;
+ }
+}
+
+@media (max-width: 1023px) {
+ .doc-nav.hidden {
+ height: auto;
+ }
+ .doc-nav.hidden #search {
+ display: none;
+ }
+ .doc-nav .search.mobile-hidden {
+ display: none;
+ }
+ .doc-nav > .heading-container > .heading > .info {
+ align-items: center;
+ }
+ .doc-nav > .heading-container > .heading > .info > .toggle-version-container {
+ flex-grow: 1;
+ padding: 0 1rem;
+ justify-content: space-between;
+ }
+}
+
+@media (min-width: 1024px) {
+ #page {
+ padding-top: 0;
+ }
+ .doc-nav {
+ width: 300px;
+ }
+ .doc-nav #toggle-menu {
+ display: none;
+ }
+ .doc-nav > .heading-container > .heading > .info {
+ height: auto;
+ padding: 1rem 2rem 0 2rem;
+ flex-direction: column-reverse;
+ justify-content: center;
+ }
+ .doc-nav > .heading-container > .heading > .info > .toggle-version-container {
+ align-items: center;
+ margin-bottom: 0.2rem;
+ display: flex;
+ flex-direction: row-reverse;
+ }
+ .doc-nav
+ > .heading-container
+ > .heading
+ > .info
+ > .toggle-version-container
+ > #dark-mode-toggle {
+ margin-right: auto;
+ }
+ .doc-nav .content.show,
+ .doc-nav .content.hidden {
+ display: flex;
+ }
+ .doc-content > .doc-node.const:nth-child(2) {
+ padding-bottom: 0 !important;
+ }
+ .doc-content > .doc-node.const:not(:first-child) {
+ padding-top: 0;
+ }
+ .doc-content > .doc-node.const:not(:last-child) {
+ padding-bottom: 1rem;
+ }
+ .doc-container {
+ margin-left: 300px;
+ }
+ .doc-node {
+ padding-top: 1rem !important;
+ margin-top: 0 !important;
+ }
+ .doc-toc {
+ top: 0;
+ }
+}
diff --git a/v_windows/v/cmd/tools/vdoc/resources/doc.js b/v_windows/v/cmd/tools/vdoc/resources/doc.js
new file mode 100644
index 0000000..c355d7e
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/doc.js
@@ -0,0 +1,235 @@
+(function () {
+ if (document.body.scrollIntoView) {
+ var docnav = document.querySelector('.doc-nav');
+ var active = docnav.querySelector('li.active');
+ if (active) {
+ active.scrollIntoView({ block: 'center', inline: 'nearest' });
+ }
+ }
+ setupScrollSpy();
+ setupMobileToggle();
+ setupDarkMode();
+ setupSearch();
+ setupCollapse();
+})();
+
+function setupScrollSpy() {
+ var sectionPositions = [];
+ var sections = document.querySelectorAll('section');
+ sections.forEach(function (section) {
+ sectionPositions.push(section.offsetTop);
+ });
+ var scrollPos = 0;
+ window.addEventListener('scroll', function (_) {
+ // Reset classes
+ document.querySelectorAll('.doc-toc a[class="active"]').forEach(function (link) {
+ link.classList.remove('active');
+ });
+ // Set current menu link as active
+ var scrollPosition = document.documentElement.scrollTop || document.body.scrollTop;
+ for (var i = 0; i < sectionPositions.length; i++) {
+ var section = sections[i];
+ var position = sectionPositions[i];
+ if (position >= scrollPosition) {
+ var link = document.querySelector('.doc-toc a[href="#' + section.id + '"]');
+ if (link) {
+ link.classList.add('active');
+ var docToc = document.querySelector('.doc-toc');
+ var tocHeight = docToc.clientHeight;
+ var scrollTop = docToc.scrollTop;
+ if ((document.body.getBoundingClientRect()).top < scrollPos && scrollTop < link.offsetTop - 10) {
+ docToc.scrollTop = link.clientHeight + link.offsetTop - tocHeight + 10;
+ } else if (scrollTop > link.offsetTop - 10) {
+ docToc.scrollTop = link.offsetTop - 10;
+ }
+ }
+ break;
+ }
+ }
+ scrollPos = (document.body.getBoundingClientRect()).top;
+ });
+}
+
+function setupMobileToggle() {
+ var toggle = document.getElementById('toggle-menu');
+ toggle.addEventListener('click', function (_) {
+ var docNav = document.querySelector('.doc-nav');
+ var isHidden = docNav.classList.contains('hidden');
+ docNav.classList.toggle('hidden');
+ var search = document.querySelector('.doc-nav > .search');
+ console.log(search);
+ var searchHasResults = search.classList.contains('has-results');
+ if (isHidden && searchHasResults) {
+ search.classList.remove('mobile-hidden');
+ } else {
+ search.classList.add('mobile-hidden');
+ }
+ var content = document.querySelector('.doc-nav .content');
+ content.classList.toggle('hidden');
+ content.classList.toggle('show');
+ });
+}
+
+function setupDarkMode() {
+ var html = document.getElementsByTagName('html')[0];
+ var darkModeToggle = document.getElementById('dark-mode-toggle');
+ darkModeToggle.addEventListener('click', function () {
+ html.classList.toggle('dark');
+ var isDarkModeEnabled = html.classList.contains('dark');
+ localStorage.setItem('dark-mode', isDarkModeEnabled);
+ darkModeToggle.setAttribute('aria-checked', isDarkModeEnabled)
+ });
+ // Check if css var() is supported and enable dark mode toggle
+ if (window.CSS && CSS.supports('color', 'var(--fake-var)')) {
+ darkModeToggle.style.visibility = 'unset';
+ }
+}
+
+function setupSearch() {
+ var searchInput = document.getElementById('search');
+ var onInputChange = debounce(function (e) {
+ var searchValue = e.target.value.toLowerCase();
+ var menu = document.querySelector('.doc-nav > .content');
+ var search = document.querySelector('.doc-nav > .search');
+ if (searchValue === '') {
+ // reset to default
+ menu.style.display = '';
+ if (!search.classList.contains('hidden')) {
+ search.classList.add('hidden');
+ search.classList.remove('has-results');
+ }
+ } else if (searchValue.length >= 2) {
+ // search for less than 2 characters can display too much results
+ search.innerHTML = '';
+ menu.style.display = 'none';
+ if (search.classList.contains('hidden')) {
+ search.classList.remove('hidden');
+ search.classList.remove('mobile-hidden');
+ search.classList.add('has-results');
+ }
+ // cache length for performance
+ var foundModule = false;
+ var searchModuleIndexLength = searchModuleIndex.length;
+ var ul = document.createElement('ul');
+ search.appendChild(ul);
+ for (var i = 0; i < searchModuleIndexLength; i++) {
+ // no toLowerCase needed because modules are always lowercase
+ var title = searchModuleIndex[i];
+ if (title.indexOf(searchValue) === -1) {
+ continue
+ }
+ foundModule = true;
+ // [description, link]
+ var data = searchModuleData[i];
+ var description = data[0];
+ var link = data[1];
+ var el = createSearchResult({
+ link: link,
+ title: title,
+ description: description,
+ badge: 'module',
+ });
+ ul.appendChild(el);
+ }
+ if (foundModule) {
+ var hr = document.createElement('hr');
+ hr.classList.add('separator');
+ search.appendChild(hr);
+ }
+ var searchIndexLength = searchIndex.length;
+ var results = [];
+ for (var i = 0; i < searchIndexLength; i++) {
+ var title = searchIndex[i];
+ if (title.toLowerCase().indexOf(searchValue) === -1) {
+ continue
+ }
+ // [badge, description, link]
+ var data = searchData[i];
+ var badge = data[0];
+ var description = data[1];
+ var link = data[2];
+ var prefix = data[3];
+ results.push({
+ badge: badge,
+ description: description,
+ link: link,
+ title: prefix + ' ' + title,
+ });
+ }
+ results.sort(function (a, b) {
+ if (a.title < b.title) {
+ return -1;
+ }
+ if (a.title > b.title) {
+ return 1;
+ }
+ return 0;
+ });
+ var ul = document.createElement('ul');
+ search.appendChild(ul);
+ for (var i = 0; i < results.length; i++) {
+ var result = results[i];
+ var el = createSearchResult(result);
+ ul.appendChild(el);
+ }
+ }
+ });
+ searchInput.addEventListener('input', onInputChange);
+}
+
+function createSearchResult(data) {
+ var li = document.createElement('li');
+ li.classList.add('result');
+ var a = document.createElement('a');
+ a.href = data.link;
+ a.classList.add('link');
+ li.appendChild(a);
+ var defintion = document.createElement('div');
+ defintion.classList.add('definition');
+ a.appendChild(defintion);
+ if (data.description) {
+ var description = document.createElement('div');
+ description.classList.add('description');
+ description.textContent = data.description;
+ a.appendChild(description);
+ }
+ var title = document.createElement('span');
+ title.classList.add('title');
+ title.textContent = data.title;
+ defintion.appendChild(title);
+ var badge = document.createElement('badge');
+ badge.classList.add('badge');
+ badge.textContent = data.badge;
+ defintion.appendChild(badge);
+ return li;
+}
+
+function setupCollapse() {
+ var dropdownArrows = document.querySelectorAll('.dropdown-arrow');
+ for (var i = 0; i < dropdownArrows.length; i++) {
+ var dropdownArrow = dropdownArrows[i];
+ dropdownArrow.addEventListener('click', function (e) {
+ var parent = e.target.parentElement.parentElement.parentElement;
+ parent.classList.toggle('open');
+ });
+ }
+}
+
+function debounce(func, timeout) {
+ var timer;
+ return (...args) => {
+ const next = () => func(...args);
+ if (timer) {
+ clearTimeout(timer);
+ }
+ timer = setTimeout(next, timeout > 0 ? timeout : 300);
+ }
+}
+
+document.addEventListener('keypress', (ev) => {
+ if (ev.key == '/') {
+ let search = document.getElementById('search');
+ ev.preventDefault();
+ search.focus();
+ }
+});
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/android-chrome-192x192.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/android-chrome-192x192.png
new file mode 100644
index 0000000..a674500
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/android-chrome-192x192.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/android-chrome-512x512.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/android-chrome-512x512.png
new file mode 100644
index 0000000..fe7294e
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/android-chrome-512x512.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/apple-touch-icon.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/apple-touch-icon.png
new file mode 100644
index 0000000..d2bedd5
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/apple-touch-icon.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/browserconfig.xml b/v_windows/v/cmd/tools/vdoc/resources/favicons/browserconfig.xml
new file mode 100644
index 0000000..b3930d0
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/browserconfig.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ #da532c
+
+
+
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon-16x16.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon-16x16.png
new file mode 100644
index 0000000..ed11964
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon-16x16.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon-32x32.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon-32x32.png
new file mode 100644
index 0000000..083808f
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon-32x32.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon.ico b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon.ico
new file mode 100644
index 0000000..5123c5e
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon.ico differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-144x144.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-144x144.png
new file mode 100644
index 0000000..f34f872
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-144x144.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-150x150.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-150x150.png
new file mode 100644
index 0000000..d511595
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-150x150.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-310x150.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-310x150.png
new file mode 100644
index 0000000..ec8e25f
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-310x150.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-310x310.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-310x310.png
new file mode 100644
index 0000000..8b98e30
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-310x310.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-70x70.png b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-70x70.png
new file mode 100644
index 0000000..4740338
Binary files /dev/null and b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-70x70.png differ
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/safari-pinned-tab.svg b/v_windows/v/cmd/tools/vdoc/resources/favicons/safari-pinned-tab.svg
new file mode 100644
index 0000000..8580c38
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/safari-pinned-tab.svg
@@ -0,0 +1,39 @@
+
+
+
+
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+
+
+
+
+
diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/site.webmanifest b/v_windows/v/cmd/tools/vdoc/resources/favicons/site.webmanifest
new file mode 100644
index 0000000..b20abb7
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "",
+ "short_name": "",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/v_windows/v/cmd/tools/vdoc/resources/light.svg b/v_windows/v/cmd/tools/vdoc/resources/light.svg
new file mode 100644
index 0000000..21606c9
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/light.svg
@@ -0,0 +1 @@
+
diff --git a/v_windows/v/cmd/tools/vdoc/resources/link.svg b/v_windows/v/cmd/tools/vdoc/resources/link.svg
new file mode 100644
index 0000000..60d93bb
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/link.svg
@@ -0,0 +1 @@
+
diff --git a/v_windows/v/cmd/tools/vdoc/resources/menu.svg b/v_windows/v/cmd/tools/vdoc/resources/menu.svg
new file mode 100644
index 0000000..c069b00
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/menu.svg
@@ -0,0 +1 @@
+
diff --git a/v_windows/v/cmd/tools/vdoc/resources/normalize.css b/v_windows/v/cmd/tools/vdoc/resources/normalize.css
new file mode 100644
index 0000000..d9fbe67
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/resources/normalize.css
@@ -0,0 +1,171 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+html {
+ line-height: 1.15;
+ -webkit-text-size-adjust: 100%;
+}
+
+main {
+ display: block;
+}
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+hr {
+ box-sizing: content-box;
+ height: 0;
+ overflow: visible;
+}
+
+pre {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+a {
+ background-color: transparent;
+}
+
+abbr[title] {
+ border-bottom: none;
+ text-decoration: underline;
+ text-decoration: underline dotted;
+}
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+kbd,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+small {
+ font-size: 80%;
+}
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+img {
+ border-style: none;
+}
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.15;
+ margin: 0;
+}
+
+button,
+input {
+ overflow: visible;
+}
+
+button,
+select {
+ text-transform: none;
+}
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button;
+}
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+
+legend {
+ box-sizing: border-box;
+ color: inherit;
+ display: table;
+ max-width: 100%;
+ padding: 0;
+ white-space: normal;
+}
+
+progress {
+ vertical-align: baseline;
+}
+
+textarea {
+ overflow: auto;
+}
+
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box;
+ padding: 0;
+}
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+[type="search"] {
+ -webkit-appearance: textfield;
+ outline-offset: -2px;
+}
+
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ font: inherit;
+}
+
+summary {
+ display: list-item;
+}
+
+template {
+ display: none;
+}
+
+[hidden] {
+ display: none;
+}
diff --git a/v_windows/v/cmd/tools/vdoc/tests/testdata/basic/main.comments.out b/v_windows/v/cmd/tools/vdoc/tests/testdata/basic/main.comments.out
new file mode 100644
index 0000000..326f0c5
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/testdata/basic/main.comments.out
@@ -0,0 +1,7 @@
+module main
+
+const (
+ source_root = 'temp'
+)
+fn funky()
+ funky - comment for function below
\ No newline at end of file
diff --git a/v_windows/v/cmd/tools/vdoc/tests/testdata/basic/main.out b/v_windows/v/cmd/tools/vdoc/tests/testdata/basic/main.out
new file mode 100644
index 0000000..08fe504
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/testdata/basic/main.out
@@ -0,0 +1,6 @@
+module main
+
+const (
+ source_root = 'temp'
+)
+fn funky()
\ No newline at end of file
diff --git a/v_windows/v/cmd/tools/vdoc/tests/testdata/basic/main.v b/v_windows/v/cmd/tools/vdoc/tests/testdata/basic/main.v
new file mode 100644
index 0000000..9cb66e3
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/testdata/basic/main.v
@@ -0,0 +1,8 @@
+pub const (
+ source_root = 'temp'
+)
+
+// funky - comment for function below
+pub fn funky() {
+ println('hi')
+}
diff --git a/v_windows/v/cmd/tools/vdoc/tests/testdata/multiline/main.comments.out b/v_windows/v/cmd/tools/vdoc/tests/testdata/multiline/main.comments.out
new file mode 100644
index 0000000..793abf1
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/testdata/multiline/main.comments.out
@@ -0,0 +1,9 @@
+module main
+
+fn a1()
+ normal comment
+fn a2()
+ this should be merged into the same line
+fn a3()
+ This should be its own parapgraph, because it ends with a dot.
+ This should be another paragraph.
diff --git a/v_windows/v/cmd/tools/vdoc/tests/testdata/multiline/main.out b/v_windows/v/cmd/tools/vdoc/tests/testdata/multiline/main.out
new file mode 100644
index 0000000..4a3c36b
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/testdata/multiline/main.out
@@ -0,0 +1,5 @@
+module main
+
+fn a1()
+fn a2()
+fn a3()
diff --git a/v_windows/v/cmd/tools/vdoc/tests/testdata/multiline/main.v b/v_windows/v/cmd/tools/vdoc/tests/testdata/multiline/main.v
new file mode 100644
index 0000000..840ca43
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/testdata/multiline/main.v
@@ -0,0 +1,16 @@
+// normal comment
+pub fn a1() {
+ println('hi')
+}
+
+// this should be merged
+// into the same line
+pub fn a2() {
+ println('hi')
+}
+
+// This should be its own parapgraph, because it ends with a dot.
+// This should be another paragraph.
+pub fn a3() {
+ println('hi')
+}
diff --git a/v_windows/v/cmd/tools/vdoc/tests/testdata/newlines/main.comments.out b/v_windows/v/cmd/tools/vdoc/tests/testdata/newlines/main.comments.out
new file mode 100644
index 0000000..f3ee942
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/testdata/newlines/main.comments.out
@@ -0,0 +1,22 @@
+module main
+
+fn funky()
+ hello
+
+ empty line
+ newline using a full stop.
+ ```v
+ code
+ ```
+
+ test
+ ====
+ - foo
+ - bar
+ # test
+ ########### deep test
+ #a shouldnt have a newline test
+
+ | foo bar | yes |
+ |-----------|--------|
+ | working | yup |
\ No newline at end of file
diff --git a/v_windows/v/cmd/tools/vdoc/tests/testdata/newlines/main.out b/v_windows/v/cmd/tools/vdoc/tests/testdata/newlines/main.out
new file mode 100644
index 0000000..02bcce9
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/testdata/newlines/main.out
@@ -0,0 +1,3 @@
+module main
+
+fn funky()
\ No newline at end of file
diff --git a/v_windows/v/cmd/tools/vdoc/tests/testdata/newlines/main.v b/v_windows/v/cmd/tools/vdoc/tests/testdata/newlines/main.v
new file mode 100644
index 0000000..8d2bf90
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/testdata/newlines/main.v
@@ -0,0 +1,23 @@
+// hello
+//
+// empty line
+// newline using a full stop.
+// ```v
+// code
+// ```
+//
+// test
+// ====
+// - foo
+// - bar
+// # test
+// ########### deep test
+// #a shouldnt have a newline
+// test
+//
+// | foo bar | yes |
+// |-----------|--------|
+// | working | yup |
+pub fn funky() {
+ println('hi')
+}
diff --git a/v_windows/v/cmd/tools/vdoc/tests/vdoc_file_test.v b/v_windows/v/cmd/tools/vdoc/tests/vdoc_file_test.v
new file mode 100644
index 0000000..254337c
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/tests/vdoc_file_test.v
@@ -0,0 +1,92 @@
+import os
+import rand
+import term
+import v.util.vtest
+import v.util.diff
+
+const vexe = @VEXE
+
+const vroot = @VMODROOT
+
+const diff_cmd = find_diff_cmd()
+
+fn find_diff_cmd() string {
+ return diff.find_working_diff_command() or { '' }
+}
+
+fn test_vet() ? {
+ os.setenv('VCOLORS', 'never', true)
+ os.chdir(vroot) ?
+ test_dir := 'cmd/tools/vdoc/tests/testdata'
+ main_files := get_main_files_in_dir(test_dir)
+ fails := check_path(vexe, test_dir, main_files)
+ assert fails == 0
+}
+
+fn get_main_files_in_dir(dir string) []string {
+ mut mfiles := os.walk_ext(dir, '.v')
+ mfiles.sort()
+ return mfiles
+}
+
+fn check_path(vexe string, dir string, tests []string) int {
+ mut nb_fail := 0
+ paths := vtest.filter_vtest_only(tests, basepath: vroot)
+ for path in paths {
+ program := path
+ print(path + ' ')
+ res := os.execute('$vexe doc $program')
+ if res.exit_code < 0 {
+ panic(res.output)
+ }
+ mut expected := os.read_file(program.replace('main.v', 'main.out')) or { panic(err) }
+ expected = clean_line_endings(expected)
+ found := clean_line_endings(res.output)
+ if expected != found {
+ print_compare(expected, found)
+ }
+
+ res_comments := os.execute('$vexe doc -comments $program')
+ if res_comments.exit_code < 0 {
+ panic(res_comments.output)
+ }
+ mut expected_comments := os.read_file(program.replace('main.v', 'main.comments.out')) or {
+ panic(err)
+ }
+ expected_comments = clean_line_endings(expected_comments)
+ found_comments := clean_line_endings(res_comments.output)
+ if expected_comments != found_comments {
+ print_compare(expected_comments, found_comments)
+ }
+
+ if expected == found && expected_comments == found_comments {
+ println(term.green('OK'))
+ } else {
+ nb_fail++
+ }
+ }
+ return nb_fail
+}
+
+fn print_compare(expected string, found string) {
+ println(term.red('FAIL'))
+ println('============')
+ println('expected:')
+ println(expected)
+ println('============')
+ println('found:')
+ println(found)
+ println('============\n')
+ println('diff:')
+ println(diff.color_compare_strings(diff_cmd, rand.ulid(), found, expected))
+ println('============\n')
+}
+
+fn clean_line_endings(s string) string {
+ mut res := s.trim_space()
+ res = res.replace(' \n', '\n')
+ res = res.replace(' \r\n', '\n')
+ res = res.replace('\r\n', '\n')
+ res = res.trim('\n')
+ return res
+}
diff --git a/v_windows/v/cmd/tools/vdoc/utils.v b/v_windows/v/cmd/tools/vdoc/utils.v
new file mode 100644
index 0000000..4b07373
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/utils.v
@@ -0,0 +1,275 @@
+module main
+
+import os
+import v.doc
+import term
+import v.ast
+import v.scanner
+import v.token
+import strings
+import v.pref
+
+[inline]
+fn slug(title string) string {
+ return title.replace(' ', '-')
+}
+
+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 dn_description.contains('\n') {
+ 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 !path.contains(ignore_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'
+}
+
+fn color_highlight(code string, tb &ast.Table) string {
+ builtin := ['bool', 'string', 'i8', 'i16', 'int', 'i64', 'i128', 'byte', 'u16', 'u32', 'u64',
+ 'u128', 'rune', 'f32', 'f64', 'int_literal', 'float_literal', 'byteptr', 'voidptr', 'any']
+ highlight_code := fn (tok token.Token, typ HighlightTokenTyp) string {
+ mut lit := ''
+ match typ {
+ .unone, .operator, .punctuation {
+ lit = tok.kind.str()
+ }
+ .string {
+ use_double_quote := tok.lit.contains("'") && !tok.lit.contains('"')
+ unescaped_val := tok.lit.replace('\\\\', '\x01').replace_each(["\\'", "'", '\\"',
+ '"',
+ ])
+ if use_double_quote {
+ s := unescaped_val.replace_each(['\x01', '\\\\', '"', '\\"'])
+ lit = term.yellow('"$s"')
+ } else {
+ s := unescaped_val.replace_each(['\x01', '\\\\', "'", "\\'"])
+ lit = term.yellow("'$s'")
+ }
+ }
+ .char {
+ lit = term.yellow('`$tok.lit`')
+ }
+ .keyword {
+ lit = term.bright_blue(tok.lit)
+ }
+ .builtin, .symbol {
+ lit = term.green(tok.lit)
+ }
+ .function {
+ lit = term.cyan(tok.lit)
+ }
+ .number, .module_ {
+ lit = term.bright_blue(tok.lit)
+ }
+ .boolean {
+ lit = term.bright_magenta(tok.lit)
+ }
+ .none_ {
+ lit = term.red(tok.lit)
+ }
+ .prefix {
+ lit = term.magenta(tok.lit)
+ }
+ else {
+ lit = tok.lit
+ }
+ }
+ return lit
+ }
+ mut s := scanner.new_scanner(code, .parse_comments, &pref.Preferences{ is_fmt: true })
+ mut prev_prev := token.Token{}
+ mut prev := token.Token{}
+ 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))
+ && (next_tok.kind != .lpar || prev.kind !in [.key_fn, .rpar]) {
+ tok_typ = .builtin
+ } else if
+ next_tok.kind in [.lcbr, .rpar, .eof, .comma, .pipe, .name, .rcbr, .assign, .key_pub, .key_mut, .pipe, .comma]
+ && prev.kind in [.name, .amp, .rsbr, .key_type, .assign, .dot, .question, .rpar, .key_struct, .key_enum, .pipe, .key_interface]
+ && (tok.lit[0].ascii_str().is_upper() || prev_prev.lit in ['C', 'JS']) {
+ tok_typ = .symbol
+ } else if next_tok.kind in [.lpar, .lt] {
+ tok_typ = .function
+ } else if next_tok.kind == .dot {
+ if tok.lit in ['C', 'JS'] {
+ tok_typ = .prefix
+ } else {
+ if tok.lit[0].ascii_str().is_upper() {
+ tok_typ = .symbol
+ } else {
+ tok_typ = .module_
+ }
+ }
+ } else if tok.lit in ['r', 'c'] && next_tok.kind == .string {
+ tok_typ = .prefix
+ } 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
+ }
+ .key_none {
+ tok_typ = .none_
+ }
+ 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_string(highlight_code(tok, tok_typ))
+ if prev_prev.kind == .eof || prev.kind == .eof || next_tok.kind == .eof {
+ break
+ }
+ prev_prev = prev
+ prev = tok
+ i = tok.pos + tok.len
+ tok = next_tok
+ next_tok = s.scan()
+ } else {
+ buf.write_b(code[i])
+ i++
+ }
+ }
+ return buf.str()
+}
diff --git a/v_windows/v/cmd/tools/vdoc/vdoc.v b/v_windows/v/cmd/tools/vdoc/vdoc.v
new file mode 100644
index 0000000..dc74001
--- /dev/null
+++ b/v_windows/v/cmd/tools/vdoc/vdoc.v
@@ -0,0 +1,517 @@
+module main
+
+import markdown
+import os
+import os.cmdline
+import time
+import strings
+import sync
+import runtime
+import v.doc
+import v.pref
+import v.vmod
+import json
+import term
+
+const (
+ allowed_formats = ['md', 'markdown', 'json', 'text', 'stdout', 'html', 'htm']
+ vexe = pref.vexe_path()
+ vroot = os.dir(vexe)
+ tabs = ['\t\t', '\t\t\t\t\t\t', '\t\t\t\t\t\t\t']
+)
+
+enum OutputType {
+ unset
+ html
+ markdown
+ json
+ plaintext
+ stdout
+}
+
+struct VDoc {
+ cfg Config [required]
+mut:
+ docs []doc.Doc
+ 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 Config {
+mut:
+ pub_only bool = true
+ show_loc bool // for plaintext
+ is_color bool
+ is_multi bool
+ is_vlib bool
+ is_verbose bool
+ include_readme bool
+ include_examples bool = true
+ include_comments bool // for plaintext
+ inline_assets bool
+ no_timestamp bool
+ output_path string
+ output_type OutputType = .unset
+ input_path string
+ symbol_name string
+ platform doc.Platform
+}
+
+//
+struct Output {
+mut:
+ path string
+ typ OutputType = .unset
+}
+
+struct ParallelDoc {
+ d doc.Doc
+ out Output
+}
+
+fn (vd VDoc) gen_json(d doc.Doc) string {
+ cfg := vd.cfg
+ mut jw := strings.new_builder(200)
+ comments := if cfg.include_examples {
+ d.head.merge_comments()
+ } else {
+ d.head.merge_comments_without_examples()
+ }
+ jw.write_string('{"module_name":"$d.head.name","description":"${escape(comments)}","contents":')
+ jw.write_string(json.encode(d.contents.keys().map(d.contents[it])))
+ jw.write_string(',"generator":"vdoc","time_generated":"$d.time_generated.str()"}')
+ return jw.str()
+}
+
+fn (vd VDoc) gen_plaintext(d doc.Doc) string {
+ cfg := vd.cfg
+ mut pw := strings.new_builder(200)
+ if cfg.is_color {
+ content_arr := d.head.content.split(' ')
+ pw.writeln('${term.bright_blue(content_arr[0])} ${term.green(content_arr[1])}\n')
+ } else {
+ pw.writeln('$d.head.content\n')
+ }
+ if cfg.include_comments {
+ comments := if cfg.include_examples {
+ d.head.merge_comments()
+ } else {
+ d.head.merge_comments_without_examples()
+ }
+ if comments.trim_space().len > 0 {
+ pw.writeln(comments.split_into_lines().map(' ' + it).join('\n'))
+ }
+ }
+ vd.write_plaintext_content(d.contents.arr(), mut pw)
+ return pw.str()
+}
+
+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 {
+ if cfg.is_color {
+ pw.writeln(color_highlight(cn.content, vd.docs[0].table))
+ } else {
+ pw.writeln(cn.content)
+ }
+ if cn.comments.len > 0 && cfg.include_comments {
+ comments := if cfg.include_examples {
+ cn.merge_comments()
+ } else {
+ cn.merge_comments_without_examples()
+ }
+ pw.writeln(comments.trim_space().split_into_lines().map(' ' + it).join('\n'))
+ }
+ if cfg.show_loc {
+ pw.writeln('Location: $cn.file_path:${cn.pos.line_nr + 1}\n')
+ }
+ }
+ vd.write_plaintext_content(cn.children, mut pw)
+ }
+}
+
+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 (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(out.path) {
+ name = os.file_name(out.path)
+ }
+ name = name + match out.typ {
+ .html { '.html' }
+ .markdown { '.md' }
+ .json { '.json' }
+ else { '.txt' }
+ }
+ return name
+}
+
+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 := 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) or { panic(err) }
+ }
+ wg.done()
+}
+
+fn (vd VDoc) render_parallel(out Output) {
+ vjobs := runtime.nr_jobs()
+ mut work := sync.new_channel(u32(vd.docs.len))
+ mut wg := sync.new_waitgroup()
+ 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 vd.work_processor(mut work, mut wg)
+ }
+ wg.wait()
+}
+
+fn (vd VDoc) render(out Output) map[string]string {
+ mut docs := map[string]string{}
+ for doc in vd.docs {
+ name, output := vd.render_doc(doc, out)
+ docs[name] = output.trim_space()
+ }
+ vd.vprintln('Rendered: ' + docs.keys().str())
+ return docs
+}
+
+fn (vd VDoc) get_readme(path string) string {
+ mut fname := ''
+ for name in ['readme', 'README'] {
+ if os.exists(os.join_path(path, '${name}.md')) {
+ fname = name
+ break
+ }
+ }
+ if fname == '' {
+ return ''
+ }
+ readme_path := os.join_path(path, '${fname}.md')
+ vd.vprintln('Reading README file from $readme_path')
+ readme_contents := os.read_file(readme_path) or { '' }
+ return readme_contents
+}
+
+fn (vd VDoc) emit_generate_err(err IError) {
+ cfg := vd.cfg
+ mut err_msg := err.msg
+ if err.code == 1 {
+ mod_list := get_modules_list(cfg.input_path, []string{})
+ println('Available modules:\n==================')
+ for mod in mod_list {
+ println(mod.all_after('vlib/').all_after('modules/').replace('/', '.'))
+ }
+ err_msg += ' Use the `-m` flag when generating docs from a directory that has multiple modules.'
+ }
+ eprintln(err_msg)
+}
+
+fn (mut vd VDoc) generate_docs_from_file() {
+ cfg := vd.cfg
+ mut out := Output{
+ path: cfg.output_path
+ typ: cfg.output_type
+ }
+ 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)
+ }
+ dir_path := if cfg.is_vlib {
+ vroot
+ } else if os.is_dir(cfg.input_path) {
+ cfg.input_path
+ } else {
+ os.dir(cfg.input_path)
+ }
+ manifest_path := os.join_path(dir_path, 'v.mod')
+ if os.exists(manifest_path) {
+ vd.vprintln('Reading v.mod info from $manifest_path')
+ if manifest := vmod.from_file(manifest_path) {
+ vd.manifest = manifest
+ }
+ }
+ if cfg.include_readme {
+ readme_contents := vd.get_readme(dir_path)
+ comment := doc.DocComment{
+ text: readme_contents
+ }
+ if out.typ == .stdout {
+ println(markdown.to_plain(readme_contents))
+ } else if out.typ == .html && cfg.is_multi {
+ vd.docs << doc.Doc{
+ head: doc.DocNode{
+ name: 'README'
+ comments: [comment]
+ }
+ time_generated: time.now()
+ }
+ }
+ }
+ dirs := if cfg.is_multi {
+ get_modules_list(cfg.input_path, []string{})
+ } else {
+ [cfg.input_path]
+ }
+ for dirpath in dirs {
+ vd.vprintln('Generating $out.typ docs for "$dirpath"')
+ mut dcs := doc.generate(dirpath, cfg.pub_only, true, cfg.platform, cfg.symbol_name) or {
+ vd.emit_generate_err(err)
+ exit(1)
+ }
+ if dcs.contents.len == 0 {
+ continue
+ }
+ if cfg.is_multi || (!cfg.is_multi && cfg.include_readme) {
+ readme_contents := vd.get_readme(dirpath)
+ comment := doc.DocComment{
+ text: readme_contents
+ }
+ dcs.head.comments = [comment]
+ }
+ if cfg.pub_only {
+ for name, dc in dcs.contents {
+ dcs.contents[name].content = dc.content.all_after('pub ')
+ for i, cc in dc.children {
+ dcs.contents[name].children[i].content = cc.content.all_after('pub ')
+ }
+ }
+ }
+ 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 := vd.docs.filter(it.head.name == 'builtin')
+ docs << vd.docs.filter(it.head.name != 'builtin')
+ vd.docs = docs
+ }
+ vd.vprintln('Rendering docs...')
+ if out.path.len == 0 || out.path == 'stdout' {
+ if out.typ == .html {
+ vd.render_static_html(out)
+ }
+ outputs := vd.render(out)
+ if outputs.len == 0 {
+ eprintln('vdoc: No documentation found for ${dirs[0]}')
+ exit(1)
+ } else {
+ first := outputs.keys()[0]
+ println(outputs[first])
+ }
+ } else {
+ if !os.exists(out.path) {
+ os.mkdir_all(out.path) or { panic(err) }
+ } else if !os.is_dir(out.path) {
+ out.path = os.real_path('.')
+ }
+ if cfg.is_multi {
+ 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 {
+ existing_asset_path := os.join_path(out.path, fname)
+ if os.exists(existing_asset_path) {
+ os.rm(existing_asset_path) or { panic(err) }
+ }
+ }
+ }
+ }
+ if out.typ == .html {
+ vd.render_static_html(out)
+ }
+ vd.render_parallel(out)
+ if out.typ == .html {
+ println('Creating search index...')
+ 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) or { panic(err) }
+ }
+ }
+ }
+}
+
+fn (vd VDoc) vprintln(str string) {
+ if vd.cfg.is_verbose {
+ println('vdoc: $str')
+ }
+}
+
+fn parse_arguments(args []string) Config {
+ mut cfg := Config{}
+ cfg.is_color = term.can_show_color_on_stdout()
+ for i := 0; i < args.len; i++ {
+ arg := args[i]
+ current_args := args[i..]
+ match arg {
+ '-all' {
+ cfg.pub_only = false
+ }
+ '-f' {
+ format := cmdline.option(current_args, '-f', '')
+ if format !in allowed_formats {
+ allowed_str := allowed_formats.join(', ')
+ eprintln('vdoc: "$format" is not a valid format. Only $allowed_str are allowed.')
+ exit(1)
+ }
+ cfg.output_type = set_output_type_from_str(format)
+ i++
+ }
+ '-color' {
+ cfg.is_color = true
+ }
+ '-no-color' {
+ cfg.is_color = false
+ }
+ '-inline-assets' {
+ cfg.inline_assets = true
+ }
+ '-l' {
+ cfg.show_loc = true
+ }
+ '-comments' {
+ cfg.include_comments = true
+ }
+ '-m' {
+ cfg.is_multi = true
+ }
+ '-o' {
+ opath := cmdline.option(current_args, '-o', '')
+ cfg.output_path = if opath == 'stdout' { opath } else { os.real_path(opath) }
+ i++
+ }
+ '-os' {
+ platform_str := cmdline.option(current_args, '-os', '')
+ if platform_str == 'cross' {
+ eprintln('`v doc -os cross` is not supported yet.')
+ exit(1)
+ }
+ selected_platform := doc.platform_from_string(platform_str) or {
+ eprintln(err.msg)
+ exit(1)
+ }
+ cfg.platform = selected_platform
+ i++
+ }
+ '-no-timestamp' {
+ cfg.no_timestamp = true
+ }
+ '-no-examples' {
+ cfg.include_examples = false
+ }
+ '-readme' {
+ cfg.include_readme = true
+ }
+ '-v' {
+ cfg.is_verbose = true
+ }
+ else {
+ if cfg.input_path.len < 1 {
+ cfg.input_path = arg
+ } else if !cfg.is_multi {
+ // Symbol name filtering should not be enabled
+ // in multi-module documentation mode.
+ cfg.symbol_name = arg
+ }
+ if i == args.len - 1 {
+ break
+ }
+ }
+ }
+ }
+ // Correct from configuration from user input
+ if cfg.output_path == 'stdout' && cfg.output_type == .html {
+ cfg.inline_assets = true
+ }
+ $if windows {
+ cfg.input_path = cfg.input_path.replace('/', os.path_separator)
+ } $else {
+ cfg.input_path = cfg.input_path.replace('\\', os.path_separator)
+ }
+ is_path := cfg.input_path.ends_with('.v') || cfg.input_path.split(os.path_separator).len > 1
+ || cfg.input_path == '.'
+ if cfg.input_path.trim_right('/') == 'vlib' {
+ cfg.is_vlib = true
+ cfg.is_multi = true
+ cfg.input_path = os.join_path(vroot, 'vlib')
+ } else if !is_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
+ }
+ return cfg
+}
+
+fn main() {
+ if os.args.len < 2 || '-h' in os.args || '-help' in os.args || '--help' in os.args
+ || os.args[1..] == ['doc', 'help'] {
+ os.system('$vexe help doc')
+ exit(0)
+ }
+ 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()
+}
--
cgit v1.2.3