Ejemplo n.º 1
0
    def _init_config(self) -> None:
        """Private method to configure an overleaf integration

        Returns:
            None
        """
        # Prompt for overleaf project URL
        intro_message = Path(
            Path(__file__).parent.absolute(), 'resources',
            'intro_message.txt').read_text()
        print(intro_message)

        project_url = input("Overleaf Git url: ").strip()

        # Handle if the user passed in the link or the whole git command that Overleaf displays
        idx = project_url.find('git.overleaf.com')
        if idx == -1:
            raise ValueError(
                "Overleaf Git URL is malformed. Should be like: https://git.overleaf.com/xxxxxxxxxxxxx"
            )
        else:
            project_url = 'https://' + project_url[idx:].split(maxsplit=1)[0]

        # Prompt for email and password
        self._init_creds()

        # Write overleaf config file
        config = {
            "overleaf_git_url": project_url,
            "gigaleaf_version": gigaleaf_version
        }
        with open(self.overleaf_config_file, 'wt') as cf:
            json.dump(config, cf)

        # Commit the config file
        Gigantum.commit_overleaf_config_file(self.overleaf_config_file)
Ejemplo n.º 2
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."
            )
Ejemplo n.º 3
0
def gigantum_project_fixture():
    unit_test_working_dir = os.path.join(tempfile.gettempdir(),
                                         uuid.uuid4().hex)
    os.makedirs(unit_test_working_dir)

    test_dir = pathlib.Path(__file__).parent.absolute()
    test_project_path = os.path.join(test_dir, 'resources',
                                     'example_project.zip')
    secret_file_path = os.path.join(test_dir, 'resources', 'secrets.json')
    with zipfile.ZipFile(test_project_path, 'r') as zip_ref:
        zip_ref.extractall(unit_test_working_dir)

    # Set the working dir to INSIDE the project
    unit_test_working_dir = os.path.join(unit_test_working_dir,
                                         'overleaf-test-project')

    with patch.object(Gigantum, "get_project_root") as patched_gigantum:
        patched_gigantum.return_value = unit_test_working_dir

        os.chdir(os.path.join(unit_test_working_dir, 'code'))

        with open(secret_file_path, 'rt') as sf:
            secrets = json.load(sf)

        # Configure overleaf config file and secrets
        config_file_path = os.path.join(unit_test_working_dir, '.gigantum',
                                        'overleaf.json')
        config = {
            "overleaf_git_url": secrets['git_url'],
            "gigaleaf_version": "0.1.0"
        }
        with open(config_file_path, 'wt') as cf:
            json.dump(config, cf)

        Gigantum.commit_overleaf_config_file(config_file_path)

        # Write credential file
        creds = {"email": secrets['email'], "password": secrets['password']}
        overleaf_dir = os.path.join(unit_test_working_dir,
                                    'output/untracked/overleaf')
        os.makedirs(overleaf_dir)
        with open(os.path.join(overleaf_dir, 'credentials.json'), 'wt') as cf:
            json.dump(creds, cf)

        # Yield and run test
        yield unit_test_working_dir

    # Clean up overleaf if it was set up
    overleaf_project_dir = os.path.join(overleaf_dir, 'project')
    gigantum_overleaf_dir = os.path.join(overleaf_project_dir, 'gigantum')
    if os.path.isdir(gigantum_overleaf_dir):
        # Remove gigantum dir in the project IN overleaf
        shutil.rmtree(gigantum_overleaf_dir)
        git_status = call_subprocess(['git', 'status'],
                                     overleaf_project_dir,
                                     check=True)
        if "nothing to commit, working tree clean" not in git_status:
            call_subprocess(['git', 'add', '-A'],
                            overleaf_project_dir,
                            check=True)
            call_subprocess(
                ['git', 'commit', '-m', 'Cleaning up integration test'],
                overleaf_project_dir,
                check=True)

            call_subprocess(['git', 'push'], overleaf_project_dir, check=True)

    # Clean up test project
    shutil.rmtree(unit_test_working_dir)