예제 #1
0
    def new(self, template_name: str, dest: str = None) -> str:
        """Creates a new file using the specified template.

        The template name will be looked up using :meth:`template_for-name`.

        Raises :exc:`FileNotFoundError` if the template cannot be found.

        If dest is not given, a target file name will be generated.

        The following names are defined in the template's namespace:

        * ``nd``: this instance of :class:`Notesdir`
        * ``directives``: an instance of :class:`notesdir.models.TemplateDirectives`
        * ``template_path``: the :class:`pathlib.Path` of the template being rendered

        Returns the path of the created file.
        """
        template_path = self.template_for_name(template_name)
        if not (template_path and os.path.isfile(template_path)):
            raise FileNotFoundError(f'Template does not exist: {template_name}')
        template = Template(filename=os.path.abspath(template_path))
        td = TemplateDirectives(dest=dest if dest is not None else None)
        content = template.render(nd=self, directives=td, template_path=template_path)
        if not td.dest:
            dirname, basename = os.path.split(template_path)
            name, suffix = basename.split('.', 1)[:2]
            suffix = re.sub(r'[\.^]mako', '', suffix)
            td.dest = f'{name}.{suffix}'
        td.dest = os.path.realpath(td.dest)
        td.dest = find_available_name(td.dest, set())
        edits = [CreateCmd(td.dest, contents=content)]
        self.repo.change(edits)
        return td.dest
예제 #2
0
 def process_fn(src: str):
     dpfn = move_fns[src]
     determinant = dpfn.determinant
     dinfo = info_map.get(determinant, FileInfo(determinant))
     if determinant in move_fns:
         process_fn(determinant)
     if determinant in moves:
         dinfo = replace(dinfo, path=moves[determinant])
     srcdest = dpfn.fn(dinfo)
     del move_fns[src]
     srcdest = find_available_name(srcdest, unavailable, src)
     if src == srcdest:
         return
     moves[src] = srcdest
     unavailable.add(srcdest)
예제 #3
0
    def move(self, moves: Dict[str, str], *, into_dirs=True, check_exists=True,
             create_parents=False, delete_empty_parents=False) -> Dict[str, str]:
        """Moves files/directories and updates references to/from them appropriately.

        moves is a dict where the keys are source paths that should be moved, and the values are the destinations.
        If a destination is a directory and into_dirs is True, the source will be moved into it,
        using the source's filename; otherwise, the source is renamed to the destination.

        This method tries not to overwrite files; if a destination path already exists, a shortened UUID
        will be appended to the path. You can disable that behavior by setting check_exists=False.

        It's OK for a path to occur as a key and also another key's value. For example,
        ``{'foo': 'bar', 'bar': 'foo'}`` will swap the two files.

        If create_parents is True, any directories in a destination path that do not yet exist will be
        created.

        If delete_empty_parents is True, after moving files out of a directory, if the directory or any of its parent
        directories are empty, they will be deleted. (The root folder or current working directory will not be
        deleted regardless.)

        Returns a dict mapping paths of files that were moved, to their final paths.
        """
        moves = {k: v for k, v in moves.items() if not k == v}
        if not moves:
            return {}

        final_moves = {}
        unavailable = set()
        for src, dest in moves.items():
            if not os.path.exists(src):
                raise FileNotFoundError(f'File does not exist: {src}')
            if os.path.isdir(dest) and into_dirs:
                srcname = os.path.split(src)[1]
                dest = os.path.join(dest, srcname)

            dest = find_available_name(dest, unavailable, src) if check_exists else dest
            final_moves[src] = dest
            unavailable.add(dest)

        edits = list(edits_for_rearrange(self.repo, final_moves))
        for edit in edits:
            if isinstance(edit, MoveCmd):
                edit.create_parents = create_parents
                edit.delete_empty_parents = delete_empty_parents
        self.repo.change(edits)

        return final_moves
예제 #4
0
    def organize(self) -> Dict[str, str]:
        """Reorganizes files using the function set in :attr:`notesdir.conf.NotesdirConf.path_organizer`.

        For every file in your note directories (defined by :attr:`notesdir.conf.RepoConf.root_paths`), this
        method will call that function with the file's FileInfo, and move the file to the path the function returns.

        Note that the function will only be called for files, not directories. You cannot directly move a directory
        by this method, but you can effectively move one by moving all the files from it to the same new directory.

        This method deletes any empty directories that result from the moves it makes, and creates any directories
        it needs to.

        The FileInfo is retrieved using :meth:`notesdir.models.FileInfoReq.full`.
        """
        infos = self.repo.query('', FileInfoReq.full())
        moves = {}
        move_fns = {}
        info_map = {}
        unavailable = set()
        for info in infos:
            if not os.path.isfile(info.path):
                continue
            info_map[info.path] = info
            dest = self.conf.path_organizer(info)
            if isinstance(dest, DependentPathFn):
                move_fns[info.path] = dest
            else:
                dest = find_available_name(dest, unavailable, info.path)
                if info.path == dest:
                    continue
                moves[info.path] = dest
                unavailable.add(dest)

        def process_fn(src: str):
            dpfn = move_fns[src]
            determinant = dpfn.determinant
            dinfo = info_map.get(determinant, FileInfo(determinant))
            if determinant in move_fns:
                process_fn(determinant)
            if determinant in moves:
                dinfo = replace(dinfo, path=moves[determinant])
            srcdest = dpfn.fn(dinfo)
            del move_fns[src]
            srcdest = find_available_name(srcdest, unavailable, src)
            if src == srcdest:
                return
            moves[src] = srcdest
            unavailable.add(srcdest)

        while move_fns:
            process_fn(next(iter(move_fns)))

        if not moves:
            return {}

        edits = list(edits_for_rearrange(self.repo, moves))
        for edit in edits:
            if isinstance(edit, MoveCmd):
                edit.create_parents = True
                edit.delete_empty_parents = True
        self.repo.change(edits)

        return moves