neo_doxygen: Force the standard output to flush.
[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 dir.files 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 print "Done."
78 if file_count < 2 then
79 print "{file_count} file read."
80 else
81 print "{file_count} files read."
82 end
83 flush_stdout
84 end
85
86 # Check the project’s name.
87 private fun check_name(name: String) do
88 assert name_valid: not name.chars.first.is_upper else
89 sys.stderr.write("{sys.program_name}: The project’s name must not" +
90 " begin with an upper case letter. Got `{name}`.\n")
91 end
92 assert name_unused: not store.has_node_label(name) else
93 sys.stderr.write("{sys.program_name}: The label `{name}` is already" +
94 " used in the specified graph.\n")
95 end
96 end
97
98 # Save the graph.
99 fun save do
100 sys.stdout.write "Linking nodes...{term_save_cursor} "
101 flush_stdout
102 model.put_edges
103 print "{term_rewind} Done."
104 var nodes = model.all_nodes
105 sys.stdout.write "Saving {nodes.length} nodes..."
106 store.save_all(nodes)
107 var edges = model.all_edges
108 sys.stdout.write "Saving {edges.length} edges..."
109 store.save_all(edges)
110 end
111 end
112
113 # The main class.
114 class NeoDoxygenCommand
115
116 # Invalid arguments
117 var e_usage = 64
118
119 # Available options for `--src-lang`.
120 var sources = new HashMap[String, SourceLanguage]
121
122 # The synopsis.
123 var synopsis: String = "[--dest <url>] [--src-lang <lang>]\n" +
124 " [--] <project_name> <doxml_dir>"
125
126 # The synopsis for the help page.
127 var help_synopsis = "[-h|--help]"
128
129 # The default destination.
130 var default_dest = "http://localhost:7474"
131
132 # Processes the options.
133 var option_context = new OptionContext
134
135 # The `--src-lang` option.
136 var opt_src_lang: OptionEnum is noinit
137
138 # The `--dest` option.
139 var opt_dest: OptionString is noinit
140
141 # The `-h|--help` option.
142 var opt_help: OptionBool is noinit
143
144 init do
145 sources["any"] = new DefaultSource
146 sources["java"] = new JavaSource
147
148 var prefix = new OptionText("""
149 {{{"NAME".bold}}}
150 {{{sys.program_name}}} — Doxygen XML to Neo4j.
151
152 {{{"SYNOPSIS".bold}}}
153 {{{sys.program_name}}} {{{synopsis}}}
154 {{{sys.program_name}}} {{{help_synopsis}}}
155
156 {{{"DESCRIPTION".bold}}}
157 Convert a Doxygen XML output into a model in Neo4j that is readable by the
158 `nx` tool.
159
160 {{{"ARGUMENTS".bold}}}
161 <project_name> The internal name of the project. Must the same name as the
162 one specified to the `nx` tool. Must not begin by an upper
163 case letter.
164
165 <doxml_dir> The directory where the XML documents generated by Doxygen are
166 located.
167
168 {{{"OPTIONS".bold}}}
169 """)
170 option_context.add_option(prefix)
171
172 opt_dest = new OptionString("The URL of the destination graph. `{default_dest}` by default.",
173 "--dest")
174 opt_dest.default_value = default_dest
175 option_context.add_option(opt_dest)
176
177 opt_help = new OptionBool("Show the help (this page).",
178 "-h", "--help")
179 option_context.add_option(opt_help)
180
181 var keys = new Array[String].from(sources.keys)
182 opt_src_lang = new OptionEnum(keys,
183 "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.",
184 keys.index_of("any"), "--src-lang")
185 option_context.add_option(opt_src_lang)
186 end
187
188 # Start the application.
189 fun main: Int do
190 if args.is_empty then
191 show_help
192 return e_usage
193 end
194 option_context.parse(args)
195
196 var errors = option_context.get_errors
197 var rest = option_context.rest
198
199 if errors.is_empty and not opt_help.value and rest.length != 2 then
200 errors.add "Unexpected number of additional arguments. Expecting 2; got {rest.length}."
201 end
202 if not errors.is_empty then
203 for e in errors do print_error(e)
204 show_usage
205 return e_usage
206 end
207 if opt_help.value then
208 show_help
209 return 0
210 end
211
212 var source = sources[opt_src_lang.value_name]
213 var dest = opt_dest.value
214 var project_name = rest[0]
215 var dir = rest[1]
216 var neo = new NeoDoxygenJob(create_store(dest or else default_dest))
217
218 neo.load_project(project_name, dir, source)
219 neo.save
220 return 0
221 end
222
223 # Create an instance of `GraphStore` for the specified destination.
224 protected fun create_store(dest: String): GraphStore do
225 return new Neo4jStore(new Neo4jClient(dest))
226 end
227
228 # Show the help.
229 fun show_help do
230 option_context.usage
231 end
232
233 # Show the usage.
234 fun show_usage do
235 sys.stderr.write "Usage: {sys.program_name} {synopsis}\n"
236 sys.stderr.write "For details, run `{sys.program_name} --help`.\n"
237 end
238
239 # Print an error.
240 fun print_error(e: String) do
241 sys.stderr.write "{sys.program_name}: {e}\n"
242 end
243 end
244
245 # Add handling of multi-line descriptions.
246 #
247 # Note: The algorithm is naive and do not handle internationalisation and
248 # escape sequences.
249 redef class Option
250
251 redef fun pretty(off) do
252 var s = super
253
254 if s.length > 80 and off < 80 then
255 var column_length = 80 - off
256 var left = 0
257 var right = 80
258 var buf = new FlatBuffer
259 var prefix = "\n{" " * off}"
260
261 loop
262 while right > left and s.chars[right] != ' ' do
263 right -= 1
264 end
265 if left == right then
266 buf.append s.substring(left, column_length)
267 right += column_length
268 else
269 buf.append s.substring(left, right - left)
270 right += 1
271 end
272 buf.append prefix
273 left = right
274 right += column_length
275 if right >= s.length then break
276 end
277 buf.append s.substring_from(left)
278 buf.append "\n"
279 return buf.to_s
280 else
281 return "{s}\n"
282 end
283 end
284 end
285
286 exit((new NeoDoxygenCommand).main)