vdoc: fix sorting + other minor improvements
							parent
							
								
									770132ff37
								
							
						
					
					
						commit
						5ff7d07138
					
				|  | @ -275,10 +275,10 @@ body { | |||
| 	word-break: break-word; | ||||
| } | ||||
| .doc-content > .doc-node.const:not(:first-child) { | ||||
| 	padding-top: 0; | ||||
| 	padding-top: 4rem; | ||||
| } | ||||
| .doc-content > .doc-node.const:not(:last-child) { | ||||
| 	padding-bottom: 1rem; | ||||
| 	padding-bottom: 2rem; | ||||
| } | ||||
| .doc-content > .timestamp { | ||||
| 	font-size: 0.8rem; | ||||
|  | @ -556,7 +556,12 @@ pre { | |||
| 	.doc-nav .content.hidden { | ||||
| 		display: flex; | ||||
| 	} | ||||
| 
 | ||||
| 	.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-top: 0; | ||||
| 		margin-left: 300px; | ||||
|  | @ -565,7 +570,6 @@ pre { | |||
| 		padding-top: 1rem !important; | ||||
| 		margin-top: 0 !important; | ||||
| 	} | ||||
| 
 | ||||
| 	.doc-toc { | ||||
| 		top: 0; | ||||
| 	} | ||||
|  |  | |||
							
								
								
									
										245
									
								
								cmd/tools/vdoc.v
								
								
								
								
							
							
						
						
									
										245
									
								
								cmd/tools/vdoc.v
								
								
								
								
							|  | @ -36,6 +36,51 @@ const ( | |||
| 	exe_dir         = os.dir(exe_path) | ||||
| 	res_path        = os.join_path(exe_dir, 'vdoc-resources') | ||||
| 	vexe_path       = os.base_dir(@VEXE) | ||||
| 	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 href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet"> | ||||
| 		{{ head_assets }} | ||||
| 	</head> | ||||
| 	<body> | ||||
| 		<div id="page"> | ||||
| 			<header class="doc-nav hidden"> | ||||
| 				<div class="heading-container"> | ||||
| 					<div class="heading"> | ||||
| 						<input type="text" id="search" placeholder="Search..."> | ||||
| 						<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> | ||||
| 				</div> | ||||
| 				<nav class="content hidden"> | ||||
| 					<ul> | ||||
| 						{{ toc_links }} | ||||
| 					</ul> | ||||
| 				</nav> | ||||
| 			</header> | ||||
| 			<div class="doc-container"> | ||||
| 				<div class="doc-content"> | ||||
| 					{{ contents }} | ||||
| 					<div class="footer"> | ||||
| 						{{ footer_content }} | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				{{ right_content }} | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		{{ footer_assets }} | ||||
| 	</body> | ||||
| 	</html> | ||||
| 	' | ||||
| ) | ||||
| 
 | ||||
| enum OutputType { | ||||
|  | @ -92,6 +137,12 @@ fn (mut cfg DocConfig) serve_html() { | |||
| 	if cfg.open_docs { | ||||
| 		open_url(server_url) | ||||
| 	} | ||||
| 	content_type := match cfg.output_type { | ||||
| 		.html { 'text/html' } | ||||
| 		.markdown { 'text/markdown' } | ||||
| 		.json { 'application/json' } | ||||
| 		else { 'text/plain' } | ||||
| 	} | ||||
| 	for { | ||||
| 		con := server.accept() or { | ||||
| 			server.close() or { } | ||||
|  | @ -106,7 +157,7 @@ fn (mut cfg DocConfig) serve_html() { | |||
| 			filename = if url.path == '/' { def_name } else { url.path.trim_left('/') } | ||||
| 		} | ||||
| 		html := docs[filename] | ||||
| 		con.write('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n$html') or { | ||||
| 		con.write('HTTP/1.1 200 OK\r\nContent-Type: $content_type\r\n\r\n$html') or { | ||||
| 			con.close() or { return } | ||||
| 			return | ||||
| 		} | ||||
|  | @ -127,9 +178,6 @@ fn get_src_link(repo_url string, file_name string, line_nr int) string { | |||
| 		'git.sir.ht' { '/tree/master/$file_name' } | ||||
| 		else { '' } | ||||
| 	} | ||||
| 	if repo_url.starts_with('https://github.com/vlang/v') && !url.path.contains('master/vlib')  { | ||||
| 		url.path = url.path.replace('/blob/master/$file_name', '/blob/master/vlib/$file_name') | ||||
| 	} | ||||
| 	if url.path == '/' { return '' } | ||||
| 	url.fragment = 'L$line_nr' | ||||
| 	return url.str() | ||||
|  | @ -138,12 +186,12 @@ fn get_src_link(repo_url string, file_name string, line_nr int) string { | |||
| fn js_compress(str string) string { | ||||
| 	mut js := strings.new_builder(200) | ||||
| 	lines := str.split_into_lines() | ||||
| 	rules := [') {', ' = ', ', ', '{ ', ' }', ' (', '; ', ' + ', ' < '] | ||||
| 	clean := ['){', '=', ',', '{', '}', '(', ';', '+', '<'] | ||||
| 	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 rules { | ||||
| 		for i in 0..rules.len-1 { | ||||
| 			trimmed = trimmed.replace(rules[i], clean[i]) | ||||
| 		} | ||||
| 		js.write(trimmed) | ||||
|  | @ -256,13 +304,13 @@ fn doc_node_html(dd doc.DocNode, link string, head bool, tb &table.Table) string | |||
| 	hlighted_code := html_highlight(dd.content, tb) | ||||
| 	is_const_class := if dd.name == 'Constants' { ' const' } else { '' } | ||||
| 	mut sym_name := dd.name | ||||
| 	if dd.parent_type !in ['void', '', 'Constants'] {  | ||||
| 		sym_name = '${dd.parent_type}.' + sym_name | ||||
| 	if dd.attrs.exists('parent') && dd.attrs['parent'] !in ['void', '', 'Constants'] {  | ||||
| 		sym_name = dd.attrs['parent'] + '.' + sym_name | ||||
| 	} | ||||
| 	node_id := slug(sym_name) | ||||
| 	hash_link := if !head { ' <a href="#$node_id">#</a>' } else { '' } | ||||
| 	dnw.writeln('<section id="$node_id" class="doc-node$is_const_class">') | ||||
| 	if dd.name != 'README' && dd.parent_type != 'Constants' { | ||||
| 	if dd.name != 'README' && dd.attrs['parent'] != 'Constants' { | ||||
| 		dnw.write('<div class="title"><$head_tag>$sym_name$hash_link</$head_tag>') | ||||
| 		if link.len != 0 { | ||||
| 			dnw.write('<a class="link" rel="noreferrer" target="_blank" href="$link">$link_svg</a>') | ||||
|  | @ -279,10 +327,19 @@ fn doc_node_html(dd doc.DocNode, link string, head bool, tb &table.Table) string | |||
| 	return dnw.str() | ||||
| } | ||||
| 
 | ||||
| fn (cfg DocConfig) readme_idx() int { | ||||
| 	for i, dc in cfg.docs { | ||||
| 		if dc.head.name != 'README' { continue } | ||||
| 		return i | ||||
| 	} | ||||
| 	return -1 | ||||
| } | ||||
| 
 | ||||
| fn write_toc(cn doc.DocNode, nodes []doc.DocNode, toc &strings.Builder) { | ||||
| 	toc.write('<li><a href="#${slug(cn.name)}">${cn.name}</a>') | ||||
| 	toc_slug := if cn.content.len == 0 { '' } else { slug(cn.name) } | ||||
| 	toc.write('<li class="open"><a href="#$toc_slug">${cn.name}</a>') | ||||
| 	children := nodes.find_children_of(cn.name) | ||||
| 	if children.len != 0 && cn.name != 'Constants' { | ||||
| 	if cn.name != 'Constants' { | ||||
| 		toc.writeln('        <ul>') | ||||
| 		for child in children { | ||||
| 			cname := cn.name + '.' + child.name | ||||
|  | @ -295,34 +352,32 @@ fn write_toc(cn doc.DocNode, nodes []doc.DocNode, toc &strings.Builder) { | |||
| 
 | ||||
| fn (cfg DocConfig) write_content(cn &doc.DocNode, dcs &doc.Doc, hw &strings.Builder) { | ||||
| 	base_dir := os.base_dir(os.real_path(cfg.input_path)) | ||||
| 	file_path_name := cn.file_path.replace('$base_dir/', '') | ||||
| 	file_path_name := if cfg.is_multi { cn.file_path.replace('$base_dir/', '') } else { os.file_name(cn.file_path) } | ||||
| 	src_link := get_src_link(cfg.manifest.repo_url, file_path_name, cn.pos.line) | ||||
| 	children := dcs.contents.find_children_of(cn.name) | ||||
| 	hw.write(doc_node_html(cn, src_link, false, dcs.table)) | ||||
| 	if children.len != 0 { | ||||
| 		for child in children { | ||||
| 			child_file_path_name := child.file_path.replace('$base_dir/', '') | ||||
| 			child_src_link := get_src_link(cfg.manifest.repo_url, child_file_path_name, child.pos.line) | ||||
| 			hw.write(doc_node_html(child, child_src_link, false, dcs.table)) | ||||
| 		} | ||||
| 	if cn.content.len != 0 { | ||||
| 		hw.write(doc_node_html(cn, src_link, false, dcs.table)) | ||||
| 	} | ||||
| 	for child in children { | ||||
| 		child_file_path_name := child.file_path.replace('$base_dir/', '') | ||||
| 		child_src_link := get_src_link(cfg.manifest.repo_url, child_file_path_name, child.pos.line) | ||||
| 		hw.write(doc_node_html(child, child_src_link, false, dcs.table)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn (cfg DocConfig) gen_html(idx int) string { | ||||
| 	dcs := cfg.docs[idx] | ||||
| 	time_gen := '$dcs.time_generated.day $dcs.time_generated.smonth() $dcs.time_generated.year $dcs.time_generated.hhmmss()' | ||||
| 	mut hw := strings.new_builder(200) | ||||
| 	mut toc := strings.new_builder(200) | ||||
| 	mut toc2 := strings.new_builder(200) | ||||
| 	mut contents := strings.new_builder(200) | ||||
| 	// generate toc first
 | ||||
| 	const_node_idx := dcs.contents.index_by_name('Constants') or { -1 } | ||||
| 	if const_node_idx != -1 { | ||||
| 		write_toc(dcs.contents[const_node_idx], dcs.contents, &toc) | ||||
| 	} | ||||
| 	contents.writeln(doc_node_html(dcs.head, '', true, dcs.table)) | ||||
| 	for cn in dcs.contents { | ||||
| 		if cn.name == 'Constants' || cn.parent_type !in ['void', ''] { continue } | ||||
| 		cfg.write_content(&cn, &dcs, &contents) | ||||
| 		if cn.attrs['parent'] == 'Constants' || cn.attrs['category'] == 'Methods' { continue } | ||||
| 		write_toc(cn, dcs.contents, &toc) | ||||
| 	}	// write head
 | ||||
| 
 | ||||
| 	// get resources
 | ||||
| 	doc_css := cfg.get_resource(css_js_assets[0], true) | ||||
| 	normalize_css := cfg.get_resource(css_js_assets[1], true) | ||||
|  | @ -331,47 +386,17 @@ fn (cfg DocConfig) gen_html(idx int) string { | |||
| 	dark_icon := cfg.get_resource('dark.svg', true) | ||||
| 	menu_icon := cfg.get_resource('menu.svg', true) | ||||
| 	arrow_icon := cfg.get_resource('arrow.svg', true) | ||||
| 
 | ||||
| 	hw.write(' | ||||
| 	<!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>${dcs.head.name} | vdoc</title> | ||||
| 		<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">') | ||||
| 
 | ||||
| 	// write css
 | ||||
| 	if cfg.inline_assets { | ||||
| 		hw.write('\n	<style>$doc_css</style>') | ||||
| 		hw.write('\n	<style>$normalize_css</style>') | ||||
| 	} else { | ||||
| 		hw.write('\n	<link rel="stylesheet" href="$doc_css" />') | ||||
| 		hw.write('\n	<link rel="stylesheet" href="$normalize_css" />') | ||||
| 	} | ||||
| 
 | ||||
| 	version := if cfg.manifest.version.len != 0 { cfg.manifest.version } else { '' } | ||||
| 	header_name := if cfg.is_multi && cfg.docs.len > 1 { os.file_name(os.real_path(cfg.input_path)) } else { dcs.head.name } | ||||
| 	header_name := if cfg.is_multi && cfg.docs.len > 1 {  | ||||
| 		os.file_name(os.real_path(cfg.input_path))  | ||||
| 	} else if cfg.docs.len == 2 && idx+1 < cfg.docs.len && cfg.readme_idx() != -1 { | ||||
| 		cfg.docs[cfg.readme_idx()+1].head.name | ||||
| 	} else { | ||||
| 		dcs.head.name | ||||
| 	} | ||||
| 	// write nav1
 | ||||
| 	hw.write(' | ||||
| 	<body> | ||||
| 	<div id="page"> | ||||
| 		<header class="doc-nav hidden"> | ||||
| 		<div class="heading-container"> | ||||
| 			<div class="heading"> | ||||
| 				<input type="text" id="search" placeholder="Search..."> | ||||
| 				<div class="module">${header_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> | ||||
| 		</div> | ||||
| 		<nav class="content hidden"> | ||||
| 			<ul>') | ||||
| 	if cfg.is_multi && cfg.docs.len > 1 { | ||||
| 	if cfg.is_multi || cfg.docs.len > 1 { | ||||
| 		mut submod_prefix := '' | ||||
| 		mut docs := cfg.docs.filter(it.head.name == 'builtin') | ||||
| 		docs << cfg.docs.filter(it.head.name != 'builtin') | ||||
|  | @ -385,6 +410,8 @@ fn (cfg DocConfig) gen_html(idx int) string { | |||
| 				'./index.html' | ||||
| 			} else if submod_prefix !in cfg.docs.map(it.head.name) { | ||||
| 				'#' | ||||
| 			} else if cfg.docs.len == 2 && cfg.readme_idx() == -1 { | ||||
| 				'./docs.html' | ||||
| 			} else { | ||||
| 				'./' + doc.head.name + '.html' | ||||
| 			} | ||||
|  | @ -397,46 +424,44 @@ fn (cfg DocConfig) gen_html(idx int) string { | |||
| 				} | ||||
| 			} | ||||
| 			active_class := if doc.head.name == dcs.head.name { ' active' } else { '' } | ||||
| 			hw.write('<li class="open$active_class"><div class="menu-row">$dropdown<a href="$href_name">${submod_prefix}</a></div>') | ||||
| 			toc2.write('<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 { | ||||
| 					hw.write('<ul>') | ||||
| 					toc2.write('<ul>') | ||||
| 				} | ||||
| 				submod_name := cdoc.head.name.all_after(submod_prefix + '.') | ||||
| 				sub_selected_classes := if cdoc.head.name == dcs.head.name { ' class="active"' } else { '' } | ||||
| 				hw.write('<li$sub_selected_classes><a href="./${cdoc.head.name}.html">${submod_name}</a></li>') | ||||
| 				toc2.write('<li$sub_selected_classes><a href="./${cdoc.head.name}.html">${submod_name}</a></li>') | ||||
| 				if j == submodules.len - 1 { | ||||
| 					hw.write('</ul>') | ||||
| 					toc2.write('</ul>') | ||||
| 				} | ||||
| 			} | ||||
| 			hw.write('</li>') | ||||
| 			toc2.write('</li>') | ||||
| 		} | ||||
| 	} else { | ||||
| 		hw.writeln(toc.str()) | ||||
| 	} | ||||
| 	hw.write('</ul>\n</nav>\n</header>') | ||||
| 	hw.write('<div class="doc-container">\n<div class="doc-content">\n') | ||||
| 	hw.write(doc_node_html(dcs.head, '', true, dcs.table)) | ||||
| 	if const_node_idx != -1 { | ||||
| 		cfg.write_content(&dcs.contents[const_node_idx], &dcs, &hw) | ||||
| 	} | ||||
| 	for cn in dcs.contents { | ||||
| 		if cn.parent_type !in ['void', ''] || cn.name == 'Constants' { continue } | ||||
| 		cfg.write_content(&cn, &dcs, &hw) | ||||
| 	} | ||||
| 	hw.write('\n<div class="footer">Powered by vdoc. Generated on: $time_gen</div>\n</div>\n') | ||||
| 	if cfg.is_multi && cfg.docs.len > 1 && dcs.head.name != 'README' { | ||||
| 		hw.write('<div class="doc-toc">\n\n<ul>\n${toc.str()}</ul>\n</div>') | ||||
| 	} | ||||
| 	hw.write('</div></div>') | ||||
| 	if cfg.inline_assets { | ||||
| 		hw.write('<script>$doc_js</script>') | ||||
| 	} else { | ||||
| 		hw.write('<script src="$doc_js"></script>') | ||||
| 	} | ||||
| 	hw.write('</body> | ||||
| 	</html>') | ||||
| 	return hw.str() | ||||
| 	return html_content | ||||
| 		.replace('{{ title }}', dcs.head.name) | ||||
| 		.replace('{{ head_name }}', header_name) | ||||
| 		.replace('{{ version }}', version) | ||||
| 		.replace('{{ light_icon }}', light_icon) | ||||
| 		.replace('{{ dark_icon }}', dark_icon) | ||||
| 		.replace('{{ menu_icon }}', menu_icon) | ||||
| 		.replace('{{ head_assets }}', 	if cfg.inline_assets { | ||||
| 			'\n	<style>$doc_css</style>\n    <style>$normalize_css</style>' | ||||
| 		} else { | ||||
| 			'\n	<link rel="stylesheet" href="$doc_css" />\n	<link rel="stylesheet" href="$normalize_css" />' | ||||
| 		}) | ||||
| 		.replace('{{ toc_links }}', if cfg.is_multi || cfg.docs.len > 1 { toc2.str() } else { toc.str() }) | ||||
| 		.replace('{{ contents }}', contents.str()) | ||||
| 		.replace('{{ right_content }}', if cfg.is_multi && cfg.docs.len > 1 && dcs.head.name != 'README' { | ||||
| 			'<div class="doc-toc"><ul>' + toc.str() + '</ul></div>' | ||||
| 		} else { '' }) | ||||
| 		.replace('{{ footer_content }}', 'Powered by vdoc. Generated on: $time_gen') | ||||
| 		.replace('{{ footer_assets }}', if cfg.inline_assets { | ||||
| 			'<script>$doc_js</script>' | ||||
| 		} else { | ||||
| 			'<script src="$doc_js"></script>' | ||||
| 		}) | ||||
| } | ||||
| 
 | ||||
| fn (cfg DocConfig) gen_plaintext(idx int) string { | ||||
|  | @ -482,13 +507,14 @@ fn (cfg DocConfig) gen_markdown(idx int, with_toc bool) string { | |||
| 
 | ||||
| fn (cfg DocConfig) render() map[string]string { | ||||
| 	mut docs := map[string]string | ||||
| 
 | ||||
| 	for i, doc in cfg.docs { | ||||
| 		// since builtin is generated first, ignore it
 | ||||
| 		mut name := if doc.head.name == 'README' { | ||||
| 		mut name := if doc.head.name == 'README' || cfg.docs.len == 1 { | ||||
| 			'index' | ||||
| 		} else if !cfg.is_multi && !os.is_dir(cfg.output_path) { | ||||
| 			os.file_name(cfg.output_path) | ||||
| 		} else if i-1 >= 0 && cfg.readme_idx() != -1 && cfg.docs.len == 2 { | ||||
| 			'docs' | ||||
| 		} else { | ||||
| 			doc.head.name | ||||
| 		} | ||||
|  | @ -579,7 +605,12 @@ fn (mut cfg DocConfig) generate_docs_from_file() { | |||
| 		mut dcs := doc.generate(dirpath, cfg.pub_only, true) or { | ||||
| 			mut err_msg := err | ||||
| 			if errcode == 1 { | ||||
| 				err_msg += ' Use the `-m` flag if you are generating docs of a directory with multiple modules inside.' | ||||
| 				mod_list := get_modules_list(cfg.input_path) | ||||
| 				println('Available modules:\n==================') | ||||
| 				for mod in mod_list { | ||||
| 					println(mod.all_after('vlib/').all_after('modules/').replace('/', '.')) | ||||
| 				} | ||||
| 				err_msg += ' Use the `-m` flag if you are generating docs of a directory containing multiple modules.' | ||||
| 			} | ||||
| 			eprintln(err_msg) | ||||
| 			exit(1) | ||||
|  | @ -613,6 +644,11 @@ fn (mut cfg DocConfig) generate_docs_from_file() { | |||
| 		if !os.is_dir(cfg.output_path) { | ||||
| 			cfg.output_path = os.real_path('.') | ||||
| 		} | ||||
| 		if !os.exists(cfg.output_path) { | ||||
| 			os.mkdir(cfg.output_path) or { | ||||
| 				panic(err) | ||||
| 			} | ||||
| 		} | ||||
| 		if cfg.is_multi { | ||||
| 			cfg.output_path = os.join_path(cfg.output_path, '_docs') | ||||
| 			if !os.exists(cfg.output_path) { | ||||
|  | @ -679,7 +715,7 @@ fn get_modules_list(path string) []string { | |||
| 	files := os.walk_ext(path, 'v') | ||||
| 	mut dirs := []string{} | ||||
| 	for file in files { | ||||
| 		if 'test' in file || 'js' in file || 'x64' in file || 'bare' in file || 'uiold' in file || 'vweb' in file { continue } | ||||
| 		if 'vlib' in path && ('examples' in file || 'test' in file || 'js' in file || 'x64' in file || 'bare' in file || 'uiold' in file || 'vweb' in file) { continue } | ||||
| 		dirname := os.base_dir(file) | ||||
| 		if dirname in dirs { continue } | ||||
| 		dirs << dirname | ||||
|  | @ -766,9 +802,11 @@ fn main() { | |||
| 			'-s' { | ||||
| 				cfg.inline_assets = true | ||||
| 				cfg.serve_http = true | ||||
| 				cfg.output_type = .html | ||||
| 				if cfg.output_type == .unset { | ||||
| 					cfg.output_type = .html | ||||
| 				} | ||||
| 			} | ||||
| 			'-r' { | ||||
| 			'-readme' { | ||||
| 				cfg.include_readme = true | ||||
| 			} | ||||
| 			'-v' { | ||||
|  | @ -784,6 +822,11 @@ fn main() { | |||
| 		eprintln('vdoc: No input path found.') | ||||
| 		exit(1) | ||||
| 	} | ||||
| 	$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 == 'vlib' { | ||||
| 		cfg.is_multi = true | ||||
|  |  | |||
|  | @ -20,6 +20,6 @@ Options: | |||
|   -open           Launches the browser when the server docs has started. | ||||
|   -p              Specifies the port to be used for the docs server. | ||||
|   -s              Serve HTML-generated docs via HTTP. | ||||
|   -r              Include README.md to docs if present. | ||||
|   -readme         Include README.md to docs if present. | ||||
|   -v              Enables verbose logging. For debugging purposes. | ||||
|   -h, -help       Prints this help text. | ||||
|  | @ -123,7 +123,7 @@ pub fn (ftp FTP) login(user, passwd string) bool { | |||
| 		} | ||||
| 		return false | ||||
| 	} | ||||
| 	mut code, mut data := ftp.read() | ||||
| 	mut code, _ := ftp.read() | ||||
| 	if code == logged_in { | ||||
| 		return true | ||||
| 	} | ||||
|  |  | |||
|  | @ -19,11 +19,11 @@ import picohttpparser | |||
| #include "src/picoev.h" | ||||
| 
 | ||||
| const ( | ||||
| 	MAX_FDS = 1024 | ||||
| 	TIMEOUT_SECS = 8 | ||||
| 	MAX_TIMEOUT = 10 | ||||
| 	MAX_READ = 4096 | ||||
| 	MAX_WRITE = 8192 | ||||
| 	max_fds = 1024 | ||||
| 	timeout_secs = 8 | ||||
| 	max_timeout = 10 | ||||
| 	max_read = 4096 | ||||
| 	max_write = 8192 | ||||
| ) | ||||
| 
 | ||||
| struct C.in_addr { | ||||
|  | @ -122,10 +122,10 @@ fn rw_callback(loop &C.picoev_loop, fd, events int, cb_arg voidptr) { | |||
| 		return | ||||
| 	} | ||||
| 	else if (events & C.PICOEV_READ) != 0 { | ||||
| 		C.picoev_set_timeout(loop, fd, TIMEOUT_SECS) | ||||
| 		buf := (p.buf + fd * MAX_READ) | ||||
| 		C.picoev_set_timeout(loop, fd, timeout_secs) | ||||
| 		buf := (p.buf + fd * max_read) | ||||
| 		idx := p.idx[fd] | ||||
| 		mut r := myread(fd, buf, MAX_READ, idx) | ||||
| 		mut r := myread(fd, buf, max_read, idx) | ||||
| 		if r == 0 { | ||||
| 			close_conn(loop, fd) | ||||
| 			p.idx[fd] = 0 | ||||
|  | @ -141,7 +141,7 @@ fn rw_callback(loop &C.picoev_loop, fd, events int, cb_arg voidptr) { | |||
| 		} else { | ||||
| 			r += idx | ||||
| 			mut s := tos(buf, r) | ||||
| 			out := (p.out + fd * MAX_WRITE) | ||||
| 			out := (p.out + fd * max_write) | ||||
| 			mut res := picohttpparser.Response{ | ||||
| 				fd: fd | ||||
| 				date: p.date | ||||
|  | @ -191,7 +191,7 @@ fn accept_callback(loop &C.picoev_loop, fd, events int, cb_arg voidptr) { | |||
| 	newfd := C.accept(fd, 0, 0) | ||||
| 	if newfd != -1 { | ||||
| 		setup_sock(newfd) | ||||
| 		C.picoev_add(loop, newfd, C.PICOEV_READ, TIMEOUT_SECS, rw_callback, cb_arg) | ||||
| 		C.picoev_add(loop, newfd, C.PICOEV_READ, timeout_secs, rw_callback, cb_arg) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -223,14 +223,14 @@ pub fn new(port int, cb voidptr) &Picoev { | |||
| 
 | ||||
| 	setup_sock(fd) | ||||
| 
 | ||||
| 	C.picoev_init(MAX_FDS) | ||||
| 	loop := C.picoev_create_loop(MAX_TIMEOUT) | ||||
| 	C.picoev_init(max_fds) | ||||
| 	loop := C.picoev_create_loop(max_timeout) | ||||
| 	pv := &Picoev{ | ||||
| 		loop: loop | ||||
| 		cb: cb | ||||
| 		date: C.get_date() | ||||
| 		buf: malloc(MAX_FDS * MAX_READ + 1) | ||||
| 		out: malloc(MAX_FDS * MAX_WRITE + 1) | ||||
| 		buf: malloc(max_fds * max_read + 1) | ||||
| 		out: malloc(max_fds * max_write + 1) | ||||
| 	} | ||||
| 	C.picoev_add(loop, fd, C.PICOEV_READ, 0, accept_callback, pv) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										158
									
								
								vlib/v/doc/doc.v
								
								
								
								
							
							
						
						
									
										158
									
								
								vlib/v/doc/doc.v
								
								
								
								
							|  | @ -21,6 +21,7 @@ pub mut: | |||
| 	head           DocNode | ||||
| 	with_comments  bool = true | ||||
| 	contents       []DocNode | ||||
| 	fmt            fmt.Fmt | ||||
| 	time_generated time.Time | ||||
| } | ||||
| 
 | ||||
|  | @ -37,7 +38,7 @@ pub mut: | |||
| 	comment     string | ||||
| 	pos         DocPos = DocPos{-1, -1} | ||||
| 	file_path   string = '' | ||||
| 	parent_type string = '' | ||||
| 	attrs       map[string]string | ||||
| } | ||||
| 
 | ||||
| pub fn merge_comments(stmts []ast.Stmt) string { | ||||
|  | @ -112,65 +113,55 @@ fn convert_pos(file_path string, pos token.Position) DocPos { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn (d Doc) get_signature(stmt ast.Stmt, file &ast.File) string { | ||||
| 	mut f := fmt.Fmt{ | ||||
| 		out: strings.new_builder(1000) | ||||
| 		out_imports: strings.new_builder(200) | ||||
| 		table: d.table | ||||
| 		file: file | ||||
| 		cur_mod: d.head.name.split('.').last() | ||||
| 		indent: 0 | ||||
| 		is_debug: false | ||||
| 	} | ||||
| 	f.process_file_imports(file) | ||||
| pub fn (mut d Doc) get_signature(stmt ast.Stmt, file &ast.File) string { | ||||
| 	match stmt { | ||||
| 		ast.Module { | ||||
| 			return 'module $it.name' | ||||
| 			return 'module $stmt.name' | ||||
| 		} | ||||
| 		ast.FnDecl { | ||||
| 			return it.str(d.table).replace(f.cur_mod + '.', '') | ||||
| 			return stmt.str(d.table).replace(d.fmt.cur_mod + '.', '') | ||||
| 		} | ||||
| 		else { | ||||
| 			f.stmt(stmt) | ||||
| 			return f.out.str().trim_space() | ||||
| 			d.fmt.out = strings.new_builder(1000) | ||||
| 			d.fmt.stmt(stmt) | ||||
| 			return d.fmt.out.str().trim_space() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn (d Doc) get_pos(stmt ast.Stmt) token.Position { | ||||
| 	match stmt { | ||||
| 		ast.FnDecl { return it.pos } | ||||
| 		ast.StructDecl { return it.pos } | ||||
| 		ast.EnumDecl { return it.pos } | ||||
| 		ast.InterfaceDecl { return it.pos } | ||||
| 		ast.ConstDecl { return it.pos } | ||||
| 		ast.FnDecl { return stmt.pos } | ||||
| 		ast.StructDecl { return stmt.pos } | ||||
| 		ast.EnumDecl { return stmt.pos } | ||||
| 		ast.InterfaceDecl { return stmt.pos } | ||||
| 		ast.ConstDecl { return stmt.pos } | ||||
| 		else { return token.Position{} } | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn (d Doc) get_type_name(decl ast.TypeDecl) string { | ||||
| 	match decl { | ||||
| 		ast.SumTypeDecl { return it.name } | ||||
| 		ast.FnTypeDecl { return it.name } | ||||
| 		ast.AliasTypeDecl { return it.name } | ||||
| 		ast.SumTypeDecl { return decl.name } | ||||
| 		ast.FnTypeDecl { return decl.name } | ||||
| 		ast.AliasTypeDecl { return decl.name } | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn (d Doc) get_name(stmt ast.Stmt) string { | ||||
| 	cur_mod := d.head.name.split('.').last() | ||||
| 	match stmt { | ||||
| 		ast.FnDecl { return it.name } | ||||
| 		ast.StructDecl { return it.name } | ||||
| 		ast.EnumDecl { return it.name } | ||||
| 		ast.InterfaceDecl { return it.name } | ||||
| 		ast.TypeDecl { return d.get_type_name(it).replace('&' + cur_mod + '.', '').replace(cur_mod + '.', '') } | ||||
| 		ast.FnDecl { return stmt.name } | ||||
| 		ast.StructDecl { return stmt.name } | ||||
| 		ast.EnumDecl { return stmt.name } | ||||
| 		ast.InterfaceDecl { return stmt.name } | ||||
| 		ast.TypeDecl { return d.get_type_name(stmt) } | ||||
| 		ast.ConstDecl { return 'Constants' } | ||||
| 		else { return '' } | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn new(input_path string) Doc { | ||||
| 	return Doc{ | ||||
| 	mut d := Doc{ | ||||
| 		input_path: os.real_path(input_path) | ||||
| 		prefs: &pref.Preferences{} | ||||
| 		table: table.new_table() | ||||
|  | @ -178,28 +169,59 @@ pub fn new(input_path string) Doc { | |||
| 		contents: []DocNode{} | ||||
| 		time_generated: time.now() | ||||
| 	} | ||||
| 	d.fmt = fmt.Fmt{ | ||||
| 		indent: 0 | ||||
| 		is_debug: false | ||||
| 	    table: d.table | ||||
| 	} | ||||
| 	return d | ||||
| } | ||||
| 
 | ||||
| pub fn (nodes []DocNode) index_by_name(node_name string) ?int { | ||||
| pub fn (mut nodes []DocNode) sort_by_name() { | ||||
| 	nodes.sort_with_compare(compare_nodes_by_name) | ||||
| } | ||||
| 
 | ||||
| pub fn (mut nodes []DocNode) sort_by_category() { | ||||
| 	nodes.sort_with_compare(compare_nodes_by_category) | ||||
| } | ||||
| 
 | ||||
| fn compare_nodes_by_name(a, b &DocNode) int { | ||||
| 	al := a.name.to_lower() | ||||
| 	bl := b.name.to_lower() | ||||
| 	return compare_strings(al, bl) | ||||
| } | ||||
| 
 | ||||
| fn compare_nodes_by_category(a, b &DocNode) int { | ||||
| 	al := a.attrs['category'] | ||||
| 	bl := b.attrs['category'] | ||||
| 	return compare_strings(al, bl) | ||||
| } | ||||
| 
 | ||||
| pub fn (nodes []DocNode) index_by_name(node_name string) int { | ||||
| 	for i, node in nodes { | ||||
| 		if node.name != node_name { continue } | ||||
| 		return i | ||||
| 	} | ||||
| 	return error('Node with the name "$node_name" was not found.') | ||||
| 	return -1 | ||||
| } | ||||
| 
 | ||||
| pub fn (nodes []DocNode) find_children_of(parent_type string) []DocNode { | ||||
| 	if parent_type.len == 0 { | ||||
| 		return []DocNode{} | ||||
| pub fn (nodes []DocNode) find_children_of(parent string) []DocNode { | ||||
| 	return nodes.find_nodes_with_attr('parent', parent) | ||||
| } | ||||
| 
 | ||||
| pub fn (nodes []DocNode) find_nodes_with_attr(attr_name string, value string) []DocNode { | ||||
| 	mut subgroup := []DocNode{} | ||||
| 	if attr_name.len == 0 { | ||||
| 		return subgroup | ||||
| 	} | ||||
| 	mut children := []DocNode{} | ||||
| 	for node in nodes { | ||||
| 		if node.parent_type != parent_type { | ||||
| 		if !node.attrs.exists(attr_name) || node.attrs[attr_name] != value { | ||||
| 			continue | ||||
| 		} | ||||
| 		children << node | ||||
| 		subgroup << node | ||||
| 	} | ||||
| 	return children | ||||
| 	subgroup.sort_by_name() | ||||
| 	return subgroup | ||||
| } | ||||
| 
 | ||||
| fn get_parent_mod(dir string) ?string { | ||||
|  | @ -242,7 +264,7 @@ fn get_parent_mod(dir string) ?string { | |||
| 	return file_ast.mod.name | ||||
| } | ||||
| 
 | ||||
| pub fn (mut d Doc) generate() ?bool { | ||||
| fn (mut d Doc) generate() ?Doc { | ||||
| 	// get all files
 | ||||
| 	base_path := if os.is_dir(d.input_path) { d.input_path } else { os.real_path(os.base_dir(d.input_path)) } | ||||
| 	project_files := os.ls(base_path) or { | ||||
|  | @ -285,7 +307,9 @@ pub fn (mut d Doc) generate() ?bool { | |||
| 			continue | ||||
| 		} | ||||
| 		stmts := file_ast.stmts | ||||
| 		//
 | ||||
| 		d.fmt.file = file_ast | ||||
| 		d.fmt.cur_mod = orig_mod_name | ||||
| 		d.fmt.process_file_imports(file_ast) | ||||
| 		mut last_import_stmt_idx := 0 | ||||
| 		for sidx, stmt in stmts { | ||||
| 			if stmt is ast.Import { | ||||
|  | @ -306,10 +330,7 @@ pub fn (mut d Doc) generate() ?bool { | |||
| 				module_comment := get_comment_block_right_before(prev_comments) | ||||
| 				prev_comments = [] | ||||
| 				if 'vlib' !in base_path && !module_comment.starts_with('Copyright (c)') { | ||||
| 					if module_comment == '' { | ||||
| 						continue | ||||
| 					} | ||||
| 					if module_comment == d.head.comment { | ||||
| 					if module_comment in ['', d.head.comment] { | ||||
| 						continue | ||||
| 					} | ||||
| 					if d.head.comment != '' { | ||||
|  | @ -350,25 +371,45 @@ pub fn (mut d Doc) generate() ?bool { | |||
| 				pos: convert_pos(v_files[i], pos) | ||||
| 				file_path: v_files[i] | ||||
| 			} | ||||
| 			if node.name.len == 0 && node.comment.len == 0 && node.content.len == 0 { | ||||
| 				continue | ||||
| 			} | ||||
| 			if stmt is ast.FnDecl { | ||||
| 				fnd := stmt as ast.FnDecl | ||||
| 				if fnd.receiver.typ != 0 { | ||||
| 					mut parent_type := d.table.get_type_name(fnd.receiver.typ) | ||||
| 					if parent_type.starts_with(module_name + '.') { | ||||
| 						parent_type = parent_type.all_after(module_name + '.') | ||||
| 					node.attrs['parent'] = d.fmt.type_to_str(fnd.receiver.typ).trim_left('&') | ||||
| 					p_idx := d.contents.index_by_name(node.attrs['parent']) | ||||
| 					if p_idx == -1 && node.attrs['parent'] != 'void' { | ||||
| 						d.contents << DocNode{ | ||||
| 							name: node.attrs['parent'] | ||||
| 							content: '' | ||||
| 							comment: '' | ||||
| 							attrs: {'category': 'Structs'} | ||||
| 						} | ||||
| 					} | ||||
| 					node.parent_type = parent_type | ||||
| 				} | ||||
| 			} | ||||
| 			if stmt is ast.ConstDecl { | ||||
| 				if const_idx == -1 { | ||||
| 					const_idx = sidx | ||||
| 				} else { | ||||
| 					node.parent_type = 'Constants' | ||||
| 					node.attrs['parent'] = 'Constants' | ||||
| 				} | ||||
| 			} | ||||
| 			if node.name.len == 0 && node.comment.len == 0 && node.content.len == 0 { | ||||
| 				continue | ||||
| 			match stmt { | ||||
| 				ast.ConstDecl { node.attrs['category'] = 'Constants' } | ||||
| 				ast.EnumDecl { node.attrs['category'] = 'Enums' } | ||||
| 				ast.InterfaceDecl { node.attrs['category'] = 'Interfaces' } | ||||
| 				ast.StructDecl { node.attrs['category'] = 'Structs' } | ||||
| 				ast.TypeDecl { node.attrs['category'] = 'Typedefs' } | ||||
| 				ast.FnDecl {  | ||||
| 					node.attrs['category'] = if node.attrs['parent'] in ['void', ''] || !node.attrs.exists('parent') { | ||||
| 						'Functions' | ||||
| 					} else { | ||||
| 						'Methods' | ||||
| 					} | ||||
| 				} | ||||
| 				else {} | ||||
| 			} | ||||
| 			d.contents << node | ||||
| 			if d.with_comments && (prev_comments.len > 0) { | ||||
|  | @ -378,17 +419,18 @@ pub fn (mut d Doc) generate() ?bool { | |||
| 			} | ||||
| 			prev_comments = [] | ||||
| 		} | ||||
| 
 | ||||
| 		d.fmt.mod2alias = map[string]string{} | ||||
| 	} | ||||
| 	d.time_generated = time.now() | ||||
| 	return true | ||||
| 	d.contents.sort_by_name() | ||||
| 	d.contents.sort_by_category() | ||||
| 	return d | ||||
| } | ||||
| 
 | ||||
| pub fn generate(input_path string, pub_only, with_comments bool) ?Doc { | ||||
| 	mut doc := new(input_path) | ||||
| 	doc.pub_only = pub_only | ||||
| 	doc.with_comments = with_comments | ||||
| 	doc.generate() or { | ||||
| 		return error_with_code(err, errcode) | ||||
| 	} | ||||
| 	return doc | ||||
| 	return doc.generate() | ||||
| } | ||||
|  |  | |||
|  | @ -514,7 +514,7 @@ pub fn (mut f Fmt) struct_field_expr(fexpr ast.Expr) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn (f &Fmt) type_to_str(t table.Type) string { | ||||
| pub fn (f &Fmt) type_to_str(t table.Type) string { | ||||
| 	mut res := f.table.type_to_str(t) | ||||
| 	for res.ends_with('_ptr') { | ||||
| 		// type_ptr => &type
 | ||||
|  |  | |||
|  | @ -282,6 +282,7 @@ fn os_from_string(os string) pref.OS { | |||
| 
 | ||||
| // Helper function to convert string names to CC enum
 | ||||
| pub fn cc_from_string(cc_str string) pref.CompilerType { | ||||
| 	if cc_str.len == 0 { return .gcc } | ||||
| 	cc := cc_str.replace('\\', '/').split('/').last().all_before('.') | ||||
| 	if 'tcc'   in cc { return .tinyc } | ||||
| 	if 'tinyc' in cc { return .tinyc } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue