pthreads: delete `NativePthread::attr` as it is not portable and unused
[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 console
24 import opts
25
26 # An importation task.
27 class NeoDoxygenJob
28 var client: Neo4jClient
29 var model: ProjectGraph is noinit
30
31 # How many operation can be executed in one batch?
32 private var batch_max_size = 1000
33
34 private var save_cursor: String = (new TermSaveCursor).to_s
35
36 # Escape control sequence to reset the current line.
37 private var reset_line: String = "{new TermRestoreCursor}{new TermEraseDisplayDown}"
38
39 # Generate a graph from the specified project model.
40 #
41 # Parameters:
42 #
43 # * `name`: project name.
44 # * `dir`: Doxygen XML output directory path.
45 # * `source`: The language-specific logics to use.
46 fun load_project(name: String, dir: String, source: SourceLanguage) do
47 check_name name
48 model = new ProjectGraph(name)
49 # TODO Let the user select the language.
50 var reader = new CompoundFileReader(model, source)
51 # Queue for sub-directories.
52 var directories = new Array[String]
53
54 if dir.length > 1 and dir.chars.last == "/" then
55 dir = dir.substring(0, dir.length - 1)
56 end
57 sys.stdout.write save_cursor
58 loop
59 for f in dir.files do
60 var path = dir/f
61 if path.file_stat.is_dir then
62 directories.push(path)
63 else if f.has_suffix(".xml") and f != "index.xml" then
64 print "{reset_line}Reading {path}..."
65 reader.read(path)
66 end
67 end
68 if directories.length <= 0 then break
69 dir = directories.pop
70 end
71 print "{reset_line}Reading... Done."
72 end
73
74 # Check the project’s name.
75 private fun check_name(name: String) do
76 assert name_valid: not name.chars.first.is_upper else
77 sys.stderr.write("{sys.program_name}: The project’s name must not" +
78 " begin with an upper case letter. Got `{name}`.\n")
79 end
80 var query = new CypherQuery.from_string("match n where \{name\} in labels(n) return count(n)")
81 query.params["name"] = name
82 var data = client.cypher(query).as(JsonObject)["data"]
83 var result = data.as(JsonArray).first.as(JsonArray).first.as(Int)
84 assert name_unused: result == 0 else
85 sys.stderr.write("{sys.program_name}: The label `{name}` is already" +
86 " used in the specified graph.\n")
87 end
88 end
89
90 # Save the graph.
91 fun save do
92 print "Linking nodes...{save_cursor}"
93 model.put_edges
94 print "{reset_line} Done."
95 var nodes = model.all_nodes
96 print "Saving {nodes.length} nodes...{save_cursor}"
97 push_all(nodes)
98 var edges = model.all_edges
99 print "Saving {edges.length} edges...{save_cursor}"
100 push_all(edges)
101 end
102
103 # Save `neo_entities` in the database using batch mode.
104 private fun push_all(neo_entities: Collection[NeoEntity]) do
105 var batch = new NeoBatch(client)
106 var len = neo_entities.length
107 var sum = 0
108 var i = 1
109
110 for nentity in neo_entities do
111 batch.save_entity(nentity)
112 if i == batch_max_size then
113 do_batch(batch)
114 sum += batch_max_size
115 print("{reset_line} {sum * 100 / len}%")
116 batch = new NeoBatch(client)
117 i = 1
118 else
119 i += 1
120 end
121 end
122 do_batch(batch)
123 print("{reset_line} Done.")
124 end
125
126 # Execute `batch` and check for errors.
127 #
128 # Abort if `batch.execute` returns errors.
129 private fun do_batch(batch: NeoBatch) do
130 var errors = batch.execute
131 if not errors.is_empty then
132 for e in errors do sys.stderr.write("{sys.program_name}: {e}\n")
133 exit(1)
134 end
135 end
136 end
137
138 # The main class.
139 class NeoDoxygenCommand
140
141 # Invalid arguments
142 var e_usage = 64
143
144 # Available options for `--src-lang`.
145 var sources = new HashMap[String, SourceLanguage]
146
147 # The synopsis.
148 var synopsis: String = "[--dest <url>] [--src-lang <lang>]\n" +
149 " [--] <project_name> <doxml_dir>"
150
151 # The synopsis for the help page.
152 var help_synopsis = "[-h|--help]"
153
154 # The default destination.
155 var default_dest = "http://localhost:7474"
156
157 # Processes the options.
158 var option_context = new OptionContext
159
160 # The `--src-lang` option.
161 var opt_src_lang: OptionEnum is noinit
162
163 # The `--dest` option.
164 var opt_dest: OptionString is noinit
165
166 # The `-h|--help` option.
167 var opt_help: OptionBool is noinit
168
169 init do
170 sources["any"] = new DefaultSource
171 sources["java"] = new JavaSource
172
173 var prefix = new OptionText("""
174 {{{"NAME".bold}}}
175 {{{sys.program_name}}} — Doxygen XML to Neo4j.
176
177 {{{"SYNOPSIS".bold}}}
178 {{{sys.program_name}}} {{{synopsis}}}
179 {{{sys.program_name}}} {{{help_synopsis}}}
180
181 {{{"DESCRIPTION".bold}}}
182 Convert a Doxygen XML output into a model in Neo4j that is readable by the
183 `nx` tool.
184
185 {{{"ARGUMENTS".bold}}}
186 <project_name> The internal name of the project. Must the same name as the
187 one specified to the `nx` tool. Must not begin by an upper
188 case letter.
189
190 <doxml_dir> The directory where the XML documents generated by Doxygen are
191 located.
192
193 {{{"OPTIONS".bold}}}
194 """)
195 option_context.add_option(prefix)
196
197 opt_dest = new OptionString("The URL of the destination graph. `{default_dest}` by default.",
198 "--dest")
199 opt_dest.default_value = default_dest
200 option_context.add_option(opt_dest)
201
202 var keys = new Array[String].from(sources.keys)
203 opt_src_lang = new OptionEnum(keys,
204 "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.",
205 keys.index_of("any"), "--src-lang")
206 option_context.add_option(opt_src_lang)
207
208 opt_help = new OptionBool("Show the help (this page).",
209 "-h", "--help")
210 option_context.add_option(opt_help)
211 end
212
213 # Start the application.
214 fun main: Int do
215 if args.is_empty then
216 show_help
217 return e_usage
218 end
219 option_context.parse(args)
220
221 var errors = option_context.get_errors
222 var rest = option_context.rest
223
224 if errors.is_empty and not opt_help.value and rest.length != 2 then
225 errors.add "Unexpected number of additional arguments. Expecting 2; got {rest.length}."
226 end
227 if not errors.is_empty then
228 for e in errors do print_error(e)
229 show_usage
230 return e_usage
231 end
232 if opt_help.value then
233 show_help
234 return 0
235 end
236
237 var source = sources[opt_src_lang.value_name]
238 var dest = opt_dest.value
239 var project_name = rest[0]
240 var dir = rest[1]
241 var neo = new NeoDoxygenJob(new Neo4jClient(dest or else default_dest))
242
243 neo.load_project(project_name, dir, source)
244 neo.save
245 return 0
246 end
247
248 # Show the help.
249 fun show_help do
250 option_context.usage
251 end
252
253 # Show the usage.
254 fun show_usage do
255 sys.stderr.write "Usage: {sys.program_name} {synopsis}\n"
256 sys.stderr.write "For details, run `{sys.program_name} --help`.\n"
257 end
258
259 # Print an error.
260 fun print_error(e: String) do
261 sys.stderr.write "{sys.program_name}: {e}\n"
262 end
263 end
264
265 # Add handling of multi-line descriptions.
266 #
267 # Note: The algorithm is naive and do not handle internationalisation and
268 # escape sequences.
269 redef class Option
270
271 redef fun pretty(off) do
272 var s = super
273
274 if s.length > 80 and off < 80 then
275 var column_length = 80 - off
276 var left = 0
277 var right = 80
278 var buf = new FlatBuffer
279 var prefix = "\n{" " * off}"
280
281 loop
282 while right > left and s.chars[right] != ' ' do
283 right -= 1
284 end
285 if left == right then
286 buf.append s.substring(left, column_length)
287 right += column_length
288 else
289 buf.append s.substring(left, right - left)
290 right += 1
291 end
292 buf.append prefix
293 left = right
294 right += column_length
295 if right >= s.length then break
296 end
297 buf.append s.substring_from(left)
298 buf.append "\n"
299 return buf.to_s
300 else
301 return "{s}\n"
302 end
303 end
304 end
305
306 exit((new NeoDoxygenCommand).main)