railbow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +31 -0
- data/LICENSE.txt +21 -0
- data/README.md +243 -0
- data/exe/railbow +110 -0
- data/lib/railbow/about_formatter.rb +37 -0
- data/lib/railbow/color_assigner.rb +53 -0
- data/lib/railbow/config.rb +113 -0
- data/lib/railbow/demo/fixtures.rb +121 -0
- data/lib/railbow/demo/migrate_demo.rb +35 -0
- data/lib/railbow/demo/routes_demo.rb +94 -0
- data/lib/railbow/demo/runner.rb +51 -0
- data/lib/railbow/demo/status_demo.rb +164 -0
- data/lib/railbow/formatters/base.rb +195 -0
- data/lib/railbow/git_utils.rb +29 -0
- data/lib/railbow/init.rb +112 -0
- data/lib/railbow/logo.rb +27 -0
- data/lib/railbow/migration_formatter.rb +64 -0
- data/lib/railbow/migration_parser.rb +87 -0
- data/lib/railbow/name_collision_resolver.rb +155 -0
- data/lib/railbow/name_formatter.rb +82 -0
- data/lib/railbow/notes_formatter.rb +311 -0
- data/lib/railbow/params.rb +229 -0
- data/lib/railbow/railtie.rb +44 -0
- data/lib/railbow/routes_formatter.rb +163 -0
- data/lib/railbow/stats_formatter.rb +99 -0
- data/lib/railbow/table/column.rb +24 -0
- data/lib/railbow/table/renderer.rb +441 -0
- data/lib/railbow/table/theme.rb +54 -0
- data/lib/railbow/table.rb +5 -0
- data/lib/railbow/tasks/init.rake +10 -0
- data/lib/railbow/tasks/migrate_status.rake +694 -0
- data/lib/railbow/tasks/stats.rake +85 -0
- data/lib/railbow/text_utils.rb +30 -0
- data/lib/railbow/version.rb +5 -0
- data/lib/railbow.rb +22 -0
- data/sig/railbow.rbs +4 -0
- metadata +137 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "formatters/base"
|
|
4
|
+
require_relative "config"
|
|
5
|
+
require_relative "table"
|
|
6
|
+
|
|
7
|
+
module Railbow
|
|
8
|
+
module RoutesFormatter
|
|
9
|
+
VERB_COLORS = {
|
|
10
|
+
"GET" => Formatters::Base::GREEN,
|
|
11
|
+
"POST" => Formatters::Base::YELLOW,
|
|
12
|
+
"PATCH" => Formatters::Base::CYAN,
|
|
13
|
+
"PUT" => Formatters::Base::CYAN,
|
|
14
|
+
"DELETE" => Formatters::Base::RED
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
RESET = Formatters::Base::RESET
|
|
18
|
+
BOLD = Formatters::Base::BOLD
|
|
19
|
+
DIM = Formatters::Base::DIM
|
|
20
|
+
CYAN = Formatters::Base::CYAN
|
|
21
|
+
|
|
22
|
+
HELP_TEXT_COLOR = <<~HELP
|
|
23
|
+
|
|
24
|
+
#{BOLD}Railbow Routes Options:#{RESET}
|
|
25
|
+
|
|
26
|
+
#{CYAN}RBW_VERB=GET#{RESET} Show only GET routes
|
|
27
|
+
#{CYAN}RBW_VERB=POST,PUT#{RESET} Show only POST and PUT routes
|
|
28
|
+
#{CYAN}RBW_COMPACT=strip-format#{RESET} Strip (.:format) suffixes
|
|
29
|
+
#{CYAN}RBW_COMPACT=oneline#{RESET} Truncate instead of wrapping
|
|
30
|
+
#{CYAN}RBW_COMPACT=dense#{RESET} Remove cell padding
|
|
31
|
+
#{CYAN}RBW_COMPACT=noheader#{RESET} Hide table header row
|
|
32
|
+
#{CYAN}RBW_COMPACT=maxw:40#{RESET} Cap column widths
|
|
33
|
+
#{CYAN}RBW_COMPACT=hide:prefix#{RESET} Hide a column by name
|
|
34
|
+
#{CYAN}RBW_PLAIN=1#{RESET} Disable Railbow formatting (plain Rails output)
|
|
35
|
+
#{CYAN}RBW_HELP=1#{RESET} Show this help message
|
|
36
|
+
|
|
37
|
+
#{DIM}Combine: RBW_COMPACT=strip-format,dense,noheader#{RESET}
|
|
38
|
+
#{DIM}Auto-disabled when piped, in CI, or when called by an LLM agent.#{RESET}
|
|
39
|
+
#{DIM}Example: RBW_VERB=GET rails routes#{RESET}
|
|
40
|
+
|
|
41
|
+
HELP
|
|
42
|
+
|
|
43
|
+
def section_title(title)
|
|
44
|
+
return super unless tty?
|
|
45
|
+
return if Railbow::Params.help?
|
|
46
|
+
|
|
47
|
+
@buffer << "\n#{BOLD}#{CYAN}#{title}:#{RESET}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def header(routes)
|
|
51
|
+
super unless tty?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def section(routes)
|
|
55
|
+
return super unless tty?
|
|
56
|
+
|
|
57
|
+
if Railbow::Params.help?
|
|
58
|
+
unless @help_shown
|
|
59
|
+
@buffer << HELP_TEXT_COLOR
|
|
60
|
+
@help_shown = true
|
|
61
|
+
end
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
strip_format = Railbow::Params.compact_strip_format?
|
|
66
|
+
prepared = routes.map { |r| prepare_route(r, strip_format) }
|
|
67
|
+
prepared = filter_by_verb(prepared)
|
|
68
|
+
|
|
69
|
+
groups = group_routes(prepared)
|
|
70
|
+
groups.each { |label, group| render_group(label, group) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def group_routes(routes)
|
|
76
|
+
grouped = routes.group_by { |r| r[:reqs].include?("#") ? r[:reqs].split("#").first : "(other)" }
|
|
77
|
+
other = grouped.delete("(other)")
|
|
78
|
+
grouped["(other)"] = other if other
|
|
79
|
+
grouped
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def filter_by_verb(routes)
|
|
83
|
+
verb_filter = Railbow::Params.verb
|
|
84
|
+
return routes if verb_filter.nil? || verb_filter.strip.empty? || verb_filter.strip.upcase == "ALL"
|
|
85
|
+
|
|
86
|
+
allowed = verb_filter.split(",").map { |v| v.strip.upcase }
|
|
87
|
+
routes.select { |r| (r[:verb].split("|") & allowed).any? }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def render_group(label, routes)
|
|
91
|
+
if label
|
|
92
|
+
@buffer << ""
|
|
93
|
+
@buffer << "#{BOLD}#{CYAN}\u2500\u2500 #{label} \u2500\u2500#{RESET}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
columns = [
|
|
97
|
+
Table::Column.new(label: "Verb", sticky: true),
|
|
98
|
+
Table::Column.new(label: "URI Pattern", sticky: true),
|
|
99
|
+
Table::Column.new(label: "Controller#Action"),
|
|
100
|
+
Table::Column.new(label: "Prefix")
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
rows = routes.map { |r|
|
|
104
|
+
[
|
|
105
|
+
colorize_verb(r[:verb]),
|
|
106
|
+
colorize_path(r[:path]),
|
|
107
|
+
colorize_reqs(r[:reqs]),
|
|
108
|
+
r[:name].empty? ? "" : colorize_name(r[:name])
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
renderer = Table::Renderer.new(
|
|
113
|
+
columns: columns,
|
|
114
|
+
theme: Table::Themes::PLAIN,
|
|
115
|
+
compact: Railbow::Params.compact_options,
|
|
116
|
+
aliases: Railbow::Config.table_aliases
|
|
117
|
+
)
|
|
118
|
+
@buffer << renderer.render(rows)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def prepare_route(route, compact)
|
|
122
|
+
r = route.dup
|
|
123
|
+
r[:path] = r[:path].sub("(.:format)", "") if compact
|
|
124
|
+
r
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def colorize_verb(verb)
|
|
128
|
+
return verb if verb.empty?
|
|
129
|
+
|
|
130
|
+
verb.split("|").map { |v|
|
|
131
|
+
color = VERB_COLORS[v]
|
|
132
|
+
color ? "#{color}#{v}#{RESET}" : v
|
|
133
|
+
}.join("#{DIM}|#{RESET}")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def colorize_path(path)
|
|
137
|
+
return path if path.empty?
|
|
138
|
+
|
|
139
|
+
path.gsub(/:[a-z_]+|\*[a-z_]+/) { |match| "#{CYAN}#{match}#{RESET}" }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def colorize_reqs(reqs)
|
|
143
|
+
return reqs if reqs.empty?
|
|
144
|
+
|
|
145
|
+
if reqs.include?("#")
|
|
146
|
+
controller, action = reqs.split("#", 2)
|
|
147
|
+
"#{BOLD}#{controller}#{RESET}#{DIM}##{RESET}#{action}"
|
|
148
|
+
else
|
|
149
|
+
"#{DIM}#{reqs}#{RESET}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def colorize_name(name)
|
|
154
|
+
return name if name.empty?
|
|
155
|
+
|
|
156
|
+
"#{DIM}#{name}#{RESET}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def tty?
|
|
160
|
+
!Railbow.plain?
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "formatters/base"
|
|
4
|
+
require_relative "config"
|
|
5
|
+
require_relative "table"
|
|
6
|
+
|
|
7
|
+
module Railbow
|
|
8
|
+
module StatsFormatter
|
|
9
|
+
def to_s
|
|
10
|
+
return super if Railbow.plain?
|
|
11
|
+
|
|
12
|
+
formatter = Formatters::Base.new
|
|
13
|
+
|
|
14
|
+
columns = [
|
|
15
|
+
Table::Column.new(label: "Name", sticky: true),
|
|
16
|
+
Table::Column.new(label: "Lines", align: :right),
|
|
17
|
+
Table::Column.new(label: "LOC", align: :right),
|
|
18
|
+
Table::Column.new(label: "Classes", align: :right),
|
|
19
|
+
Table::Column.new(label: "Methods", align: :right),
|
|
20
|
+
Table::Column.new(label: "M/C", align: :right),
|
|
21
|
+
Table::Column.new(label: "LOC/M", align: :right)
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
rows = []
|
|
25
|
+
total_lines = 0
|
|
26
|
+
total_loc = 0
|
|
27
|
+
total_classes = 0
|
|
28
|
+
total_methods = 0
|
|
29
|
+
code_loc = 0
|
|
30
|
+
test_loc = 0
|
|
31
|
+
|
|
32
|
+
@pairs.each do |pair|
|
|
33
|
+
name, stats = pair
|
|
34
|
+
lines = stats.lines
|
|
35
|
+
loc = stats.code_lines
|
|
36
|
+
classes = stats.classes
|
|
37
|
+
methods = stats.methods
|
|
38
|
+
mc = (classes > 0) ? (methods.to_f / classes).round(0).to_i : 0
|
|
39
|
+
locm = (methods > 0) ? ((loc.to_f / methods) - 2).round(0).to_i : 0
|
|
40
|
+
|
|
41
|
+
total_lines += lines
|
|
42
|
+
total_loc += loc
|
|
43
|
+
total_classes += classes
|
|
44
|
+
total_methods += methods
|
|
45
|
+
|
|
46
|
+
if stats.test_file?
|
|
47
|
+
test_loc += loc
|
|
48
|
+
else
|
|
49
|
+
code_loc += loc
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
rows << [
|
|
53
|
+
name,
|
|
54
|
+
lines.to_s,
|
|
55
|
+
loc.to_s,
|
|
56
|
+
classes.to_s,
|
|
57
|
+
methods.to_s,
|
|
58
|
+
mc.to_s,
|
|
59
|
+
locm.to_s
|
|
60
|
+
]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Total row
|
|
64
|
+
total_mc = (total_classes > 0) ? (total_methods.to_f / total_classes).round(0).to_i : 0
|
|
65
|
+
total_locm = (total_methods > 0) ? ((total_loc.to_f / total_methods) - 2).round(0).to_i : 0
|
|
66
|
+
|
|
67
|
+
rows << [
|
|
68
|
+
formatter.bold("Total"),
|
|
69
|
+
formatter.bold(total_lines.to_s),
|
|
70
|
+
formatter.bold(total_loc.to_s),
|
|
71
|
+
formatter.bold(total_classes.to_s),
|
|
72
|
+
formatter.bold(total_methods.to_s),
|
|
73
|
+
formatter.bold(total_mc.to_s),
|
|
74
|
+
formatter.bold(total_locm.to_s)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
renderer = Table::Renderer.new(
|
|
78
|
+
columns: columns,
|
|
79
|
+
theme: Table::Themes::WALLS,
|
|
80
|
+
compact: Railbow::Params.compact_options,
|
|
81
|
+
aliases: Railbow::Config.table_aliases
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
output = +"\n"
|
|
85
|
+
output << renderer.render(rows)
|
|
86
|
+
output << "\n"
|
|
87
|
+
|
|
88
|
+
# Code/test ratio
|
|
89
|
+
if code_loc > 0 && test_loc > 0
|
|
90
|
+
ratio = (test_loc.to_f / code_loc).round(1)
|
|
91
|
+
output << "\n #{formatter.bold("Code LOC:")} #{code_loc} " \
|
|
92
|
+
"#{formatter.bold("Test LOC:")} #{test_loc} " \
|
|
93
|
+
"#{formatter.bold("Code to Test Ratio:")} 1:#{ratio}\n"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
output
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Railbow
|
|
4
|
+
module Table
|
|
5
|
+
class Column
|
|
6
|
+
attr_reader :label, :width, :min_width, :max_width, :align, :truncate, :truncate_fn, :sticky
|
|
7
|
+
|
|
8
|
+
def initialize(label:, width: :auto, min_width: nil, max_width: nil, align: :left, truncate: false, truncate_fn: nil, sticky: false)
|
|
9
|
+
@label = label
|
|
10
|
+
@width = width
|
|
11
|
+
@min_width = min_width
|
|
12
|
+
@max_width = max_width
|
|
13
|
+
@align = align
|
|
14
|
+
@truncate = truncate
|
|
15
|
+
@truncate_fn = truncate_fn
|
|
16
|
+
@sticky = sticky
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fixed?
|
|
20
|
+
width.is_a?(Integer)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|