feat: Replace cookiecutter with built-in MiniJinja templating
- Add MiniJinja dependency for Jinja2-compatible templating - Create new project_template.rs module for template rendering - Bundle snowflake template in templates/snowflake/ directory - Remove dependency on external cookiecutter Python tool - Handle Gitea publishing natively in Rust instead of Python hooks - Update default template path to use bundled template with fallback - Update README to reflect new built-in templating - All tests pass Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# A Novel Outlining Cookiecutter
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"project_name": "Novel",
|
||||
"project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_') }}",
|
||||
"author": "Lane Wight",
|
||||
"description": "A short desciption of the project."
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# create_snowflake_structure.sh
|
||||
# -------------------------------------------------
|
||||
# Creates a ready‑to‑use folder layout for the Snowflake
|
||||
# writing outline method.
|
||||
#
|
||||
# Usage:
|
||||
# ./create_snowflake_structure.sh "MyNovel"
|
||||
#
|
||||
# If no argument is supplied, the script defaults to a folder
|
||||
# named "SnowflakeProject".
|
||||
# -------------------------------------------------
|
||||
|
||||
set -euo pipefail # safety: exit on error, treat unset vars as errors
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Helper: print a short usage message
|
||||
# ------------------------------------------------------------------
|
||||
usage() {
|
||||
echo "Usage: $0 [project_folder_name]"
|
||||
echo "Creates the Snowflake writing‑method folder structure."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Determine the root folder name
|
||||
# ------------------------------------------------------------------
|
||||
PROJECT_NAME="${1:-SnowflakeProject}"
|
||||
ROOT_DIR="${PWD}/${PROJECT_NAME}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Define the directory tree (relative to ROOT_DIR)
|
||||
# ------------------------------------------------------------------
|
||||
DIRS=(
|
||||
"01-Premise"
|
||||
"02-Synopsis"
|
||||
"03-Characters/main_characters"
|
||||
"03-Characters/supporting_characters"
|
||||
"03-Characters/character_grid"
|
||||
"04-PlotOutline"
|
||||
"04-PlotOutline/scene_breakdown"
|
||||
"05-NarrativeSheets/act_1"
|
||||
"05-NarrativeSheets/act_2"
|
||||
"05-NarrativeSheets/act_3"
|
||||
"06-FirstDraft"
|
||||
"07-Revisions/structural_edits"
|
||||
"07-Revisions/line_edits"
|
||||
"07-Revisions/proofreading"
|
||||
"08-Final/metadata"
|
||||
"08-Final/submission_materials"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Create the directories
|
||||
# ------------------------------------------------------------------
|
||||
echo "Creating project folder: ${ROOT_DIR}"
|
||||
mkdir -p "${ROOT_DIR}"
|
||||
|
||||
for d in "${DIRS[@]}"; do
|
||||
mkdir -p "${ROOT_DIR}/${d}"
|
||||
done
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Optional: create placeholder files so you have a starting point
|
||||
# ------------------------------------------------------------------
|
||||
touch "${ROOT_DIR}/01-Premise/premise.txt"
|
||||
touch "${ROOT_DIR}/02-Synopsis/synopsis.txt"
|
||||
touch "${ROOT_DIR}/03-Characters/main_characters/protagonist.md"
|
||||
touch "${ROOT_DIR}/03-Characters/main_characters/antagonist.md"
|
||||
touch "${ROOT_DIR}/03-Characters/character_grid/character_grid.xlsx"
|
||||
touch "${ROOT_DIR}/04-PlotOutline/plot_outline.docx"
|
||||
touch "${ROOT_DIR}/05-NarrativeSheets/act_1/scene_01.md"
|
||||
touch "${ROOT_DIR}/06-FirstDraft/draft_v1.docx"
|
||||
touch "${ROOT_DIR}/07-Revisions/structural_edits/notes.txt"
|
||||
touch "${ROOT_DIR}/08-Final/final_manuscript.docx"
|
||||
touch "${ROOT_DIR}/README.md"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Finished
|
||||
# ------------------------------------------------------------------
|
||||
echo "Folder structure created successfully!"
|
||||
echo "Root directory: ${ROOT_DIR}"
|
||||
echo "You can now start populating the files."
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Post-generation hook: optionally publish the new project to Gitea.
|
||||
|
||||
Cookiecutter renders this file as a Jinja template before running it, so a
|
||||
doubled opening brace anywhere outside a cookiecutter variable is read as the
|
||||
start of a Jinja expression and breaks the render. The JSON body below is
|
||||
therefore built with ``json.dumps`` rather than written out with literal braces.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# This one comes from cookiecutter.json.
|
||||
PROJECT_NAME = "{{cookiecutter.project_name}}"
|
||||
|
||||
GITEA_URL = os.environ.get("GITEA_URL") # e.g. https://gitea.example.com
|
||||
GITEA_USER = os.environ.get("GITEA_USER")
|
||||
GITEA_TOKEN = os.environ.get("GITEA_TOKEN")
|
||||
|
||||
|
||||
def run(cmd, **kwargs):
|
||||
"""Run a command given as an argument list, raising if it fails."""
|
||||
return subprocess.run(cmd, check=True, **kwargs)
|
||||
|
||||
|
||||
def create_gitea_repo():
|
||||
# 1. Create the repo via the Gitea API. `--fail` makes curl exit non-zero on
|
||||
# an HTTP error, so a bad token surfaces here instead of silently
|
||||
# "succeeding" and leaving a push to fail later.
|
||||
api_url = f"{GITEA_URL.rstrip('/')}/api/v1/user/repos"
|
||||
payload = json.dumps({"name": PROJECT_NAME, "private": False, "auto_init": False})
|
||||
run([
|
||||
"curl", "-sS", "--fail", "-X", "POST", api_url,
|
||||
"-H", "Content-Type: application/json",
|
||||
"-H", f"Authorization: token {GITEA_TOKEN}",
|
||||
"-d", payload,
|
||||
])
|
||||
|
||||
# 2. Init git and push. `-b main` matters because the push below names
|
||||
# `main`, while git's built-in default is still `master`.
|
||||
repo_url = f"{GITEA_URL.rstrip('/')}/{GITEA_USER}/{PROJECT_NAME}.git"
|
||||
auth_url = repo_url.replace("://", f"://{GITEA_USER}:{GITEA_TOKEN}@", 1)
|
||||
|
||||
run(["git", "init", "-b", "main"])
|
||||
run(["git", "add", "."])
|
||||
run(["git", "commit", "-m", "Initial commit from cookiecutter"])
|
||||
# The stored remote keeps the plain URL: embedding the token would write it
|
||||
# in cleartext into .git/config for the life of the project. It is passed to
|
||||
# this one push only.
|
||||
run(["git", "remote", "add", "origin", repo_url])
|
||||
run(["git", "push", auth_url, "main"])
|
||||
run(["git", "fetch", "origin"])
|
||||
run(["git", "branch", "--set-upstream-to=origin/main", "main"])
|
||||
print(f"✅ Repo created and pushed: {repo_url}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
missing = [
|
||||
name
|
||||
for name, value in (
|
||||
("GITEA_URL", GITEA_URL),
|
||||
("GITEA_USER", GITEA_USER),
|
||||
("GITEA_TOKEN", GITEA_TOKEN),
|
||||
)
|
||||
if not value
|
||||
]
|
||||
if missing:
|
||||
# Publishing is opt-in. Without credentials the project is still
|
||||
# perfectly good on disk, so skip rather than fail the generation and
|
||||
# have cookiecutter throw the whole tree away.
|
||||
print(f"ℹ Skipping Gitea publish; not set: {', '.join(missing)}")
|
||||
sys.exit(0)
|
||||
try:
|
||||
create_gitea_repo()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,3 @@
|
||||
# Logline
|
||||
|
||||
<!-- Your one‑sentence pitch. -->
|
||||
@@ -0,0 +1,3 @@
|
||||
# Synopsis
|
||||
|
||||
<!-- The synopsis expanded to a paragraph (≈5 sentences). -->
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<!-- A quick‑look table summarizing all characters (goal, motivation, stakes, etc.) -->
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Age</th>
|
||||
<th>Description</th>
|
||||
<th>Motivation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>John Doe</td>
|
||||
<td>30</td>
|
||||
<td>Average Joe</td>
|
||||
<td>Just wants a cheeseburger.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Name
|
||||
|
||||
### Slug
|
||||
|
||||
Age:
|
||||
|
||||
Description:
|
||||
|
||||
Motivation:
|
||||
|
||||
Single Quote:
|
||||
|
||||
### Summary:
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Name
|
||||
|
||||
### Slug
|
||||
|
||||
Age:
|
||||
|
||||
Description:
|
||||
|
||||
Motivation:
|
||||
|
||||
Single Quote:
|
||||
|
||||
### Summary:
|
||||
@@ -0,0 +1,35 @@
|
||||
<!-- The expanded outline (≈4 pages). -->
|
||||
|
||||
## Act I – Setup (≈ 25 % of the story)
|
||||
|
||||
<!-- Opening Image / Hook: A vivid snapshot of the protagonist’s ordinary world. Sets tone, setting, and the status‑quo the hero will soon leave. -->
|
||||
|
||||
<!-- Inciting Incident: An event that disrupts the status‑quo and forces the protagonist to confront a problem. Creates the central conflict and gives the hero a reason to act. -->
|
||||
|
||||
<!-- Key Event / Call to Adventure: The protagonist becomes aware of the stakes and is presented with a clear choice or challenge. Pushes the hero toward the journey; the point of no return begins to form. -->
|
||||
|
||||
<!-- Debate / Refusal of the Call: The hero hesitates, doubts, or resists the upcoming change. Highlights internal conflict and makes the eventual commitment feel earned. | -->
|
||||
|
||||
<!-- Lock‑In / End of Act I: The hero decides to take action (or is forced into it). The story’s direction is now set. Marks the transition into the main confrontation; the protagonist is now “in the game.” -->
|
||||
|
||||
## Act II – Confrontation (≈ 50 % of the story)
|
||||
|
||||
<!-- First Plot Point: A decisive event propels the hero into a new world or situation (often a “point of no return”). Raises the stakes and moves the narrative firmly into the central conflict. -->
|
||||
|
||||
<!-- Rising Action / Obstacles: Series of challenges, setbacks, and revelations that test the hero’s resolve. Builds tension, deepens character development, and expands the story’s scope. -->
|
||||
|
||||
<!-- Midpoint: A major turning point—often a revelation, victory, or crisis—that changes the hero’s understanding or strategy. Re‑energizes the plot; the hero’s goal may shift or become clearer. -->
|
||||
|
||||
<!-- Complications & Darkest Moment: Stakes intensify; the hero may suffer a major loss or face apparent defeat. Heightens emotional investment and sets up the need for a final push. -->
|
||||
|
||||
<!-- Second Plot Point: The hero discovers a crucial piece of information or makes a decisive plan that leads to the climax. Provides the catalyst for the ultimate confrontation in Act III. -->
|
||||
|
||||
## Act III – Resolution (≈ 25 % of the story)
|
||||
|
||||
<!-- Climax: The highest‑stakes confrontation where the hero faces the central conflict head‑on. Delivers the story’s primary payoff; the outcome resolves the main tension. -->
|
||||
|
||||
<!-- Falling Action: Immediate consequences of the climax unfold; loose ends begin to tie up. Shows the impact of the climax on characters and world. -->
|
||||
|
||||
<!-- Denouement / Resolution: Remaining subplots are resolved; the protagonist’s new equilibrium is shown. Gives closure, illustrating how the hero (and world) have changed. -->
|
||||
|
||||
<!-- Final Image: A closing snapshot that mirrors (or contrasts) the opening image, highlighting transformation. Reinforces the thematic arc and leaves a lasting impression. -->
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 697 KiB |
+11
@@ -0,0 +1,11 @@
|
||||
## Act I – Setup (≈ 25 % of the story)
|
||||
|
||||
<!-- Opening Image / Hook: A vivid snapshot of the protagonist’s ordinary world. Sets tone, setting, and the status‑quo the hero will soon leave. -->
|
||||
|
||||
<!-- Inciting Incident: An event that disrupts the status‑quo and forces the protagonist to confront a problem. Creates the central conflict and gives the hero a reason to act. -->
|
||||
|
||||
<!-- Key Event / Call to Adventure: The protagonist becomes aware of the stakes and is presented with a clear choice or challenge. Pushes the hero toward the journey; the point of no return begins to form. -->
|
||||
|
||||
<!-- Debate / Refusal of the Call: The hero hesitates, doubts, or resists the upcoming change. Highlights internal conflict and makes the eventual commitment feel earned. | -->
|
||||
|
||||
<!-- Lock‑In / End of Act I: The hero decides to take action (or is forced into it). The story’s direction is now set. Marks the transition into the main confrontation; the protagonist is now “in the game.” -->
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
## Act II – Confrontation (≈ 50 % of the story)
|
||||
|
||||
<!-- First Plot Point: A decisive event propels the hero into a new world or situation (often a “point of no return”). Raises the stakes and moves the narrative firmly into the central conflict. -->
|
||||
|
||||
<!-- Rising Action / Obstacles: Series of challenges, setbacks, and revelations that test the hero’s resolve. Builds tension, deepens character development, and expands the story’s scope. -->
|
||||
|
||||
<!-- Midpoint: A major turning point—often a revelation, victory, or crisis—that changes the hero’s understanding or strategy. Re‑energizes the plot; the hero’s goal may shift or become clearer. -->
|
||||
|
||||
<!-- Complications & Darkest Moment: Stakes intensify; the hero may suffer a major loss or face apparent defeat. Heightens emotional investment and sets up the need for a final push. -->
|
||||
|
||||
<!-- Second Plot Point: The hero discovers a crucial piece of information or makes a decisive plan that leads to the climax. Provides the catalyst for the ultimate confrontation in Act III. -->
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
## Act III – Resolution (≈ 25 % of the story)
|
||||
|
||||
<!-- Climax: The highest‑stakes confrontation where the hero faces the central conflict head‑on. Delivers the story’s primary payoff; the outcome resolves the main tension. -->
|
||||
|
||||
<!-- Falling Action: Immediate consequences of the climax unfold; loose ends begin to tie up. Shows the impact of the climax on characters and world. -->
|
||||
|
||||
<!-- Denouement / Resolution: Remaining subplots are resolved; the protagonist’s new equilibrium is shown. Gives closure, illustrating how the hero (and world) have changed. -->
|
||||
|
||||
<!-- Final Image: A closing snapshot that mirrors (or contrasts) the opening image, highlighting transformation. Reinforces the thematic arc and leaves a lasting impression. -->
|
||||
@@ -0,0 +1,23 @@
|
||||
# Title
|
||||
|
||||
# Slug
|
||||
|
||||
## POV:
|
||||
|
||||
## Characters:
|
||||
|
||||
## Setting:
|
||||
|
||||
## Conflict:
|
||||
|
||||
## Feeling to convey:
|
||||
|
||||
## Word Count Target:
|
||||
|
||||
## The Setup:
|
||||
|
||||
## The Reveal:
|
||||
|
||||
### Things to Note:
|
||||
|
||||
### Rough draft:
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
# {{ cookiecutter.project_name }}
|
||||
|
||||
{{ cookiecutter.description }}
|
||||
|
||||
This repository was bootstrapped with a Cookiecutter template that also provides a helper script (`post_gen_project.sh`) to initialise a local Git repository.
|
||||
Reference in New Issue
Block a user