# This file is part of NIT ( http://www.nitlanguage.org ). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Doxygen XML to Neo4j. # # Converts a Doxygen XML output into a model in Neo4j that is readable by the # `nx` tool. module neo_doxygen import model import doxml import graph_store import console import opts # An importation task. class NeoDoxygenJob # The storage medium to use. var store: GraphStore # The loaded project graph. var model: ProjectGraph is noinit # Escape control sequence to save the cursor position. private var term_save_cursor: String = (new TermSaveCursor).to_s # Escape control sequence to rewind to the last saved cursor position. private var term_rewind: String = "{new TermRestoreCursor}{new TermEraseDisplayDown}" # Generate a graph from the specified project model. # # Parameters: # # * `name`: project name. # * `dir`: Doxygen XML output directory path. # * `source`: The language-specific logics to use. fun load_project(name: String, dir: String, source: SourceLanguage) do check_name name model = new ProjectGraph(name) var reader = new CompoundFileReader(model, source) # Queue for sub-directories. var directories = new Array[String] var file_count = 0 if dir == "" then printn "Reading the current directory... " else printn "Reading {dir}... " end loop for f in list_files(dir) do var path = dir/f if path.file_stat.as(not null).is_dir then directories.push(path) else if f.has_suffix(".xml") and f != "index.xml" then reader.read(path) file_count += 1 end end if directories.length <= 0 then break dir = directories.pop end model.add_global_modules print "Done." if file_count < 2 then print "{file_count} file read." else print "{file_count} files read." end end # List files in a directory. # # This method may be redefined to force the order in which the files # are read by `load_project`. protected fun list_files(dir: String): Collection[String] do return dir.files end # Check the project’s name. private fun check_name(name: String) do assert name_valid: not name.chars.first.is_upper else sys.stderr.write("{sys.program_name}: The project’s name must not" + " begin with an upper case letter. Got `{name}`.\n") end assert name_unused: not store.has_node_label(name) else sys.stderr.write("{sys.program_name}: The label `{name}` is already" + " used in the specified graph.\n") end end # Save the graph. fun save do sys.stdout.write "Linking nodes...{term_save_cursor} " model.put_edges print "{term_rewind} Done." var nodes = model.all_nodes sys.stdout.write "Saving {nodes.length} nodes..." store.save_all(nodes) var edges = model.all_edges sys.stdout.write "Saving {edges.length} edges..." store.save_all(edges) end end # The main class. class NeoDoxygenCommand # Invalid arguments var e_usage = 64 # Available options for `--src-lang`. var sources = new HashMap[String, SourceLanguage] # The synopsis. var synopsis: String = "[--dest ] [--src-lang ]\n" + " [--] " # The synopsis for the help page. var help_synopsis = "[-h|--help]" # The default destination. var default_dest = "http://localhost:7474" # Processes the options. var option_context = new OptionContext # The `--src-lang` option. var opt_src_lang: OptionEnum is noinit # The `--dest` option. var opt_dest: OptionString is noinit # The `-h|--help` option. var opt_help: OptionBool is noinit init do sources["any"] = new DefaultSource sources["java"] = new JavaSource sources["python"] = new PythonSource var prefix = new OptionText(""" {{{"NAME".bold}}} {{{sys.program_name}}} — Doxygen XML to Neo4j. {{{"SYNOPSIS".bold}}} {{{sys.program_name}}} {{{synopsis}}} {{{sys.program_name}}} {{{help_synopsis}}} {{{"DESCRIPTION".bold}}} Convert a Doxygen XML output into a model in Neo4j that is readable by the `nx` tool. {{{"ARGUMENTS".bold}}} The internal name of the project. Must the same name as the one specified to the `nx` tool. Must not begin by an upper case letter. The directory where the XML documents generated by Doxygen are located. {{{"OPTIONS".bold}}} """) option_context.add_option(prefix) opt_dest = new OptionString("The URL of the destination graph. `{default_dest}` by default.", "--dest") opt_dest.default_value = default_dest option_context.add_option(opt_dest) opt_help = new OptionBool("Show the help (this page).", "-h", "--help") option_context.add_option(opt_help) var keys = new Array[String].from(sources.keys) opt_src_lang = new OptionEnum(keys, "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.", keys.index_of("any"), "--src-lang") option_context.add_option(opt_src_lang) end # Start the application. fun main: Int do if args.is_empty then show_help return e_usage end option_context.parse(args) var errors = option_context.errors var rest = option_context.rest if errors.is_empty and not opt_help.value and rest.length != 2 then errors.add "Unexpected number of additional arguments. Expecting 2; got {rest.length}." end if not errors.is_empty then for e in errors do print_error(e) show_usage return e_usage end if opt_help.value then show_help return 0 end var source = sources[opt_src_lang.value_name] var dest = opt_dest.value var project_name = rest[0] var dir = rest[1] var neo = new NeoDoxygenJob(create_store(dest or else default_dest)) neo.load_project(project_name, dir, source) neo.save return 0 end # Create an instance of `GraphStore` for the specified destination. protected fun create_store(dest: String): GraphStore do return new Neo4jStore(new Neo4jClient(dest)) end # Show the help. fun show_help do option_context.usage end # Show the usage. fun show_usage do sys.stderr.write "Usage: {sys.program_name} {synopsis}\n" sys.stderr.write "For details, run `{sys.program_name} --help`.\n" end # Print an error. fun print_error(e: String) do sys.stderr.write "{sys.program_name}: {e}\n" end end # Add handling of multi-line descriptions. # # Note: The algorithm is naive and do not handle internationalisation, # multi-byte characters and control characters. redef class Option redef fun pretty(off) do var s = super if s.length > 80 and off < 80 then var column_length = 80 - off var left = 0 var right = 80 var buf = new FlatBuffer var prefix = "\n{" " * off}" loop while right > left and s.chars[right] != ' ' do right -= 1 end if left == right then buf.append s.substring(left, column_length) right += column_length else buf.append s.substring(left, right - left) right += 1 end buf.append prefix left = right right += column_length if right >= s.length then break end buf.append s.substring_from(left) buf.append "\n" return buf.to_s else return "{s}\n" end end end exit((new NeoDoxygenCommand).main)