Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
fabien-marty committed Jan 27, 2024
1 parent c42703a commit 93e1ce9
Show file tree
Hide file tree
Showing 16 changed files with 156 additions and 45 deletions.
36 changes: 27 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ recursively in a directory tree.

It is very configurable and very easy to extend with its included plugin system.

The default behavior is to recursively search for files with a given extension (`.template` for example) and to process the context with [jinja (jinja2)](https://jinja.palletsprojects.com/) templates engine reading the context variables from:
The default behavior is to recursively search for files with a given extension (`.template` for example) and to process the context with [Jinja (Jinja2)](https://jinja.palletsprojects.com/) templates engine reading the context variables from:

- a configuration file
- environment variables
Expand All @@ -23,7 +23,7 @@ Then, the processed content is written into another file with the same name/path
## What's it for?

Your imagination is your limit 😅 but it's very useful for maintaining DRY documentation (for example: `your-cli --help` output automatically updated in a markdown file), configuration files with default values read in code, included common blocks in different files...
Your imagination is your limit 😅 but it's very useful for maintaining DRY documentation (for example: `your-cli --help` output automatically updated in a markdown file), configuration files with default values read in code, including common blocks in different files...

**So it's a great tool for maintaining repositories in general.**

Expand All @@ -33,30 +33,50 @@ Your imagination is your limit 😅 but it's very useful for maintaining DRY doc
> - [getting `jinja-tree --help` output automatically added (and updated) in this README](docs/fixme)
> - [getting a reference TOML configuration file rendered with defaults values read from code](docs/fixme)
> [!NOTE]
> Another "action" plugin will be soon 🕒 provided to bootstrap directory trees from templates (like with the [cookiecutter](https://github.com/cookiecutter/cookiecutter) project).
## Features

#### 1️⃣ Easy to extend

`jinja-tree` includes a plugin system. You can override the default behavior with your own plugins.

There is two extension points:
There are two extension points:

- context plugins: to provide context variables to jinja templates
- file plugins: to change the way how `jinja-tree` find files to process (including target files)
- context plugins: to provide context variables to Jinja templates
- file plugins: to change the way how `jinja-tree` finds files to process (including target files)

See [this specification documentation page](docs/fixme) for more details.

#### 2️⃣ Very configurable

`jinja-tree` is very configurable. You can configure global options via CLI options or a configuration file.

Plugins are configurable via the configuration file.

See [this specification documentation page](docs/fixme) for more details.

#### 3️⃣ Embedded extensions

`jinja-tree` includes some extensions to Jinja templates engine:

- [to execute some commands (and get the corresponding output)](jinja_tree/app/embedded_extensions/shell.py)
- [to parse JSON strings into Python objects)](jinja_tree/app/embedded_extensions/from_json.py)
- ..

See [this directory](jinja_tree/app/embedded_extensions/) for others

#### 4️⃣ Full Jinja / Jinja2 support (including "includes" and "inheritance")

`jinja-tree` has several options about Jinja "search paths". So you can use Jinja "includes" and "inheritance" features.

## Installation

`pip install jinja-tree`

A docker image will also be available soon 🕒
> [!NOTE]
> A docker image will also be available soon 🕒
## CLI options

Expand All @@ -82,15 +102,13 @@ Options:
add root directory to jinja search path
--jinja-extension TEXT jinja extension to load
--context-plugin TEXT context plugin (full python class path)
--action-plugin TEXT file-action plugin (full python class path)
--action-plugin TEXT action plugin (full python class path)
--strict-undefined / --no-strict-undefined
if set, raise an error if a variable does
not exist in context
--blank-run / --no-blank-run if set, execute a blank run (without
modifying or deleting anything) [default:
no-blank-run]
--delete-original / --no-delete-original
delete original file
--disable-embedded-jinja-extensions / --no-disable-embedded-jinja-extensions
disable embedded jinja extensions
--help Show this message and exit.
Expand Down
32 changes: 26 additions & 6 deletions README.md.template
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ recursively in a directory tree.

It is very configurable and very easy to extend with its included plugin system.

The default behavior is to recursively search for files with a given extension (`.template` for example) and to process the context with [jinja (jinja2)](https://jinja.palletsprojects.com/) templates engine reading the context variables from:
The default behavior is to recursively search for files with a given extension (`.template` for example) and to process the context with [Jinja (Jinja2)](https://jinja.palletsprojects.com/) templates engine reading the context variables from:

- a configuration file
- environment variables
Expand All @@ -22,7 +22,7 @@ Then, the processed content is written into another file with the same name/path

## What's it for?

Your imagination is your limit 😅 but it's very useful for maintaining DRY documentation (for example: `your-cli --help` output automatically updated in a markdown file), configuration files with default values read in code, included common blocks in different files...
Your imagination is your limit 😅 but it's very useful for maintaining DRY documentation (for example: `your-cli --help` output automatically updated in a markdown file), configuration files with default values read in code, including common blocks in different files...

**So it's a great tool for maintaining repositories in general.**

Expand All @@ -32,30 +32,50 @@ Your imagination is your limit 😅 but it's very useful for maintaining DRY doc
> - [getting `jinja-tree --help` output automatically added (and updated) in this README](docs/fixme)
> - [getting a reference TOML configuration file rendered with defaults values read from code](docs/fixme)

> [!NOTE]
> Another "action" plugin will be soon 🕒 provided to bootstrap directory trees from templates (like with the [cookiecutter](https://github.com/cookiecutter/cookiecutter) project).

## Features

#### 1️⃣ Easy to extend

`jinja-tree` includes a plugin system. You can override the default behavior with your own plugins.

There is two extension points:
There are two extension points:

- context plugins: to provide context variables to jinja templates
- file plugins: to change the way how `jinja-tree` find files to process (including target files)
- context plugins: to provide context variables to Jinja templates
- file plugins: to change the way how `jinja-tree` finds files to process (including target files)

See [this specification documentation page](docs/fixme) for more details.

#### 2️⃣ Very configurable

`jinja-tree` is very configurable. You can configure global options via CLI options or a configuration file.

Plugins are configurable via the configuration file.

See [this specification documentation page](docs/fixme) for more details.

#### 3️⃣ Embedded extensions

`jinja-tree` includes some extensions to Jinja templates engine:

- [to execute some commands (and get the corresponding output)](jinja_tree/app/embedded_extensions/shell.py)
- [to parse JSON strings into Python objects)](jinja_tree/app/embedded_extensions/from_json.py)
- ..

See [this directory](jinja_tree/app/embedded_extensions/) for others

#### 4️⃣ Full Jinja / Jinja2 support (including "includes" and "inheritance")

`jinja-tree` has several options about Jinja "search paths". So you can use Jinja "includes" and "inheritance" features.

## Installation

`pip install jinja-tree`

A docker image will also be available soon 🕒
> [!NOTE]
> A docker image will also be available soon 🕒

## CLI options

Expand Down
5 changes: 5 additions & 0 deletions docs/details-about-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Details about configuration

FIXME

Go back to [../README.md](main README.md) file.
15 changes: 9 additions & 6 deletions docs/jinja-tree.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,12 @@ change_cwd = true
# Crash when templates use undefined variables (if true)
strict_undefined = true

# Delete original (template) file after processing (if true)
delete_original = false

# Disable embedded jinja extensions (if true)
# List of embedded jinja extensions (for information only):
# - jinja_tree.app.embedded_extensions.from_json.FromJsonExtension
# - jinja_tree.app.embedded_extensions.shell.ShellExtension
# - jinja_tree.app.embedded_extensions.fnmatch.FnMatchExtension
# - jinja_tree.app.embedded_extensions.double_quotes.DoubleQuotesExtension

disable_embedded_jinja_extensions = false

# Jinja extensions to add (full paths)
Expand Down Expand Up @@ -83,18 +79,25 @@ plugin_configuration_ignores = ["plugin", "generated_comment_line1", "generated_
########################################
[action]

# Plugin full classpath [common key for all file-action plugins]
# Plugin full classpath [common key for all action plugins]
plugin = "jinja_tree.infra.adapters.action.ExtensionsFileActionAdapter"

# File extensions to process [specific to ExtensionsFileActionAdapter plugin]
# Example: [".j2", ".jinja2", ".template"] for processing all files ending with .j2, .jinja2 or .template
extensions = [".template"]

# Filename patterns to ignore (fnmatch patterns to match against basename only)
# [specific to ExtensionsFileActionAdapter plugin]
filename_ignores = [ ".*" ]

# Dirname patterns to ignore recursively (fnmatch patterns to match against dirname only)
# [specific to ExtensionsFileActionAdapter plugin]
dirname_ignores = [ "venv", "site-packages", "__pypackages__", "node_modules", "__pycache__", ".*" ]

# Replace target files if they already exist (if true)
replace = true
# [specific to ExtensionsFileActionAdapter plugin]
replace = true

# Delete original (template) file after processing (if true)
# [specific to ExtensionsFileActionAdapter plugin]
delete_original = false
16 changes: 10 additions & 6 deletions docs/jinja-tree.toml.template
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,11 @@ change_cwd = {{ default_config.change_cwd|lower }}
# Crash when templates use undefined variables (if true)
strict_undefined = {{ default_config.strict_undefined|lower }}

# Delete original (template) file after processing (if true)
delete_original = {{ default_config.delete_original|lower }}

# Disable embedded jinja extensions (if true)
# List of embedded jinja extensions (for information only):
{% for x in embedded_jinja_extensions -%}
# - {{x}}
{% endfor %}
{% endfor -%}
disable_embedded_jinja_extensions = {{ default_config.disable_embedded_jinja_extensions|lower }}

# Jinja extensions to add (full paths)
Expand Down Expand Up @@ -81,18 +78,25 @@ plugin_configuration_ignores = [{{ default_config.context_plugin_config.plugin_c
########################################
[action]

# Plugin full classpath [common key for all file-action plugins]
# Plugin full classpath [common key for all action plugins]
plugin = "{{default_config.action_plugin_config.plugin}}"

# File extensions to process [specific to ExtensionsFileActionAdapter plugin]
# Example: [".j2", ".jinja2", ".template"] for processing all files ending with .j2, .jinja2 or .template
extensions = [{{ default_config.action_plugin_config.extensions|map('double_quotes')|join(', ') }}]

# Filename patterns to ignore (fnmatch patterns to match against basename only)
# [specific to ExtensionsFileActionAdapter plugin]
filename_ignores = [ {{ filename_ignores_default|map('double_quotes')|join(', ') }} ]

# Dirname patterns to ignore recursively (fnmatch patterns to match against dirname only)
# [specific to ExtensionsFileActionAdapter plugin]
dirname_ignores = [ {{ dirname_ignores_default|map('double_quotes')|join(', ') }} ]

# Replace target files if they already exist (if true)
replace = {{ default_config.action_plugin_config.replace|lower }}
# [specific to ExtensionsFileActionAdapter plugin]
replace = {{ default_config.action_plugin_config.replace|lower }}

# Delete original (template) file after processing (if true)
# [specific to ExtensionsFileActionAdapter plugin]
delete_original = {{ default_config.action_plugin_config.delete_original|lower }}
3 changes: 2 additions & 1 deletion jinja_tree/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
DOTENV_PATH_DEFAULT = ".env"
FILE_ACTION_PLUGIN_DEFAULT_EXTENSIONS = [".template"]
REPLACE_DEFAULT = True
DELETE_ORIGINAL_DEFAULT = False

EMBEDDED_EXTENSIONS = [
"jinja_tree.app.embedded_extensions.from_json.FromJsonExtension",
Expand Down Expand Up @@ -53,6 +54,7 @@ def make_default_action_plugin_config() -> Dict[str, Any]:
"filename_ignores": FILENAME_IGNORES_DEFAULT,
"dirname_ignores": DIRNAME_IGNORES_DEFAULT,
"replace": REPLACE_DEFAULT,
"delete_original": DELETE_ORIGINAL_DEFAULT,
}


Expand All @@ -67,7 +69,6 @@ class Config:
jinja_extensions: List[str] = field(default_factory=list)
strict_undefined: bool = True
root_dir: str = field(default_factory=os.getcwd)
delete_original: bool = False
disable_embedded_jinja_extensions: bool = False

# Plugin config
Expand Down
9 changes: 9 additions & 0 deletions jinja_tree/app/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ def add_extra_keys_to_context(
)

def get_context(self, absolute_path: Optional[str] = None) -> Dict[str, Any]:
"""
Retrieve the Jinja context to apply.
Note: it can depends on the current working directory (CWD).
Returns:
The context dictionary.
"""
res = self.adapter.get_context()
self.add_extra_keys_to_context(res, absolute_path=absolute_path)
return res
9 changes: 9 additions & 0 deletions jinja_tree/app/embedded_extensions/double_quotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ def double_quotes(eval_ctx, value):


class DoubleQuotesExtension(Extension):
"""Jinja2 extension to add double quotes around a string.
Example:
{{ 'foo-bar'|double_quotes() }}
=> "foo-bar"
"""

def __init__(self, environment):
super().__init__(environment)
environment.filters["double_quotes"] = double_quotes
9 changes: 9 additions & 0 deletions jinja_tree/app/embedded_extensions/fnmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ def _fnmatch(eval_ctx, value, pattern):


class FnMatchExtension(Extension):
"""Jinja2 extension to provide a fnmatch filter.
Example:
{{ 'foo-bar'|fnmatch('foo-*') }}
=> True
"""

def __init__(self, environment):
super().__init__(environment)
environment.filters["fnmatch"] = _fnmatch
2 changes: 2 additions & 0 deletions jinja_tree/app/embedded_extensions/from_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ def from_json(eval_ctx, value):


class FromJsonExtension(Extension):
"""Jinja2 extension to load a JSON string into a Python object."""

def __init__(self, environment):
super().__init__(environment)
environment.filters["from_json"] = from_json
12 changes: 12 additions & 0 deletions jinja_tree/app/embedded_extensions/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ def shell(eval_ctx, value, die_on_error=True, encoding="utf8", **kwargs):


class ShellExtension(Extension):
"""Jinja2 extension to execute shell commands.
Example:
{{ "date"|shell() }}
=> "Thu 7 Nov 2019 15:55:01 CET"
WARNING: be sure to valid any string submitted to this filter as
you can open security holes with it
"""

def __init__(self, environment):
super().__init__(environment)
environment.filters["shell"] = shell
6 changes: 5 additions & 1 deletion jinja_tree/infra/adapters/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
ProcessFileAction,
)
from jinja_tree.app.config import (
DELETE_ORIGINAL_DEFAULT,
DIRNAME_IGNORES_DEFAULT,
FILE_ACTION_PLUGIN_DEFAULT_EXTENSIONS,
FILENAME_IGNORES_DEFAULT,
Expand All @@ -39,6 +40,9 @@ def __init__(self, config: Config):
"dirname_ignores", DIRNAME_IGNORES_DEFAULT
)
self.replace = config.action_plugin_config.get("replace", REPLACE_DEFAULT)
self.delete_original = config.action_plugin_config.get(
"delete_original", DELETE_ORIGINAL_DEFAULT
)

def get_file_action(self, absolute_path: str) -> FileAction:
if is_fnmatch_ignored(os.path.basename(absolute_path), self.filename_ignores):
Expand Down Expand Up @@ -68,7 +72,7 @@ def get_file_action(self, absolute_path: str) -> FileAction:
return ProcessFileAction(
source_absolute_path=absolute_path,
target_absolute_path=target_absolute_path,
delete_original=self.config.delete_original,
delete_original=self.delete_original,
)

def get_directory_action(self, absolute_path: str) -> DirectoryAction:
Expand Down
Loading

0 comments on commit 93e1ce9

Please sign in to comment.