Skip to content

Commit 90b1f44

Browse files
committed
state viz CI
1 parent 56a8244 commit 90b1f44

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

.github/scripts/visualizeStates.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import sys
2+
import re
3+
import networkx as nx
4+
from pathlib import Path
5+
import matplotlib.pyplot as plt
6+
7+
def get_states(manager_states_path):
8+
file = Path(manager_states_path).read_text(encoding='utf8')
9+
states = re.findall(r'\s*(?:\w+)\s*\((?:[\s\S]*?)\)[,;]', file, re.MULTILINE | re.IGNORECASE)
10+
return states
11+
12+
def parse_states(states):
13+
parsed_states = []
14+
for state in states:
15+
state_name_match = re.match(r'^\s*(\w+)\s*\(', state, re.IGNORECASE)
16+
sub_states = re.findall(r'\b(?:\w+\.)+\w+\b', state)
17+
parsed_states.append({
18+
'stateName': state_name_match[1] if state_name_match else None,
19+
'subStates': sub_states
20+
})
21+
return parsed_states
22+
23+
def get_triggers(manager_path):
24+
file = Path(manager_path).read_text(encoding='utf8')
25+
triggers = re.findall(r'addTrigger\(.+?\)', file, re.MULTILINE | re.IGNORECASE)
26+
return triggers
27+
28+
def parse_triggers(triggers):
29+
parsed_triggers = []
30+
for trigger in triggers:
31+
matched = re.search(r'\s*(\w+)\s*,\s*(\w+)\s*,\s*((?:[\w:]+)|(?:\(\)\s*->\s*[\w.]+\(\)))\s*\)', trigger)
32+
if matched:
33+
parsed_triggers.append({
34+
'from': matched[1],
35+
'to': matched[2],
36+
'condition': matched[3]
37+
})
38+
return parsed_triggers
39+
40+
def create_state_map(states, triggers):
41+
state_map = {state['stateName']: state for state in states}
42+
43+
for trigger in triggers:
44+
state = state_map.get(trigger['from'])
45+
if state is not None:
46+
if 'connectionsTo' not in state:
47+
state['connectionsTo'] = []
48+
state['connectionsTo'].append({
49+
'to': trigger['to'],
50+
'condition': trigger['condition']
51+
})
52+
return state_map
53+
54+
def generate_graph(state_map):
55+
graph = nx.DiGraph()
56+
for (state_name, state_info) in state_map.items():
57+
graph.add_node(state_name, label=state_name)
58+
for connection in state_info.get('connectionsTo', []):
59+
graph.add_edge(state_name, connection['to'], weight=1)
60+
61+
62+
pos = nx.shell_layout(graph)
63+
pos["IDLE"] = (0, 0)
64+
65+
fixed_nodes = ["IDLE"]
66+
pos = nx.spring_layout(graph, pos=pos, fixed=fixed_nodes, k=0.2)
67+
68+
edge_labels = nx.get_edge_attributes(graph, 'label')
69+
70+
nx.draw(graph, pos, with_labels=True, node_color='lightblue', node_size=1000, font_size=6, arrows=True)
71+
nx.draw_networkx_edge_labels(graph, pos, edge_labels=edge_labels, font_size=6)
72+
73+
plt.title("State Machine Visualization")
74+
plt.tight_layout()
75+
plt.savefig("state_machine.png", format='png', dpi=300)
76+
77+
78+
def main(managerStatesPath, managerPath):
79+
states = get_states(managerStatesPath)
80+
states = parse_states(states)
81+
82+
triggers = get_triggers(managerPath)
83+
triggers = parse_triggers(triggers)
84+
85+
state_map = create_state_map(states, triggers)
86+
87+
generate_graph(state_map)
88+
89+
print("done")
90+
91+
if __name__ == '__main__':
92+
main(sys.argv[1], sys.argv[2])
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Compare Vendor Dependencies
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
generate-graph:
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- name: Checkout repo
12+
uses: actions/checkout@v3
13+
14+
- name: Set up Python
15+
uses: actions/setup-python@v4
16+
with:
17+
python-version: '3.x'
18+
19+
- name: Install dependencies
20+
run: |
21+
pip install networkx matplotlib
22+
23+
- name: Run graph generation
24+
run: |
25+
python3 "${{ github.workspace }}/.github/scripts/visualizeStates.py" ./src/main/java/frc/robot/Manager/ManagerStates.java ./src/main/java/frc/robot/Manager/Manager.java
26+
27+
- name: Upload graph as artifact
28+
uses: actions/upload-artifact@v3
29+
with:
30+
name: state-machine-graph
31+
path: state_machine.png
32+
33+
- name: Comment on PR
34+
uses: actions/github-script@v6
35+
with:
36+
script: |
37+
const prNumber = context.payload.pull_request.number;
38+
const commentBody = `✅ State machine graph generated and uploaded as an artifact.\n\nYou can download it from the [Actions tab](https://github.com/${context.repo.owner}/${context.repo.repo}/actions).`;
39+
40+
github.rest.issues.createComment({
41+
issue_number: prNumber,
42+
owner: context.repo.owner,
43+
repo: context.repo.repo,
44+
body: commentBody
45+
})

0 commit comments

Comments
 (0)