Class: Rigor::ModuleGraph::CLI::StatsCmd

Inherits:
Object
  • Object
show all
Includes:
EdgeFilters
Defined in:
lib/rigor/module_graph/cli.rb

Overview

‘stats` reports the fan-out / fan-in / internal / nodes numbers per namespace. Same filter flags as the renderers so a focused subgraph can be summarised without regenerating the JSONL.

Constant Summary collapse

FORMATS =
%w[text json].freeze
HEADERS =
%w[namespace nodes fan-out fan-in internal total].freeze

Constants included from EdgeFilters

EdgeFilters::VALID_CONFIDENCES, EdgeFilters::VALID_DIRECTIONS, EdgeFilters::VALID_EDGE_SCOPES, EdgeFilters::VALID_KINDS

Instance Method Summary collapse

Methods included from EdgeFilters

#add_filter_options, #apply_filters, #validate!

Constructor Details

#initialize(stdout:, stderr:, stdin:) ⇒ StatsCmd

Returns a new instance of StatsCmd.



859
860
861
862
863
864
865
866
867
868
# File 'lib/rigor/module_graph/cli.rb', line 859

def initialize(stdout:, stderr:, stdin:)
  @stdout = stdout
  @stderr = stderr
  @stdin = stdin
  @state = {
    kinds: nil, confidences: nil,
    from: nil, depth: nil, direction: :both, edge_scope: :cluster,
    grouping_depth: 1, format: "text", limit: nil
  }
end

Instance Method Details

#format_row(row, widths) ⇒ Object



952
953
954
955
956
# File 'lib/rigor/module_graph/cli.rb', line 952

def format_row(row, widths)
  row.each_with_index.map do |cell, idx|
    idx.zero? ? cell.ljust(widths[idx]) : cell.rjust(widths[idx])
  end.join("  ")
end

#format_table(metrics) ⇒ Object

A space-padded text table sized to the widest cell per column. Numeric columns are right-aligned so a quick eye-scan finds the hotspots.



934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
# File 'lib/rigor/module_graph/cli.rb', line 934

def format_table(metrics)
  if metrics.empty?
    return "(no edges)\n"
  end

  rows = metrics.map do |m|
    [m.namespace, m.nodes.to_s, m.fan_out.to_s, m.fan_in.to_s,
     m.internal.to_s, m.total.to_s]
  end
  widths = HEADERS.zip(*rows).map { |col| col.map(&:length).max }

  out = +""
  out << format_row(HEADERS, widths) << "\n"
  out << ("-" * widths.sum { |w| w + 2 }) << "\n"
  rows.each { |row| out << format_row(row, widths) << "\n" }
  out
end

#parse_options!(argv) ⇒ Object



898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
# File 'lib/rigor/module_graph/cli.rb', line 898

def parse_options!(argv)
  parser = OptionParser.new do |opts|
    opts.banner = "Usage: rigor-module-graph stats [options] [FILE]"
    opts.on("--grouping-depth N", Integer,
            "How many leading namespace segments to group by (default: 1)") do |n|
      @state[:grouping_depth] = n
    end
    opts.on("--limit N", Integer,
            "Show only the top N namespaces by fan-out") do |n|
      @state[:limit] = n
    end
    opts.on("--format FORMAT", FORMATS,
            "Output format (#{FORMATS.join("/")}; default: text)") do |fmt|
      @state[:format] = fmt
    end
    add_filter_options(opts, @state)
    opts.on("-h", "--help") do
      @stdout.puts opts
      exit 0
    end
  end
  parser.parse!(argv)
end

#render(metrics) ⇒ Object



922
923
924
925
926
927
928
929
# File 'lib/rigor/module_graph/cli.rb', line 922

def render(metrics)
  case @state[:format]
  when "json"
    @stdout.puts(JSON.pretty_generate(metrics.map(&:to_h)))
  when "text"
    @stdout.print(format_table(metrics))
  end
end

#run(argv) ⇒ Object



870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
# File 'lib/rigor/module_graph/cli.rb', line 870

def run(argv)
  argv = argv.dup
  parse_options!(argv)
  path, = argv
  io = path ? File.open(path, "r") : @stdin
  begin
    edges = EdgeIO.read(io)
  ensure
    io.close if path && !io.closed?
  end
  edges = apply_filters(
    edges,
    kinds: @state[:kinds],
    confidences: @state[:confidences],
    from: @state[:from],
    depth: @state[:depth],
    direction: @state[:direction],
    edge_scope: @state[:edge_scope]
  )
  metrics = Stats.compute(edges, depth: @state[:grouping_depth])
  metrics = metrics.first(@state[:limit]) if @state[:limit]
  render(metrics)
  0
rescue OptionParser::ParseError => e
  @stderr.puts "rigor-module-graph stats: #{e.message}"
  2
end