diff --git a/blurry/__init__.py b/blurry/__init__.py
index d2cf1ef..cccf180 100644
--- a/blurry/__init__.py
+++ b/blurry/__init__.py
@@ -25,6 +25,8 @@
from blurry.async_typer import AsyncTyper
from blurry.cli import print_blurry_name
from blurry.cli import print_plugin_table
+from blurry.commands.clean import clean_build_directory
+from blurry.commands.init import initialize_new_project
from blurry.constants import ENV_VAR_PREFIX
from blurry.images import generate_images_for_srcset
from blurry.markdown import convert_markdown_file_to_html
@@ -229,15 +231,13 @@ def gather_file_data_by_directory() -> DirectoryFileData:
@app.command(name="clean")
-def clean_build_directory():
- """Removes the build directory for a clean build."""
- update_settings()
- build_directory = get_build_directory()
+def clean_command():
+ clean_build_directory()
- try:
- shutil.rmtree(build_directory)
- except FileNotFoundError:
- pass
+
+@app.command(name="init")
+def init_command(name: str | None = None, domain: str | None = None):
+ initialize_new_project(name, domain)
@app.async_command()
@@ -328,10 +328,6 @@ def on_non_markdown_file_processed(future: concurrent.futures.Future):
print(f"Built site in {difference.total_seconds()} seconds")
-async def build_development():
- await build(release=False)
-
-
async def reload_local_plugins_and_build():
"""Reloads a project's local modules and performs a dev build"""
cwd = str(Path.cwd())
@@ -343,7 +339,7 @@ async def reload_local_plugins_and_build():
continue
importlib.reload(module)
- await build_development()
+ await build(release=False)
@app.command()
@@ -358,7 +354,7 @@ def runserver():
SETTINGS["RUNSERVER"] = True
event_loop = asyncio.get_event_loop()
- event_loop.create_task(build_development())
+ event_loop.create_task(build(release=False))
jinja_env = get_jinja_env()
@@ -392,7 +388,7 @@ def handle_changed_markdown_files(filepaths: list[str]):
)
livereload_server.watch(
f"{SETTINGS['CONTENT_DIRECTORY_NAME']}/**/*",
- lambda: event_loop.create_task(build_development()),
+ lambda: event_loop.create_task(build(release=False)),
ignore=lambda filepath: any(
[
filepath.endswith(".md"),
@@ -403,7 +399,7 @@ def handle_changed_markdown_files(filepaths: list[str]):
)
livereload_server.watch(
f"{SETTINGS['TEMPLATES_DIRECTORY_NAME']}/**/*",
- lambda: event_loop.create_task(build_development()),
+ lambda: event_loop.create_task(build(release=False)),
ignore=lambda filepath: Path(filepath).is_dir(),
)
livereload_server.watch(
@@ -411,7 +407,7 @@ def handle_changed_markdown_files(filepaths: list[str]):
lambda: event_loop.create_task(reload_local_plugins_and_build()),
)
livereload_server.watch(
- "blurry.toml", lambda: event_loop.create_task(build_development())
+ "blurry.toml", lambda: event_loop.create_task(build(release=False))
)
livereload_server.serve(
host=SETTINGS["DEV_HOST"], port=SETTINGS["DEV_PORT"], root=get_build_directory()
diff --git a/blurry/commands/__init__.py b/blurry/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/blurry/commands/clean.py b/blurry/commands/clean.py
new file mode 100644
index 0000000..5de9153
--- /dev/null
+++ b/blurry/commands/clean.py
@@ -0,0 +1,19 @@
+import shutil
+
+from rich import print
+
+from blurry.settings import get_build_directory
+from blurry.settings import update_settings
+
+
+def clean_build_directory():
+ """Removes the build directory for a clean build."""
+ update_settings()
+ build_directory = get_build_directory()
+
+ try:
+ shutil.rmtree(build_directory)
+ except FileNotFoundError:
+ pass
+
+ print(f"Cleaned build directory: {build_directory}")
diff --git a/blurry/commands/init.py b/blurry/commands/init.py
new file mode 100644
index 0000000..8f1a4e6
--- /dev/null
+++ b/blurry/commands/init.py
@@ -0,0 +1,135 @@
+import tomllib
+from datetime import datetime
+from pathlib import Path
+
+from rich.prompt import Prompt
+
+from blurry.settings import get_content_directory
+from blurry.settings import get_templates_directory
+from blurry.settings import update_settings
+
+
+BLURRY_CONFIG_TEMPLATE = """
+[blurry]
+domain = "{domain}"
+markdown_file_jinja_template_extension = ".jinja"
+
+[blurry.schema_data.sourceOrganization]
+name = "{project_name}"
+url = "https://{domain}"
+""".strip()
+
+HOMEPAGE_MARKDOWN = """
++++
+"@type" = "WebSite"
+name = "Home"
+abstract = "The homepage of {project_name}, built with Blurry."
+datePublished = {date_published}
++++
+
+# Welcome to your new Blurry site
+
+To learn more about how to get started, check out Blurry's quick start docs:
+
+
+""".strip()
+
+BASE_TEMPLATE = """
+
+
+
+
+
+
+
+ {% block title %}{% endblock %} | {{ sourceOrganization.name }}
+
+
+ {{ open_graph_tags|safe }}
+ {{ schema_type_tag|safe }}
+
+
+
+
+
+ {% block body %}{% endblock %}
+
+
+
+"""
+
+
+WEBSITE_TEMPLATE = """
+{% extends "base.jinja" %}
+
+{% block title %}{{ name }}{% endblock %}
+{% block description %}{{ abstract }}{% endblock %}
+
+{% block body %}
+
+ {{ body|safe }}
+
+{% endblock %}
+"""
+
+
+def initialize_new_project(name: str | None, domain: str | None):
+ update_settings()
+ blurry_config_file = Path("blurry.toml")
+ blurry_template_directory = get_templates_directory()
+ blurry_content_directory = get_content_directory()
+
+ if (
+ blurry_config_file.exists()
+ or blurry_template_directory.exists()
+ or blurry_content_directory.exists()
+ ):
+ print("Blurry project already initialized.")
+ return
+ name = name or Prompt.ask(
+ "What is the name of your company, project, or website?",
+ )
+ domain = domain or Prompt.ask("What is your website's domain?")
+
+ config_text = BLURRY_CONFIG_TEMPLATE.format(domain=domain, project_name=name)
+
+ try:
+ tomllib.loads(config_text)
+ except tomllib.TOMLDecodeError as e:
+ print(f"Error in configuration file: {e}.")
+ print("Please check your input and try again.")
+ exit(1)
+
+ blurry_config_file.write_text(config_text)
+
+ # Write template files
+ blurry_template_directory.mkdir(exist_ok=True)
+ base_template_file = blurry_template_directory / "base.jinja"
+ base_template_file.write_text(BASE_TEMPLATE)
+
+ website_template_file = blurry_template_directory / "WebSite.jinja"
+ website_template_file.write_text(WEBSITE_TEMPLATE)
+
+ # Write homepage Markdown file
+ date_published = datetime.now().strftime("%Y-%m-%d")
+ blurry_content_directory.mkdir(exist_ok=True)
+ homepage = blurry_content_directory / "index.md"
+ homepage.write_text(
+ HOMEPAGE_MARKDOWN.format(
+ date_published=date_published,
+ project_name=name,
+ )
+ )
+
+ print("Blurry project initialized!")
+ print("Run 'blurry runserver' to start the dev server.")
diff --git a/docs/content/commands/build.md b/docs/content/commands/build.md
index 9720f3d..fcf3baa 100644
--- a/docs/content/commands/build.md
+++ b/docs/content/commands/build.md
@@ -3,12 +3,12 @@
name = "Commands: build"
abstract = "Documentation for Blurry's build command"
datePublished = 2023-04-09
-dateModified = 2024-01-03
+dateModified = 2025-01-07
+++
# Commands: build
-## Usage
+## Description
`build` builds a production-ready version of a Blurry static site.
It outputs the site in the folder specified by the `build_directory_name` [setting](./../configuration/settings.md), which defaults to `./dist/`
@@ -101,6 +101,26 @@ dist
18 directories, 19 files
```
+## Usage
+
+```shell
+$ blurry build
+. .
+|-.| . ..-..-.. .
+`-''-'-'' ' '-|
+ `-'
+┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
+┃ Markdown Plugins ┃ HTML Plugins ┃ Jinja Plugins ┃
+┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
+│ container │ minify_html │ body_to_cards │
+│ punctuation │ │ headings │
+│ python_code │ │ url_path │
+│ python_code_in_list │ │ blurry_image │
+└─────────────────────┴──────────────┴───────────────┘
+Blurring 22 Markdown files and 6 other files
+Built site in 2.576354 seconds
+```
+
## Options
`clean`: cleans the build directory before building.
diff --git a/docs/content/commands/init.md b/docs/content/commands/init.md
new file mode 100644
index 0000000..367c027
--- /dev/null
+++ b/docs/content/commands/init.md
@@ -0,0 +1,43 @@
++++
+"@type" = "WebPage"
+name = "Commands: init"
+abstract = "Documentation for Blurry's build command"
+datePublished = 2025-01-07
++++
+
+# Commands: init
+
+## Description
+
+The `init` command creates a new Blurry project in the current directory.
+It creates everything you need to start working on your site:
+
+- A `blurry.toml` [configuration file](../configuration/blurry.toml.md)
+- A [Markdown file](../content/markdown.md) for your site's homepage in `content/index.md`
+- A base [template file](../templates/syntax.md) and a `WebSite` template file for your homepage in `templates/`
+
+## Usage
+
+Example:
+
+```shell
+$ blurry init
+What is the name of your company, project, or website?: Blurry
+What is your website's domain?: blurry-dev.netlify.app
+Blurry project initialized!
+Run 'blurry runserver' to start the dev server.
+```
+
+## Options
+
+`name`: Your project's name
+
+`domain`: Your project's domain
+
+Example:
+
+```shell
+$ blurry init --name "Blurry" --domain "blurry-docs.netlify.app"
+Blurry project initialized!
+Run 'blurry runserver' to start the dev server.
+```
diff --git a/docs/poetry.lock b/docs/poetry.lock
index 9b6ed11..3277128 100644
--- a/docs/poetry.lock
+++ b/docs/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "annotated-types"
@@ -33,7 +33,7 @@ pydantic2-schemaorg = "^0.2.0"
PyLD = "^2.0.3"
rich = "^13.3.3"
selectolax = "^0.3.27"
-typer = "^0.6.1"
+typer = "^0.15.1"
Wand = "^0.6.6"
[package.source]
@@ -654,13 +654,13 @@ pydantic = ">=2.9.2,<3.0.0"
[[package]]
name = "pygments"
-version = "2.18.0"
+version = "2.19.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
files = [
- {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
- {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
+ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
+ {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
]
[package.extras]
@@ -784,6 +784,17 @@ files = [
[package.extras]
cython = ["Cython (==3.0.11)"]
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+description = "Tool to Detect Surrounding Shell"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
+ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
+]
+
[[package]]
name = "tornado"
version = "6.4.2"
@@ -806,23 +817,20 @@ files = [
[[package]]
name = "typer"
-version = "0.6.1"
+version = "0.15.1"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
files = [
- {file = "typer-0.6.1-py3-none-any.whl", hash = "sha256:54b19e5df18654070a82f8c2aa1da456a4ac16a2a83e6dcd9f170e291c56338e"},
- {file = "typer-0.6.1.tar.gz", hash = "sha256:2d5720a5e63f73eaf31edaa15f6ab87f35f0690f8ca233017d7d23d743a91d73"},
+ {file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"},
+ {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"},
]
[package.dependencies]
-click = ">=7.1.1,<9.0.0"
-
-[package.extras]
-all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
-dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"]
-doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)"]
-test = ["black (>=22.3.0,<23.0.0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
+click = ">=8.0.0"
+rich = ">=10.11.0"
+shellingham = ">=1.3.0"
+typing-extensions = ">=3.7.4.3"
[[package]]
name = "typing-extensions"
diff --git a/docs/templates/WebPage.html.jinja b/docs/templates/WebPage.html.jinja
index 4682342..d8f1502 100644
--- a/docs/templates/WebPage.html.jinja
+++ b/docs/templates/WebPage.html.jinja
@@ -35,8 +35,10 @@
Commands
+ init
build
runserver
+ clean
diff --git a/poetry.lock b/poetry.lock
index ac6d349..698a419 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1182,6 +1182,17 @@ files = [
[package.extras]
cython = ["Cython (==3.0.11)"]
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+description = "Tool to Detect Surrounding Shell"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
+ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
+]
+
[[package]]
name = "toml"
version = "0.10.2"
@@ -1215,23 +1226,20 @@ files = [
[[package]]
name = "typer"
-version = "0.6.1"
+version = "0.15.1"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
files = [
- {file = "typer-0.6.1-py3-none-any.whl", hash = "sha256:54b19e5df18654070a82f8c2aa1da456a4ac16a2a83e6dcd9f170e291c56338e"},
- {file = "typer-0.6.1.tar.gz", hash = "sha256:2d5720a5e63f73eaf31edaa15f6ab87f35f0690f8ca233017d7d23d743a91d73"},
+ {file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"},
+ {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"},
]
[package.dependencies]
-click = ">=7.1.1,<9.0.0"
-
-[package.extras]
-all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
-dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"]
-doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)"]
-test = ["black (>=22.3.0,<23.0.0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
+click = ">=8.0.0"
+rich = ">=10.11.0"
+shellingham = ">=1.3.0"
+typing-extensions = ">=3.7.4.3"
[[package]]
name = "typing-extensions"
@@ -1324,4 +1332,4 @@ watchmedo = ["PyYAML (>=3.10)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "6b9769c7c71be685c87333cf72d998b3ecb16205e79dc91fcdf94015ea54d254"
+content-hash = "6f442b6993816e3148089f5730b8439ce307e5d14a6ec0cf4b868efbed13442e"
diff --git a/pyproject.toml b/pyproject.toml
index 5eb0062..7ba77e2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,7 +32,7 @@ mistune = "^3.0.10"
python = "^3.11"
rich = "^13.3.3"
selectolax = "^0.3.27"
-typer = "^0.6.1"
+typer = "^0.15.1"
htmlmin2 = "^0.1.13"
pydantic2-schemaorg = "^0.2.0"
dpath = "^2.1.6"