Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,40 @@
History
=======

2.6 (unreleased)
----------------
7.0a5 (unreleased)
------------------

- Nothing changed yet.


7.0a4 (2025-08-20)
------------------

- add --non-interactive parameter
[MrTango]


7.0a3 (2025-08-20)
------------------

- fix tmplate registry
[MrTango]


7.0a2 (2025-05-30)
------------------

- fix lookup of bobtemplates-plone templates
[MrTango]


7.0a1 (2025-05-30)
------------------

- bump bobtemplates.plone dependency to 7.0.0a1
[MrTango]


2.5 (2022-11-03)
----------------

Expand Down
115 changes: 115 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Architecture Overview

plonecli is a command-line interface for creating Plone packages. It orchestrates three main components:

1. **mr.bob** - The low-level templating engine that handles file generation and Q&A sessions
2. **bobtemplates.plone** - Collection of Plone-specific templates (addon, content_type, theme, etc.)
3. **plonecli** - High-level CLI wrapper providing developer workflow commands

### Key Components

#### Template Registry (`plonecli/registry.py`)
- **Template Discovery**: Uses `pkg_resources.iter_entry_points("mrbob_templates")` to find available templates
- **Context Detection**: Detects if you're in a package via `bobtemplate.cfg` to show relevant commands
- **Template Organization**: Separates standalone templates (addon) from subtemplates (content_type, theme, etc.)
- **Template Resolution**: Maps friendly CLI names to actual mr.bob template paths

#### CLI Interface (`plonecli/cli.py`)
- **Context-Sensitive Commands**: Different commands available globally vs. in package directories
- **Command Chaining**: Supports multiple commands in sequence (e.g., `plonecli create addon my.package build serve`)
- **Click Integration**: Uses `ClickFilteredAliasedGroup` for context-aware command filtering

#### mr.bob Configuration (`plonecli/configure_mrbob.py`)
- **Global Settings**: Manages user preferences in `~/.mrbob`
- **Template Defaults**: Pre-fills common values (author, email, Plone version)
- **Hooks System**: Pre/post question and render hooks for template customization

### Template Integration Flow

1. Templates register via Python entry points in `setup.py`:
```python
entry_points={
'mrbob_templates': [
'plone_addon = bobtemplates.plone.bobregistry:plone_addon',
'plone_content_type = bobtemplates.plone.bobregistry:plone_content_type',
],
}
```

2. Each entry point returns a `RegEntry` object with:
- `template`: actual mr.bob template path
- `plonecli_alias`: CLI-friendly name
- `depend_on`: parent template (None for standalone, 'plone_addon' for subtemplates)
- `deprecated`: deprecation flag
- `info`: additional information

3. Registry builds hierarchical template map and filters based on current context

## Common Development Commands

### Setup Development Environment
```bash
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
./venv/bin/pip install -e .[dev,test]
```

### Running Tests
```bash
# Run all tests
tox

# Run tests for specific Python version
tox -e py39

# Run tests directly with pytest
py.test tests/

# Run specific test
py.test tests/ -k test_get_package_root
```

### Code Quality
```bash
# Run linting
tox -e lint-py39

# Apply import sorting
tox -e isort-apply

# Apply code formatting
tox -e autopep8
```

### Testing Template Changes
When modifying template discovery or registry behavior, test with:
```bash
# List available templates
plonecli -l

# Test template creation in tmp directory
cd tmp/
plonecli create addon test.package
cd test.package
plonecli add content_type
```

## Configuration Files

- `setup.py`: Dependencies include `mr.bob` and `bobtemplates.plone>=7.0.0a2`
- `tox.ini`: Comprehensive testing across Python 3.7-3.10 with coverage reporting
- `setup.cfg`: flake8, isort, and metadata configuration
- `.mrbob.ini`: mr.bob template configuration for the configure_mrbob command

## Context-Aware Behavior

The CLI behaves differently based on current directory:

- **Global Context**: Only `create` and `config` commands available
- **Package Context**: All commands available except `create` (detected via `bobtemplate.cfg`)

