mirror of
https://github.com/django-components/django-components.git
synced 2025-11-26 00:41:54 +00:00
feat: allow extensions to add commands (#1017)
* feat: allow extensions to add commands * refactor: fix tests * refactor: more test fix * refactor: more test fixes * refactor: more linter fixes
This commit is contained in:
parent
3a139127cd
commit
d3d2d0ab08
28 changed files with 2320 additions and 397 deletions
|
|
@ -4,6 +4,7 @@ Django-components functionality can be extended with "extensions". Extensions al
|
|||
|
||||
- Tap into lifecycle events, such as when a component is created, deleted, registered, or unregistered.
|
||||
- Add new attributes and methods to the components under an extension-specific nested class.
|
||||
- Define custom commands that can be executed via the Django management command interface.
|
||||
|
||||
## Setting up extensions
|
||||
|
||||
|
|
@ -328,3 +329,286 @@ class MyComponent(Component):
|
|||
```
|
||||
|
||||
This will log the component name and color when the component is created, deleted, or rendered.
|
||||
|
||||
## Extension Commands
|
||||
|
||||
Extensions in django-components can define custom commands that can be executed via the Django management command interface. This allows for powerful automation and customization capabilities.
|
||||
|
||||
For example, if you have an extension that defines a command that prints "Hello world", you can run the command with:
|
||||
|
||||
```bash
|
||||
python manage.py components ext run my_ext hello
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `python manage.py components ext run` - is the Django command run
|
||||
- `my_ext` - is the extension name
|
||||
- `hello` - is the command name
|
||||
|
||||
### Defining Commands
|
||||
|
||||
To define a command, subclass from [`ComponentCommand`](../../../reference/api#django_components.ComponentCommand).
|
||||
This subclass should define:
|
||||
|
||||
- `name` - the command's name
|
||||
- `help` - the command's help text
|
||||
- `handle` - the logic to execute when the command is run
|
||||
|
||||
```python
|
||||
from django_components import ComponentCommand, ComponentsExtension
|
||||
|
||||
class HelloCommand(ComponentCommand):
|
||||
name = "hello"
|
||||
help = "Say hello"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
print("Hello, world!")
|
||||
|
||||
class MyExt(ComponentsExtension):
|
||||
name = "my_ext"
|
||||
commands = [HelloCommand]
|
||||
```
|
||||
|
||||
### Defining Command Arguments and Options
|
||||
|
||||
Commands can accept positional arguments and options (e.g. `--foo`), which are defined using the
|
||||
[`arguments`](../../../reference/api#django_components.ComponentCommand.arguments)
|
||||
attribute of the [`ComponentCommand`](../../../reference/api#django_components.ComponentCommand) class.
|
||||
|
||||
The arguments are parsed with [`argparse`](https://docs.python.org/3/library/argparse.html)
|
||||
into a dictionary of arguments and options. These are then available
|
||||
as keyword arguments to the [`handle`](../../../reference/api#django_components.ComponentCommand.handle)
|
||||
method of the command.
|
||||
|
||||
```python
|
||||
from django_components import CommandArg, ComponentCommand, ComponentsExtension
|
||||
|
||||
class HelloCommand(ComponentCommand):
|
||||
name = "hello"
|
||||
help = "Say hello"
|
||||
|
||||
arguments = [
|
||||
# Positional argument
|
||||
CommandArg(
|
||||
name_or_flags="name",
|
||||
help="The name to say hello to",
|
||||
),
|
||||
# Optional argument
|
||||
CommandArg(
|
||||
name_or_flags=["--shout", "-s"],
|
||||
action="store_true",
|
||||
help="Shout the hello",
|
||||
),
|
||||
]
|
||||
|
||||
def handle(self, name: str, *args, **kwargs):
|
||||
shout = kwargs.get("shout", False)
|
||||
msg = f"Hello, {name}!"
|
||||
if shout:
|
||||
msg = msg.upper()
|
||||
print(msg)
|
||||
```
|
||||
|
||||
You can run the command with arguments and options:
|
||||
|
||||
```bash
|
||||
python manage.py components ext run my_ext hello John --shout
|
||||
>>> HELLO, JOHN!
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Command definitions are parsed with `argparse`, so you can use all the features of `argparse` to define your arguments and options.
|
||||
|
||||
See the [argparse documentation](https://docs.python.org/3/library/argparse.html) for more information.
|
||||
|
||||
django-components defines types as
|
||||
[`CommandArg`](../../../reference/api#django_components.CommandArg),
|
||||
[`CommandArgGroup`](../../../reference/api#django_components.CommandArgGroup),
|
||||
[`CommandSubcommand`](../../../reference/api#django_components.CommandSubcommand),
|
||||
and [`CommandParserInput`](../../../reference/api#django_components.CommandParserInput)
|
||||
to help with type checking.
|
||||
|
||||
!!! note
|
||||
|
||||
If a command doesn't have the [`handle`](../../../reference/api#django_components.ComponentCommand.handle)
|
||||
method defined, the command will print a help message and exit.
|
||||
|
||||
### Grouping Arguments
|
||||
|
||||
Arguments can be grouped using [`CommandArgGroup`](../../../reference/api#django_components.CommandArgGroup)
|
||||
to provide better organization and help messages.
|
||||
|
||||
Read more on [argparse argument groups](https://docs.python.org/3/library/argparse.html#argument-groups).
|
||||
|
||||
```python
|
||||
from django_components import CommandArg, CommandArgGroup, ComponentCommand, ComponentsExtension
|
||||
|
||||
class HelloCommand(ComponentCommand):
|
||||
name = "hello"
|
||||
help = "Say hello"
|
||||
|
||||
# Argument parsing is managed by `argparse`.
|
||||
arguments = [
|
||||
# Positional argument
|
||||
CommandArg(
|
||||
name_or_flags="name",
|
||||
help="The name to say hello to",
|
||||
),
|
||||
# Optional argument
|
||||
CommandArg(
|
||||
name_or_flags=["--shout", "-s"],
|
||||
action="store_true",
|
||||
help="Shout the hello",
|
||||
),
|
||||
# When printing the command help message, `--bar` and `--baz`
|
||||
# will be grouped under "group bar".
|
||||
CommandArgGroup(
|
||||
title="group bar",
|
||||
description="Group description.",
|
||||
arguments=[
|
||||
CommandArg(
|
||||
name_or_flags="--bar",
|
||||
help="Bar description.",
|
||||
),
|
||||
CommandArg(
|
||||
name_or_flags="--baz",
|
||||
help="Baz description.",
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
def handle(self, name: str, *args, **kwargs):
|
||||
shout = kwargs.get("shout", False)
|
||||
msg = f"Hello, {name}!"
|
||||
if shout:
|
||||
msg = msg.upper()
|
||||
print(msg)
|
||||
```
|
||||
|
||||
### Subcommands
|
||||
|
||||
Extensions can define subcommands, allowing for more complex command structures.
|
||||
|
||||
Subcommands are defined similarly to root commands, as subclasses of
|
||||
[`ComponentCommand`](../../../reference/api#django_components.ComponentCommand) class.
|
||||
|
||||
However, instead of defining the subcommands in the
|
||||
[`commands`](../../../reference/api#django_components.ComponentExtension.commands)
|
||||
attribute of the extension, you define them in the
|
||||
[`subcommands`](../../../reference/api#django_components.ComponentCommand.subcommands)
|
||||
attribute of the parent command:
|
||||
|
||||
```python
|
||||
from django_components import CommandArg, CommandArgGroup, ComponentCommand, ComponentsExtension
|
||||
|
||||
class ChildCommand(ComponentCommand):
|
||||
name = "child"
|
||||
help = "Child command"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
print("Child command")
|
||||
|
||||
class ParentCommand(ComponentCommand):
|
||||
name = "parent"
|
||||
help = "Parent command"
|
||||
subcommands = [
|
||||
ChildCommand,
|
||||
]
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
print("Parent command")
|
||||
```
|
||||
|
||||
In this example, we can run two commands.
|
||||
|
||||
Either the parent command:
|
||||
|
||||
```bash
|
||||
python manage.py components ext run parent
|
||||
>>> Parent command
|
||||
```
|
||||
|
||||
Or the child command:
|
||||
|
||||
```bash
|
||||
python manage.py components ext run parent child
|
||||
>>> Child command
|
||||
```
|
||||
|
||||
!!! warning
|
||||
|
||||
Subcommands are independent of the parent command. When a subcommand runs, the parent command is NOT executed.
|
||||
|
||||
As such, if you want to pass arguments to both the parent and child commands, e.g.:
|
||||
|
||||
```bash
|
||||
python manage.py components ext run parent --foo child --bar
|
||||
```
|
||||
|
||||
You should instead pass all the arguments to the subcommand:
|
||||
|
||||
```bash
|
||||
python manage.py components ext run parent child --foo --bar
|
||||
```
|
||||
|
||||
### Print command help
|
||||
|
||||
By default, all commands will print their help message when run with the `--help` / `-h` flag.
|
||||
|
||||
```bash
|
||||
python manage.py components ext run my_ext --help
|
||||
```
|
||||
|
||||
The help message prints out all the arguments and options available for the command, as well as any subcommands.
|
||||
|
||||
### Testing Commands
|
||||
|
||||
Commands can be tested using Django's [`call_command()`](https://docs.djangoproject.com/en/5.1/ref/django-admin/#running-management-commands-from-your-code)
|
||||
function, which allows you to simulate running the command in tests.
|
||||
|
||||
```python
|
||||
from django.core.management import call_command
|
||||
|
||||
call_command('components', 'ext', 'run', 'my_ext', 'hello', '--name', 'John')
|
||||
```
|
||||
|
||||
To capture the output of the command, you can use the [`StringIO`](https://docs.python.org/3/library/io.html#io.StringIO)
|
||||
module to redirect the output to a string:
|
||||
|
||||
```python
|
||||
from io import StringIO
|
||||
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command('components', 'ext', 'run', 'my_ext', 'hello', '--name', 'John')
|
||||
output = out.getvalue()
|
||||
```
|
||||
|
||||
And to temporarily set the extensions, you can use the [`@djc_test`](../../../reference/testing_api#djc_test) decorator.
|
||||
|
||||
Thus, a full test example can then look like this:
|
||||
|
||||
```python
|
||||
from io import StringIO
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.core.management import call_command
|
||||
from django_components.testing import djc_test
|
||||
|
||||
@djc_test(
|
||||
components_settings={
|
||||
"extensions": [
|
||||
"my_app.extensions.MyExtension",
|
||||
],
|
||||
},
|
||||
)
|
||||
def test_hello_command(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command('components', 'ext', 'run', 'my_ext', 'hello', '--name', 'John')
|
||||
output = out.getvalue()
|
||||
assert output == "Hello, John!\n"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -7,6 +7,30 @@
|
|||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.CommandArg
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.CommandArgGroup
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.CommandHandler
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.CommandLiteralAction
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.CommandParserInput
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.CommandSubcommand
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.Component
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
|
@ -39,6 +63,10 @@
|
|||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.ComponentCommand
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.ComponentsSettings
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
|
|
|||
|
|
@ -6,21 +6,361 @@ These are all the [Django management commands](https://docs.djangoproject.com/en
|
|||
that will be added by installing `django_components`:
|
||||
|
||||
|
||||
## `upgradecomponent`
|
||||
## ` components`
|
||||
|
||||
```txt
|
||||
usage: manage.py upgradecomponent [-h] [--path PATH] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
|
||||
[--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks]
|
||||
usage: python manage.py components [-h] {create,upgrade,ext} ...
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/management/commands/upgradecomponent.py#L12" target="_blank">See source code</a>
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/commands/components.py#L9" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
Updates component and component_block tags to the new syntax
|
||||
The entrypoint for the 'components' commands.
|
||||
|
||||
**Options:**
|
||||
|
||||
- `-h`, `--help`
|
||||
- show this help message and exit
|
||||
|
||||
**Subcommands:**
|
||||
|
||||
- [`create`](../commands#components-`create`)
|
||||
- Create a new django component.
|
||||
- [`upgrade`](../commands#components-`upgrade`)
|
||||
- Upgrade django components syntax from '{% component_block ... %}' to '{% component ... %}'.
|
||||
- [`ext`](../commands#components-`ext`)
|
||||
- Run extension commands.
|
||||
|
||||
|
||||
|
||||
|
||||
The entrypoint for the "components" commands.
|
||||
|
||||
```bash
|
||||
python manage.py components start <name>
|
||||
python manage.py components upgrade <name>
|
||||
python manage.py components ext list
|
||||
python manage.py components ext run <extension> <command>
|
||||
```
|
||||
|
||||
|
||||
## `components create`
|
||||
|
||||
```txt
|
||||
usage: python manage.py components create [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose] [--dry-run] name
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/commands/create.py#L11" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
Create a new django component.
|
||||
|
||||
**Positional Arguments:**
|
||||
|
||||
- `name`
|
||||
- The name of the component to create. This is a required argument.
|
||||
|
||||
**Options:**
|
||||
|
||||
- `-h`, `--help`
|
||||
- show this help message and exit
|
||||
- `--path PATH`
|
||||
- The path to the component's directory. This is an optional argument. If not provided, the command will use the `COMPONENTS.dirs` setting from your Django settings.
|
||||
- `--js JS`
|
||||
- The name of the JavaScript file. This is an optional argument. The default value is `script.js`.
|
||||
- `--css CSS`
|
||||
- The name of the CSS file. This is an optional argument. The default value is `style.css`.
|
||||
- `--template TEMPLATE`
|
||||
- The name of the template file. This is an optional argument. The default value is `template.html`.
|
||||
- `--force`
|
||||
- This option allows you to overwrite existing files if they exist. This is an optional argument.
|
||||
- `--verbose`
|
||||
- This option allows the command to print additional information during component creation. This is an optional argument.
|
||||
- `--dry-run`
|
||||
- This option allows you to simulate component creation without actually creating any files. This is an optional argument. The default value is `False`.
|
||||
|
||||
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
To use the command, run the following command in your terminal:
|
||||
|
||||
```bash
|
||||
python manage.py components create <name> --path <path> --js <js_filename> --css <css_filename> --template <template_filename> --force --verbose --dry-run
|
||||
```
|
||||
|
||||
Replace `<name>`, `<path>`, `<js_filename>`, `<css_filename>`, and `<template_filename>` with your desired values.
|
||||
|
||||
### Examples
|
||||
|
||||
Here are some examples of how you can use the command:
|
||||
|
||||
**Creating a Component with Default Settings**
|
||||
|
||||
To create a component with the default settings, you only need to provide the name of the component:
|
||||
|
||||
```bash
|
||||
python manage.py components create my_component
|
||||
```
|
||||
|
||||
This will create a new component named `my_component` in the `components` directory of your Django project. The JavaScript, CSS, and template files will be named `script.js`, `style.css`, and `template.html`, respectively.
|
||||
|
||||
**Creating a Component with Custom Settings**
|
||||
|
||||
You can also create a component with custom settings by providing additional arguments:
|
||||
|
||||
```bash
|
||||
python manage.py components create new_component --path my_components --js my_script.js --css my_style.css --template my_template.html
|
||||
```
|
||||
|
||||
This will create a new component named `new_component` in the `my_components` directory. The JavaScript, CSS, and template files will be named `my_script.js`, `my_style.css`, and `my_template.html`, respectively.
|
||||
|
||||
**Overwriting an Existing Component**
|
||||
|
||||
If you want to overwrite an existing component, you can use the `--force` option:
|
||||
|
||||
```bash
|
||||
python manage.py components create my_component --force
|
||||
```
|
||||
|
||||
This will overwrite the existing `my_component` if it exists.
|
||||
|
||||
**Simulating Component Creation**
|
||||
|
||||
If you want to simulate the creation of a component without actually creating any files, you can use the `--dry-run` option:
|
||||
|
||||
```bash
|
||||
python manage.py components create my_component --dry-run
|
||||
```
|
||||
|
||||
This will simulate the creation of `my_component` without creating any files.
|
||||
|
||||
|
||||
## `components upgrade`
|
||||
|
||||
```txt
|
||||
usage: python manage.py components upgrade [-h] [--path PATH]
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/commands/upgrade.py#L13" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
Upgrade django components syntax from '{% component_block ... %}' to '{% component ... %}'.
|
||||
|
||||
**Options:**
|
||||
|
||||
- `-h`, `--help`
|
||||
- show this help message and exit
|
||||
- `--path PATH`
|
||||
- Path to search for components
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## `components ext`
|
||||
|
||||
```txt
|
||||
usage: python manage.py components ext [-h] {list,run} ...
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/commands/ext.py#L5" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
Run extension commands.
|
||||
|
||||
**Options:**
|
||||
|
||||
- `-h`, `--help`
|
||||
- show this help message and exit
|
||||
|
||||
**Subcommands:**
|
||||
|
||||
- [`list`](../commands#components-ext-`list`)
|
||||
- List all extensions.
|
||||
- [`run`](../commands#components-ext-`run`)
|
||||
- Run a command added by an extension.
|
||||
|
||||
|
||||
|
||||
|
||||
Run extension commands.
|
||||
|
||||
```bash
|
||||
python manage.py components ext list
|
||||
python manage.py components ext run <extension> <command>
|
||||
```
|
||||
|
||||
|
||||
## `components ext list`
|
||||
|
||||
```txt
|
||||
usage: python manage.py components ext list [-h] [-v {0,1}]
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/commands/ext_list.py#L6" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
List all extensions.
|
||||
|
||||
**Options:**
|
||||
|
||||
- `-h`, `--help`
|
||||
- show this help message and exit
|
||||
- `-v {0,1}`, `--verbosity {0,1}`
|
||||
- Verbosity level; 0=minimal output, 1=normal output
|
||||
|
||||
|
||||
|
||||
|
||||
List all extensions.
|
||||
|
||||
```bash
|
||||
python manage.py components ext list
|
||||
```
|
||||
|
||||
Prints the list of installed extensions:
|
||||
|
||||
```txt
|
||||
Installed extensions:
|
||||
view
|
||||
my_extension
|
||||
```
|
||||
|
||||
If you need to omit the title in order to programmatically post-process the output,
|
||||
you can use the `--verbosity` (or `-v`) flag:
|
||||
|
||||
```bash
|
||||
python manage.py components ext list -v 0
|
||||
```
|
||||
|
||||
Which prints just:
|
||||
|
||||
```txt
|
||||
view
|
||||
my_extension
|
||||
```
|
||||
|
||||
|
||||
## `components ext run`
|
||||
|
||||
```txt
|
||||
usage: python manage.py components ext run [-h]
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/commands/ext_run.py#L48" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
Run a command added by an extension.
|
||||
|
||||
**Options:**
|
||||
|
||||
- `-h`, `--help`
|
||||
- show this help message and exit
|
||||
|
||||
|
||||
|
||||
|
||||
Run a command added by an [extension](../../concepts/advanced/extensions).
|
||||
|
||||
Each extension can add its own commands, which will be available to run with this command.
|
||||
|
||||
For example, if you define and install the following extension:
|
||||
|
||||
```python
|
||||
from django_components ComponentCommand, ComponentsExtension
|
||||
|
||||
class HelloCommand(ComponentCommand):
|
||||
name = "hello"
|
||||
help = "Say hello"
|
||||
def handle(self, *args, **kwargs):
|
||||
print("Hello, world!")
|
||||
|
||||
class MyExt(ComponentsExtension):
|
||||
name = "my_ext"
|
||||
commands = [HelloCommand]
|
||||
```
|
||||
|
||||
You can run the `hello` command with:
|
||||
|
||||
```bash
|
||||
python manage.py components ext run my_ext hello
|
||||
```
|
||||
|
||||
You can also define arguments for the command, which will be passed to the command's `handle` method.
|
||||
|
||||
```python
|
||||
from django_components import CommandArg, ComponentCommand, ComponentsExtension
|
||||
|
||||
class HelloCommand(ComponentCommand):
|
||||
name = "hello"
|
||||
help = "Say hello"
|
||||
arguments = [
|
||||
CommandArg(name="name", help="The name to say hello to"),
|
||||
CommandArg(name=["--shout", "-s"], action="store_true"),
|
||||
]
|
||||
|
||||
def handle(self, name: str, *args, **kwargs):
|
||||
shout = kwargs.get("shout", False)
|
||||
msg = f"Hello, {name}!"
|
||||
if shout:
|
||||
msg = msg.upper()
|
||||
print(msg)
|
||||
```
|
||||
|
||||
You can run the command with:
|
||||
|
||||
```bash
|
||||
python manage.py components ext run my_ext hello --name John --shout
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Command arguments and options are based on Python's `argparse` module.
|
||||
|
||||
For more information, see the [argparse documentation](https://docs.python.org/3/library/argparse.html).
|
||||
|
||||
|
||||
## `upgradecomponent`
|
||||
|
||||
```txt
|
||||
usage: upgradecomponent [-h] [--path PATH] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH]
|
||||
[--traceback] [--no-color] [--force-color] [--skip-checks]
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/management/commands/upgradecomponent.py#L83" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
Deprecated. Use `components upgrade` instead.
|
||||
|
||||
**Options:**
|
||||
|
||||
|
|
@ -48,24 +388,26 @@ Updates component and component_block tags to the new syntax
|
|||
|
||||
|
||||
|
||||
**Deprecated**. Use [`components upgrade`](../commands#components-upgrade) instead.
|
||||
|
||||
|
||||
## `startcomponent`
|
||||
|
||||
```txt
|
||||
usage: manage.py startcomponent [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose]
|
||||
[--dry-run] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH]
|
||||
[--traceback] [--no-color] [--force-color] [--skip-checks]
|
||||
name
|
||||
usage: startcomponent [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose] [--dry-run]
|
||||
[--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback]
|
||||
[--no-color] [--force-color] [--skip-checks]
|
||||
name
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/management/commands/startcomponent.py#L8" target="_blank">See source code</a>
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/management/commands/startcomponent.py#L83" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
Create a new django component.
|
||||
Deprecated. Use `components create` instead.
|
||||
|
||||
**Positional Arguments:**
|
||||
|
||||
|
|
@ -110,58 +452,6 @@ Create a new django component.
|
|||
|
||||
|
||||
|
||||
### Management Command Usage
|
||||
|
||||
To use the command, run the following command in your terminal:
|
||||
|
||||
```bash
|
||||
python manage.py startcomponent <name> --path <path> --js <js_filename> --css <css_filename> --template <template_filename> --force --verbose --dry-run
|
||||
```
|
||||
|
||||
Replace `<name>`, `<path>`, `<js_filename>`, `<css_filename>`, and `<template_filename>` with your desired values.
|
||||
|
||||
### Management Command Examples
|
||||
|
||||
Here are some examples of how you can use the command:
|
||||
|
||||
#### Creating a Component with Default Settings
|
||||
|
||||
To create a component with the default settings, you only need to provide the name of the component:
|
||||
|
||||
```bash
|
||||
python manage.py startcomponent my_component
|
||||
```
|
||||
|
||||
This will create a new component named `my_component` in the `components` directory of your Django project. The JavaScript, CSS, and template files will be named `script.js`, `style.css`, and `template.html`, respectively.
|
||||
|
||||
#### Creating a Component with Custom Settings
|
||||
|
||||
You can also create a component with custom settings by providing additional arguments:
|
||||
|
||||
```bash
|
||||
python manage.py startcomponent new_component --path my_components --js my_script.js --css my_style.css --template my_template.html
|
||||
```
|
||||
|
||||
This will create a new component named `new_component` in the `my_components` directory. The JavaScript, CSS, and template files will be named `my_script.js`, `my_style.css`, and `my_template.html`, respectively.
|
||||
|
||||
#### Overwriting an Existing Component
|
||||
|
||||
If you want to overwrite an existing component, you can use the `--force` option:
|
||||
|
||||
```bash
|
||||
python manage.py startcomponent my_component --force
|
||||
```
|
||||
|
||||
This will overwrite the existing `my_component` if it exists.
|
||||
|
||||
#### Simulating Component Creation
|
||||
|
||||
If you want to simulate the creation of a component without actually creating any files, you can use the `--dry-run` option:
|
||||
|
||||
```bash
|
||||
python manage.py startcomponent my_component --dry-run
|
||||
```
|
||||
|
||||
This will simulate the creation of `my_component` without creating any files.
|
||||
**Deprecated**. Use [`components create`](../commands#components-create) instead.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ If you insert this tag multiple times, ALL JS scripts will be duplicately insert
|
|||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1568" target="_blank">See source code</a>
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1573" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,14 +42,15 @@ from argparse import ArgumentParser
|
|||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Type, Union
|
||||
from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Tuple, Type, Union
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.urls import URLPattern, URLResolver
|
||||
|
||||
from django_components import ComponentVars, TagFormatterABC
|
||||
from django_components.component import Component
|
||||
from django_components import Component, ComponentVars, ComponentCommand, TagFormatterABC
|
||||
from django_components.commands.components import ComponentsRootCommand
|
||||
from django_components.node import BaseNode
|
||||
from django_components.util.command import setup_parser_from_command
|
||||
from django_components.util.misc import get_import_path
|
||||
|
||||
# NOTE: This file is an entrypoint for the `gen-files` plugin in `mkdocs.yml`.
|
||||
|
|
@ -462,13 +463,6 @@ def gen_reference_commands():
|
|||
|
||||
These are discovered by looking at the files defined inside `management/commands`.
|
||||
"""
|
||||
command_files = Path("./src/django_components/management/commands").glob("*.py")
|
||||
command_modules = [
|
||||
(p.stem, f"django_components.management.commands.{p.stem}")
|
||||
for p in command_files
|
||||
if not p.stem.startswith("_")
|
||||
]
|
||||
|
||||
preface = "<!-- Autogenerated by reference.py -->\n\n"
|
||||
preface += (root / "docs/templates/reference_commands.md").read_text()
|
||||
out_file = root / "docs/reference/commands.md"
|
||||
|
|
@ -477,13 +471,72 @@ def gen_reference_commands():
|
|||
with out_file.open("w", encoding="utf-8") as f:
|
||||
f.write(preface + "\n\n")
|
||||
|
||||
for cmd_name, cmd_path in command_modules:
|
||||
cmd_module = import_module(cmd_path)
|
||||
# Document all commands defined by django-components
|
||||
# All our commands are scoped under `components` (e.g. `components create`, `components upgrade`, etc.)
|
||||
# Furthermore, all subcommands are declared statically, so we can walk down the tree of subcommands.
|
||||
commands_stack: List[Tuple[Type[ComponentCommand], Tuple[str, ...]]] = [(ComponentsRootCommand, ())]
|
||||
while commands_stack:
|
||||
cmd_def_cls, cmd_path = commands_stack.pop()
|
||||
# NOTE: Argparse formats the help string, and so it uses `%%` to escape `%` characters.
|
||||
# So we need to replace them with `%`
|
||||
cmd_summary = cmd_def_cls.help.replace("%%", "%") if cmd_def_cls.help else ""
|
||||
cmd_desc = dedent(cmd_def_cls.__doc__ or "")
|
||||
cmd_name = " ".join(cmd_path) + " " + cmd_def_cls.name
|
||||
|
||||
cmd_parser = setup_parser_from_command(cmd_def_cls)
|
||||
cmd_usage = cmd_parser.format_usage()
|
||||
# NOTE: The generated usage shows only the command name, not the full path.
|
||||
# So we need to add it manually.
|
||||
#
|
||||
# So this:
|
||||
# `usage: ext run [-h]`
|
||||
#
|
||||
# becomes this:
|
||||
# `usage: python manage.py components ext run [-h]`
|
||||
cmd_usage = cmd_usage[:7] + "python manage.py " + " ".join(cmd_path) + " " + cmd_usage[7:]
|
||||
formatted_args = _format_command_args(cmd_parser, cmd_path + (cmd_def_cls.name,))
|
||||
|
||||
# Add link to source code
|
||||
module_abs_path = import_module(cmd_def_cls.__module__).__file__
|
||||
module_rel_path = Path(module_abs_path).relative_to(Path.cwd()).as_posix() # type: ignore[arg-type]
|
||||
obj_lineno = inspect.findsource(cmd_def_cls)[1]
|
||||
source_code_link = _format_source_code_html(module_rel_path, obj_lineno)
|
||||
|
||||
# NOTE: For the commands we have to generate the markdown entries ourselves,
|
||||
# instead of delegating to mkdocs, for two reasons:
|
||||
# 1. All commands have to use the class name `Command` for Django to pick them up
|
||||
# 2. The command name is actually defined by the file name.
|
||||
f.write(
|
||||
f"## `{cmd_name}`\n\n"
|
||||
f"```txt\n{cmd_usage}\n```\n\n"
|
||||
f"{source_code_link}\n\n"
|
||||
f"{cmd_summary}\n\n"
|
||||
f"{formatted_args}\n\n"
|
||||
f"{cmd_desc}\n\n"
|
||||
)
|
||||
|
||||
# Add subcommands
|
||||
for subcmd_cls in reversed(cmd_def_cls.subcommands):
|
||||
commands_stack.append((subcmd_cls, cmd_path + (cmd_def_cls.name,)))
|
||||
|
||||
# TODO_v1 - REMOVE - This this section as it only for legacy commands `startcomponent` and `upgradecomponent`
|
||||
command_files = Path("./src/django_components/management/commands").glob("*.py")
|
||||
command_modules = [
|
||||
(p.stem, f"django_components.management.commands.{p.stem}")
|
||||
for p in command_files
|
||||
if not p.stem.startswith("_")
|
||||
]
|
||||
for cmd_name, cmd_import_path in command_modules:
|
||||
# NOTE: `components` command is already documented in the non-legacy section
|
||||
if cmd_name == "components":
|
||||
continue
|
||||
|
||||
cmd_module = import_module(cmd_import_path)
|
||||
cmd_cls: BaseCommand = cmd_module.Command
|
||||
cmd_summary = cmd_cls.help
|
||||
cmd_desc = dedent(cmd_cls.__doc__ or "")
|
||||
cmd_parser: ArgumentParser = cmd_cls().create_parser("manage.py", cmd_name)
|
||||
cmd_usage: str = cmd_parser.format_usage()
|
||||
cmd_parser = cmd_cls().create_parser("manage.py", cmd_name)
|
||||
cmd_usage = cmd_parser.format_usage()
|
||||
formatted_args = _format_command_args(cmd_parser)
|
||||
|
||||
# Add link to source code
|
||||
|
|
@ -850,6 +903,36 @@ def _gen_command_args(parser: ArgumentParser) -> str:
|
|||
# {'desc': "Show program's version number and exit.",
|
||||
# ```
|
||||
def _parse_command_args(cmd_inputs: str) -> Dict[str, List[Dict]]:
|
||||
# Replace
|
||||
# ```
|
||||
# subcommands:
|
||||
# {create,upgrade,ext}
|
||||
# create Create a new django component.
|
||||
# upgrade Upgrade django components syntax from ...
|
||||
# ext Run extension commands.
|
||||
# ```
|
||||
#
|
||||
# Into:
|
||||
# ```
|
||||
# subcommands:
|
||||
# create Create a new django component.
|
||||
# upgrade Upgrade django components syntax from ...
|
||||
# ext Run extension commands.
|
||||
# ```
|
||||
if "subcommands:" in cmd_inputs:
|
||||
cmd_inputs = re.compile(r"subcommands:\n.*?\}", re.DOTALL).sub("subcommands:", cmd_inputs)
|
||||
|
||||
# Dedent the lines that contain subcommands from 4 spaces to 2 spaces
|
||||
text_before_subcommands, text_after_subcommands = cmd_inputs.split("subcommands:\n")
|
||||
lines_after_subcommands = text_after_subcommands.split("\n")
|
||||
new_text_after_subcommands = ""
|
||||
for line in lines_after_subcommands:
|
||||
if line.startswith(" " * 4):
|
||||
new_text_after_subcommands += line[2:] + "\n"
|
||||
else:
|
||||
new_text_after_subcommands += line + "\n"
|
||||
cmd_inputs = text_before_subcommands + "subcommands:\n" + new_text_after_subcommands
|
||||
|
||||
section: Optional[str] = None
|
||||
data: Dict[str, List[Dict]] = {}
|
||||
|
||||
|
|
@ -893,7 +976,7 @@ def _parse_command_args(cmd_inputs: str) -> Dict[str, List[Dict]]:
|
|||
return data
|
||||
|
||||
|
||||
def _format_command_args(cmd_parser: ArgumentParser):
|
||||
def _format_command_args(cmd_parser: ArgumentParser, cmd_path: Optional[Sequence[str]] = None):
|
||||
cmd_inputs: str = _gen_command_args(cmd_parser)
|
||||
parsed_cmd_inputs = _parse_command_args(cmd_inputs)
|
||||
|
||||
|
|
@ -901,9 +984,15 @@ def _format_command_args(cmd_parser: ArgumentParser):
|
|||
for section, args in parsed_cmd_inputs.items():
|
||||
formatted_args += f"**{section.title()}:**\n\n"
|
||||
for arg in args:
|
||||
formatted_args += (
|
||||
"- " + ", ".join([f"`{name}`" for name in arg["names"]]) + f"\n - {arg['desc']}" + "\n"
|
||||
)
|
||||
# Add link to the subcommand
|
||||
if section == "subcommands":
|
||||
name = "`" + arg["names"][0] + "`"
|
||||
if cmd_path:
|
||||
name = "[" + name + "](../commands#" + "-".join(cmd_path) + "-" + name + ")"
|
||||
else:
|
||||
name = ", ".join([f"`{name}`" for name in arg["names"]])
|
||||
|
||||
formatted_args += "- " + name + f"\n - {arg['desc']}" + "\n"
|
||||
formatted_args += "\n"
|
||||
|
||||
return formatted_args
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue