Directories

This document explains how to render directories (i.e. multiple files) using diecutter web API. See also Files about how to render single files.

Here is service’s API overview:

  • GET: list files in directory.
  • POST: render templates in directory against context, as an archive.
  • PUT: directories are automatically created when you PUT a file in it.
  • other HTTP verbs aren’t implemented yet.

Note

In the examples below, let’s communicate with a diecutter server using Python requests.

This diecutter serves diecutter’s demo templates.

The diecutter_url variable holds root URL of diecutter service, i.e. something like http://diecutter.io/api/.

PUT

In order to create a directory, PUT a file in it.

As an example, let’s create some “greetings” directory with “hello.txt” and “goodbye.txt” inside:

>>> name = 'greetings/hello.txt'
>>> files = {'file': ('client-side-name', 'Hello {{ name }}\n')}
>>> response = requests.put(diecutter_url + name, files=files)
>>> response.status_code
201
>>> name = 'greetings/goodbye.txt'
>>> files = {'file': ('client-side-name', 'Goodbye {{ name }}\n')}
>>> response = requests.put(diecutter_url + name, files=files)
>>> response.status_code
201

There is currently no way to create an empty directory. And no way to upload multiple files at once.

GET

GET lists files in directory, recursively:

>>> response = requests.get(diecutter_url + 'greetings/')
>>> response.status_code
200
>>> print response.content
goodbye.txt
hello.txt

Notice that, if you don’t set the trailing slash, you get the same list with folder name as prefix:

>>> response = requests.get(diecutter_url + 'greetings')
>>> response.status_code
200
>>> print response.content
greetings/goodbye.txt
greetings/hello.txt

POST

POST renders directory against data, and the result is an archive.

>>> response = requests.post(
...     diecutter_url + 'greetings/',
...     data={'name': 'Remy'})
>>> response.status_code
200
>>> archive = tarfile.open(fileobj=StringIO(response.content))
>>> print archive.getnames()
['goodbye.txt', 'hello.txt']
>>> print archive.extractfile('hello.txt').read()
Hello Remy
>>> print archive.extractfile('goodbye.txt').read()
Goodbye Remy

By default, archives are in tar.gz format. See accept header below for alternatives.

Rendering only parts of a directory

You can render only parts of a directory! It means you can render sub-directories or single files:

>>> response = requests.post(
...     diecutter_url + 'greetings/hello.txt',
...     data={'name': 'world'})
>>> print response.content
Hello world

This is because diecutter handles templates just as files and directories, in any filesystem it can read. If it can read a directory or a file, they can be used as a template, and so can be the sub-directories and files. diecutter does not need you to register templates, it only needs to be able to read the “filesystem” (repositories are kind of filesystems too) holding templates.

Dynamic trees

By default, all files in directory are rendered, and the context data does not vary. See Dynamic directory trees templates if you need to dynamically alter the list of files or the context data.

Trailing slash

Like with GET, the trailing slash affects filenames: without trailing slash, filenames are prefixed with directory name.

>>> response = requests.post(
...     diecutter_url + 'greetings',
...     data={'name': 'Remy'})
>>> archive = tarfile.open(fileobj=StringIO(response.content))
>>> print archive.getnames()
['greetings/goodbye.txt', 'greetings/hello.txt']

In the archive, dates reflect generation time:

>>> time_floor = int(time.time())  # Time before generation.
>>> response = requests.post(
...     diecutter_url + 'greetings/',
...     data={'name': 'Remy'})
>>> archive = tarfile.open(fileobj=StringIO(response.content))
>>> time_ceil = time.time()  # Time after generation.
>>> info = archive.getmember('hello.txt')
>>> info.mtime != 0
True
>>> info.mtime >= time_floor
True
>>> info.mtime <= time_ceil
True

accept header

By default, diecutter returns TAR.GZ archives. You can get the content as a ZIP archive using the HTTP accept header:

>>> response = requests.post(
...     diecutter_url + 'greetings/',
...     data={'name': 'Remy'},
...     headers={'accept': 'application/zip'})
>>> import zipfile
>>> zip_filename = os.path.join(diecutter_template_dir, 'response.zip')
>>> open(zip_filename, 'w').write(response.content)
>>> zipfile.is_zipfile(zip_filename)  # File is actually a ZIP.
True
>>> archive = zipfile.ZipFile(zip_filename)
>>> archive.testzip() is None  # ZIP integrity is OK.
True
>>> print archive.namelist()
['goodbye.txt', 'hello.txt']
>>> print archive.read('hello.txt')
Hello Remy

Supported archive formats

You can see all supported “accept” headers by requesting an unknown mime type:

$ curl -X POST --header "accept:fake/mime-type" -d name="world" http://localhost:8106/greetings
406 Not Acceptable

The server could not comply with the request since it is either malformed or
otherwise incorrect.


Supported mime types: */*, application/gzip, application/x-gzip,
application/zip