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