Beispiel #1
0
    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()
Beispiel #2
0
    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)
Beispiel #3
0
    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
Beispiel #4
0
    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
Beispiel #5
0
    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
Beispiel #6
0
    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
Beispiel #7
0
    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()
Beispiel #8
0
    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
Beispiel #9
0
    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()
Beispiel #10
0
    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']
Beispiel #11
0
    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()
Beispiel #12
0
    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'
Beispiel #13
0
    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)
Beispiel #14
0
    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)
Beispiel #15
0
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."
            )