Github Projects (#1872)

* github projects

* add project prefixes
This commit is contained in:
Pheenoh
2023-08-09 16:27:37 -06:00
committed by GitHub
parent bb546a286f
commit 244892aeb8
22 changed files with 15422 additions and 64 deletions
+26
View File
@@ -0,0 +1,26 @@
name: Assign issue to commenter
on:
issue_comment:
types: [created]
jobs:
assign_to_commenter:
permissions:
issues: write
runs-on: ubuntu-latest
steps:
- name: Assign issue to commenter
uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const issueComment = context.payload.comment;
const issue = context.payload.issue;
if (issueComment.body === "/assign") {
github.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
assignees: [issueComment.user.login]
});
}
+30
View File
@@ -32,3 +32,33 @@ jobs:
run: |
./tp progress -f JSON > progress-${{ github.run_id }}.json
./tp upload-progress progress-${{ github.run_id }}.json -b https://progress.deco.mp/ -p twilightprincess -v gcn_usa
- name: Get changed .c, .cpp, .inc files
if: github.event_name != 'pull_request'
id: changed-files-specific
uses: tj-actions/changed-files@v36
with:
files: |
**/*.{c,cpp,inc}
- name: Update Status
if: github.event_name != 'pull_request' && steps.changed-files-specific.outputs.any_changed == 'true'
run: |
# Install libclang-16-dev for FunctionChecker
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-16 main"
sudo apt update
sudo apt install -y libclang-16-dev
FILENAMES="${{ steps.changed-files-specific.outputs.all_changed_files }}"
CMD="./tp github-check-update-status --personal-access-token ${{ secrets.PAT_TOKEN }} --debug"
# If .inc files are found, assume it's for d_a_alink.cpp for now.
# We can come up something better here later if needed.
if echo "$FILENAMES" | grep -q ".inc"; then
CMD="$CMD --filename src/d/a/d_a_alink.cpp"
else
for FILE in $FILENAMES; do
CMD="$CMD --filename $FILE"
done
fi
$CMD
+76 -54
View File
@@ -1,3 +1,9 @@
/**
* @file d_a_e_yk.h
* This header contains information about the Shadow Keese actor.
*
*/
#ifndef D_A_E_YK_H
#define D_A_E_YK_H
@@ -8,20 +14,28 @@
#include "d/cc/d_cc_uty.h"
#include "d/d_path.h"
/**
* \enum daE_YK_Action
* \brief Shadow Keese current action.
*
*/
enum daE_YK_Action {
ACT_ROOF, // keese is on roof
ACT_FIGHT_FLY, // keese is transitioning from flying to aggro
ACT_FIGHT, // keese is aggroed
ACT_ATTACK, // keese is attacking
ACT_RETURN, // keese is returning to roof
ACT_FLY, // keese is flying around
ACT_PATH_FLY, // Unconfirmed: keese is flying along its path?
ACT_CHANCE = 10, // keese is stunned/fell down
ACT_WOLFBITE = 13, // keese is being bitten by wolf
ACT_WIND // keese is in the gale boomerang
ACT_ROOF, /**< Keese is on roof. */
ACT_FIGHT_FLY, /**< Keese is transitioning from flying to aggro. */
ACT_FIGHT, /**< Keese is aggroed. */
ACT_ATTACK, /**< Keese is attacking. */
ACT_RETURN, /**< Keese is returning to roof. */
ACT_FLY, /**< Keese is flying around. */
ACT_PATH_FLY, /**< Unconfirmed: Keese is flying along its path. */
ACT_CHANCE = 10, /**< Keese is stunned/fell down. */
ACT_WOLFBITE = 13, /**< Keese is being bitten by wolf. */
ACT_WIND /**< Keese is in the gale boomerang. */
};
// tmp struct to get HIO to match
/**
* \brief Temporary HIO struct to get a match. Remove later.
*
*/
struct daE_YK_HIO_c_tmp {
void* vtable;
/* 0x04 */ s8 field_0x04;
@@ -32,6 +46,11 @@ struct daE_YK_HIO_c_tmp {
/* 0x18 */ f32 field_0x18;
};
/**
* \brief Shadow Keese Host Input Output class
*
*/
class daE_YK_HIO_c {
public:
/* 8080482C */ daE_YK_HIO_c();
@@ -46,52 +65,55 @@ public:
/* 0x18 */ f32 field_0x18;
};
/**
* \brief Shadow Keese actor class
*
*/
class e_yk_class : public fopEn_enemy_c {
public:
/* 0x5AC */ request_of_phase_process_class mPhase;
/* 0x5B4 */ u8 mParam1; // paramter - used to determine default action?
/* 0x5B5 */ u8 mPlayerTriggerBase; // parameter - base value used to determine mPlayerTrigger below
/* 0x5B6 */ u8 field_0x5b6;
/* 0x5B7 */ u8 mPathIdx; // parameter - used to lookup/set mpPath based on the room
/* 0x5B8 */ u8 field_0x5b8;
/* 0x5B9 */ s8 mPathPntIdx; // tracks the index of the points along the keese's path
/* 0x5BA */ s8 field_0x5ba;
/* 0x5BB */ u8 field_0x5bb;
/* 0x5BC */ dPath* mpPath; // flight path for keese to follow?
/* 0x5C0 */ mDoExt_McaMorfSO* mpMorfSO; // last res loaded
/* 0x5C4 */ Z2CreatureEnemy mCreature; // used for playing keese noises
/* 0x668 */ int mResIdx; // index number of last res loaded
/* 0x66C */ s16 field_0x66c;
/* 0x66E */ s16 mAction; // current action (see daE_YK_Action above)
/* 0x670 */ s16 mActionPhase; // current phase of current action
/* 0x674 */ cXyz mPathPntPos; // tracks the position of the point the keese is along a path
/* 0x680 */ s16 mAngleFromPlayer; // current keese angle from player
/* 0x684 */ f32 mDistanceXZFromPlayer; // current keese distance from player
/* 0x688 */ f32 mPlayerTrigger; // how close to the keese the player has to be before aggro
/* 0x68C */ f32 field_0x68c;
/* 0x690 */ f32 field_0x690;
/* 0x694 */ f32 field_0x694;
/* 0x698 */ s16 field_0x698;
/* 0x69A */ csXyz field_0x69a;
/* 0x6A0 */ s8 field_0x6a0;
/* 0x6A1 */ u8 field_0x6a1;
/* 0x6A2 */ s16 field_0x6a2[4];
/* 0x6AA */ s16 field_0x6aa;
/* 0x6AC */ s16 field_0x6ac[6];
/* 0x6B8 */ cXyz mBoomrangPosOffset; // offset position when keese caught in rang
/* 0x6C4 */ s16 mBoomrangXRotOffset; // offset x rotation when keese caught in rang
/* 0x6C8 */ dBgS_AcchCir field_0x6c8;
/* 0x708 */ dBgS_ObjAcch field_0x708;
/* 0x8E0 */ dCcD_Stts mCollisionStatus; // collision status
/* 0x91C */ dCcD_Sph mCollisionSphere; // collision sphere
/* 0xA54 */ dCcU_AtInfo mAtColliderInfo; // attack collider info (used when keese gets hit)
/* 0xA78 */ u32 field_0xa78;
/* 0xA7C */ u32 field_0xa7c;
/* 0xA80 */ u32 field_0xa80;
/* 0xA84 */ u32 mParticleEmitterIds[2];
/* 0xA8C */ u8 field_0xa8c;
/* 0x5AC */ request_of_phase_process_class mPhase; ///< @brief Actor phase process class.
/* 0x5B4 */ u8 mParam1; ///< @brief Actor parameter. Used to determine default action?
/* 0x5B5 */ u8 mPlayerTriggerBase; ///< @brief Actor parameter. Used to determine mPlayerTrigger value. @see mPlayerTrigeer
/* 0x5B6 */ u8 field_0x5b6; ///< @brief ???
/* 0x5B7 */ u8 mPathIdx; ///< @brief Path index. Used to lookup/set mpPath based on the room.
/* 0x5B8 */ u8 field_0x5b8; ///< @brief ???
/* 0x5B9 */ s8 mPathPntIdx; ///< @brief Path point index. Tracks the index of the points along the actor's path.
/* 0x5BA */ s8 field_0x5ba; ///< @brief ???
/* 0x5BB */ u8 field_0x5bb; ///< @brief ???
/* 0x5BC */ dPath* mpPath; ///< @brief Flight path. Flight path for the actor to follow?
/* 0x5C0 */ mDoExt_McaMorfSO* mpMorfSO; ///< @brief MorfSO pointer. Tracks the last resource loaded.
/* 0x5C4 */ Z2CreatureEnemy mCreature; ///< @brief Z2 Audio class. Used for playing actor noises.
/* 0x668 */ int mResIdx; ///< @brief Resource index. Tracks the index of the last resource loaded.
/* 0x66C */ s16 field_0x66c; ///< @brief ???
/* 0x66E */ s16 mAction; ///< @brief Current action. Tracks the current action of the actor. @see daE_YK_Action
/* 0x670 */ s16 mActionPhase; ///< @brief Current action phase. Tracks the phase of the current action of the actor. @see daE_YK_Action
/* 0x674 */ cXyz mPathPntPos; ///< @brief Path point position. Tracks the position of the point the actor is along a path.
/* 0x680 */ s16 mAngleFromPlayer; ///< @brief Angle from player. Tracks the current actor angle from player.
/* 0x684 */ f32 mDistanceXZFromPlayer; ///< @brief Distance from player. Tracks the current distance the actor is from the player.
/* 0x688 */ f32 mPlayerTrigger; ///< @brief Trigger distance from player. Tracks how close to the actor the player has to be before it will begin to attack the player.
/* 0x68C */ f32 field_0x68c; ///< @brief ???
/* 0x690 */ f32 field_0x690; ///< @brief ???
/* 0x694 */ f32 field_0x694; ///< @brief ???
/* 0x698 */ s16 field_0x698; ///< @brief ???
/* 0x69A */ csXyz field_0x69a; ///< @brief ???
/* 0x6A0 */ s8 field_0x6a0; ///< @brief ???
/* 0x6A1 */ u8 field_0x6a1; ///< @brief ???
/* 0x6A2 */ s16 field_0x6a2[4]; ///< @brief ???
/* 0x6AA */ s16 field_0x6aa; ///< @brief ???
/* 0x6AC */ s16 field_0x6ac[6]; ///< @brief ???
/* 0x6B8 */ cXyz mBoomrangPosOffset; ///< @brief Boomerang position offset. Tracks the offset position when the actor is caught in the gale boomerang.
/* 0x6C4 */ s16 mBoomrangXRotOffset; ///< @brief Boomerang rotation offset. Tracks the offset x rotation when the actor is caught in the gale boomerang.
/* 0x6C8 */ dBgS_AcchCir field_0x6c8; ///< @brief ???
/* 0x708 */ dBgS_ObjAcch field_0x708; ///< @brief ???
/* 0x8E0 */ dCcD_Stts mCollisionStatus; ///< @brief Collision status.
/* 0x91C */ dCcD_Sph mCollisionSphere; ///< @brief Collision sphere.
/* 0xA54 */ dCcU_AtInfo mAtColliderInfo; ///< @brief Attack collider info. Used when actor gets hit.
/* 0xA78 */ u32 field_0xa78; ///< @brief ???
/* 0xA7C */ u32 field_0xa7c; ///< @brief ???
/* 0xA80 */ u32 field_0xa80; ///< @brief ???
/* 0xA84 */ u32 mParticleEmitterIds[2]; ///< @brief ???
/* 0xA8C */ u8 field_0xa8c; ///< @brief ???
};
// size: 0xA90
#endif /* D_A_E_YK_H */
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* d_a_e_yk.cpp
* @file d_a_e_yk.cpp
* Enemy - Shadow Keese
*/
+127
View File
@@ -0,0 +1,127 @@
import clang.cindex
import re
import shutil
from logger import LOG
# Regular expression to match #include "asm/..."
asm_pattern = re.compile(r'#\s*include\s*"asm/.*"')
# Include directories passed to clang
include_dirs = ['include','include/dolphin','src']
class FunctionChecker:
def __init__(self, file_path):
self.file_path = file_path
self.backup_file_path = None
self.index = clang.cindex.Index.create()
self.include_options = []
self.not_attempted = []
self.new_not_attempted = []
self.attempted = []
self.completed = []
for dir in include_dirs:
self.include_options += ['-I', dir]
def replace_static_asm(self):
pattern = r'\bstatic\s+asm\b'
self.backup_file_path = self.file_path + '.bak'
# Make a backup copy of the file
shutil.copy2(self.file_path, self.backup_file_path)
with open(self.file_path, 'r') as file:
lines = file.readlines()
replaced_lines = [re.sub(pattern, 'asm', line) for line in lines]
with open(self.file_path, 'w') as file:
file.writelines(replaced_lines)
def restore_backup(self):
shutil.move(self.backup_file_path, self.file_path)
def check_function(self,line):
# Check if there are any characters between { and }
if re.search(r'\{.+\}', line, re.DOTALL):
# Check if those characters are not '/* empty function */' or whitespaces or 'return True;' or 'return False;'
if not re.search(r'\{\s*(/\*\s*empty function\s*\*/)?\s*\}', line) and not re.search(r'\{\s*return (True|False)\s*;\s*\}', line, re.IGNORECASE):
return True
return False
def return_status(self) -> str:
unattempted = len(self.new_not_attempted)
attempted = len(self.attempted)
completed = len(self.completed)
if attempted > 0 or (unattempted > 0 and completed > 0):
return "in progress"
if unattempted > 0:
return "todo"
if completed > 0 or (completed == 0 and attempted == 0 and unattempted == 0):
return "done"
return "error"
def find_includes(self,node, nonmatching: bool):
if str(node.location.file) == self.file_path or str(node.location.file).endswith(".inc"):
# Check if the node is a function
if node.kind in (clang.cindex.CursorKind.FUNCTION_DECL,
clang.cindex.CursorKind.CXX_METHOD,
clang.cindex.CursorKind.CONSTRUCTOR,
clang.cindex.CursorKind.DESTRUCTOR,
clang.cindex.CursorKind.CONVERSION_FUNCTION):
# Get the function body
body = [token.spelling for token in node.get_tokens()]
body_str = ' '.join(body)
check_body_str = body_str.strip()
# Skip if the function body doesn't exist or is empty
if self.check_function(check_body_str):
LOG.debug(f"Checking function: {node.mangled_name}")
if asm_pattern.search(body_str):
if nonmatching:
self.new_not_attempted.append(node.mangled_name)
else:
self.not_attempted.append(node.mangled_name)
else:
if nonmatching and node.mangled_name not in self.not_attempted:
self.completed.append(node.mangled_name)
# Recurse for children of this node
for c in node.get_children():
self.find_includes(c, nonmatching)
def run(input_filename: str) -> str:
checker = FunctionChecker(input_filename)
try:
# tiny hack to get all functions to show up correctly in the AST
checker.replace_static_asm()
# first categorize unattempted functions
tu = checker.index.parse(checker.file_path, checker.include_options)
checker.find_includes(tu.cursor, False)
# enable nonmatching functions
checker.include_options += ['-DNONMATCHING']
# then categorize attempted/completed functions
tu = checker.index.parse(input_filename, checker.include_options)
checker.find_includes(tu.cursor, True)
checker.attempted = set(checker.not_attempted) - set(checker.new_not_attempted)
LOG.debug(f"Not Attempted: {len(set(checker.new_not_attempted))}")
LOG.debug(f"Attempted: {len(set(checker.attempted))}")
LOG.debug(f"Complete: {len(set(checker.completed))}")
LOG.debug(f"Total: {len(set(checker.new_not_attempted)) + len(set(checker.attempted)) + len(set(checker.completed))}")
return checker.return_status()
except Exception as e:
print("Exception:", e)
return e
finally:
checker.restore_backup()
+4
View File
@@ -0,0 +1,4 @@
from .issue import *
from .project import *
from .label import *
from .repository import *
+85
View File
@@ -0,0 +1,85 @@
import sys
from .option import Option
from .graphql import GraphQLClient
from logger import LOG
class Field:
def __init__(self, id, name, options):
self.id = id
self.name = name
self.options = options
@staticmethod
def get_status_field(project_id: str) -> 'Field':
LOG.debug(f'Getting status field for project ID {project_id}')
query = '''
query ($projectId: ID!) {
node(id: $projectId) {
... on ProjectV2 {
fields(first: 100) {
nodes {
... on ProjectV2SingleSelectField {
name
id
}
}
}
}
}
}
'''
variables = {
"projectId": project_id
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
fields = data['data']['node']['fields']['nodes']
for field in fields:
if 'name' in field and field['name'] == 'Status':
field_id = field['id']
LOG.info(f'Status Field ID: {field_id}')
return Field(
id=field_id,
name='Status',
options=Option.get_all_options(field_id)
)
else:
LOG.critica(f'No field found with name "Status"!')
sys.exit(1)
def create_option(self, option_name: str):
LOG.debug(f'Creating option with name {option_name} for field {self.name}')
query = '''
mutation ($fieldId: ID!, $optionName: String!) {
createProjectOption(input: {projectId: $fieldId, name: $optionName}) {
projectOption {
id
name
}
}
}
'''
variables = {
"fieldId": self.id,
"optionName": option_name
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
option_id = data['data']['createProjectOption']['projectOption']['id']
LOG.info(f'Created option with name {option_name} and ID {option_id}')
return Option(option_id, option_name)
else:
LOG.warning(f'Could not create option with name {option_name}')
return None
# Finish later if we decide to add more fields other than the default Status field
def create(self, project_id: str):
pass
# Finish later if we decide to add more fields other than the default Status field
def delete(self):
pass
+60
View File
@@ -0,0 +1,60 @@
import requests
import sys
import time
from typing import Optional
from logger import LOG
class GraphQLClient:
instance = None
url: str = 'https://api.github.com/graphql'
headers: dict
personal_access_token: Optional[str] = None
@classmethod
def setup(cls, pat_token: Optional[str] = None):
if cls.instance is None:
cls.instance = GraphQLClient()
if pat_token is not None:
cls.personal_access_token = pat_token
cls.headers = {
'Authorization': f'Bearer {cls.personal_access_token}',
'Accept': 'application/vnd.github.v3+json',
'Accept': 'application/vnd.github.bane-preview+json',
}
else:
LOG.error('No personal access token provided. Please provide one with the --personal-access-token flag.')
sys.exit(1)
@classmethod
def get_instance(cls):
if cls.instance is None:
raise Exception("The singleton instance has not been set up. Call setup() first.")
return cls.instance
def __init__(self):
if self.instance is not None:
raise Exception("This class is a singleton!")
def make_request(self, query_or_mutation: str, variables: dict):
payload = {
'query': query_or_mutation,
'variables': variables
}
while True:
response = requests.post(self.url, headers=self.headers, json=payload)
data = response.json()
if 'errors' in data:
error_message = data['errors'][0]['message']
if 'was submitted too quickly' in error_message or 'API rate limit exceeded' in error_message or 'Something went wrong while executing your query' in error_message:
LOG.warning('Hit rate limit. Sleeping for 30 seconds...')
time.sleep(30)
continue
else:
LOG.error(f"Fail. Error: {error_message}")
return None
return data
+443
View File
@@ -0,0 +1,443 @@
import sys
from .label import *
from typing import Optional
@dataclass
class Issue:
yaml_data: Optional[dict] = None
def __eq__(self, other):
if isinstance(other, Issue):
return self.unique_id == other.unique_id
return False
def __hash__(self):
return hash(self.unique_id)
def __init__(self,id=None, title=None, body=None, label_ids=None, file_path=None, data=None):
if data is not None:
self.yaml_ctor(data)
else:
self.id = id
self.title = title
self.body = body
self.label_ids = label_ids
self.file_path = file_path
def yaml_ctor(self,data):
for tu, labels, file_path in get_translation_units(data):
self.id = None
self.title = tu
self.body = None
self.file_path = file_path
self.label_ids = []
def set_open(self):
pass
def set_closed(self):
LOG.debug(f'Closing issue {self.id}')
mutation = """
mutation CloseIssue($id: ID!) {
closeIssue(input: {issueId: $id}) {
clientMutationId
}
}
"""
variables = {
"id": self.id
}
data = GraphQLClient.get_instance().make_request(mutation, variables)
if data:
LOG.info(f'Closed issue {self.id}')
else:
LOG.error(f'Failed to close issue {self.id}')
sys.exit(1)
@staticmethod
def delete_all():
LOG.debug(f'Deleting all issues in {RepoInfo.owner.name}/{RepoInfo.name}')
issue_state = StateFile.data["issues"]
if issue_state is not None and len(issue_state) > 0:
for issue in issue_state.copy():
Issue(
file_path=issue["file_path"],
id=issue["id"],
title=issue["file_path"]
).delete()
else:
LOG.warning(f'No issues found in state file, nothing to delete.')
return
@staticmethod
def get_all_from_github() -> list['Issue']:
return Issue.get_open() + Issue.get_closed()
@staticmethod
def get_closed() -> list['Issue']:
return Issue.get_by_state("CLOSED")
@staticmethod
def get_open() -> list['Issue']:
return Issue.get_by_state("OPEN")
@staticmethod
def get_all_from_yaml(data):
ret_issues = []
labels_dict = {label['name']: label['id'] for label in StateFile.data["labels"]}
for d in data:
# Get tu, labels, filepath for current project
tu_info = get_translation_units(d)
# Add in project name as a label
for idx, (tu, labels, path) in enumerate(tu_info):
new_labels = labels + [d['project']['title']]
tu_info[idx] = (tu, new_labels, path)
for tu, labels, file_path in tu_info:
state_label_ids = []
# Fetch label ids from state file
for label in labels:
if label in labels_dict:
LOG.debug(f'Found label {label} for TU {tu} in state file, adding to issue.')
state_label_ids.append(labels_dict[label])
else:
LOG.error(f'Label {label} not found in state file, please run ./tp github-sync-labels first.')
sys.exit(1)
issue = Issue(
id=None, # set in check_and_create or create method
title=tu,
body=None,
label_ids=state_label_ids,
file_path=file_path
)
ret_issues.append(issue)
return ret_issues
@staticmethod
def get_by_unique_id(unique_id: str) -> 'Issue':
LOG.debug(f'Getting issue with unique ID {unique_id} on {RepoInfo.owner.name}/{RepoInfo.name}')
query = '''
query ($owner: String!, $repo: String!, $first: Int!, $after: String) {
repository(owner: $owner, name: $repo) {
issues(first: $first, after: $after) {
pageInfo {
endCursor
hasNextPage
}
nodes {
title
id
body
labels(first: 100) {
nodes {
id
name
}
}
}
}
}
}
'''
has_next_page = True
cursor = None
while has_next_page:
variables = {
"owner": RepoInfo.owner.name,
"repo": RepoInfo.name,
"first": 100,
"after": cursor
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
issues = data['data']['repository']['issues']['nodes']
for issue in issues:
labels = sorted([label['name'] for label in issue['labels']['nodes']])
issue_unique_id = issue['title'] + '-' + '-'.join(labels)
if issue_unique_id == unique_id:
return Issue(
id=issue['id'],
title=issue['title'],
body=issue['body'],
labels=[
Label(
id=label['id'],
name=label['name']
) for label in issue['labels']['nodes']
],
unique_id=unique_id
)
page_info = data['data']['repository']['issues']['pageInfo']
has_next_page = page_info['hasNextPage']
cursor = page_info['endCursor']
else:
LOG.warning(f'No issue found with unique ID {unique_id}')
return None
@staticmethod
def get_by_state(state: str) -> list['Issue']:
LOG.debug(f'Getting {state} issues on {RepoInfo.owner.name}/{RepoInfo.name}')
query = '''
query ($owner: String!, $repo: String!, $state: [IssueState!], $cursor: String) {
repository(owner: $owner, name: $repo) {
issues(first: 100, states: $state, after: $cursor) {
pageInfo {
endCursor
hasNextPage
}
nodes {
title
id
body
labels(first: 100) {
nodes {
id
name
}
}
}
}
}
}
'''
all_issues = []
has_next_page = True
cursor = None
while has_next_page:
variables = {
"owner": RepoInfo.owner.name,
"repo": RepoInfo.name,
"state": state,
"cursor": cursor
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
issues = data['data']['repository']['issues']['nodes']
for issue in issues:
insert_issue = Issue(
id=issue['id'],
title=issue['title'],
body=issue['body']
)
all_issues.append(insert_issue)
LOG.debug(f'{state} issues retrieved: {issues}')
page_info = data['data']['repository']['issues']['pageInfo']
has_next_page = page_info['hasNextPage']
cursor = page_info['endCursor']
LOG.debug(f'All {state} issues retrieved: {all_issues}')
return all_issues
@staticmethod
def get_labels_by_id(issue_id: str) -> list[Label]:
LOG.debug(f'Getting all labels for issue {issue_id} on {RepoInfo.owner.name}/{RepoInfo.name}')
query = '''
query ($id: ID!) {
node(id: $id) {
... on Issue {
labels(first: 100) {
nodes {
id
name
}
}
}
}
}
'''
variables = {
"id": issue_id
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
labels = data['data']['node']['labels']['nodes']
LOG.debug(f'Labels retrieved: {labels} for issue {issue_id}')
ret_labels = []
for label in labels:
label = Label(
id=label["id"],
name=label["name"]
)
ret_labels.append(label)
return ret_labels
else:
LOG.debug(f'No labels found for issue {issue_id}')
return []
def attach_labels(self) -> None:
LOG.debug(f'Attaching labels to issue {self.id} on {RepoInfo.owner.name}/{RepoInfo.name}')
def check_and_create(self) -> None:
issues = StateFile.data.get('issues')
if issues is None:
issue_dict = {}
else:
issue_dict = {issue['file_path']: issue for issue in issues}
if self.file_path in issue_dict:
LOG.info(f"Issue {self.title} from TU {self.file_path} already setup!")
self.id = issue_dict[self.file_path]["id"]
else:
LOG.info(f'Creating missing issue {self.title}.')
self.create()
# def check_and_attach_labels(self) -> None:
# LOG.debug(f'Checking labels for issue {self.title} on {RepoInfo.owner.name}/{RepoInfo.name}')
# issues = StateFile.data.get('issues')
# if issues is None:
# issue_dict = {}
# else:
# issue_dict = {issue['file_path']: issue for issue in issues}
# if self.file_path in issue_dict:
# state_labels = StateFile.data.get('labels')
# label_ids = issue_dict[self.file_path]["label_ids"]
# if label_ids is not None:
# state_label_ids = [label['id'] for label in state_labels]
# for label_id in label_ids:
# if label_id in state_label_ids:
# LOG.debug(f'Label {label_id} exists in state, continuing')
# continue
# else:
# LOG.error(f'Label {label_id} does not exist in state, please run sync-labels first')
# sys.exit(1)
# LOG.info(f'All labels already attached to issue {self.title} on {RepoInfo.owner.name}/{RepoInfo.name}')
# else:
# LOG.info(f'Attaching labels to issue {self.title} on {RepoInfo.owner.name}/{RepoInfo.name}')
# # use yaml data to fetch label names for this issue
# # lookup id from state and attach to issue
# <replace> =
# for label in <replace>:
# self.attach_label_by_id() # finish
# LOG.info(f'Labels attached to issue {self.title} on {RepoInfo.owner.name}/{RepoInfo.name}')
# print(label_ids)
# sys.exit(0)
# else:
# LOG.error(f"Issue {self.title} from TU {self.file_path} is missing")
# sys.exit(1)
def create(self):
repo_id = RepoInfo.id
mutation = """
mutation CreateIssue($input: CreateIssueInput!) {
createIssue(input: $input) {
issue {
id
title
}
}
}
"""
input_dict = {
"repositoryId": repo_id,
"title": self.title,
"body": self.body,
}
if self.label_ids is not None:
input_dict["labelIds"] = self.label_ids
variables = {
"input": input_dict
}
data = GraphQLClient.get_instance().make_request(mutation, variables)
if data:
self.id = data["data"]["createIssue"]["issue"]["id"]
self.title = data["data"]["createIssue"]["issue"]["title"]
self.write_state_to_file()
LOG.info(f'Created Issue {self.title} with ID {self.id}')
return self.id
def delete(self) -> None:
LOG.debug(f'Deleting issue {self.title} with ID {self.id}')
mutation = '''
mutation DeleteIssue($id: ID!) {
deleteIssue(input: {issueId: $id}) {
clientMutationId
}
}
'''
variables = {
"id": self.id
}
data = GraphQLClient.get_instance().make_request(mutation, variables)
if data:
self.write_state_to_file(delete=True)
LOG.info(f'Successfully deleted issue {self.title}.')
else:
LOG.error(f'Failed to delete issue {self.title}')
def write_state_to_file(self, delete: bool = False):
state = {
"id": self.id,
"title": self.title,
"body": self.body,
"label_ids": self.label_ids,
"file_path": self.file_path,
}
curr_state_issues = StateFile.data.get("issues", None)
if curr_state_issues is not None:
for i, issue in enumerate(curr_state_issues):
if issue['id'] == self.id:
if delete:
del StateFile.data['issues'][i]
break
else:
StateFile.data['issues'][i] = state
break
else:
StateFile.data['issues'].append((state))
else:
StateFile.data['issues'] = [state]
with open("tools/pjstate.yml", 'w') as f:
yaml.safe_dump(StateFile.data, f)
+229
View File
@@ -0,0 +1,229 @@
from .repository import RepoInfo
from .graphql import GraphQLClient
from .util import *
from .state import *
from logger import LOG
from dataclasses import dataclass
from typing import Optional
import yaml, sys
@dataclass
class Label:
@staticmethod
def get_all_from_yaml(data):
ret_labels = []
sub_labels = []
for d in data:
sub_labels = get_sub_labels(d)
for label in sub_labels:
ret_label = Label(name=label)
ret_labels.append(ret_label)
title_label = Label(data=d)
ret_labels.append(title_label)
return ret_labels
def __eq__(self, other):
if isinstance(other, Label):
return self.name == other.name and self.id == other.id
return False
def __hash__(self):
return hash((self.name, self.id))
def __init__(self,data=None,id=None,name=None):
if data is not None:
project_data = data.get('project', {})
self.yaml_ctor(project_data)
else:
self.id = id
self.name = name
def yaml_ctor(self,project_data):
self.id = None
self.name = project_data.get('title', 'MISSING_TITLE')
@staticmethod
def delete_all():
LOG.debug(f'Deleting all labels in {RepoInfo.owner.name}/{RepoInfo.name}')
label_state = StateFile.data["labels"]
if label_state is not None and len(StateFile.data) > 0:
for label in label_state.copy():
Label(
id=label["id"],
name=label["name"]
).delete()
else:
LOG.warning(f'No labels found in state file, nothing to delete.')
return
@staticmethod
def get_all_from_github() -> list['Label']:
LOG.debug(f'Fetch all current labels for {RepoInfo.owner.name}/{RepoInfo.name}')
query = '''
query ($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
labels(first: 100) {
nodes {
name
id
}
}
}
}
'''
variables = {
"owner": RepoInfo.owner.name,
"repo": RepoInfo.name
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
labels = data['data']['repository']['labels']['nodes']
LOG.debug(f'Labels retrieved: {labels}')
return [
Label(
id=label["id"],
name=label["name"]
) for label in labels
]
else:
LOG.warning(f'No labels found in {RepoInfo.owner.name}/{RepoInfo.name}')
return []
@staticmethod
def get_id_by_name(label_name: str):
LOG.debug(f'Fetch label ID for label {label_name} in {RepoInfo.owner.name}/{RepoInfo.name}')
query = '''
query ($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
labels(first: 100) {
nodes {
name
id
}
}
}
}
'''
variables = {
"owner": RepoInfo.owner.name,
"repo": RepoInfo.name
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
labels = data['data']['repository']['labels']['nodes']
for label in labels:
if label['name'] == label_name:
LOG.debug(f'Label ID for {label_name} retrieved: {label["id"]}')
return label['id']
LOG.warning(f'Label {label_name} not found in {RepoInfo.owner.name}/{RepoInfo.name}')
return None
def check_and_create(self) -> None:
labels = StateFile.data.get('labels')
if labels is None:
label_dict = {}
else:
label_dict = {label['name']: label for label in labels}
if self.name in label_dict:
LOG.info(f"Label {self.name} already setup!")
self.id = label_dict[self.name]["id"]
self.name = label_dict[self.name]["name"]
else:
LOG.debug(f'Creating missing label {self.name}.')
self.create()
def create(self) -> None:
LOG.debug(f'Creating issue label: {self.name}')
mutation = '''
mutation ($repoId: ID!, $labelName: String!, $color: String!) {
createLabel(input: { name: $labelName, repositoryId: $repoId, color: $color }) {
label {
name
id
}
}
}
'''
variables = {
"repoId": RepoInfo.id,
"labelName": self.name,
"color": generate_random_rgb_hex()
}
data = GraphQLClient.get_instance().make_request(mutation, variables)
if data:
LOG.debug(f'Create label data: {data}')
self.id = data['data']['createLabel']['label']['id']
self.write_state_to_file()
LOG.info(f"Successfully created label '{self.name} with ID {self.id}'!")
else:
LOG.error(f"Failed to create label {self.name}")
sys.exit(1)
def delete(self) -> None:
query = '''
mutation ($labelId: ID!) {
deleteLabel(input: {
id: $labelId
}) {
clientMutationId
}
}
'''
variables = {
"labelId": self.id
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
self.write_state_to_file(delete=True)
LOG.info(f'Successfully deleted label {self.name}')
else:
LOG.error(f'Failed to delete label {self.name}')
def write_state_to_file(self, delete: bool = False):
state = {
"id": self.id,
"name": self.name
}
curr_state_labels = StateFile.data.get('labels',None)
if curr_state_labels is not None:
for i, label in enumerate(StateFile.data['labels']):
if label['id'] == self.id:
if delete:
del StateFile.data['labels'][i]
break
else:
StateFile.data['labels'][i] = state
break
else:
StateFile.data['labels'].append((state))
else:
StateFile.data['labels'] = [state]
with open("tools/pjstate.yml", 'w') as f:
yaml.safe_dump(StateFile.data, f)
+78
View File
@@ -0,0 +1,78 @@
from .util import *
from .graphql import GraphQLClient
from dataclasses import dataclass
from logger import LOG
class Option:
def __init__(self, id, name):
self.id = id
self.name = name
@staticmethod
def get_all_options(field_id: str) -> list['Option']:
LOG.debug(f'Getting all options for field {field_id}')
query = '''
query ($fieldId: ID!) {
node(id: $fieldId) {
... on ProjectV2SingleSelectField {
options {
id
name
}
}
}
}
'''
variables = {
"fieldId": field_id
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
options = data['data']['node']['options']
LOG.info(f'Options: {options}')
ret_options = []
for option in options:
option_id = option['id']
option_name = option['name']
ret_options.append(Option(id = option_id, name = option_name))
return ret_options
else:
LOG.warning(f'No options found for field {field_id}')
return []
@staticmethod
def get_id(field_id: str, option_name: str) -> str:
LOG.debug(f'Getting option ID for field {field_id} with name {option_name}')
query = '''
query ($fieldId: ID!) {
node(id: $fieldId) {
... on ProjectV2SingleSelectField {
options {
id
name
}
}
}
}
'''
variables = {
"fieldId": field_id
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
options = data['data']['node']['options']
for option in options:
if option['name'] == option_name:
option_id = option['id']
LOG.info(f'{option_name} Option ID: {option_id}')
return option_id
LOG.warning(f'No option found with name {option_name}')
return None
def delete(self):
pass
File diff suppressed because it is too large Load Diff
+41
View File
@@ -0,0 +1,41 @@
import sys
from .graphql import GraphQLClient
from typing import ClassVar
from logger import LOG
class OwnerInfo:
id: ClassVar[str]
name: ClassVar[str]
class RepoInfo:
id: ClassVar[str]
name: ClassVar[str]
owner: ClassVar[OwnerInfo]
@classmethod
def set_ids(cls):
LOG.debug(f'Fetching repo ID for {cls.name}')
query = '''
query ($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
owner {
id
}
id
}
}
'''
variables = {
"owner": cls.owner.name,
"repo": cls.name
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
cls.id = data['data']['repository']['id']
cls.owner.id = data['data']['repository']['owner']['id']
else:
LOG.error(f"Failed to fetch repo ID! Make sure {cls.owner.name}/{cls.name} exists and isn't private.")
sys.exit(1)
+9
View File
@@ -0,0 +1,9 @@
import yaml, pathlib
class StateFile:
data = None
@classmethod
def load(self, file_name: pathlib.Path):
with open(file_name, 'r') as f:
self.data = yaml.safe_load(f)
+86
View File
@@ -0,0 +1,86 @@
import random, os
from typing import List, Tuple
from logger import LOG
def generate_random_rgb_hex() -> str:
return ''.join([random.choice('0123456789ABCDEF') for _ in range(6)])
def get_translation_units(data) -> list[Tuple[str, List[str], str]]:
dirs = [{'path': dir_data['path'], 'sub_labels': dir_data.get('sub_labels', [])} for dir_data in data.get('dirs', [])]
not_dirs = [{'path': dir_data['path'], 'sub_labels': dir_data.get('sub_labels', [])} for dir_data in data.get('notDirs', [])]
files = [{'path': file_data['path'], 'sub_labels': file_data.get('sub_labels', [])} for file_data in data.get('files', [])]
not_files = [{'path': file_data['path'], 'sub_labels': file_data.get('sub_labels', [])} for file_data in data.get('notFiles', [])]
ignore_files = [
"ctx.c",
"unknown_translation_unit.cpp",
"unknown_translation_unit_bss.cpp",
] + [file['path'] for file in not_files]
ignore_dirs = [
"build",
"tools",
"expected"
] + [directory['path'] for directory in not_dirs]
tus = []
LOG.debug('Adding include files directly to tu list')
for file in files:
if file['path'] not in ignore_files and file['path'].endswith((".c", ".cpp")):
tus.append((file['path'].split("/")[-1], file['sub_labels'], file['path'])) # Use sub_labels from file and include file path
LOG.debug(f'TU name: {file["path"]}')
directories_to_walk = dirs
LOG.debug('Adding files from include dirs directly to tu list')
for directory in directories_to_walk:
for root, _, files in os.walk(directory['path']):
if any(ignore_dir in root for ignore_dir in ignore_dirs):
continue
for file in files:
if file not in ignore_files and file.endswith((".c", ".cpp")):
full_file_path = os.path.join(root, file)
tus.append((file, directory['sub_labels'], full_file_path)) # Use sub_labels from directory and include file path
return tus
def get_sub_labels(data) -> list[str]:
dirs = [{'path': dir_data['path'], 'sub_labels': dir_data.get('sub_labels', [])} for dir_data in data.get('dirs', [])]
not_dirs = [{'path': dir_data['path'], 'sub_labels': dir_data.get('sub_labels', [])} for dir_data in data.get('notDirs', [])]
files = [{'path': file_data['path'], 'sub_labels': file_data.get('sub_labels', [])} for file_data in data.get('files', [])]
not_files = [{'path': file_data['path'], 'sub_labels': file_data.get('sub_labels', [])} for file_data in data.get('notFiles', [])]
ignore_files = [
"ctx.c",
"unknown_translation_unit.cpp",
"unknown_translation_unit_bss.cpp",
] + [file['path'] for file in not_files]
ignore_dirs = [
"build",
"tools",
"expected"
] + [directory['path'] for directory in not_dirs]
sub_labels = []
for file in files:
if file['path'] not in ignore_files and file['path'].endswith((".c", ".cpp")):
sub_labels.append(file['sub_labels'])
for directory in dirs:
for root, _, files in os.walk(directory['path']):
if any(ignore_dir in root for ignore_dir in ignore_dirs):
continue
for file in files:
if file not in ignore_files and file.endswith((".c", ".cpp")):
sub_labels.append(directory['sub_labels'])
sub_labels = [item for sublist in sub_labels for item in sublist]
# Convert the list of strings to a set
sub_labels = set(sub_labels)
return sub_labels
+51
View File
@@ -0,0 +1,51 @@
# This is mostly useless until Github extends their API to allow for view creation.
from .graphql import GraphQLClient
from dataclasses import dataclass
from logger import LOG
@dataclass
class View:
def set_layoutout(self, layout: str):
self.layout = layout
# Doesn't actually work (yet)
def check_and_create(self):
LOG.debug(f'Checking if view {self.name} exists')
query = '''
query ($projectNumber: String!) {
node(id: $projectNumber) {
... on ProjectV2 {
views {
nodes {
name
number
}
}
}
}
}
'''
variables = {
"projectNumber": self.number
}
data = GraphQLClient.get_instance().make_request(query, variables)
if data:
views = data['data']['node']['views']['nodes']
LOG.info(f'Views: {views}')
for view in views:
if view['name'] == self.name:
LOG.info(f'View {self.name} exists')
return
LOG.info(f'View {self.name} does not exist, creating')
self.create()
else:
LOG.warning(f'No views found for project {self.number}')
# Waiting on Github to update their API
def create(self):
pass
+21
View File
@@ -0,0 +1,21 @@
try:
import logging
import sys
from rich.logging import RichHandler
from rich.console import Console
except ImportError as e:
print(f"Failed to import module in {__file__}: {e}")
sys.exit(1)
CONSOLE = Console()
logging.basicConfig(
level="NOTSET",
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)],
)
LOG = logging.getLogger("rich")
LOG.setLevel(logging.INFO)
+13005
View File
File diff suppressed because it is too large Load Diff
+243
View File
@@ -0,0 +1,243 @@
# This file controls what projects, issues and labels will be created in Github
#
#
#
# title - Title of the project. Titles are also used as labels on issues
# views - Not supported yet (thanks github)
# dirs - Includes any TUs inside the listed dirs, recursively
# not_dirs - Excludes specific dirs from the dirs selection
# files - Includes any TUs manually specified
# not_files - Excludes any TUs manually specified
# sub_labels - Additional labels used for sub categorization on large projects
# so that views can be built later, and also so identically named TUs can
# be distinguished
- project:
title: tp_TRK_MINNOW_DOLPHIN
dirs:
- path: libs/TRK_MINNOW_DOLPHIN/GCN/EXI2_DDH_GCN
sub_labels: ["TRK_MINNOW_DOLPHIN/EXI2_DDH_GCN"]
- path: libs/TRK_MINNOW_DOLPHIN/GCN/EXI2_GDEV_GCN
sub_labels: ["TRK_MINNOW_DOLPHIN/EXI2_GDEV_GCN"]
- path: libs/TRK_MINNOW_DOLPHIN/MetroTRK
sub_labels: ["TRK_MINNOW_DOLPHIN/MetroTRK"]
- path: libs/TRK_MINNOW_DOLPHIN/ppc
sub_labels: ["TRK_MINNOW_DOLPHIN/ppc"]
- path: libs/TRK_MINNOW_DOLPHIN/Os
sub_labels: ["TRK_MINNOW_DOLPHIN/dolphin"]
- path: libs/TRK_MINNOW_DOLPHIN/utils
sub_labels: ["TRK_MINNOW_DOLPHIN/utils"]
- project:
title: tp_Actors
dirs:
- path: rel/d/a/d_a_alldie
- path: rel/d/a/d_a_andsw
- path: rel/d/a/d_a_andsw2
- path: rel/d/a/d_a_arrow
- path: rel/d/a/d_a_balloon_2D
- path: rel/d/a/d_a_bd
- path: rel/d/a/d_a_bg
- path: rel/d/a/d_a_bg_obj
- path: rel/d/a/d_a_boomerang
- path: rel/d/a/d_a_bullet
- path: rel/d/a/d_a_canoe
- path: rel/d/a/d_a_coach_2D
- path: rel/d/a/d_a_coach_fire
- path: rel/d/a/d_a_cow
- path: rel/d/a/d_a_crod
- path: rel/d/a/d_a_cstaF
- path: rel/d/a/d_a_cstatue
- path: rel/d/a/d_a_demo00
- path: rel/d/a/d_a_demo_item
- path: rel/d/a/d_a_disappear
- path: rel/d/a/d_a_dmidna
- path: rel/d/a/d_a_do
- path: rel/d/a/d_a_dshutter
- path: rel/d/a/d_a_econt
- path: rel/d/a/d_a_ep
- path: rel/d/a/d_a_formation_mng
- path: rel/d/a/d_a_fr
- path: rel/d/a/d_a_grass
- path: rel/d/a/d_a_guard_mng
- path: rel/d/a/d_a_hitobj
- path: rel/d/a/d_a_horse
- path: rel/d/a/d_a_hozelda
- path: rel/d/a/d_a_izumi_gate
- path: rel/d/a/d_a_kago
- path: rel/d/a/d_a_L7demo_dr
- path: rel/d/a/d_a_L7low_dr
- path: rel/d/a/d_a_L7op_demo_dr
- path: rel/d/a/d_a_mant
- path: rel/d/a/d_a_mg_fish
- path: rel/d/a/d_a_mg_fshop
- path: rel/d/a/d_a_mg_rod
- path: rel/d/a/d_a_midna
- path: rel/d/a/d_a_mirror
- path: rel/d/a/d_a_movie_player
- path: rel/d/a/d_a_myna
- path: rel/d/a/d_a_nbomb
- path: rel/d/a/d_a_ni
- path: rel/d/a/d_a_passer_mng
- path: rel/d/a/d_a_path_line
- path: rel/d/a/d_a_peru
- path: rel/d/a/d_a_ppolamp
- path: rel/d/a/d_a_scene_exit
- path: rel/d/a/d_a_scene_exit2
- path: rel/d/a/d_a_set_bgobj
- path: rel/d/a/d_a_shop_item
- path: rel/d/a/d_a_skip_2D
- path: rel/d/a/d_a_spinner
- path: rel/d/a/d_a_sq
- path: rel/d/a/d_a_startAndGoal
- path: rel/d/a/d_a_suspend
- path: rel/d/a/d_a_swBall
- path: rel/d/a/d_a_swc00
- path: rel/d/a/d_a_swhit0
- path: rel/d/a/d_a_swLBall
- path: rel/d/a/d_a_swTime
- path: rel/d/a/d_a_talk
- path: rel/d/a/d_a_tbox
- path: rel/d/a/d_a_tbox2
- path: rel/d/a/d_a_tboxSw
- path: rel/d/a/d_a_title
- path: rel/d/a/d_a_vrbox
- path: rel/d/a/d_a_vrbox2
- path: rel/d/a/d_a_warp_bug
- path: rel/d/a/d_a_ykgr
- path: rel/d/a/door
sub_labels: ["Actors/Door"]
- path: rel/d/a/e
sub_labels: ["Actors/Enemy"]
- path: rel/d/a/b
sub_labels: ["Actors/Boss"]
- path: rel/d/a/kytag
sub_labels: ["Actors/Kytag"]
- path: rel/d/a/npc
sub_labels: ["Actors/NPC"]
- path: rel/d/a/obj
sub_labels: ["Actors/Object"]
- path: rel/d/a/tag
sub_labels: ["Actors/Tag"]
files:
- path: src/d/a/d_a_alink.cpp
- path: src/d/a/d_a_horse_static.cpp
- path: src/d/a/d_a_item_static.cpp
- path: src/d/a/d_a_itembase.cpp
- path: src/d/a/d_a_itembase_static.cpp
- path: src/d/a/d_a_player.cpp
- path: src/d/a/d_a_no_chg_room.cpp
- path: src/d/a/d_a_shop_item_static.cpp
- path: src/d/a/d_a_npc.cpp
sub_labels: ["Actors/NPC"]
- path: src/d/a/d_a_npc_cd.cpp
sub_labels: ["Actors/NPC"]
- path: src/d/a/d_a_npc_cd2.cpp
sub_labels: ["Actors/NPC"]
- path: src/d/a/d_a_obj.cpp
sub_labels: ["Actors/Object"]
- path: src/d/a/d_a_obj_item.cpp
sub_labels: ["Actors/Object"]
- path: src/d/a/d_a_obj_ss_base.cpp
sub_labels: ["Actors/Object"]
- project:
title: tp_dolphin
dirs:
- path: libs/dolphin
- project:
title: tp_dolzel
dirs:
- path: src/d
- path: src/msg
notDirs:
- path: src/d/a
files:
- path: src/c/c_damagereaction.cpp
- project:
title: tp_Framework
dirs:
- path: src/f_ap
- path: src/f_op
- path: src/f_pc
- project:
title: tp_JSystem
dirs:
- path: libs/JSystem/J2DGraph
sub_labels: ["JSystem/J2DGraph"]
- path: libs/JSystem/J3DGraphAnimator
sub_labels: ["JSystem/J3DGraphAnimator"]
- path: libs/JSystem/J3DGraphBase
sub_labels: ["JSystem/J3DGraphBase"]
- path: libs/JSystem/J3DGraphLoader
sub_labels: ["JSystem/J3DGraphLoader"]
- path: libs/JSystem/J3DU
sub_labels: ["JSystem/J3DU"]
- path: libs/JSystem/JAudio2
sub_labels: ["JSystem/JAudio2"]
- path: libs/JSystem/JFramework
sub_labels: ["JSystem/JFramework"]
- path: libs/JSystem/JGadget
sub_labels: ["JSystem/JGadget"]
- path: libs/JSystem/JKernel
sub_labels: ["JSystem/JKernel"]
- path: libs/JSystem/JMath
sub_labels: ["JSystem/JMath"]
- path: libs/JSystem/JMessage
sub_labels: ["JSystem/JMessage"]
- path: libs/JSystem/JParticle
sub_labels: ["JSystem/JParticle"]
- path: libs/JSystem/JStage
sub_labels: ["JSystem/JStage"]
- path: libs/JSystem/JStudio/JStudio_JAudio2
sub_labels: ["JSystem/JStudio_JAudio2"]
- path: libs/JSystem/JStudio/JStudio_JStage
sub_labels: ["JSystem/JStudio_JStage"]
- path: libs/JSystem/JStudio/JStudio_JParticle
sub_labels: ["JSystem/JStudio_JParticle"]
- path: libs/JSystem/JStudio/JStudio
sub_labels: ["JSystem/JStudio"]
- path: libs/JSystem/JSupport
sub_labels: ["JSystem/JSupport"]
- path: libs/JSystem/JUtility
sub_labels: ["JSystem/JUtility"]
- project:
title: tp_m_Do
dirs:
- path: src/m_Do
- project:
title: tp_Misc
dirs:
- path: libs/odenotstub
- path: libs/odemuexi2
- path: libs/amcstubs
files:
- path: src/init.c
- path: src/__start.c
- path: src/DynamicLink.cpp
- path: src/c/c_dylink.cpp
- project:
title: tp_MSL_C
dirs:
- path: libs/MSL_C
- project:
title: tp_Runtime.PPCEABI.H
dirs:
- path: libs/Runtime.PPCEABI.H
- project:
title: tp_SSystem
dirs:
- path: libs/SSystem
- project:
title: tp_Z2AudioLib
dirs:
- path: libs/Z2AudioLib
+1
View File
@@ -10,3 +10,4 @@ cxxfilt
pyelftools
requests
GitPython
clang

Some files were not shown because too many files have changed in this diff Show More