Files
gochat/scripts/parse_chatwoot_routes.rb
T
2026-06-04 15:44:48 +08:00

149 lines
5.4 KiB
Ruby

import re, sys
# Parse Chatwoot routes.rb to extract all API routes
# Strategy: walk through the file tracking namespace nesting depth
with open('/home/yanghao05/Workspace/chatwoot-reference/config/routes.rb') as f:
text = f.read()
# We need to track namespace nesting and extract route definitions
# Rails routes.rb uses nested blocks like:
# namespace :api do
# namespace :v1 do
# resources :accounts do
# resources :conversations do
# member { post :assign }
# end
# end
# end
# end
# Parse the file line by line, tracking nesting
routes = []
namespace_stack = []
resource_stack = [] # Each entry: (resource_name, is_singular, id_param_name)
current_scope = [] # Track scope/member/collection blocks
in_member = False
in_collection = False
skip_frontend = True # Skip frontend/dashboard routes
lines = text.split('\n')
i = 0
while i < len(lines):
line = lines[i].strip()
# Skip comments
if line.startswith('#'):
i += 1
continue
# Skip frontend routes (dashboard, app routes)
if skip_frontend and ('dashboard' in line or 'app' in line and '/app' in line):
# Need to skip entire frontend block
if 'root' in line or '/app' in line or 'dashboard#index' in line:
# Skip until we hit the next significant keyword
i += 1
continue
# Detect namespace declarations
ns_match = re.match(r'namespace\s+:(\w+)\s+do', line)
if ns_match:
namespace_stack.append(ns_match.group(1))
i += 1
continue
# Detect scope declarations
scope_match = re.match(r'scope\s+[\'":](\w+)[\'":]\s+do', line)
if scope_match:
namespace_stack.append(scope_match.group(1))
i += 1
continue
# Detect resources declarations
res_match = re.match(r'resources\s+:(\w+)(?:\s*,\s*(.+))?\s+do', line)
if res_match:
resource_name = res_match.group(1)
options = res_match.group(2) or ''
# Check if only certain actions
only_match = re.search(r'only:\s*\[([^\]]+)\]', options)
resource_stack.append(resource_name)
i += 1
continue
# Also handle resources without block (one-liner)
res_simple = re.match(r'resources\s+:(\w+)(?:\s*,\s*(.+))?(?!\s+do)', line)
if res_simple and not res_match:
resource_name = res_simple.group(1)
options = res_simple.group(2) or ''
# Generate standard CRUD routes for this resource
only_match = re.search(r'only:\s*\[([^\]]+)\]', options)
except_match = re.search(r'except:\s*\[([^\]]+)\]', options)
allowed_actions = ['index', 'show', 'create', 'update', 'destroy', 'new', 'edit']
if only_match:
allowed_actions = [a.strip() for a in only_match.group(1).split(',')]
elif except_match:
excluded = [a.strip() for a in except_match.group(1).split(',')]
allowed_actions = [a for a in allowed_actions if a not in excluded]
# Build path prefix from namespace stack + resource stack
path_prefix = '/'.join(namespace_stack + resource_stack)
id_param = f':{resource_name}_id' if resource_name != resource_stack[-1] if resource_stack else f':id'
# Map actions to routes
action_routes = {
'index': ('GET', f'/api/{path_prefix}/{resource_name}'),
'show': ('GET', f'/api/{path_prefix}/{resource_name}/:{resource_name[:-1] if resource_name.endswith("s") else resource_name}_id' if resource_name.endswith('s') else f'/api/{path_prefix}/{resource_name}/:id'),
'create': ('POST', f'/api/{path_prefix}/{resource_name}'),
'update': ('PUT', f'/api/{path_prefix}/{resource_name}/:{resource_name[:-1] if resource_name.endswith("s") else resource_name}_id' if resource_name.endswith('s') else f'/api/{path_prefix}/{resource_name}/:id'),
'destroy': ('DELETE', f'/api/{path_prefix}/{resource_name}/:{resource_name[:-1] if resource_name.endswith("s") else resource_name}_id' if resource_name.endswith('s') else f'/api/{path_prefix}/{resource_name}/:id'),
}
# Simplify: just record the resource with its nesting
routes.append(f'CRUD:{"/".join(namespace_stack + resource_stack)}/{resource_name}')
i += 1
continue
# Detect member block
if 'member' in line and 'do' in line:
in_member = True
i += 1
continue
# Detect collection block
if 'collection' in line and 'do' in line:
in_collection = True
i += 1
continue
# Detect explicit route definitions (get/post/put/patch/delete)
route_match = re.match(r'(get|post|put|patch|delete)\s+[\'":]([^\'":\s]+)[\'":]', line)
if route_match:
method = route_match.group(1).upper()
path = route_match.group(2)
routes.append(f'{method}:{"/".join(namespace_stack)}/{path}')
i += 1
continue
# Detect end blocks (pop from stacks)
if line == 'end':
if in_member:
in_member = False
elif in_collection:
in_collection = False
elif resource_stack:
resource_stack.pop()
elif namespace_stack:
namespace_stack.pop()
i += 1
continue
i += 1
# Print what we found
print("=== Parsed Chatwoot routes.rb ===")
for r in routes:
print(r)
print(f"\nTotal route definitions found: {len(routes)}")