This is implemented in `ClickFilteredAliasedGroup.list_commands()` using `reg.root_folder` detection.
Empty file added README.md
Empty file.
13 changes: 13 additions & 0 deletions _pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# [project]
# name = "plonecli"
# version = "0.1.0"
# description = "Add your description here"
# readme = "README.md"
# requires-python = ">=3.12"
# dependencies = [
# "bobtemplates.plone>=7.0.0a4",
# "Click>=7.0",
# "click-aliases",
# "mr.bob",
# "tox",
# ]
22 changes: 18 additions & 4 deletions plonecli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def cli(context, list_templates, versions):
click.echo(reg.list_templates())
if versions:
ws = WorkingSet()
bobtemplates_dist = ws.by_key["bobtemplates.plone"]
bobtemplates_dist = ws.by_key["bobtemplates-plone"]
bobtemplates_version = bobtemplates_dist.version
plonecli_version = ws.by_key["plonecli"].version
version_str = """Available packages:\n
Expand All @@ -79,8 +79,13 @@ def cli(context, list_templates, versions):
default=None,
help="mrbob configuration file. The default is ~/.mrbob.",
)
@click.option(
"--non-interactive",
is_flag=True,
help="Run in non-interactive mode using default answers.",
)
@click.pass_context
def create(context, template, name, bobconfig):
def create(context, template, name, bobconfig, non_interactive):
"""Create a new Plone package"""
bobtemplate = reg.resolve_template_name(template)
if bobtemplate is None:
Expand All @@ -93,9 +98,11 @@ def create(context, template, name, bobconfig):
mrbob_args = [bobtemplate, "-O", name]
if bobconfig is not None:
mrbob_args.extend(["-c", bobconfig])
if non_interactive:
mrbob_args.append("-n")

echo(
"\nRUN: {0}".format(" ".join(mrbob_args)),
"\nRUN: mrbob {0}".format(" ".join(mrbob_args)),
fg="green",
reverse=True,
)
Expand All @@ -110,8 +117,13 @@ def create(context, template, name, bobconfig):
default=None,
help="mrbob configuration file. The default is ~/.mrbob.",
)
@click.option(
"--non-interactive",
is_flag=True,
help="Run in non-interactive mode using default answers.",
)
@click.pass_context
def add(context, template, bobconfig):
def add(context, template, bobconfig, non_interactive):
"""Add features to your existing Plone package"""
if context.obj.get("target_dir", None) is None:
raise NotInPackageError(context.command.name)
Expand All @@ -123,6 +135,8 @@ def add(context, template, bobconfig):
mrbob_args = [bobtemplate]
if bobconfig is not None:
mrbob_args.extend(["-c", bobconfig])
if non_interactive:
mrbob_args.append("-n")

echo("\nRUN: mrbob {0}".format(" ".join(mrbob_args)), fg="green", reverse=True)
mrbobmain(mrbob_args)
Expand Down
29 changes: 23 additions & 6 deletions plonecli/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ def __init__(self, cur_dir=None):
for entry_point in pkg_resources.iter_entry_points("mrbob_templates"):
template_info_method = entry_point.load()
self.template_infos[entry_point.name] = template_info_method()

for entry_point_name, tmpl_info in self.template_infos.items():
if tmpl_info.depend_on:
continue
Expand All @@ -94,14 +93,18 @@ def __init__(self, cur_dir=None):
if not tmpl_info.depend_on:
continue
if tmpl_info.depend_on not in self.templates:
# Create virtual parent template if it doesn't exist
self.templates[tmpl_info.depend_on] = {
"template_name": tmpl_info.depend_on.replace('plone_', ''),
"subtemplates": {},
"info": "Virtual template for subtemplates",
"deprecated": False,
}
print(
"{",
'Template dependency "{0}" not found!'.format(
"Created virtual template '{0}' for subtemplates".format(
tmpl_info.depend_on,
),
"}",
)
continue
self.templates[tmpl_info.depend_on]["subtemplates"][entry_point_name] = (
tmpl_info.plonecli_alias or entry_point_name
)
Expand Down Expand Up @@ -147,4 +150,18 @@ def resolve_template_name(self, plonecli_alias):
return template_name


template_registry = TemplateRegistry()
# Lazy initialization to ensure correct working directory
_template_registry = None

def get_template_registry():
global _template_registry
if _template_registry is None:
_template_registry = TemplateRegistry()
return _template_registry

# For backward compatibility
class LazyTemplateRegistry:
def __getattr__(self, name):
return getattr(get_template_registry(), name)

template_registry = LazyTemplateRegistry()
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = plonecli
version = 2.6.dev0
version = 7.0a5.dev0
description = A Plone CLI for creating Plone packages
long_description = file: README.rst, CHANGES.rst
keywords = plone cli bobtemplates mrbob zopeskel development
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"Click>=7.0",
"click-aliases",
"mr.bob",
"bobtemplates.plone>=6.0b15",
"bobtemplates.plone>=7.0.0a4",
"tox",
],
extras_require={
Expand Down
Loading
Loading