Last active
August 6, 2022 00:32
-
-
Save samueldy/97a86e681e20dc188951781ff747161b to your computer and use it in GitHub Desktop.
Automate upload of local LaTeX project to Overleaf free plan
This file contains hidden or 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
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# 30 May 2021 - Attempt to automate upload of git repository HEAD to Overleaf" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"- Because they want to charge us for it." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import os\n", | |
"import subprocess\n", | |
"import time\n", | |
"\n", | |
"from selenium import webdriver\n", | |
"from selenium.common.exceptions import WebDriverException\n", | |
"from selenium.webdriver.common.keys import Keys" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"%load_ext blackcellmagic" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Start a webdriver\n", | |
"try:\n", | |
" driver = webdriver.Firefox()\n", | |
"except (FileNotFoundError, WebDriverException):\n", | |
" driver = webdriver.Firefox(\n", | |
" executable_path=os.path.expanduser(\n", | |
" r\"~/Miniconda3/envs/selenium/Scripts/geckodriver.exe\"\n", | |
" )\n", | |
" )" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Log into Overleaf manually\n", | |
"driver.get(\"https://www.overleaf.com\")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Upload local working TeX source files to Overleaf" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def upload_file(path: str, target_folder: str = None):\n", | |
" \"\"\"\n", | |
" Upload a single file to a specific folder in our Overleaf file tree.\n", | |
"\n", | |
" Parameters\n", | |
" ----------\n", | |
" path : str\n", | |
" The fully-qualitifed path to the file on your local computer\n", | |
" target_folder : str\n", | |
" The name of the folder in your Overleaf file tree to which the\n", | |
" folder should be uploaded, by default None. Leave as None in order\n", | |
" to upload to the root of the file tree.\n", | |
" \"\"\"\n", | |
"\n", | |
" # If a target folder was specified, click that.\n", | |
" # If not, click a file in the root to restore focus to the\n", | |
" # root target folder\n", | |
" if target_folder:\n", | |
" folder_button = driver.find_element_by_css_selector(\n", | |
" f\"li[role='treeitem'][aria-label='{target_folder}']\"\n", | |
" )\n", | |
" folder_button.click()\n", | |
" else:\n", | |
" # Click the last file button in the file tree (ignoring buttons\n", | |
" # for the outline)\n", | |
" time.sleep(1)\n", | |
" print(\"Clicking last file button\")\n", | |
" last_file_button = driver.find_elements_by_css_selector(\n", | |
" f\"li[role='treeitem'] button.item-name-button\"\n", | |
" )\n", | |
" last_file_button[-1].click()\n", | |
" time.sleep(1)\n", | |
"\n", | |
" # Click upload button\n", | |
" btn_upload = driver.find_element_by_class_name(name=\"fa-upload\")\n", | |
" btn_upload.click()\n", | |
"\n", | |
" # Get input element for file upload\n", | |
" input_field = driver.find_element_by_css_selector(\n", | |
" css_selector=\"input.uppy-Dashboard-input\"\n", | |
" )\n", | |
"\n", | |
" # Specify absolute path of file name\n", | |
" input_field.send_keys(os.path.abspath(path))\n", | |
"\n", | |
" # Sleep a little to allow the overwrite/OK button to appear\n", | |
" time.sleep(0.35)\n", | |
"\n", | |
" # Look for an overwrite or OK button\n", | |
" try:\n", | |
" overwrite_button = [\n", | |
" btn\n", | |
" for btn in driver.find_elements_by_class_name(name=\"btn.btn-primary\")\n", | |
" if btn.text == \"Overwrite\"\n", | |
" ][0]\n", | |
" overwrite_button.click()\n", | |
" except IndexError:\n", | |
" pass\n", | |
"\n", | |
" try:\n", | |
" ok_button = [\n", | |
" btn\n", | |
" for btn in driver.find_elements_by_class_name(name=\"btn.btn-info\")\n", | |
" if btn.text == \"OK\"\n", | |
" ][0]\n", | |
" ok_button.click()\n", | |
" except IndexError:\n", | |
" pass" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Change directory\n", | |
"os.chdir(\"../lanl-perovskites-ordering-formation-energy\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Now walk through our local file path and upload everything\n", | |
"# Exclude non-LaTeX-source files\n", | |
"exclude_patterns = [\n", | |
" \".aux\",\n", | |
" \".xml\",\n", | |
" \".fls\",\n", | |
" \".out\",\n", | |
" \".log\",\n", | |
" \"main.pdf\",\n", | |
" \".md\",\n", | |
" \".fdb\",\n", | |
" \".bbl\",\n", | |
" \".bcf\",\n", | |
" \"synctex\",\n", | |
" \".ipynb\",\n", | |
" \".blg\",\n", | |
" \".sh\",\n", | |
" \".git\",\n", | |
"]\n", | |
"\n", | |
"file_paths = []\n", | |
"\n", | |
"for root, dirs, files in os.walk(top=\".\"):\n", | |
" dirs[:] = [d for d in dirs if not d.startswith(\".\")]\n", | |
" file_paths.extend(\n", | |
" [\n", | |
" os.path.join(root, file)\n", | |
" for file in files\n", | |
" if not any([pattern in file for pattern in exclude_patterns])\n", | |
" ]\n", | |
" )" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"file_paths" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# For each file path, upload the file and wait a delay\n", | |
"target_file_exts = [\".tex\", \".pdf\", \".png\", \".jpg\"]\n", | |
"files_to_upload = [\n", | |
" file_path\n", | |
" for file_path in file_paths\n", | |
" if any([ext in file_path for ext in target_file_exts])\n", | |
"]\n", | |
"for file_path in files_to_upload:\n", | |
" dirname = os.path.basename(os.path.dirname(file_path))\n", | |
" target_folder = None if dirname == \".\" else dirname.replace(\n", | |
" \".\" + os.path.sep, \"\")\n", | |
"\n", | |
" print(file_path, \"-->\", target_folder)\n", | |
"\n", | |
" upload_file(path=file_path, target_folder=target_folder)\n", | |
" time.sleep(1)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Take snapshot of current Overleaf project" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Hit the download button and save to temporary file\n", | |
"btn_menu = driver.find_element_by_css_selector(\"i.editor-menu-icon\")\n", | |
"btn_menu.click()\n", | |
"time.sleep(0.5)\n", | |
"btn_download = driver.find_element_by_css_selector(\"i.fa-file-archive-o\")\n", | |
"btn_download.click()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"- Now manually save the zip file to `~/Downloads` under the correct name.\n", | |
"- Then we'll just run the snapshot script in the current directory." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Change directory\n", | |
"os.chdir(\"../lanl-perovskites-ordering-formation-energy\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"result = subprocess.run(\"./snapshot.sh\", capture_output=True, encoding=\"utf8\")\n", | |
"print(result.stdout)\n", | |
"print(result.stderr)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Closing the browser" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Log out and close browser\n", | |
"driver.find_element_by_class_name(name=\"fa-home\").click()\n", | |
"time.sleep(1)\n", | |
"driver.find_element_by_xpath(xpath=r\"//*[contains(text(), 'Account')]\").click()\n", | |
"time.sleep(1)\n", | |
"driver.find_element_by_xpath(xpath=r\"//*[contains(text(), 'Log Out')]\").click()\n", | |
"\n", | |
"driver.close()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python [conda env:selenium]", | |
"language": "python", | |
"name": "selenium" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.7.6" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 4 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment