neo_doxygen: Do not guess the namespace of the classes.
[nit.git] / contrib / neo_doxygen / src / neo_doxygen.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Doxygen XML to Neo4j.
16 #
17 # Converts a Doxygen XML output into a model in Neo4j that is readable by the
18 # `nx` tool.
19 module neo_doxygen
20
21 import model
22 import doxml
23 import graph_store
24 import console
25 import flush_stdout
26 import opts
27
28 # An importation task.
29 class NeoDoxygenJob
30
31 # The storage medium to use.
32 var store: GraphStore
33
34 # The loaded project graph.
35 var model: ProjectGraph is noinit
36
37 # Escape control sequence to save the cursor position.
38 private var term_save_cursor: String = (new TermSaveCursor).to_s
39
40 # Escape control sequence to rewind to the last saved cursor position.
41 private var term_rewind: String = "{new TermRestoreCursor}{new TermEraseDisplayDown}"
42
43 # Generate a graph from the specified project model.
44 #
45 # Parameters:
46 #
47 # * `name`: project name.
48 # * `dir`: Doxygen XML output directory path.
49 # * `source`: The language-specific logics to use.
50 fun load_project(name: String, dir: String, source: SourceLanguage) do
51 check_name name
52 model = new ProjectGraph(name)
53 var reader = new CompoundFileReader(model, source)
54 # Queue for sub-directories.
55 var directories = new Array[String]
56 var file_count = 0
57
58 if dir == "" then
59 printn "Reading the current directory... "
60 else
61 printn "Reading {dir}... "
62 end
63 flush_stdout
64 loop
65 for f in list_files(dir) do
66 var path = dir/f
67 if path.file_stat.is_dir then
68 directories.push(path)
69 else if f.has_suffix(".xml") and f != "index.xml" then
70 reader.read(path)
71 file_count += 1
72 end
73 end
74 if directories.length <= 0 then break
75 dir = directories.pop
76 end
77 model.add_global_modules
78 print "Done."
79 if file_count < 2 then
80 print "{file_count} file read."
81 else
82 print "{file_count} files read."
83 end
84 flush_stdout
85 end
86
87 # List files in a directory.
88 #
89 # This method may be redefined to force the order in which the files
90 # are read by `load_project`.
91 protected fun list_files(dir: String): Collection[String] do
92 return dir.files
93 end
94
95 # Check the project’s name.
96 private fun check_name(name: String) do
97 assert name_valid: not name.chars.first.is_upper else
98 sys.stderr.write("{sys.program_name}: The project’s name must not" +
99 " begin with an upper case letter. Got `{name}`.\n")
100 end
101 assert name_unused: not store.has_node_label(name) else
102 sys.stderr.write("{sys.program_name}: The label `{name}` is already" +
103 " used in the specified graph.\n")
104 end
105 end
106
107 # Save the graph.
108 fun save do
109 sys.stdout.write "Linking nodes...{term_save_cursor} "
110 flush_stdout
111 model.put_edges
112 print "{term_rewind} Done."
113 var nodes = model.all_nodes
114 sys.stdout.write "Saving {nodes.length} nodes..."
115 store.save_all(nodes)
116 var edges = model.all_edges
117 sys.stdout.write "Saving {edges.length} edges..."
118 store.save_all(edges)
119 end
120 end
121
122 # The main class.
123 class NeoDoxygenCommand
124
125 # Invalid arguments
126 var e_usage = 64
127
128 # Available options for `--src-lang`.
129 var sources = new HashMap[String, SourceLanguage]
130
131 # The synopsis.
132 var synopsis: String = "[--dest <url>] [--src-lang <lang>]\n" +
133 " [--] <project_name> <doxml_dir>"
134
135 # The synopsis for the help page.
136 var help_synopsis = "[-h|--help]"
137
138 # The default destination.
139 var default_dest = "http://localhost:7474"
140
141 # Processes the options.
142 var option_context = new OptionContext
143
144 # The `--src-lang` option.
145 var opt_src_lang: OptionEnum is noinit
146
147 # The `--dest` option.
148 var opt_dest: OptionString is noinit
149
150 # The `-h|--help` option.
151 var opt_help: OptionBool is noinit
152
153 init do
154 sources["any"] = new DefaultSource
155 sources["java"] = new JavaSource
156
157 var prefix = new OptionText("""
158 {{{"NAME".bold}}}
159 {{{sys.program_name}}} — Doxygen XML to Neo4j.
160
161 {{{"SYNOPSIS".bold}}}
162 {{{sys.program_name}}} {{{synopsis}}}
163 {{{sys.program_name}}} {{{help_synopsis}}}
164
165 {{{"DESCRIPTION".bold}}}
166 Convert a Doxygen XML output into a model in Neo4j that is readable by the
167 `nx` tool.
168
169 {{{"ARGUMENTS".bold}}}
170 <project_name> The internal name of the project. Must the same name as the
171 one specified to the `nx` tool. Must not begin by an upper
172 case letter.
173
174 <doxml_dir> The directory where the XML documents generated by Doxygen are
175 located.
176
177 {{{"OPTIONS".bold}}}
178 """)
179 option_context.add_option(prefix)
180
181 opt_dest = new OptionString("The URL of the destination graph. `{default_dest}` by default.",
182 "--dest")
183 opt_dest.default_value = default_dest
184 option_context.add_option(opt_dest)
185
186 opt_help = new OptionBool("Show the help (this page).",
187 "-h", "--help")
188 option_context.add_option(opt_help)
189
190 var keys = new Array[String].from(sources.keys)
191 opt_src_lang = new OptionEnum(keys,
192 "The programming language to assume when processing chunk in the declarations left as-is by Doxygen. Use `any` (the default) to disable any language-specific processing.",
193 keys.index_of("any"), "--src-lang")
194 option_context.add_option(opt_src_lang)
195 end
196
197 # Start the application.
198 fun main: Int do
199 if args.is_empty then
200 show_help
201 return e_usage
202 end
203 option_context.parse(args)
204
205 var errors = option_context.get_errors
206 var rest = option_context.rest
207
208 if errors.is_empty and not opt_help.value and rest.length != 2 then
209 errors.add "Unexpected number of additional arguments. Expecting 2; got {rest.length}."
210 end
211 if not errors.is_empty then
212 for e in errors do print_error(e)
213 show_usage
214 return e_usage
215 end
216 if opt_help.value then
217 show_help
218 return 0
219 end
220
221 var source = sources[opt_src_lang.value_name]
222 var dest = opt_dest.value
223 var project_name = rest[0]
224 var dir = rest[1]
225 var neo = new NeoDoxygenJob(create_store(dest or else default_dest))
226
227 neo.load_project(project_name, dir, source)
228 neo.save
229 return 0
230 end
231
232 # Create an instance of `GraphStore` for the specified destination.
233 protected fun create_store(dest: String): GraphStore do
234 return new Neo4jStore(new Neo4jClient(dest))
235 end
236
237 # Show the help.
238 fun show_help do
239 option_context.usage
240 end
241
242 # Show the usage.
243 fun show_usage do
244 sys.stderr.write "Usage: {sys.program_name} {synopsis}\n"
245 sys.stderr.write "For details, run `{sys.program_name} --help`.\n"
246 end
247
248 # Print an error.
249 fun print_error(e: String) do
250 sys.stderr.write "{sys.program_name}: {e}\n"
251 end
252 end
253
254 # Add handling of multi-line descriptions.
255 #
256 # Note: The algorithm is naive and do not handle internationalisation and
257 # escape sequences.
258 redef class Option
259
260 redef fun pretty(off) do
261 var s = super
262
263 if s.length > 80 and off < 80 then
264 var column_length = 80 - off
265 var left = 0
266 var right = 80
267 var buf = new FlatBuffer
268 var prefix = "\n{" " * off}"
269
270 loop
271 while right > left and s.chars[right] != ' ' do
272 right -= 1
273 end
274 if left == right then
275 buf.append s.substring(left, column_length)
276 right += column_length
277 else
278 buf.append s.substring(left, right - left)
279 right += 1
280 end
281 buf.append prefix
282 left = right
283 right += column_length
284 if right >= s.length then break
285 end
286 buf.append s.substring_from(left)
287 buf.append "\n"
288 return buf.to_s
289 else
290 return "{s}\n"
291 end
292 end
293 end
294
295 exit((new NeoDoxygenCommand).main)