conner.xyz
conner.xyz

Reputation: 7295

How to get cookiecutter to output to current working directory instead of nested directory?

We have a number of use cases where we'd like to be able to call cookiecutter and have the files and directories generated get put into the current working directory.

Simple example

What is essentially desired.

$ cd path/to/repo
$ ls
a.txt
$ cookiecutter path/to/template
$ls
a.txt b.txt

What the result actually is

$ cd path/to/repo
$ ls
a.txt
$ cookiecutter path/to/template
$ls
a.txt 
template/
  b.txt

Things I've tried

  1. You could imagine the template being something like this.
template/
  cookiecutter.json
  b.txt

But cookiecutter appears to require there to be a template root by default to avoid cookiecutter.exceptions.NonTemplatedInputDirException.

  1. So, add a directory to contain the template files and try and output it to a particular directory.
template/
  cookiecutter.json
  {{cookiecutter.name}}

But then the template files are just nested in a subdirectory of the current working directory like shown above of course.

  1. I also expect one could use a post-generate hook to move the template's output files to where you want, but that seems like a bit of a hack...

I keep seeing comments on GitHub saying basically "this is solved", but I can't seem to find documentation on how to use whatever feature was added to accommodate this use case.

Upvotes: 4

Views: 4159

Answers (2)

Ben Jeffrey
Ben Jeffrey

Reputation: 951

Alternatively to the previous answer, which should work perfectly on linux/mac, here is a solution in pure python

hooks/post_gen_project.py:

import os
import shutil
from pathlib import Path, PurePath
from tempfile import TemporaryDirectory


def _move_single_file(src_dir: PurePath, dst_dir: PurePath, file_name: str):
    shutil.move(
        str(src_dir.joinpath(file_name)),
        dst_dir.joinpath(file_name),
        copy_function=lambda x, y: shutil.copytree(
            x, y, dirs_exist_ok=True, copy_function=shutil.copy2
        ),
    )


def move_directory_contents(src: PurePath, dst: PurePath):
    temp_dir = TemporaryDirectory()
    temp_dir_path = Path(temp_dir.name)

    directory_contents = os.listdir(src)
    for item in directory_contents:
        print(f"Moving {item} to {temp_dir_path}")
        _move_single_file(src, temp_dir_path, item)

    directory_contents.remove(src.name)

    for item in directory_contents:
        print(f"Moving {item} to {dst}")
        _move_single_file(temp_dir_path, dst, item)

    os.removedirs(src)

    _move_single_file(temp_dir_path, dst, src.name)


if __name__ == "__main__":
    if "{{ cookiecutter.use_current_directory }}".lower() == "y":
        src = Path.cwd()
        assert src.name == "{{ cookiecutter.project_slug }}"
        move_directory_contents(src, src.parent)

and the cookiecutter.json will need to have this line:

{
  "use_current_directory": "y/n",
}

This should work on any OS and only requires the hooks/post_gen_project.py

Upvotes: 0

wankata
wankata

Reputation: 989

The issue is closed, but this doesn't mean it is solved.

The only way to do this is with post-generate hook moving the files out of your folder and deleting the folder.

If you are on a *NIX system, you can use the following simple script:

#!/bin/sh

mv * ..
rm -rfv ../{{ cookiecutter.project_name }}

Where cookiecutter.project_name is your template folder.

Upvotes: 7

Related Questions