Skip to content

Commit 1b428c0

Browse files
authored
Add --grep option to marten routes (#378)
1 parent 78897e5 commit 1b428c0

3 files changed

Lines changed: 137 additions & 10 deletions

File tree

docs/docs/development/reference/management-commands.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ marten resetmigrations foo # Resets the migrations of the "foo" application
235235

236236
Displays all the routes of the application.
237237

238+
### Options
239+
240+
* `-g PATTERN, --grep=PATTERN` - Only display routes whose path or name contains the given substring (case-insensitive)
241+
238242
## `seed`
239243

240244
**Usage:** `marten seed [options]`

spec/marten/cli/manage/command/routes_spec.cr

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,89 @@ describe Marten::CLI::Manage::Command::Routes do
7373
output.includes?("/<locale>/nested-1-localized/nested-2/dummy/<id:int>").should be_true
7474
end
7575
end
76+
77+
describe "#handle" do
78+
it "only displays routes whose path matches the --grep option" do
79+
stdout = IO::Memory.new
80+
stderr = IO::Memory.new
81+
82+
command = Marten::CLI::Manage::Command::Routes.new(
83+
options: ["--grep=nested-2"],
84+
stdout: stdout,
85+
stderr: stderr
86+
)
87+
88+
command.handle
89+
90+
output = stdout.rewind.gets_to_end
91+
output.includes?("/nested-1/nested-2/dummy/<id:int>").should be_true
92+
output.includes?("/dummy/<id:int>/and/<scope:slug>").should be_false
93+
end
94+
95+
it "supports the -g short option" do
96+
stdout = IO::Memory.new
97+
stderr = IO::Memory.new
98+
99+
command = Marten::CLI::Manage::Command::Routes.new(
100+
options: ["-g", "nested-2"],
101+
stdout: stdout,
102+
stderr: stderr
103+
)
104+
105+
command.handle
106+
107+
output = stdout.rewind.gets_to_end
108+
output.includes?("/nested-1/nested-2/dummy/<id:int>").should be_true
109+
output.includes?("/dummy/<id:int>/and/<scope:slug>").should be_false
110+
end
111+
112+
it "matches the grep option in a case-insensitive way" do
113+
stdout = IO::Memory.new
114+
stderr = IO::Memory.new
115+
116+
command = Marten::CLI::Manage::Command::Routes.new(
117+
options: ["--grep=NESTED-2"],
118+
stdout: stdout,
119+
stderr: stderr
120+
)
121+
122+
command.handle
123+
124+
output = stdout.rewind.gets_to_end
125+
output.includes?("/nested-1/nested-2/dummy/<id:int>").should be_true
126+
end
127+
128+
it "also matches the grep option against the route name" do
129+
stdout = IO::Memory.new
130+
stderr = IO::Memory.new
131+
132+
command = Marten::CLI::Manage::Command::Routes.new(
133+
options: ["--grep=dummy_with_id_and_scope"],
134+
stdout: stdout,
135+
stderr: stderr
136+
)
137+
138+
command.handle
139+
140+
output = stdout.rewind.gets_to_end
141+
output.includes?("[dummy_with_id_and_scope]").should be_true
142+
output.includes?("/nested-1/dummy/<id:int>").should be_false
143+
end
144+
145+
it "prints a notice when no route matches the grep option" do
146+
stdout = IO::Memory.new
147+
stderr = IO::Memory.new
148+
149+
command = Marten::CLI::Manage::Command::Routes.new(
150+
options: ["--grep=totally-missing-route"],
151+
stdout: stdout,
152+
stderr: stderr
153+
)
154+
155+
command.handle
156+
157+
output = stdout.rewind.gets_to_end
158+
output.includes?(%{No routes found matching "totally-missing-route".}).should be_true
159+
end
160+
end
76161
end

src/marten/cli/manage/command/routes.cr

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,69 @@ module Marten
55
class Routes < Base
66
help "Display all the routes of the application."
77

8+
private getter! filter : String
9+
10+
def setup
11+
on_option_with_arg(
12+
:g,
13+
:grep,
14+
:pattern,
15+
"Only display routes whose path or name contains the given substring (case-insensitive)"
16+
) do |v|
17+
@filter = v
18+
end
19+
end
20+
821
def run
9-
process_routes_map(Marten.routes)
22+
printed = process_routes_map(Marten.routes)
23+
24+
if filter? && printed.zero?
25+
print("No routes found matching #{filter.inspect}.")
26+
end
1027
end
1128

12-
private def process_routes_map(map, parent_path = "", parent_name = nil)
29+
# Walks the routes map (recursing into nested and localized maps) and prints every matching route.
30+
# Returns the number of routes that were actually printed.
31+
private def process_routes_map(map, parent_path = "", parent_name = nil) : Int32
32+
printed = 0
33+
1334
map.rules.each do |rule|
1435
case rule
1536
when Marten::Routing::Rule::Path
16-
print_path(rule, parent_path, parent_name)
37+
printed += 1 if print_path(rule, parent_path, parent_name)
1738
when Marten::Routing::Rule::Map
1839
rule_name = parent_name ? "#{parent_name}:#{rule.name}" : rule.name
1940
rule_path = parent_path + resolve_path(rule.path)
20-
process_routes_map(rule.map, parent_path: rule_path, parent_name: rule_name)
41+
printed += process_routes_map(rule.map, parent_path: rule_path, parent_name: rule_name)
2142
when Marten::Routing::Rule::Localized
2243
rule.rules.each do |localized_rule|
2344
case localized_rule
2445
when Marten::Routing::Rule::Path
25-
print_path(localized_rule, "/<locale>", nil)
46+
printed += 1 if print_path(localized_rule, "/<locale>", nil)
2647
when Marten::Routing::Rule::Map
2748
rule_name = parent_name ? "#{parent_name}:#{localized_rule.name}" : localized_rule.name
2849
rule_path = "/<locale>" + parent_path + resolve_path(localized_rule.path)
29-
process_routes_map(localized_rule.map, parent_path: rule_path, parent_name: rule_name)
50+
printed += process_routes_map(localized_rule.map, parent_path: rule_path, parent_name: rule_name)
3051
end
3152
end
3253
end
3354
end
55+
56+
printed
3457
end
3558

36-
private def print_path(rule, parent_path, parent_name)
37-
parts = [] of String
59+
# Prints the given route unless it is filtered out. Returns `true` when the route was printed.
60+
private def print_path(rule, parent_path, parent_name) : Bool
3861
empty_parent_name = parent_name.nil? || parent_name.empty?
3962

40-
parts << style(parent_path + resolve_path(rule.path), fore: :light_blue)
41-
parts << style("[#{empty_parent_name ? rule.name : "#{parent_name}:#{rule.name}"}]", fore: :light_yellow)
63+
full_path = parent_path + resolve_path(rule.path)
64+
full_name = empty_parent_name ? rule.name : "#{parent_name}:#{rule.name}"
65+
66+
return false unless matches_filter?(full_path, full_name)
67+
68+
parts = [] of String
69+
parts << style(full_path, fore: :light_blue)
70+
parts << style("[#{full_name}]", fore: :light_yellow)
4271
parts << ""
4372
parts << style(rule.handler.name, fore: :light_green)
4473

@@ -47,6 +76,15 @@ module Marten
4776
end
4877

4978
print(parts.join(" "))
79+
80+
true
81+
end
82+
83+
private def matches_filter?(path : String, name : String) : Bool
84+
return true unless filter?
85+
86+
pattern = filter.downcase
87+
path.downcase.includes?(pattern) || name.downcase.includes?(pattern)
5088
end
5189

5290
private def resolve_path(path) : String

0 commit comments

Comments
 (0)