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