diff --git a/.github/workflows/on_template_changes.yml b/.github/workflows/on_template_changes.yml new file mode 100644 index 0000000..405afa6 --- /dev/null +++ b/.github/workflows/on_template_changes.yml @@ -0,0 +1,62 @@ +name: integrate changed templates + +on: + push: + branches: + - main + paths: + - 'frontTemplate.html' + - 'backTemplate.html' + - 'style.css' + workflow_dispatch: + +permissions: + contents: write + +jobs: + update-files: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Update Readme and demo deck + run: python actions/update_files.py ${{ github.repository }} ${{ github.sha }} 'README.md' + + - name: Commit and Push Changes + run: | + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + git add README.md PrettyYomitanCardsDemo.apkg + git commit -m "Updated demo deck and permalinks in README" + git push + + create-release-draft: + runs-on: ubuntu-latest + needs: update-files + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Bump Version + id: tag_version + uses: mathieudutour/github-tag-action@v6.2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + dry_run: true + + - name: Create new Release + uses: softprops/action-gh-release@v2 + with: + body_path: actions/release_body.md + name: AnkiDemoDeck ${{ steps.tag_version.outputs.new_tag }} + tag_name: ${{ steps.tag_version.outputs.new_tag }} + files: PrettyYomitanCardsDemo.apkg + draft: true diff --git a/actions/release_body.md b/actions/release_body.md new file mode 100644 index 0000000..90dbb98 --- /dev/null +++ b/actions/release_body.md @@ -0,0 +1,8 @@ +A small deck in Anki's .apkg format to show of the template and streamline the setup for inexperienced users. + +To import this deck into your Anki collection simply download the .apkg file and open your Anki application. Under File click `Import...`, navigate to your downloaded file's location, select it and confirm. Anki will now import the new deck under the name PrettyYomitanCardsDemo. +When it is done you can open a card of the deck in the card browser and confirm if everything is working correctly in the preview. + +You can now reuse the *PrettyYomitan* note type to generate your own flashcards with the template and the Yomitan browser extension. + +If you are following the setup tutorial return to the [Configure Yomitan](https://github.com/99-Knots/PrettyYomitanCards?tab=readme-ov-file#configuring-yomitan) section and continue the instructions from there. \ No newline at end of file diff --git a/actions/update_files.py b/actions/update_files.py new file mode 100644 index 0000000..27eb2a4 --- /dev/null +++ b/actions/update_files.py @@ -0,0 +1,125 @@ +import sys, re +import zipfile, os +import sqlite3, json + +TEMP_DIR = 'temp_dir' +NOTE_NAME = 'PrettyYomitan' +CARD_TYPE = 'Recognition' + + +def setStyle(note, file_path): + with open(file_path, encoding='utf-8') as file: + css_file = file.read() + note['css'] = css_file + +def setTemplate(note, card_name, file_path, key): + with open(file_path, encoding='utf-8') as file: + file_content = file.read() + template = next((item for item in note['tmpls'] if item['name'] == card_name), None) + if template: + template[key] = file_content + +def setFrontTemplate(note, template_name, file): + setTemplate(note, template_name, file, 'qfmt') + +def setBackTemplate(note, template_name, file): + setTemplate(note, template_name, file, 'afmt') + + +def createAnkiPkg(source_dir, old_pkg): + with zipfile.ZipFile('tempDeck.apkg', 'w', zipfile.ZIP_DEFLATED) as zip_ref: + for folder_name, subfolders, filenames in os.walk(source_dir): + for filename in filenames: + file_path = os.path.join(folder_name, filename) + zip_ref.write(file_path, filename) + os.replace('tempDeck.apkg', old_pkg) + + +def extractOldDeck(filename): + with zipfile.ZipFile(filename, 'r') as old_deck: + old_deck.extractall(TEMP_DIR) + + +def editDB(source, front_file=None, back_file=None, style_file=None): + if not(front_file is None and back_file is None and style_file is None): + try: + conn = sqlite3.connect(source) + cursor = conn.cursor() + + cursor.execute('SELECT id, models FROM col') + mod_id, models = cursor.fetchall()[0] + models = json.loads(models) + notetype = next((models[k] for k in models.keys() if models[k]['name'] == NOTE_NAME), None) + + if front_file is not None: + setFrontTemplate(notetype, CARD_TYPE, front_file) + if back_file is not None: + setBackTemplate(notetype, CARD_TYPE, back_file) + if style_file is not None: + setStyle(notetype, style_file) + + new_models = json.dumps(models) + cursor.execute((f'UPDATE col SET models = ? WHERE id = ?'), (new_models, mod_id)) + + conn.commit() + + except sqlite3.Error as e: + print(f'Database error: {e}') + conn.rollback() + sys.exit(1) + + finally: + conn.close() + + +def updateDemoDeck(): + source = os.path.join(TEMP_DIR, 'collection.anki21') + deck_name = 'PrettyYomitanCardsDemo.apkg' + front = 'frontTemplate.html' + back = 'backTemplate.html' + styling = 'style.css' + + extractOldDeck(deck_name) + editDB(source, front, back, styling) + createAnkiPkg(TEMP_DIR, deck_name) + print('updated the .apkg with new template files') + + +def replacePermalink(start_marker, end_marker, content, prefix): + def replace(match): + f = match.group(2) + with open(f) as file: + line_count = len(file.readlines()) + start_m = start_marker.format(file=' ' + f) + return f'{start_m}\n{prefix}{f}#L1-L{line_count}\n{end_marker}' + + start_m = start_marker.format(file=r'\s*(.*?)\s*') + pattern = f'({start_m})(.*?)({end_marker})' + + return(re.sub(pattern, replace, content, flags=re.DOTALL)) + + +def processReadme(readme, repo, hash_long): + with open(readme, 'r') as file: + content = file.read() + start_marker = '' + end_marker = '' + with open(readme, 'w') as file: + file.write(replacePermalink(start_marker, end_marker, content, prefix='https://github.com/' + repo + '/blob/' + hash_long + '/')) + print('inserted new permalinks') + +if __name__ == '__main__': + if len(sys.argv) < 4: + print('Usage: python update_files.py ') + sys.exit(1) + + user_repo = sys.argv[1].strip() + hash_long = sys.argv[2].strip() + readme_path = sys.argv[3].strip() + + if not user_repo or not hash_long or not readme_path: + print('Error: One or more required arguments are empty.') + sys.exit(1) + + processReadme(readme_path, user_repo, hash_long) + updateDemoDeck()