149 lines
5.4 KiB
Ruby
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)}") |