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

124 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Parse Chatwoot routes.rb and generate full route list comparable to GoChat dump_routes output."""
import re
import sys
def main():
with open('/home/yanghao05/Workspace/chatwoot-reference/config/routes.rb', 'r') as f:
content = f.read()
lines = content.split('\n')
namespace_stack = [] # (name, indent_level)
routes = []
def current_prefix():
parts = [ns for ns, _ in namespace_stack]
if parts:
return '/' + '/'.join(parts)
return ''
# Method mapping for Rails actions
method_map = {
'index': 'GET', 'show': 'GET', 'create': 'POST',
'update': 'PATCH', 'destroy': 'DELETE', 'new': 'GET', 'edit': 'GET'
}
# Parse each line
i = 0
while i < len(lines):
line = lines[i]
stripped = line.strip()
indent = len(line) - len(line.lstrip())
# Namespace
ns_match = re.match(r'namespace\s+:(\w+)', stripped)
if ns_match:
ns = ns_match.group(1)
namespace_stack.append((ns, indent))
i += 1
continue
# scope module:
scope_match = re.match(r'scope\s+module:\s+:(\w+)', stripped)
if scope_match:
mod = scope_match.group(1)
namespace_stack.append(('__scope_' + mod, indent))
i += 1
continue
# End block
if stripped == 'end':
if namespace_stack and namespace_stack[-1][1] >= indent:
namespace_stack.pop()
i += 1
continue
# resources / resource
res_match = re.match(r'(resources|resource)\s+:([a-zA-Z_]\w*)', stripped)
if res_match:
kind = res_match.group(1)
name = res_match.group(2)
is_singular = (kind == 'resource')
# Extract only constraint
only_match = re.search(r'only:\s*\[([^\]]+)\]', stripped)
if only_match:
actions = [a.strip().strip(':').strip("'").strip('"') for a in only_match.group(1).split(',')]
else:
if is_singular:
actions = ['show', 'create', 'update', 'destroy']
else:
actions = ['index', 'show', 'create', 'update', 'destroy', 'new', 'edit']
# Extract except constraint
except_match = re.search(r'except:\s*\[([^\]]+)\]', stripped)
if except_match:
excluded = [a.strip().strip(':').strip("'").strip('"') for a in except_match.group(1).split(',')]
actions = [a for a in actions if a not in excluded]
prefix = current_prefix()
for action in actions:
method = method_map.get(action, 'GET')
if is_singular:
path = prefix + '/' + name
elif action in ['index', 'create', 'new']:
path = prefix + '/' + name
else:
path = prefix + '/' + name + '/:id'
routes.append((method, path, action))
i += 1
continue
# member do block - look for routes inside
# collection do block
# Direct route definitions
direct_match = re.match(r"(get|post|put|patch|delete)\s+'([^']+)'", stripped)
if direct_match:
method = direct_match.group(1).upper()
path = direct_match.group(2)
routes.append((method, current_prefix() + path, ''))
i += 1
continue
# member action: post :action, on: :member
member_action = re.match(r"(get|post|put|patch|delete)\s+:(\w+)", stripped)
if member_action and not res_match:
method = member_action.group(1).upper()
action = member_action.group(2)
routes.append((method, current_prefix() + '/:action_' + action, action))
i += 1
continue
i += 1
# Deduplicate
unique_routes = sorted(set(routes))
print(f"Total unique routes: {len(unique_routes)}")
for method, path, action in unique_routes:
print(f"{method} {path}")
if __name__ == '__main__':
main()