Update Jurassic Parka.ash #369
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: bundle ASH script(s), and push to new branch | |
# By https://github.com/fredg1 | |
# USER MANUAL | |
# This workflow should run by itself without the need for any change. | |
# It's, however, currently tailored to TourGuide. | |
# If you wish to change its behavior (change the name of the target branch, where it places the result in it, or if you wish to use it on a folder + script other than Source + relay/TourGuide.ash), all you need to modify should be in the "env" section down below. | |
# I'm working on making this its own action that can be called as many times as needed, on any script, with as much customization as possible, but github is still not done on the feature I'd need for that :/ | |
env: | |
# Branch to checkout under ./target_branch/ . If contains {0}, {0} will be replaced by the source branch's name. | |
# (doesn't need to be the name of an existing branch. Will create one if needed (will be a copy of the source branch)) | |
# examples: 'Pre-Release', 'Bundled-{0}', 'my_{0}_2.0' or just '{0}' (not the best idea, though...) | |
TARGET_NAME: 'Bundled-{0}' | |
# The path to take (folders to go through) to reach the "mafia folder" in both branches. | |
# RELATIVE paths only (relative to the root of the repository). | |
# Empty string if it is reachable from the root. | |
# Only its content will be transferred to <target_branch> | |
SOURCE_PATH_TO_MAFIA: 'Source' | |
# If this folder already exists on the target branch, IT'S PREVIOUS CONTENT WILL BE ERASED. | |
# Stuff outside of it won't. | |
TARGET_PATH_TO_MAFIA: '' | |
# comma-separated list of (relative) path and name of the ASH scripts you want to bundle, and their new names and (relative) destinations. | |
# Paths need to be relative to the "mafia folder". | |
# Any .ash script not in this list won't be transferred, so if you have scripts that don't have imports, but want them in the result, include them here anyway | |
# The scripts to bundle | |
ASH_SCRIPT_SOURCES: 'relay/relay_TourGuide.ash' | |
# Their (RESPECTIVE; order matters) destinations | |
ASH_SCRIPT_DESTINATIONS: 'relay/relay_TourGuide.ash' | |
on: | |
workflow_dispatch: | |
# for more info about this if you want to modify it, see | |
# https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestbranchestags | |
# and | |
# https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestpaths | |
push: | |
paths: | |
#- '${{ SOURCE_PATH_TO_MAFIA }}/**' # it's impossible to query env. variables at this point, sorry :( | |
- '**' | |
- '!.github/**' # security measure: doesn't trigger if anything in .github was changed (such as the creation, modification or deletion of this very file) | |
# Anything past this point shouldn't need to be modified. | |
jobs: | |
build: | |
runs-on: windows-latest | |
steps: | |
- name: setup Python | |
uses: actions/setup-python@v2 | |
with: | |
python-version: 3.8 | |
- name: Get branch name | |
uses: nelonoel/branch-name@v1.0.1 | |
# Checkout referrer (source) under ./source_branch/ | |
- name: checkout current branch | |
uses: actions/checkout@v2 | |
with: | |
path: ./source_branch | |
# Parse wanted target name, then checkout under ./target_branch/ | |
- name: set desired target branch | |
id: set-target | |
shell: python | |
run: | | |
source = '${{ env.BRANCH_NAME }}' | |
target = '${{ env.TARGET_NAME }}' | |
if target == source: | |
pass | |
elif target == '': | |
target = 'Bundled-' + source | |
elif '{0}' in target: | |
target = target.format( source ) | |
print(f'::set-output name=target-branch::{target}') | |
- name: checkout target branch | |
id: target_get | |
uses: actions/checkout@v2 | |
continue-on-error: true | |
with: | |
ref: ${{ steps.set-target.outputs.target-branch }} | |
path: ./target_branch | |
persist-credentials: false | |
fetch-depth: 0 | |
# target branch doesn't exist; create it | |
- name: create target branch | |
id: target_create | |
if: ${{ steps.target_get.outcome == 'failure' }} | |
uses: peterjgrainger/action-create-branch@v2.0.1 | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
with: | |
branch: ${{ steps.set-target.outputs.target-branch }} | |
# NOW checkout the newly-made target branch | |
- name: checkout created target branch | |
id: target_get_created | |
if: ${{ steps.target_create.outcome == 'success' }} | |
uses: actions/checkout@v2 | |
with: | |
ref: ${{ steps.set-target.outputs.target-branch }} | |
path: ./target_branch | |
persist-credentials: false | |
fetch-depth: 0 | |
# If this branch needed to be created, assume the user doesn't want to have this workflow trigger in it | |
# this was way too much work for something so situational/niche... X_X | |
- name: get workflow path | |
id: get-workflow-path | |
if: ${{ steps.target_get_created.outcome == 'success' }} | |
shell: pwsh | |
run: | | |
$Header = @{"Accept" = "application/vnd.github.v3+json"} | |
$uri = "https://api.github.com/repos/${{ github.repository }}/actions/workflows" | |
$Workflows = Invoke-RestMethod -Method GET -Header $Header -uri $uri | |
Foreach ($workflow in $Workflows.workflows) | |
{ | |
if ( $workflow.path -eq "${{ github.workflow }}" -or $workflow.name -eq "${{ github.workflow }}" ) | |
{ | |
$workflow_path = $workflow.path | |
echo "::set-output name=workflow-path::$workflow_path" | |
break | |
} | |
} | |
- name: don't carry workflow | |
if: ${{ steps.get-workflow-path.outcome == 'success' }} | |
working-directory: ./target_branch | |
shell: python | |
run: | | |
import os | |
workflow_file = '${{ steps.get-workflow-path.outputs.workflow-path }}' | |
new_branch_to_exclude = '${{ steps.set-target.outputs.target-branch }}' | |
def removeComment(line: str, start = 0): | |
comment_start = line.find('#', start) | |
if comment_start == -1: | |
return line | |
else: | |
if line[comment_start - 1] == '\\': | |
removeComment( line, comment_start + 1 ) | |
else: | |
return line[:comment_start] | |
extra_line_separator_characters = len( os.linesep ) - 1 # may mess us up when we manually move the pointer on Windows (which is \r\n , 2 characters) | |
if os.path.exists( workflow_file ): | |
with open(workflow_file, mode='r+', encoding='UTF-8') as f: | |
in_event_trigger_section = False # whether we reached "on:". Never set back to False; we exit the loop instead | |
in_push_event_trigger = False # whether we reached "push:", and if we're still in it | |
push_event_trigger_indentation = 0 # the indentation of the "push:" block. Used to check when to set in_push_event_trigger back to False, and in case the push: block ends up being empty, as a backup of indentation_after_push | |
push_event_trigger_location = 0 # pointer location after the "push:" block. Used in case the push: block ends up being empty | |
indentation_after_push = 0 # the indentation of the "branches:"/"branches-ignore:" block (or, if we don't see it, the first block inside of "push:" that we see). Used to tell when we left the branches:/branches-ignore: block, and, if that block didn't end up being here, the indentation we'll use to MAKE said block | |
found_branches = False # which, of branches or branches-ignore, we found | |
found_branches_ignore = False # which, of branches or branches-ignore, we found | |
branches_start_location = 0 # pointer location after the "branches:" or "branches-ignore:" block (currently unused) | |
branches_end_location = None # furthest we got the pointer while in_branches was True. We want to add something right after that | |
branches_indentation = 0 # by how much the user indented what's IN their branches:/branches-ignore: scope. They all need the same indentation | |
in_branches = False # whether we reached "branches:" or "branches-ignore:", and if we're still in it | |
branches_lines = [] # list of lines interpreted as being under the "branches:" or "branches-ignore:" scope | |
while True: | |
line = f.readline() | |
# we're looking for: | |
#on: (event_trigger_section) | |
# [...] | |
# push: (push_event_trigger) | |
# [...] | |
# branches: | |
if line == '': | |
if in_branches: | |
branches_end_location = f.tell() | |
break # end of file | |
elif in_event_trigger_section: | |
# remove comments from the line | |
no_commented_line = removeComment( line ) | |
stripped_line = no_commented_line.lstrip() | |
current_indentation = len( no_commented_line ) - len( stripped_line ) | |
if stripped_line == '': | |
if in_branches: | |
branches_lines.append( line ) | |
continue | |
if in_push_event_trigger and current_indentation <= push_event_trigger_indentation: | |
in_push_event_trigger = False # we left that block | |
if in_branches and current_indentation <= indentation_after_push and (current_indentation < indentation_after_push or not stripped_line.startswith('-')): | |
in_branches = False | |
branches_end_location = f.tell() - ( len( line ) + extra_line_separator_characters ) | |
if current_indentation == 0: | |
break # we left the event trigger section | |
if stripped_line.startswith('push:') and ( push_event_trigger_indentation == 0 or current_indentation < push_event_trigger_indentation ): | |
in_push_event_trigger = True | |
push_event_trigger_indentation = current_indentation | |
push_event_trigger_location = f.tell() | |
# also reset all the other variables in case we previously set them in the wrong(???) "push:" block | |
indentation_after_push = 0 | |
found_branches = False | |
found_branches_ignore = False | |
branches_start_location = 0 | |
branches_end_location = None | |
branches_indentation = 0 | |
in_branches = False | |
branches_lines = [] | |
elif in_push_event_trigger: | |
if indentation_after_push == 0: | |
indentation_after_push = current_indentation # save this for later in case there's no "branches:" tag, and we need to add it ourselves | |
if in_branches: | |
if stripped_line.startswith('-') and branches_indentation == 0: | |
branches_indentation = current_indentation | |
branches_lines.append( line ) | |
elif stripped_line.startswith('branches:'): | |
found_branches = True | |
branches_start_location = f.tell() | |
in_branches = True | |
indentation_after_push = current_indentation | |
elif stripped_line.startswith('branches-ignore:'): | |
found_branches_ignore = True | |
branches_start_location = f.tell() | |
in_branches = True | |
indentation_after_push = current_indentation | |
elif line.startswith('on:'): | |
in_event_trigger_section = True | |
if push_event_trigger_location > 0: # if it's just not there, don't bother creating it | |
rest_of_file = """""" | |
if indentation_after_push == 0: # push: is empty | |
indentation_after_push = push_event_trigger_indentation * 2 | |
if not found_branches and not found_branches_ignore: # need to create one ourselves | |
f.seek(push_event_trigger_location) | |
rest_of_file = f.read() | |
f.seek(push_event_trigger_location) | |
most_recently_added_line = ''.rjust( indentation_after_push ) | |
most_recently_added_line += 'branches-ignore:\n' | |
most_recently_added_line += ''.rjust( indentation_after_push ) + "- '" | |
else: | |
# trim any lines at the end that are empty lines / only a comment | |
for x in range(len( branches_lines ), 0, -1): | |
branches_line = branches_lines[x-1] | |
if removeComment( branches_line ).lstrip() != '': | |
break | |
branches_lines.pop(x) | |
branches_end_location -= len( branches_line ) + extra_line_separator_characters | |
f.seek( branches_end_location ) | |
rest_of_file = f.read() | |
f.seek( branches_end_location ) | |
if branches_indentation == 0: | |
branches_indentation = indentation_after_push | |
most_recently_added_line = ''.rjust( branches_indentation ) + "- '" | |
if found_branches: | |
most_recently_added_line += '!' | |
most_recently_added_line += new_branch_to_exclude + "'\n" | |
f.write( most_recently_added_line ) | |
f.write( rest_of_file ) | |
# Clean mafia folder in target branch | |
- name: clean target branch | |
shell: python | |
run: | | |
import os | |
import shutil | |
target_base = os.path.join( 'target_branch', '${{ env.TARGET_PATH_TO_MAFIA }}' ) | |
if not os.path.exists( target_base ): | |
os.makedirs( target_base ) | |
with os.scandir(target_base) as cur_dir: | |
for dir_entry in cur_dir: | |
if dir_entry.name != '.git': | |
path_to_entry = os.path.join(target_base, dir_entry.name) | |
if dir_entry.is_dir(follow_symlinks=False): | |
shutil.rmtree(path=path_to_entry) | |
else: | |
os.remove(path=path_to_entry) | |
- name: checkout bundler | |
uses: actions/checkout@v2 | |
with: | |
repository: fredg1/ASH-bundler | |
path: bundler_branch | |
- name: Forward bundle output to target branch | |
id: bundle-ASH | |
shell: python | |
run: | | |
import os | |
import sys | |
sys.path.append( os.path.join( os.getcwd(), 'bundler_branch' ) ) | |
import Bundle_ASH_script | |
source_scripts = tuple( map(str, '${{ env.ASH_SCRIPT_SOURCES }}'.split(',') ) ) | |
destinations = tuple( map(str, '${{ env.ASH_SCRIPT_DESTINATIONS }}'.split(',') ) ) | |
if len(source_scripts) != len(destinations): | |
raise Exception("ASH_SCRIPT_SOURCES and ASH_SCRIPT_DESTINATIONS aren't properly paired") | |
imported_files = [] | |
bundled_files = [] | |
for i in range( len( source_scripts ) ): | |
# The script that bundles the target script. | |
# First argument is the source script. | |
source_script = source_scripts[i] | |
# Second argument is the path to reach the file to create/put the result in. | |
destination = os.path.join( 'target_branch', '${{ env.TARGET_PATH_TO_MAFIA }}', destinations[i] ) | |
# Third argument (optional) is the path to reach the "mafia folder". | |
path_to_source_script = os.path.join( 'source_branch', '${{ env.SOURCE_PATH_TO_MAFIA }}' ) | |
imports = Bundle_ASH_script.bundle_and_write( source_script, destination, path_to_source_script, allow_overwrite=True, return_imported_files=True ) | |
imported_files.extend( imports ) | |
bundled_files.append( source_script ) | |
print(f'::set-output name=imported_files::{imported_files}') | |
print(f'::set-output name=bundled_files::{bundled_files}') | |
# copy any non-ASH file in target_branch | |
- name: Add non-ash in Source | |
id: parse-rest | |
shell: python | |
run: | | |
import os | |
source_base = os.path.join('source_branch', '${{ env.SOURCE_PATH_TO_MAFIA }}') | |
target_base = os.path.join('target_branch', '${{ env.TARGET_PATH_TO_MAFIA }}') | |
imported_files = ${{ steps.bundle-ASH.outputs.imported_files }} | |
bundled_files = ${{ steps.bundle-ASH.outputs.bundled_files }} | |
used_ASH_files = [] | |
for file in (imported_files + bundled_files): | |
used_ASH_files.append( os.path.normcase( os.path.normpath( file ) ) ) | |
unused_ASH_files = [] | |
def parse_folder(cur_dir, current_path = ''): | |
for dir_entry in cur_dir: | |
if dir_entry.is_symlink(): | |
continue | |
if dir_entry.is_dir(): | |
next_path = os.path.join(current_path, dir_entry.name) | |
with os.scandir( os.path.join(source_base, next_path) ) as next_dir: | |
parse_folder(next_dir, next_path) | |
elif dir_entry.is_file(): | |
this_path = os.path.join(current_path, dir_entry.name) | |
if this_path.endswith('.ash'): | |
if used_ASH_files.count( os.path.normcase( os.path.normpath( this_path ) ) ) == 0: | |
unused_ASH_files.append( this_path ) | |
else: | |
print('Grabbing ' + this_path + ' ...') | |
this_path_source = os.path.join(source_base, this_path) | |
this_path_target = os.path.join(target_base, this_path) | |
if os.path.exists( this_path_target ): | |
os.remove( this_path_target ) | |
os.renames(this_path_source, this_path_target) | |
with os.scandir(source_base) as source_dir: | |
parse_folder(cur_dir = source_dir) | |
if len( unused_ASH_files ) > 0: | |
print() | |
with open('artifact.txt', mode='x', encoding='UTF-8') as f: | |
message = str( len( unused_ASH_files ) ) + ' unused ASH file(s) (not imported by any of the scripts) found:' | |
print( message ) | |
f.write( message + '\n' ) | |
for unused_file in unused_ASH_files: | |
message = ' ' + unused_file | |
print( message ) | |
f.write( message + '\n' ) | |
- name: upload unused files message as artifact | |
uses: actions/upload-artifact@v2 | |
with: | |
name: unused files found | |
path: ./artifact.txt | |
if-no-files-found: ignore | |
- name: commit changes to target | |
working-directory: ./target_branch | |
run: | | |
git config --local user.email "action@github.com" | |
git config --local user.name "GitHub Action" | |
git add --all -v * | |
git commit -m "Import changes from ${{ github.ref }}" -a | |
git push "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" HEAD:refs/heads/${{ steps.set-target.outputs.target-branch }} --follow-tags --tags |