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 var reader
= new CompoundFileReader(model
, source
)
50 # Queue for sub-directories.
51 var directories
= new Array[String]
55 sys
.stdout
.write
"Reading the current directory... "
57 sys
.stdout
.write
"Reading {dir}... "
62 if path
.file_stat
.is_dir
then
63 directories
.push
(path
)
64 else if f
.has_suffix
(".xml") and f
!= "index.xml" then
69 if directories
.length
<= 0 then break
73 if file_count
< 2 then
74 print
"{file_count} file read."
76 print
"{file_count} files read."
80 # Check the project’s name.
81 private fun check_name
(name
: String) do
82 assert name_valid
: not name
.chars
.first
.is_upper
else
83 sys
.stderr
.write
("{sys.program_name}: The project’s name must not" +
84 " begin with an upper case letter. Got `{name}`.\n")
86 var query
= new CypherQuery.from_string
("match n where \{name\} in labels(n) return count(n)")
87 query
.params
["name"] = name
88 var data
= client
.cypher
(query
).as(JsonObject)["data"]
89 var result
= data
.as(JsonArray).first
.as(JsonArray).first
.as(Int)
90 assert name_unused
: result
== 0 else
91 sys
.stderr
.write
("{sys.program_name}: The label `{name}` is already" +
92 " used in the specified graph.\n")
98 sys
.stdout
.write
"Linking nodes...{save_cursor} "
100 print
"{reset_line} Done."
101 var nodes
= model
.all_nodes
102 sys
.stdout
.write
"Saving {nodes.length} nodes...{save_cursor} "
104 var edges
= model
.all_edges
105 sys
.stdout
.write
"Saving {edges.length} edges...{save_cursor} "
109 # Save `neo_entities` in the database using batch mode.
110 private fun push_all
(neo_entities
: Collection[NeoEntity]) do
111 var batch
= new NeoBatch(client
)
112 var len
= neo_entities
.length
116 for nentity
in neo_entities
do
117 batch
.save_entity
(nentity
)
118 if i
== batch_max_size
then
120 sum
+= batch_max_size
121 sys
.stdout
.write
("{reset_line} {sum * 100 / len}% ")
122 batch
= new NeoBatch(client
)
129 print
("{reset_line} Done.")
132 # Execute `batch` and check for errors.
134 # Abort if `batch.execute` returns errors.
135 private fun do_batch
(batch
: NeoBatch) do
136 var errors
= batch
.execute
137 if not errors
.is_empty
then
138 for e
in errors
do sys
.stderr
.write
("{sys.program_name}: {e}\n")
145 class NeoDoxygenCommand
150 # Available options for `--src-lang`.
151 var sources
= new HashMap[String, SourceLanguage]
154 var synopsis
: String = "[--dest <url>] [--src-lang <lang>]\n" +
155 " [--] <project_name> <doxml_dir>"
157 # The synopsis for the help page.
158 var help_synopsis
= "[-h|--help]"
160 # The default destination.
161 var default_dest
= "http://localhost:7474"
163 # Processes the options.
164 var option_context
= new OptionContext
166 # The `--src-lang` option.
167 var opt_src_lang
: OptionEnum is noinit
169 # The `--dest` option.
170 var opt_dest
: OptionString is noinit
172 # The `-h|--help` option.
173 var opt_help
: OptionBool is noinit
176 sources
["any"] = new DefaultSource
177 sources
["java"] = new JavaSource
179 var prefix
= new OptionText("""
181 {{{sys.program_name}}} — Doxygen XML to Neo4j.
183 {{{"SYNOPSIS".bold}}}
184 {{{sys.program_name}}} {{{synopsis}}}
185 {{{sys.program_name}}} {{{help_synopsis}}}
187 {{{"DESCRIPTION".bold}}}
188 Convert a Doxygen XML output into a model in Neo4j that is readable by the
191 {{{"ARGUMENTS".bold}}}
192 <project_name> The internal name of the project. Must the same name as the
193 one specified to the `nx` tool. Must not begin by an upper
196 <doxml_dir> The directory where the XML documents generated by Doxygen are
201 option_context
.add_option
(prefix
)
203 opt_dest
= new OptionString("The URL of the destination graph. `{default_dest}` by default.",
205 opt_dest
.default_value
= default_dest
206 option_context
.add_option
(opt_dest
)
208 opt_help
= new OptionBool("Show the help (this page).",
210 option_context
.add_option
(opt_help
)
212 var keys
= new Array[String].from
(sources
.keys
)
213 opt_src_lang
= new OptionEnum(keys
,
214 "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.",
215 keys
.index_of
("any"), "--src-lang")
216 option_context
.add_option
(opt_src_lang
)
219 # Start the application.
221 if args
.is_empty
then
225 option_context
.parse
(args
)
227 var errors
= option_context
.get_errors
228 var rest
= option_context
.rest
230 if errors
.is_empty
and not opt_help
.value
and rest
.length
!= 2 then
231 errors
.add
"Unexpected number of additional arguments. Expecting 2; got {rest.length}."
233 if not errors
.is_empty
then
234 for e
in errors
do print_error
(e
)
238 if opt_help
.value
then
243 var source
= sources
[opt_src_lang
.value_name
]
244 var dest
= opt_dest
.value
245 var project_name
= rest
[0]
247 var neo
= new NeoDoxygenJob(new Neo4jClient(dest
or else default_dest
))
249 neo
.load_project
(project_name
, dir
, source
)
261 sys
.stderr
.write
"Usage: {sys.program_name} {synopsis}\n"
262 sys
.stderr
.write
"For details, run `{sys.program_name} --help`.\n"
266 fun print_error
(e
: String) do
267 sys
.stderr
.write
"{sys.program_name}: {e}\n"
271 # Add handling of multi-line descriptions.
273 # Note: The algorithm is naive and do not handle internationalisation and
277 redef fun pretty
(off
) do
280 if s
.length
> 80 and off
< 80 then
281 var column_length
= 80 - off
284 var buf
= new FlatBuffer
285 var prefix
= "\n{" " * off}"
288 while right
> left
and s
.chars
[right
] != ' ' do
291 if left
== right
then
292 buf
.append s
.substring
(left
, column_length
)
293 right
+= column_length
295 buf
.append s
.substring
(left
, right
- left
)
300 right
+= column_length
301 if right
>= s
.length
then break
303 buf
.append s
.substring_from
(left
)
312 exit
((new NeoDoxygenCommand).main
)