Skip to content

Instantly share code, notes, and snippets.

@samueldy
Last active August 6, 2022 00:32
Show Gist options
  • Save samueldy/97a86e681e20dc188951781ff747161b to your computer and use it in GitHub Desktop.
Save samueldy/97a86e681e20dc188951781ff747161b to your computer and use it in GitHub Desktop.
Automate upload of local LaTeX project to Overleaf free plan
Display the source blob
Display the rendered blob
Raw
{
"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