Skip to content

Commit

Permalink
[feature] Add template download from got repository (#7)
Browse files Browse the repository at this point in the history
* readme

* add [feature] template from repo

* Tech

* error and debug

* debug

* deleted DEBUG

* add help and libraries

* Working with any template

* add metavars

* [fix] add substitute_yml_plugs method for proper processing of non-template plugs

* [fix] remove unnecessary imports

* [style] adhere to PEP

* [docs] refactor structure

* [release] update changelog and setup for version 1.0.9

* [doc] update cli example

* [fix] decorators for init was in a wrong place

Co-authored-by: Ekaterina.Markova <[email protected]>
Co-authored-by: holamgadol <[email protected]>
  • Loading branch information
3 people authored Aug 15, 2022
1 parent c8952de commit ae0cd12
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 71 deletions.
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ $ pip install foliantcontrib.init

## Usage

Create project from the default “base” template:
You can create a project from the default _base_ template or from a custom template

### Base template

Create project from the default _base_ template:

```shell
$ foliant init
Expand All @@ -24,7 +28,11 @@ Enter the project name: Awesome Docs
Project "Awesome Docs" created in awesome-docs
```

Create project from a custom template:
### Custom template

You can load a custom template from a local path or from a git repo

#### Custom template from a local path

```shell
$ foliant init --template /path/to/custom/template
Expand All @@ -34,6 +42,18 @@ Enter the project name: Awesome Customized Docs
Project "Awesome Customized Docs" created in awesome-customized-docs
```

#### Custom template from a git repository


```shell
$ foliant init --template https://github.com/path/to/custom/template
Enter the project name: Awesome Docs from git
────────────────────
Project "Awesome Docs from git" created in awesome-docs-from-git
```

### Other options

You can provide the project name without user prompt:

```shell
Expand All @@ -54,21 +74,22 @@ To see all available options, run `foliant init --help`:

```shell
$ foliant init --help
usage: foliant init [-h] [-n NAME] [-t NAME or PATH] [-q]
usage: foliant init [-h] [-n NAME] [-t NAME, PATH or git-repo] [-q] [-d]

Generate new Foliant project.
Generate a new Foliant project.

optional arguments:
-h, --help show this help message and exit
-n NAME, --name NAME Name of the Foliant project
-t NAME or PATH, --template NAME or PATH
Name of a built-in project template or path to custom one
-n NAME, --name NAME Name of the Foliant project.
-t NAME, PATH or git-repo, --template NAME, PATH or git-repo
Name of a built-in project template or path to custom one.
-q, --quiet Hide all output accept for the result. Useful for piping.
-d, --debug Log all events during project creation. If not set, only warnings and errors are logged.
```


## Project Templates

A project template is a regular Foliant project but containing placeholders in files. When the project is generated, the placeholders are replaced with the values you provide. Currently, there are two placeholders: `$title` and `$slug`.
A project template is a regular Foliant project but maybe containing placeholders in files. When the project is generated, the placeholders are replaced with the values you provide. Currently, there are two placeholders: `$title` and `$slug`.

There is a built-in template called `base`. It's used by default if no template is specified.
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.0.9

- Add template download from got repository

# 1.0.8

- Add comment to Dockerfile with option to use Foliant full image.
Expand Down
166 changes: 108 additions & 58 deletions foliant/cli/init/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
'''Project generator for Foliant.'''
"""Project generator for Foliant."""

import validators
from git import Repo

from pathlib import Path
from shutil import copytree
from shutil import copytree, rmtree
from functools import reduce
from string import Template
from logging import DEBUG, WARNING
Expand All @@ -16,17 +19,18 @@

from foliant.utils import spinner
from foliant.cli.base import BaseCli
import re


class BuiltinTemplateValidator(Validator):
'''Validator for the interactive template selection prompt.'''
"""Validator for the interactive template selection prompt."""

def __init__(self, builtin_templates: List[str]):
super().__init__()
self.builtin_templates = builtin_templates

def validate(self, document):
'''Check if the selected template exists.'''
"""Check if the selected template exists."""

template = document.text

Expand All @@ -38,19 +42,37 @@ def validate(self, document):
)


def replace_placeholders(path: Path, properties: Dict[str, str]):
'''Replace placeholders in a file with the values from the mapping.'''
class Cli(BaseCli):

def substitute_yml_plugs(self, properties: Dict[str, str], filedata):
"""Substitute non-template (without $) plugs with properties' values in yml."""
for key in properties:
regex = rf"(^ *{key}:.*?) (\w.*)$"
substitute = rf"\1 {properties[key]}"
self.logger.debug(f'substituting {properties[key]} for {key}')
filedata = re.sub(regex, substitute, filedata, 1, re.MULTILINE)
return filedata

with open(path, encoding='utf8') as file:
file_content = Template(file.read())
def replace_placeholders(self, path: Path, properties: Dict[str, str]):
"""Replace placeholders in a file with the values from the mapping."""

with open(path, 'w', encoding='utf8') as file:
file.write(file_content.safe_substitute(properties))
with open(path, encoding='utf8') as file:
file_content = Template(file.read())

with open(path, 'w', encoding='utf8') as file:
file.write(file_content.safe_substitute(properties))

with open(path, 'r', encoding='utf8') as file:
filedata = file.read()

if path.suffix == '.yml':
filedata = self.substitute_yml_plugs(properties, filedata)

with open(path, 'w', encoding='utf8') as file:
file.write(filedata)

class Cli(BaseCli):
@set_arg_map({'project_name': 'name'})
@set_metavars({'project_name': 'NAME', 'template': 'NAME or PATH'})
@set_metavars({'project_name': 'NAME', 'template': 'NAME, PATH or git-repo'})
@set_help(
{
'project_name': 'Name of the Foliant project.',
Expand All @@ -60,49 +82,7 @@ class Cli(BaseCli):
}
)
def init(self, project_name='', template='base', quiet=False, debug=False):
'''Generate new Foliant project.'''

self.logger.setLevel(DEBUG if debug else WARNING)

self.logger.info('Project creation started.')

self.logger.debug(f'Template: {template}')

template_path = Path(template)

if not template_path.exists():
self.logger.debug(
f'Template not found in {template_path}, looking in installed templates.'
)

installed_templates_path = Path(Path(__file__).parent / 'templates')

installed_templates = [
item.name for item in installed_templates_path.iterdir() if item.is_dir()
]

self.logger.debug(f'Available templates: {installed_templates}')

if template in installed_templates:
self.logger.debug('Template found.')

else:
self.logger.debug('Template not found, asking for user input.')

try:
template = prompt(
f'Please pick a template from {installed_templates}: ',
completer=WordCompleter(installed_templates),
validator=BuiltinTemplateValidator(installed_templates)
)

except KeyboardInterrupt:
self.logger.warning('Project creation interrupted.')
return

template_path = installed_templates_path / template

self.logger.debug(f'Template path: {template_path}')
"""Generate a new Foliant project."""

if not project_name:
self.logger.debug('Project name not specified, asking for user input.')
Expand All @@ -126,8 +106,18 @@ def init(self, project_name='', template='base', quiet=False, debug=False):

result = None

with spinner('Generating project', self.logger, quiet, debug):
copytree(template_path, project_path)
path_to_folder = template

self.logger.setLevel(DEBUG if debug else WARNING)

self.logger.info('Project creation started.')

self.logger.debug(f'Template: {template}')

if validators.url(path_to_folder):
Repo.clone_from(path_to_folder, project_path)

rmtree("./"+project_path.__str__()+"/.git")

text_types = '*.md', '*.yml', '*.txt', '*.py'

Expand All @@ -139,14 +129,74 @@ def init(self, project_name='', template='base', quiet=False, debug=False):

for text_file_path in text_file_paths:
self.logger.debug(f'Processing content of {text_file_path}')
replace_placeholders(text_file_path, properties)
self.replace_placeholders(text_file_path, properties)

for item in project_path.rglob('*'):
self.logger.debug(f'Processing name of {item}')
item.rename(Template(item.as_posix()).safe_substitute(properties))

result = project_path

else:
self.logger.info("The path to the template is not a url, or incorrect url address to the git repository")

template_path = Path(template)

if not template_path.exists():
self.logger.debug(
f'Template not found in {template_path}, looking in installed templates.'
)

installed_templates_path = Path(Path(__file__).parent / 'templates')

installed_templates = [
item.name for item in installed_templates_path.iterdir() if item.is_dir()
]

self.logger.debug(f'Available templates: {installed_templates}')

if template in installed_templates:
self.logger.debug('Template found.')

else:
self.logger.debug('Template not found, asking for user input.')

try:
template = prompt(
f'Please pick a template from {installed_templates}: ',
completer=WordCompleter(installed_templates),
validator=BuiltinTemplateValidator(installed_templates)
)

except KeyboardInterrupt:
self.logger.warning('Project creation interrupted.')
return

template_path = installed_templates_path / template

self.logger.debug(f'Template path: {template_path}')

with spinner('Generating project', self.logger, quiet, debug):
copytree(template_path, project_path)

text_types = '*.md', '*.yml', '*.txt', '*.py'

text_file_paths = reduce(
lambda acc, matches: acc + [*matches],
(project_path.rglob(text_type) for text_type in text_types),
[]
)

for text_file_path in text_file_paths:
self.logger.debug(f'Processing content of {text_file_path}')
self.replace_placeholders(text_file_path, properties)

for item in project_path.rglob('*'):
self.logger.debug(f'Processing name of {item}')
item.rename(Template(item.as_posix()).safe_substitute(properties))

result = project_path

if result:
self.logger.info(f'Result: {result}')

Expand Down
15 changes: 10 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from setuptools import setup, find_packages
from setuptools import setup
from pathlib import Path
from typing import List

Expand All @@ -11,11 +11,12 @@
except FileNotFoundError:
LONG_DESCRIPTION = SHORT_DESCRIPTION


def get_templates(path: Path) -> List[str]:
'''List all files in ``templates`` directory, including all subdirectories.
"""List all files in ``templates`` directory, including all subdirectories.
The resulting list contains UNIX-like relative paths starting with ``templates``.
'''
"""

result = []

Expand All @@ -25,12 +26,13 @@ def get_templates(path: Path) -> List[str]:

return result


setup(
name='foliantcontrib.init',
description=SHORT_DESCRIPTION,
long_description=LONG_DESCRIPTION,
long_description_content_type='text/markdown',
version='1.0.8',
version='1.0.9',
author='Konstantin Molchanov',
author_email='[email protected]',
url='https://github.com/foliant-docs/foliantcontrib.init',
Expand All @@ -40,7 +42,10 @@ def get_templates(path: Path) -> List[str]:
platforms='any',
install_requires=[
'foliant>=1.0.8',
'python-slugify'
'python-slugify',
'validators',
'gitpython',
'cliar'
],
classifiers=[
'Development Status :: 5 - Production/Stable',
Expand Down

0 comments on commit ae0cd12

Please sign in to comment.