diff options
| author | Indrajith K L | 2022-12-03 17:00:20 +0530 | 
|---|---|---|
| committer | Indrajith K L | 2022-12-03 17:00:20 +0530 | 
| commit | f5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch) | |
| tree | 2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/cmd/tools/vdoc | |
| download | cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.tar.gz cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.tar.bz2 cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.zip | |
Diffstat (limited to 'v_windows/v/cmd/tools/vdoc')
38 files changed, 2814 insertions, 0 deletions
| diff --git a/v_windows/v/cmd/tools/vdoc/html.v b/v_windows/v/cmd/tools/vdoc/html.v new file mode 100644 index 0000000..e5a7f32 --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/html.v @@ -0,0 +1,554 @@ +module main + +import os +import net.urllib +import strings +import markdown +import regex +import v.scanner +import v.ast +import v.token +import v.doc +import v.pref + +const ( +	css_js_assets = ['doc.css', 'normalize.css', 'doc.js', 'dark-mode.js'] +	res_path      = os.resource_abs_path('resources') +	favicons_path = os.join_path(res_path, 'favicons') +	link_svg      = '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>' +	html_content  = '<!DOCTYPE html> +<html lang="en"> +	<head> +		<meta charset="UTF-8"> +		<meta http-equiv="x-ua-compatible" content="IE=edge" /> +		<meta name="viewport" content="width=device-width, initial-scale=1.0"> +		<title>{{ title }} | vdoc</title> +		<link rel="preconnect" href="https://fonts.googleapis.com"> +		<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> +		<link href="https://fonts.googleapis.com/css2?family=Jost:wght@300;400;500;600;700&display=swap" rel="stylesheet"> +		<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap" rel="stylesheet"> +		<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"> +		<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png"> +		<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png"> +		<link rel="manifest" href="site.webmanifest"> +		<link rel="mask-icon" href="safari-pinned-tab.svg" color="#5bbad5"> +		<meta name="msapplication-TileColor" content="#da532c"> +		<meta name="theme-color" content="#ffffff"> +		{{ head_assets }} +	</head> +	<body> +		<div id="page"> +			<header class="doc-nav hidden"> +				<div class="heading-container"> +					<div class="heading"> +						<div class="info"> +							<div class="module">{{ head_name }}</div> +							<div class="toggle-version-container"> +								<span>{{ version }}</span> +								<div id="dark-mode-toggle" role="switch" aria-checked="false" aria-label="Toggle dark mode">{{ light_icon }}{{ dark_icon }}</div> +							</div> +							{{ menu_icon }} +						</div> +						<input type="text" id="search" placeholder="Search... (beta)" autocomplete="off"> +					</div> +				</div> +				<nav class="search hidden"></nav> +				<nav class="content hidden"> +					<ul> +						{{ toc_links }} +					</ul> +				</nav> +			</header> +			<div class="doc-scrollview"> +				<div class="doc-container"> +					<div class="doc-content"> +{{ contents }} +						<div class="footer"> +							{{ footer_content }} +						</div> +					</div> +					{{ right_content }} +				</div> +			</div> +		</div> +		{{ footer_assets }} +		<script async src="search_index.js" type="text/javascript"></script> +	</body> +</html>' +) + +enum HighlightTokenTyp { +	unone +	boolean +	builtin +	char +	comment +	function +	keyword +	name +	number +	operator +	punctuation +	string +	symbol +	none_ +	module_ +	prefix +} + +struct SearchModuleResult { +	description string +	link        string +} + +struct SearchResult { +	prefix      string +	badge       string +	description string +	link        string +} + +fn (vd VDoc) render_search_index(out Output) { +	mut js_search_index := strings.new_builder(200) +	mut js_search_data := strings.new_builder(200) +	js_search_index.write_string('var searchModuleIndex = [') +	js_search_data.write_string('var searchModuleData = [') +	for i, title in vd.search_module_index { +		data := vd.search_module_data[i] +		js_search_index.write_string('"$title",') +		js_search_data.write_string('["$data.description","$data.link"],') +	} +	js_search_index.writeln('];') +	js_search_index.write_string('var searchIndex = [') +	js_search_data.writeln('];') +	js_search_data.write_string('var searchData = [') +	for i, title in vd.search_index { +		data := vd.search_data[i] +		js_search_index.write_string('"$title",') +		// array instead of object to reduce file size +		js_search_data.write_string('["$data.badge","$data.description","$data.link","$data.prefix"],') +	} +	js_search_index.writeln('];') +	js_search_data.writeln('];') +	out_file_path := os.join_path(out.path, 'search_index.js') +	os.write_file(out_file_path, js_search_index.str() + js_search_data.str()) or { panic(err) } +} + +fn (mut vd VDoc) render_static_html(out Output) { +	vd.assets = { +		'doc_css':       vd.get_resource(css_js_assets[0], out) +		'normalize_css': vd.get_resource(css_js_assets[1], out) +		'doc_js':        vd.get_resource(css_js_assets[2], out) +		'dark_mode_js':  vd.get_resource(css_js_assets[3], out) +		'light_icon':    vd.get_resource('light.svg', out) +		'dark_icon':     vd.get_resource('dark.svg', out) +		'menu_icon':     vd.get_resource('menu.svg', out) +		'arrow_icon':    vd.get_resource('arrow.svg', out) +	} +} + +fn (vd VDoc) get_resource(name string, out Output) string { +	cfg := vd.cfg +	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(out.path, name) +		if !os.exists(output_path) { +			println('Generating $out.typ in "$output_path"') +			os.write_file(output_path, res) or { panic(err) } +		} +		return name +	} +} + +fn (mut vd VDoc) collect_search_index(out Output) { +	cfg := vd.cfg +	for doc in vd.docs { +		mod := doc.head.name +		vd.search_module_index << mod +		comments := if cfg.include_examples { +			doc.head.merge_comments() +		} else { +			doc.head.merge_comments_without_examples() +		} +		vd.search_module_data << SearchModuleResult{ +			description: trim_doc_node_description(comments) +			link: vd.get_file_name(mod, out) +		} +		for _, dn in doc.contents { +			vd.create_search_results(mod, dn, out) +		} +	} +} + +fn (mut vd VDoc) create_search_results(mod string, dn doc.DocNode, out Output) { +	cfg := vd.cfg +	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) +	vd.search_index << dn.name +	vd.search_data << SearchResult{ +		prefix: if dn.parent_name != '' { '$dn.kind ($dn.parent_name)' } else { '$dn.kind ' } +		description: dn_description +		badge: mod +		link: vd.get_file_name(mod, out) + '#' + get_node_id(dn) +	} +	for child in dn.children { +		vd.create_search_results(mod, child, out) +	} +} + +fn (vd VDoc) write_content(cn &doc.DocNode, d &doc.Doc, mut hw strings.Builder) { +	cfg := vd.cfg +	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(vd.manifest.repo_url, file_path_name, cn.pos.line_nr + 1) +	if cn.content.len != 0 || (cn.name == 'Constants') { +		hw.write_string(doc_node_html(cn, src_link, false, cfg.include_examples, d.table)) +	} +	for child in cn.children { +		child_file_path_name := child.file_path.replace('$base_dir/', '') +		child_src_link := get_src_link(vd.manifest.repo_url, child_file_path_name, +			child.pos.line_nr + 1) +		hw.write_string(doc_node_html(child, child_src_link, false, cfg.include_examples, +			d.table)) +	} +} + +fn (vd VDoc) gen_html(d doc.Doc) string { +	cfg := vd.cfg +	mut symbols_toc := strings.new_builder(200) +	mut modules_toc := strings.new_builder(200) +	mut contents := strings.new_builder(200) +	dcs_contents := d.contents.arr() +	// generate toc first +	contents.writeln(doc_node_html(d.head, '', true, cfg.include_examples, d.table)) +	if is_module_readme(d.head) { +		write_toc(d.head, mut symbols_toc) +	} +	for cn in dcs_contents { +		vd.write_content(&cn, &d, mut contents) +		write_toc(cn, mut symbols_toc) +	} // write head +	// write css +	version := if vd.manifest.version.len != 0 { vd.manifest.version } else { '' } +	header_name := if cfg.is_multi && vd.docs.len > 1 { +		os.file_name(os.real_path(cfg.input_path)) +	} else { +		d.head.name +	} +	// write nav1 +	if cfg.is_multi || vd.docs.len > 1 { +		mut submod_prefix := '' +		for i, dc in vd.docs { +			if i - 1 >= 0 && dc.head.name.starts_with(submod_prefix + '.') { +				continue +			} +			names := dc.head.name.split('.') +			submod_prefix = if names.len > 1 { names[0] } else { dc.head.name } +			mut href_name := './${dc.head.name}.html' +			if (cfg.is_vlib && dc.head.name == 'builtin' && !cfg.include_readme) +				|| dc.head.name == 'README' { +				href_name = './index.html' +			} else if submod_prefix !in vd.docs.map(it.head.name) { +				href_name = '#' +			} +			submodules := vd.docs.filter(it.head.name.starts_with(submod_prefix + '.')) +			dropdown := if submodules.len > 0 { vd.assets['arrow_icon'] } else { '' } +			active_class := if dc.head.name == d.head.name { ' active' } else { '' } +			modules_toc.write_string('<li class="open$active_class"><div class="menu-row">$dropdown<a href="$href_name">$submod_prefix</a></div>') +			for j, cdoc in submodules { +				if j == 0 { +					modules_toc.write_string('<ul>') +				} +				submod_name := cdoc.head.name.all_after(submod_prefix + '.') +				sub_selected_classes := if cdoc.head.name == d.head.name { +					' class="active"' +				} else { +					'' +				} +				modules_toc.write_string('<li$sub_selected_classes><a href="./${cdoc.head.name}.html">$submod_name</a></li>') +				if j == submodules.len - 1 { +					modules_toc.write_string('</ul>') +				} +			} +			modules_toc.write_string('</li>') +		} +	} +	modules_toc_str := modules_toc.str() +	symbols_toc_str := symbols_toc.str() +	result := html_content.replace('{{ title }}', d.head.name).replace('{{ head_name }}', +		header_name).replace('{{ version }}', version).replace('{{ light_icon }}', vd.assets['light_icon']).replace('{{ dark_icon }}', +		vd.assets['dark_icon']).replace('{{ menu_icon }}', vd.assets['menu_icon']).replace('{{ head_assets }}', +		if cfg.inline_assets { +		'\n${tabs[0]}<style>' + vd.assets['doc_css'] + '</style>\n${tabs[0]}<style>' + +			vd.assets['normalize_css'] + '</style>\n${tabs[0]}<script>' + +			vd.assets['dark_mode_js'] + '</script>' +	} else { +		'\n${tabs[0]}<link rel="stylesheet" href="' + vd.assets['doc_css'] + +			'" />\n${tabs[0]}<link rel="stylesheet" href="' + vd.assets['normalize_css'] + +			'" />\n${tabs[0]}<script src="' + vd.assets['dark_mode_js'] + '"></script>' +	}).replace('{{ toc_links }}', if cfg.is_multi || vd.docs.len > 1 { +		modules_toc_str +	} else { +		symbols_toc_str +	}).replace('{{ contents }}', contents.str()).replace('{{ right_content }}', if cfg.is_multi +		&& vd.docs.len > 1 && d.head.name != 'README' { +		'<div class="doc-toc"><ul>' + symbols_toc_str + '</ul></div>' +	} else { +		'' +	}).replace('{{ footer_content }}', gen_footer_text(d, !cfg.no_timestamp)).replace('{{ footer_assets }}', +		if cfg.inline_assets { +		'<script>' + vd.assets['doc_js'] + '</script>' +	} else { +		'<script src="' + vd.assets['doc_js'] + '"></script>' +	}) +	return result +} + +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 html_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 { +		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 +		} +		if typ in [.unone, .name] { +			return lit +		} +		return '<span class="token $typ">$lit</span>' +	} +	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_string(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(dn doc.DocNode, link string, head bool, include_examples bool, tb &ast.Table) string { +	mut dnw := strings.new_builder(200) +	head_tag := if head { 'h1' } else { 'h2' } +	comments := dn.merge_comments_without_examples() +	// Allow README.md to go through unescaped except for script tags +	escaped_html := if head && is_module_readme(dn) { +		// Strip markdown [TOC] directives, since we generate our own. +		stripped := comments.replace('[TOC]', '') +		markdown_escape_script_tags(stripped) +	} else { +		html_tag_escape(comments) +	} +	md_content := markdown.to_html(escaped_html) +	highlighted_code := html_highlight(dn.content, tb) +	node_class := if dn.kind == .const_group { ' const' } else { '' } +	sym_name := get_sym_name(dn) +	has_deprecated := 'deprecated' in dn.tags +	mut tags := dn.tags.filter(it != 'deprecated') +	tags.sort() +	mut node_id := get_node_id(dn) +	mut hash_link := if !head { ' <a href="#$node_id">#</a>' } else { '' } +	if head && is_module_readme(dn) { +		node_id = 'readme_$node_id' +		hash_link = ' <a href="#$node_id">#</a>' +	} +	dnw.writeln('${tabs[1]}<section id="$node_id" class="doc-node$node_class">') +	if dn.name.len > 0 { +		if dn.kind == .const_group { +			dnw.write_string('${tabs[2]}<div class="title"><$head_tag>$sym_name$hash_link</$head_tag>') +		} else { +			dnw.write_string('${tabs[2]}<div class="title"><$head_tag>$dn.kind $sym_name$hash_link</$head_tag>') +		} +		if link.len != 0 { +			dnw.write_string('<a class="link" rel="noreferrer" target="_blank" href="$link">$link_svg</a>') +		} +		dnw.write_string('</div>') +	} +	if tags.len > 0 || has_deprecated { +		mut attributes := if has_deprecated { +			'<div class="attribute attribute-deprecated">deprecated</div>' +		} else { +			'' +		} +		attributes += tags.map('<div class="attribute">$it</div>').join('') +		dnw.writeln('<div class="attributes">$attributes</div>') +	} +	if !head && dn.content.len > 0 { +		dnw.writeln('<pre class="signature"><code>$highlighted_code</code></pre>') +	} +	// 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('<section class="doc-node examples"><h4>$example_title</h4>') +		for example in examples { +			// hl_example := html_highlight(example, tb) +			dnw.writeln('<pre><code class="language-v">$example</code></pre>') +		} +		dnw.writeln('</section>') +	} +	dnw.writeln('</section>') +	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('<li class="open"><a href="#readme_$toc_slug">README</a>') +	} else if dn.name != 'Constants' { +		toc.write_string('<li class="open"><a href="#$toc_slug">$dn.kind $dn.name</a>') +		toc.writeln('        <ul>') +		for child in dn.children { +			cname := dn.name + '.' + child.name +			toc.writeln('<li><a href="#${slug(cname)}">$child.kind $child.name</a></li>') +		} +		toc.writeln('</ul>') +	} else { +		toc.write_string('<li class="open"><a href="#$toc_slug">$dn.name</a>') +	} +	toc.writeln('</li>') +} 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>') == '<abc>' +	assert html_tag_escape('`<abc>`') == '`<abc>`' +} 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(['<script>', '`', '</script>', '`']) +} + +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 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="dropdown-arrow" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 10l5 5 5-5z"/></svg>
\ 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="dark-icon" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><g><rect fill="none" height="24" width="24"/></g><g><g><g><path d="M11.1,12.08C8.77,7.57,10.6,3.6,11.63,2.01C6.27,2.2,1.98,6.59,1.98,12c0,0.14,0.02,0.28,0.02,0.42 C2.62,12.15,3.29,12,4,12c1.66,0,3.18,0.83,4.1,2.15C9.77,14.63,11,16.17,11,18c0,1.52-0.87,2.83-2.12,3.51 c0.98,0.32,2.03,0.5,3.11,0.5c3.5,0,6.58-1.8,8.37-4.52C18,17.72,13.38,16.52,11.1,12.08z"/></g><path d="M7,16l-0.18,0C6.4,14.84,5.3,14,4,14c-1.66,0-3,1.34-3,3s1.34,3,3,3c0.62,0,2.49,0,3,0c1.1,0,2-0.9,2-2 C9,16.9,8.1,16,7,16z"/></g></g></svg> 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.pngBinary files differ new file mode 100644 index 0000000..a674500 --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/android-chrome-192x192.png 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.pngBinary files differ new file mode 100644 index 0000000..fe7294e --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/android-chrome-512x512.png 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.pngBinary files differ new file mode 100644 index 0000000..d2bedd5 --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/apple-touch-icon.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<browserconfig> +    <msapplication> +        <tile> +            <square150x150logo src="/mstile-150x150.png"/> +            <TileColor>#da532c</TileColor> +        </tile> +    </msapplication> +</browserconfig> 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.pngBinary files differ new file mode 100644 index 0000000..ed11964 --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon-16x16.png 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.pngBinary files differ new file mode 100644 index 0000000..083808f --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon-32x32.png diff --git a/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon.ico b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon.icoBinary files differ new file mode 100644 index 0000000..5123c5e --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/favicon.ico 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.pngBinary files differ new file mode 100644 index 0000000..f34f872 --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-144x144.png 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.pngBinary files differ new file mode 100644 index 0000000..d511595 --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-150x150.png 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.pngBinary files differ new file mode 100644 index 0000000..ec8e25f --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-310x150.png 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.pngBinary files differ new file mode 100644 index 0000000..8b98e30 --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-310x310.png 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.pngBinary files differ new file mode 100644 index 0000000..4740338 --- /dev/null +++ b/v_windows/v/cmd/tools/vdoc/resources/favicons/mstile-70x70.png 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 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.11, written by Peter Selinger 2001-2013 +</metadata> +<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"> +<path d="M580 6459 c-45 -10 -80 -50 -80 -94 0 -18 20 -89 44 -156 63 -178 +287 -801 371 -1034 62 -172 123 -341 205 -570 39 -110 81 -225 92 -255 19 -52 +62 -173 157 -440 23 -63 44 -122 48 -130 3 -8 7 -19 9 -25 4 -18 354 -989 369 +-1025 8 -19 30 -82 50 -140 20 -58 55 -154 77 -215 22 -60 51 -144 65 -185 14 +-41 29 -79 34 -85 5 -5 9 -17 9 -27 0 -9 7 -32 15 -50 8 -18 53 -139 99 -268 +186 -518 281 -784 326 -910 72 -205 97 -240 200 -289 l55 -25 795 -2 c437 -1 +809 0 826 3 50 8 88 51 118 133 15 41 34 91 41 110 7 19 49 136 94 260 176 +496 213 597 231 645 29 76 472 1309 474 1320 1 6 10 27 19 49 9 21 17 41 17 +45 0 3 32 94 71 201 39 107 72 200 73 205 2 6 6 17 9 25 4 8 38 105 78 215 39 +110 100 281 136 380 76 208 133 366 173 480 29 84 292 814 437 1215 208 573 +204 560 154 611 -26 25 -56 35 -101 31 -6 -1 -102 -10 -185 -17 -27 -3 -70 -7 +-95 -10 -25 -3 -74 -7 -110 -10 -36 -3 -81 -7 -100 -10 -19 -2 -64 -7 -100 +-10 -82 -7 -152 -14 -210 -20 -25 -3 -74 -7 -110 -10 -36 -3 -81 -7 -100 -10 +-19 -2 -64 -7 -100 -10 -147 -13 -262 -24 -315 -30 -16 -2 -64 -6 -105 -10 +-102 -9 -140 -20 -196 -58 -75 -51 -101 -92 -156 -248 -28 -79 -51 -148 -53 +-154 -1 -5 -5 -17 -8 -25 -4 -8 -40 -112 -81 -230 -82 -239 -197 -567 -256 +-735 -21 -60 -59 -171 -85 -245 -26 -74 -64 -184 -85 -245 -22 -60 -89 -254 +-150 -430 -61 -176 -120 -345 -131 -375 -11 -30 -45 -130 -77 -222 -65 -192 +-51 -195 -126 19 -24 71 -72 207 -106 303 -34 96 -81 231 -105 300 -24 69 -71 +204 -105 300 -34 96 -65 186 -70 200 -4 14 -34 97 -65 185 -31 88 -65 185 -75 +215 -17 52 -70 203 -210 600 -97 276 -141 403 -150 430 -77 237 -117 309 -199 +362 -54 33 -91 44 -186 53 -73 6 -130 12 -205 20 -25 3 -74 7 -110 10 -36 3 +-85 8 -110 11 -25 3 -70 7 -100 9 -30 3 -75 7 -100 10 -25 3 -72 7 -105 10 +-33 3 -80 7 -105 10 -25 3 -72 7 -105 10 -33 3 -82 8 -110 10 -27 3 -71 8 -97 +10 -27 3 -72 7 -100 10 -83 9 -137 14 -218 21 -41 3 -77 7 -80 8 -3 2 -21 -1 +-40 -5z"/> +</g> +</svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="light-icon" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z"/></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" id="toggle-menu" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg> 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<ParallelDoc>(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() +} | 
