1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Doxygen XML to Neo4j.
17 # Converts a Doxygen XML output into a model in Neo4j that is readable by the
26 # An importation task.
28 var client
: Neo4jClient
29 var model
: ProjectGraph is noinit
31 # How many operation can be executed in one batch?
32 private var batch_max_size
= 1000
34 private var save_cursor
: String = (new TermSaveCursor).to_s
36 # Escape control sequence to reset the current line.
37 private var reset_line
: String = "{new TermRestoreCursor}{new TermEraseDisplayDown}"
39 # Generate a graph from the specified project model.
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
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]
54 if dir
.length
> 1 and dir
.chars
.last
== "/" then
55 dir
= dir
.substring
(0, dir
.length
- 1)
57 sys
.stdout
.write save_cursor
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}..."
68 if directories
.length
<= 0 then break
71 print
"{reset_line}Reading... Done."
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")
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")
92 print
"Linking nodes...{save_cursor}"
94 print
"{reset_line} Done."
95 var nodes
= model
.all_nodes
96 print
"Saving {nodes.length} nodes...{save_cursor}"
98 var edges
= model
.all_edges
99 print
"Saving {edges.length} edges...{save_cursor}"
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
110 for nentity
in neo_entities
do
111 batch
.save_entity
(nentity
)
112 if i
== batch_max_size
then
114 sum
+= batch_max_size
115 print
("{reset_line} {sum * 100 / len}%")
116 batch
= new NeoBatch(client
)
123 print
("{reset_line} Done.")
126 # Execute `batch` and check for errors.
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")
139 class NeoDoxygenCommand
144 # Available options for `--src-lang`.
145 var sources
= new HashMap[String, SourceLanguage]
148 var synopsis
: String = "[--dest <url>] [--src-lang <lang>]\n" +
149 " [--] <project_name> <doxml_dir>"
151 # The synopsis for the help page.
152 var help_synopsis
= "[-h|--help]"
154 # The default destination.
155 var default_dest
= "http://localhost:7474"
157 # Processes the options.
158 var option_context
= new OptionContext
160 # The `--src-lang` option.
161 var opt_src_lang
: OptionEnum is noinit
163 # The `--dest` option.
164 var opt_dest
: OptionString is noinit
166 # The `-h|--help` option.
167 var opt_help
: OptionBool is noinit
170 sources
["any"] = new DefaultSource
171 sources
["java"] = new JavaSource
173 var prefix
= new OptionText("""
175 {{{sys.program_name}}} — Doxygen XML to Neo4j.
177 {{{"SYNOPSIS".bold}}}
178 {{{sys.program_name}}} {{{synopsis}}}
179 {{{sys.program_name}}} {{{help_synopsis}}}
181 {{{"DESCRIPTION".bold}}}
182 Convert a Doxygen XML output into a model in Neo4j that is readable by the
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
190 <doxml_dir> The directory where the XML documents generated by Doxygen are
195 option_context
.add_option
(prefix
)
197 opt_dest
= new OptionString("The URL of the destination graph. `{default_dest}` by default.",
199 opt_dest
.default_value
= default_dest
200 option_context
.add_option
(opt_dest
)
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
)
208 opt_help
= new OptionBool("Show the help (this page).",
210 option_context
.add_option
(opt_help
)
213 # Start the application.
215 if args
.is_empty
then
219 option_context
.parse
(args
)
221 var errors
= option_context
.get_errors
222 var rest
= option_context
.rest
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}."
227 if not errors
.is_empty
then
228 for e
in errors
do print_error
(e
)
232 if opt_help
.value
then
237 var source
= sources
[opt_src_lang
.value_name
]
238 var dest
= opt_dest
.value
239 var project_name
= rest
[0]
241 var neo
= new NeoDoxygenJob(new Neo4jClient(dest
or else default_dest
))
243 neo
.load_project
(project_name
, dir
, source
)
255 sys
.stderr
.write
"Usage: {sys.program_name} {synopsis}\n"
256 sys
.stderr
.write
"For details, run `{sys.program_name} --help`.\n"
260 fun print_error
(e
: String) do
261 sys
.stderr
.write
"{sys.program_name}: {e}\n"
265 # Add handling of multi-line descriptions.
267 # Note: The algorithm is naive and do not handle internationalisation and
271 redef fun pretty
(off
) do
274 if s
.length
> 80 and off
< 80 then
275 var column_length
= 80 - off
278 var buf
= new FlatBuffer
279 var prefix
= "\n{" " * off}"
282 while right
> left
and s
.chars
[right
] != ' ' do
285 if left
== right
then
286 buf
.append s
.substring
(left
, column_length
)
287 right
+= column_length
289 buf
.append s
.substring
(left
, right
- left
)
294 right
+= column_length
295 if right
>= s
.length
then break
297 buf
.append s
.substring_from
(left
)
306 exit
((new NeoDoxygenCommand).main
)