v / cmd / tools / vdoc / document / module.v
148 lines · 140 sloc · 4.59 KB · 02fa15644899c2a3feae1f161730c19666e5846e
Raw
1module document
2
3import os
4import v.ast
5import v.parser
6import v.pref
7import v.vmod
8
9fn module_path_from_vmod_root(vmod_root string, mod string) !string {
10 vmod_path := os.join_path(vmod_root, 'v.mod')
11 if !os.is_file(vmod_path) {
12 return error('module not found')
13 }
14 manifest := vmod.from_file(vmod_path)!
15 tail_path := mod_tail_after_vmod_name(mod, manifest.name)!
16 mut try_path := manifest.source_root(vmod_root)
17 if tail_path.len > 0 {
18 try_path = os.join_path(try_path, tail_path)
19 }
20 if os.is_dir(try_path) && !os.is_dir_empty(try_path) {
21 return try_path
22 }
23 return error('module not found')
24}
25
26fn module_path_from_search_root(search_path string, mod string) !string {
27 mod_path := mod.replace('.', os.path_separator)
28 try_path := os.join_path_single(search_path, mod_path)
29 if os.is_dir(try_path) && !os.is_dir_empty(try_path) {
30 return try_path
31 }
32 return module_path_from_vmod_root(search_path, mod)
33}
34
35fn mod_tail_after_vmod_name(mod string, vmod_name string) !string {
36 if vmod_name == '' {
37 return error('module not found')
38 }
39 mod_parts := mod.split('.')
40 vmod_parts := vmod_name.split('.')
41 for i := 0; i + vmod_parts.len <= mod_parts.len; i++ {
42 if i > 1 {
43 break
44 }
45 if mod_parts[i..i + vmod_parts.len].join('.') == vmod_name {
46 return mod_parts[i + vmod_parts.len..].join(os.path_separator)
47 }
48 }
49 return error('module not found')
50}
51
52// get_parent_mod returns the parent mod name, in dot format.
53// It works by climbing up the folder hierarchy, until a folder,
54// that either contains main .v files, or a v.mod file is reached.
55// For example, given something like /languages/v/vlib/x/websocket/tests/autobahn
56// it returns `x.websocket.tests`, because /languages/v/ has v.mod file in it.
57// Note: calling this is expensive, so keep the result, instead of recomputing it.
58// TODO: turn this to a Doc method, so that the new_vdoc_preferences call here can
59// be removed.
60fn get_parent_mod(input_dir string) !string {
61 if input_dir == '' {
62 return error('no input folder')
63 }
64 // windows root path is C: or D:
65 if input_dir.len == 2 && input_dir[1] == `:` {
66 return error('root folder reached')
67 }
68 // unix systems have / at the top:
69 if input_dir == '/' {
70 return error('root folder reached')
71 }
72 base_dir := os.dir(input_dir)
73 input_dir_name := os.file_name(base_dir)
74 prefs := new_vdoc_preferences()
75 fentries := os.ls(base_dir) or { []string{} }
76 files := fentries.filter(!os.is_dir(os.join_path(base_dir, it)))
77 if 'v.mod' in files {
78 // the top level is reached, no point in climbing up further
79 return ''
80 }
81 v_files := prefs.should_compile_filtered_files(base_dir, files)
82 if v_files.len == 0 {
83 parent_mod := get_parent_mod(base_dir) or { return input_dir_name }
84 if parent_mod.len > 0 {
85 return parent_mod + '.' + input_dir_name
86 }
87 return error('No V files found.')
88 }
89 mut tbl := ast.new_table()
90 file_ast := parser.parse_file(v_files[0], mut tbl, .skip_comments, prefs)
91 if file_ast.mod.short_name == 'main' {
92 return ''
93 }
94 parent_mod := get_parent_mod(base_dir) or { return input_dir_name }
95 if parent_mod.len > 0 {
96 return '${parent_mod}.${file_ast.mod.short_name}'
97 }
98 return file_ast.mod.short_name
99}
100
101// lookup_module_with_path looks up the path of a given module name.
102// Throws an error if the module was not found.
103pub fn lookup_module_with_path(mod string, base_path string) !string {
104 vexe := pref.vexe_path()
105 vroot := os.dir(vexe)
106 mut compile_dir := os.real_path(base_path)
107 if !os.is_dir(compile_dir) {
108 compile_dir = os.dir(compile_dir)
109 }
110 if path := module_path_from_search_root(compile_dir, mod) {
111 return path
112 }
113 modules_dir := os.join_path(compile_dir, 'modules')
114 if path := module_path_from_search_root(modules_dir, mod) {
115 return path
116 }
117 mut current_dir := compile_dir
118 for {
119 parent_dir := os.dir(current_dir)
120 if parent_dir == current_dir {
121 break
122 }
123 current_dir = parent_dir
124 if path := module_path_from_search_root(current_dir, mod) {
125 return path
126 }
127 }
128 mut search_roots := [os.join_path(vroot, 'vlib')]
129 search_roots << os.vmodules_paths()
130 for search_root in search_roots {
131 if path := module_path_from_search_root(search_root, mod) {
132 return path
133 }
134 }
135 return error('module "${mod}" not found.')
136}
137
138// lookup_module returns the result of the `lookup_module_with_path`
139// but with the current directory as the provided base lookup path.
140pub fn lookup_module(mod string) !string {
141 return lookup_module_with_path(mod, os.dir('.'))
142}
143
144// generate_from_mod generates a documentation from a specific module.
145pub fn generate_from_mod(module_name string, pub_only bool, with_comments bool) !Doc {
146 mod_path := lookup_module(module_name)!
147 return generate(mod_path, pub_only, with_comments, .auto)
148}
149