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
27 # An importation task.
30 # The storage medium to use.
33 # The loaded project graph.
34 var model
: ProjectGraph is noinit
36 # Escape control sequence to save the cursor position.
37 private var term_save_cursor
: String = (new TermSaveCursor).to_s
39 # Escape control sequence to rewind to the last saved cursor position.
40 private var term_rewind
: String = "{new TermRestoreCursor}{new TermEraseDisplayDown}"
42 # Generate a graph from the specified project model.
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
51 model
= new ProjectGraph(name
)
52 var reader
= new CompoundFileReader(model
, source
)
53 # Queue for sub-directories.
54 var directories
= new Array[String]
58 printn
"Reading the current directory... "
60 printn
"Reading {dir}... "
63 for f
in list_files
(dir
) do
65 if path
.file_stat
.is_dir
then
66 directories
.push
(path
)
67 else if f
.has_suffix
(".xml") and f
!= "index.xml" then
72 if directories
.length
<= 0 then break
75 model
.add_global_modules
77 if file_count
< 2 then
78 print
"{file_count} file read."
80 print
"{file_count} files read."
84 # List files in a directory.
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
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")
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")
106 sys
.stdout
.write
"Linking nodes...{term_save_cursor} "
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
)
119 class NeoDoxygenCommand
124 # Available options for `--src-lang`.
125 var sources
= new HashMap[String, SourceLanguage]
128 var synopsis
: String = "[--dest <url>] [--src-lang <lang>]\n" +
129 " [--] <project_name> <doxml_dir>"
131 # The synopsis for the help page.
132 var help_synopsis
= "[-h|--help]"
134 # The default destination.
135 var default_dest
= "http://localhost:7474"
137 # Processes the options.
138 var option_context
= new OptionContext
140 # The `--src-lang` option.
141 var opt_src_lang
: OptionEnum is noinit
143 # The `--dest` option.
144 var opt_dest
: OptionString is noinit
146 # The `-h|--help` option.
147 var opt_help
: OptionBool is noinit
150 sources
["any"] = new DefaultSource
151 sources
["java"] = new JavaSource
152 sources
["python"] = new PythonSource
154 var prefix
= new OptionText("""
156 {{{sys.program_name}}} — Doxygen XML to Neo4j.
158 {{{"SYNOPSIS".bold}}}
159 {{{sys.program_name}}} {{{synopsis}}}
160 {{{sys.program_name}}} {{{help_synopsis}}}
162 {{{"DESCRIPTION".bold}}}
163 Convert a Doxygen XML output into a model in Neo4j that is readable by the
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
171 <doxml_dir> The directory where the XML documents generated by Doxygen are
176 option_context
.add_option
(prefix
)
178 opt_dest
= new OptionString("The URL of the destination graph. `{default_dest}` by default.",
180 opt_dest
.default_value
= default_dest
181 option_context
.add_option
(opt_dest
)
183 opt_help
= new OptionBool("Show the help (this page).",
185 option_context
.add_option
(opt_help
)
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
)
194 # Start the application.
196 if args
.is_empty
then
200 option_context
.parse
(args
)
202 var errors
= option_context
.get_errors
203 var rest
= option_context
.rest
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}."
208 if not errors
.is_empty
then
209 for e
in errors
do print_error
(e
)
213 if opt_help
.value
then
218 var source
= sources
[opt_src_lang
.value_name
]
219 var dest
= opt_dest
.value
220 var project_name
= rest
[0]
222 var neo
= new NeoDoxygenJob(create_store
(dest
or else default_dest
))
224 neo
.load_project
(project_name
, dir
, source
)
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
))
241 sys
.stderr
.write
"Usage: {sys.program_name} {synopsis}\n"
242 sys
.stderr
.write
"For details, run `{sys.program_name} --help`.\n"
246 fun print_error
(e
: String) do
247 sys
.stderr
.write
"{sys.program_name}: {e}\n"
251 # Add handling of multi-line descriptions.
253 # Note: The algorithm is naive and do not handle internationalisation,
254 # multi-byte characters and control characters.
257 redef fun pretty
(off
) do
260 if s
.length
> 80 and off
< 80 then
261 var column_length
= 80 - off
264 var buf
= new FlatBuffer
265 var prefix
= "\n{" " * off}"
268 while right
> left
and s
.chars
[right
] != ' ' do
271 if left
== right
then
272 buf
.append s
.substring
(left
, column_length
)
273 right
+= column_length
275 buf
.append s
.substring
(left
, right
- left
)
280 right
+= column_length
281 if right
>= s
.length
then break
283 buf
.append s
.substring_from
(left
)
292 exit
((new NeoDoxygenCommand).main
)