EmilStenstrom / django-components

Create simple reusable template components in Django.
MIT License
973 stars 62 forks source link

Rename component_block to compnent (Fixes #232) #376

Closed EmilStenstrom closed 3 months ago

EmilStenstrom commented 3 months ago

This PR removes the component tag without an end tag, and renames the component_block tag to just component.

This is a breaking change, and one that I've been hesitant to make, because it requires code changes for everyone using this library:

After the first step I had problems with lines growing longer, which broke some of the configured linters. I've now standardized the entire project on 119 line length, and reformatted the entire project to follow that standard. It was a pain in the butt to get everything synced, but I think we're now fine.

Reasons to make this breaking change now:

Looking forward for reviews of this. I've tried to differentiate things into different commits to make it slightly easier to go through... But this is a major rewrite which touches almost all files in the entire library, so if it's hard to review I totally understand.

EmilStenstrom commented 3 months ago

@dylanjcastillo Mind taking a look?

dylanjcastillo commented 3 months ago

For sure. Will try to take a look by Friday!

dylanjcastillo commented 3 months ago

@EmilStenstrom It looks good to me. I just have a couple of comments/questions:

  1. Shouldn't we add a warning in the README about this breaking change? Otherwise, it might be a bit frustrating to understand what's going on if you update the library.
  2. Would it make sense to have and document a simple management command people can use to try to automatically make these changes? I wrote one that seems to work well for django-htmx-components, in case you want to use it or make a more generalized version of it:
    
    import os
    import re

from pathlib import Path

from django.conf import settings from django.core.management.base import BaseCommand from django.template.engine import Engine

from django_components.template_loader import Loader

class Command(BaseCommand): help = "Updates component and component_block tags to the new syntax"

def add_arguments(self, parser):
    parser.add_argument("--path", type=str, help="Path to search for components")

def handle(self, *args, **options):
    current_engine = Engine.get_default()
    loader = Loader(current_engine)
    dirs = loader.get_dirs() 

    if settings.BASE_DIR:
        dirs.append(Path(settings.BASE_DIR) / "templates")

    if options["path"]:
        dirs = [options["path"]]

    for dir_path in dirs:
        self.stdout.write(f"Searching for components in {dir_path}...")
        for root, _, files in os.walk(dir_path):
            for file in files:
                if file.endswith(".html") or file.endswith(".py"):
                    file_path = os.path.join(root, file)
                with open(file_path, "r+", encoding="utf-8") as f:
                        content = f.read()
                        content_with_closed_components, step0_count = re.subn(
                            r'({%\s*component\s*"(\w+?)"(.*?)%})(?!.*?{%\s*endcomponent\s*%})',
                            r'\1{% endcomponent %}',
                            content,
                            flags=re.DOTALL
                        )
                        updated_content, step1_count_opening = re.subn(
                            r'{%\s*component_block\s*"(\w+?)"\s*(.*?)%}',
                            r'{% component "\1" \2%}',
                            content_with_closed_components,
                            flags=re.DOTALL
                        )
                        updated_content, step2_count_closing = re.subn(
                            r'{%\s*endcomponent_block\s*"(\w+?)"\s*%}',
                            r'{% endcomponent %}',
                            updated_content,
                            flags=re.DOTALL
                        )
                        updated_content, step2_count_closing_no_name = re.subn(
                            r'{%\s*endcomponent_block\s*%}',
                            r'{% endcomponent %}',
                            updated_content,
                            flags=re.DOTALL
                        )
                        total_updates = step0_count + step1_count_opening + step2_count_closing + step2_count_closing_no_name
                        if total_updates > 0:
                            f.seek(0)
                            f.write(updated_content)
                            f.truncate()
                            self.stdout.write(f"Updated {file_path}: {total_updates} changes made")
EmilStenstrom commented 3 months ago

@dylanjcastillo README should definitely be added, don't know why I didn't include that in this PR. Will do.

About the script: I was thinking of recommending two regexes as part of the docs, but your code is even better. I'll add a modified version of that too. Thanks!

EmilStenstrom commented 3 months ago

@dylanjcastillo I've updated with your suggestions. Ok to merge?

dylanjcastillo commented 3 months ago

LGTM!

EmilStenstrom commented 3 months ago

And we're live! I released this as part of django_components==0.50. Big version jump to signify big change.