def __init__(self) -> None: """Load configuration or initialize on instance creation""" self.overleaf_config_file = os.path.join( Gigantum.get_gigantum_directory(), 'overleaf.json') self.overleaf_repo_directory = os.path.join( Gigantum.get_overleaf_root_directory(), 'project') self.overleaf_credential_file = os.path.join( Gigantum.get_overleaf_root_directory(), 'credentials.json') self.config: OverleafConfig = self._load_config() # Clone the Overleaf git repo if needed if os.path.isfile(self.overleaf_config_file): if not os.path.isdir(self.overleaf_repo_directory): # Overleaf project does not exist locally yet, clone self._clone()
def _set_creds(self, email: str, password: str) -> None: """ Write credentials to an untracked file Args: email: the Overleaf email to save password: the Overleaf password to save Returns: None """ if not os.path.isdir(Gigantum.get_overleaf_root_directory()): os.makedirs(Gigantum.get_overleaf_root_directory()) creds = {"email": email, "password": password} with open(self.overleaf_credential_file, 'wt') as cf: json.dump(creds, cf)
def test_link_image_and_sync(self, gigantum_project_fixture): gigaleaf = Gigaleaf() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is False gigaleaf.link_image('../output/fig1.png', caption="My figure", label='fig111', alignment='right', width='0.3\\textwidth') assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is True gigaleaf.sync() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'data', 'fig1.png').is_file() is True # Delete everything in untracked, reinit, and should still see the files shutil.rmtree(gigaleaf.overleaf.overleaf_repo_directory) gigaleaf = None assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is False assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'data', 'fig1.png').is_file() is False gigaleaf = Gigaleaf() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is True assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'data', 'fig1.png').is_file() is True
def test_unlink_image(self, gigantum_project_fixture): gigaleaf = Gigaleaf() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is False gigaleaf.link_image('../output/fig1.png') assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is True gigaleaf.sync() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'data', 'fig1.png').is_file() is True gigaleaf.unlink_image('../output/fig1.png') assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is False assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'data', 'fig1.png').is_file() is False gigaleaf.sync() # Delete everything in untracked, reinit, and should still not see the files shutil.rmtree(gigaleaf.overleaf.overleaf_repo_directory) gigaleaf = None gigaleaf = Gigaleaf() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is False assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'data', 'fig1.png').is_file() is False
def test_link_csv_file_and_sync(self, gigantum_project_fixture): gigaleaf = Gigaleaf() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'table_pkl.json').is_file() is False gigaleaf.link_dataframe('../output/table.pkl', to_latex_kwargs={ "index": False, "caption": "My table" }) assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'table_pkl.json').is_file() is True gigaleaf.sync() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'subfiles', 'table_pkl.tex').is_file() is True # Delete everything in untracked, reinit, and should still see the files shutil.rmtree(gigaleaf.overleaf.overleaf_repo_directory) gigaleaf = None assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'table_pkl.json').is_file() is False assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'subfiles', 'table_pkl.tex').is_file() is False gigaleaf = Gigaleaf() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'table_pkl.json').is_file() is True assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'subfiles', 'table_pkl.tex').is_file() is True
def test_delete_project_when_empty(self, gigantum_project_fixture): gigaleaf = Gigaleaf() assert Path(gigaleaf.overleaf.overleaf_config_file).is_file() is True gigaleaf.delete() assert Path(Gigantum.get_overleaf_root_directory()).is_dir() is False assert Path(gigaleaf.overleaf.overleaf_config_file).is_file() is False
def data_filename(self) -> str: """The absolute path to the linked file's data in the data directory The data directory is the location managed by gigaleaf where linked file contents are stored Returns: absolute path to the file """ overleaf_gigantum_path = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'data') return Path(overleaf_gigantum_path, Path(self.metadata.gigantum_relative_path).name).absolute().as_posix()
def test_delete_project_link(self, gigantum_project_fixture): gigaleaf = Gigaleaf() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is False gigaleaf.link_image('../output/fig1.png') assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is True gigaleaf.sync() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'data', 'fig1.png').is_file() is True gigaleaf.delete() assert Path(Gigantum.get_overleaf_root_directory()).is_dir() is False assert Path(gigaleaf.overleaf.overleaf_config_file).is_file() is False
def subfile_filename(self) -> str: """The absolute path to the Linked File's subfile in the subfiles directory The subfiles directory is the location managed by gigaleaf where all generated subfiles are stored Returns: absolute path to the subfile """ filename = Path(self.metadata_filename).name filename = filename.replace('.json', '.tex') overleaf_gigantum_path = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'subfiles') return Path(overleaf_gigantum_path, filename).absolute().as_posix()
def test_update_image(self, gigantum_project_fixture): gigaleaf = Gigaleaf() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is False gigaleaf.link_image('../output/fig1.png', width='0.8\\textwidth') assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is True gigaleaf.sync() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'data', 'fig1.png').is_file() is True metadata_file = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json') with open(metadata_file, 'rt') as mf: data = json.load(mf) first_hash = data['content_hash'] test_dir = Path(__file__).parent.absolute() shutil.copyfile( Path(test_dir, 'resources', 'fig1.png').as_posix(), Path(Gigantum.get_project_root(), 'output', 'fig1.png')) gigaleaf.sync() metadata_file = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json') with open(metadata_file, 'rt') as mf: data = json.load(mf) assert first_hash != data['content_hash']
def unlink_csv(self, relative_path: str) -> None: """Method to unlink a csv file from your Overleaf project. Args: relative_path: relative path to the file from the current working dir, e.g. `../output/my_table.csv` Returns: None """ metadata_filename = ImageFile.get_metadata_filename(relative_path) metadata_abs_filename = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', metadata_filename) csv_file = load_linked_file(metadata_abs_filename.as_posix()) csv_file.unlink()
def test_link_image_with_defaults(self, gigantum_project_fixture): gigaleaf = Gigaleaf() assert Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json').is_file() is False gigaleaf.link_image('../output/fig1.png') metadata_file = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', 'fig1_png.json') assert metadata_file.is_file() is True with open(metadata_file, 'rt') as mf: data = json.load(mf) assert data['gigantum_relative_path'] == 'output/fig1.png' assert data['gigantum_version'] != 'init' assert len(data['gigantum_version']) == 40 assert data['classname'] == 'ImageFile' assert data['content_hash'] == 'init' assert data['caption'] is None assert data['label'] == 'fig:fig1' assert data['width'] == '0.5\\textwidth' assert data['alignment'] == 'center'
def write_metadata(metadata_filename: str, **kwargs: Any) -> None: """Method to write metadata to disk Args: metadata_filename: name of the metadata file **kwargs: Returns: None """ data = dict() if Path(metadata_filename).exists(): # Update existing file with open(metadata_filename, 'rt') as mf: data = json.load(mf) data.update(kwargs) metadata_abs_filename = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', metadata_filename) with open(metadata_abs_filename, 'wt') as mf: json.dump(data, mf)
def link(cls, relative_path: str, **kwargs: Dict[str, Any]) -> None: """Method to link a file output in a Gigantum Project to an Overleaf project Args: relative_path: relative path to the file from the current working dir, e.g. `../output/my_fig.png` **kwargs: args specific to each LinkedFile implementation Returns: None """ file_path = Path(relative_path).resolve() if file_path.is_file() is False: # File provided does not exist raise ValueError(f"The file {file_path} does not exist. Provide a relative path from the working" f"directory to your file. In Jupyter, the working directory is the directory containing " f"your notebook.") metadata_filename = cls.get_metadata_filename(relative_path) metadata_abs_filename = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', metadata_filename) if metadata_abs_filename.exists() is True: # This is an update to the link, so get the current content hash for the file. with open(metadata_abs_filename, 'rt') as mf: current_metadata: Dict[str, Any] = json.load(mf) content_hash = current_metadata['content_hash'] else: # Set content hash to init so it is always detected as "modified" on first link content_hash = "init" full_kwargs = { "gigantum_relative_path": file_path.relative_to(Path(Gigantum.get_project_root()).resolve()).as_posix(), "gigantum_version": Gigantum.get_current_revision(), "classname": cls.__name__, "content_hash": content_hash, "metadata_filename": metadata_filename} full_kwargs.update(kwargs) cls.write_metadata(**full_kwargs)
class Gigaleaf: """Class to link Gigantum Project outputs to an Overleaf Project""" def __init__(self) -> None: self.overleaf = Overleaf() self.gigantum = Gigantum(self.overleaf.overleaf_repo_directory) def link_image(self, relative_path: str, caption: Optional[str] = None, label: Optional[str] = None, width: str = "0.5\\textwidth", alignment: str = 'center') -> None: """Method to link an image file to your Overleaf project for automatic updating Args: relative_path: relative path to the file from the current working dir, e.g. `../output/my_fig.png` caption: The caption for the figure in the auto-generated latex subfile label: The label for the figure in the auto-generated latex subfile width: A string setting the width of the figure for the figure in the auto-generated latex subfile alignment: A string setting the alignment for the figure in the auto-generated latex subfile. Supported values are `left, right, center, inner, and outer` If this method is called more than once for a given `gigantum_relative_path`, the link will simply be updated. This is useful for doing things like editing a caption. Returns: None """ if not label: safe_filename = ImageFile.get_safe_filename(relative_path) label = f"fig:{Path(safe_filename).stem}" kwargs: Dict[str, Any] = { "caption": caption, "label": label, "width": width, "alignment": alignment } ImageFile.link(relative_path, **kwargs) def unlink_image(self, relative_path: str) -> None: """Method to unlink an image file from your Overleaf project. Args: relative_path: relative path to the file from the current working dir, e.g. `../output/my_fig.png` Returns: None """ metadata_filename = ImageFile.get_metadata_filename(relative_path) metadata_abs_filename = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', metadata_filename) img_file = load_linked_file(metadata_abs_filename.as_posix()) img_file.unlink() def link_csv(self, relative_path: str, caption: Optional[str] = None, label: Optional[str] = None) -> None: """Method to link a csv file to your Overleaf project for automatic updating Args: relative_path: relative path to the file from the current working dir, e.g. `../output/my_table.csv` caption: The caption for the table in the auto-generated latex subfile label: The label for the table in the auto-generated latex subfile Returns: None """ if not label: safe_filename = ImageFile.get_safe_filename(relative_path) label = f"table:{Path(safe_filename).stem}" kwargs: Dict[str, Any] = {"caption": caption, "label": label} CsvFile.link(relative_path, **kwargs) def unlink_csv(self, relative_path: str) -> None: """Method to unlink a csv file from your Overleaf project. Args: relative_path: relative path to the file from the current working dir, e.g. `../output/my_table.csv` Returns: None """ metadata_filename = ImageFile.get_metadata_filename(relative_path) metadata_abs_filename = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', metadata_filename) csv_file = load_linked_file(metadata_abs_filename.as_posix()) csv_file.unlink() def link_dataframe(self, relative_path: str, to_latex_kwargs: Dict[str, Any]) -> None: """Method to link a dataframe file to your Overleaf project for automatic updating Args: relative_path: relative path to the file from the current working dir, e.g. `../output/my_table.csv` to_latex_kwargs: a dictionary of key word arguments to pass into the pandas.DataFrame.to_latex method (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_latex.html) Returns: None """ # Clean kwargs sent to .to_latex() if 'buf' in to_latex_kwargs: del to_latex_kwargs['buf'] kwargs = {"to_latex_kwargs": to_latex_kwargs} DataframeFile.link(relative_path, **kwargs) def unlink_dataframe(self, relative_path: str) -> None: """Method to unlink a dataframe file from your Overleaf project. Args: relative_path: relative path to the file from the current working dir, e.g. `../output/my_table.csv` Returns: None """ metadata_filename = ImageFile.get_metadata_filename(relative_path) metadata_abs_filename = Path(Gigantum.get_overleaf_root_directory(), 'project', 'gigantum', 'metadata', metadata_filename) dataframe_file = load_linked_file(metadata_abs_filename.as_posix()) dataframe_file.unlink() def sync(self) -> None: """Method to synchronize your Gigantum and Overleaf projects. When you call this method, gigaleaf will do the following: * Pull changes from the Overleaf project * Check all linked files for changes. If changes exist it will update files in the Overleaf project * Commit changes to the Overleaf project * Push changes to the Overleaf project Returns: None """ print("Syncing with Overleaf. Please wait...") self.overleaf.pull() linked_files = load_all_linked_files( self.overleaf.overleaf_repo_directory) for lf in linked_files: lf.update() self.overleaf.commit() self.overleaf.push() print("Sync complete.") def delete(self) -> None: """Removes the link between a Gigantum Project from an Overleaf Project Returns: None """ gigaleaf_config_file = Path(self.overleaf.overleaf_config_file) if gigaleaf_config_file.is_file(): print( "Removing integration from Overleaf and Gigantum projects. Please wait..." ) self.overleaf.pull() gigantum_overleaf_dir = Path(self.overleaf.overleaf_repo_directory, 'gigantum') if gigantum_overleaf_dir.is_dir(): # Remove Gigantum dir from Overleaf Project if it exists (maybe you haven't synced yet) shutil.rmtree(gigantum_overleaf_dir.as_posix()) # Commit and Push try: self.overleaf.commit() self.overleaf.push() except ValueError as err: if "Your branch is up to date with 'origin/master'" not in str( err): # If you haven't synced yet, you'll get a git error because removing the dir doesn't actually # change the repository state. If you get any other error, raise. raise # Remove Overleaf Project dir and credentials from Gigantum Project overleaf_root_dir = Path( self.gigantum.get_overleaf_root_directory()) if overleaf_root_dir.is_dir(): shutil.rmtree(overleaf_root_dir.as_posix()) # Remove gigaleaf config file from Gigantum Project & commit. gigaleaf_config_file.unlink() self.gigantum.commit_overleaf_config_file( gigaleaf_config_file.as_posix()) print("Removal complete.") else: print( "gigaleaf has not been configured yet. Skipping removal process." )