class Lutaml::Cli::TreeViewFormatter
TreeViewFormatter formats UML repository contents as a colored tree
def colorize(text, color_key)
def colorize(text, color_key) return text if @no_color color = COLORS[color_key] return text unless color Paint[text, color] end
def determine_class_type(klass)
def determine_class_type(klass) return :enumeration if enumeration?(klass) return :interface if interface?(klass) :class end
def enumeration?(klass)
def enumeration?(klass) klass.class.name&.include?("Enum") end
def find_package_path(repository, package)
def find_package_path(repository, package) repository.indexes[:package_paths].each do |path, pkg| return path if pkg == package end nil end
def format(repository) # rubocop:disable Metrics/MethodLength
-
(String)- Formatted tree output
Parameters:
-
repository(Lutaml::UmlRepository::Repository) --
def format(repository) # rubocop:disable Metrics/MethodLength output = [] # Start with ModelRoot output << colorize("ModelRoot", :package) # Get all top-level packages root_packages = repository.list_packages("ModelRoot", recursive: false) root_packages.each_with_index do |pkg, idx| is_last = idx == root_packages.size - 1 output << format_package(pkg, repository, 0, is_last, "") end # Add statistics at the end output << "" output << format_statistics(repository.statistics) output.join("\n") end
def format_association(assoc, depth, is_last, prefix) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
Format an association node
def format_association(assoc, depth, is_last, prefix) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity return "" if @max_depth && depth >= @max_depth assoc_name = assoc.name || "(unnamed)" target = assoc.member_end || "Unknown" connector = is_last ? TREE_CHARS[:last_branch] : TREE_CHARS[:branch] icon = ICONS[:association] "#{prefix}#{connector} #{icon} #{colorize("#{assoc_name} → #{target}", :association)}" end
def format_attribute(attr, depth, is_last, prefix)
def format_attribute(attr, depth, is_last, prefix) return "" if @max_depth && depth >= @max_depth attr_name = attr.name || "(unnamed)" attr_type = attr.type || "Unknown" connector = is_last ? TREE_CHARS[:last_branch] : TREE_CHARS[:branch] icon = ICONS[:attribute] "#{prefix}#{connector} #{icon} #{colorize( "#{attr_name} : #{attr_type}", :attribute )}" end
def format_class(klass, repository, depth, is_last, prefix, package_path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity,Metrics/ParameterLists
Format a class node
def format_class(klass, repository, depth, is_last, prefix, package_path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity,Metrics/ParameterLists return "" if @max_depth && depth >= @max_depth output = [] class_name = klass.name || "(unnamed)" # Determine class type type = determine_class_type(klass) icon = ICONS[type] || ICONS[:class] color = COLORS[type] || COLORS[:class] # Class line connector = is_last ? TREE_CHARS[:last_branch] : TREE_CHARS[:branch] type_label = type == :class ? "Class" : type.to_s.capitalize output << "#{prefix}#{connector} #{icon} " \ "#{colorize(class_name, color)} (#{type_label})" # Update prefix for children child_prefix = prefix + ( is_last ? TREE_CHARS[:space] : TREE_CHARS[:continuation] ) # Collect children children = [] # Add attributes if @show_attributes && klass.is_a?(Lutaml::Uml::Classifier) && klass.attributes children.concat(klass.attributes.map do |a| { type: :attribute, obj: a } end) end # Add operations if @show_operations && klass.is_a?(Lutaml::Uml::Classifier) && klass.operations children.concat(klass.operations.map do |o| { type: :operation, obj: o } end) end # Add associations if @show_associations qualified_name = "#{package_path}::#{class_name}" associations = repository.associations_of(qualified_name) children.concat(associations.map do |a| { type: :association, obj: a } end) end # Format each child children.each_with_index do |child, idx| is_last_child = idx == children.size - 1 case child[:type] when :attribute output << format_attribute(child[:obj], depth + 1, is_last_child, child_prefix) when :operation output << format_operation(child[:obj], depth + 1, is_last_child, child_prefix) when :association output << format_association(child[:obj], depth + 1, is_last_child, child_prefix) end end output.join("\n") end
def format_diagram(diagram, depth, is_last, prefix)
def format_diagram(diagram, depth, is_last, prefix) return "" if @max_depth && depth >= @max_depth diag_name = diagram.name || "(unnamed)" connector = is_last ? TREE_CHARS[:last_branch] : TREE_CHARS[:branch] icon = ICONS[:diagram] "#{prefix}#{connector} #{icon} #{colorize(diag_name, :diagram)}" end
def format_operation(op, depth, is_last, prefix)
def format_operation(op, depth, is_last, prefix) return "" if @max_depth && depth >= @max_depth op_name = op.name || "(unnamed)" connector = is_last ? TREE_CHARS[:last_branch] : TREE_CHARS[:branch] icon = ICONS[:operation] "#{prefix}#{connector} #{icon} #{colorize("#{op_name}()", :operation)}" end
def format_package(package, repository, depth, is_last, prefix) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
Format a package node
def format_package(package, repository, depth, is_last, prefix) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity return "" if @max_depth && depth >= @max_depth output = [] pkg_name = package.name || "(unnamed)" # Package line connector = is_last ? TREE_CHARS[:last_branch] : TREE_CHARS[:branch] icon = ICONS[:package] output << "#{prefix}#{connector} #{icon} #{colorize(pkg_name, :package)}" # Update prefix for children child_prefix = prefix + ( is_last ? TREE_CHARS[:space] : TREE_CHARS[:continuation] ) # Get package path for this package pkg_path = find_package_path(repository, package) return output.join("\n") unless pkg_path # Collect all children children = [] # Add sub-packages sub_packages = repository.list_packages(pkg_path, recursive: false) children.concat(sub_packages.map { |p| { type: :package, obj: p } }) # Add classes classes = repository.classes_in_package(pkg_path, recursive: false) children.concat(classes.map { |c| { type: :class, obj: c } }) # Add diagrams if at top level if depth.zero? diagrams = repository.diagrams_in_package(pkg_path) children.concat(diagrams.map { |d| { type: :diagram, obj: d } }) end # Format each child children.each_with_index do |child, idx| is_last_child = idx == children.size - 1 case child[:type] when :package output << format_package(child[:obj], repository, depth + 1, is_last_child, child_prefix) when :class output << format_class(child[:obj], repository, depth + 1, is_last_child, child_prefix, pkg_path) when :diagram output << format_diagram(child[:obj], depth + 1, is_last_child, child_prefix) end end output.join("\n") end
def format_statistics(stats)
def format_statistics(stats) output = [] output << colorize("📊 Statistics", :statistics) output << "#{TREE_CHARS[:branch]} Total Packages: " \ "#{stats[:total_packages]}" output << "#{TREE_CHARS[:branch]} Total Classes: " \ "#{stats[:total_classes]}" output << "#{TREE_CHARS[:last_branch]} Total Diagrams: " \ "#{stats[:total_diagrams]}" output.join("\n") end
def initialize(options = {})
def initialize(options = {}) @max_depth = options[:max_depth] @show_attributes = options.fetch(:show_attributes, true) @show_operations = options.fetch(:show_operations, true) @show_associations = options.fetch(:show_associations, false) @no_color = options.fetch(:no_color, false) @current_depth = 0 end
def interface?(klass)
def interface?(klass) klass.is_a?(Lutaml::Uml::TopElement) && Array(klass.stereotype).any? { |s| s&.downcase == "interface" } end